Commit fdd3d972 by mvginghina

Initial commit

parents
Showing with 4839 additions and 0 deletions
[config]
command = npm run build && npm start
\ No newline at end of file
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# next.js
/.next/
/out/
# production
/build
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
\ No newline at end of file
# TickMeIn - Aplicație pentru Bilete la Evenimente
## Descriere
TickMeIn este o aplicație web pentru gestionarea biletelor la evenimente, similară cu Booking.com sau Airbnb, dar specializată pentru evenimente. Utilizatorii pot crea conturi, pot cumpăra bilete la evenimente și pot vedea descrierile acestora.
## Cerințe
### Backend
- Python 3.8+
- FastAPI
- SQLAlchemy
- MySQL
### Frontend
- Node.js 14+
- Next.js
- React
## Instalare
### Configurare Bază de Date
1. Asigură-te că ai MySQL instalat și rulând
2. Creează o bază de date numită `webapp`:
\`\`\`sql
CREATE DATABASE webapp;
\`\`\`
3. Importă schema bazei de date din fișierul `database_schema.sql`:
\`\`\`bash
mysql -u root -p webapp < database_schema.sql
\`\`\`
### Configurare Backend
1. Navighează în directorul backend:
\`\`\`bash
cd backend
\`\`\`
2. Instalează dependențele:
\`\`\`bash
pip install -r requirements.txt
\`\`\`
3. Configurează variabilele de mediu în fișierul `.env`:
\`\`\`
DATABASE_URL=mysql+pymysql://root:password@localhost/webapp
SECRET_KEY=your-secret-key
\`\`\`
4. Populează baza de date cu date de test (opțional):
\`\`\`bash
python seed_db.py
\`\`\`
5. Pornește serverul backend:
\`\`\`bash
python run.py
\`\`\`
### Configurare Frontend
1. Navighează în directorul principal:
\`\`\`bash
cd ..
\`\`\`
2. Instalează dependențele:
\`\`\`bash
npm install
\`\`\`
3. Pornește serverul de dezvoltare:
\`\`\`bash
npm run dev
\`\`\`
## Utilizare
1. Deschide un browser web și navighează la `http://localhost:3000`
2. Creează un cont nou sau autentifică-te cu un cont existent
3. Explorează evenimentele disponibile, cumpără bilete și urmărește evenimentele preferate
## Credențiale de Test
- **Admin**:
- Email: admin@example.com
- Parolă: admin
- **Utilizator**:
- Email: user1@example.com
- Parolă: password1
## Structura Proiectului
- `backend/` - Codul backend FastAPI
- `main.py` - Punctul de intrare al aplicației
- `models.py` - Modelele SQLAlchemy
- `schemas.py` - Schemele Pydantic
- `crud.py` - Operațiile CRUD
- `database.py` - Configurarea bazei de date
- `run.py` - Script pentru pornirea serverului
- `app/` - Codul frontend Next.js
- `page.tsx` - Pagina principală
- `auth/` - Paginile de autentificare și înregistrare
- `events/` - Paginile de evenimente
- `tickets/` - Paginile de bilete
- `components/` - Componentele React reutilizabile
- `app-header.tsx` - Header-ul aplicației
- `event-card.tsx` - Cardul de eveniment
- `menu-popup.tsx` - Meniul popup
- `api.js` - Funcțiile pentru apelurile API
- `config.js` - Configurația aplicației
## Licență
Acest proiect este licențiat sub licența MIT.
\`\`\`
Acum, să adăugăm un script pentru a verifica starea aplicației:
```python file="backend/check_app.py"
import requests
import sys
import time
def check_backend():
try:
response = requests.get("http://localhost:8000/health")
if response.status_code == 200:
print("✅ Backend-ul rulează corect.")
return True
else:
print(f"❌ Backend-ul a returnat un cod de stare neașteptat: {response.status_code}")
return False
except requests.exceptions.ConnectionError:
print("❌ Nu s-a putut conecta la backend. Asigură-te că serverul rulează pe portul 8000.")
return False
def check_frontend():
try:
response = requests.get("http://localhost:3000")
if response.status_code == 200:
print("✅ Frontend-ul rulează corect.")
return True
else:
print(f"❌ Frontend-ul a returnat un cod de stare neașteptat: {response.status_code}")
return False
except requests.exceptions.ConnectionError:
print("❌ Nu s-a putut conecta la frontend. Asigură-te că serverul rulează pe portul 3000.")
return False
def check_database_connection():
try:
from database import engine
from sqlalchemy import text
with engine.connect() as connection:
result = connection.execute(text("SELECT 1"))
print("✅ Conexiunea la baza de date funcționează corect.")
return True
except Exception as e:
print(f"❌ Eroare la conectarea la baza de date: {e}")
return False
def main():
print("Verificare stare aplicație TickMeIn...")
# Verifică backend-ul
backend_ok = check_backend()
# Verifică frontend-ul
frontend_ok = check_frontend()
# Verifică conexiunea la baza de date
db_ok = check_database_connection()
# Afișează rezumatul
print("\nRezumat:")
print(f"Backend: {'✅' if backend_ok else '❌'}")
print(f"Frontend: {'✅' if frontend_ok else '❌'}")
print(f"Bază de date: {'✅' if db_ok else '❌'}")
# Returnează codul de ieșire
if backend_ok and frontend_ok and db_ok:
print("\n✅ Aplicația rulează corect!")
return 0
else:
print("\n❌ Aplicația are probleme. Verifică erorile de mai sus.")
return 1
if __name__ == "__main__":
sys.exit(main())
# TickMeIn Application Startup Instructions
## Prerequisites
- Python 3.8 or higher
- Node.js 14 or higher
- MySQL server running
- A database named "webapp" created in MySQL
## Step 1: Start the Backend (FastAPI)
1. Open a terminal and navigate to the fastapi directory:
\`\`\`
cd fastapi
\`\`\`
2. Create a virtual environment (if not already created):
\`\`\`
python -m venv venv
\`\`\`
3. Activate the virtual environment:
- On Windows:
\`\`\`
venv\Scripts\activate
\`\`\`
- On macOS/Linux:
\`\`\`
source venv/bin/activate
\`\`\`
4. Install the required dependencies:
\`\`\`
pip install -r requirements.txt
\`\`\`
5. Make sure your MySQL server is running and the database "webapp" is created.
6. Start the FastAPI server:
\`\`\`
python run.py
\`\`\`
The backend should now be running at http://localhost:8000
## Step 2: Start the Frontend (Next.js)
1. Open a new terminal window and navigate to the project root directory:
\`\`\`
cd event-ticket-app
\`\`\`
2. Install the required dependencies:
\`\`\`
npm install
\`\`\`
3. Start the Next.js development server:
\`\`\`
npm run dev
\`\`\`
The frontend should now be running at http://localhost:3000
## Step 3: Access the Application
1. Open your web browser and go to http://localhost:3000
2. You should see the login page of the TickMeIn application
3. You can sign up for a new account or use the following test credentials:
- Email: admin@example.com
- Password: admin
## Troubleshooting
### Backend Issues
1. If you get a "ModuleNotFoundError", make sure you've installed all required packages:
\`\`\`
pip install PyJWT fastapi uvicorn sqlalchemy pymysql pydantic python-jose passlib python-multipart bcrypt python-dotenv
\`\`\`
2. If you have database connection issues, check:
- MySQL server is running
- Database "webapp" exists
- Credentials in `.env` file are correct
### Frontend Issues
1. If you get dependency errors, try:
\`\`\`
npm install --force
\`\`\`
2. If you have API connection issues, check:
- Backend server is running
- `config.js` has the correct API URL (should be http://localhost:8000)
## Database Structure
The application uses the following tables:
- Admin: Stores admin user information
- User: Stores regular user information
- Event: Stores event information
- Ticket: Stores ticket purchases
- Rating: Stores event ratings
## Important Notes
1. The backend uses JWT for authentication. Tokens are valid for 30 minutes.
2. Admin users can create, edit, and delete events.
3. Regular users can purchase tickets and rate events.
4. All data is stored in the MySQL database.
This diff is collapsed. Click to expand it.
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { Line, Bar } from "react-chartjs-2"
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarElement,
Title,
Tooltip,
Legend,
} from "chart.js"
import { AdminHeader } from "@/components/admin-header"
import { AdminSidebar } from "@/components/admin-sidebar"
import { getOverallAnalytics } from "@/api"
// Register required ChartJS components
ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, BarElement, Title, Tooltip, Legend)
export default function AnalyticsPage() {
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState("")
const [analytics, setAnalytics] = useState(null)
useEffect(() => {
// Check if user is authenticated and is admin
const isLoggedIn = localStorage.getItem("isLoggedIn")
const isAdmin = localStorage.getItem("isAdmin")
if (isLoggedIn !== "true" || isAdmin !== "true") {
router.push("/auth/signin")
return
}
// Load overall analytics
const loadAnalytics = async () => {
try {
setIsLoading(true)
const analyticsData = await getOverallAnalytics()
setAnalytics(analyticsData)
} catch (err) {
console.error("Error loading analytics:", err)
setError("Failed to load analytics data. Please try again.")
} finally {
setIsLoading(false)
}
}
loadAnalytics()
}, [router])
// Prepare data for charts
const ticketSalesData = {
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct"],
datasets: [
{
label: "Ticket Sales",
data: analytics?.ticketSales || [10, 25, 15, 30, 20, 40, 35, 25, 45, 30],
fill: false,
borderColor: "rgb(75, 192, 192)",
tension: 0.1,
},
],
}
const pageViewsData = {
labels: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
datasets: [
{
label: "Page Views",
data: analytics?.pageViews || [50, 20, 100, 40, 10, 80, 60],
backgroundColor: "rgb(0, 100, 0)",
},
],
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AdminHeader title="TickMeIn Admin" />
<div className="bg-olive-600 text-white py-4">
<div className="container">
<h1 className="text-xl font-bold">Analytics</h1>
</div>
</div>
<div className="flex flex-1">
<AdminSidebar />
<div className="flex-1 overflow-auto ml-[220px]">
<div className="container py-6">
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">{error}</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
<div>
<h3 className="text-xl font-bold mb-4">Ticket buy history</h3>
<div className="bg-white p-4 rounded-lg shadow">
<Line data={ticketSalesData} />
</div>
</div>
<div>
<h3 className="text-xl font-bold mb-4">Page activity</h3>
<div className="bg-white p-4 rounded-lg shadow">
<Bar data={pageViewsData} />
</div>
</div>
</div>
<div className="mb-8">
<h3 className="text-xl font-bold mb-4">Reviews</h3>
<div className="space-y-4">
{analytics?.reviews?.map((review, index) => (
<div key={index} className="bg-white p-4 rounded-lg shadow">
<div className="flex justify-between items-start mb-2">
<h4 className="font-semibold">{review.user?.Username || "Anonymous"}</h4>
<div className="flex">
{[...Array(5)].map((_, i) => (
<span
key={i}
className={`text-xl ${i < review.Stars || i < review.rating ? "text-yellow-400" : "text-gray-300"}`}
>
</span>
))}
</div>
</div>
<p>{review.Comment || review.comment}</p>
</div>
))}
{(!analytics?.reviews || analytics.reviews.length === 0) && (
<div className="bg-gray-100 p-4 rounded-lg text-center">No reviews yet.</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter, useParams } from "next/navigation"
import { Star, User } from "lucide-react"
import { AdminHeader } from "@/components/admin-header"
import { fetchEventById, getEventReviews } from "@/api"
export default function EventAnalyticsPage() {
const router = useRouter()
const params = useParams()
const eventId = params?.id as string
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const [event, setEvent] = useState(null)
const [reviews, setReviews] = useState([])
useEffect(() => {
const loadEventData = async () => {
if (!eventId) return
try {
setIsLoading(true)
// Check authentication
const isLoggedIn = localStorage.getItem("isLoggedIn")
const isAdmin = localStorage.getItem("isAdmin")
if (isLoggedIn !== "true" || isAdmin !== "true") {
router.push("/auth/signin")
return
}
// Fetch event data
const eventData = await fetchEventById(eventId)
setEvent(eventData)
// Fetch reviews
try {
const reviewsData = await getEventReviews(eventId)
setReviews(reviewsData)
} catch (reviewErr) {
console.error("Error fetching reviews:", reviewErr)
// Mock reviews for demo
setReviews([
{
id: 1,
user: { Username: "Stanescu Lata" },
Stars: 5,
Comment: "It was very fun! I had a great time!",
},
{
id: 2,
user: { Username: "McChickenLover" },
Stars: 3,
Comment: "It was okay, but could be better.",
},
{
id: 3,
user: { Username: "Juan" },
Stars: 4,
Comment:
"It was a pleasant experience that me and my family really enjoyed! But the bathroom was not very clean.",
},
])
}
} catch (err) {
console.error("Error loading event data:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadEventData()
}, [eventId, router])
// Mock data for charts with unique keys
const ticketSalesData = [
{ name: "Jan", sales: 20, id: "jan" },
{ name: "Feb", sales: 40, id: "feb" },
{ name: "Mar", sales: 30, id: "mar" },
{ name: "Apr", sales: 20, id: "apr" },
{ name: "May", sales: 50, id: "may" },
{ name: "Jun", sales: 10, id: "jun" },
{ name: "Jul", sales: 40, id: "jul" },
{ name: "Aug", sales: 30, id: "aug" },
{ name: "Sep", sales: 45, id: "sep" },
{ name: "Oct", sales: 25, id: "oct" },
]
const pageActivityData = [
{ day: "Mon", views: 50, id: "mon" },
{ day: "Tue", views: 20, id: "tue" },
{ day: "Wed", views: 100, id: "wed" },
{ day: "Thu", views: 40, id: "thu" },
{ day: "Fri", views: 10, id: "fri" },
{ day: "Sat", views: 80, id: "sat" },
{ day: "Sun", views: 60, id: "sun" },
]
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
return (
<div className="min-h-screen flex flex-col">
<AdminHeader title="TickMeIn Admin" />
<div className="bg-olive-600 text-white py-4">
<div className="container mx-auto text-center">
<h1 className="text-xl font-bold">Analytics</h1>
</div>
</div>
<main className="flex-1 p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8">
<div>
<h2 className="text-2xl font-bold mb-4">Ticket buy history</h2>
<div className="bg-white p-4 rounded-lg shadow border">
<div className="h-[200px] w-full">
{/* Simple line chart representation instead of using Recharts */}
<div className="relative h-full w-full">
<div className="absolute inset-0 flex items-end">
{ticketSalesData.map((item) => (
<div
key={item.id}
className="flex-1 bg-blue-500 mx-1 rounded-t-sm"
style={{ height: `${(item.sales / 100) * 100}%` }}
></div>
))}
</div>
</div>
</div>
</div>
</div>
<div>
<h2 className="text-2xl font-bold mb-4">Page activity</h2>
<div className="bg-white p-4 rounded-lg shadow border">
<div className="h-[200px] w-full">
{/* Simple bar chart representation instead of using Recharts */}
<div className="relative h-full w-full">
<div className="absolute inset-0 flex items-end">
{pageActivityData.map((item) => (
<div
key={item.id}
className="flex-1 bg-green-800 mx-1 rounded-t-sm"
style={{ height: `${(item.views / 100) * 100}%` }}
></div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
<div className="mb-8">
<h2 className="text-2xl font-bold mb-4">Reviews</h2>
<div className="space-y-4">
{reviews.map((review, index) => (
<div key={review.id || index} className="bg-white p-4 rounded-lg shadow border">
<div className="flex justify-between items-start mb-2">
<div className="flex items-center">
<div className="w-8 h-8 rounded-full bg-gray-300 mr-2 flex items-center justify-center">
<User className="h-4 w-4 text-gray-600" />
</div>
<h3 className="font-semibold">{review.user?.Username || "Anonymous"}</h3>
</div>
<div className="flex">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`h-4 w-4 ${i < review.Stars ? "fill-yellow-400 text-yellow-400" : "text-gray-300"}`}
/>
))}
</div>
</div>
<p className="text-sm">{review.Comment}</p>
</div>
))}
{reviews.length === 0 && <div className="bg-gray-100 p-4 rounded-lg text-center">No reviews yet.</div>}
</div>
</div>
</main>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter, useParams } from "next/navigation"
import Image from "next/image"
import { Edit } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { AdminHeader } from "@/components/admin-header"
import { fetchEventById, updateEvent } from "@/api"
export default function EditEventPage() {
const router = useRouter()
const params = useParams()
const eventId = params?.id as string
const [isLoading, setIsLoading] = useState(true)
const [isSaving, setIsSaving] = useState(false)
const [error, setError] = useState(null)
const [event, setEvent] = useState({
title: "",
description: "",
price: "30",
location: "",
date: "",
image: "/placeholder.svg?height=200&width=400",
mapImage: "/placeholder.svg?height=200&width=200",
discount: 0,
adminId: 0,
})
const [currentAdminId, setCurrentAdminId] = useState(1) // Default admin ID
useEffect(() => {
const loadEventData = async () => {
if (!eventId) return
try {
setIsLoading(true)
// Check authentication
const isLoggedIn = localStorage.getItem("isLoggedIn")
const isAdmin = localStorage.getItem("isAdmin")
if (isLoggedIn !== "true" || isAdmin !== "true") {
router.push("/auth/signin")
return
}
// Set current admin ID (in a real app, this would come from the auth token)
const adminId = Number(localStorage.getItem("userId") || "0")
setCurrentAdminId(adminId)
// Fetch event data
const eventData = await fetchEventById(eventId)
// Format event data
setEvent({
title: eventData.Title || "Tickets to Untold Festival 2025",
description:
eventData.Description ||
"The UNTOLD Festival, Romania's largest electronic music event, is gearing up for its monumental 10th anniversary edition, dubbed UNTOLD X, scheduled from August 7 to 10, 2025, in Cluj-Napoca, Transylvania. UNTOLD X promises an extraordinary celebration of music, love, friendship, and unity. Attendees can anticipate an impressive lineup of top DJs and live acts spanning genres like EDM, house, techno, hip-hop, and pop. The festival is renowned for its breathtaking stage designs, immersive experiences, and a variety of activities beyond music, including VR games, fashion showcases, and diverse culinary offerings.",
price: eventData.Price?.toString() || "30",
location: eventData.Location || "Cluj-Napoca, Romania",
date: eventData.Date ? new Date(eventData.Date).toISOString().split("T")[0] : "7 - 10 August",
image: "/placeholder.svg?height=200&width=400",
mapImage: "/placeholder.svg?height=200&width=200",
discount: 0,
adminId: eventData.admin_id || 1,
})
} catch (err) {
console.error("Error loading event:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadEventData()
}, [eventId, router])
const handleSaveChanges = async () => {
try {
setIsSaving(true)
setError("")
// Prepare event data for update
const eventData = {
Title: event.title,
Description: event.description,
Date: event.date,
Location: event.location,
Price: Number(event.price),
}
// Update event
await updateEvent(eventId, eventData)
alert("Event updated successfully!")
} catch (err) {
console.error("Error updating event:", err)
setError(err.message || "Failed to update event")
} finally {
setIsSaving(false)
}
}
const handleEditPrice = () => {
const newPrice = prompt("Enter new price:", event.price)
if (newPrice !== null) {
setEvent({ ...event, price: newPrice })
}
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading event: {error}</p>
<p>Please try again later</p>
</div>
)
}
// Check if current admin is the creator of this event
const isEventCreator = currentAdminId === event.adminId || currentAdminId === 0
return (
<div className="min-h-screen flex flex-col">
<AdminHeader title="TickMeIn Admin" />
<div className="bg-yellow-300 text-black py-4">
<div className="container mx-auto text-center">
<h1 className="text-xl font-bold">Edit Event</h1>
</div>
</div>
<main className="flex-1 p-6">
{!isEventCreator ? (
<div className="max-w-3xl mx-auto bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
<p>You don't have permission to edit this event. Only the creator can edit it.</p>
</div>
) : (
<div className="max-w-5xl mx-auto">
<div className="mb-6 flex items-center">
<Input
value={event.title}
onChange={(e) => setEvent({ ...event, title: e.target.value })}
className="text-2xl font-bold border-2 p-2 flex-grow"
/>
<Button variant="ghost" className="ml-2">
<Edit className="h-5 w-5" />
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
<div className="md:col-span-2">
{/* Map View */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image
src={event.mapImage || "/placeholder.svg"}
alt="Event location map"
fill
className="object-cover"
/>
<Button variant="ghost" className="absolute bottom-2 right-2 bg-white rounded-full p-2">
<Edit className="h-5 w-5" />
</Button>
</div>
{/* Gallery */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image src={event.image || "/placeholder.svg"} alt="Event gallery" fill className="object-cover" />
<Button variant="ghost" className="absolute bottom-2 right-2 bg-white rounded-full p-2">
<Edit className="h-5 w-5" />
</Button>
</div>
{/* Description */}
<div className="mb-6 relative">
<Textarea
value={event.description}
onChange={(e) => setEvent({ ...event, description: e.target.value })}
className="min-h-[200px] p-4"
/>
<Button variant="ghost" className="absolute bottom-2 right-2 bg-white rounded-full p-2">
<Edit className="h-5 w-5" />
</Button>
</div>
{/* Location */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-1">Location</label>
<Input
value={event.location}
onChange={(e) => setEvent({ ...event, location: e.target.value })}
className="w-full"
/>
</div>
{/* Date */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-1">Date</label>
<Input
type="date"
value={event.date}
onChange={(e) => setEvent({ ...event, date: e.target.value })}
className="w-full"
/>
</div>
</div>
<div>
<div className="mb-4">
<div className="flex justify-between items-center mb-2">
<span className="text-blue-500 font-medium">Add discount!</span>
<div className="flex items-center">
<Input
type="number"
value={event.discount.toString()}
onChange={(e) => setEvent({ ...event, discount: Number.parseInt(e.target.value) || 0 })}
className="w-16 mr-1"
/>
<span>%</span>
</div>
</div>
<div className="flex items-center justify-between mb-4">
<span className="font-bold">Price: {event.price}</span>
<Button variant="ghost" onClick={handleEditPrice}>
<Edit className="h-5 w-5" />
</Button>
</div>
<Button
className="w-full bg-black hover:bg-gray-800 mb-2"
onClick={handleSaveChanges}
disabled={isSaving}
>
{isSaving ? "Saving..." : "Save Changes"}
</Button>
</div>
</div>
</div>
</div>
)}
</main>
</div>
)
}
export default function Loading() {
return null
}
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { AdminHeader } from "@/components/admin-header"
import { fetchEvents, deleteEvent } from "@/api"
export default function MyEventsPage() {
const router = useRouter()
const [events, setEvents] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const [currentAdminId, setCurrentAdminId] = useState(null)
useEffect(() => {
const checkAuth = () => {
const isLoggedIn = localStorage.getItem("isLoggedIn") === "true"
const isAdmin = localStorage.getItem("isAdmin") === "true"
if (!isLoggedIn || !isAdmin) {
router.push("/auth/signin")
return false
}
return true
}
const loadEvents = async () => {
if (!checkAuth()) return
try {
setIsLoading(true)
setError(null)
// Get current admin ID from localStorage
const adminId = Number(localStorage.getItem("userId") || "0")
setCurrentAdminId(adminId)
const eventsData = await fetchEvents()
// Filtrăm evenimentul problematic "aaaaaaaaaaaaaaaaaa"
const filteredEvents = eventsData.filter(
(event) => event.title !== "aaaaaaaaaaaaaaaaaa" && event.Title !== "aaaaaaaaaaaaaaaaaa",
)
// Transform data to match expected format
const formattedEvents = filteredEvents.map((event) => ({
id: event.id?.toString() || event.ID?.toString() || Math.random().toString(36).substring(2, 9),
title: event.title || event.Title || "Untitled Event",
priceRange: event.Price ? `${event.Price}€` : event.priceRange || "Price not set",
location: event.location || event.Location || "Unknown Location",
date: event.date ? `${event.date}` : event.Date ? `${new Date(event.Date).toLocaleDateString()}` : "TBA",
image: "/placeholder.svg?height=100&width=100",
isActive: Math.random() > 0.3, // Randomly set some events as inactive for demo
adminId: event.admin_id || 0, // Add admin ID
}))
// Filter events to only show those created by the current admin
// Admin with ID 0 can see all events
const filteredByAdminEvents =
adminId === 0 ? formattedEvents : formattedEvents.filter((event) => event.adminId === adminId)
setEvents(filteredByAdminEvents)
} catch (err) {
console.error("Error loading events:", err)
setError(err.message || "Failed to load events")
} finally {
setIsLoading(false)
}
}
loadEvents()
}, [router])
const handleCreateEvent = () => {
router.push("/admin/events/new")
}
const handleDeleteEvent = async (eventId) => {
if (confirm("Are you sure you want to delete this event? This action cannot be undone.")) {
try {
await deleteEvent(eventId)
setEvents(events.filter((event) => event.id !== eventId))
alert("Event deleted successfully!")
} catch (error) {
console.error("Error deleting event:", error)
alert("Failed to delete event. Please try again.")
}
}
}
const canEditEvent = (eventAdminId) => {
return currentAdminId === eventAdminId || currentAdminId === 0 // Admin ID 0 can edit all events
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AdminHeader title="TickMeIn Admin" />
<div className="bg-red-600 text-white py-4">
<div className="container mx-auto text-center">
<h1 className="text-xl font-bold">Your events</h1>
</div>
</div>
<main className="flex-1 p-6">
<div className="space-y-4 mb-8">
{events.length > 0 ? (
events.map((event) => (
<div key={event.id} className="border rounded-md overflow-hidden bg-white">
<div className="flex">
<div className="w-[150px] h-[150px] relative bg-gray-200">
<Image
src={event.image || "/placeholder.svg"}
alt={event.title}
width={150}
height={150}
className="object-cover"
/>
</div>
<div className="flex-1 p-4">
<Link href={`/events/${event.id}`}>
<h3 className="font-bold text-lg hover:underline">{event.title}</h3>
</Link>
<div className="mt-2 space-y-1 text-sm">
<p>Ticket prices may vary: {event.priceRange}</p>
<p>Location: {event.location}</p>
<p>{event.date}</p>
</div>
</div>
<div className="flex flex-col justify-between p-4">
<div className="flex space-x-2">
{canEditEvent(event.adminId) ? (
<>
<Button
variant="outline"
size="sm"
className="text-blue-500"
onClick={() => router.push(`/admin/events/${event.id}/edit`)}
>
Edit
</Button>
<Button
variant="outline"
size="sm"
className="text-blue-500"
onClick={() => router.push(`/admin/events/${event.id}/analytics`)}
>
Analytics
</Button>
<Button
variant="outline"
size="sm"
className="text-red-500"
onClick={() => handleDeleteEvent(event.id)}
>
Delete
</Button>
</>
) : null}
</div>
<div className="flex justify-end">
<div className={`w-6 h-6 rounded-full ${event.isActive ? "bg-green-600" : "bg-red-500"}`}></div>
</div>
</div>
</div>
</div>
))
) : (
<div className="text-center py-10">
<p className="text-gray-500 mb-4">You haven't created any events yet.</p>
<Button className="bg-orange-600 hover:bg-orange-700 text-white" onClick={handleCreateEvent}>
Create Your First Event
</Button>
</div>
)}
</div>
{events.length > 0 && (
<div className="flex justify-end">
<Button className="bg-orange-600 hover:bg-orange-700 text-white px-6 py-3" onClick={handleCreateEvent}>
Create New Event!!!
</Button>
</div>
)}
</main>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { EventCard } from "@/components/event-card"
import { AdminHeader } from "@/components/admin-header"
import { getUserTickets } from "@/api"
export default function AdminMyTicketsPage() {
const router = useRouter()
const [tickets, setTickets] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const loadTickets = async () => {
try {
setIsLoading(true)
// Check authentication
const isLoggedIn = localStorage.getItem("isLoggedIn")
const isAdmin = localStorage.getItem("isAdmin")
if (isLoggedIn !== "true" || isAdmin !== "true") {
router.push("/auth/signin")
return
}
// Fetch tickets from API
const ticketsData = await getUserTickets()
// Format tickets data
const formattedTickets = await Promise.all(
ticketsData.map(async (ticket) => {
// In a real app, we would fetch event details for each ticket
// For now, we'll use some default values
return {
id: ticket.ID.toString(),
eventId: ticket.Event_ID.toString(),
title: `Event #${ticket.Event_ID}`,
priceRange: "€50",
location: "Event Location",
date: `Purchase Date: ${new Date(ticket.Purchase_date).toLocaleDateString()}`,
image: "/placeholder.svg?height=200&width=200",
}
}),
)
setTickets(formattedTickets)
} catch (err) {
console.error("Error loading tickets:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadTickets()
}, [router])
const handleViewTicket = (ticketId) => {
router.push(`/admin/tickets/${ticketId}`)
}
const handleBrowseEvents = () => {
router.push("/admin/events")
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading tickets: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AdminHeader />
<div className="bg-green-600 text-white py-4">
<div className="container mx-auto text-center">
<h1 className="text-xl font-bold">My Tickets</h1>
</div>
</div>
<main className="flex-1 p-6">
{tickets.length === 0 ? (
<div className="text-center py-10">
<p className="text-gray-500 mb-4">You haven't purchased any tickets yet.</p>
<button onClick={handleBrowseEvents} className="text-blue-600 hover:underline">
Browse events
</button>
</div>
) : (
<div className="space-y-4">
{tickets.map((ticket) => (
<div key={ticket.id} className="relative">
<EventCard
title={ticket.title}
priceRange={ticket.priceRange}
location={ticket.location}
date={ticket.date}
image={ticket.image}
id={ticket.eventId}
/>
<div className="absolute bottom-4 right-4">
<button
onClick={() => handleViewTicket(ticket.id)}
className="px-4 py-2 bg-black text-white text-sm font-medium rounded-md hover:bg-gray-700"
>
View Ticket
</button>
</div>
</div>
))}
</div>
)}
</main>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import config from "@/config"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { AdminHeader } from "@/components/admin-header"
import { deleteAccount } from "@/api"
export default function AdminProfilePage() {
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)
const [isDeleting, setIsDeleting] = useState(false)
const [profile, setProfile] = useState({
username: "",
email: "",
fullName: "",
})
useEffect(() => {
// Check if user is authenticated and is admin
const isLoggedIn = localStorage.getItem("isLoggedIn")
const isAdmin = localStorage.getItem("isAdmin")
const token = localStorage.getItem("token")
const userId = localStorage.getItem("userId")
const username = localStorage.getItem("username")
if (isLoggedIn !== "true" || isAdmin !== "true") {
window.location.href = "/auth/signin"
return
}
// Încărcăm datele de profil din backend
const fetchAdminProfile = async () => {
try {
// Încercăm să obținem datele din backend folosind endpoint-ul pentru admin
const response = await fetch(`${config.apiBaseUrl}/admin/${userId || username}`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (response.ok) {
const data = await response.json()
setProfile({
username: data.Username || username || "",
email: data.Email || "",
fullName: data.FullName || "Admin User",
})
} else {
// Încercăm endpoint-ul general pentru utilizatori
const userResponse = await fetch(`${config.apiBaseUrl}/users/${userId || username}`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (userResponse.ok) {
const userData = await userResponse.json()
setProfile({
username: userData.Username || username || "",
email: userData.Email || "",
fullName: userData.FullName || "Admin User",
})
} else {
// Folosim datele din localStorage ca ultimă opțiune
setProfile({
username: username || "",
email: localStorage.getItem("email") || "",
fullName: "Admin User",
})
}
}
} catch (error) {
console.error("Error fetching admin profile:", error)
// Folosim datele din localStorage ca ultimă opțiune
setProfile({
username: username || "",
email: localStorage.getItem("email") || "",
fullName: "Admin User",
})
} finally {
setIsLoading(false)
}
}
fetchAdminProfile()
}, [router])
const handleLogout = () => {
// Clear authentication data
localStorage.removeItem("isLoggedIn")
localStorage.removeItem("token")
localStorage.removeItem("userId")
localStorage.removeItem("username")
localStorage.removeItem("isAdmin")
localStorage.removeItem("email")
// Redirect to login page using window.location for faster navigation
window.location.href = "/auth/signin"
}
const handleDeleteAccount = async () => {
if (confirm("Are you sure you want to delete your account? This action cannot be undone.")) {
try {
setIsDeleting(true)
// Call the API to delete the account
await deleteAccount()
// Redirect to signin page using window.location for faster navigation
window.location.href = "/auth/signin"
} catch (err) {
console.error("Error deleting account:", err)
alert("Failed to delete account. Please try again.")
setIsDeleting(false)
}
}
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AdminHeader title="TickMeIn Admin" />
<main className="flex-1 p-6">
<div className="max-w-3xl mx-auto">
<h1 className="text-2xl font-bold mb-6">Admin Profile</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="md:col-span-1">
<div className="flex flex-col items-center mb-6">
<div className="w-32 h-32 rounded-full bg-gray-300 overflow-hidden relative mb-4">
<Image src="/images/user-avatar.png" alt="Profile picture" fill className="object-cover" />
</div>
<Button variant="outline" size="sm" className="hover:bg-gray-300">
Upload New Photo
</Button>
</div>
</div>
<div className="md:col-span-2">
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Username</label>
<Input name="username" value={profile.username} readOnly />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
<Input name="email" type="email" value={profile.email} readOnly />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Full Name</label>
<Input name="fullName" value={profile.fullName} readOnly />
</div>
<div className="flex justify-between pt-4">
<div className="space-x-2">
<Button variant="destructive" onClick={handleLogout}>
Logout
</Button>
<Button variant="destructive" onClick={handleDeleteAccount} disabled={isDeleting}>
{isDeleting ? "Deleting..." : "Delete Account"}
</Button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { EventCard } from "@/components/event-card"
import { AdminHeader } from "@/components/admin-header"
import { getUserTickets, fetchEventById } from "@/api"
export default function AdminTrackedEventsPage() {
const router = useRouter()
const [events, setEvents] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const loadTrackedEvents = async () => {
try {
setIsLoading(true)
// Verificăm autentificarea
const isLoggedIn = localStorage.getItem("isLoggedIn")
const isAdmin = localStorage.getItem("isAdmin")
if (isLoggedIn !== "true" || isAdmin !== "true") {
router.push("/auth/signin")
return
}
// Obținem biletele utilizatorului
const tickets = await getUserTickets()
// Pentru fiecare bilet, obținem detaliile evenimentului
const trackedEvents = []
for (const ticket of tickets) {
try {
const eventId = ticket.Event_ID
const eventDetails = await fetchEventById(eventId)
// Verificăm să nu fie evenimentul problematic
if (eventDetails.Title !== "aaaaaaaaaaaaaaaaaa" && eventDetails.title !== "aaaaaaaaaaaaaaaaaa") {
const eventDate = eventDetails.Date || eventDetails.date
trackedEvents.push({
id: eventId.toString(),
title: eventDetails.Title || eventDetails.title,
priceRange: `${eventDetails.Price || 0}€`,
location: eventDetails.Location || eventDetails.location,
date: `${new Date(eventDate).toLocaleDateString()}`,
image: "/placeholder.svg?height=200&width=200",
rawDate: eventDate, // Store the raw date for comparison
})
}
} catch (err) {
console.error(`Error fetching event details for ticket ${ticket.ID}:`, err)
}
}
setEvents(trackedEvents)
} catch (err) {
console.error("Error loading tracked events:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadTrackedEvents()
}, [router])
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading tracked events: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AdminHeader title="TickMeIn Admin" />
<div className="bg-purple-900 text-white py-4">
<div className="container mx-auto text-center">
<h1 className="text-xl font-bold">Your Tracked Events</h1>
</div>
</div>
<main className="flex-1 p-6">
{events.length > 0 ? (
<div className="space-y-4">
{events.map((event, index) => {
// Check if event is in the past
const eventDate = new Date(event.date.split("").slice(0, -1).join(""))
const isPastEvent = eventDate < new Date()
return (
<div key={index} onClick={() => router.push(`/events/${event.id}`)} className="cursor-pointer">
<div className="relative">
<EventCard
title={event.title}
priceRange={event.priceRange}
location={event.location}
date={event.date}
image={event.image}
/>
{isPastEvent && (
<div
className="absolute top-4 right-4 h-4 w-4 rounded-full bg-red-500"
title="This event has passed"
></div>
)}
</div>
</div>
)
})}
</div>
) : (
<div className="text-center py-8">
<p>You haven't tracked any events yet.</p>
<p className="mt-2">Purchase tickets to track events!</p>
</div>
)}
</main>
</div>
)
}
"use client"
import { useState } from "react"
import Link from "next/link"
import { Eye, EyeOff } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { loginUser } from "@/api"
export default function SignInPage() {
const [emailOrUsername, setEmailOrUsername] = useState("")
const [password, setPassword] = useState("")
const [showPassword, setShowPassword] = useState(false)
const [error, setError] = useState("")
const [isLoading, setIsLoading] = useState(false)
const handleSubmit = async (e) => {
e.preventDefault()
if (isLoading) return
setError("")
setIsLoading(true)
try {
console.log("Attempting login with:", emailOrUsername, password)
if (!emailOrUsername || !password) {
setError("Email/username and password are required")
setIsLoading(false)
return
}
// Call the API to login
const userData = await loginUser({
emailOrUsername,
password,
})
console.log("Login successful:", userData)
// Store user data in localStorage
localStorage.setItem("token", userData.token || "")
localStorage.setItem("isLoggedIn", "true")
localStorage.setItem("userId", userData.ID?.toString() || "")
localStorage.setItem("username", userData.Username || "")
localStorage.setItem("email", userData.Email || "")
// Check if user is admin
localStorage.setItem("isAdmin", userData.isAdmin ? "true" : "false")
// Redirect based on user role - folosim window.location.href pentru redirecționare directă
if (userData.isAdmin) {
window.location.href = "/admin"
} else {
window.location.href = "/events"
}
} catch (err) {
console.error("Login error:", err)
setError("Login failed. Please check your credentials and try again.")
setIsLoading(false)
}
}
const toggleShowPassword = () => {
setShowPassword(!showPassword)
}
return (
<div className="flex min-h-screen items-center justify-center bg-gray-100 p-4">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl font-bold">Sign in to TickMeIn</CardTitle>
<CardDescription>Enter your email/username and password to access your account</CardDescription>
</CardHeader>
<form onSubmit={handleSubmit}>
<CardContent className="space-y-4">
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
<p>{error}</p>
</div>
)}
<div className="space-y-2">
<Label htmlFor="email">Email or Username</Label>
<Input
id="email"
type="text"
placeholder="your.email@example.com or username"
value={emailOrUsername}
onChange={(e) => setEmailOrUsername(e.target.value)}
disabled={isLoading}
required
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label htmlFor="password">Password</Label>
<Link href="#" className="text-sm text-blue-600 hover:text-blue-800">
Forgot password?
</Link>
</div>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
required
/>
<button
type="button"
onClick={toggleShowPassword}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500"
disabled={isLoading}
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
</CardContent>
<CardFooter className="flex flex-col space-y-4">
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? (
<div className="flex items-center">
<div className="animate-spin mr-2 h-4 w-4 border-t-2 border-b-2 border-white rounded-full"></div>
Signing in...
</div>
) : (
"Sign in"
)}
</Button>
<div className="text-center text-sm">
Don&apos;t have an account?{" "}
<Link href="/auth/signup" className="text-blue-600 hover:text-blue-800">
Sign up
</Link>
</div>
</CardFooter>
</form>
</Card>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import Image from "next/image"
import { MapPin, Calendar, Clock, User } from "lucide-react"
import { useRouter, useParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { AppHeader } from "@/components/app-header"
import { fetchEventById, purchaseTicket } from "@/api"
// Default event data
const defaultEvent = {
id: "unknown",
title: "Event Not Found",
description: "The event you're looking for could not be found.",
priceRange: "N/A",
location: "Unknown",
date: "Unknown",
time: "Unknown",
image: "/placeholder.svg?height=400&width=800",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
}
export default function EventDetailPage() {
const router = useRouter()
const params = useParams()
const eventId = params?.id as string
const [isLoading, setIsLoading] = useState(true)
const [event, setEvent] = useState(defaultEvent)
const [isTracked, setIsTracked] = useState(false)
const [error, setError] = useState(null)
// Load event data
useEffect(() => {
const loadEventData = async () => {
if (!eventId) return
try {
setIsLoading(true)
// Check authentication
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
return
}
// Fetch event data from API
try {
const eventData = await fetchEventById(eventId)
// Format event data
const formattedEvent = {
id: eventData.ID?.toString() || eventData.id?.toString(),
title: eventData.Title || eventData.title || "Event Details",
description: eventData.Description || eventData.description || "No description available.",
priceRange: `${eventData.Price || eventData.price || 0}€`,
location: eventData.Location || eventData.location || "Unknown Location",
date: eventData.Date
? new Date(eventData.Date).toLocaleDateString()
: eventData.date
? eventData.date
: "Date TBA",
time: "12:00 PM - 11:00 PM", // Default time
image: "/placeholder.svg?height=400&width=800",
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
}
setEvent(formattedEvent)
// Check if event is tracked
const trackedEventsJSON = localStorage.getItem("trackedEvents")
if (trackedEventsJSON) {
const trackedEvents = JSON.parse(trackedEventsJSON)
setIsTracked(trackedEvents.some((e) => e.id === formattedEvent.id))
}
} catch (err) {
console.error("Error loading event:", err)
// Set default event data if fetch fails
setEvent({
...defaultEvent,
id: eventId,
title: "Event " + eventId,
})
}
} catch (err) {
console.error("Error in event page:", err)
setError(err.message || "An unexpected error occurred")
} finally {
setIsLoading(false)
}
}
loadEventData()
}, [eventId, router])
const handleBuyTicket = async () => {
try {
// Call API to purchase ticket
const ticket = await purchaseTicket(eventId)
router.push(`/tickets/${ticket.ID}`)
} catch (err) {
console.error("Error purchasing ticket:", err)
alert("Could not purchase ticket. Please try again.")
}
}
const handleTrackEvent = async () => {
try {
if (!isTracked) {
// For now, we'll just use localStorage for tracking events
const existingEventsJSON = localStorage.getItem("trackedEvents")
const existingEvents = existingEventsJSON ? JSON.parse(existingEventsJSON) : []
const updatedEvents = [...existingEvents, event]
localStorage.setItem("trackedEvents", JSON.stringify(updatedEvents))
setIsTracked(true)
alert(`Event "${event.title}" has been added to your tracked events!`)
} else {
// Remove from tracked events
const existingEventsJSON = localStorage.getItem("trackedEvents")
if (existingEventsJSON) {
const existingEvents = JSON.parse(existingEventsJSON)
const updatedEvents = existingEvents.filter((e) => e.id !== event.id)
localStorage.setItem("trackedEvents", JSON.stringify(updatedEvents))
setIsTracked(false)
alert(`Event "${event.title}" has been removed from your tracked events.`)
}
}
} catch (err) {
console.error("Error updating tracked events:", err)
alert("Could not update tracked events. Please try again.")
}
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading event: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="min-h-screen flex flex-col">
<AppHeader />
<main className="flex-1 p-6">
<div className="max-w-5xl mx-auto">
<h1 className="text-3xl font-bold mb-6">{event.title}</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
<div className="md:col-span-2">
{/* Main Image */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image src={event.image || "/placeholder.svg"} alt={event.title} fill className="object-cover" />
</div>
{/* Map View */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image
src={event.mapImage || "/placeholder.svg"}
alt="Event location map"
fill
className="object-cover"
/>
<div className="absolute bottom-2 left-2 bg-black text-white p-1 rounded-full">
<MapPin className="h-5 w-5" />
</div>
</div>
{/* Description */}
<div className="mb-6">
<p className="text-sm leading-relaxed">{event.description}</p>
</div>
{/* Event Details */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<div className="flex items-center gap-2">
<Calendar className="h-5 w-5 text-gray-500" />
<span>{event.date}</span>
</div>
<div className="flex items-center gap-2">
<Clock className="h-5 w-5 text-gray-500" />
<span>{event.time}</span>
</div>
<div className="flex items-center gap-2">
<MapPin className="h-5 w-5 text-gray-500" />
<span>{event.location}</span>
</div>
<div className="flex items-center gap-2">
<User className="h-5 w-5 text-gray-500" />
<span>All ages welcome</span>
</div>
</div>
</div>
<div>
<Card className="mb-4">
<CardContent className="p-4">
<div className="mb-4">
<div className="text-lg font-semibold">Price: {event.priceRange}</div>
</div>
<Button className="w-full bg-black hover:bg-gray-700 mb-2" onClick={handleBuyTicket}>
Buy Ticket
</Button>
<Button variant="outline" className="w-full hover:bg-gray-300" onClick={handleTrackEvent}>
{isTracked ? "Untrack event" : "Track event"}
</Button>
</CardContent>
</Card>
</div>
</div>
</div>
</main>
</div>
)
}
export default function Loading() {
return null
}
@tailwind base;
@tailwind components;
@tailwind utilities;
.bg-olive-600 {
background-color: #556b2f;
}
body {
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
import type React from "react"
import { Inter } from "next/font/google"
import "./globals.css"
import { ThemeProvider } from "@/components/theme-provider"
const inter = Inter({ subsets: ["latin"] })
export const metadata = {
title: "TickMeIn",
description: "Platform for event tickets",
generator: 'v0.dev'
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head />
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="light" enableSystem disableTransitionOnChange>
{children}
</ThemeProvider>
</body>
</html>
)
}
export default function Loading() {
return null
}
"use client"
import { useEffect } from "react"
import { useRouter } from "next/navigation"
export default function LogoutPage() {
const router = useRouter()
useEffect(() => {
// In a real app, you would handle logout logic here
// For now, we'll just redirect to the home page
setTimeout(() => {
router.push("/")
}, 1000)
}, [router])
return (
<div className="flex min-h-screen flex-col items-center justify-center">
<h1 className="text-2xl font-bold mb-4">Logging out...</h1>
<p>You will be redirected shortly.</p>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { EventCard } from "@/components/event-card"
import { AppHeader } from "@/components/app-header"
import { getUserTickets, fetchEventById } from "@/api"
export default function MyTicketsPage() {
const router = useRouter()
const [tickets, setTickets] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const loadTickets = async () => {
try {
setIsLoading(true)
// Check authentication
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
return
}
// Fetch tickets from API
const ticketsData = await getUserTickets()
// Format tickets data
const formattedTickets = await Promise.all(
ticketsData.map(async (ticket) => {
try {
// Fetch the event details for this ticket
const eventDetails = await fetchEventById(ticket.Event_ID)
return {
id: ticket.ID.toString(),
eventId: ticket.Event_ID.toString(),
title: eventDetails.Title || `Event #${ticket.Event_ID}`,
priceRange: `€${eventDetails.Price}`,
location: eventDetails.Location || "Event Location",
date: `${new Date(eventDetails.Date).toLocaleDateString()}`,
image: "/placeholder.svg?height=200&width=200",
}
} catch (err) {
console.error(`Error fetching event details for ticket ${ticket.ID}:`, err)
return {
id: ticket.ID.toString(),
eventId: ticket.Event_ID.toString(),
title: `Event #${ticket.Event_ID}`,
priceRange: "€50",
location: "Event Location",
date: `Purchase Date: ${new Date(ticket.Purchase_date).toLocaleDateString()}`,
image: "/placeholder.svg?height=200&width=200",
}
}
}),
)
setTickets(formattedTickets)
} catch (err) {
console.error("Error loading tickets:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadTickets()
}, [router])
const handleViewTicket = (ticketId) => {
router.push(`/tickets/${ticketId}`)
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading tickets: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AppHeader />
<main className="flex-1 p-6">
<h1 className="text-2xl font-bold mb-6">My Tickets</h1>
{tickets.length === 0 ? (
<div className="text-center py-10">
<p className="text-gray-500 mb-4">You haven't purchased any tickets yet.</p>
<button onClick={() => router.push("/events")} className="text-blue-600 hover:underline">
Browse events
</button>
</div>
) : (
<div className="space-y-4">
{tickets.map((ticket) => (
<div key={ticket.id} className="relative">
<EventCard
title={ticket.title}
priceRange={ticket.priceRange}
location={ticket.location}
date={ticket.date}
image={ticket.image}
id={ticket.eventId}
/>
<div className="absolute bottom-4 right-4">
<button
onClick={() => handleViewTicket(ticket.id)}
className="px-4 py-2 bg-black text-white text-sm font-medium rounded-md hover:bg-gray-700"
>
View Ticket
</button>
</div>
</div>
))}
</div>
)}
</main>
</div>
)
}
"use client"
import { useEffect } from "react"
export default function Home() {
useEffect(() => {
// Folosim setTimeout pentru a ne asigura că redirecționarea se întâmplă după ce pagina este complet încărcată
const redirectTimer = setTimeout(() => {
window.location.replace("/auth/signin")
}, 100)
// Curățăm timerul la demontarea componentei
return () => clearTimeout(redirectTimer)
}, [])
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-gray-900 mx-auto mb-4"></div>
<p>Redirecting to login...</p>
</div>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import Image from "next/image"
import { MapPin, Star } from "lucide-react"
import { useParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Textarea } from "@/components/ui/textarea"
import { AppHeader } from "@/components/app-header"
import { fetchEventById, getEventReviews, submitReview } from "@/api"
export default function PastEventPage() {
const params = useParams()
const eventId = params?.id as string
const [rating, setRating] = useState(0)
const [review, setReview] = useState("")
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const [event, setEvent] = useState({
title: "",
description: "",
location: "",
date: "",
mapImage: "",
galleryImages: [""],
reviews: [],
})
const [isSubmitting, setIsSubmitting] = useState(false)
useEffect(() => {
const loadEventData = async () => {
if (!eventId) return
try {
setIsLoading(true)
// Fetch event data from API
const eventData = await fetchEventById(eventId)
// Fetch reviews for this event
let reviewsData = []
try {
reviewsData = await getEventReviews(eventId)
} catch (reviewErr) {
console.error("Error fetching reviews:", reviewErr)
reviewsData = []
}
// Format reviews
const formattedReviews = reviewsData.map((review) => ({
name: "User", // In a real app, we would fetch user details
rating: review.Stars,
comment: review.Comment,
}))
// Set event data
setEvent({
title: eventData.Title,
description: eventData.Description,
location: eventData.Location,
date: new Date(eventData.Date).toLocaleDateString(),
mapImage: "/placeholder.svg?height=200&width=400",
galleryImages: ["/placeholder.svg?height=200&width=400"],
reviews: formattedReviews,
})
} catch (err) {
console.error("Error loading event data:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadEventData()
}, [eventId])
const handleSubmitReview = async () => {
if (rating === 0) {
alert("Please select a rating before submitting your review.")
return
}
if (!review.trim()) {
alert("Please enter a review comment.")
return
}
try {
setIsSubmitting(true)
// Submit review to API
await submitReview(eventId, rating, review)
// Add the new review to the list
const newReview = {
name: "You",
rating,
comment: review,
}
setEvent((prev) => ({
...prev,
reviews: [...prev.reviews, newReview],
}))
// Reset form
setRating(0)
setReview("")
alert("Review submitted successfully!")
} catch (err) {
console.error("Error submitting review:", err)
alert("Failed to submit review. Please try again.")
} finally {
setIsSubmitting(false)
}
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading event: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="min-h-screen flex flex-col">
<AppHeader />
<main className="flex-1 p-6">
<div className="max-w-5xl mx-auto">
<h1 className="text-3xl font-bold mb-6">{event.title}</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
<div className="md:col-span-2">
{/* Map View */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image
src={event.mapImage || "/placeholder.svg"}
alt="Event location map"
fill
className="object-cover"
/>
<div className="absolute bottom-2 left-2 bg-black text-white p-1 rounded-full">
<MapPin className="h-5 w-5" />
</div>
</div>
{/* Gallery */}
<div className="relative aspect-[2/1] mb-6 rounded-lg overflow-hidden">
<Image
src={event.galleryImages[0] || "/placeholder.svg"}
alt="Event gallery"
fill
className="object-cover"
/>
</div>
{/* Description */}
<div className="mb-6">
<p className="text-sm leading-relaxed">{event.description}</p>
</div>
{/* Reviews Section */}
<div className="mb-6">
<h2 className="text-xl font-bold mb-4">Reviews ({event.reviews.length}):</h2>
<div className="space-y-4 mb-6">
{event.reviews.map((review, index) => (
<Card key={index}>
<CardContent className="p-4">
<div className="flex justify-between items-start mb-2">
<h3 className="font-semibold">{review.name}</h3>
<div className="flex">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`h-4 w-4 ${
i < review.rating ? "fill-yellow-400 text-yellow-400" : "text-gray-300"
}`}
/>
))}
</div>
</div>
<p className="text-sm">{review.comment}</p>
</CardContent>
</Card>
))}
{event.reviews.length === 0 && (
<div className="bg-gray-100 p-4 rounded-lg text-center">No reviews yet.</div>
)}
</div>
{/* Leave a Review */}
<div>
<h3 className="text-lg font-semibold mb-3">Leave a review:</h3>
<div className="flex justify-center mb-4">
{[...Array(5)].map((_, i) => (
<Star
key={i}
className={`h-8 w-8 cursor-pointer ${
i < rating ? "fill-yellow-400 text-yellow-400" : "text-gray-300"
}`}
onClick={() => setRating(i + 1)}
/>
))}
</div>
<Textarea
placeholder="Type a comment..."
className="mb-4"
value={review}
onChange={(e) => setReview(e.target.value)}
disabled={isSubmitting}
/>
<div className="flex justify-end">
<Button className="bg-black hover:bg-gray-700" onClick={handleSubmitReview} disabled={isSubmitting}>
{isSubmitting ? (
<div className="flex items-center justify-center">
<div className="animate-spin h-5 w-5 border-t-2 border-b-2 border-white rounded-full mr-2"></div>
Posting...
</div>
) : (
"Post"
)}
</Button>
</div>
</div>
</div>
</div>
<div>
<Card className="mb-4">
<CardContent className="p-4">
<div className="mb-4">
<div className="font-semibold mb-2">Date:</div>
<div className="mb-4">
<div className="bg-gray-100 p-2 rounded">
<div className="text-center border-b pb-1 mb-2">{event.date}</div>
<div className="grid grid-cols-7 text-center text-xs">
<div>Su</div>
<div>Mo</div>
<div>Tu</div>
<div>We</div>
<div>Th</div>
<div>Fr</div>
<div>Sa</div>
</div>
<div className="grid grid-cols-7 text-center text-xs gap-1 mt-1">
{[...Array(28)].map((_, i) => (
<div key={i} className={`p-1 ${i + 1 === 11 ? "bg-black text-white rounded-full" : ""}`}>
{i + 1}
</div>
))}
</div>
</div>
</div>
<div className="font-semibold mb-2">Location:</div>
<div className="mb-4">
<div className="border rounded p-2 text-center text-sm">{event.location}</div>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</main>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { EventCard } from "@/components/event-card"
import { AppHeader } from "@/components/app-header"
import { getUserTickets, fetchEventById } from "@/api"
export default function PastEventsPage() {
const router = useRouter()
const [events, setEvents] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const loadPastEvents = async () => {
try {
setIsLoading(true)
// Verificăm autentificarea
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
return
}
// Obținem biletele utilizatorului
const tickets = await getUserTickets()
// Filtrăm biletele pentru evenimente care au trecut
const today = new Date()
const pastEvents = []
// Pentru fiecare bilet, obținem detaliile evenimentului
for (const ticket of tickets) {
try {
const eventId = ticket.Event_ID
const eventDetails = await fetchEventById(eventId)
// Verificăm dacă data evenimentului a trecut
const eventDate = new Date(eventDetails.Date || eventDetails.date)
if (eventDate < today) {
pastEvents.push({
title: eventDetails.Title || eventDetails.title,
priceRange: `${eventDetails.Price || 0}€`,
location: eventDetails.Location || eventDetails.location,
date: `Time: ${new Date(eventDetails.Date || eventDetails.date).toLocaleDateString()}`,
image: "/placeholder.svg?height=200&width=200",
slug: eventId.toString(),
})
}
} catch (err) {
console.error(`Error fetching event details for ticket ${ticket.ID}:`, err)
}
}
setEvents(pastEvents)
} catch (err) {
console.error("Error loading past events:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadPastEvents()
}, [router])
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading past events: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AppHeader />
<main className="flex-1 p-6">
<h1 className="text-2xl font-bold mb-6">Past Events</h1>
{events.length > 0 ? (
<div className="space-y-4">
{events.map((event, index) => (
<div key={index} onClick={() => router.push(`/past-events/${event.slug}`)} className="cursor-pointer">
<EventCard
title={event.title}
priceRange={event.priceRange}
location={event.location}
date={event.date}
image={event.image}
/>
</div>
))}
</div>
) : (
<div className="text-center py-8">
<p>You haven't attended any past events yet.</p>
<p className="mt-2">Check out upcoming events and get your tickets!</p>
</div>
)}
</main>
</div>
)
}
"use client"
import { useEffect, useState } from "react"
import { Download, Share } from "lucide-react"
import { useRouter, useParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { AppHeader } from "@/components/app-header"
import { getTicketById } from "@/api"
// Default ticket data
const defaultTicketData = {
eventName: "Event Not Found",
description: "The ticket you're looking for could not be found.",
ticketType: "Unknown",
price: "N/A",
}
export default function TicketPage() {
const router = useRouter()
const params = useParams()
const ticketId = params?.id as string
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const [ticketData, setTicketData] = useState({
...defaultTicketData,
ticketId: ticketId || "",
purchaseDate: new Date().toLocaleDateString(),
eventDate: "N/A",
})
const [uniqueCode, setUniqueCode] = useState("")
useEffect(() => {
const loadTicketData = async () => {
if (!ticketId) return
try {
setIsLoading(true)
// Check authentication
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
return
}
// Fetch ticket data from API
const ticket = await getTicketById(ticketId)
// In a real app, we would fetch event details for the ticket
// For now, we'll use some default values
setTicketData({
eventName: `Event #${ticket.Event_ID}`,
description: "Thank you for purchasing a ticket to this event.",
ticketType: "General Admission",
price: "€50",
ticketId: ticket.ID.toString(),
purchaseDate: new Date(ticket.Purchase_date).toLocaleDateString(),
eventDate: "Event Date: TBA",
})
// Generate unique code
const purchaseDate = new Date(ticket.Purchase_date).toISOString().split("T")[0].replace(/-/g, "")
const uniqueCode = `TIX-${purchaseDate}-${ticket.Event_ID}-${ticket.ID}`
setUniqueCode(uniqueCode)
} catch (err) {
console.error("Error loading ticket:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadTicketData()
}, [ticketId, router])
const handleDownloadTicket = () => {
alert("Ticket download functionality would be implemented here")
}
const handleShareTicket = () => {
alert("Ticket sharing functionality would be implemented here")
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading ticket: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="min-h-screen flex flex-col">
<AppHeader />
<main className="flex-1 p-6">
<div className="max-w-2xl mx-auto">
<Card className="overflow-hidden">
<CardContent className="p-6">
<h2 className="text-2xl font-bold mb-4">Ticket for {ticketData.eventName}</h2>
<p className="text-sm mb-6">{ticketData.description}</p>
<div className="flex justify-center mb-6">
<div className="p-4 bg-gray-100 rounded-lg text-center">
<h3 className="text-lg font-semibold mb-2">Unique Ticket Code</h3>
<p className="font-mono text-lg bg-white p-3 border border-gray-300 rounded">{uniqueCode}</p>
<p className="text-xs text-gray-500 mt-2">Please present this code at the venue</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4 mb-6">
<div>
<p className="text-sm text-gray-500">Ticket ID</p>
<p className="font-medium">{ticketData.ticketId}</p>
</div>
<div>
<p className="text-sm text-gray-500">Purchase Date</p>
<p className="font-medium">{ticketData.purchaseDate}</p>
</div>
<div>
<p className="text-sm text-gray-500">Event Date</p>
<p className="font-medium">{ticketData.eventDate}</p>
</div>
<div>
<p className="text-sm text-gray-500">Ticket Type</p>
<p className="font-medium">{ticketData.ticketType}</p>
</div>
<div>
<p className="text-sm text-gray-500">Price</p>
<p className="font-medium">{ticketData.price}</p>
</div>
</div>
<div className="flex gap-4">
<Button variant="outline" className="flex-1 hover:bg-gray-300" onClick={handleDownloadTicket}>
<Download className="h-4 w-4 mr-2" />
Download
</Button>
<Button variant="outline" className="flex-1 hover:bg-gray-300" onClick={handleShareTicket}>
<Share className="h-4 w-4 mr-2" />
Share
</Button>
</div>
</CardContent>
</Card>
</div>
</main>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { EventCard } from "@/components/event-card"
import { AppHeader } from "@/components/app-header"
export default function TrackedEventsPage() {
const router = useRouter()
const [events, setEvents] = useState<any[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const loadTrackedEvents = async () => {
try {
setIsLoading(true)
// Verificăm autentificarea
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
return
}
// În mod normal, am apela API-ul pentru a obține evenimentele urmărite
// const trackedEvents = await getTrackedEvents();
// Pentru demo, folosim localStorage
const storedEvents = localStorage.getItem("trackedEvents")
if (storedEvents) {
setEvents(JSON.parse(storedEvents))
}
} catch (err) {
console.error("Error loading tracked events:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadTrackedEvents()
}, [router])
const handleUntrackEvent = async (eventId: string) => {
try {
// În mod normal, am apela API-ul pentru a anula urmărirea evenimentului
// await untrackEvent(eventId);
// Pentru demo, folosim localStorage
const updatedEvents = events.filter((event) => event.id !== eventId)
setEvents(updatedEvents)
localStorage.setItem("trackedEvents", JSON.stringify(updatedEvents))
alert("Event has been removed from your tracked events.")
} catch (err) {
console.error("Error untracking event:", err)
alert("Could not untrack event. Please try again.")
}
}
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-black"></div>
</div>
)
}
if (error) {
return (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mx-auto max-w-md mt-10">
<p>Error loading tracked events: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AppHeader />
<main className="flex-1 p-6">
<h1 className="text-2xl font-bold mb-6">Tracked Events</h1>
{events.length === 0 ? (
<div className="text-center py-10">
<p className="text-gray-500 mb-4">You haven't tracked any events yet.</p>
<button onClick={() => router.push("/events")} className="text-blue-600 hover:underline">
Browse events
</button>
</div>
) : (
<div className="space-y-4">
{events.map((event, index) => (
<div key={index} className="relative">
<EventCard
title={event.title}
priceRange={event.priceRange}
location={event.location}
date={event.date}
image={event.image}
id={event.id}
/>
<div className="absolute bottom-4 right-4">
<button
onClick={() => handleUntrackEvent(event.id)}
className="px-4 py-2 bg-red-600 text-white text-sm font-medium rounded-md hover:bg-red-700"
>
Untrack Event
</button>
</div>
</div>
))}
</div>
)}
</main>
</div>
)
}
from sqlalchemy.orm import Session
from . import models, schemas
from datetime import date
# User operations
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.ID == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.Email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
# În producție, ar trebui să hașezi parola
db_user = models.User(Username=user.Username, Email=user.Email, Password=user.Password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
# Event operations
def get_event(db: Session, event_id: int):
return db.query(models.Event).filter(models.Event.ID == event_id).first()
def get_events(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Event).offset(skip).limit(limit).all()
def create_event(db: Session, event: schemas.EventCreate):
db_event = models.Event(**event.dict())
db.add(db_event)
db.commit()
db.refresh(db_event)
return db_event
# Ticket operations
def get_ticket(db: Session, ticket_id: int):
return db.query(models.Ticket).filter(models.Ticket.ID == ticket_id).first()
def get_user_tickets(db: Session, user_id: int, skip: int = 0, limit: int = 100):
return db.query(models.Ticket).filter(models.Ticket.User_ID == user_id).offset(skip).limit(limit).all()
def create_ticket(db: Session, ticket: schemas.TicketCreate):
db_ticket = models.Ticket(**ticket.dict(), Purchase_date=date.today())
db.add(db_ticket)
db.commit()
db.refresh(db_ticket)
return db_ticket
# Rating operations
def get_rating(db: Session, rating_id: int):
return db.query(models.Rating).filter(models.Rating.ID == rating_id).first()
def get_event_ratings(db: Session, event_id: int, skip: int = 0, limit: int = 100):
return db.query(models.Rating).filter(models.Rating.Event_ID == event_id).offset(skip).limit(limit).all()
def create_rating(db: Session, rating: schemas.RatingCreate):
db_rating = models.Rating(**rating.dict())
db.add(db_rating)
db.commit()
db.refresh(db_rating)
return db_rating
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# Configurare pentru MySQL local
# Modifică aceste detalii pentru a se potrivi cu configurația ta phpMyAdmin
SQLALCHEMY_DATABASE_URL = "mysql+pymysql://root:@localhost/webapp"
# Dacă ai o parolă pentru utilizatorul root, modifică URL-ul astfel:
# SQLALCHEMY_DATABASE_URL = "mysql+pymysql://root:parola_ta@localhost/webapp"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from fastapi.middleware.cors import CORSMiddleware
from . import crud, models, schemas
from .database import engine, get_db
from typing import List
# Creează tabelele în baza de date dacă nu există
# models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Configurare CORS pentru a permite cereri de la frontend
origins = [
"http://localhost:3000",
"http://localhost:8080",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# User routes
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.Email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.get("/users/", response_model=List[schemas.User])
def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_users(db, skip=skip, limit=limit)
return users
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
# Event routes
@app.post("/events/", response_model=schemas.Event)
def create_event(event: schemas.EventCreate, db: Session = Depends(get_db)):
return crud.create_event(db=db, event=event)
@app.get("/events/", response_model=List[schemas.Event])
def read_events(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
events = crud.get_events(db, skip=skip, limit=limit)
return events
@app.get("/events/{event_id}", response_model=schemas.Event)
def read_event(event_id: int, db: Session = Depends(get_db)):
db_event = crud.get_event(db, event_id=event_id)
if db_event is None:
raise HTTPException(status_code=404, detail="Event not found")
return db_event
# Ticket routes
@app.post("/tickets/", response_model=schemas.Ticket)
def create_ticket(ticket: schemas.TicketCreate, db: Session = Depends(get_db)):
return crud.create_ticket(db=db, ticket=ticket)
@app.get("/tickets/user/{user_id}", response_model=List[schemas.Ticket])
def read_user_tickets(user_id: int, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
tickets = crud.get_user_tickets(db, user_id=user_id, skip=skip, limit=limit)
return tickets
@app.get("/tickets/{ticket_id}", response_model=schemas.Ticket)
def read_ticket(ticket_id: int, db: Session = Depends(get_db)):
db_ticket = crud.get_ticket(db, ticket_id=ticket_id)
if db_ticket is None:
raise HTTPException(status_code=404, detail="Ticket not found")
return db_ticket
# Rating routes
@app.post("/ratings/", response_model=schemas.Rating)
def create_rating(rating: schemas.RatingCreate, db: Session = Depends(get_db)):
return crud.create_rating(db=db, rating=rating)
@app.get("/ratings/event/{event_id}", response_model=List[schemas.Rating])
def read_event_ratings(event_id: int, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
ratings = crud.get_event_ratings(db, event_id=event_id, skip=skip, limit=limit)
return ratings
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Date, Float
from sqlalchemy.orm import relationship
from .database import Base
class Admin(Base):
__tablename__ = "Admin"
ID = Column(Integer, primary_key=True, index=True)
Username = Column(String(80), unique=True, index=True)
Email = Column(String(80), unique=True, index=True)
Password = Column(String(80))
events = relationship("Event", back_populates="admin")
class User(Base):
__tablename__ = "User"
ID = Column(Integer, primary_key=True, index=True)
Username = Column(String(80), unique=True, index=True)
Email = Column(String(80), unique=True, index=True)
Password = Column(String(80))
tickets = relationship("Ticket", back_populates="user")
ratings = relationship("Rating", back_populates="user")
class Event(Base):
__tablename__ = "Event"
ID = Column(Integer, primary_key=True, index=True)
Title = Column(String(100), index=True)
Description = Column(String(250))
Date = Column(Date)
Location = Column(String(80))
Price = Column(Integer)
admin_id = Column(Integer, ForeignKey("Admin.ID"))
admin = relationship("Admin", back_populates="events")
tickets = relationship("Ticket", back_populates="event")
ratings = relationship("Rating", back_populates="event")
class Ticket(Base):
__tablename__ = "Ticket"
ID = Column(Integer, primary_key=True, index=True)
User_ID = Column(Integer, ForeignKey("User.ID"))
Event_ID = Column(Integer, ForeignKey("Event.ID"))
Purchase_date = Column(Date)
user = relationship("User", back_populates="tickets")
event = relationship("Event", back_populates="tickets")
class Rating(Base):
__tablename__ = "Rating"
ID = Column(Integer, primary_key=True, index=True)
User_ID = Column(Integer, ForeignKey("User.ID"))
Event_ID = Column(Integer, ForeignKey("Event.ID"))
Stars = Column(Integer)
Comment = Column(String(250))
user = relationship("User", back_populates="ratings")
event = relationship("Event", back_populates="ratings")
from pydantic import BaseModel
from typing import Optional, List
from datetime import date
# Admin schemas
class AdminBase(BaseModel):
Username: str
Email: str
class AdminCreate(AdminBase):
Password: str
class Admin(AdminBase):
ID: int
class Config:
orm_mode = True
# User schemas
class UserBase(BaseModel):
Username: str
Email: str
class UserCreate(UserBase):
Password: str
class User(UserBase):
ID: int
class Config:
orm_mode = True
# Event schemas
class EventBase(BaseModel):
Title: str
Description: str
Date: date
Location: str
Price: int
class EventCreate(EventBase):
admin_id: int
class Event(EventBase):
ID: int
admin_id: int
class Config:
orm_mode = True
# Ticket schemas
class TicketBase(BaseModel):
User_ID: int
Event_ID: int
class TicketCreate(TicketBase):
pass
class Ticket(TicketBase):
ID: int
Purchase_date: date
class Config:
orm_mode = True
# Rating schemas
class RatingBase(BaseModel):
User_ID: int
Event_ID: int
Stars: int
Comment: str
class RatingCreate(RatingBase):
pass
class Rating(RatingBase):
ID: int
class Config:
orm_mode = True
from sqlalchemy import create_engine, text
# Modifică aceste detalii pentru a se potrivi cu configurația ta phpMyAdmin
DATABASE_URL = "mysql+pymysql://root:@localhost/webapp"
def test_connection():
try:
# Creează un engine de test
engine = create_engine(DATABASE_URL)
# Încearcă să te conectezi și să execuți o interogare simplă
with engine.connect() as connection:
result = connection.execute(text("SELECT 1"))
print("Conexiune reușită la baza de date!")
# Verifică tabelele existente
tables = connection.execute(text("SHOW TABLES"))
print("Tabele existente în baza de date:")
for table in tables:
print(f"- {table[0]}")
return True
except Exception as e:
print(f"Eroare la conectarea la baza de date: {e}")
return False
if __name__ == "__main__":
test_connection()
from sqlalchemy.orm import Session
from datetime import date, datetime, timedelta
from passlib.context import CryptContext
from jose import JWTError, jwt
from typing import Optional
from . import models, schemas
# Configurare pentru hashing parole
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Configurare JWT
SECRET_KEY = "your-secret-key" # Ar trebui să fie o cheie secretă generată aleatoriu
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# User operations
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.ID == user_id).first()
def get_user_by_email(db: Session, email: str):
return db.query(models.User).filter(models.User.Email == email).first()
def get_users(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate):
hashed_password = pwd_context.hash(user.Password)
db_user = models.User(Username=user.Username, Email=user.Email, Password=hashed_password)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
def authenticate_user(db: Session, email: str, password: str):
user = get_user_by_email(db, email)
if not user:
return False
if not pwd_context.verify(password, user.Password):
return False
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# Event operations
def get_event(db: Session, event_id: int):
return db.query(models.Event).filter(models.Event.ID == event_id).first()
def get_events(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Event).offset(skip).limit(limit).all()
def get_past_events(db: Session, skip: int = 0, limit: int = 100):
today = date.today()
return db.query(models.Event).filter(models.Event.Date < today).offset(skip).limit(limit).all()
def create_event(db: Session, event: schemas.EventCreate):
db_event = models.Event(**event.dict())
db.add(db_event)
db.commit()
db.refresh(db_event)
return db_event
# Ticket operations
def get_ticket(db: Session, ticket_id: int):
return db.query(models.Ticket).filter(models.Ticket.ID == ticket_id).first()
def get_user_tickets(db: Session, user_id: int, skip: int = 0, limit: int = 100):
return db.query(models.Ticket).filter(models.Ticket.User_ID == user_id).offset(skip).limit(limit).all()
def create_ticket(db: Session, ticket: schemas.TicketCreate):
db_ticket = models.Ticket(**ticket.dict(), Purchase_date=date.today())
db.add(db_ticket)
db.commit()
db.refresh(db_ticket)
return db_ticket
# Rating operations
def get_rating(db: Session, rating_id: int):
return db.query(models.Rating).filter(models.Rating.ID == rating_id).first()
def get_event_ratings(db: Session, event_id: int, skip: int = 0, limit: int = 100):
return db.query(models.Rating).filter(models.Rating.Event_ID == event_id).offset(skip).limit(limit).all()
def create_rating(db: Session, rating: schemas.RatingCreate):
db_rating = models.Rating(**rating.dict())
db.add(db_rating)
db.commit()
db.refresh(db_rating)
return db_rating
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
# Configurare pentru MySQL local
# Obține URL-ul bazei de date din variabilele de mediu sau folosește valoarea implicită
DATABASE_URL = os.getenv("DATABASE_URL", "mysql+pymysql://root:@localhost/webapp")
# Creează engine-ul SQLAlchemy
engine = create_engine(DATABASE_URL)
# Creează o clasă de sesiune
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Creează o clasă de bază pentru modele
Base = declarative_base()
# Dependency pentru a obține o sesiune de bază de date
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from fastapi.middleware.cors import CORSMiddleware
from datetime import timedelta
from typing import List
from jose import JWTError, jwt
import os
from datetime import datetime
from . import crud, models, schemas
from .database import engine, get_db
# Creează tabelele în baza de date dacă nu există
# models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# Configurare CORS pentru a permite cereri de la frontend
origins = [
"http://localhost:3000",
"http://localhost:8080",
"*", # Permite toate originile în dezvoltare - NU FOLOSI ÎN PRODUCȚIE!
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Configurare JWT
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-for-development-only")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
# Funcție pentru a verifica token-ul JWT
async def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
email: str = payload.get("sub")
if email is None:
raise credentials_exception
token_exp = payload.get("exp")
if token_exp is None:
raise credentials_exception
if datetime.utcnow() > datetime.fromtimestamp(token_exp):
raise credentials_exception
except JWTError:
raise credentials_exception
user = crud.get_user_by_email(db, email=email)
if user is None:
raise credentials_exception
return user
# User routes
@app.post("/users/register", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.get_user_by_email(db, email=user.Email)
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
return crud.create_user(db=db, user=user)
@app.post("/users/login", response_model=schemas.UserToken)
def login_for_access_token(form_data: schemas.UserLogin, db: Session = Depends(get_db)):
user = crud.authenticate_user(db, form_data.Email, form_data.Password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = crud.create_access_token(
data={"sub": user.Email}, expires_delta=access_token_expires
)
return {"ID": user.ID, "Username": user.Username, "Email": user.Email, "token": access_token}
@app.get("/users/profile", response_model=schemas.User)
def read_user_profile(current_user: models.User = Depends(get_current_user)):
return current_user
# Event routes
@app.get("/events/", response_model=List[schemas.Event])
def read_events(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
events = crud.get_events(db, skip=skip, limit=limit)
return events
@app.get("/events/{event_id}", response_model=schemas.Event)
def read_event(event_id: int, db: Session = Depends(get_db)):
db_event = crud.get_event(db, event_id=event_id)
if db_event is None:
raise HTTPException(status_code=404, detail="Event not found")
return db_event
@app.get("/events/slug/{slug}", response_model=schemas.Event)
def read_event_by_slug(slug: str, db: Session = Depends(get_db)):
# Implementare simplă - în realitate, ar trebui să ai un câmp slug în baza de date
# Sau să convertești titlul în slug
title_words = slug.replace('-', ' ').split()
events = db.query(models.Event).all()
for event in events:
event_title_lower = event.Title.lower()
match = True
for word in title_words:
if word.lower() not in event_title_lower:
match = False
break
if match:
return event
raise HTTPException(status_code=404, detail="Event not found")
@app.get("/events/past", response_model=List[schemas.Event])
def read_past_events(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
events = crud.get_past_events(db, skip=skip, limit=limit)
return events
# Ticket routes
@app.post("/tickets/", response_model=schemas.Ticket)
def create_ticket(ticket: schemas.TicketCreate, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)):
# Asigură-te că User_ID este cel al utilizatorului autentificat
if ticket.User_ID != current_user.ID:
raise HTTPException(status_code=403, detail="Not authorized to create tickets for other users")
return crud.create_ticket(db=db, ticket=ticket)
@app.get("/tickets/user", response_model=List[schemas.Ticket])
def read_user_tickets(current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)):
tickets = crud.get_user_tickets(db, user_id=current_user.ID)
return tickets
@app.get("/tickets/{ticket_id}", response_model=schemas.Ticket)
def read_ticket(ticket_id: int, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)):
db_ticket = crud.get_ticket(db, ticket_id=ticket_id)
if db_ticket is None:
raise HTTPException(status_code=404, detail="Ticket not found")
if db_ticket.User_ID != current_user.ID:
raise HTTPException(status_code=403, detail="Not authorized to access this ticket")
return db_ticket
# Rating routes
@app.post("/ratings/", response_model=schemas.Rating)
def create_rating(rating: schemas.RatingCreate, current_user: models.User = Depends(get_current_user), db: Session = Depends(get_db)):
# Asigură-te că User_ID este cel al utilizatorului autentificat
if rating.User_ID != current_user.ID:
raise HTTPException(status_code=403, detail="Not authorized to create ratings for other users")
return crud.create_rating(db=db, rating=rating)
@app.get("/ratings/event/{event_id}", response_model=List[schemas.Rating])
def read_event_ratings(event_id: int, skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
ratings = crud.get_event_ratings(db, event_id=event_id, skip=skip, limit=limit)
return ratings
# Endpoint pentru verificarea stării serverului
@app.get("/health")
def health_check():
return {"status": "ok", "timestamp": datetime.now().isoformat()}
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Date, Float
from sqlalchemy.orm import relationship
from .database import Base
class Admin(Base):
__tablename__ = "Admin"
ID = Column(Integer, primary_key=True, index=True)
Username = Column(String(80), unique=True, index=True)
Email = Column(String(80), unique=True, index=True)
Password = Column(String(80))
events = relationship("Event", back_populates="admin")
class User(Base):
__tablename__ = "User"
ID = Column(Integer, primary_key=True, index=True)
Username = Column(String(80), unique=True, index=True)
Email = Column(String(80), unique=True, index=True)
Password = Column(String(80))
tickets = relationship("Ticket", back_populates="user")
ratings = relationship("Rating", back_populates="user")
class Event(Base):
__tablename__ = "Event"
ID = Column(Integer, primary_key=True, index=True)
Title = Column(String(100), index=True)
Description = Column(String(250))
Date = Column(Date)
Location = Column(String(80))
Price = Column(Integer)
admin_id = Column(Integer, ForeignKey("Admin.ID"))
admin = relationship("Admin", back_populates="events")
tickets = relationship("Ticket", back_populates="event")
ratings = relationship("Rating", back_populates="event")
class Ticket(Base):
__tablename__ = "Ticket"
ID = Column(Integer, primary_key=True, index=True)
User_ID = Column(Integer, ForeignKey("User.ID"))
Event_ID = Column(Integer, ForeignKey("Event.ID"))
Purchase_date = Column(Date)
user = relationship("User", back_populates="tickets")
event = relationship("Event", back_populates="tickets")
class Rating(Base):
__tablename__ = "Rating"
ID = Column(Integer, primary_key=True, index=True)
User_ID = Column(Integer, ForeignKey("User.ID"))
Event_ID = Column(Integer, ForeignKey("Event.ID"))
Stars = Column(Integer)
Comment = Column(String(250))
user = relationship("User", back_populates="ratings")
event = relationship("Event", back_populates="ratings")
fastapi==0.95.0
uvicorn==0.21.1
sqlalchemy==2.0.7
pymysql==1.0.3
pydantic==1.10.7
python-jose==3.3.0
passlib==1.7.4
python-multipart==0.0.6
bcrypt==4.0.1
python-dotenv==1.0.0
import uvicorn
import os
if __name__ == "__main__":
# Obține portul din variabilele de mediu sau folosește valoarea implicită
port = int(os.getenv("PORT", 8000))
# Obține host-ul din variabilele de mediu sau folosește valoarea implicită
host = os.getenv("HOST", "0.0.0.0")
# Pornește serverul
uvicorn.run("main:app", host=host, port=port, reload=True)
from pydantic import BaseModel
from typing import Optional, List
from datetime import date
# Admin schemas
class AdminBase(BaseModel):
Username: str
Email: str
class AdminCreate(AdminBase):
Password: str
class Admin(AdminBase):
ID: int
class Config:
orm_mode = True
# User schemas
class UserBase(BaseModel):
Username: str
Email: str
class UserCreate(UserBase):
Password: str
class UserLogin(BaseModel):
Email: str
Password: str
class User(UserBase):
ID: int
class Config:
orm_mode = True
class UserToken(User):
token: str
# Event schemas
class EventBase(BaseModel):
Title: str
Description: str
Date: date
Location: str
Price: int
class EventCreate(EventBase):
admin_id: int
class Event(EventBase):
ID: int
admin_id: int
class Config:
orm_mode = True
# Ticket schemas
class TicketBase(BaseModel):
User_ID: int
Event_ID: int
class TicketCreate(TicketBase):
pass
class Ticket(TicketBase):
ID: int
Purchase_date: date
class Config:
orm_mode = True
class TicketWithEvent(Ticket):
event: Event
# Rating schemas
class RatingBase(BaseModel):
User_ID: int
Event_ID: int
Stars: int
Comment: str
class RatingCreate(RatingBase):
pass
class Rating(RatingBase):
ID: int
class Config:
orm_mode = True
class RatingWithUser(Rating):
user: User
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from datetime import date, timedelta
import os
from passlib.context import CryptContext
from models import Base, Admin, User, Event, Ticket, Rating
from database import engine
# Configurare pentru hashing parole
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def seed_database():
# Creează tabelele dacă nu există
Base.metadata.create_all(bind=engine)
# Creează o sesiune
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = SessionLocal()
try:
# Verifică dacă există deja date în baza de date
admin_count = db.query(Admin).count()
user_count = db.query(User).count()
event_count = db.query(Event).count()
if admin_count > 0 and user_count > 0 and event_count > 0:
print("Baza de date conține deja date. Nu se adaugă date de test.")
return
# Adaugă un admin dacă nu există
if admin_count == 0:
admin = Admin(
Username="admin",
Email="admin@example.com",
Password=pwd_context.hash("admin")
)
db.add(admin)
db.commit()
print("Admin adăugat cu succes.")
# Adaugă utilizatori dacă nu există
if user_count == 0:
users = [
User(
Username="user1",
Email="user1@example.com",
Password=pwd_context.hash("password1")
),
User(
Username="user2",
Email="user2@example.com",
Password=pwd_context.hash("password2")
)
]
db.add_all(users)
db.commit()
print("Utilizatori adăugați cu succes.")
# Adaugă evenimente dacă nu există
if event_count == 0:
today = date.today()
events = [
Event(
Title="Summer Music Festival",
Description="The biggest music festival of the year with top artists from around the world.",
Date=today + timedelta(days=30),
Location="Central Park, New York",
Price=100,
admin_id=1
),
Event(
Title="Tech Conference 2025",
Description="Join industry leaders and innovators at the premier tech event of the year.",
Date=today + timedelta(days=60),
Location="Convention Center, San Francisco",
Price=200,
admin_id=1
),
Event(
Title="Food Festival",
Description="Taste cuisines from over 50 countries in one amazing weekend event.",
Date=today + timedelta(days=90),
Location="Downtown, Chicago",
Price=50,
admin_id=1
)
]
db.add_all(events)
db.commit()
print("Evenimente adăugate cu succes.")
# Adaugă bilete și recenzii pentru evenimente
users = db.query(User).all()
events = db.query(Event).all()
if len(users) > 0 and len(events) > 0:
# Adaugă bilete
tickets = [
Ticket(
User_ID=users[0].ID,
Event_ID=events[0].ID,
Purchase_date=date.today()
),
Ticket(
User_ID=users[1].ID,
Event_ID=events[0].ID,
Purchase_date=date.today()
),
Ticket(
User_ID=users[0].ID,
Event_ID=events[1].ID,
Purchase_date=date.today()
)
]
db.add_all(tickets)
db.commit()
print("Bilete adăugate cu succes.")
# Adaugă recenzii
ratings = [
Rating(
User_ID=users[0].ID,
Event_ID=events[0].ID,
Stars=5,
Comment="Amazing event! Would definitely go again."
),
Rating(
User_ID=users[1].ID,
Event_ID=events[0].ID,
Stars=4,
Comment="Great music and atmosphere."
)
]
db.add_all(ratings)
db.commit()
print("Recenzii adăugate cu succes.")
print("Baza de date a fost populată cu succes cu date de test.")
except Exception as e:
print(f"Eroare la popularea bazei de date: {e}")
db.rollback()
finally:
db.close()
if __name__ == "__main__":
seed_database()
from sqlalchemy import create_engine, text
import os
# Obține URL-ul bazei de date din variabilele de mediu sau folosește valoarea implicită
DATABASE_URL = os.getenv("DATABASE_URL", "mysql+pymysql://root:@localhost/webapp")
def test_connection():
try:
# Creează un engine de test
engine = create_engine(DATABASE_URL)
# Încearcă să te conectezi și să execuți o interogare simplă
with engine.connect() as connection:
result = connection.execute(text("SELECT 1"))
print("Conexiune reușită la baza de date!")
# Verifică tabelele existente
tables = connection.execute(text("SHOW TABLES"))
print("Tabele existente în baza de date:")
for table in tables:
print(f"- {table[0]}")
# Verifică structura tabelelor
for table_name in ["Admin", "User", "Event", "Ticket", "Rating"]:
print(f"\nStructura tabelului {table_name}:")
columns = connection.execute(text(f"DESCRIBE {table_name}"))
for column in columns:
print(f" - {column[0]}: {column[1]}")
return True
except Exception as e:
print(f"Eroare la conectarea la baza de date: {e}")
return False
if __name__ == "__main__":
test_connection()
#!/bin/bash
# Curăță cache-ul Next.js
rm -rf .next
rm -rf build
rm -rf node_modules/.cache
# Pornește aplicația
npm run dev
#!/bin/bash
# Oprește orice procese Next.js care rulează
echo "Oprirea proceselor Next.js..."
pkill -f "next"
# Șterge directoarele de cache și node_modules
echo "Ștergerea directoarelor de cache..."
rm -rf .next
rm -rf build
rm -rf node_modules
rm -rf .cache
# Reinstalează dependențele
echo "Reinstalarea dependențelor..."
npm install
echo "Curățare completă. Acum poți rula 'npm run dev' pentru a porni aplicația."
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
\ No newline at end of file
"use client"
import Link from "next/link"
import { Edit, MoreHorizontal, Trash } from "lucide-react"
import { useEffect, useState } from "react"
import { Button } from "@/components/ui/button"
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { deleteEvent } from "@/api"
export function AdminEventList({ events: initialEvents, onEventDeleted }) {
const [events, setEvents] = useState([])
useEffect(() => {
if (initialEvents && initialEvents.length > 0) {
// Adăugăm câmpul createdAt dacă nu există
const eventsWithCreatedAt = initialEvents.map((event) => ({
...event,
createdAt: event.createdAt || event.CreatedAt || new Date().toISOString(),
// Asigurăm-ne că prețul este afișat corect
price_range: event.price_range || `${event.Price || 0}€`,
}))
// Sortăm evenimentele de la cel mai nou la cel mai vechi
const sortedEvents = [...eventsWithCreatedAt].sort((a, b) => {
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
})
setEvents(sortedEvents)
} else {
setEvents([])
}
}, [initialEvents])
if (!events || events.length === 0) {
return <div className="text-center py-4">No events found</div>
}
// Get current admin ID from localStorage
const currentAdminId = Number(localStorage.getItem("userId") || "0")
// Function to check if admin can edit an event
const canEditEvent = (eventAdminId) => {
return currentAdminId === eventAdminId || currentAdminId === 0 // Admin ID 0 can edit all events
}
const handleDeleteEvent = async (eventId) => {
if (confirm("Are you sure you want to delete this event? This action cannot be undone.")) {
try {
await deleteEvent(eventId)
alert("Event deleted successfully!")
if (onEventDeleted) {
onEventDeleted(eventId)
}
} catch (error) {
console.error("Error deleting event:", error)
alert("Failed to delete event. Please try again.")
}
}
}
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Event Name</TableHead>
<TableHead>Date</TableHead>
<TableHead>Location</TableHead>
<TableHead>Price</TableHead>
<TableHead className="text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{events.map((event) => (
<TableRow key={event.id || event.ID}>
<TableCell>
<Link href={`/events/${event.id || event.ID}`} className="font-medium hover:underline">
{event.title || event.Title}
</Link>
</TableCell>
<TableCell>{event.date || (event.Date && new Date(event.Date).toLocaleDateString())}</TableCell>
<TableCell>{event.location || event.Location}</TableCell>
<TableCell>{event.price_range || `${event.Price || 0}€`}</TableCell>
<TableCell className="text-right">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{canEditEvent(event.admin_id) && (
<DropdownMenuItem asChild>
<Link href={`/admin/events/${event.id || event.ID}/edit`}>
<Edit className="mr-2 h-4 w-4" />
Edit
</Link>
</DropdownMenuItem>
)}
{canEditEvent(event.admin_id) && (
<DropdownMenuItem asChild>
<Link href={`/admin/events/${event.id || event.ID}/analytics`}>
<Edit className="mr-2 h-4 w-4" />
Analytics
</Link>
</DropdownMenuItem>
)}
{canEditEvent(event.admin_id) && (
<DropdownMenuItem className="text-red-600" onClick={() => handleDeleteEvent(event.id || event.ID)}>
<Trash className="mr-2 h-4 w-4" />
Delete
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}
"use client"
import Link from "next/link"
import { Menu } from "lucide-react"
import { useState } from "react"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
import { AdminSidebar } from "@/components/admin-sidebar"
import { MenuPopup } from "@/components/menu-popup"
interface AdminHeaderProps {
title?: string
}
export function AdminHeader({ title = "TickMeIn Admin" }: AdminHeaderProps) {
const [menuOpen, setMenuOpen] = useState(false)
const handleLogout = () => {
// Clear authentication data
localStorage.removeItem("isLoggedIn")
localStorage.removeItem("token")
localStorage.removeItem("userId")
localStorage.removeItem("username")
localStorage.removeItem("isAdmin")
// Redirect to login page using window.location for faster navigation
window.location.href = "/auth/signin"
}
const handleNavigation = (path) => {
// Folosim window.location pentru navigare mai rapidă
window.location.href = path
}
const adminMenuItems = [
{ label: "My Events", href: "/admin/my-events" },
{ label: "Create Event", href: "/admin/events/new" },
{ label: "Tracked Events", href: "/admin/tracked-events" },
{ label: "My Tickets", href: "/admin/my-tickets" },
]
return (
<header className="sticky top-0 z-50 flex h-16 items-center gap-4 border-b bg-black text-white px-4 md:px-6">
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" size="icon" className="shrink-0 md:hidden">
<Menu className="h-5 w-5" />
<span className="sr-only">Toggle navigation menu</span>
</Button>
</SheetTrigger>
<SheetContent side="left" className="w-72 p-0">
<AdminSidebar />
</SheetContent>
</Sheet>
<div className="flex-1 text-center">
<Link
href="/admin"
className="inline-flex items-center gap-2 font-semibold"
onClick={(e) => {
e.preventDefault()
handleNavigation("/admin")
}}
>
<span className="text-lg">{title}</span>
</Link>
</div>
<div className="flex items-center gap-4">
<Button variant="ghost" size="icon" className="text-white" onClick={() => setMenuOpen(!menuOpen)}>
<Menu className="h-5 w-5" />
</Button>
<Button
variant="ghost"
size="icon"
className="text-white"
onClick={(e) => {
e.preventDefault()
handleNavigation("/admin/profile")
}}
>
<div className="relative h-5 w-5 overflow-hidden rounded-full">
<Image src="/images/user-avatar.png" alt="User avatar" width={20} height={20} className="object-cover" />
</div>
</Button>
</div>
{menuOpen && <MenuPopup items={adminMenuItems} onClose={() => setMenuOpen(false)} onLogout={handleLogout} />}
</header>
)
}
"use client"
import { useState } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { Slider } from "@/components/ui/slider"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
interface AdminSidebarProps {
activePage?: string
}
export function AdminSidebar({ activePage }: AdminSidebarProps) {
const router = useRouter()
const [priceRange, setPriceRange] = useState([50])
const [eventType, setEventType] = useState("all")
return (
<aside className="w-[220px] bg-zinc-700 text-white fixed h-[calc(100vh-4rem)] overflow-y-auto">
<div className={`p-4 ${activePage === "my-events" ? "bg-cyan-500" : ""}`}>
<Link href="/admin/my-events" className="block w-full text-center font-medium">
My events
</Link>
</div>
<div className="p-4 bg-gray-500">
<div className="text-sm mb-4">Filter by...</div>
</div>
<div className="p-4 border-b border-zinc-600">
<label className="text-sm mb-2 block">Type of event</label>
<select
value={eventType}
onChange={(e) => setEventType(e.target.value)}
className="w-full bg-white text-black p-2 rounded text-sm"
>
<option value="all">All events</option>
<option value="concerts">Concerts</option>
<option value="festivals">Festivals</option>
<option value="sports">Sports</option>
<option value="theater">Theater</option>
<option value="comedy">Comedy</option>
</select>
</div>
<div className="p-4 border-b border-zinc-600">
<label className="text-sm mb-2 block">Price range (€)</label>
<div className="space-y-4">
<Slider
defaultValue={[50]}
max={300}
step={5}
value={priceRange}
onValueChange={setPriceRange}
className="py-4"
/>
<div className="flex justify-between text-sm">
<span>€0</span>
<span>{priceRange[0]}</span>
<span>€300</span>
</div>
</div>
</div>
<div className="p-4 space-y-4">
<div className="flex items-center space-x-2">
<Checkbox id="vegan" />
<Label htmlFor="vegan" className="text-sm text-white">
Vegan food choices
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="parking" />
<Label htmlFor="parking" className="text-sm text-white">
Parking spaces for disabilities
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="age" />
<Label htmlFor="age" className="text-sm text-white">
Age restriction
</Label>
</div>
</div>
</aside>
)
}
"use client"
import { useState, useEffect } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { Menu } from "lucide-react"
import { Button } from "@/components/ui/button"
import { MenuPopup } from "@/components/menu-popup"
import Image from "next/image"
export function AppHeader() {
const router = useRouter()
const [menuOpen, setMenuOpen] = useState(false)
const [username, setUsername] = useState("")
useEffect(() => {
// Încărcăm numele utilizatorului din localStorage
const storedUsername = localStorage.getItem("username")
if (storedUsername) {
setUsername(storedUsername)
}
}, [])
const handleLogout = () => {
// Ștergem toate datele de autentificare
localStorage.removeItem("isLoggedIn")
localStorage.removeItem("token")
localStorage.removeItem("userId")
localStorage.removeItem("username")
// Redirecționăm către pagina de autentificare folosind window.location pentru viteză
window.location.href = "/auth/signin"
}
const handleNavigation = (path) => {
// Folosim window.location pentru navigare mai rapidă
window.location.href = path
}
return (
<header className="sticky top-0 z-50 w-full bg-black text-white">
<div className="flex h-16 items-center justify-between px-4">
<div className="w-10"></div> {/* Spacer for centering */}
<div className="flex items-center gap-4">
<Link
href="/events"
onClick={(e) => {
e.preventDefault()
handleNavigation("/events")
}}
>
<h1 className="text-xl font-bold cursor-pointer hover:text-gray-300">TickMeIn</h1>
</Link>
</div>
<div className="flex items-center gap-2">
{username && <span className="text-sm hidden md:inline-block">Hello, {username}</span>}
<Button variant="ghost" size="icon" className="text-white" onClick={() => setMenuOpen(!menuOpen)}>
<Menu className="h-5 w-5" />
</Button>
<Button
variant="ghost"
size="icon"
className="text-white"
onClick={(e) => {
e.preventDefault()
handleNavigation("/account")
}}
>
<div className="relative h-5 w-5 overflow-hidden rounded-full">
<Image src="/images/user-avatar.png" alt="User avatar" width={20} height={20} className="object-cover" />
</div>
</Button>
</div>
</div>
{menuOpen && <MenuPopup onClose={() => setMenuOpen(false)} onLogout={handleLogout} />}
</header>
)
}
import Link from "next/link"
import { Music, Utensils, Trophy, Ticket, Palette, Mic, Landmark, Tent } from "lucide-react"
export function CategoryFilter() {
const categories = [
{
name: "Concerts",
icon: <Music className="h-6 w-6" />,
color: "bg-pink-100 text-pink-700",
href: "/events/category/concerts",
},
{
name: "Food & Drink",
icon: <Utensils className="h-6 w-6" />,
color: "bg-orange-100 text-orange-700",
href: "/events/category/food-drink",
},
{
name: "Sports",
icon: <Trophy className="h-6 w-6" />,
color: "bg-blue-100 text-blue-700",
href: "/events/category/sports",
},
{
name: "Theater",
icon: <Ticket className="h-6 w-6" />,
color: "bg-purple-100 text-purple-700",
href: "/events/category/theater",
},
{
name: "Art",
icon: <Palette className="h-6 w-6" />,
color: "bg-green-100 text-green-700",
href: "/events/category/art",
},
{
name: "Comedy",
icon: <Mic className="h-6 w-6" />,
color: "bg-yellow-100 text-yellow-700",
href: "/events/category/comedy",
},
{
name: "Conferences",
icon: <Landmark className="h-6 w-6" />,
color: "bg-red-100 text-red-700",
href: "/events/category/conferences",
},
{
name: "Festivals",
icon: <Tent className="h-6 w-6" />,
color: "bg-indigo-100 text-indigo-700",
href: "/events/category/festivals",
},
]
return (
<div className="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-8 gap-4">
{categories.map((category) => (
<Link
key={category.name}
href={category.href}
className="flex flex-col items-center justify-center p-4 rounded-lg hover:bg-muted transition-colors"
>
<div className={`p-3 rounded-full ${category.color} mb-2`}>{category.icon}</div>
<span className="text-sm font-medium text-center">{category.name}</span>
</Link>
))}
</div>
)
}
"use client"
import Image from "next/image"
import Link from "next/link"
import { Card, CardContent } from "@/components/ui/card"
interface EventCardProps {
id?: string
title: string
priceRange: string
location: string
date: string
image: string
category?: string
}
export function EventCard({ id, title, priceRange, location, date, image, category }: EventCardProps) {
// Creăm un ID din titlu dacă nu este furnizat
const eventId = id || title.toLowerCase().replace(/\s+/g, "-").replace(/[&.]/g, "")
// Asigurăm-ne că prețul este afișat corect
const displayPrice = priceRange === "Price not set" ? "0€" : priceRange
return (
<Card className="overflow-hidden">
<CardContent className="p-0">
<div className="flex flex-col md:flex-row">
<div className="w-full md:w-[150px] bg-gray-200 relative aspect-square md:aspect-auto">
<Image src={image || "/placeholder.svg"} alt={title} fill className="object-cover" />
</div>
<div className="p-4 flex-1">
<Link href={`/events/${eventId}`} className="hover:underline">
<h3 className="font-bold text-lg">{title}</h3>
</Link>
<div className="mt-2 space-y-1 text-sm">
<p>
<span className="font-semibold">Ticket prices may vary:</span> {displayPrice}
</p>
<p>
<span className="font-semibold">Location:</span> {location}
</p>
<p>
<span className="font-semibold">{date}</span>
</p>
</div>
</div>
</div>
</CardContent>
</Card>
)
}
export default EventCard
import Image from "next/image"
import Link from "next/link"
import { Card, CardContent } from "@/components/ui/card"
export default function EventList() {
const events = [
{
id: "1",
title: "Tickets to Untold Romania 2025",
priceRange: "70€ - 300€",
location: "Cluj Napoca, Romania",
timePeriod: "7 - 10 August",
image: "/placeholder.svg?height=100&width=100",
},
{
id: "2",
title: "Meet & Greet feat. Mario Casa",
priceRange: "15€ - 100€",
location: "Jaen, Spain",
timePeriod: "25th of February",
image: "/placeholder.svg?height=100&width=100",
},
{
id: "3",
title: "Beerpong Party",
priceRange: "5€",
location: "Comedy Club, London",
timePeriod: "1st of May",
image: "/placeholder.svg?height=100&width=100",
},
]
return (
<div className="space-y-4">
{events.map((event) => (
<Link key={event.id} href={`/events/${event.id}`}>
<Card className="overflow-hidden hover:shadow-md transition-shadow">
<CardContent className="p-0">
<div className="flex">
<div className="w-24 h-24 relative bg-gray-200">
<Image src={event.image || "/placeholder.svg"} alt={event.title} fill className="object-cover" />
</div>
<div className="flex-1 p-4">
<h3 className="font-semibold text-lg">{event.title}</h3>
<div className="mt-1 text-sm">
<div className="flex flex-col sm:flex-row sm:gap-4">
<div>
<span className="font-medium">Ticket prices:</span> {event.priceRange}
</div>
</div>
<div>
<span className="font-medium">Location:</span> {event.location}
</div>
<div>
<span className="font-medium">Time Period:</span> {event.timePeriod}
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</Link>
))}
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import Image from "next/image"
import Link from "next/link"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { useMobile } from "@/hooks/use-mobile"
export function FeaturedEvents() {
const [current, setCurrent] = useState(0)
const isMobile = useMobile()
const featuredEvents = [
{
id: "1",
title: "Summer Music Festival",
description: "The biggest music festival of the year with top artists from around the world.",
date: "Aug 15-17, 2023",
location: "Central Park, New York",
price: 89.99,
image: "/placeholder.svg?height=600&width=1200",
category: "Music",
},
{
id: "2",
title: "International Food Festival",
description: "Taste cuisines from over 50 countries in one amazing weekend event.",
date: "Aug 20-22, 2023",
location: "Pier 39, San Francisco",
price: 39.99,
image: "/placeholder.svg?height=600&width=1200",
category: "Food",
},
{
id: "3",
title: "Tech Conference 2023",
description: "Join industry leaders and innovators at the premier tech event of the year.",
date: "Sep 5-7, 2023",
location: "Convention Center, San Francisco",
price: 199.99,
image: "/placeholder.svg?height=600&width=1200",
category: "Conference",
},
]
const next = () => {
setCurrent((current + 1) % featuredEvents.length)
}
const previous = () => {
setCurrent((current - 1 + featuredEvents.length) % featuredEvents.length)
}
// Auto-advance carousel
useEffect(() => {
const timer = setTimeout(() => {
next()
}, 5000)
return () => clearTimeout(timer)
}, [current])
return (
<div className="relative">
<div className="overflow-hidden rounded-xl">
{featuredEvents.map((event, index) => (
<div
key={event.id}
className={`transition-all duration-500 ease-in-out ${index === current ? "block" : "hidden"}`}
>
<div className="relative">
<div className="relative aspect-[21/9] w-full overflow-hidden rounded-xl">
<Image src={event.image || "/placeholder.svg"} alt={event.title} fill className="object-cover" />
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent" />
</div>
<Card className="absolute bottom-4 left-4 right-4 md:bottom-8 md:left-8 md:right-auto md:max-w-md bg-background/80 backdrop-blur">
<CardContent className="p-4 md:p-6">
<div className="space-y-2">
<Badge>{event.category}</Badge>
<h3 className="text-xl font-bold md:text-2xl">{event.title}</h3>
<p className="text-sm text-muted-foreground line-clamp-2 md:line-clamp-3">{event.description}</p>
<div className="flex flex-wrap gap-2 text-sm">
<div className="text-muted-foreground">{event.date}</div>
<div className="text-muted-foreground"></div>
<div className="text-muted-foreground">{event.location}</div>
</div>
<div className="flex items-center justify-between pt-2">
<div className="font-semibold">${event.price.toFixed(2)}</div>
<Link href={`/events/${event.id}`}>
<Button size="sm">View Details</Button>
</Link>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
))}
</div>
<div className="absolute right-4 bottom-4 md:right-8 md:bottom-8 flex gap-2">
<Button
variant="secondary"
size="icon"
className="h-8 w-8 rounded-full bg-background/80 backdrop-blur"
onClick={previous}
>
<ChevronLeft className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
<Button
variant="secondary"
size="icon"
className="h-8 w-8 rounded-full bg-background/80 backdrop-blur"
onClick={next}
>
<ChevronRight className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
</div>
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-1">
{featuredEvents.map((_, index) => (
<Button
key={index}
variant="ghost"
size="icon"
className={`h-2 w-2 rounded-full ${index === current ? "bg-primary" : "bg-muted"}`}
onClick={() => setCurrent(index)}
>
<span className="sr-only">Go to slide {index + 1}</span>
</Button>
))}
</div>
</div>
)
}
"use client"
import Link from "next/link"
interface MenuItem {
label: string
href: string
}
interface MenuPopupProps {
onClose: () => void
onLogout?: () => void
items?: MenuItem[]
}
export function MenuPopup({ onClose, onLogout, items }: MenuPopupProps) {
const defaultItems = [
{ label: "My tickets", href: "/my-tickets" },
{ label: "Tracked events", href: "/tracked-events" },
{ label: "Past events", href: "/past-events" },
]
const menuItems = items || defaultItems
return (
<div className="absolute top-16 right-0 z-50 w-48 bg-gray-700 text-white shadow-lg rounded-bl-md overflow-hidden">
<div className="py-1">
{menuItems.map((item, index) => (
<Link key={index} href={item.href} className="block px-4 py-2 hover:bg-gray-600" onClick={onClose}>
{item.label}
</Link>
))}
<button
className="block w-full text-left px-4 py-2 hover:bg-gray-600"
onClick={() => {
if (onLogout) onLogout()
onClose()
}}
>
Log out
</button>
</div>
</div>
)
}
'use client'
import * as React from 'react'
import {
ThemeProvider as NextThemesProvider,
type ThemeProviderProps,
} from 'next-themes'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
"use client"
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Accordion = AccordionPrimitive.Root
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
AccordionContent.displayName = AccordionPrimitive.Content.displayName
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
This diff is collapsed. Click to expand it.
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
This diff could not be displayed because it is too large.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment