Skip to content

Commit a1d2f8d

Browse files
committed
feat: ability to change profile settings
1 parent a3d8a37 commit a1d2f8d

File tree

18 files changed

+292
-71
lines changed

18 files changed

+292
-71
lines changed

apps/web/src/app/(main)/(auth)/register/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,12 @@ export default function Page() {
7070
type="name"
7171
inputName="Username"
7272
placeholder="Username"
73+
regex={/^[a-z0-9_.]+$/}
7374
error={errors?.errors?.username}
7475
/>
75-
<span className="text-600 ">
76-
Username must contain [A-Z][a-z][0-9], underscore, and periods.
77-
</span>
76+
{/* <span className="text-600 ">
77+
Username must contain [a-z][0-9], underscore, and periods.
78+
</span> */}
7879
<InputField
7980
inputName="Password"
8081
type="password"

apps/web/src/app/(main)/(settings)/settings/(account)/privacy/page.tsx

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,26 @@ export const metadata = {
99
export default function SettingsPrivacyPage() {
1010
return (
1111
<GroupContainer>
12-
<Group title="Change Password">
13-
<form className="flex flex-col gap-y-2">
14-
<InputField type="password" inputName="Current password" />
15-
<InputField type="password" inputName="New password" />
16-
<InputField type="password" inputName="Repeat new password" />
17-
<span>
18-
<Button>Change password</Button>
19-
</span>
20-
</form>
21-
</Group>
2212
<Group
23-
title="Linked accounts"
24-
description="Manage your login from third-party authenticators"
13+
title="Danger zone"
14+
description={
15+
<>
16+
Deleting an account is irreversable and cannot be undone! Once you
17+
delete your account—your characters, images, and account history
18+
will be completely wiped from our servers to comply with GDPR
19+
standards. If you are certain that you'll delete your account,
20+
it's important to export your data first before proceeding.
21+
</>
22+
}
23+
learnMoreLink="/lmao"
2524
>
26-
<Button>Link account via Google</Button>
27-
<Button>Link account via X</Button>
28-
<Button>Link account via Apple ID</Button>
25+
<div className="flex gap-x-2">
26+
<Button>Export data</Button>
27+
<Button variant="alert-secondary">Deactivate account</Button>
28+
<Button variant="alert">Delete account</Button>
29+
</div>
2930
</Group>
30-
<Group title="Two-factor authentication">content</Group>
31+
3132
</GroupContainer>
3233
)
3334
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"use client"
2+
3+
import { UserType } from "@/types/users"
4+
import { pronounOptions } from "@/utils/constants"
5+
import { Button } from "@mav/ui/components/buttons"
6+
import { InputField } from "@mav/ui/components/fields"
7+
import { Group, GroupContainer } from "@mav/ui/components/layouts"
8+
import { SelectField } from "@/components/layouts/Forms"
9+
import { useEffect, useState } from "react"
10+
import { updateProfile } from "@/utils/api"
11+
import DropZone from "@/components/Modals/DropZone"
12+
13+
14+
export default function ProfileSettings({ user }: { user: UserType }) {
15+
const [displayName, setDisplayName] = useState(user.displayName ?? "")
16+
const [handle, setHandle] = useState(user.handle)
17+
const [pronouns, setPronouns] = useState(user.pronouns ?? "")
18+
const [avatarUrl, setAvatarUrl] = useState("")
19+
const [isDirty, setIsDirty] = useState(false)
20+
const [isSaving, setIsSaving] = useState(false)
21+
22+
useEffect(() => {
23+
const changed =
24+
displayName !== (user.displayName ?? "") ||
25+
handle !== user.handle ||
26+
pronouns !== (user.pronouns ?? "") ||
27+
avatarUrl !== (user.avatarUrl ?? "")
28+
setIsDirty(changed)
29+
}, [displayName, handle, pronouns, user])
30+
31+
const handleSave = async () => {
32+
setIsSaving(true)
33+
try {
34+
await updateProfile({
35+
displayName,
36+
handle,
37+
pronouns,
38+
avatarLink: avatarUrl
39+
})
40+
setIsDirty(false)
41+
} catch (error) {
42+
console.error("Failed to save profile settings", error)
43+
} finally {
44+
setIsSaving(false)
45+
}
46+
}
47+
48+
return (
49+
<GroupContainer>
50+
<Group
51+
title="Public profile"
52+
potentialActions={
53+
isDirty ? (
54+
<Button
55+
onClick={handleSave}
56+
disabled={isSaving}
57+
variant="primary"
58+
>
59+
{isSaving ? "Saving..." : "Save Changes"}
60+
</Button>
61+
) : (
62+
<></>
63+
)
64+
}
65+
>
66+
<div className="flex flex-row gap-6 justify-between">
67+
<div className="w-full flex flex-col gap-y-4">
68+
<InputField
69+
inputName="Name"
70+
value={displayName}
71+
placeholder="Enter your display name"
72+
onChange={(e) => setDisplayName(e.target.value)}
73+
/>
74+
<InputField
75+
inputName="Handle"
76+
value={handle}
77+
onChange={(e) => setHandle(e.target.value)}
78+
/>
79+
<SelectField
80+
inputName="Pronouns"
81+
value={pronouns}
82+
onChange={(e) => setPronouns(e.target.value)}
83+
options={pronounOptions}
84+
/>
85+
</div>
86+
<DropZone setData={(url) => setAvatarUrl(url)} value={avatarUrl} />
87+
</div>
88+
</Group>
89+
</GroupContainer>
90+
)
91+
}
Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,16 @@
1-
import { Button } from "@mav/ui/components/buttons"
2-
import { Group, GroupContainer } from "@mav/ui/components/layouts"
1+
import { fetchUserData } from "@/utils/api"
2+
import ProfileSettings from "./ProfileSettings"
3+
import { redirect } from "next/navigation"
34

45
export const metadata = {
56
title: "Profile"
67
}
78

8-
export default function SettingsProfilePage() {
9+
export default async function SettingsProfilePage() {
10+
const user = await fetchUserData()
11+
if (!user) return redirect("/login")
12+
// TODO: Update API
913
return (
10-
<GroupContainer>
11-
<Group title="Public profile">content</Group>
12-
<Group
13-
title="Danger zone"
14-
description={
15-
<>
16-
Deleting an account is irreversable and cannot be undone! Once you
17-
delete your account—your characters, images, and account history
18-
will be completely wiped from our servers to comply with GDPR
19-
standards. If you are certain that you'll delete your account,
20-
it's important to export your data first before proceeding.
21-
</>
22-
}
23-
learnMoreLink="/lmao"
24-
>
25-
<div className="flex gap-x-2">
26-
<Button>Export data</Button>
27-
<Button variant="alert-secondary">Deactivate account</Button>
28-
<Button variant="alert">Delete account</Button>
29-
</div>
30-
</Group>
31-
</GroupContainer>
14+
<ProfileSettings user={user} />
3215
)
3316
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"use client"
2+
3+
import { UserType } from "@/types/users";
4+
import { Button } from "@mav/ui/components/buttons";
5+
import { InputField } from "@mav/ui/components/fields";
6+
import { GroupContainer, Group } from "@mav/ui/components/layouts";
7+
8+
export default function SecuritySettings({ user }: { user: UserType }) {
9+
return (
10+
<GroupContainer>
11+
<Group title="Change Password">
12+
<form className="flex flex-col gap-y-2">
13+
<InputField type="password" inputName="Current password" />
14+
<InputField type="password" inputName="New password" />
15+
<InputField type="password" inputName="Repeat new password" />
16+
<span>
17+
<Button>Change password</Button>
18+
</span>
19+
</form>
20+
</Group>
21+
<Group
22+
title="Linked accounts"
23+
description="Manage your login from third-party authenticators"
24+
>
25+
<Button>Link account via Google</Button>
26+
<Button>Link account via X</Button>
27+
<Button>Link account via Apple ID</Button>
28+
</Group>
29+
<Group title="Two-factor authentication">content</Group>
30+
31+
</GroupContainer>
32+
)
33+
}
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
import { fetchUserData } from "@/utils/api"
2+
import SecuritySettings from "./SecuritySettings"
3+
import { redirect } from "next/navigation"
4+
15
export const metadata = {
2-
title: "Security"
6+
title: "Profile"
37
}
48

5-
export default function SettingsSecurityPage() {
6-
return <>Security page</>
9+
export default async function SettingsProfilePage() {
10+
const user = await fetchUserData()
11+
if (!user) return redirect("/login")
12+
return (
13+
<SecuritySettings user={user} />
14+
)
715
}

apps/web/src/app/(main)/(settings)/settings/layout.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,12 @@ export const metadata: Metadata = {
99
}
1010
}
1111

12-
export default function SettingsLayout(
12+
export default async function SettingsLayout(
1313
props: Readonly<React.PropsWithChildren>
1414
) {
1515
return (
1616
<div className="mx-auto mb-20 max-w-[1400px] px-4">
17-
{/* User info */}
18-
<div className="bg-200 my-3 flex items-center gap-x-3 rounded-md px-3.5 py-3">
19-
<div className="bg-300 size-11 rounded-full" />
20-
<div className="flex flex-col">
21-
<div className="text-lg font-bold">Logged in as USER</div>
22-
<div className="inline-flex gap-x-1 text-xs">
23-
<span className="text-subtext">{"Not you? "}</span>
24-
<span className="text-hyperlink hover:underline">
25-
Switch accounts
26-
</span>
27-
</div>
28-
</div>
29-
</div>
3017
<div className="flex gap-x-6">
31-
{/* Setting items */}
3218
<SidebarSettingsList />
3319
<div className="flex-1 px-0.5 pt-1">{props.children}</div>
3420
</div>

apps/web/src/app/(profile)/profile/[handle]/(user)/(overview)/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { DefineRouteParams } from "@/types"
22
import { fetchUser, getPanels } from "@/utils/api"
33
import OverviewContent from "./OverviewContent"
4+
import { redirect } from "next/navigation"
45

56
type AsyncProps = DefineRouteParams<{ handle: string }>
67

apps/web/src/app/(studio)/studio/(general)/characters/CharactersView.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,32 @@
33
import { Character } from '@/types/characters'
44
import { Button } from '@mav/ui/components/buttons'
55
import { Group } from '@mav/ui/components/layouts'
6-
import React from 'react'
6+
import { useEffect, useState } from 'react'
77
import { FaEllipsisVertical } from 'react-icons/fa6'
88
import CreateCharacterModal from '@/components/Modals/CreateCharacter'
99
import { CharacterCard } from '@/components/layouts/Cards'
1010
import Avatar from '@/components/Avatar'
1111
import Checkbox from '@/components/layouts/Forms/Checkbox'
1212
import { LuEye, LuLock } from 'react-icons/lu'
13+
import { useRouter, useSearchParams } from 'next/navigation'
1314

1415
export default function CharactersView({ characters }: { characters: Character[] }) {
15-
const [isCreateModalOpen, setIsCreateModalOpen] = React.useState(false)
16+
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false)
17+
const router = useRouter()
18+
const searchParams = useSearchParams()
1619
const toggleCreateCharacterModal = () => {
1720
setIsCreateModalOpen(!isCreateModalOpen)
1821
}
22+
useEffect(() => {
23+
const showModal = searchParams.get('createModal') === 'true'
24+
if (showModal) {
25+
setIsCreateModalOpen(true)
26+
const params = new URLSearchParams(Array.from(searchParams.entries()))
27+
params.delete('createModal')
28+
const newUrl = `${window.location.pathname}?${params.toString()}`
29+
router.replace(newUrl, { scroll: false })
30+
}
31+
}, [searchParams, router])
1932

2033
return (
2134
<div className="grid">

0 commit comments

Comments
 (0)