- {/* Left: Image */}
- {item.imageUrl && (
-
- )}
+ {filteredItems.map((item) => {
+ const myClaim = myClaimFor(item);
+ const maxForMe = item.remainingQuantity + (myClaim?.quantity ?? 0);
+ const showQuantitySummary = item.quantity > 1;
+ const sold = item.remainingQuantity === 0 && !myClaim;
- {/* Middle: Item Details */}
-
-
- {item.name}
-
- {item.description && (
-
- {item.description}
-
+ return (
+
+
+ {/* Left: Image */}
+ {item.imageUrl && (
+
+

+
)}
-
- {/* Right: Action Area */}
-
-
- {item.purchaseUrls && item.purchaseUrls.length > 0 && (
-
+ {/* Middle: Item Details */}
+
+
+ {item.name}
+
+ {showQuantitySummary && (
+
+ {item.claimedQuantity} de {item.quantity} reservados
+
+ )}
+ {item.description && (
+
+ {item.description}
+
+ )}
+
+ {/* Existing claims list */}
+ {item.claims.length > 0 && (
+
)}
- {/* Claimed Badge, Success Message, or Claim Button/Form */}
-
- {justClaimedItemId === item.id ? (
-
-
-
-
+ {/* Right: Action Area */}
+
+
+ {item.purchaseUrls && item.purchaseUrls.length > 0 && (
+
-
-
- Item reservado!
-
-
- O status está confirmado.
-
- {justClaimedNote && (
-
- Sua nota: "{justClaimedNote}"
-
)}
- ) : item.claimedAt ? (
-
- {item.claimedByNote && (
-
- Nota: {item.claimedByNote}
-
- )}
- {item.isPurchased && (
-
- ✓ Comprado
-
- )}
- {showClaimed && (
-
- )}
-
- ) : claimingItemId === item.id ? (
-
-
- ))}
+ );
+ })}
)}
-
-
+
);
}
diff --git a/app/admin/login/page.tsx b/app/admin/login/page.tsx
deleted file mode 100644
index 14d647c..0000000
--- a/app/admin/login/page.tsx
+++ /dev/null
@@ -1,140 +0,0 @@
-'use client';
-
-import { useState, useEffect } from 'react';
-import { useAuth } from '@/lib/auth-context';
-import { useRouter } from 'next/navigation';
-import Link from 'next/link';
-import type { ApiError } from '@/lib/api';
-
-export default function AdminLoginPage() {
- const [username, setUsername] = useState('');
- const [password, setPassword] = useState('');
- const [error, setError] = useState('');
- const [isLoading, setIsLoading] = useState(false);
- const { login, isAuthenticated, isLoading: authLoading } = useAuth();
- const router = useRouter();
-
- // Redirect if already logged in
- useEffect(() => {
- if (!authLoading && isAuthenticated) {
- router.push('/admin');
- }
- }, [isAuthenticated, authLoading, router]);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setError('');
- setIsLoading(true);
-
- try {
- await login(username, password);
- router.push('/admin');
- } catch (err) {
- const apiError = err as ApiError;
- setError(apiError.message || 'Login failed');
- } finally {
- setIsLoading(false);
- }
- };
-
- // Show loading while checking auth status
- if (authLoading) {
- return (
-
- );
- }
-
- // Don't render login form if already authenticated (will redirect)
- if (isAuthenticated) {
- return null;
- }
-
- return (
-
- {/* Hero Section */}
-
-
-
-
- Admin Login
-
-
- Sign in to your account
-
-
-
- Back to Home
-
-
-
-
-
- {/* Main Content */}
-
-
-
-
-
-
-
-
- );
-}
diff --git a/app/admin/page.tsx b/app/admin/page.tsx
index a4982a4..381ea9d 100644
--- a/app/admin/page.tsx
+++ b/app/admin/page.tsx
@@ -1,9 +1,9 @@
'use client';
import { useEffect, useState } from 'react';
-import ProtectedRoute from '@/components/protected-route';
-import { useAuth } from '@/lib/auth-context';
-import { wishlistsApi, itemsApi, settingsApi, type Wishlist, type Settings } from '@/lib/api';
+import { useRouter } from 'next/navigation';
+import AdminGuard from '@/components/admin-guard';
+import { authApi, wishlistsApi, itemsApi, settingsApi, type Wishlist, type Settings } from '@/lib/api';
import Header from '@/components/header';
import Link from 'next/link';
import StatsGrid from '@/components/admin/StatsGrid';
@@ -13,15 +13,27 @@ import CreateWishlistModal from '@/components/admin/CreateWishlistModal';
import ShareButton from '@/components/share-button';
export default function AdminPage() {
- const { logout } = useAuth();
+ return (
+
+
+
+ );
+}
+
+function AdminPageContent() {
+ const router = useRouter();
const [wishlists, setWishlists] = useState
([]);
const [itemCounts, setItemCounts] = useState>({});
const [isLoading, setIsLoading] = useState(true);
const [settings, setSettings] = useState({
siteTitle: 'Wishlist',
homepageSubtext: 'Browse and explore available wishlists',
- passwordLockEnabled: false,
});
+
+ const logout = async () => {
+ await authApi.logout();
+ router.push('/');
+ };
const [showCreateModal, setShowCreateModal] = useState(false);
const [createError, setCreateError] = useState('');
@@ -124,7 +136,7 @@ export default function AdminPage() {
};
return (
-
+ <>
-
+ >
);
}
diff --git a/app/api/lock/route.ts b/app/api/lock/route.ts
deleted file mode 100644
index 7b592cd..0000000
--- a/app/api/lock/route.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { eq } from 'drizzle-orm';
-import { db, settings } from '@/lib/db';
-import crypto from 'crypto';
-import { isSecureCookie } from '@/lib/auth/utils';
-
-// POST /api/lock - Verify password
-export async function POST(request: NextRequest) {
- try {
- const body = await request.json();
- const { password } = body;
-
- if (!password) {
- return NextResponse.json(
- { error: 'Password is required' },
- { status: 400 }
- );
- }
-
- // Get the stored password hash
- const hashSetting = await db
- .select()
- .from(settings)
- .where(eq(settings.key, 'passwordLockHash'))
- .limit(1);
-
- if (hashSetting.length === 0) {
- return NextResponse.json(
- { error: 'Password lock not configured' },
- { status: 400 }
- );
- }
-
- // Hash the provided password
- const hash = crypto.createHash('sha256').update(password).digest('hex');
-
- // Compare hashes
- if (hash === hashSetting[0].value) {
- // Password correct - set a cookie
- const response = NextResponse.json({
- success: true,
- message: 'Password verified',
- });
-
- response.cookies.set('site_unlocked', 'true', {
- httpOnly: true,
- secure: isSecureCookie(request),
- sameSite: 'lax',
- maxAge: 60 * 60 * 24,
- path: '/',
- });
-
- return response;
- } else {
- return NextResponse.json(
- { error: 'Incorrect password' },
- { status: 401 }
- );
- }
- } catch (error) {
- console.error('Error verifying password:', error);
- return NextResponse.json(
- { error: 'Failed to verify password' },
- { status: 500 }
- );
- }
-}
diff --git a/app/layout.tsx b/app/layout.tsx
index 6c5ee1c..0f1aed8 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,8 +1,6 @@
import type { Metadata } from "next";
import "./globals.css";
-import { AuthProvider } from "@/lib/auth-context";
import { db, settings } from "@/lib/db";
-import { eq } from "drizzle-orm";
async function getSettings() {
try {
@@ -51,9 +49,7 @@ export default function RootLayout({
/>
-
- {children}
-
+ {children}