fix: party mutation parameter types
- favorite/unfavorite take shortcode string directly
- delete takes {id, shortcode} since API expects UUID
- remix takes shortcode string directly
- update spreads updates at top level
This commit is contained in:
parent
f346a73b61
commit
18328e2d38
3 changed files with 71 additions and 27 deletions
|
|
@ -133,9 +133,10 @@ export class PartyAdapter extends BaseAdapter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a party
|
* Deletes a party
|
||||||
|
* @param id - The party's UUID (not shortcode - API requires UUID for delete)
|
||||||
*/
|
*/
|
||||||
async delete(shortcode: string): Promise<void> {
|
async delete(id: string): Promise<void> {
|
||||||
return this.request<void>(`/parties/${shortcode}`, {
|
return this.request<void>(`/parties/${id}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ export function useUpdateParty() {
|
||||||
* Delete party mutation
|
* Delete party mutation
|
||||||
*
|
*
|
||||||
* Deletes a party and removes it from all caches.
|
* Deletes a party and removes it from all caches.
|
||||||
|
* Note: The API expects the party UUID (id), not the shortcode.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```svelte
|
* ```svelte
|
||||||
|
|
@ -116,8 +117,8 @@ export function useUpdateParty() {
|
||||||
*
|
*
|
||||||
* const deleteParty = useDeleteParty()
|
* const deleteParty = useDeleteParty()
|
||||||
*
|
*
|
||||||
* function handleDelete(shortcode: string) {
|
* function handleDelete(id: string, shortcode: string) {
|
||||||
* deleteParty.mutate(shortcode)
|
* deleteParty.mutate({ id, shortcode })
|
||||||
* }
|
* }
|
||||||
* </script>
|
* </script>
|
||||||
* ```
|
* ```
|
||||||
|
|
@ -126,10 +127,10 @@ export function useDeleteParty() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
return createMutation(() => ({
|
return createMutation(() => ({
|
||||||
mutationFn: (shortcode: string) => partyAdapter.delete(shortcode),
|
mutationFn: (params: { id: string; shortcode: string }) => partyAdapter.delete(params.id),
|
||||||
onSuccess: (_data, shortcode) => {
|
onSuccess: (_data, params) => {
|
||||||
// Remove the party from cache
|
// Remove the party from cache (keyed by shortcode)
|
||||||
queryClient.removeQueries({ queryKey: partyKeys.detail(shortcode) })
|
queryClient.removeQueries({ queryKey: partyKeys.detail(params.shortcode) })
|
||||||
// Invalidate party lists
|
// Invalidate party lists
|
||||||
queryClient.invalidateQueries({ queryKey: partyKeys.lists() })
|
queryClient.invalidateQueries({ queryKey: partyKeys.lists() })
|
||||||
// Invalidate user's party lists
|
// Invalidate user's party lists
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@
|
||||||
import { extractErrorMessage } from '$lib/utils/errors'
|
import { extractErrorMessage } from '$lib/utils/errors'
|
||||||
import { transformSkillsToArray } from '$lib/utils/jobSkills'
|
import { transformSkillsToArray } from '$lib/utils/jobSkills'
|
||||||
import { findNextEmptySlot, SLOT_NOT_FOUND } from '$lib/utils/gridHelpers'
|
import { findNextEmptySlot, SLOT_NOT_FOUND } from '$lib/utils/gridHelpers'
|
||||||
|
import ConflictDialog from '$lib/components/dialogs/ConflictDialog.svelte'
|
||||||
|
import type { ConflictData } from '$lib/types/api/conflict'
|
||||||
|
import { isConflictResponse, createConflictData } from '$lib/types/api/conflict'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
party?: Party
|
party?: Party
|
||||||
|
|
@ -87,6 +90,8 @@
|
||||||
let selectedSlot = $state<number>(0)
|
let selectedSlot = $state<number>(0)
|
||||||
let editDialogOpen = $state(false)
|
let editDialogOpen = $state(false)
|
||||||
let editingTitle = $state('')
|
let editingTitle = $state('')
|
||||||
|
let conflictDialogOpen = $state(false)
|
||||||
|
let conflictData = $state<ConflictData | null>(null)
|
||||||
|
|
||||||
// TanStack Query mutations - Grid
|
// TanStack Query mutations - Grid
|
||||||
const createWeapon = useCreateGridWeapon()
|
const createWeapon = useCreateGridWeapon()
|
||||||
|
|
@ -243,6 +248,16 @@
|
||||||
|
|
||||||
function handleTabChange(tab: GridType) {
|
function handleTabChange(tab: GridType) {
|
||||||
activeTab = tab
|
activeTab = tab
|
||||||
|
// Update selectedSlot to the first valid empty slot for this tab
|
||||||
|
// Characters tab: position 0 is protagonist (not selectable), so start at 1
|
||||||
|
// Weapons/Summons: start at first empty slot
|
||||||
|
const nextEmpty = findNextEmptySlot(party, tab)
|
||||||
|
if (nextEmpty !== SLOT_NOT_FOUND) {
|
||||||
|
selectedSlot = nextEmpty
|
||||||
|
} else {
|
||||||
|
// Fallback: Characters start at 1 (skip protagonist), others at 0
|
||||||
|
selectedSlot = tab === GridType.Character ? 1 : 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit dialog functions
|
// Edit dialog functions
|
||||||
|
|
@ -278,7 +293,7 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use TanStack Query mutation to update party
|
// Use TanStack Query mutation to update party
|
||||||
await updatePartyMutation.mutateAsync({ shortcode: party.shortcode, updates })
|
await updatePartyMutation.mutateAsync({ shortcode: party.shortcode, ...updates })
|
||||||
// Party will be updated via cache invalidation
|
// Party will be updated via cache invalidation
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
error = err.message || 'Failed to update party'
|
error = err.message || 'Failed to update party'
|
||||||
|
|
@ -295,9 +310,9 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (party.favorited) {
|
if (party.favorited) {
|
||||||
await unfavoritePartyMutation.mutateAsync({ shortcode: party.shortcode })
|
await unfavoritePartyMutation.mutateAsync(party.shortcode)
|
||||||
} else {
|
} else {
|
||||||
await favoritePartyMutation.mutateAsync({ shortcode: party.shortcode })
|
await favoritePartyMutation.mutateAsync(party.shortcode)
|
||||||
}
|
}
|
||||||
// Party will be updated via cache invalidation
|
// Party will be updated via cache invalidation
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|
@ -312,20 +327,10 @@
|
||||||
error = null
|
error = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await remixPartyMutation.mutateAsync({
|
const newParty = await remixPartyMutation.mutateAsync(party.shortcode)
|
||||||
shortcode: party.shortcode,
|
|
||||||
localId,
|
|
||||||
editKey: editKey || undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
// Store new edit key if returned
|
|
||||||
if (result.editKey) {
|
|
||||||
storeEditKey(result.party.shortcode, result.editKey)
|
|
||||||
editKey = result.editKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigate to new party
|
// Navigate to new party
|
||||||
window.location.href = `/teams/${result.party.shortcode}`
|
window.location.href = `/teams/${newParty.shortcode}`
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
error = err.message || 'Failed to remix party'
|
error = err.message || 'Failed to remix party'
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -353,8 +358,8 @@
|
||||||
deleting = true
|
deleting = true
|
||||||
error = null
|
error = null
|
||||||
|
|
||||||
// Delete the party using mutation
|
// Delete the party using mutation (API expects UUID, cache uses shortcode)
|
||||||
await deletePartyMutation.mutateAsync({ shortcode: party.shortcode })
|
await deletePartyMutation.mutateAsync({ id: party.id, shortcode: party.shortcode })
|
||||||
|
|
||||||
// Navigate to user's own profile page after deletion
|
// Navigate to user's own profile page after deletion
|
||||||
if (party.user?.username) {
|
if (party.user?.username) {
|
||||||
|
|
@ -502,13 +507,22 @@
|
||||||
// Call appropriate create mutation based on current tab
|
// Call appropriate create mutation based on current tab
|
||||||
// Use granblueId (camelCase) as that's what the SearchResult type uses
|
// Use granblueId (camelCase) as that's what the SearchResult type uses
|
||||||
const itemId = item.granblueId
|
const itemId = item.granblueId
|
||||||
|
let result: unknown
|
||||||
|
|
||||||
if (activeTab === GridType.Weapon) {
|
if (activeTab === GridType.Weapon) {
|
||||||
await createWeapon.mutateAsync({
|
result = await createWeapon.mutateAsync({
|
||||||
partyId: party.id,
|
partyId: party.id,
|
||||||
weaponId: itemId,
|
weaponId: itemId,
|
||||||
position: targetSlot,
|
position: targetSlot,
|
||||||
mainhand: targetSlot === -1
|
mainhand: targetSlot === -1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Check if the result is a conflict response
|
||||||
|
if (isConflictResponse(result)) {
|
||||||
|
conflictData = createConflictData(result, 'weapon')
|
||||||
|
conflictDialogOpen = true
|
||||||
|
return
|
||||||
|
}
|
||||||
} else if (activeTab === GridType.Summon) {
|
} else if (activeTab === GridType.Summon) {
|
||||||
await createSummon.mutateAsync({
|
await createSummon.mutateAsync({
|
||||||
partyId: party.id,
|
partyId: party.id,
|
||||||
|
|
@ -518,11 +532,18 @@
|
||||||
friend: targetSlot === 6
|
friend: targetSlot === 6
|
||||||
})
|
})
|
||||||
} else if (activeTab === GridType.Character) {
|
} else if (activeTab === GridType.Character) {
|
||||||
await createCharacter.mutateAsync({
|
result = await createCharacter.mutateAsync({
|
||||||
partyId: party.id,
|
partyId: party.id,
|
||||||
characterId: itemId,
|
characterId: itemId,
|
||||||
position: targetSlot
|
position: targetSlot
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Check if the result is a conflict response
|
||||||
|
if (isConflictResponse(result)) {
|
||||||
|
conflictData = createConflictData(result, 'character')
|
||||||
|
conflictDialogOpen = true
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Party will be updated via cache invalidation from the mutation
|
// Party will be updated via cache invalidation from the mutation
|
||||||
|
|
@ -692,6 +713,8 @@
|
||||||
getParty: () => party,
|
getParty: () => party,
|
||||||
canEdit: () => canEdit(),
|
canEdit: () => canEdit(),
|
||||||
getEditKey: () => editKey,
|
getEditKey: () => editKey,
|
||||||
|
getSelectedSlot: () => selectedSlot,
|
||||||
|
getActiveTab: () => activeTab,
|
||||||
services: {
|
services: {
|
||||||
gridService: clientGridService // Uses TanStack Query mutations
|
gridService: clientGridService // Uses TanStack Query mutations
|
||||||
},
|
},
|
||||||
|
|
@ -933,6 +956,25 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Conflict Resolution Dialog -->
|
||||||
|
<ConflictDialog
|
||||||
|
bind:open={conflictDialogOpen}
|
||||||
|
conflict={conflictData}
|
||||||
|
partyId={party.id}
|
||||||
|
partyShortcode={party.shortcode}
|
||||||
|
onResolve={() => {
|
||||||
|
conflictData = null
|
||||||
|
// Find next empty slot after conflict resolution
|
||||||
|
const nextEmptySlot = findNextEmptySlot(party, activeTab)
|
||||||
|
if (nextEmptySlot !== SLOT_NOT_FOUND) {
|
||||||
|
selectedSlot = nextEmptySlot
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
conflictData = null
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use '$src/themes/typography' as *;
|
@use '$src/themes/typography' as *;
|
||||||
@use '$src/themes/colors' as *;
|
@use '$src/themes/colors' as *;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue