89d456fc76
Replaces template's mock account form with real Appwrite-backed actions. Server actions (lib/appwrite/profile-actions.ts): - updateNameAction: account.updateName via session SDK; revalidates layout so the new name shows in sidebar/header right away. - updateEmailAction: account.updateEmail (requires current password as Appwrite confirmation). Maps user_email_already_exists to a friendly Turkish message. - updatePasswordAction: account.updatePassword(new, old). Validates old != empty, new >= 8 chars, new === confirm. Maps user_password_recently_used / user_password_mismatch. - All audit-logged with entityType user_name / user_email / user_password and tenantId of the user's currently-active workspace (or 'global' if none). Audit failures swallowed. UI: - /settings/account is now an async server page that pulls the user via getCurrentUser, renders an account info card ($id, registration date, email verification, MFA), then 3 separate small forms — one card each — for name, email, password. Each form clears its own state and gives toast feedback independently. - Removed the template's react-hook-form-based mock page. Side note: skipped email-verification flow + MFA setup for later.
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
"use client";
|
||
|
||
import { useActionState, useEffect } from "react";
|
||
import { Loader2, Save } from "lucide-react";
|
||
import { toast } from "sonner";
|
||
|
||
import { Button } from "@/components/ui/button";
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Label } from "@/components/ui/label";
|
||
import { updateNameAction } from "@/lib/appwrite/profile-actions";
|
||
import { initialProfileState } from "@/lib/appwrite/profile-types";
|
||
|
||
export function NameForm({ currentName }: { currentName: string }) {
|
||
const [state, formAction, isPending] = useActionState(updateNameAction, initialProfileState);
|
||
|
||
useEffect(() => {
|
||
if (state.ok) toast.success("İsim güncellendi.");
|
||
else if (state.error) toast.error(state.error);
|
||
}, [state]);
|
||
|
||
return (
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>Görünür isim</CardTitle>
|
||
<CardDescription>
|
||
Header'da, davetlerde ve takım listesinde görünecek isim.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<form action={formAction} className="grid gap-4 md:grid-cols-[1fr_auto] md:items-end">
|
||
<div className="grid gap-2">
|
||
<Label htmlFor="name">İsim</Label>
|
||
<Input id="name" name="name" defaultValue={currentName} required maxLength={128} />
|
||
{state.fieldErrors?.name && (
|
||
<p className="text-destructive text-xs">{state.fieldErrors.name}</p>
|
||
)}
|
||
</div>
|
||
<Button type="submit" disabled={isPending}>
|
||
{isPending ? (
|
||
<>
|
||
<Loader2 className="size-4 animate-spin" />
|
||
Kaydediliyor...
|
||
</>
|
||
) : (
|
||
<>
|
||
<Save className="size-4" />
|
||
Kaydet
|
||
</>
|
||
)}
|
||
</Button>
|
||
</form>
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
}
|