HouseholdSettings.vue — fixed template, added proper imports, typed members, and rendered current members list.
This commit is contained in:
parent
b75ece2fc8
commit
6d95764516
4 changed files with 79 additions and 2 deletions
|
|
@ -165,6 +165,7 @@ Progress Log (Nov 1, 2025)
|
||||||
- Added `useAuth.createAccount` with state update and tests for it; implemented `CreateAccount.vue` with form and navigation.
|
- Added `useAuth.createAccount` with state update and tests for it; implemented `CreateAccount.vue` with form and navigation.
|
||||||
- Implemented Invitation Accept flow: added `src/api/invitations.ts` with `acceptInvitation` (temporary raw fetch), `InvitationAccept.vue` reads token and redirects to household; added `tests/invitations.api.test.ts`.
|
- Implemented Invitation Accept flow: added `src/api/invitations.ts` with `acceptInvitation` (temporary raw fetch), `InvitationAccept.vue` reads token and redirects to household; added `tests/invitations.api.test.ts`.
|
||||||
- Next: Implement Household Settings (invite members form), then remove legacy Person UI.
|
- Next: Implement Household Settings (invite members form), then remove legacy Person UI.
|
||||||
|
- Added a Settings link to `HouseholdSwitcher.vue` to surface the `household-settings` route for easier discovery.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
18
src/api/households.ts
Normal file
18
src/api/households.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { api, fetchApi } from '@/api/client'
|
||||||
|
import type { components, paths } from '@/api/types'
|
||||||
|
|
||||||
|
export type Member = components['schemas']['User']
|
||||||
|
|
||||||
|
// Prefer typed endpoint if exists, fallback to raw fetch for now
|
||||||
|
export async function listMembers(): Promise<Member[]> {
|
||||||
|
// Try typed path if openapi exposes it
|
||||||
|
const hasTyped: boolean = Boolean((api as unknown as { GET?: unknown }).GET)
|
||||||
|
if (hasTyped) {
|
||||||
|
// Our OpenAPI file doesn't specify this route yet, so default to raw fetch
|
||||||
|
}
|
||||||
|
const resp = await fetchApi('/api/v1/households/members', { method: 'GET' })
|
||||||
|
if (!resp.ok) throw new Error(`${resp.status} ${resp.statusText || 'HTTP error'}`)
|
||||||
|
const data = await resp.json().catch(() => null)
|
||||||
|
const arr = Array.isArray(data) ? data : []
|
||||||
|
return arr.filter((m): m is Member => typeof m === 'object' && m !== null && typeof (m as { id: unknown }).id === 'number')
|
||||||
|
}
|
||||||
|
|
@ -35,7 +35,18 @@
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Current members</h2>
|
<h2>Current members</h2>
|
||||||
<p class="muted">
|
<ul v-if="members.length > 0">
|
||||||
|
<li
|
||||||
|
v-for="m in members"
|
||||||
|
:key="m.id"
|
||||||
|
>
|
||||||
|
{{ m.displayName }} <small>({{ m.email }})</small>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p
|
||||||
|
v-else
|
||||||
|
class="muted"
|
||||||
|
>
|
||||||
Listing members will be added once the backend endpoint is available.
|
Listing members will be added once the backend endpoint is available.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -43,13 +54,23 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { sendInvitation } from '@/api/invitations'
|
import { sendInvitation } from '@/api/invitations'
|
||||||
|
import { listMembers, type Member } from '@/api/households'
|
||||||
|
|
||||||
const email = ref('')
|
const email = ref('')
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const message = ref('')
|
const message = ref('')
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
|
const members = ref<Member[]>([])
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
members.value = await listMembers()
|
||||||
|
} catch (_e) {
|
||||||
|
// ignore; backend may not support this yet
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
async function onInvite() {
|
async function onInvite() {
|
||||||
message.value = ''
|
message.value = ''
|
||||||
|
|
|
||||||
37
tests/households.members.api.test.ts
Normal file
37
tests/households.members.api.test.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { server, http, HttpResponse } from './test-setup'
|
||||||
|
import { loginWithPassword } from '@/api/auth'
|
||||||
|
import { setHouseholdSlugProvider } from '@/api/client'
|
||||||
|
import { listMembers } from '@/api/households'
|
||||||
|
|
||||||
|
describe('households api (list members)', () => {
|
||||||
|
it('GETs members with Authorization and X-Household-Slug headers', async () => {
|
||||||
|
// Simulate login token
|
||||||
|
server.use(
|
||||||
|
http.post('*/api/v1/auth/login', () =>
|
||||||
|
HttpResponse.json({ accessToken: 'tokLM', tokenType: 'bearer', user: { id: 3, email: 'u@e', displayName: 'User' } })
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await loginWithPassword('u@e', 'pw')
|
||||||
|
|
||||||
|
// Provide a household slug for header injection
|
||||||
|
setHouseholdSlugProvider(() => 'the-smiths')
|
||||||
|
|
||||||
|
server.use(
|
||||||
|
http.get('*/api/v1/households/members', ({ request }) => {
|
||||||
|
const auth = request.headers.get('authorization')
|
||||||
|
expect(auth?.toLowerCase()).toBe('bearer toklm')
|
||||||
|
const slug = request.headers.get('x-household-slug')
|
||||||
|
expect(slug).toBe('the-smiths')
|
||||||
|
return HttpResponse.json([
|
||||||
|
{ id: 10, email: 'a@example.com', displayName: 'Alice' },
|
||||||
|
{ id: 11, email: 'b@example.com', displayName: 'Bob' },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const members = await listMembers()
|
||||||
|
expect(members.length).toBe(2)
|
||||||
|
expect(members[0]?.displayName).toBe('Alice')
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue