diff --git a/app/(account)/personal-info/page.tsx b/app/(account)/personal-info/page.tsx
index b932152..b094fc8 100644
--- a/app/(account)/personal-info/page.tsx
+++ b/app/(account)/personal-info/page.tsx
@@ -2,13 +2,14 @@
import React, { useRef, useState } from 'react';
import { useAuth } from '@samkiel/authsdk/react';
-import { SamkielAuthError } from '@samkiel/authsdk';
+import { SamkielAuthError, UsernameChangeCooldownError } from '@samkiel/authsdk';
import { Card } from '@/components/ui/Card';
import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { Avatar } from '@/components/ui/Avatar';
import { Badge } from '@/components/ui/Badge';
import { Skeleton } from '@/components/ui/Skeleton';
+import { CopyButton } from '@/components/ui/CopyButton';
import { Camera, Loader2 } from 'lucide-react';
import { toast } from 'sonner';
@@ -21,12 +22,25 @@ function formatJoinDate(iso?: string) {
}
}
+function formatFullDate(iso?: string) {
+ if (!iso) return 'a later date';
+ try {
+ return new Date(iso).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
+ } catch {
+ return 'a later date';
+ }
+}
+
export default function PersonalInfoPage() {
- const { user, isLoading: authLoading, updateName, updateEmail, uploadAvatar } = useAuth();
+ const { user, isLoading: authLoading, updateName, updateEmail, uploadAvatar, changeUsername } = useAuth();
const [name, setName] = useState(user?.name ?? '');
const [isSavingName, setIsSavingName] = useState(false);
+ const [editingUsername, setEditingUsername] = useState(false);
+ const [usernameInput, setUsernameInput] = useState('');
+ const [isSavingUsername, setIsSavingUsername] = useState(false);
+
const [newEmail, setNewEmail] = useState('');
const [currentPassword, setCurrentPassword] = useState('');
const [isSavingEmail, setIsSavingEmail] = useState(false);
@@ -74,6 +88,36 @@ export default function PersonalInfoPage() {
}
};
+ const startEditUsername = () => {
+ setUsernameInput(user?.username ?? '');
+ setEditingUsername(true);
+ };
+
+ const handleChangeUsername = async (e: React.FormEvent) => {
+ e.preventDefault();
+ const trimmed = usernameInput.trim().toLowerCase();
+ if (!trimmed || trimmed === user?.username) {
+ setEditingUsername(false);
+ return;
+ }
+
+ setIsSavingUsername(true);
+ try {
+ await changeUsername(trimmed);
+ toast.success('Username updated.');
+ setEditingUsername(false);
+ } catch (err) {
+ if (err instanceof UsernameChangeCooldownError) {
+ toast.error(`You can next change your username on ${formatFullDate(err.nextChangeAt)}.`);
+ } else {
+ const msg = err instanceof Error ? err.message : 'Could not update your username.';
+ toast.error(msg);
+ }
+ } finally {
+ setIsSavingUsername(false);
+ }
+ };
+
const handleUpdateEmail = async (e: React.FormEvent) => {
e.preventDefault();
if (!newEmail || !currentPassword) return;
@@ -205,6 +249,72 @@ export default function PersonalInfoPage() {
+ {/* Username */}
+
+
+
+
Username
+
+ Your unique handle across SAMKIEL. Changeable once every 23 days.
+
+
+ {!editingUsername && (
+
+ )}
+
+ {editingUsername ? (
+
+ ) : (
+ @{user?.username ?? '—'}
+ )}
+
+
+ {/* SAMKIEL ID */}
+
+
+
SAMKIEL ID
+
Cannot be changed.
+
+
+
+ {user?.samkielId ?? '—'}
+
+ {user?.samkielId && }
+
+
+
Email address
diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx
index 76b0e7c..8094efc 100644
--- a/app/(auth)/login/page.tsx
+++ b/app/(auth)/login/page.tsx
@@ -114,13 +114,16 @@ function LoginContent() {