qwen 1 and some manual fixes

This commit is contained in:
2025-08-18 13:28:31 +02:00
parent b1e85d800d
commit 47ab127a5f
6 changed files with 145 additions and 26 deletions

View File

@@ -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=="],

View File

@@ -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"
},

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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'}