mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 17:24:10 +00:00
Existing accounts inherited the company language at creation time and there was no way to change UI language per user. Add a 'Default (Company Language)' entry to the language selector in UserGeneralView, persist the choice through userStore.updateUserSettings and reload the i18n bundle via window.loadLanguage. The 'default' sentinel keeps the user opted in to the company-wide setting. Bootstrap (global.store) now syncs userForm from current_user data and resolves the active UI language as user > company > 'en'. RegisterController, InvitationRegistrationController and MemberService seed new users with language=default instead of copying the current company setting, so promoting/inviting members no longer leaks the inviter's frozen language.
165 lines
4.6 KiB
TypeScript
165 lines
4.6 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import { userService } from '@v2/api/services/user.service'
|
|
import type { UpdateProfilePayload, UserSettingsPayload } from '@v2/api/services/user.service'
|
|
import { useNotificationStore } from './notification.store'
|
|
import { handleApiError } from '../utils/error-handling'
|
|
import type { User } from '@v2/types/domain/user'
|
|
import type { Ability } from '@v2/types/domain/role'
|
|
import type { ApiResponse } from '@v2/types/api'
|
|
|
|
export interface UserForm {
|
|
name: string
|
|
email: string
|
|
password: string
|
|
confirm_password: string
|
|
language: string
|
|
}
|
|
|
|
export const useUserStore = defineStore('user', () => {
|
|
// State
|
|
const currentUser = ref<User | null>(null)
|
|
const currentAbilities = ref<Ability[]>([])
|
|
const currentUserSettings = ref<Record<string, string>>({})
|
|
|
|
const userForm = ref<UserForm>({
|
|
name: '',
|
|
email: '',
|
|
password: '',
|
|
confirm_password: '',
|
|
language: '',
|
|
})
|
|
|
|
// Getters
|
|
const currentAbilitiesCount = computed<number>(() => currentAbilities.value.length)
|
|
|
|
const isOwner = computed<boolean>(() => currentUser.value?.is_owner ?? false)
|
|
|
|
// Actions
|
|
async function fetchCurrentUser(): Promise<ApiResponse<User>> {
|
|
try {
|
|
const response = await userService.getProfile()
|
|
currentUser.value = response.data
|
|
userForm.value = {
|
|
name: response.data.name,
|
|
email: response.data.email,
|
|
password: '',
|
|
confirm_password: '',
|
|
language: currentUserSettings.value.language || '',
|
|
}
|
|
return response
|
|
} catch (err: unknown) {
|
|
handleApiError(err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
async function updateCurrentUser(data: UpdateProfilePayload): Promise<ApiResponse<User>> {
|
|
try {
|
|
const response = await userService.updateProfile(data)
|
|
currentUser.value = response.data
|
|
userForm.value = {
|
|
name: response.data.name,
|
|
email: response.data.email,
|
|
password: '',
|
|
confirm_password: '',
|
|
language: currentUserSettings.value.language || '',
|
|
}
|
|
|
|
const notificationStore = useNotificationStore()
|
|
notificationStore.showNotification({
|
|
type: 'success',
|
|
message: 'settings.account_settings.updated_message',
|
|
})
|
|
|
|
return response
|
|
} catch (err: unknown) {
|
|
handleApiError(err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
async function uploadAvatar(data: FormData): Promise<ApiResponse<User>> {
|
|
try {
|
|
const response = await userService.uploadAvatar(data)
|
|
if (currentUser.value) {
|
|
currentUser.value.avatar = response.data.avatar
|
|
}
|
|
return response
|
|
} catch (err: unknown) {
|
|
handleApiError(err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
async function fetchUserSettings(settings?: string[]): Promise<Record<string, string | null>> {
|
|
try {
|
|
const response = await userService.getSettings(settings)
|
|
return response
|
|
} catch (err: unknown) {
|
|
handleApiError(err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
async function updateUserSettings(data: UserSettingsPayload): Promise<void> {
|
|
try {
|
|
await userService.updateSettings(data)
|
|
|
|
const settings = data.settings as Record<string, string | number | boolean | null>
|
|
|
|
if (settings.language && typeof settings.language === 'string') {
|
|
currentUserSettings.value.language = settings.language
|
|
}
|
|
|
|
if (settings.default_estimate_template && typeof settings.default_estimate_template === 'string') {
|
|
currentUserSettings.value.default_estimate_template = settings.default_estimate_template
|
|
}
|
|
|
|
if (settings.default_invoice_template && typeof settings.default_invoice_template === 'string') {
|
|
currentUserSettings.value.default_invoice_template = settings.default_invoice_template
|
|
}
|
|
} catch (err: unknown) {
|
|
handleApiError(err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
function hasAbilities(abilities: string | string[]): boolean {
|
|
return !!currentAbilities.value.find((ab) => {
|
|
if (ab.name === '*') return true
|
|
if (typeof abilities === 'string') {
|
|
return ab.name === abilities
|
|
}
|
|
return !!abilities.find((p) => ab.name === p)
|
|
})
|
|
}
|
|
|
|
function hasAllAbilities(abilities: string[]): boolean {
|
|
let isAvailable = true
|
|
currentAbilities.value.filter((ab) => {
|
|
const hasContain = !!abilities.find((p) => ab.name === p)
|
|
if (!hasContain) {
|
|
isAvailable = false
|
|
}
|
|
})
|
|
return isAvailable
|
|
}
|
|
|
|
return {
|
|
currentUser,
|
|
currentAbilities,
|
|
currentUserSettings,
|
|
userForm,
|
|
currentAbilitiesCount,
|
|
isOwner,
|
|
fetchCurrentUser,
|
|
updateCurrentUser,
|
|
uploadAvatar,
|
|
fetchUserSettings,
|
|
updateUserSettings,
|
|
hasAbilities,
|
|
hasAllAbilities,
|
|
}
|
|
})
|