Commit fbee6945 by mvginghina

Almost done, Some (a lot of work on master)

parent aba923fb
Showing with 4890 additions and 0 deletions
.venv/
__pycache__/
# 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())
import config from "./config"
const apiBaseUrl = config.apiBaseUrl
// Helper function to handle API requests with timeout
const fetchWithTimeout = async (url, options = {}) => {
const controller = new AbortController()
const { signal } = controller
const timeoutId = setTimeout(() => controller.abort(), config.apiTimeout)
try {
console.log(`Fetching ${url} with options:`, options)
const response = await fetch(url, { ...options, signal })
clearTimeout(timeoutId)
if (!response.ok) {
const errorText = await response.text()
console.error(`HTTP error! Status: ${response.status}, Response:`, errorText)
throw new Error(`HTTP error! Status: ${response.status}`)
}
const data = await response.json()
console.log(`Response from ${url}:`, data)
return data
} catch (error) {
clearTimeout(timeoutId)
if (error.name === "AbortError") {
throw new Error("Request timeout")
}
throw error
}
}
// Helper function to get auth header
const getAuthHeader = () => {
const token = localStorage.getItem("token")
return token ? { Authorization: `Bearer ${token}` } : {}
}
// ===== Event API calls =====
export const fetchEvents = async () => {
try {
return await fetchWithTimeout(`${apiBaseUrl}/events/`)
} catch (error) {
console.error("Error fetching events:", error)
throw error
}
}
export const fetchEventById = async (eventId) => {
try {
return await fetchWithTimeout(`${apiBaseUrl}/events/${eventId}`)
} catch (error) {
console.error(`Error fetching event with ID ${eventId}:`, error)
throw error
}
}
export const fetchPastEvents = async () => {
try {
return await fetchWithTimeout(`${apiBaseUrl}/events/past`)
} catch (error) {
console.error("Error fetching past events:", error)
throw error
}
}
// ===== User API calls =====
export const loginUser = async ({ emailOrUsername, password }) => {
try {
console.log("Login attempt with:", { emailOrUsername, password })
// Use the users/login endpoint with JSON body
const response = await fetch(`${apiBaseUrl}/users/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
EmailOrUsername: emailOrUsername,
Password: password,
}),
})
// Log the response status
console.log("Login response status:", response.status)
if (!response.ok) {
const errorText = await response.text()
console.error(`Login error (${response.status}):`, errorText)
throw new Error(`HTTP error! Status: ${response.status}`)
}
const data = await response.json()
console.log("Login response data:", data)
return data
} catch (error) {
console.error("Error during login:", error)
throw error
}
}
export const registerUser = async (userData) => {
try {
console.log("Registering user:", userData)
return await fetchWithTimeout(`${apiBaseUrl}/users/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(userData),
})
} catch (error) {
console.error("Error registering user:", error)
throw error
}
}
export const getUserProfile = async () => {
try {
const token = localStorage.getItem("token")
if (!token) {
throw new Error("No authentication token found")
}
return await fetchWithTimeout(`${apiBaseUrl}/users/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
} catch (error) {
console.error("Error fetching user profile:", error)
throw error
}
}
// ===== Ticket API calls =====
export const purchaseTicket = async (eventId) => {
try {
const token = localStorage.getItem("token")
if (!token) {
throw new Error("No authentication token found")
}
return await fetchWithTimeout(`${apiBaseUrl}/tickets/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ Event_ID: eventId }),
})
} catch (error) {
console.error("Error purchasing ticket:", error)
throw error
}
}
export const getUserTickets = async () => {
try {
const token = localStorage.getItem("token")
if (!token) {
throw new Error("No authentication token found")
}
return await fetchWithTimeout(`${apiBaseUrl}/tickets/user`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
} catch (error) {
console.error("Error fetching user tickets:", error)
throw error
}
}
export const getTicketById = async (ticketId) => {
try {
const token = localStorage.getItem("token")
if (!token) {
throw new Error("No authentication token found")
}
return await fetchWithTimeout(`${apiBaseUrl}/tickets/${ticketId}`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
} catch (error) {
console.error(`Error fetching ticket with ID ${ticketId}:`, error)
throw error
}
}
// ===== Rating API calls =====
export const submitReview = async (eventId, stars, comment) => {
try {
const token = localStorage.getItem("token")
if (!token) {
throw new Error("No authentication token found")
}
return await fetchWithTimeout(`${apiBaseUrl}/ratings/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
Event_ID: eventId,
Stars: stars,
Comment: comment,
}),
})
} catch (error) {
console.error("Error submitting review:", error)
throw error
}
}
export const getEventReviews = async (eventId) => {
try {
return await fetchWithTimeout(`${apiBaseUrl}/ratings/event/${eventId}`)
} catch (error) {
console.error(`Error fetching reviews for event ${eventId}:`, error)
throw error
}
}
// ===== Admin API calls =====
export const createEvent = async (eventData) => {
try {
const token = localStorage.getItem("token")
if (!token) {
throw new Error("No authentication token found")
}
return await fetchWithTimeout(`${apiBaseUrl}/events/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(eventData),
})
} catch (error) {
console.error("Error creating event:", error)
throw error
}
}
export const getOverallAnalytics = async () => {
try {
// Mock data for overall analytics
const mockAnalytics = {
ticketSales: [50, 75, 100, 90, 120, 150, 130, 160, 140, 170],
pageViews: [150, 120, 180, 200, 160, 220, 250],
reviews: [
{
user: { Username: "User1" },
Stars: 5,
Comment: "Great event!",
},
{
user: { Username: "User2" },
Stars: 4,
Comment: "Enjoyed it a lot.",
},
],
}
return mockAnalytics
} catch (error) {
console.error("Error fetching overall analytics:", error)
throw error
}
}
# app/__init__.py
# Fișier de inițializare pentru pachetul app
# app/models/__init__.py
from .user import User
from .event import Event
from .ticket import Ticket
from .rating import Rating
# app/schemas/__init__.py
from .user import UserBase, UserCreate, UserLogin, UserUpdate, User, Token, TokenData
from .event import EventBase, EventCreate, EventUpdate, Event
from .ticket import TicketBase, TicketCreate, Ticket, TicketWithEvent
from .rating import RatingBase, RatingCreate, Rating, RatingWithUser
# app/routers/__init__.py
from .auth import router as auth_router
from .events import router as events_router
from .tickets import router as tickets_router
from .admin import router as admin_router
\ No newline at end of file
"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 } from "@/api"
export default function EditEventPage() {
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({
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)
setCurrentAdminId(1)
// 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).toLocaleDateString() : "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 handleBuyTicket = () => {
alert("Buy ticket functionality would be implemented here")
}
const handleTrackEvent = () => {
alert("Track event functionality would be implemented here")
}
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
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>
</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={handleBuyTicket}>
Buy Ticket
</Button>
<Button variant="outline" className="w-full" onClick={handleTrackEvent}>
Track event
</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 { Button } from "@/components/ui/button"
import { AdminHeader } from "@/components/admin-header"
import { fetchEvents } 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(1) // Default admin ID
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)
// Set current admin ID (in a real app, this would come from the auth token)
setCurrentAdminId(1)
const eventsData = await fetchEvents()
// Transform data to match expected format
const formattedEvents = eventsData.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.priceRange || `${event.Price || 0}€ - ${(event.Price || 0) * 3}€`,
location: event.location || event.Location || "Unknown Location",
date: event.date ? `${event.date}` : event.Date ? `${new Date(event.Date).toLocaleDateString()}` : "TBA",
time: event.time || "Time Period: 7 - 10 August",
image: "/placeholder.svg?height=100&width=100",
isActive: Math.random() > 0.3, // Randomly set some events as inactive for demo
adminId: event.admin_id || 1, // Add admin ID
}))
// Filter events to only show those created by the current admin
const adminEvents = formattedEvents.filter((event) => event.adminId === currentAdminId)
setEvents(adminEvents)
} 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")
}
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.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">
<h3 className="font-bold text-lg">{event.title}</h3>
<div className="mt-2 space-y-1 text-sm">
<p>Ticket prices may vary: {event.priceRange}</p>
<p>Location: {event.location}</p>
<p>Time Period: {event.time || event.date}</p>
</div>
</div>
<div className="flex flex-col justify-between p-4">
<div className="flex space-x-2">
<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">
Close
</Button>
</div>
<div className="flex justify-end">
<div className={`w-6 h-6 rounded-full ${event.isActive ? "bg-olive-600" : "bg-red-500"}`}></div>
</div>
</div>
</div>
</div>
))}
</div>
<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 { useEffect, useState } from "react"
import { useRouter } from "next/navigation"
import { Search, Calendar, User } from "lucide-react"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { AdminHeader } from "@/components/admin-header"
import { AdminSidebar } from "@/components/admin-sidebar"
import { fetchEvents } from "@/api"
export default function AdminDashboard() {
const router = useRouter()
const [events, setEvents] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const [currentAdminId, setCurrentAdminId] = useState(1) // Default admin ID
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)
// Set current admin ID (in a real app, this would come from the auth token)
setCurrentAdminId(1)
const eventsData = await fetchEvents()
// Transform data to match expected format
const formattedEvents = eventsData.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.priceRange || `${event.Price || 0}€ - ${(event.Price || 0) * 3}€`,
location: event.location || event.Location || "Unknown Location",
date: event.date
? `${event.date}`
: event.Date
? `Time Period: ${new Date(event.Date).toLocaleDateString()}`
: "Time Period: 7 - 10 August",
image: "/placeholder.svg?height=100&width=100",
adminId: event.admin_id || 1, // Add admin ID
}))
setEvents(formattedEvents)
} catch (err) {
console.error("Error loading events:", err)
setError(err.message || "Failed to load events")
} finally {
setIsLoading(false)
}
}
loadEvents()
}, [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>
)
}
return (
<div className="flex min-h-screen flex-col">
<AdminHeader title="TickMeIn Admin" />
<div className="flex flex-1">
<AdminSidebar activePage="dashboard" />
<div className="flex-1 ml-[220px] overflow-auto">
<div className="p-6">
{/* Search Filters */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<div className="relative">
<Input placeholder="Choose location..." className="pl-10" />
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
</div>
<div className="relative">
<Input placeholder="Choose starting date..." className="pl-10" />
<Calendar className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
</div>
<div className="relative">
<Input placeholder="Who is going?" className="pl-10" />
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
</div>
</div>
{/* Popular Events Section */}
<div className="mb-6">
<div className="border rounded-md p-4 mb-6 text-center">
<h2 className="text-lg font-semibold">Here are the most popular events of the day:</h2>
</div>
<div className="space-y-4 pb-16">
{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">
<h3 className="font-bold text-lg">{event.title}</h3>
<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">
<Button
variant="outline"
size="sm"
className="text-blue-500"
onClick={() => {
// Only allow editing if current admin is the creator
if (event.adminId === currentAdminId) {
router.push(`/admin/events/${event.id}/edit`)
} else {
alert("You can only edit events that you created.")
}
}}
>
Edit
</Button>
<Button
variant="outline"
size="sm"
className="text-blue-500"
onClick={() => router.push(`/admin/events/${event.id}/analytics`)}
>
Analytics
</Button>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</div>
)
}
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { AdminHeader } from "@/components/admin-header"
export default function AdminProfilePage() {
const router = useRouter()
const [isLoading, setIsLoading] = useState(true)
const [profile, setProfile] = useState({
username: "",
email: "",
fullName: "",
bio: "",
})
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 profile data
const username = localStorage.getItem("username") || "admin"
setProfile({
username,
email: "admin@example.com",
fullName: "Admin User",
bio: "Event manager and administrator for TickMeIn platform.",
})
setIsLoading(false)
}, [router])
const handleChange = (e) => {
const { name, value } = e.target
setProfile((prev) => ({ ...prev, [name]: value }))
}
const handleSave = () => {
alert("Profile saved successfully!")
}
const handleLogout = () => {
// Clear authentication data
localStorage.removeItem("isLoggedIn")
localStorage.removeItem("token")
localStorage.removeItem("userId")
localStorage.removeItem("username")
localStorage.removeItem("isAdmin")
// Redirect to login page
router.push("/auth/signin")
}
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="/placeholder.svg?height=128&width=128"
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} onChange={handleChange} />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
<Input name="email" type="email" value={profile.email} onChange={handleChange} />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Full Name</label>
<Input name="fullName" value={profile.fullName} onChange={handleChange} />
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Bio</label>
<Textarea name="bio" value={profile.bio} onChange={handleChange} rows={4} />
</div>
<div className="flex justify-between pt-4">
<Button variant="destructive" onClick={handleLogout}>
Logout
</Button>
<Button onClick={handleSave}>Save Changes</Button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
)
}
"use client"
import { useState } from "react"
import { useRouter } from "next/navigation"
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 router = useRouter()
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()
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)
// Check if user is admin
localStorage.setItem("isAdmin", userData.isAdmin ? "true" : "false")
// Redirect based on user role
if (userData.isAdmin) {
router.push("/admin")
} else {
router.push("/events")
}
} catch (err) {
console.error("Login error:", err)
setError("Login failed. Please check your credentials and try again.")
} finally {
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)}
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)}
required
/>
<button
type="button"
onClick={toggleShowPassword}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500"
>
{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 type React from "react"
import { useState, useRef } from "react"
import Link from "next/link"
import Image from "next/image"
import { useRouter } from "next/navigation"
import { Eye, EyeOff, Upload } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { registerUser } from "@/api"
export default function SignUpPage() {
const router = useRouter()
const fileInputRef = useRef<HTMLInputElement>(null)
const [formData, setFormData] = useState({
Username: "",
Email: "",
Password: "",
confirmPassword: "",
})
const [profileImage, setProfileImage] = useState<string | null>(null)
const [showPassword, setShowPassword] = useState(false)
const [error, setError] = useState("")
const [isLoading, setIsLoading] = useState(false)
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target
setFormData((prev) => ({ ...prev, [name]: value }))
}
const handleImageUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
const reader = new FileReader()
reader.onload = (e) => {
if (e.target?.result) {
setProfileImage(e.target.result as string)
}
}
reader.readAsDataURL(file)
}
}
const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError("")
// Simple validation
if (!formData.Username || !formData.Email || !formData.Password) {
setError("Please fill in all required fields")
setIsLoading(false)
return
}
if (formData.Password !== formData.confirmPassword) {
setError("Passwords do not match")
setIsLoading(false)
return
}
try {
// Încercăm să înregistrăm utilizatorul prin API
try {
const userData = await registerUser({
Username: formData.Username,
Email: formData.Email,
Password: formData.Password,
})
localStorage.setItem("token", userData.token || "demo_token_12345")
localStorage.setItem("isLoggedIn", "true")
localStorage.setItem("userId", userData.ID?.toString() || "1")
localStorage.setItem("username", userData.Username)
// Redirecționăm către pagina de evenimente
router.push("/events")
} catch (apiError) {
console.error("API error:", apiError)
// Pentru demo, simulăm un răspuns de succes dacă API-ul nu este disponibil
localStorage.setItem("token", "demo_token_12345")
localStorage.setItem("isLoggedIn", "true")
localStorage.setItem("userId", "1")
localStorage.setItem("username", formData.Username)
// Redirecționăm către pagina de evenimente
router.push("/events")
}
} catch (err) {
console.error("Error during registration:", err)
setError("Registration failed. Please try again.")
} finally {
setIsLoading(false)
}
}
return (
<div className="min-h-screen flex flex-col">
<header className="bg-black text-white py-4">
<div className="container mx-auto text-center">
<h1 className="text-xl font-bold">TickMeIn</h1>
</div>
</header>
<main className="flex-1 flex items-center justify-center p-6">
<div className="w-full max-w-md">
<div className="border rounded-md p-6">
<h2 className="text-2xl font-bold mb-6">Sign Up</h2>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">{error}</div>
)}
<form onSubmit={handleSignUp} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="Username">Username</Label>
<Input
id="Username"
name="Username"
value={formData.Username}
onChange={handleChange}
placeholder="Enter your username"
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Email">Email address</Label>
<Input
id="Email"
name="Email"
type="email"
value={formData.Email}
onChange={handleChange}
placeholder="Enter your email"
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Password">Password</Label>
<div className="relative">
<Input
id="Password"
name="Password"
type={showPassword ? "text" : "password"}
value={formData.Password}
onChange={handleChange}
placeholder="Create a password"
disabled={isLoading}
/>
<button
type="button"
className="absolute right-3 top-1/2 transform -translate-y-1/2"
onClick={() => setShowPassword(!showPassword)}
disabled={isLoading}
>
{showPassword ? (
<EyeOff className="h-4 w-4 text-gray-500" />
) : (
<Eye className="h-4 w-4 text-gray-500" />
)}
</button>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
name="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={handleChange}
placeholder="Confirm your password"
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<Label>Profile Picture</Label>
<span className="text-xs text-blue-600 cursor-pointer">optional</span>
</div>
<div className="flex flex-col items-center space-y-4">
<div className="w-24 h-24 rounded-full bg-gray-200 overflow-hidden relative">
{profileImage ? (
<Image
src={profileImage || "/placeholder.svg"}
alt="Profile preview"
fill
className="object-cover"
/>
) : (
<div className="flex items-center justify-center h-full">
<Upload className="h-8 w-8 text-gray-400" />
</div>
)}
</div>
<input
type="file"
ref={fileInputRef}
onChange={handleImageUpload}
className="hidden"
accept="image/*"
disabled={isLoading}
/>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => fileInputRef.current?.click()}
disabled={isLoading}
>
Upload Photo
</Button>
</div>
</div>
<div className="pt-4">
<Button type="submit" className="w-full bg-gray-800 hover:bg-black" disabled={isLoading}>
{isLoading ? (
<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>
Signing up...
</div>
) : (
"Sign Up"
)}
</Button>
</div>
<div className="text-xs text-center text-gray-500 mt-4">
By creating an account, you agree to our{" "}
<Link href="#" className="text-blue-600 hover:underline">
Terms of Service
</Link>{" "}
and{" "}
<Link href="#" className="text-blue-600 hover:underline">
Privacy Policy
</Link>
.
</div>
</form>
<div className="mt-4 text-center text-sm border-t pt-4">
<p>
Already have an account?{" "}
<Link href="/auth/signin" className="text-blue-600 hover:underline">
Log in here
</Link>
</p>
</div>
</div>
</div>
</main>
</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
const eventData = await fetchEventById(eventId)
// Format event data
const formattedEvent = {
id: eventData.ID.toString(),
title: eventData.Title,
description: eventData.Description,
priceRange: `${eventData.Price}€`,
location: eventData.Location,
date: new Date(eventData.Date).toLocaleDateString(),
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)
setError(err.message)
} 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
}
"use client"
import { useState, useEffect } from "react"
import { Calendar, Filter, Search, User } from "lucide-react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Slider } from "@/components/ui/slider"
import { Checkbox } from "@/components/ui/checkbox"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import EventCard from "@/components/event-card"
import { AppHeader } from "@/components/app-header"
import { fetchEvents, purchaseTicket } from "@/api"
export default function EventsPage() {
const router = useRouter()
const [priceRange, setPriceRange] = useState([5])
const [isLoading, setIsLoading] = useState(true)
const [events, setEvents] = useState([])
const [error, setError] = useState(null)
// Verificăm dacă utilizatorul este autentificat și încărcăm evenimentele
useEffect(() => {
try {
const isLoggedIn = localStorage.getItem("isLoggedIn")
if (isLoggedIn !== "true") {
router.push("/auth/signin")
return
}
// Încărcăm evenimentele
const loadEvents = async () => {
try {
setIsLoading(true)
console.log("Fetching events...")
const eventsData = await fetchEvents()
console.log("Events data received:", eventsData)
// Transform data to match expected format
const formattedEvents = eventsData.map((event) => ({
id: event.ID?.toString(),
title: event.Title,
priceRange: `${event.Price}€`,
location: event.Location,
date: event.Date ? `Time Period: ${new Date(event.Date).toLocaleDateString()}` : "Time: TBA",
image: "/placeholder.svg?height=200&width=200",
}))
console.log("Formatted events:", formattedEvents)
setEvents(formattedEvents)
} catch (err) {
console.error("Error fetching events:", err)
setError(err.message)
} finally {
setIsLoading(false)
}
}
loadEvents()
} catch (err) {
console.error("Error checking authentication:", err)
setIsLoading(false)
}
}, [router])
const handleBuyTicket = async (eventId) => {
try {
// Call the API to purchase a 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 (event) => {
try {
// For now, we'll just use localStorage for tracking events
const existingEventsJSON = localStorage.getItem("trackedEvents")
const existingEvents = existingEventsJSON ? JSON.parse(existingEventsJSON) : []
const isAlreadyTracked = existingEvents.some((e) => e.id === event.id)
if (!isAlreadyTracked) {
const updatedEvents = [...existingEvents, event]
localStorage.setItem("trackedEvents", JSON.stringify(updatedEvents))
alert(`Event "${event.title}" has been added to your tracked events!`)
} else {
alert("This event is already in your tracked events.")
}
} catch (err) {
console.error("Error tracking event:", err)
alert("Could not track 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 events: {error}</p>
<p>Please try again later</p>
</div>
)
}
return (
<div className="flex min-h-screen flex-col">
<AppHeader />
<div className="flex flex-1">
{/* Sidebar */}
<aside className="w-[220px] bg-zinc-700 text-white fixed h-[calc(100vh-4rem)] overflow-y-auto">
<div className="p-4 bg-zinc-600">
<div className="flex items-center gap-2">
<Filter className="h-4 w-4" />
<span className="text-sm">Filter by...</span>
</div>
</div>
<div className="p-4 border-b border-zinc-600">
<label className="text-sm mb-2 block">Type of event</label>
<Select>
<SelectTrigger className="bg-white text-black">
<SelectValue placeholder="All events" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All events</SelectItem>
<SelectItem value="concerts">Concerts</SelectItem>
<SelectItem value="festivals">Festivals</SelectItem>
<SelectItem value="sports">Sports</SelectItem>
<SelectItem value="theater">Theater</SelectItem>
<SelectItem value="comedy">Comedy</SelectItem>
</SelectContent>
</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={[5]}
max={50}
step={1}
value={priceRange}
onValueChange={setPriceRange}
className="py-4"
/>
<div className="flex justify-between text-sm">
<span>{priceRange[0]}</span>
<span>€50</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">
Vegan food choices
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="parking" />
<label htmlFor="parking" className="text-sm">
Parking spaces for disabilities
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="age" />
<label htmlFor="age" className="text-sm">
Age restriction
</label>
</div>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 p-6 ml-[220px] overflow-auto">
{/* Search Filters */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<div className="relative">
<Input placeholder="Choose location..." className="pl-10" />
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
</div>
<div className="relative">
<Input placeholder="Choose starting date..." className="pl-10" />
<Calendar className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
</div>
<div className="relative">
<Input placeholder="Who is going?" className="pl-10" />
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-500" />
</div>
</div>
{/* Popular Events Section */}
<div className="mb-6">
<Card className="mb-6">
<CardContent className="p-4">
<h2 className="text-lg font-semibold">Here are the most popular events of the day:</h2>
</CardContent>
</Card>
<div className="space-y-4 pb-16">
{events.length > 0 ? (
events.map((event) => (
<div key={event.id} 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 flex gap-2">
<Button
size="sm"
variant="outline"
className="hover:bg-gray-300"
onClick={() => handleTrackEvent(event)}
>
Track Event
</Button>
<Button
size="sm"
className="bg-black hover:bg-gray-700"
onClick={() => handleBuyTicket(event.id)}
>
Buy Ticket
</Button>
</div>
</div>
))
) : (
<div className="text-center py-10">
<p className="text-gray-500 mb-4">No events found.</p>
</div>
)}
</div>
</div>
</main>
</div>
</div>
)
}
@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 } 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) => {
// 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(`/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"
import { useRouter } from "next/navigation"
export default function Home() {
const router = useRouter()
useEffect(() => {
// Redirect to signin page immediately
router.push("/auth/signin")
}, [router])
return (
<div className="flex items-center justify-center min-h-screen">
<p>Redirecting to login...</p>
</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"
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
}
// În mod normal, am apela API-ul pentru a obține evenimentele trecute
// const pastEvents = await fetchPastEvents();
// Pentru demo, folosim date hardcodate
const mockPastEvents = [
{
title: "Summer Music Festival",
priceRange: "40€ - 120€",
location: "Barcelona, Spain",
date: "Time: 20-22 June 2023",
image: "/placeholder.svg?height=200&width=200",
slug: "summer-music-festival",
},
{
title: "International Food Fair",
priceRange: "10€",
location: "Paris, France",
date: "Time: 5-7 April 2023",
image: "/placeholder.svg?height=200&width=200",
slug: "international-food-fair",
},
{
title: "Untold Festival 2023",
priceRange: "50€ - 300€",
location: "Cluj-Napoca, Romania",
date: "Time: 3-6 August 2023",
image: "/placeholder.svg?height=200&width=200",
slug: "untold-festival-2023",
},
]
setEvents(mockPastEvents)
} 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>
<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>
</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")
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()
{
"$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
import Link from "next/link"
import { Edit, MoreHorizontal, Trash } from "lucide-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"
export function AdminEventList({ events }) {
if (!events || events.length === 0) {
return <div className="text-center py-4">No events found</div>
}
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}>
<TableCell>
<Link href={`/admin/events/${event.id}/edit`} 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}€`}</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">
<DropdownMenuItem asChild>
<Link href={`/admin/events/${event.id}/edit`}>
<Edit className="mr-2 h-4 w-4" />
Edit
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href={`/admin/events/${event.id}/analytics`}>
<Edit className="mr-2 h-4 w-4" />
Analytics
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="text-red-600">
<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, User } from "lucide-react"
import { useState } from "react"
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
window.location.href = "/auth/signin"
}
const adminMenuItems = [
{ label: "My Events", href: "/admin/my-events" },
{ label: "Create Event", href: "/admin/events/new" },
{ label: "Analytics", href: "/admin/analytics" },
{ label: "Tracked Events", href: "/tracked-events" },
{ label: "My Tickets", href: "/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>
<Link href="/admin" className="flex items-center gap-2 font-semibold">
<span className="text-lg">{title}</span>
</Link>
<div className="ml-auto flex items-center gap-4">
<Button variant="ghost" size="icon" className="text-white" onClick={() => setMenuOpen(!menuOpen)}>
<Menu className="h-5 w-5" />
</Button>
<Link href="/admin/profile">
<Button variant="ghost" size="icon" className="text-white">
<User className="h-5 w-5" />
</Button>
</Link>
</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, User } from "lucide-react"
import { Button } from "@/components/ui/button"
import { MenuPopup } from "@/components/menu-popup"
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
router.push("/auth/signin")
}
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">
<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>
<Link href="/account">
<Button variant="ghost" size="icon" className="text-white">
<User className="h-5 w-5" />
</Button>
</Link>
</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, "")
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> {priceRange}
</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>
)
}
This diff could not be displayed because it is too large.
No preview for this file type
The file could not be displayed because it is too large.
This diff could not be displayed because it is too large.
No preview for this file type
This diff could not be displayed because it is too large.
No preview for this file type
This diff could not be displayed because it is too large.
No preview for this file type
No preview for this file type
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