Skip to content

Commit d515fbe

Browse files
committed
feat: character info updates
1 parent a1d2f8d commit d515fbe

File tree

4 files changed

+105
-14
lines changed

4 files changed

+105
-14
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default function CharactersView({ characters }: { characters: Character[]
6363
</thead>
6464
<tbody>
6565
{characters.map((characters, index) => (
66-
<tr key={characters.name} className="border-b border-100 hover:bg-200" onClick={() => window.location.href = `/studio/characters/${characters.id}`}>
66+
<tr key={characters.name} className="border-b border-100 hover:bg-200 cursor-pointer" onClick={() => window.location.href = `/studio/characters/${characters.id}`}>
6767
<td className="px-2">
6868
<Checkbox
6969
inputName={`character-select-${index}`}

apps/web/src/app/(studio)/studio/(general)/characters/[id]/EditCharacter.tsx

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState } from 'react'
3+
import { useEffect, useState } from 'react'
44
import Checkbox from '@/components/layouts/Forms/Checkbox'
55
import MarginClamp from '@/components/layouts/Layouts/MarginClamp'
66
import DropZone from '@/components/Modals/DropZone'
@@ -9,6 +9,9 @@ import { Group, GroupContainer, MarginGutter } from '@mav/ui/components/layouts'
99
import { InputField, Textarea } from '@mav/ui/components/fields'
1010
import { SelectField } from '@/components/layouts/Forms'
1111
import { furrySpeciesOptions, pronounOptions } from '@/utils/constants'
12+
import { Button } from '@mav/ui/components/buttons/Button'
13+
import { updateCharacter } from '@/utils/api'
14+
import { redirect } from 'next/navigation'
1215

1316
export default function EditCharacter({ character }: { character: Character }) {
1417
const [avatarUrl, setAvatarUrl] = useState<string>(character.avatarUrl)
@@ -20,6 +23,49 @@ export default function EditCharacter({ character }: { character: Character }) {
2023
const [species, setSpecies] = useState<string>(character.species ?? '')
2124
const [bio, setBio] = useState<string>(character.attributes.bio ?? '')
2225

26+
const [isDirty, setIsDirty] = useState(false)
27+
const [isSaving, setIsSaving] = useState(false)
28+
29+
useEffect(() => {
30+
const changed =
31+
displayName !== (character.name ?? "") ||
32+
nickname !== (character.nickname ?? "") ||
33+
isMainCharacter !== character.mainCharacter ||
34+
characterUrl !== (character.slug ?? "") ||
35+
pronouns !== (character.attributes.pronouns ?? "") ||
36+
species !== (character.species ?? "") ||
37+
bio !== (character.attributes.bio ?? "")
38+
setIsDirty(changed)
39+
}, [displayName, nickname, isMainCharacter, characterUrl, pronouns, species, bio])
40+
41+
const handleSave = async () => {
42+
setIsSaving(true)
43+
try {
44+
const attributes = {
45+
pronouns,
46+
bio,
47+
gender: character.attributes.gender ?? "",
48+
preferences: character.attributes.preferences ?? { likes: "", dislikes: "" },
49+
custom_fields: character.attributes.custom_fields ?? [],
50+
}
51+
await updateCharacter(character.id, {
52+
name: displayName,
53+
nickname,
54+
mainCharacter: isMainCharacter,
55+
slug: characterUrl,
56+
attributes,
57+
species,
58+
avatarUrl
59+
})
60+
setIsDirty(false)
61+
redirect('/studio/characters')
62+
} catch (error) {
63+
console.error("Failed to save profile settings", error)
64+
} finally {
65+
setIsSaving(false)
66+
}
67+
}
68+
2369
return (
2470
<MarginGutter screenSize="xl" className="px-6 py-5 *:mt-6 *:gap-6 first:*:mt-0">
2571
<GroupContainer>
@@ -82,7 +128,33 @@ export default function EditCharacter({ character }: { character: Character }) {
82128
/>
83129
</div>
84130
</Group>
131+
<Group title='Reference sheets' description="Reference sheets will appear in the list if you have this character linked and be shown on the public profile. Learn more">
132+
<div className='flex flex-row gap-x-2'>
133+
<Button
134+
variant="primary"
135+
onClick={() => {}}
136+
>
137+
Add Reference Sheet
138+
</Button>
139+
<Button
140+
variant="secondary"
141+
onClick={() => {}}
142+
>
143+
Manage Reference Sheet
144+
</Button>
145+
</div>
146+
</Group>
85147
</GroupContainer>
148+
{isDirty && (
149+
<div className="flex justify-end mt-4">
150+
<Button
151+
onClick={handleSave}
152+
disabled={isSaving}
153+
>
154+
{isSaving ? "Saving..." : "Save Changes"}
155+
</Button>
156+
</div>
157+
)}
86158
</MarginGutter>
87159
)
88160
}

apps/web/src/components/layouts/StudioLayout/Sidebar.tsx

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { Button } from "@mav/ui/components/buttons"
44
import { motion } from "framer-motion"
5-
import { LuSettings } from "react-icons/lu"
5+
import { LuArrowLeft, LuSettings } from "react-icons/lu"
66
import { useSidebarOpenAtom } from "./Sidebar.atom"
77
import SidebarItem from "./SidebarItem"
88
import { generateEditSidebarItems, generateSidebarItems } from "./SidebarItems"
@@ -11,6 +11,7 @@ import { useRouter, usePathname } from "next/navigation"
1111
import Image from "next/image"
1212
import Avatar from "@/components/Avatar"
1313
import clsx from 'clsx'
14+
import { SelectField } from "../Forms"
1415

1516
export default function Sidebar({ user }: { user: User }) {
1617
const { sidebarState: isSidebarExpanded } = useSidebarOpenAtom()
@@ -32,18 +33,32 @@ export default function Sidebar({ user }: { user: User }) {
3233
>
3334
<div
3435
data-mav-list-renderer=""
35-
className="flex h-full flex-col px-2 py-1.5 gap-2"
36+
className="flex h-full flex-col py-1.5 gap-2"
3637
>
37-
<div className={clsx("p-3 flex flex-row bg-100 rounded-md", isSidebarExpanded ? "justify-start" : "justify-center")}>
38-
<Avatar
39-
username={user.handle}
40-
size={isSidebarExpanded ? 40 : 30}
41-
src={user.avatarUrl || "/UserProfile.png"}
42-
/>
43-
{isSidebarExpanded && (
44-
<div className="flex flex-col justify-center ml-2">
45-
<span className="text-sm">{user.displayName}</span>
46-
<span className="text-sm text-subtext">@{user.handle}</span>
38+
<div className={clsx("flex items-center justify-between", isSidebarExpanded ? "px-3" : "pr-2")}>
39+
{characterId ? (
40+
<div className="flex gap-2 w-full flex-col">
41+
<Button href="/studio/characters" icon={<LuArrowLeft size={20} />}>Back to Characters</Button>
42+
<SelectField
43+
options={user.characters.map(character => ({ value: character.id, label: character.name }))}
44+
inputName="Editing"
45+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => window.location.href = `/studio/characters/${e.target.value}`}
46+
value={characterId ?? ""}
47+
/>
48+
</div>
49+
) : (
50+
<div className={clsx("p-3 flex flex-row bg-100 rounded-md", isSidebarExpanded ? "justify-start" : "justify-center")}>
51+
<Avatar
52+
username={user.handle}
53+
size={isSidebarExpanded ? 40 : 30}
54+
src={user.avatarUrl || "/UserProfile.png"}
55+
/>
56+
{isSidebarExpanded && (
57+
<div className="flex flex-col justify-center ml-2">
58+
<span className="text-sm">{user.displayName}</span>
59+
<span className="text-sm text-subtext">@{user.handle}</span>
60+
</div>
61+
)}
4762
</div>
4863
)}
4964
</div>

apps/web/src/utils/api.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ export const fetchUserCharacters = async (handle: string) => {
156156
return data
157157
}
158158

159+
export const updateCharacter = async (characterId: string, data: Partial<Character>) => {
160+
return apiWithAuth("PUT", `/v1/character/update/${characterId}`, data)
161+
}
162+
159163
export const fetchSelfCharacters = async () => {
160164
const characters = await apiWithAuth<Character[]>("GET", "/v1/character/")
161165
return characters

0 commit comments

Comments
 (0)