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
9 changes: 5 additions & 4 deletions templates/app/app-vite/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions templates/app/app-vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
"@eslint/js": "^9.0.0",
"@vitejs/plugin-react": "^4.2.1",
"concurrently": "^9.1.0",
"nodemon": "^3.1.0",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"globals": "^15.0.0",
"json-server": "^0.17.4",
"json-server-auth": "^2.1.0",
"prettier": "^3.2.5",
"nodemon": "^3.1.0",
"prettier": "^3.8.3",
"vite": "^5.2.0"
}
}
54 changes: 54 additions & 0 deletions templates/app/app-vite/src/components/EventCard/EventCard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
.event-card {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
max-width: 420px;
display: flex;
flex-direction: column;
gap: 12px;
}
.event-category {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #666;
}
.event-title {
margin: 0;
font-size: 18px;
}
.event-meta {
display: flex;
flex-direction: column;
gap: 4px;
color: #444;
font-size: 14px;
}
.event-meta p {
margin: 0;
}
.event-footer {
display: flex;
align-items: center;
gap: 12px;
margin-top: 4px;
}
.event-price {
font-weight: 700;
font-size: 16px;
}
.sold-out {
color: #c0392b;
font-size: 13px;
}
.available {
color: #27ae60;
font-size: 13px;
}
.event-link {
margin-left: auto;
font-size: 14px;
color: #0066cc;
text-decoration: none;
}
50 changes: 50 additions & 0 deletions templates/app/app-vite/src/components/EventCard/EventCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Link } from "react-router-dom";
import "./EventCard.css";


export default function EventCard({ event }) {
const {
id,
name,
date,
time,
venue,
city,
category,
price,
ticketsAvailable,
} = event;

return (
<article className="event-card">
<span className="event-category">{category}</span>

<h2 className="event-title">{name}</h2>

<div className="event-meta">
<p>
📅 {date} at {time}
</p>
<p>
📍 {venue}, {city}
</p>
</div>

<div className="event-footer">
<span className="event-price">
{price === 0 ? "Free" : `€${price}`}
</span>
<span
className={`event-availability ${ticketsAvailable === 0 ? "sold-out" : ""}`}
>
{ticketsAvailable === 0
? "Sold out"
: `${ticketsAvailable} tickets left`}
</span>
<Link to={`/events/${id}`} className="event-link">
View details →
</Link>
</div>
</article>
);
}
62 changes: 62 additions & 0 deletions templates/app/app-vite/src/components/EventDetail/EventDetail.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.event-detail {
max-width: 680px;
margin: 40px auto;
padding: 0 20px;
display: flex;
flex-direction: column;
gap: 24px;
}

.back-link {
font-size: 14px;
color: #0066cc;
text-decoration: none;
}

.event-detail-title {
margin: 0;
font-size: 28px;
}

.event-detail-meta {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
background: #f9f9f9;
border-radius: 8px;
padding: 20px;
}

.meta-item {
display: flex;
flex-direction: column;
gap: 4px;
}

.meta-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #999;
}

.meta-value {
font-size: 15px;
color: #222;
}

.event-description h2 {
font-size: 18px;
margin-bottom: 8px;
}
.event-description p {
line-height: 1.7;
color: #444;
}
.sold-out {
color: #c0392b;
}
.available {
color: #27ae60;
}
59 changes: 53 additions & 6 deletions templates/app/app-vite/src/components/EventDetail/EventDetail.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,58 @@
// TODO: display at least date, time, venue, city, and description for one event
// TODO: use useParams() to get the event id from the URL
// TODO: fetch the event from GET /events/:id instead of using mock data
import events from "../../data/events.js";
import { Link } from "react-router-dom";
import "./EventDetail.css";

export default function EventDetail() {
const event = events[0];
return (
<div>
<p>Event detail — coming soon.</p>
<div className="event-detail">
<Link to="/events" className="back-link">
← Back to all events
</Link>

<span className="event-category">{event.category}</span>

<h1 className="event-detail-title">{event.name}</h1>

<div className="event-detail-meta">
<div className="meta-item">
<span className="meta-label">Date</span>
<span className="meta-value">{event.date}</span>
</div>
<div className="meta-item">
<span className="meta-label">Time</span>
<span className="meta-value">{event.time}</span>
</div>
<div className="meta-item">
<span className="meta-label">Venue</span>
<span className="meta-value">{event.venue}</span>
</div>
<div className="meta-item">
<span className="meta-label">City</span>
<span className="meta-value">{event.city}</span>
</div>
<div className="meta-item">
<span className="meta-label">Price</span>
<span className="meta-value">
{event.price === 0 ? "Free" : `€${event.price}`}
</span>
</div>
<div className="meta-item">
<span className="meta-label">Availability</span>
<span
className={`meta-value ${event.ticketsAvailable === 0 ? "sold-out" : "available"}`}
>
{event.ticketsAvailable === 0
? "Sold out"
: `${ticketsAvailable} ticket${ticketsAvailable === 1 ? "" : "s"} left`}
</span>
</div>
</div>

<div className="event-description">
<h2>About this event</h2>
<p>{event.description}</p>
</div>
</div>
);
}
}
29 changes: 29 additions & 0 deletions templates/app/app-vite/src/components/EventList/EventList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.event-list-container {
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}

.event-list-container h1 {
text-align: center;
margin-bottom: 30px;
font-size: 32px;
color: #333;
}

.event-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 24px;
}

@media (max-width: 768px) {
.event-list-container {
padding: 20px 16px;
}

.event-list-container h1 {
font-size: 24px;
margin-bottom: 20px;
}
}
34 changes: 10 additions & 24 deletions templates/app/app-vite/src/components/EventList/EventList.jsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import events from "../../data/events.js";

// TODO: split each event below into its own EventCard component
// TODO: add a "Buy ticket" button to each event card
// TODO: replace the mock data import with a fetch call to GET /events
import EventCard from "../EventCard/EventCard.jsx";
import "./EventList.css";

export default function EventList() {
return (
<ul>
{events.map((event) => (
<li key={event.id}>
<h2>{event.name}</h2>
<p>
{event.date} at {event.time}
</p>
<p>
{event.venue}, {event.city}
</p>
<p>{event.category}</p>
<p>{event.price === 0 ? "Free" : `€${event.price}`}</p>
<p>
{event.ticketsAvailable === 0
? "Sold out"
: `${event.ticketsAvailable} tickets left`}
</p>
</li>
))}
</ul>
<div className="event-list-container">
<h1>Upcoming Events</h1>
<div className="event-list">
{events.map((event) => (
<EventCard key={event.id} event={event} />
))}
</div>
</div>
);
}
52 changes: 52 additions & 0 deletions templates/app/app-vite/src/components/HomePage/HomePage.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.hero {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 80px 24px;
gap: 20px;
}

.hero-badge {
background: #eef2ff;
color: #3730a3;
font-size: 13px;
font-weight: 600;
padding: 6px 14px;
border-radius: 20px;
letter-spacing: 0.03em;
}

.hero-title {
font-size: 48px;
font-weight: 700;
line-height: 1.15;
color: #111;
margin: 0;
max-width: 600px;
}

.hero-subtitle {
font-size: 18px;
color: #666;
max-width: 480px;
line-height: 1.6;
margin: 0;
}

.hero-cta {
display: inline-block;
background: #3730a3;
color: #fff;
text-decoration: none;
padding: 14px 32px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
margin-top: 8px;
transition: background 0.15s;
}

.hero-cta:hover {
background: #312e81;
}
Loading