|
| 1 | +# Login "Last Used" Badge — Design |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +The login page (`web/app/pages/login.vue` → `web/app/components/FirebaseAuth.vue`) |
| 6 | +offers three authentication methods: Continue with Google, Continue with GitHub, |
| 7 | +and Continue with email. Returning users don't get any hint about which method |
| 8 | +they used last, so they may pick the wrong provider and end up creating a second |
| 9 | +account or failing to sign in. |
| 10 | + |
| 11 | +## Goal |
| 12 | + |
| 13 | +Show a small "Last Used" badge on the button corresponding to the login method |
| 14 | +the user most recently used successfully on this device, so they can quickly pick |
| 15 | +the right one next time. |
| 16 | + |
| 17 | +## Scope |
| 18 | + |
| 19 | +Single component change: `web/app/components/FirebaseAuth.vue`. No API, store, or |
| 20 | +backend changes. |
| 21 | + |
| 22 | +## Approach |
| 23 | + |
| 24 | +### Storage |
| 25 | + |
| 26 | +- Persist the last successful method in `localStorage` under the key |
| 27 | + `httpsms_last_login_method`. |
| 28 | +- Value is one of `'google' | 'github' | 'email'`. |
| 29 | +- The value is written only **after a successful login** (inside `onSuccess`), |
| 30 | + never on click/attempt. |
| 31 | + |
| 32 | +### Recording the method |
| 33 | + |
| 34 | +- `onSuccess(user)` is currently shared by all three flows. Extend it to |
| 35 | + `onSuccess(user, method)` where `method` is `'google' | 'github' | 'email'`. |
| 36 | + - `signInWithGoogle` → `onSuccess(result.user, 'google')` |
| 37 | + - `signInWithGithub` → `onSuccess(result.user, 'github')` |
| 38 | + - `submitEmail` → `onSuccess(result.user, 'email')` |
| 39 | +- Inside `onSuccess`, write the method to `localStorage` before redirecting. |
| 40 | + |
| 41 | +### Reading the method |
| 42 | + |
| 43 | +- A reactive `lastUsedMethod = ref<string | null>(null)`. |
| 44 | +- Populated in `onMounted` from `localStorage` (client-only, SSR-safe; the |
| 45 | + component is already rendered inside `<ClientOnly>` on the login page). |
| 46 | + |
| 47 | +### Display |
| 48 | + |
| 49 | +- Each of the three method buttons gets `class="position-relative"`. |
| 50 | +- A floating `v-chip` is rendered in the top-right corner of the matching button: |
| 51 | + - Vuetify `v-chip` with `label`, `size="x-small"`, `color="primary"`. |
| 52 | + - `class="position-absolute"` pinned to the top-right corner (slightly |
| 53 | + overlapping), text `Last Used`. |
| 54 | + - Shown via `v-if="lastUsedMethod === 'google'"` (and `'github'`, `'email'` |
| 55 | + respectively). |
| 56 | + |
| 57 | +### Edge cases |
| 58 | + |
| 59 | +- The email button is hidden once the inline email form opens |
| 60 | + (`v-if="!showEmailForm"`), so its badge hides with it automatically. No extra |
| 61 | + handling needed. |
| 62 | +- An unknown/empty stored value shows no badge anywhere. |
| 63 | +- `localStorage` access is guarded (wrapped in try/catch or `onMounted` only) so |
| 64 | + it never runs during SSR. |
| 65 | + |
| 66 | +## Testing |
| 67 | + |
| 68 | +- Manual verification: sign in with each method, confirm the badge appears on the |
| 69 | + correct button on the next visit to the login page. |
| 70 | +- Confirm no SSR/hydration errors (badge logic runs client-side only). |
| 71 | + |
| 72 | +## Out of scope |
| 73 | + |
| 74 | +- Syncing the preference across devices. |
| 75 | +- Remembering the specific email address used. |
0 commit comments