Feature: Add password change functionality for admin and customers
- Add customer password change page at /account/change-password - Add admin password change page at /admin/change-password - Create AdminUser database table for storing admin credentials - Admin login now checks database first, falls back to env vars - Update admin auth to support bcrypt password hashing - Add change password links to customer account and admin nav - Fix customer login logs back link (was pointing to non-existent /admin) - Add success/error message handling for password changes Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 4.5
parent
36cc6dddff
commit
9623ac2fdb
@@ -36,6 +36,9 @@ export default function AdminNav() {
|
||||
<Link href="/admin/gallery" className="tag-label hover:text-ink">
|
||||
Gallery
|
||||
</Link>
|
||||
<Link href="/admin/change-password" className="tag-label hover:text-ink">
|
||||
Change password
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface GalleryPhoto {
|
||||
id: string;
|
||||
imageDataUrl: string;
|
||||
caption: string | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
galleryPhotos: GalleryPhoto[];
|
||||
}
|
||||
|
||||
export default function RandomGalleryHero({ galleryPhotos }: Props) {
|
||||
const [photos, setPhotos] = useState<GalleryPhoto[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (galleryPhotos.length === 0) {
|
||||
setPhotos([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pick 2 random photos
|
||||
const shuffled = [...galleryPhotos].sort(() => Math.random() - 0.5);
|
||||
setPhotos(shuffled.slice(0, 2));
|
||||
|
||||
// Rotate every 8 seconds
|
||||
const interval = setInterval(() => {
|
||||
const newShuffled = [...galleryPhotos].sort(() => Math.random() - 0.5);
|
||||
setPhotos(newShuffled.slice(0, 2));
|
||||
}, 8000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [galleryPhotos]);
|
||||
|
||||
if (photos.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
{photos.map((photo, idx) => (
|
||||
<div key={photo.id} className={`flex-1 transition-opacity duration-500 ${idx === 1 ? 'translate-y-6' : ''}`}>
|
||||
<div className="border-2 border-line rounded-lg overflow-hidden aspect-square">
|
||||
<img
|
||||
src={photo.imageDataUrl}
|
||||
alt={photo.caption || 'Customer design'}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
{photo.caption && (
|
||||
<p className="mt-3 text-xs text-muted text-center font-medium">{photo.caption}</p>
|
||||
)}
|
||||
{!photo.caption && (
|
||||
<p className="mt-3 text-xs text-muted text-center font-medium">Customer work</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user