From 4fc7af85a67577283bae97f93b88df96139c7be4 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 21 Dec 2025 23:10:10 -0800 Subject: [PATCH] add optimistic updates and description tile to teams/new --- src/routes/(app)/teams/new/+page.svelte | 223 ++++++++++++++++++------ 1 file changed, 174 insertions(+), 49 deletions(-) diff --git a/src/routes/(app)/teams/new/+page.svelte b/src/routes/(app)/teams/new/+page.svelte index 7ac0ad5c..3f2f8e4a 100644 --- a/src/routes/(app)/teams/new/+page.svelte +++ b/src/routes/(app)/teams/new/+page.svelte @@ -46,6 +46,12 @@ } from '$lib/api/mutations/grid.mutations' import { Dialog } from 'bits-ui' import { replaceState } from '$app/navigation' + import DescriptionTile from '$lib/components/party/info/DescriptionTile.svelte' + import { openDescriptionPane } from '$lib/features/description/openDescriptionPane.svelte' + import { + openPartyEditSidebar, + type PartyEditValues + } from '$lib/features/party/openPartyEditSidebar.svelte' // Props interface Props { @@ -127,22 +133,25 @@ openJobSelectionSidebar({ currentJobId: party.job?.id, onSelectJob: async (job) => { - // If party exists, update via API + // Get the cache key being used by the query + const cacheKey = shortcode || 'new' + + // Optimistically update cache first for immediate UI response + queryClient.setQueryData(partyKeys.detail(cacheKey), (old: Party | undefined) => { + if (!old) return { ...placeholderParty, job } + return { ...old, job } + }) + + // If party exists, persist to API if (partyId && shortcode) { try { await partyAdapter.updateJob(shortcode, job.id) - // Cache will be updated via invalidation } catch (e) { console.error('Failed to update job:', e) errorMessage = e instanceof Error ? e.message : 'Failed to update job' errorDialogOpen = true + // Revert on error would go here if needed } - } else { - // Update cache locally for new party - queryClient.setQueryData(partyKeys.detail('new'), (old: Party | undefined) => { - if (!old) return placeholderParty - return { ...old, job } - }) } } }) @@ -154,11 +163,20 @@ currentSkills: party.jobSkills, targetSlot: slot, onSelectSkill: async (skill) => { - // If party exists, update via API + // Get the cache key being used by the query + const cacheKey = shortcode || 'new' + const updatedSkills = { ...party.jobSkills } + updatedSkills[String(slot) as keyof typeof updatedSkills] = skill + + // Optimistically update cache first for immediate UI response + queryClient.setQueryData(partyKeys.detail(cacheKey), (old: Party | undefined) => { + if (!old) return { ...placeholderParty, jobSkills: updatedSkills } + return { ...old, jobSkills: updatedSkills } + }) + + // If party exists, persist to API if (partyId && shortcode) { try { - const updatedSkills = { ...party.jobSkills } - updatedSkills[String(slot) as keyof typeof updatedSkills] = skill const skillsArray = transformSkillsToArray(updatedSkills) await partyAdapter.updateJobSkills(shortcode, skillsArray) } catch (e) { @@ -166,14 +184,6 @@ errorMessage = e instanceof Error ? e.message : 'Failed to update skill' errorDialogOpen = true } - } else { - // Update cache locally for new party - queryClient.setQueryData(partyKeys.detail('new'), (old: Party | undefined) => { - if (!old) return placeholderParty - const updatedSkills = { ...old.jobSkills } - updatedSkills[String(slot) as keyof typeof updatedSkills] = skill - return { ...old, jobSkills: updatedSkills } - }) } }, onRemoveSkill: async () => { @@ -183,10 +193,20 @@ } async function handleRemoveJobSkill(slot: number) { + // Get the cache key being used by the query + const cacheKey = shortcode || 'new' + const updatedSkills = { ...party.jobSkills } + delete updatedSkills[String(slot) as keyof typeof updatedSkills] + + // Optimistically update cache first for immediate UI response + queryClient.setQueryData(partyKeys.detail(cacheKey), (old: Party | undefined) => { + if (!old) return { ...placeholderParty, jobSkills: updatedSkills } + return { ...old, jobSkills: updatedSkills } + }) + + // If party exists, persist to API if (partyId && shortcode) { try { - const updatedSkills = { ...party.jobSkills } - delete updatedSkills[String(slot) as keyof typeof updatedSkills] const skillsArray = transformSkillsToArray(updatedSkills) await partyAdapter.updateJobSkills(shortcode, skillsArray) } catch (e) { @@ -194,17 +214,132 @@ errorMessage = e instanceof Error ? e.message : 'Failed to remove skill' errorDialogOpen = true } - } else { - // Update cache locally for new party - queryClient.setQueryData(partyKeys.detail('new'), (old: Party | undefined) => { - if (!old) return placeholderParty - const updatedSkills = { ...old.jobSkills } - delete updatedSkills[String(slot) as keyof typeof updatedSkills] - return { ...old, jobSkills: updatedSkills } - }) } } + // Helper to ensure party exists before saving metadata + async function ensurePartyExists(): Promise<{ id: string; shortcode: string }> { + if (partyId && shortcode) { + return { id: partyId, shortcode } + } + + // Create party with current metadata from cache + const partyPayload: any = { + name: party.name || 'New Team', + visibility: 1, + element: party.element || 0 + } + + if (!isAuthenticated) { + partyPayload.localId = getLocalId() + } + + isCreatingParty = true + try { + const createdParty = await createPartyMutation.mutateAsync(partyPayload) + + partyId = createdParty.id + shortcode = createdParty.shortcode + + if (createdParty.editKey) { + storeEditKey(createdParty.shortcode, createdParty.editKey) + storeEditKey(createdParty.id, createdParty.editKey) + } + + // Update URL + replaceState(`/teams/${createdParty.shortcode}`, {}) + + // Update cache + queryClient.setQueryData(partyKeys.detail(createdParty.shortcode), createdParty) + + return { id: createdParty.id, shortcode: createdParty.shortcode } + } finally { + isCreatingParty = false + } + } + + // Description/Edit handlers + function handleOpenDescription() { + openDescriptionPane({ + title: party.name || 'New Team', + description: party.description, + videoUrl: party.videoUrl, + canEdit: true, + partyId: partyId ?? undefined, + partyShortcode: shortcode ?? undefined, + onSave: async (description) => { + const { id, shortcode: sc } = await ensurePartyExists() + await partyAdapter.update({ id, shortcode: sc, description }) + // Update cache + queryClient.setQueryData(partyKeys.detail(sc), (old: Party | undefined) => { + if (!old) return placeholderParty + return { ...old, description } + }) + } + }) + } + + function handleOpenEdit() { + const initialValues: PartyEditValues = { + name: party.name ?? 'New Team', + description: party.description ?? null, + fullAuto: party.fullAuto ?? false, + autoGuard: party.autoGuard ?? false, + autoSummon: party.autoSummon ?? false, + chargeAttack: party.chargeAttack ?? true, + clearTime: party.clearTime ?? null, + buttonCount: party.buttonCount ?? null, + chainCount: party.chainCount ?? null, + summonCount: party.summonCount ?? null, + videoUrl: party.videoUrl ?? null, + raid: party.raid ?? null, + raidId: party.raid?.id ?? null + } + + openPartyEditSidebar({ + initialValues, + onSave: async (values) => { + const { id, shortcode: sc } = await ensurePartyExists() + await partyAdapter.update({ + id, + shortcode: sc, + name: values.name, + description: values.description ?? undefined, + fullAuto: values.fullAuto, + autoGuard: values.autoGuard, + autoSummon: values.autoSummon, + chargeAttack: values.chargeAttack, + clearTime: values.clearTime ?? undefined, + buttonCount: values.buttonCount ?? undefined, + chainCount: values.chainCount ?? undefined, + summonCount: values.summonCount ?? undefined, + videoUrl: values.videoUrl ?? undefined, + raidId: values.raidId ?? undefined + }) + // Update cache + queryClient.setQueryData(partyKeys.detail(sc), (old: Party | undefined) => { + if (!old) return placeholderParty + return { + ...old, + name: values.name, + description: values.description, + fullAuto: values.fullAuto, + autoGuard: values.autoGuard, + autoSummon: values.autoSummon, + chargeAttack: values.chargeAttack, + clearTime: values.clearTime, + buttonCount: values.buttonCount, + chainCount: values.chainCount, + summonCount: values.summonCount, + videoUrl: values.videoUrl, + raid: values.raid, + raidId: values.raidId + } + }) + } + }) + } + // Party state let partyId = $state(null) let shortcode = $state(null) @@ -622,12 +757,15 @@
-
-
-

Create a new team

-

Search and click items to add them to your grid

-
-
+
+ +