251127:1700 Frontend Start Build

This commit is contained in:
admin
2025-11-27 17:08:49 +07:00
parent 6abb746e08
commit 4f3aa87a93
1795 changed files with 893474 additions and 10 deletions

View File

@@ -0,0 +1,285 @@
// File: app/(dashboard)/profile/page.tsx
"use client";
import { useState } from "react";
import { useSession } from "next-auth/react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Loader2, User, Shield, Bell } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Switch } from "@/components/ui/switch";
import apiClient from "@/lib/api/client";
// -----------------------------------------------------------------------------
// Schemas
// -----------------------------------------------------------------------------
const passwordSchema = z
.object({
currentPassword: z.string().min(1, "กรุณาระบุรหัสผ่านปัจจุบัน"),
newPassword: z.string().min(8, "รหัสผ่านใหม่ต้องมีอย่างน้อย 8 ตัวอักษร"),
confirmPassword: z.string().min(1, "กรุณายืนยันรหัสผ่านใหม่"),
})
.refine((data) => data.newPassword === data.confirmPassword, {
message: "รหัสผ่านใหม่ไม่ตรงกัน",
path: ["confirmPassword"],
});
type PasswordValues = z.infer<typeof passwordSchema>;
export default function ProfilePage() {
const { data: session } = useSession();
const [isLoading, setIsLoading] = useState(false);
// --- Password Form Handling ---
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm<PasswordValues>({
resolver: zodResolver(passwordSchema),
});
const onPasswordSubmit = async (data: PasswordValues) => {
setIsLoading(true);
try {
// เรียก API เปลี่ยนรหัสผ่าน
await apiClient.put("/users/change-password", {
currentPassword: data.currentPassword,
newPassword: data.newPassword,
});
alert("เปลี่ยนรหัสผ่านสำเร็จ"); // ในอนาคตใช้ Toast
reset();
} catch (error) {
console.error(error);
alert("ไม่สามารถเปลี่ยนรหัสผ่านได้: รหัสผ่านปัจจุบันไม่ถูกต้อง");
} finally {
setIsLoading(false);
}
};
// --- Notification State (Mockup) ---
// ในการใช้งานจริง ควรดึงค่าจาก API /users/preferences หรือ UserPreferenceService
const [notifyEmail, setNotifyEmail] = useState(true);
const [notifyLine, setNotifyLine] = useState(true);
const [digestMode, setDigestMode] = useState(false);
// Helper to get initials
const userName = session?.user?.name || "User";
const userInitials = userName
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.substring(0, 2);
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Profile & Settings</h3>
<p className="text-sm text-muted-foreground">
</p>
</div>
<Tabs defaultValue="general" className="space-y-4">
<TabsList>
<TabsTrigger value="general" className="flex items-center gap-2">
<User className="h-4 w-4" />
General
</TabsTrigger>
<TabsTrigger value="security" className="flex items-center gap-2">
<Shield className="h-4 w-4" />
Security
</TabsTrigger>
<TabsTrigger value="notifications" className="flex items-center gap-2">
<Bell className="h-4 w-4" />
Notifications
</TabsTrigger>
</TabsList>
{/* 1. General Tab */}
<TabsContent value="general">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center gap-4">
<Avatar className="h-20 w-20">
<AvatarImage src={session?.user?.image || ""} />
<AvatarFallback className="text-lg">{userInitials}</AvatarFallback>
</Avatar>
<div>
<h4 className="text-lg font-semibold">{userName}</h4>
<p className="text-sm text-muted-foreground">{session?.user?.email}</p>
<div className="mt-2 inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-primary text-primary-foreground hover:bg-primary/80">
{session?.user?.role || "Member"}
</div>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label></Label>
<Input defaultValue={userName.split(" ")[0]} disabled />
</div>
<div className="space-y-2">
<Label></Label>
<Input defaultValue={userName.split(" ")[1] || ""} disabled />
</div>
<div className="space-y-2">
<Label></Label>
<Input defaultValue={session?.user?.email || ""} disabled />
</div>
<div className="space-y-2">
<Label> / </Label>
{/* ในอนาคตดึงจาก Organization ID */}
<Input defaultValue={`Organization ID: ${session?.user?.organizationId || "-"}`} disabled />
</div>
</div>
</CardContent>
{/* <CardFooter>
<Button>บันทึกการเปลี่ยนแปลง</Button>
</CardFooter>
*/}
</Card>
</TabsContent>
{/* 2. Security Tab */}
<TabsContent value="security">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
( 8 )
</CardDescription>
</CardHeader>
<form onSubmit={handleSubmit(onPasswordSubmit)}>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="currentPassword"></Label>
<Input
id="currentPassword"
type="password"
{...register("currentPassword")}
/>
{errors.currentPassword && (
<p className="text-xs text-destructive">{errors.currentPassword.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="newPassword"></Label>
<Input
id="newPassword"
type="password"
{...register("newPassword")}
/>
{errors.newPassword && (
<p className="text-xs text-destructive">{errors.newPassword.message}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword"></Label>
<Input
id="confirmPassword"
type="password"
{...register("confirmPassword")}
/>
{errors.confirmPassword && (
<p className="text-xs text-destructive">{errors.confirmPassword.message}</p>
)}
</div>
</CardContent>
<CardFooter>
<Button type="submit" disabled={isLoading}>
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
</Button>
</CardFooter>
</form>
</Card>
</TabsContent>
{/* 3. Notifications Tab */}
<TabsContent value="notifications">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="notify-email" className="flex flex-col space-y-1">
<span>Email Notifications</span>
<span className="font-normal text-xs text-muted-foreground">
</span>
</Label>
<Switch
id="notify-email"
checked={notifyEmail}
onCheckedChange={setNotifyEmail}
/>
</div>
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="notify-line" className="flex flex-col space-y-1">
<span>LINE Notifications</span>
<span className="font-normal text-xs text-muted-foreground">
LINE Official Account
</span>
</Label>
<Switch
id="notify-line"
checked={notifyLine}
onCheckedChange={setNotifyLine}
/>
</div>
<div className="flex items-center justify-between space-x-2">
<Label htmlFor="digest-mode" className="flex flex-col space-y-1">
<span>Digest Mode ()</span>
<span className="font-normal text-xs text-muted-foreground">
( Spam)
</span>
</Label>
<Switch
id="digest-mode"
checked={digestMode}
onCheckedChange={setDigestMode}
/>
</div>
</CardContent>
<CardFooter>
<Button variant="outline" onClick={() => alert("บันทึกการตั้งค่าแจ้งเตือนแล้ว")}>
</Button>
</CardFooter>
</Card>
</TabsContent>
</Tabs>
</div>
);
}