diff --git a/docs/FEATURES/user-profile-settings.md b/docs/FEATURES/user-profile-settings.md new file mode 100644 index 0000000..d98b6b0 --- /dev/null +++ b/docs/FEATURES/user-profile-settings.md @@ -0,0 +1,34 @@ +# Feature: User Profile Settings + +## Status + +Draft + +## Goal + +Allow authenticated users to manage the profile information shown inside the application shell and workspace activity. + +## User Stories + +- As an authenticated user, I want to update my name, alias, email, and portrait so that other workspace members see accurate profile information. + +## Frontend Areas + +- `/app/settings/user-information` +- `frontend/src/features/user-profile/` + +## Backend Modules + +- Identity + +## Domain Rules + +- Profile updates apply only to the authenticated user. +- Portrait uploads flow through the existing blob storage abstraction. +- Email changes use the identity module endpoint and should remain auditable through backend identity behavior. + +## Done When + +- [ ] User information settings show editable name, alias, and email fields. +- [ ] Portrait upload remains available from the settings page. +- [ ] Successful updates refresh the user profile state used by the app shell. diff --git a/docs/TASKS/user-profile-settings/001-edit-user-information.md b/docs/TASKS/user-profile-settings/001-edit-user-information.md new file mode 100644 index 0000000..9f22efe --- /dev/null +++ b/docs/TASKS/user-profile-settings/001-edit-user-information.md @@ -0,0 +1,23 @@ +# Task: Edit user information settings + +## Goal + +Allow users to edit their profile details from the user information settings page. + +## Feature Spec + +- `docs/FEATURES/user-profile-settings.md` + +## Scope + +- Replace read-only user information details with editable first name, last name, alias, and email fields. +- Keep portrait upload available on the page. +- Use the existing Identity endpoints for full name, alias, email, and portrait updates. +- Keep the profile store as the source of truth for app-shell user identity. + +## Validation + +```bash +cd frontend +npm run build +``` diff --git a/frontend/src/features/user-profile/stores/userProfileStore.js b/frontend/src/features/user-profile/stores/userProfileStore.js index e1b0a13..ff33688 100644 --- a/frontend/src/features/user-profile/stores/userProfileStore.js +++ b/frontend/src/features/user-profile/stores/userProfileStore.js @@ -1,4 +1,4 @@ -import {computed, watch} from 'vue' +import {computed, ref, watch} from 'vue' import {defineStore} from 'pinia' import {useAuthStore} from "@/features/auth/stores/authStore.js"; import {useClient} from "@/plugins/api.js"; @@ -9,6 +9,9 @@ export const useUserProfileStore = defineStore( () => { const authStore = useAuthStore() + const isUpdating = ref(false) + const isUploadingPortrait = ref(false) + const error = ref(null) const authWatcher = watch( () => authStore.isAuthenticated, @@ -64,12 +67,15 @@ export const useUserProfileStore = defineStore( const client = useClient() const userResponse = await client.get("/api/users/profile"); value.value = userResponse.data - } catch (error) { - console.error(error) + } catch (fetchError) { + console.error(fetchError) } } async function changeFullname(firstname, lastname) { + isUpdating.value = true + error.value = null + try { const client = useClient() await client.post( @@ -80,12 +86,19 @@ export const useUserProfileStore = defineStore( }) value.value.firstname = firstname; value.value.lastname = lastname; - } catch (error) { - console.error(error) + } catch (updateError) { + console.error(updateError) + error.value = 'Failed to update profile.' + throw updateError + } finally { + isUpdating.value = false } } async function changeAlias(alias) { + isUpdating.value = true + error.value = null + try { const client = useClient() await client.post( @@ -94,8 +107,12 @@ export const useUserProfileStore = defineStore( alias: alias }) value.value.alias = alias; - } catch (error) { - console.error(error) + } catch (updateError) { + console.error(updateError) + error.value = 'Failed to update profile.' + throw updateError + } finally { + isUpdating.value = false } } @@ -128,6 +145,9 @@ export const useUserProfileStore = defineStore( } async function changeEmail(email) { + isUpdating.value = true + error.value = null + try { const client = useClient() await client.post( @@ -136,8 +156,12 @@ export const useUserProfileStore = defineStore( email: email }) value.value.email = email; - } catch (error) { - console.error(error) + } catch (updateError) { + console.error(updateError) + error.value = 'Failed to update profile.' + throw updateError + } finally { + isUpdating.value = false } } @@ -156,6 +180,9 @@ export const useUserProfileStore = defineStore( } async function changePortrait(selectedFile) { + isUploadingPortrait.value = true + error.value = null + try { const client = useClient() const formData = new FormData(); @@ -166,8 +193,12 @@ export const useUserProfileStore = defineStore( formData) value.value.portraitUrl = `${response.data.blobUrl}?${Date.now()}` // the Date.now() is for cache-busting - } catch (error) { - console.error(error) + } catch (uploadError) { + console.error(uploadError) + error.value = 'Failed to update portrait.' + throw uploadError + } finally { + isUploadingPortrait.value = false } } @@ -176,6 +207,9 @@ export const useUserProfileStore = defineStore( alias, fullname, portraitUrl, + isUpdating, + isUploadingPortrait, + error, roles, persona, authorizedWorkspaceIds, diff --git a/frontend/src/features/user-profile/views/UserSettingsView.vue b/frontend/src/features/user-profile/views/UserSettingsView.vue index 4cf341c..33e923d 100644 --- a/frontend/src/features/user-profile/views/UserSettingsView.vue +++ b/frontend/src/features/user-profile/views/UserSettingsView.vue @@ -1,5 +1,5 @@