mirror of
https://github.com/SrIzan10/fireentity-movienights.git
synced 2026-06-06 00:56:52 +00:00
qwen 1 and some manual fixes
This commit is contained in:
3
bun.lock
3
bun.lock
@@ -19,6 +19,7 @@
|
||||
"react": "19.1.0",
|
||||
"react-calendar": "^6.0.0",
|
||||
"react-dom": "19.1.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tw-animate-css": "^1.3.7",
|
||||
},
|
||||
@@ -407,6 +408,8 @@
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
|
||||
|
||||
"sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"react": "19.1.0",
|
||||
"react-calendar": "^6.0.0",
|
||||
"react-dom": "19.1.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tw-animate-css": "^1.3.7"
|
||||
},
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
import { authClient } from "@/lib/auth";
|
||||
import { useRouter } from "next/navigation";
|
||||
import MovieCalendar from "@/components/MovieCalendar";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { toast, Toaster } from "sonner";
|
||||
|
||||
interface Movie {
|
||||
id: string;
|
||||
@@ -32,6 +34,9 @@ interface Movie {
|
||||
posterUrl: string;
|
||||
suggestedBy: string;
|
||||
approved?: boolean;
|
||||
votes?: any[];
|
||||
userVote?: boolean;
|
||||
isOwnSubmission?: boolean;
|
||||
}
|
||||
|
||||
interface MovieSchedule {
|
||||
@@ -49,6 +54,7 @@ export default function AdminPage() {
|
||||
const [selectedMovie, setSelectedMovie] = useState<string>("");
|
||||
const [selectedDate, setSelectedDate] = useState<string>("");
|
||||
const [isScheduleDialogOpen, setIsScheduleDialogOpen] = useState(false);
|
||||
const [votingMovieId, setVotingMovieId] = useState<string | null>(null);
|
||||
|
||||
const fetchUnapprovedMovies = async () => {
|
||||
try {
|
||||
@@ -64,7 +70,7 @@ export default function AdminPage() {
|
||||
|
||||
const fetchApprovedMovies = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/movies");
|
||||
const res = await fetch("/api/movies?approved=true");
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setApprovedMovies(data);
|
||||
@@ -106,9 +112,13 @@ export default function AdminPage() {
|
||||
if (res.ok) {
|
||||
fetchUnapprovedMovies();
|
||||
fetchApprovedMovies();
|
||||
toast.success("Movie approved successfully!");
|
||||
} else {
|
||||
toast.error("Failed to approve movie");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error approving movie:", error);
|
||||
toast.error("Error approving movie");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,9 +142,13 @@ export default function AdminPage() {
|
||||
setSelectedMovie("");
|
||||
setSelectedDate("");
|
||||
setIsScheduleDialogOpen(false);
|
||||
toast.success("Movie scheduled successfully!");
|
||||
} else {
|
||||
toast.error("Failed to schedule movie");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error scheduling movie:", error);
|
||||
toast.error("Error scheduling movie");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -146,9 +160,42 @@ export default function AdminPage() {
|
||||
|
||||
if (res.ok) {
|
||||
fetchSchedules();
|
||||
toast.success("Schedule removed successfully!");
|
||||
} else {
|
||||
toast.error("Failed to remove schedule");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting schedule:", error);
|
||||
toast.error("Error removing schedule");
|
||||
}
|
||||
};
|
||||
|
||||
const handleVote = async (movieId: string) => {
|
||||
if (!session) return;
|
||||
|
||||
setVotingMovieId(movieId);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/movies/${movieId}/vote`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Refresh movies to show updated vote count
|
||||
fetchUnapprovedMovies();
|
||||
toast.success("Vote recorded successfully!");
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
toast.error(errorData.error || 'Failed to vote');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error voting:', error);
|
||||
toast.error('Failed to vote. Please try again.');
|
||||
} finally {
|
||||
setVotingMovieId(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -158,6 +205,7 @@ export default function AdminPage() {
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-4 space-y-8">
|
||||
<Toaster position="top-right" />
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold">Admin Dashboard</h1>
|
||||
<Button onClick={() => router.push("/")} variant="outline">
|
||||
@@ -177,6 +225,7 @@ export default function AdminPage() {
|
||||
<TableHead>Title</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
<TableHead>Suggested By</TableHead>
|
||||
<TableHead>Votes</TableHead>
|
||||
<TableHead>Action</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -186,6 +235,25 @@ export default function AdminPage() {
|
||||
<TableCell className="font-medium">{movie.title}</TableCell>
|
||||
<TableCell className="max-w-xs truncate">{movie.description}</TableCell>
|
||||
<TableCell>{movie.suggestedBy}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{movie.votes?.length || 0} votes</span>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => handleVote(movie.id)}
|
||||
disabled={votingMovieId === movie.id || movie.userVote || movie.isOwnSubmission}
|
||||
variant={movie.userVote ? "secondary" : "default"}
|
||||
>
|
||||
{movie.isOwnSubmission
|
||||
? "Own Submission"
|
||||
: movie.userVote
|
||||
? "Voted"
|
||||
: votingMovieId === movie.id
|
||||
? "Voting..."
|
||||
: "Vote"}
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Button onClick={() => handleApprove(movie.id)}>
|
||||
Approve
|
||||
|
||||
@@ -18,7 +18,30 @@ export async function GET() {
|
||||
where: {
|
||||
approved: false,
|
||||
},
|
||||
include: {
|
||||
votes: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Add user vote information
|
||||
if (session && session.user?.id) {
|
||||
const userId = session.user.id;
|
||||
|
||||
const moviesWithUserData = movies.map(movie => ({
|
||||
...movie,
|
||||
userVote: movie.votes.some(vote => vote.userId === userId),
|
||||
isOwnSubmission: movie.suggestedBy === session.user?.name
|
||||
}));
|
||||
|
||||
return NextResponse.json(moviesWithUserData);
|
||||
}
|
||||
|
||||
// For non-logged-in users, just return movies with false values
|
||||
const moviesWithoutUserData = movies.map(movie => ({
|
||||
...movie,
|
||||
userVote: false,
|
||||
isOwnSubmission: false
|
||||
}));
|
||||
|
||||
return NextResponse.json(movies);
|
||||
return NextResponse.json(moviesWithoutUserData);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// src/app/api/movies/route.ts
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { NextResponse } from "next/server";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { headers } from "next/headers";
|
||||
import { auth } from "../auth/auth";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -19,14 +21,40 @@ export async function POST(req: Request) {
|
||||
return NextResponse.json(movie);
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
export async function GET(req: NextRequest) {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
const approved = !!req.nextUrl.searchParams.get("approved");
|
||||
|
||||
// Get all approved movies with votes
|
||||
const movies = await prisma.movie.findMany({
|
||||
where: {
|
||||
approved: true,
|
||||
approved,
|
||||
},
|
||||
include: {
|
||||
votes: true,
|
||||
},
|
||||
});
|
||||
return NextResponse.json(movies);
|
||||
|
||||
// If user is logged in, add vote information
|
||||
if (session && session.user?.id) {
|
||||
const userId = session.user.id;
|
||||
|
||||
// Add userVote and isOwnSubmission properties to each movie
|
||||
const moviesWithUserData = movies.map(movie => ({
|
||||
...movie,
|
||||
userVote: movie.votes.some(vote => vote.userId === userId),
|
||||
isOwnSubmission: movie.suggestedBy === session.user?.name
|
||||
}));
|
||||
|
||||
return NextResponse.json(moviesWithUserData);
|
||||
}
|
||||
|
||||
// For non-logged-in users, just return movies with false values
|
||||
const moviesWithoutUserData = movies.map(movie => ({
|
||||
...movie,
|
||||
userVote: false,
|
||||
isOwnSubmission: false
|
||||
}));
|
||||
|
||||
return NextResponse.json(moviesWithoutUserData);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { authClient } from "@/lib/auth";
|
||||
import MovieCalendar from "@/components/MovieCalendar";
|
||||
import Link from "next/link";
|
||||
import { toast, Toaster } from "sonner";
|
||||
|
||||
interface Movie {
|
||||
id: string;
|
||||
@@ -13,6 +14,8 @@ interface Movie {
|
||||
description: string;
|
||||
posterUrl: string;
|
||||
votes: any[];
|
||||
userVote?: boolean;
|
||||
isOwnSubmission?: boolean;
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
@@ -20,7 +23,6 @@ export default function Home() {
|
||||
const [movies, setMovies] = useState<Movie[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [votingMovieId, setVotingMovieId] = useState<string | null>(null);
|
||||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMovies();
|
||||
@@ -44,7 +46,6 @@ export default function Home() {
|
||||
if (!session) return;
|
||||
|
||||
setVotingMovieId(movieId);
|
||||
setMessage(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/movies/${movieId}/vote`, {
|
||||
@@ -57,24 +58,23 @@ export default function Home() {
|
||||
if (response.ok) {
|
||||
// Refresh movies to show updated vote count
|
||||
fetchMovies();
|
||||
setMessage({ type: 'success', text: 'Vote recorded successfully!' });
|
||||
toast.success("Vote recorded successfully!");
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
setMessage({ type: 'error', text: errorData.error || 'Failed to vote' });
|
||||
toast.error(errorData.error || 'Failed to vote');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error voting:', error);
|
||||
setMessage({ type: 'error', text: 'Failed to vote. Please try again.' });
|
||||
toast.error('Failed to vote. Please try again.');
|
||||
} finally {
|
||||
setVotingMovieId(null);
|
||||
// Clear message after 3 seconds
|
||||
setTimeout(() => setMessage(null), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
if (session) {
|
||||
return (
|
||||
<div className="container mx-auto p-4 space-y-8">
|
||||
<Toaster position="top-right" />
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<p>Signed in as {session.user?.name}</p>
|
||||
<div>
|
||||
@@ -95,17 +95,6 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Message Display */}
|
||||
{message && (
|
||||
<div className={`p-4 rounded-md mb-4 ${
|
||||
message.type === 'success'
|
||||
? 'bg-green-50 text-green-800 border border-green-200'
|
||||
: 'bg-red-50 text-red-800 border border-red-200'
|
||||
}`}>
|
||||
{message.text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Movie Calendar */}
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4">Movie Calendar</h2>
|
||||
@@ -155,10 +144,17 @@ export default function Home() {
|
||||
<div className="flex justify-between items-center">
|
||||
<Button
|
||||
onClick={() => handleVote(movie.id)}
|
||||
disabled={votingMovieId === movie.id}
|
||||
disabled={votingMovieId === movie.id || movie.userVote || movie.isOwnSubmission}
|
||||
className="transition-colors"
|
||||
variant={movie.userVote ? "secondary" : "default"}
|
||||
>
|
||||
{votingMovieId === movie.id ? 'Voting...' : 'Vote'}
|
||||
{movie.isOwnSubmission
|
||||
? "Your Submission"
|
||||
: movie.userVote
|
||||
? "Already Voted"
|
||||
: votingMovieId === movie.id
|
||||
? "Voting..."
|
||||
: "Vote"}
|
||||
</Button>
|
||||
<span className="text-sm font-medium text-gray-600">
|
||||
{movie.votes.length} {movie.votes.length === 1 ? 'Vote' : 'Votes'}
|
||||
|
||||
Reference in New Issue
Block a user