diff --git a/docs/analytics.md b/docs/analytics.md index c2b71946e..66bf2099e 100644 --- a/docs/analytics.md +++ b/docs/analytics.md @@ -55,6 +55,7 @@ the tables here list the same events; a test | `account:profile_update` | | the profile form was submitted | | `account:fio_update` | `is_active` | the profile form was submitted | | `account:fio_link` | | FIO became active for a profile that had none | +| `account:fio_link_failed` | `reason` | saving FIO credentials was refused: `fio_required`, `fio_invalid_key` or `fio_username_mismatch` | | `account:api_key_create` | | | ### Plan diff --git a/docs/features/fio.md b/docs/features/fio.md index 46411b0ef..afe3e6f61 100644 --- a/docs/features/fio.md +++ b/docs/features/fio.md @@ -25,8 +25,11 @@ The backend proxies the data, and the user links FIO in their profile. - FIO storage and sites live in `planningStore.fio_*` and are persisted. They are loaded by the `GetFIOStorage` query (`callDataFIOStorage`), - which `NavigationBar` triggers on app load and `PlanConstructionCart` - triggers when needed. + which `NavigationBar` triggers whenever `userStore.fioStatus` is `ok` or + `error` (the states that can have stored data, so also when polling turns + `syncing` into `ok`) and `PlanConstructionCart` triggers when needed. In + every other state the nav drops the query cache, but the persisted + `planningStore.fio_*` stays until logout. - The burn view calculates every plan with the planning engine (`calculatePlan`, game data loaded once) first, then passes the results into `useFIOBurn`. diff --git a/docs/features/profile.md b/docs/features/profile.md index b207170ba..e3bcb1cc0 100644 --- a/docs/features/profile.md +++ b/docs/features/profile.md @@ -9,7 +9,7 @@ | Component | Does | | --- | --- | -| `ChangeProfile.vue` | Edit profile fields (e.g. FIO username/key): `PatchUserProfile`, then `userStore.performGetProfile()` | +| `ChangeProfile.vue` | Edit profile fields (e.g. FIO username/key): `PatchUserProfile`, then `userStore.performGetProfile()`. The backend checks changed FIO credentials and answers 400 with a code per field (`fio_required`, `fio_invalid_key`, `fio_username_mismatch`), shown under the field; the FIO status line reads `userStore.fioStatus` (the same state drives the nav FIO tag) | | `ChangePassword.vue` | `PatchUserChangePassword` | | `FIOInformation.vue` | Explains how to link FIO (static, i18n `profile.fio_information.*`) | | `UserPreferences.vue` | Edits global preferences through `usePreferences`: default empire and CX (`CXPreferenceSelector`), burn thresholds, XIT defaults, and so on | diff --git a/src/features/api/schemas/user.schemas.ts b/src/features/api/schemas/user.schemas.ts index 248b1cbf2..c201bd1cf 100644 --- a/src/features/api/schemas/user.schemas.ts +++ b/src/features/api/schemas/user.schemas.ts @@ -20,6 +20,16 @@ export const RefreshTokenResponseSchema = TokenResponseSchema.pick({ }); export type RefreshTokenResponse = z.infer; +export const FIOStatusSchema = z.enum([ + "none", + "syncing", + "ok", + "no_data", + "invalid_credentials", + "error", +]); +export type FIOStatus = z.infer; + export const UserProfileSchema = z.object({ id: z.number(), username: z.string(), @@ -36,6 +46,8 @@ export const UserProfileSchema = z.object({ .string() .transform((val) => (val === "" ? null : val)) .nullable(), + fio_status: FIOStatusSchema, + fio_last_refreshed_at: z.string().nullable(), }); export type UserProfile = z.infer; diff --git a/src/features/profile/components/ChangeProfile.vue b/src/features/profile/components/ChangeProfile.vue index 88f52ae55..a57c64819 100644 --- a/src/features/profile/components/ChangeProfile.vue +++ b/src/features/profile/components/ChangeProfile.vue @@ -1,5 +1,5 @@