Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export default function App() {

<main>
<div className="shows-grid">
{/* TODO - shows.map((show)=>{ return <ShowCard id={show.id} .../> })*/}
{shows.map((show) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the name of the prop is the same as what you are sending you can spread the show object something like this :

{shows.map(show => (
  <ShowCard key={show.id} {...show} />
))}

Notice that I added the key prop, why is that?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i read we use key so react can uniquely identify each component in a list and preserve the correct state between renders and the key is not passed as a prop

return <ShowCard {...show} key={show.id} />;
})}
</div>
</main>
</div>
Expand Down
80 changes: 28 additions & 52 deletions src/components/showCard/ShowCard.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,48 @@
import { useState } from "react";

export interface ShowCardProps {
artist: string;
stage: string;
day: string;
hour: string;
ticketsLeft: number;
image: string;
}

export default function ShowCard(
/* TODO 1: Accept props with ShowCardProps type here */
) {
// TODO 2: Create state called isInterested of type boolean (default false)
// const [isInterested, setIsInterested] = ...

// TODO 3: Function that toggles true/false in isInterested
// function handleToggleInterested() {}

// TODO 4: Create ticketsStatusText (string):
// 0 → "SOLD OUT"
// 1–30 → "Last tickets – hurry up!"
// >30 → "Tickets available"

// TODO 5: Create text for interest status based on isInterested:
// false → "You haven't added this show yet"
// true → "This show is in your interested list 🎟️"
import { getArtistPrefix, getInterestedButtonText } from "../../utils/showCardUtils";
import {getInterestedStatusText,getTicketsStatusText} from "../../utils/showCardUtils";
import type { Show } from "../../interfaces/show";
import { useInterested } from "../../hooks/useInterested";

export default function ShowCard({
artist,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work destructing those props

stage,
day,
hour,
ticketsLeft,
image,
}: Show) {

const { isInterested, toggleInterested } = useInterested();

const ticketsStatusText = getTicketsStatusText(ticketsLeft);
const interestedText = getInterestedStatusText(isInterested);
const artistPrefix = getArtistPrefix(isInterested);
const buttonText = getInterestedButtonText(isInterested);

return (
<article className="show-card">
<div className="show-image-wrapper">
{/* TODO 6: Replace placeholder with real <img> from props */}
{/* <img src={image} alt={artist} className="show-image" /> */}
<div className="show-image-placeholder">Image goes here</div>
<img src={image} alt={artist} className="show-image" />
</div>

<header className="show-header">
<h2 className="show-artist">
{/* TODO 7: If isInterested → show ⭐ before artist name */}
"Artist name here"
{artistPrefix}
{artist}
</h2>
<p className="show-meta">
{/* TODO 8: Display stage, day, hour from props */}
"Stage · Day · Hour"
{stage} · {day} · {hour}
</p>
</header>

<section className="show-info">
<p className="tickets-status">
{/* TODO 9: Display the ticketsStatusText */}
"Tickets status text"
</p>
<p className="tickets-status">{ticketsStatusText}</p>

<p className="interested-status">
{/* TODO 10: Display the interestedText */}
"Interested status text"
</p>
<p className="interested-status">{interestedText}</p>
</section>

<button
className="interested-button"
// TODO 11: Connect onClick to handleToggleInterested function
>
{/* TODO 12: Button text:
false → "Mark as interested"
true → "Remove from my list"
*/}
"Button text"
<button className="interested-button" onClick={toggleInterested}>
{buttonText}
</button>
</article>
);
Expand Down
21 changes: 21 additions & 0 deletions src/constants/showCardConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const SHOW_CARD_CONSTANTS = {
SOLD_OUT: "SOLD OUT",
LAST_TICKETS: "Last tickets – hurry up!",
TICKETS_AVAILABLE: "Tickets available",
YOUR_INTERESTED_LIST: "This show is in your interested list 🎟️",
NO_ADDED_SHOW: "You haven't added this show yet",
} as const;

export const SHOW_CARD_LIMITS = {
SOLD_OUT: 0,
LAST_TICKETS_THRESHOLD: 30,
} as const;

export const SHOW_CARD_BUTTON_TEXT = {
REMOVE: "Remove from my list",
ADD: "Mark as interested",
} as const;

export const SHOW_CARD_UI = {
INTERESTED_ICON: "⭐ ",
} as const;
14 changes: 14 additions & 0 deletions src/hooks/useInterested.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useState } from "react";

export function useInterested() {
const [isInterested, setIsInterested] = useState<boolean>(false);

function toggleInterested() {
setIsInterested((prev) => !prev);
}

return {
isInterested,
toggleInterested,
};
}
8 changes: 8 additions & 0 deletions src/interfaces/show.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Show {
artist: string;
stage: string;
day: string;
hour: string;
ticketsLeft: number;
image: string;
}
33 changes: 33 additions & 0 deletions src/utils/showCardUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
SHOW_CARD_BUTTON_TEXT,
SHOW_CARD_CONSTANTS,
SHOW_CARD_LIMITS,
SHOW_CARD_UI,
} from "../constants/showCardConstants";

export function getTicketsStatusText(ticketsLeft: number): string {
if (ticketsLeft === SHOW_CARD_LIMITS.SOLD_OUT) {
return SHOW_CARD_CONSTANTS.SOLD_OUT;
}
if (ticketsLeft <= SHOW_CARD_LIMITS.LAST_TICKETS_THRESHOLD) {
return SHOW_CARD_CONSTANTS.LAST_TICKETS;
}
return SHOW_CARD_CONSTANTS.TICKETS_AVAILABLE;
}

export function getInterestedStatusText(isInterested: boolean): string {
if (isInterested) {
return SHOW_CARD_CONSTANTS.YOUR_INTERESTED_LIST;
}
return SHOW_CARD_CONSTANTS.NO_ADDED_SHOW;
}

export function getArtistPrefix(isInterested: boolean): string {
return isInterested ? SHOW_CARD_UI.INTERESTED_ICON : "";
}

export function getInterestedButtonText(isInterested: boolean): string {
return isInterested
? SHOW_CARD_BUTTON_TEXT.REMOVE
: SHOW_CARD_BUTTON_TEXT.ADD;
}