Appearance
Profile Page ​
File Information ​
- Path:
app/pages/[brand_name]/profile.vue - Route:
/{brand_name}/profile - Purpose: User profile management with profile editing, image upload, notification settings, and password change
- Layout:
collage-layout - Middleware:
check-url
Overview ​
The Profile page allows users to view and manage their profile information. It provides comprehensive profile management features including profile image upload, name editing, notification preferences, and password change functionality.
Key Features ​
Profile Display
- User profile image (with avatar fallback)
- User name and email display
- Profile image upload
- Image preview
Profile Editing
- Full name editing
- Real-time validation
- Form state management
- Cancel functionality
Notification Settings
- Digital Asset Management email notification toggle
- Tooltip with explanation
- Preference persistence
Password Change
- Old password input
- New password input
- Confirm password input
- Password validation
- Form validation
Form Validation
- Name validation (required, format)
- Password validation (required, minimum length, match)
- Real-time error display
- Submit button state management
Components Used ​
v-text-field- Vuetify text input componentsv-textarea- Vuetify textarea componentv-file-input- Vuetify file input componentv-btn- Vuetify button componentv-switch- Vuetify switch componentv-progress-circular- Loading spinnerv-skeleton-loader- Skeleton loadersv-tooltip- Tooltip componentAsyncIcon- Async icon component
Composables Used ​
useProfile()- Profile management (app/composables/core/pages/useProfile.ts)useProfileApi()- Profile API (app/composables/api/useProfileApi.ts)useAuthStore()- Authentication storeuseSnackbar()- Snackbar notificationsuseHelpers()- Helper functions
Data Properties ​
Reactive Data (from useProfile composable) ​
typescript
{
name: string | null, // User full name
email: string | null, // User email (read-only)
logo_preview: string | null, // Profile image URL
ifNotImageChar: string, // Avatar character if no image
upload_profile: string | null, // Uploaded image preview (data URL)
upload_profile_file: File | null, // Uploaded image file
contentLoading: boolean, // Initial content loading
loading: boolean, // Profile save loading
password_loading: boolean, // Password change loading
notification: boolean, // Notification preference
profileImageReload: number, // Profile image reload key
changePasswordForm: { // Password change form
old_pass: string,
password: string,
confirm_password: string
},
errors: { // Form errors
name: string,
changePasswordForm: {
old_pass: string,
password: string,
confirm_password: string
}
},
initData: { // Initial form data
name: string,
notification: boolean
},
user_id: number | null, // User ID
workspace_id: string // Workspace ID
}Computed Properties ​
submitProfileDisabled ​
Determines if profile save button should be disabled.
typescript
computed(() => {
if (loading.value || errors.name) return true
const formName = (name.value ?? "").trim()
const initName = (initData.name ?? "").trim()
return (
formName === initName &&
initData.notification === notification.value &&
!upload_profile.value
)
})Returns: boolean - True if form is unchanged or invalid
submitPasswordDisabled ​
Determines if password save button should be disabled.
typescript
computed(() => {
const f = changePasswordForm
if (
!f.old_pass.trim() ||
!f.password.trim() ||
!f.confirm_password.trim()
) {
return true
}
return Boolean(
errors.changePasswordForm.old_pass ||
errors.changePasswordForm.password ||
errors.changePasswordForm.confirm_password ||
password_loading.value
)
})Returns: boolean - True if form is invalid or loading
Methods ​
fetchUserProfile() ​
Fetches user profile data from auth store.
typescript
async function fetchUserProfile() {
try {
user_id.value = authUser.id
name.value = authUser.name
ifNotImageChar.value = authUser.name?.charAt(0)?.toUpperCase() ?? ""
email.value = authUser.email
logo_preview.value = authUser.profile_image
notification.value = !!authUser.enable_notification
Object.assign(initData, {
name: authUser.name,
notification: notification.value,
})
} catch (e) {
snackbar.showError(getErrorMessage(e))
}
}Flow:
- Gets user data from auth store
- Sets form fields
- Sets avatar character
- Sets initial data for comparison
- Handles errors
handleSubmit() ​
Handles profile form submission.
typescript
async function handleSubmit() {
validateName()
if (submitProfileDisabled.value) return
loading.value = true
try {
const formData = new FormData()
formData.append("id", String(user_id.value))
formData.append("instance_id", String(authUser.default_instance_id))
if (upload_profile_file.value) {
formData.append("profile_image", upload_profile_file.value)
}
formData.append("name", name.value)
formData.append("phone", "")
formData.append("enable_notification", notification.value ? "1" : "0")
const response = await updateUserProfile(formData)
snackbar.showSuccess(response?.message)
if (name.value !== initData.name) {
(nuxtApp as any).$mittBus.emit("profile-name-updated", name.value)
}
Object.assign(initData, {
name: name.value,
notification: notification.value,
})
if (upload_profile.value) {
const newLogo =
response?.data?.user?.profile_image || upload_profile.value
// Add cache-busting query param
const cacheBustedLogo =
newLogo && typeof newLogo === "string"
? `${newLogo}${newLogo.includes("?") ? "&" : "?"}t=${Date.now()}`
: newLogo
logo_preview.value = cacheBustedLogo as string | null
upload_profile.value = null
upload_profile_file.value = null
profileImageReload.value++
(nuxtApp as any).$mittBus.emit(
"profile-image-updated",
logo_preview.value
)
}
} catch (e) {
console.log(e)
snackbar.showError(getErrorMessage(e))
} finally {
loading.value = false
}
}Flow:
- Validates name
- Checks if form is valid
- Builds FormData with profile data
- Appends image file if uploaded
- Calls update API
- Updates initial data
- Updates profile image with cache-busting
- Emits events for header updates
- Handles errors
handleCancel() ​
Cancels profile changes and resets form.
typescript
function handleCancel() {
upload_profile.value = null
upload_profile_file.value = null
name.value = initData.name
notification.value = initData.notification
}changeProfile(payload, fileInput) ​
Handles profile image upload.
typescript
function changeProfile(payload: any, fileInput: Ref) {
const file = payload?.target?.files[0] || payload
const resetFile = () => {
// Clear file input
const inputEl =
fileInput.value?.$el?.querySelector('input[type="file"]') ??
fileInput.value?.$el?.querySelector("input")
if (inputEl) {
inputEl.value = ""
}
upload_profile_file.value = null
upload_profile.value = null
profileImageReload.value++
}
if (!file) {
resetFile()
return
}
try {
// Validate file type
if (!file.type.includes("image")) {
snackbar.showError("Please choose an image file")
resetFile()
return
}
// Validate file size (4MB max)
const maxSize = 4 * 1024 * 1024
if (file.size > maxSize) {
snackbar.showError("File size should not be more than 4MB")
resetFile()
return
}
upload_profile_file.value = file as File
const reader = new FileReader()
reader.onload = (e) => {
upload_profile.value = e.target?.result as string
}
reader.readAsDataURL(file)
} catch (error) {
console.log("changeProfile error", error)
upload_profile.value = null
upload_profile_file.value = null
resetFile()
}
profileImageReload.value++
}Validation:
- File must be an image
- File size must be ≤ 4MB
- Creates data URL preview
validateName() ​
Validates full name field.
typescript
function validateName() {
errors.name = validateFullName(name.value)
}Uses: validateFullName helper from useHelpers()
validatePassword(value, key) ​
Validates password fields.
typescript
function validatePassword(
value: string,
key: keyof typeof changePasswordForm
) {
if (!key) return
if (!value.trim()) {
errors.changePasswordForm[key] = "This field is required"
} else if (value.trim().length < 6 && key !== "confirm_password") {
errors.changePasswordForm[key] =
"Password should have at least 6 characters!"
} else if (
(key === "confirm_password" ||
changePasswordForm.confirm_password.trim().length > 0) &&
changePasswordForm.password.trim() !==
changePasswordForm.confirm_password.trim()
) {
errors.changePasswordForm.confirm_password =
"Both passwords must be exactly same!"
if (value.trim().length >= 6 && key === "password") {
errors.changePasswordForm.password = ""
}
} else {
errors.changePasswordForm[key] = ""
}
}Validation Rules:
- All fields required
- Password minimum 6 characters
- Confirm password must match password
- Real-time validation
changeUserPassword() ​
Handles password change form submission.
typescript
async function changeUserPassword() {
validatePassword(changePasswordForm.old_pass, "old_pass")
validatePassword(changePasswordForm.password, "password")
validatePassword(changePasswordForm.confirm_password, "confirm_password")
if (submitPasswordDisabled.value) return
password_loading.value = true
try {
const response = await changeUserPasswordApi({
old_password: changePasswordForm.old_pass,
password: changePasswordForm.password,
})
snackbar.showSuccess(response?.message)
resetErrors()
changePasswordForm.old_pass = ""
changePasswordForm.password = ""
changePasswordForm.confirm_password = ""
} catch (e) {
snackbar.showError(getErrorMessage(e))
} finally {
password_loading.value = false
}
}Flow:
- Validates all password fields
- Checks if form is valid
- Calls password change API
- Shows success message
- Resets form and errors
- Handles errors
resetErrors() ​
Resets all form errors.
typescript
function resetErrors() {
errors.name = ""
errors.changePasswordForm = {
old_pass: "",
password: "",
confirm_password: "",
}
}User Interactions ​
Profile Image
- Click to upload new image
- Image preview before upload
- Validation (image type, 4MB max)
Profile Editing
- Edit full name
- Real-time validation
- Save changes
- Cancel changes (resets form)
Notification Settings
- Toggle notification preference
- Tooltip with explanation
- Preference saved with profile
Password Change
- Enter old password
- Enter new password
- Confirm new password
- Real-time validation
- Submit password change
Real-time Features ​
- Profile image updates in header via event bus
- Name updates in header via event bus
- Changes reflected immediately after save
Styling ​
- Uses custom CSS from
assets/scss/custom.scss - Form layout styles
- Input field styles with validation states
- Button styles with loading states
- Image upload area styling
- Profile image preview styling
Watchers ​
None - uses event handlers (@input, @blur) for validation.
Lifecycle Hooks ​
onMounted ​
Initializes profile data.
typescript
onMounted(async () => {
await fetchUserProfile()
contentLoading.value = false
})API Endpoints ​
Update User Profile ​
- Endpoint:
PUT /user/profile(viaupdateUserProfile()) - Request: FormDatatypescript
{ id: number, instance_id: number, profile_image?: File, name: string, phone: string, enable_notification: '0' | '1' } - Response:typescript
{ message: string, data: { user: { profile_image: string, name: string, enable_notification: boolean } } }
Change User Password ​
- Endpoint:
POST /user/change-password(viachangeUserPasswordApi()) - Request Body:typescript
{ old_password: string, password: string } - Response:typescript
{ message: string }
User Flow ​
- User navigates to profile page
- Page fetches user profile data from auth store
- User can:
- Upload profile image (validates type and size)
- Edit full name (with validation)
- Toggle notification preference
- Save profile changes
- Cancel changes (resets form)
- Change password (with validation)
- Form validation occurs in real-time
- Success/error messages displayed
- Profile image updates in header via event bus
- Name updates in header via event bus
Performance Optimizations ​
- Image Preview: Uses FileReader for client-side preview
- Cache Busting: Adds timestamp to image URL to force refresh
- Form State: Tracks initial state to detect changes
- Validation: Real-time validation prevents unnecessary API calls
Notes for Development ​
- Profile image upload limited to 4MB
- Only image files accepted for profile image
- Name validation uses
validateFullNamehelper - Password minimum length is 6 characters
- Profile changes emit events for header updates
- Image reload key forces component re-render
- Form state compared to initial data for submit button
- Cancel resets form to initial state
Related Documentation ​
- useProfile Composable - Profile composable details
- Profile API - Profile API endpoints
- Auth Store - Authentication store
- useHelpers Composable - Helper functions