diff --git a/frontend/src/stores/flare.test.ts b/frontend/src/stores/flare.test.ts index b1c8934..7fe4789 100644 --- a/frontend/src/stores/flare.test.ts +++ b/frontend/src/stores/flare.test.ts @@ -158,6 +158,35 @@ describe('flare store', () => { expect(flare.error).toBe('invalid_discovery') }) + it('clears Flare state after the server deletes the owned profile', async () => { + mockNuiCall.mockResolvedValueOnce({ success: true }) + const flare = useFlareStore() + flare.applyBootstrap(bootstrap) + flare.activeMatchId = 'match-to-delete' + + expect(await flare.deleteProfile()).toBe(true) + expect(mockNuiCall).toHaveBeenCalledWith('flare:delete-profile') + expect(flare.profile).toBeNull() + expect(flare.activeMatchId).toBe('') + expect(flare.likes).toEqual([]) + expect(flare.matches).toEqual([]) + expect(flare.suggestions).toEqual([]) + expect(flare.error).toBe('') + }) + + it('keeps the Flare profile when account deletion is rejected', async () => { + mockNuiCall.mockResolvedValueOnce({ + error: 'request_failed', + success: false, + }) + const flare = useFlareStore() + flare.applyBootstrap(bootstrap) + + expect(await flare.deleteProfile()).toBe(false) + expect(flare.profile).toEqual(bootstrap.profile) + expect(flare.error).toBe('request_failed') + }) + it('sends media messages and keeps numeric database timestamps intact', async () => { const match: FlareMatch = { id: 'match-media', diff --git a/frontend/src/stores/flare.ts b/frontend/src/stores/flare.ts index 0af6a59..fb215d2 100644 --- a/frontend/src/stores/flare.ts +++ b/frontend/src/stores/flare.ts @@ -47,6 +47,12 @@ export const useFlareStore = defineStore('flare', { } return response.success }, + async deleteProfile(): Promise { + const response = await nuiCall('flare:delete-profile') + this.error = response.success ? '' : (response.error ?? 'default') + if (response.success) this.reset() + return response.success + }, async saveProfile(draft: FlareProfileDraft): Promise { const response = await nuiCall( 'flare:save-profile', @@ -132,6 +138,17 @@ export const useFlareStore = defineStore('flare', { } return response.success }, + reset(error = ''): void { + this.activeMatchId = '' + this.error = error + this.loading = false + this.likes = [] + this.matches = [] + this.messages = [] + this.profile = null + this.sending = false + this.suggestions = [] + }, applyBootstrap(data: FlareBootstrap): void { this.profile = data.profile this.likes = data.likes diff --git a/frontend/src/stores/phone.ts b/frontend/src/stores/phone.ts index eaf7c3f..40dfb89 100644 --- a/frontend/src/stores/phone.ts +++ b/frontend/src/stores/phone.ts @@ -862,6 +862,9 @@ const defaultLocales: LocaleTree = { choosePhotos: 'Choose from Photos', primaryPhoto: 'Main', removePhoto: 'Remove photo {number}', + profilePhotoFallback: 'Flare profile avatar', + profilePhotoNumber: 'Profile photo {number} of {count}', + viewProfilePhoto: 'Show profile photo {number} of {count}', yourName: 'Your name', namePlaceholder: 'What should people call you?', age: 'Age', @@ -917,6 +920,19 @@ const defaultLocales: LocaleTree = { editProfile: 'Edit profile', editRelationshipGoal: 'Edit relationship goal', profileGoalBody: 'Shown on your discovery card.', + accountActions: 'Account', + signOut: 'Sign Out', + signOutTitle: 'Sign out of Sky Cloud?', + signOutBody: + 'This signs the whole phone out of Sky Cloud. Your Flare profile, matches and chats stay stored.', + signingOut: 'Signing Out...', + signOutHint: + 'Signing out affects every app that uses Sky Cloud on this phone.', + deleteAccount: 'Delete Flare Account', + deleteAccountTitle: 'Delete your Flare account?', + deleteAccountBody: + 'Your Flare profile, swipes, matches and conversations will be permanently deleted. Your Sky Cloud account and Photos library stay available.', + deletingAccount: 'Deleting...', relationshipGoal: 'Relationship goal', interests: 'Interests', interestsPlaceholder: 'Music, travel, coffee', @@ -978,6 +994,7 @@ const defaultLocales: LocaleTree = { }, errors: { invalid_profile: 'Check your name, age and profile text.', + profile_not_found: 'This Flare account is no longer available.', invalid_profile_photos: 'Choose or take up to six photos saved in your own Photos library.', request_failed: 'Flare could not save those changes. Try again.', diff --git a/frontend/src/views/apps/FlareApp.contract.test.ts b/frontend/src/views/apps/FlareApp.contract.test.ts index f7c9c67..d371e56 100644 --- a/frontend/src/views/apps/FlareApp.contract.test.ts +++ b/frontend/src/views/apps/FlareApp.contract.test.ts @@ -3,6 +3,18 @@ import { readFileSync } from 'node:fs' import { describe, expect, it } from 'vitest' const source = readFileSync(new URL('./FlareApp.vue', import.meta.url), 'utf8') +const clientSource = readFileSync( + new URL('../../../../sky_phone/source/client/main.lua', import.meta.url), + 'utf8', +) +const serverSource = readFileSync( + new URL('../../../../sky_phone/source/server/flare.lua', import.meta.url), + 'utf8', +) +const localeSource = readFileSync( + new URL('../../../../sky_phone/config/locales/en.lua', import.meta.url), + 'utf8', +) describe('FlareApp profile editing contract', () => { it('opens the central photo source picker while creating an account', () => { @@ -66,4 +78,62 @@ describe('FlareApp profile editing contract', () => { /openProfileGoalEditor\(\)[\s\S]*?profileEditing\.value = true[\s\S]*?openChoice\('lookingFor', null\)/, ) }) + + it('uses the central surfaced SkyNavbar back action for profile screens', () => { + const mainNavbarStart = + source.match(/