change sqlite user table column id to increment int ids

This commit is contained in:
Indigo Tang 2025-07-06 13:34:11 +00:00
parent 0f224287fd
commit dad13c9cfb
13 changed files with 200 additions and 103 deletions

View File

@ -0,0 +1,103 @@
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import ToyList from '@/components/toys/ToyList';
import { getToysByOwner, getOwnerProfile, getAllToys } from '@/data/operations';
import { getI18n, getStaticParams as getLocaleStaticParams } from '@/locales/server';
import { Home, UserCircle } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { ToyBrick } from 'lucide-react';
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
interface OwnerToysPageProps {
params: { ownerId: string; locale: string };
}
export default async function OwnerToysPage({ params }: OwnerToysPageProps) {
const t = await getI18n();
const ownerId = Number(params.ownerId);
const ownerToys = getToysByOwner(ownerId);
const ownerProfile = getOwnerProfile(ownerId);
const ownerNameFromToys = ownerToys.length > 0 ? ownerToys[0].ownerName : undefined;
let displayOwnerName = ownerProfile?.name || ownerNameFromToys || t('owner_toys.unknown_owner');
const pageTitle = displayOwnerName !== t('owner_toys.unknown_owner')
? t('owner_toys.title_specific', { ownerName: displayOwnerName })
: t('owner_toys.title_generic');
return (
<div className="space-y-8">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<h1 className="text-3xl font-bold font-headline text-primary">{pageTitle}</h1>
<Link href={`/${params.locale}/`} passHref>
<Button variant="outline" className="w-full sm:w-auto">
<Home className="mr-2 h-4 w-4" />
{t('owner_toys.back_to_home')}
</Button>
</Link>
</div>
{ownerProfile && (
<Card className="mb-8 shadow-lg border-accent/30">
<CardHeader>
<div className="flex flex-col sm:flex-row items-center gap-4">
<Avatar className="h-20 w-20 sm:h-24 sm:w-24 border-2 border-accent">
<AvatarImage src={ownerProfile.avatarUrl} alt={displayOwnerName} data-ai-hint="owner avatar" />
<AvatarFallback className="text-3xl bg-muted">
{displayOwnerName ? displayOwnerName.split(' ').map(n => n[0]).join('').toUpperCase() : <UserCircle />}
</AvatarFallback>
</Avatar>
<div className="text-center sm:text-left">
<CardTitle className="text-2xl font-headline text-accent">
{t('owner_toys.about_owner', { ownerName: displayOwnerName })}
</CardTitle>
{ownerNameFromToys && ownerNameFromToys !== displayOwnerName && (
<CardDescription>{t('toy_details.owner')}: {ownerNameFromToys}</CardDescription>
)}
</div>
</div>
</CardHeader>
<CardContent>
<p className="text-foreground/80 leading-relaxed">{ownerProfile.bio}</p>
</CardContent>
</Card>
)}
{ownerToys.length > 0 ? (
<ToyList toys={ownerToys} t={t} />
) : (
<Card className="text-center py-12 shadow-md">
<CardHeader>
<ToyBrick className="h-16 w-16 mx-auto text-muted-foreground mb-4" />
<CardTitle>
{displayOwnerName !== t('owner_toys.unknown_owner')
? t('owner_toys.no_toys_listed_by', { ownerName: displayOwnerName })
: t('owner_toys.owner_not_found')}
</CardTitle>
{displayOwnerName !== t('owner_toys.unknown_owner') && <CardDescription>{t('home.explore_toys')}</CardDescription>}
</CardHeader>
<CardContent>
<Link href={`/${params.locale}/`} passHref>
<Button size="lg">
{t('home.explore_toys')}
</Button>
</Link>
</CardContent>
</Card>
)}
</div>
);
}
export async function generateStaticParams() {
const localeParams = getLocaleStaticParams();
const allToys = getAllToys();
const ownerIds = Array.from(new Set(allToys.map(toy => toy.ownerId)));
const ownerParams = ownerIds.map(id => ({ ownerId: String(id) }));
return localeParams.flatMap(lang =>
ownerParams.map(owner => ({ ...lang, ...owner }))
);
}

View File

@ -13,7 +13,8 @@ interface EditUserPageProps {
export default async function EditUserPage({ params }: EditUserPageProps) {
const t = await getI18n();
const userData = getUserById(params.id);
const userId = Number(params.id);
const userData = getUserById(userId);
if (!userData) {
return (

View File

@ -53,7 +53,7 @@ export default function AdminUserManagementPage() {
}, [t, toast]);
const handleDeleteUser = async (userId: string) => {
const handleDeleteUser = async (userId: number) => {
setIsDeleting(true);
const result = await deleteUser(userId);
if (result.success) {

View File

@ -17,20 +17,20 @@ import { ArrowLeft, Send, Loader2, AlertTriangle, ToyBrick } from 'lucide-react'
import { useToast } from '@/hooks/use-toast';
import { format } from 'date-fns';
// Assume current user is 'user1' for mock purposes
const currentUserId = 'user1';
const currentUserProfiles: Record<string, { name: string; avatarInitial: string; avatarUrl?: string }> = {
'user1': { name: 'Alice Wonderland', avatarInitial: 'AW', avatarUrl: 'https://placehold.co/40x40.png?text=AW' }, // Logged in user
'user2': { name: 'Bob The Builder', avatarInitial: 'BT', avatarUrl: 'https://placehold.co/40x40.png?text=BT' },
'user3': { name: 'Carol Danvers', avatarInitial: 'CD', avatarUrl: 'https://placehold.co/40x40.png?text=CD' },
'user4': { name: 'Charlie Brown', avatarInitial: 'CB', avatarUrl: 'https://placehold.co/40x40.png?text=CB' },
'user5': { name: 'Diana Prince', avatarInitial: 'DP', avatarUrl: 'https://placehold.co/40x40.png?text=DP' },
'user6': { name: 'Edward Nigma', avatarInitial: 'EN', avatarUrl: 'https://placehold.co/40x40.png?text=EN' },
// Assume current user is 'user1' (ID: 1) for mock purposes
const currentUserId = 1;
const currentUserProfiles: Record<number, { name: string; avatarInitial: string; avatarUrl?: string }> = {
1: { name: 'Alice Wonderland', avatarInitial: 'AW', avatarUrl: 'https://placehold.co/40x40.png?text=AW' }, // Logged in user
2: { name: 'Bob The Builder', avatarInitial: 'BT', avatarUrl: 'https://placehold.co/40x40.png?text=BT' },
3: { name: 'Carol Danvers', avatarInitial: 'CD', avatarUrl: 'https://placehold.co/40x40.png?text=CD' },
4: { name: 'Charlie Brown', avatarInitial: 'CB', avatarUrl: 'https://placehold.co/40x40.png?text=CB' },
5: { name: 'Diana Prince', avatarInitial: 'DP', avatarUrl: 'https://placehold.co/40x40.png?text=DP' },
6: { name: 'Edward Nigma', avatarInitial: 'EN', avatarUrl: 'https://placehold.co/40x40.png?text=EN' },
};
// Helper function to get the other participant in a conversation
const getOtherParticipant = (request: RentalRequest, currentUserId: string) => {
const getOtherParticipant = (request: RentalRequest, currentUserId: number) => {
if (request.toy.ownerId === currentUserId) {
return { id: request.requesterId, name: request.requesterName };
}
@ -238,5 +238,3 @@ export default function MessageDetailPage({ params }: { params: { id: string } }
</div>
);
}

View File

@ -9,10 +9,10 @@ import Link from "next/link";
import { MessageSquareQuote, ToyBrick } from "lucide-react";
import { format } from 'date-fns';
const currentUserId = 'user1'; // Assume this is the logged-in user's ID
const currentUserId = 1; // Assume this is the logged-in user's ID
// Helper function to get the other participant in a conversation
const getOtherParticipant = (request: RentalRequest, currentUserId: string) => {
const getOtherParticipant = (request: RentalRequest, currentUserId: number) => {
if (request.toy.ownerId === currentUserId) {
return { id: request.requesterId, name: request.requesterName };
}

View File

@ -10,7 +10,7 @@ import type { Toy } from "@/types";
import { Badge } from "@/components/ui/badge";
import { getI18n } from "@/locales/server";
const currentUserId = 'user1';
const currentUserId = 1;
export default async function MyToysPage() {
const t = await getI18n();

View File

@ -11,7 +11,7 @@ import type { Locale } from "@/locales/server";
import { Badge } from "@/components/ui/badge";
import { format } from 'date-fns';
const currentUserId = 'user1'; // Assume this is the logged-in user's ID
const currentUserId = 1; // Assume this is the logged-in user's ID
export default async function RentalHistoryPage({ params }: { params: { locale: Locale } }) {
const t = await getI18n();
@ -112,4 +112,3 @@ function RentalHistoryItemCard({ item, t, locale }: RentalHistoryItemCardProps)
</Card>
);
}

View File

@ -4,55 +4,53 @@ import { ListOrdered, Check, X, MessageSquareText } from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import Image from "next/image";
import type { Toy } from "@/types";
import { mockToys } from "@/lib/mockData";
import type { Toy, RentalRequest } from "@/types";
import { getAllToys } from "@/data/operations";
import { Badge } from "@/components/ui/badge";
import { getI18n } from "@/locales/server";
interface RentalRequest {
id: string;
toy: Toy;
requesterName: string;
requesterId: string;
requestedDates: string; // e.g., "Aug 5, 2024 - Aug 10, 2024"
status: 'pending' | 'approved' | 'declined';
message?: string;
dataAiHint?: string;
}
// This is still mock data. In a real app, this would come from the database.
function getMockRequests(): RentalRequest[] {
const allToys = getAllToys();
const myToys = allToys.filter(t => t.ownerId === 1);
if (myToys.length === 0) return [];
const rentalRequests: RentalRequest[] = [
{
id: 'req1',
toy: mockToys[0],
toy: myToys[0],
requesterName: 'Charlie Brown',
requesterId: 'user4',
requesterId: 4,
requestedDates: 'August 10, 2024 - August 17, 2024',
status: 'pending',
message: 'My son would love to play with these for his birthday week! We are very careful with toys and will ensure it is returned in perfect condition. Could we possibly pick it up on the 9th evening?',
dataAiHint: mockToys[0]?.category.toLowerCase(),
dataAiHint: myToys[0]?.category.toLowerCase(),
},
{
id: 'req2',
toy: mockToys[3],
toy: myToys.length > 1 ? myToys[1] : myToys[0],
requesterName: 'Diana Prince',
requesterId: 'user5',
requesterId: 5,
requestedDates: 'September 1, 2024 - September 5, 2024',
status: 'approved',
dataAiHint: mockToys[3]?.category.toLowerCase(),
dataAiHint: (myToys.length > 1 ? myToys[1] : myToys[0])?.category.toLowerCase(),
},
{
id: 'req3',
toy: mockToys[0],
toy: myToys[0],
requesterName: 'Edward Nigma',
requesterId: 'user6',
requesterId: 6,
requestedDates: 'July 20, 2024 - July 22, 2024',
status: 'declined',
message: 'Looking for a weekend rental.',
dataAiHint: mockToys[0]?.category.toLowerCase(),
dataAiHint: myToys[0]?.category.toLowerCase(),
},
];
return rentalRequests;
}
const currentUserToyRequests = rentalRequests.filter(req => req.toy.ownerId === 'user1');
const currentUserToyRequests = getMockRequests();
export default async function RentalRequestsPage() {
@ -167,4 +165,3 @@ function RequestItemCard({ request, t }: RequestItemCardProps) {
</Card>
);
}

View File

@ -3,7 +3,6 @@
import db from '@/lib/db';
import type { User } from '@/types';
import { randomUUID } from 'crypto';
import { revalidatePath } from 'next/cache';
import { getAllUsers as dbGetAllUsers, getUserById } from '@/data/operations';
@ -26,21 +25,21 @@ export async function registerUser(data: { name: string; nickname?: string; emai
return { success: false, message: 'An account with this email already exists.' };
}
const newUser: User = {
id: `user-${randomUUID()}`,
const stmt = db.prepare(
'INSERT INTO users (name, nickname, email, role, avatarUrl, bio) VALUES (@name, @nickname, @email, @role, @avatarUrl, @bio)'
);
const info = stmt.run({
name,
nickname,
nickname: nickname ?? null,
email,
role: role,
avatarUrl: '',
bio: ''
};
});
const stmt = db.prepare(
'INSERT INTO users (id, name, nickname, email, role, avatarUrl, bio) VALUES (@id, @name, @nickname, @email, @role, @avatarUrl, @bio)'
);
stmt.run(newUser);
const newUserId = info.lastInsertRowid as number;
const newUser = getUserById(newUserId);
revalidatePath('/admin/users');
@ -96,7 +95,7 @@ interface DeleteUserResult {
message: string;
}
export async function deleteUser(id: string): Promise<DeleteUserResult> {
export async function deleteUser(id: number): Promise<DeleteUserResult> {
try {
const user = getUserById(id);
if (user && (user.email === 'user@example.com' || user.email === 'admin@example.com')) {
@ -143,7 +142,7 @@ export async function getUserByEmail(email: string): Promise<User | null> {
}
interface UpdateProfileData {
id: string;
id: number;
name: string;
nickname?: string;
avatarUrl?: string;

View File

@ -39,7 +39,7 @@ export function getToyById(id: string): Toy | undefined {
return toy ? parseToy(toy) : undefined;
}
export function getToysByOwner(ownerId: string): Toy[] {
export function getToysByOwner(ownerId: number): Toy[] {
const stmt = db.prepare(`
SELECT t.*, u.name as ownerName, u.avatarUrl as ownerAvatarUrl
FROM toys t
@ -93,7 +93,7 @@ export function updateToy(toyData: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'd
return getToyById(toyData.id)!;
}
export function getOwnerProfile(ownerId: string): User | undefined {
export function getOwnerProfile(ownerId: number): User | undefined {
return getUserById(ownerId);
}
@ -104,7 +104,7 @@ export function getAllUsers(): User[] {
return stmt.all() as User[];
}
export function getUserById(id: string): User | undefined {
export function getUserById(id: number): User | undefined {
const stmt = db.prepare('SELECT * FROM users WHERE id = ?');
return stmt.get(id) as User | undefined;
}

View File

@ -20,7 +20,7 @@ function initDb() {
// Create Users Table if it doesn't exist
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
nickname TEXT,
email TEXT NOT NULL UNIQUE,
@ -51,7 +51,7 @@ function initDb() {
category TEXT NOT NULL,
images TEXT,
unavailableRanges TEXT,
ownerId TEXT NOT NULL,
ownerId INTEGER NOT NULL,
pricePerDay REAL,
location TEXT,
FOREIGN KEY (ownerId) REFERENCES users(id) ON DELETE CASCADE

View File

@ -5,13 +5,13 @@ import { addDays, formatISO, subDays } from 'date-fns';
const today = new Date();
export const rawUsers: User[] = [
{ id: 'user1', name: 'Alice Wonderland', nickname: 'Alice', email: 'user@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=AW', bio: "Lover of imaginative play and sharing joy. I have a collection of classic storybooks and dress-up costumes that my kids have outgrown but still have lots of life left in them!" },
{ id: 'user2', name: 'Bob The Builder', nickname: 'Bob', email: 'user2@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=BT', bio: "Can we fix it? Yes, we can! Sharing my collection of construction toys, tools, and playsets. Always happy to help another budding builder." },
{ id: 'user3', name: 'Carol Danvers', nickname: 'Captain Marvel', email: 'user3@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=CD', bio: "Higher, further, faster. Sharing toys that inspire adventure, courage, and exploration. My collection includes superhero action figures and space-themed playsets." },
{ id: 'user4', name: 'Charlie Brown', nickname: 'Chuck', email: 'user4@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 'user5', name: 'Diana Prince', nickname: 'Wonder Woman', email: 'user5@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 'user6', name: 'Edward Nigma', nickname: 'Riddler', email: 'user6@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 'admin-main', name: 'Main Admin', nickname: 'Head Honcho', email: 'admin@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=ADM', bio: 'Keeping the toy box tidy.' },
{ id: 1, name: 'Alice Wonderland', nickname: 'Alice', email: 'user@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=AW', bio: "Lover of imaginative play and sharing joy. I have a collection of classic storybooks and dress-up costumes that my kids have outgrown but still have lots of life left in them!" },
{ id: 2, name: 'Bob The Builder', nickname: 'Bob', email: 'user2@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=BT', bio: "Can we fix it? Yes, we can! Sharing my collection of construction toys, tools, and playsets. Always happy to help another budding builder." },
{ id: 3, name: 'Carol Danvers', nickname: 'Captain Marvel', email: 'user3@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=CD', bio: "Higher, further, faster. Sharing toys that inspire adventure, courage, and exploration. My collection includes superhero action figures and space-themed playsets." },
{ id: 4, name: 'Charlie Brown', nickname: 'Chuck', email: 'user4@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 5, name: 'Diana Prince', nickname: 'Wonder Woman', email: 'user5@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 6, name: 'Edward Nigma', nickname: 'Riddler', email: 'user6@example.com', role: 'User', avatarUrl: '', bio: '' },
{ id: 7, name: 'Main Admin', nickname: 'Head Honcho', email: 'admin@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=ADM', bio: 'Keeping the toy box tidy.' },
];
export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[] = [
@ -25,7 +25,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
{ startDate: formatISO(addDays(today, 5), { representation: 'date' }), endDate: formatISO(addDays(today, 7), { representation: 'date' }) },
{ startDate: formatISO(addDays(today, 15), { representation: 'date' }), endDate: formatISO(addDays(today, 16), { representation: 'date' }) },
],
ownerId: 'user1',
ownerId: 1,
pricePerDay: 5,
location: 'Springfield Gardens',
},
@ -38,7 +38,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
unavailableRanges: [
{ startDate: formatISO(addDays(today, 10), { representation: 'date' }), endDate: formatISO(addDays(today, 12), { representation: 'date' }) },
],
ownerId: 'user2',
ownerId: 2,
pricePerDay: 8,
location: 'Willow Creek',
},
@ -49,7 +49,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
category: 'Electronics',
images: ['https://placehold.co/600x400.png?text=Kids+Tablet', 'https://placehold.co/600x400.png?text=Tablet+Screen'],
unavailableRanges: [],
ownerId: 'user3',
ownerId: 3,
pricePerDay: 7,
location: 'Metro City',
},
@ -62,7 +62,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
unavailableRanges: [
{ startDate: formatISO(addDays(today, 20), { representation: 'date' }), endDate: formatISO(addDays(today, 25), { representation: 'date' }) },
],
ownerId: 'user1',
ownerId: 1,
pricePerDay: 3,
location: 'Springfield Gardens',
},
@ -73,7 +73,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
category: 'Musical',
images: ['https://placehold.co/600x400.png?text=Kids+Guitar'],
unavailableRanges: [],
ownerId: 'user2',
ownerId: 2,
pricePerDay: 10,
location: 'Willow Creek',
},
@ -84,7 +84,7 @@ export const rawToys: Omit<Toy, 'ownerName' | 'ownerAvatarUrl' | 'dataAiHint'>[]
category: 'Outdoor',
images: ['https://placehold.co/600x400.png?text=Sports+Kit'],
unavailableRanges: [],
ownerId: 'user3',
ownerId: 3,
pricePerDay: 6,
location: 'Metro City',
}

View File

@ -1,7 +1,7 @@
export interface MessageEntry {
id: string;
senderId: string;
senderId: number;
senderName: string;
text: string;
timestamp: string; // ISO date string
@ -15,7 +15,7 @@ export interface Toy {
images: string[];
unavailableRanges: { startDate: string; endDate: string }[];
ownerName: string;
ownerId: string;
ownerId: number;
ownerAvatarUrl?: string;
pricePerDay?: number;
location?: string;
@ -23,7 +23,7 @@ export interface Toy {
}
export interface User {
id: string;
id: number;
name: string; // This is now Full Name
nickname?: string;
email: string;
@ -40,7 +40,7 @@ export interface DailyAvailability {
export interface RentalHistoryEntry {
id:string;
userId: string;
userId: number;
toy: Toy;
rentalStartDate: string;
rentalEndDate: string;
@ -53,7 +53,7 @@ export interface RentalRequest {
id: string;
toy: Toy;
requesterName: string;
requesterId: string;
requesterId: number;
requestedDates: string;
status: 'pending' | 'approved' | 'declined';
message?: string;