fix: removed user_ids from create list payload; improved share button on invites

This commit is contained in:
2026-09-01 15:29:31 +02:00
parent 3998679810
commit 6196556012
6 changed files with 46 additions and 8 deletions
+1 -1
View File
@@ -101,7 +101,7 @@ describe('lists API', () => {
} as unknown as Response) } as unknown as Response)
global.fetch = fetchMock global.fetch = fetchMock
const result = await createListApi({ name: 'Groceries', user_ids: [] }) const result = await createListApi({ name: 'Groceries' })
expect(fetchMock).toHaveBeenCalledWith( expect(fetchMock).toHaveBeenCalledWith(
`${API_BASE_URL}/lists`, `${API_BASE_URL}/lists`,
+14 -2
View File
@@ -138,7 +138,11 @@ async function shareInvite(invite: Invite) {
if (typeof navigator.share === 'function') { if (typeof navigator.share === 'function') {
try { try {
await navigator.share({ title: 'Join dttmr', text: 'Use this link to create your account', url }) await navigator.share({
title: 'Join dttmr',
text: 'Use this link to create your account',
url,
})
return return
} catch (err) { } catch (err) {
if (err instanceof Error && err.name === 'AbortError') { if (err instanceof Error && err.name === 'AbortError') {
@@ -248,7 +252,15 @@ function inviteDetail(invite: Invite): string {
<span class="invite-detail">{{ inviteDetail(invite) }}</span> <span class="invite-detail">{{ inviteDetail(invite) }}</span>
<div v-if="pendingDeleteId !== invite.id" class="invite-actions"> <div v-if="pendingDeleteId !== invite.id" class="invite-actions">
<button type="button" class="ticket-btn" @click="shareInvite(invite)"> <button
type="button"
class="ticket-btn"
:disabled="inviteStatus(invite) !== 'active'"
:title="
inviteStatus(invite) !== 'active' ? 'Only active invites can be shared' : ''
"
@click="shareInvite(invite)"
>
{{ sharedId === invite.id ? 'Copied!' : 'Share' }} {{ sharedId === invite.id ? 'Copied!' : 'Share' }}
</button> </button>
<button <button
@@ -262,6 +262,34 @@ describe('InvitesPanel', () => {
expect((deleteBtn.element as HTMLButtonElement).disabled).toBe(true) expect((deleteBtn.element as HTMLButtonElement).disabled).toBe(true)
}) })
it('disables share for used and expired invites, but leaves expired invites deletable', async () => {
const usedInvite: Invite = {
id: 'invite-1',
code: 'USEDCODE',
consumed_at: '2026-01-01T00:00:00.000Z',
}
const expiredInvite: Invite = {
id: 'invite-2',
code: 'EXPCODE',
expires_at: '2020-01-01T00:00:00.000Z',
}
vi.spyOn(invitesApi, 'getInvitesApi').mockResolvedValueOnce(
paginated([usedInvite, expiredInvite]),
)
const wrapper = mount(InvitesPanel)
await wrapper.find('.invites-toggle').trigger('click')
await flushPromises()
const shareButtons = wrapper.findAll('.ticket-btn:not(.ticket-btn-danger)')
expect((shareButtons[0]!.element as HTMLButtonElement).disabled).toBe(true)
expect((shareButtons[1]!.element as HTMLButtonElement).disabled).toBe(true)
const deleteButtons = wrapper.findAll('.ticket-btn-danger')
expect((deleteButtons[0]!.element as HTMLButtonElement).disabled).toBe(true)
expect((deleteButtons[1]!.element as HTMLButtonElement).disabled).toBe(false)
})
it('shows an error banner when loading invites fails', async () => { it('shows an error banner when loading invites fails', async () => {
vi.spyOn(invitesApi, 'getInvitesApi').mockRejectedValueOnce(new Error('Network error')) vi.spyOn(invitesApi, 'getInvitesApi').mockRejectedValueOnce(new Error('Network error'))
+1 -2
View File
@@ -162,14 +162,13 @@ describe('useListsStore', () => {
listsApiMocks.getListsApi.mockResolvedValueOnce([{ id: 'server-id-1', name: 'Groceries' }]) listsApiMocks.getListsApi.mockResolvedValueOnce([{ id: 'server-id-1', name: 'Groceries' }])
const store = useListsStore() const store = useListsStore()
const localList = await store.createList('Groceries', []) const localList = await store.createList('Groceries')
// wait for the fire-and-forget sync triggered by createList to settle // wait for the fire-and-forget sync triggered by createList to settle
await store.sync() await store.sync()
expect(listsApiMocks.createListApi).toHaveBeenCalledWith({ expect(listsApiMocks.createListApi).toHaveBeenCalledWith({
name: 'Groceries', name: 'Groceries',
user_ids: [],
}) })
expect(store.lists.find((list) => list.id === localList.id)).toBeUndefined() expect(store.lists.find((list) => list.id === localList.id)).toBeUndefined()
const synced = store.lists.find((list) => list.id === 'server-id-1') const synced = store.lists.find((list) => list.id === 'server-id-1')
+2 -2
View File
@@ -134,7 +134,7 @@ export const useListsStore = defineStore('lists', () => {
}, SYNC_DEBOUNCE_MS) }, SYNC_DEBOUNCE_MS)
} }
async function createList(name: string, userIds: string[] = []): Promise<LocalList> { async function createList(name: string): Promise<LocalList> {
const now = new Date().toISOString() const now = new Date().toISOString()
const localList: LocalList = { const localList: LocalList = {
id: generateId(), id: generateId(),
@@ -148,7 +148,7 @@ export const useListsStore = defineStore('lists', () => {
upsertList(localList) upsertList(localList)
await enqueue({ await enqueue({
type: 'createList', type: 'createList',
payload: { name, user_ids: userIds }, payload: { name },
localListId: localList.id, localListId: localList.id,
}) })
scheduleSync() scheduleSync()
-1
View File
@@ -18,7 +18,6 @@ export interface ListItem {
export interface CreateListPayload { export interface CreateListPayload {
name: string name: string
user_ids?: string[]
} }
export interface CreateListItemPayload { export interface CreateListItemPayload {