mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 01:04:03 +00:00
Phase 2: Typed global Pinia stores in scripts-v2/
Rewrite all 7 global stores from JS options API to TypeScript composition API. 8 files, 1005 lines, zero any types. - auth.store.ts: login/logout with authService - global.store.ts: bootstrap, menus, sidebar, config fetching - company.store.ts: company selection, admin mode, settings - user.store.ts: current user, abilities, settings - notification.store.ts: typed toast notifications - dialog.store.ts: confirm dialog returning Promise<boolean> - modal.store.ts: modal state with isEdit getter All use async/await, typed API services, localStore utility, and explicit ref<T> generics. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
111
resources/scripts-v2/stores/auth.store.ts
Normal file
111
resources/scripts-v2/stores/auth.store.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { authService } from '../api/services/auth.service'
|
||||
import type { LoginPayload, ForgotPasswordPayload, ResetPasswordPayload } from '../api/services/auth.service'
|
||||
import { useNotificationStore } from './notification.store'
|
||||
import { handleApiError } from '../utils/error-handling'
|
||||
import * as localStore from '../utils/local-storage'
|
||||
|
||||
export interface LoginData {
|
||||
email: string
|
||||
password: string
|
||||
remember: boolean
|
||||
}
|
||||
|
||||
export interface ForgotPasswordData {
|
||||
email: string
|
||||
}
|
||||
|
||||
export interface ResetPasswordData {
|
||||
email: string
|
||||
password: string
|
||||
password_confirmation: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// State
|
||||
const loginData = ref<LoginData>({
|
||||
email: '',
|
||||
password: '',
|
||||
remember: false,
|
||||
})
|
||||
|
||||
const forgotPasswordData = ref<ForgotPasswordData>({
|
||||
email: '',
|
||||
})
|
||||
|
||||
const resetPasswordData = ref<ResetPasswordData>({
|
||||
email: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
token: '',
|
||||
})
|
||||
|
||||
// Actions
|
||||
async function login(data: LoginPayload): Promise<void> {
|
||||
try {
|
||||
await authService.login(data)
|
||||
|
||||
setTimeout(() => {
|
||||
loginData.value.email = ''
|
||||
loginData.value.password = ''
|
||||
}, 1000)
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
const notificationStore = useNotificationStore()
|
||||
|
||||
try {
|
||||
await authService.logout()
|
||||
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: 'Logged out successfully.',
|
||||
})
|
||||
|
||||
localStore.remove('auth.token')
|
||||
localStore.remove('selectedCompany')
|
||||
|
||||
await authService.refreshCsrfCookie().catch(() => {})
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
localStore.remove('auth.token')
|
||||
localStore.remove('selectedCompany')
|
||||
await authService.refreshCsrfCookie().catch(() => {})
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function forgotPassword(data: ForgotPasswordPayload): Promise<void> {
|
||||
try {
|
||||
await authService.forgotPassword(data)
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function resetPassword(data: ResetPasswordPayload): Promise<void> {
|
||||
try {
|
||||
await authService.resetPassword(data)
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loginData,
|
||||
forgotPasswordData,
|
||||
resetPasswordData,
|
||||
login,
|
||||
logout,
|
||||
forgotPassword,
|
||||
resetPassword,
|
||||
}
|
||||
})
|
||||
194
resources/scripts-v2/stores/company.store.ts
Normal file
194
resources/scripts-v2/stores/company.store.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { companyService } from '../api/services/company.service'
|
||||
import type {
|
||||
UpdateCompanyPayload,
|
||||
CompanySettingsPayload,
|
||||
CreateCompanyPayload,
|
||||
} from '../api/services/company.service'
|
||||
import { useNotificationStore } from './notification.store'
|
||||
import { handleApiError } from '../utils/error-handling'
|
||||
import * as localStore from '../utils/local-storage'
|
||||
import type { Company } from '../types/domain/company'
|
||||
import type { Currency } from '../types/domain/currency'
|
||||
import type { ApiResponse } from '../types/api'
|
||||
|
||||
export const useCompanyStore = defineStore('company', () => {
|
||||
// State
|
||||
const companies = ref<Company[]>([])
|
||||
const selectedCompany = ref<Company | null>(null)
|
||||
const selectedCompanySettings = ref<Record<string, string>>({})
|
||||
const selectedCompanyCurrency = ref<Currency | null>(null)
|
||||
const isAdminMode = ref<boolean>(localStore.get<string>('isAdminMode') === 'true')
|
||||
const defaultCurrency = ref<Currency | null>(null)
|
||||
|
||||
// Actions
|
||||
function setSelectedCompany(data: Company | null): void {
|
||||
if (data) {
|
||||
localStore.set('selectedCompany', data.id)
|
||||
localStore.remove('isAdminMode')
|
||||
isAdminMode.value = false
|
||||
} else {
|
||||
localStore.remove('selectedCompany')
|
||||
}
|
||||
selectedCompany.value = data
|
||||
}
|
||||
|
||||
function setAdminMode(enabled: boolean): void {
|
||||
isAdminMode.value = enabled
|
||||
if (enabled) {
|
||||
localStore.set('isAdminMode', 'true')
|
||||
localStore.remove('selectedCompany')
|
||||
selectedCompany.value = null
|
||||
} else {
|
||||
localStore.remove('isAdminMode')
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchBasicMailConfig(): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
return await companyService.getMailConfig()
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCompany(data: UpdateCompanyPayload): Promise<ApiResponse<Company>> {
|
||||
try {
|
||||
const response = await companyService.update(data)
|
||||
|
||||
const notificationStore = useNotificationStore()
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: 'settings.company_info.updated_message',
|
||||
})
|
||||
|
||||
selectedCompany.value = response.data
|
||||
const companyIndex = companies.value.findIndex(
|
||||
(company) => company.unique_hash === response.data.unique_hash
|
||||
)
|
||||
if (companyIndex !== -1) {
|
||||
companies.value[companyIndex] = response.data
|
||||
}
|
||||
|
||||
return response
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCompanyLogo(data: FormData): Promise<ApiResponse<Company>> {
|
||||
try {
|
||||
return await companyService.uploadLogo(data)
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function addNewCompany(data: CreateCompanyPayload): Promise<ApiResponse<Company>> {
|
||||
try {
|
||||
const response = await companyService.create(data)
|
||||
|
||||
const notificationStore = useNotificationStore()
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: 'company_switcher.created_message',
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCompany(): Promise<Company> {
|
||||
try {
|
||||
const response = await companyService.listUserCompanies()
|
||||
return response.data[0]
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUserCompanies(): Promise<ApiResponse<Company[]>> {
|
||||
try {
|
||||
return await companyService.listUserCompanies()
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCompanySettings(settings?: string[]): Promise<Record<string, string>> {
|
||||
try {
|
||||
return await companyService.getSettings(settings)
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCompanySettings(params: {
|
||||
data: CompanySettingsPayload
|
||||
message?: string
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await companyService.updateSettings(params.data)
|
||||
|
||||
Object.assign(
|
||||
selectedCompanySettings.value,
|
||||
params.data.settings
|
||||
)
|
||||
|
||||
if (params.message) {
|
||||
const notificationStore = useNotificationStore()
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: params.message,
|
||||
})
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteCompany(data: { id: number }): Promise<void> {
|
||||
try {
|
||||
await companyService.delete(data)
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function setDefaultCurrency(data: { currency: Currency }): void {
|
||||
defaultCurrency.value = data.currency
|
||||
}
|
||||
|
||||
return {
|
||||
companies,
|
||||
selectedCompany,
|
||||
selectedCompanySettings,
|
||||
selectedCompanyCurrency,
|
||||
isAdminMode,
|
||||
defaultCurrency,
|
||||
setSelectedCompany,
|
||||
setAdminMode,
|
||||
fetchBasicMailConfig,
|
||||
updateCompany,
|
||||
updateCompanyLogo,
|
||||
addNewCompany,
|
||||
fetchCompany,
|
||||
fetchUserCompanies,
|
||||
fetchCompanySettings,
|
||||
updateCompanySettings,
|
||||
deleteCompany,
|
||||
setDefaultCurrency,
|
||||
}
|
||||
})
|
||||
90
resources/scripts-v2/stores/dialog.store.ts
Normal file
90
resources/scripts-v2/stores/dialog.store.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type DialogVariant = 'primary' | 'danger'
|
||||
|
||||
export type DialogSize = 'sm' | 'md' | 'lg'
|
||||
|
||||
export interface OpenDialogPayload {
|
||||
title: string
|
||||
message: string
|
||||
size?: DialogSize
|
||||
data?: unknown
|
||||
variant?: DialogVariant
|
||||
yesLabel?: string
|
||||
noLabel?: string
|
||||
hideNoButton?: boolean
|
||||
}
|
||||
|
||||
export const useDialogStore = defineStore('dialog', () => {
|
||||
// State
|
||||
const active = ref<boolean>(false)
|
||||
const title = ref<string>('')
|
||||
const message = ref<string>('')
|
||||
const size = ref<DialogSize>('md')
|
||||
const data = ref<unknown>(null)
|
||||
const variant = ref<DialogVariant>('danger')
|
||||
const yesLabel = ref<string>('Yes')
|
||||
const noLabel = ref<string>('No')
|
||||
const hideNoButton = ref<boolean>(false)
|
||||
const resolve = ref<((value: boolean) => void) | null>(null)
|
||||
|
||||
// Actions
|
||||
function openDialog(payload: OpenDialogPayload): Promise<boolean> {
|
||||
active.value = true
|
||||
title.value = payload.title
|
||||
message.value = payload.message
|
||||
size.value = payload.size ?? 'md'
|
||||
data.value = payload.data ?? null
|
||||
variant.value = payload.variant ?? 'danger'
|
||||
yesLabel.value = payload.yesLabel ?? 'Yes'
|
||||
noLabel.value = payload.noLabel ?? 'No'
|
||||
hideNoButton.value = payload.hideNoButton ?? false
|
||||
|
||||
return new Promise<boolean>((res) => {
|
||||
resolve.value = res
|
||||
})
|
||||
}
|
||||
|
||||
function confirm(): void {
|
||||
if (resolve.value) {
|
||||
resolve.value(true)
|
||||
}
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
if (resolve.value) {
|
||||
resolve.value(false)
|
||||
}
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
function closeDialog(): void {
|
||||
active.value = false
|
||||
|
||||
setTimeout(() => {
|
||||
title.value = ''
|
||||
message.value = ''
|
||||
data.value = null
|
||||
resolve.value = null
|
||||
}, 300)
|
||||
}
|
||||
|
||||
return {
|
||||
active,
|
||||
title,
|
||||
message,
|
||||
size,
|
||||
data,
|
||||
variant,
|
||||
yesLabel,
|
||||
noLabel,
|
||||
hideNoButton,
|
||||
resolve,
|
||||
openDialog,
|
||||
confirm,
|
||||
cancel,
|
||||
closeDialog,
|
||||
}
|
||||
})
|
||||
286
resources/scripts-v2/stores/global.store.ts
Normal file
286
resources/scripts-v2/stores/global.store.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import groupBy from 'lodash/groupBy'
|
||||
import { bootstrapService } from '../api/services/bootstrap.service'
|
||||
import type { MenuItem, BootstrapResponse } from '../api/services/bootstrap.service'
|
||||
import { settingService } from '../api/services/setting.service'
|
||||
import type {
|
||||
DateFormat,
|
||||
TimeFormat,
|
||||
ConfigResponse,
|
||||
GlobalSettingsPayload,
|
||||
NumberPlaceholdersParams,
|
||||
NumberPlaceholder,
|
||||
} from '../api/services/setting.service'
|
||||
import { useCompanyStore } from './company.store'
|
||||
import { useUserStore } from './user.store'
|
||||
import { useNotificationStore } from './notification.store'
|
||||
import { handleApiError } from '../utils/error-handling'
|
||||
import * as localStore from '../utils/local-storage'
|
||||
import type { Currency } from '../types/domain/currency'
|
||||
import type { Country } from '../types/domain/customer'
|
||||
|
||||
export const useGlobalStore = defineStore('global', () => {
|
||||
// State
|
||||
const config = ref<Record<string, unknown> | null>(null)
|
||||
const globalSettings = ref<Record<string, string> | null>(null)
|
||||
|
||||
const timeZones = ref<string[]>([])
|
||||
const dateFormats = ref<DateFormat[]>([])
|
||||
const timeFormats = ref<TimeFormat[]>([])
|
||||
const currencies = ref<Currency[]>([])
|
||||
const countries = ref<Country[]>([])
|
||||
const languages = ref<Array<{ code: string; name: string }>>([])
|
||||
const fiscalYears = ref<Array<{ key: string; value: string }>>([])
|
||||
|
||||
const mainMenu = ref<MenuItem[]>([])
|
||||
const settingMenu = ref<MenuItem[]>([])
|
||||
|
||||
const isAppLoaded = ref<boolean>(false)
|
||||
const isSidebarOpen = ref<boolean>(false)
|
||||
const isSidebarCollapsed = ref<boolean>(
|
||||
localStore.get<string>('sidebarCollapsed') === 'true'
|
||||
)
|
||||
const areCurrenciesLoading = ref<boolean>(false)
|
||||
|
||||
const downloadReport = ref<string | null>(null)
|
||||
|
||||
// Getters
|
||||
const menuGroups = computed<MenuItem[][]>(() => {
|
||||
return Object.values(groupBy(mainMenu.value, 'group'))
|
||||
})
|
||||
|
||||
// Actions
|
||||
async function bootstrap(): Promise<BootstrapResponse> {
|
||||
const companyStore = useCompanyStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
try {
|
||||
const response = await bootstrapService.bootstrap(companyStore.isAdminMode)
|
||||
|
||||
mainMenu.value = response.main_menu
|
||||
settingMenu.value = response.setting_menu
|
||||
|
||||
config.value = response.config
|
||||
globalSettings.value = response.global_settings
|
||||
|
||||
// user store
|
||||
userStore.currentUser = response.current_user
|
||||
userStore.currentUserSettings = response.current_user_settings
|
||||
userStore.currentAbilities = response.current_user_abilities
|
||||
|
||||
// company store
|
||||
companyStore.companies = response.companies
|
||||
|
||||
if (response.current_company) {
|
||||
companyStore.setSelectedCompany(response.current_company)
|
||||
companyStore.selectedCompanySettings = response.current_company_settings
|
||||
companyStore.selectedCompanyCurrency = response.current_company_currency
|
||||
} else {
|
||||
companyStore.setSelectedCompany(null)
|
||||
companyStore.selectedCompanySettings = {}
|
||||
companyStore.selectedCompanyCurrency = null
|
||||
}
|
||||
|
||||
isAppLoaded.value = true
|
||||
return response
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCurrencies(): Promise<Currency[]> {
|
||||
if (currencies.value.length || areCurrenciesLoading.value) {
|
||||
return currencies.value
|
||||
}
|
||||
|
||||
areCurrenciesLoading.value = true
|
||||
|
||||
try {
|
||||
const response = await settingService.getCurrencies()
|
||||
currencies.value = response.data.map((currency) => ({
|
||||
...currency,
|
||||
name: `${currency.code} - ${currency.name}`,
|
||||
}))
|
||||
areCurrenciesLoading.value = false
|
||||
return currencies.value
|
||||
} catch (err: unknown) {
|
||||
areCurrenciesLoading.value = false
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchConfig(params?: Record<string, string>): Promise<ConfigResponse> {
|
||||
try {
|
||||
const response = await settingService.getConfig(params)
|
||||
|
||||
if ((response as Record<string, unknown>).languages) {
|
||||
languages.value = (response as Record<string, unknown>).languages as Array<{
|
||||
code: string
|
||||
name: string
|
||||
}>
|
||||
} else if ((response as Record<string, unknown>).fiscal_years) {
|
||||
fiscalYears.value = (response as Record<string, unknown>).fiscal_years as Array<{
|
||||
key: string
|
||||
value: string
|
||||
}>
|
||||
}
|
||||
|
||||
return response
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDateFormats(): Promise<DateFormat[]> {
|
||||
if (dateFormats.value.length) {
|
||||
return dateFormats.value
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await settingService.getDateFormats()
|
||||
dateFormats.value = response.date_formats
|
||||
return dateFormats.value
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTimeFormats(): Promise<TimeFormat[]> {
|
||||
if (timeFormats.value.length) {
|
||||
return timeFormats.value
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await settingService.getTimeFormats()
|
||||
timeFormats.value = response.time_formats
|
||||
return timeFormats.value
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTimeZones(): Promise<string[]> {
|
||||
if (timeZones.value.length) {
|
||||
return timeZones.value
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await settingService.getTimezones()
|
||||
timeZones.value = response.time_zones
|
||||
return timeZones.value
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchCountries(): Promise<Country[]> {
|
||||
if (countries.value.length) {
|
||||
return countries.value
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await settingService.getCountries()
|
||||
countries.value = response.data
|
||||
return countries.value
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPlaceholders(
|
||||
params: NumberPlaceholdersParams
|
||||
): Promise<{ placeholders: NumberPlaceholder[] }> {
|
||||
try {
|
||||
return await settingService.getNumberPlaceholders(params)
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function setSidebarVisibility(val: boolean): void {
|
||||
isSidebarOpen.value = val
|
||||
}
|
||||
|
||||
function toggleSidebarCollapse(): void {
|
||||
isSidebarCollapsed.value = !isSidebarCollapsed.value
|
||||
localStore.set(
|
||||
'sidebarCollapsed',
|
||||
isSidebarCollapsed.value ? 'true' : 'false'
|
||||
)
|
||||
}
|
||||
|
||||
function setIsAppLoaded(loaded: boolean): void {
|
||||
isAppLoaded.value = loaded
|
||||
}
|
||||
|
||||
async function updateGlobalSettings(params: {
|
||||
data: GlobalSettingsPayload
|
||||
message?: string
|
||||
}): Promise<void> {
|
||||
try {
|
||||
await settingService.updateGlobalSettings(params.data)
|
||||
|
||||
if (globalSettings.value) {
|
||||
Object.assign(
|
||||
globalSettings.value,
|
||||
params.data.settings
|
||||
)
|
||||
}
|
||||
|
||||
if (params.message) {
|
||||
const notificationStore = useNotificationStore()
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: params.message,
|
||||
})
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
config,
|
||||
globalSettings,
|
||||
timeZones,
|
||||
dateFormats,
|
||||
timeFormats,
|
||||
currencies,
|
||||
countries,
|
||||
languages,
|
||||
fiscalYears,
|
||||
mainMenu,
|
||||
settingMenu,
|
||||
isAppLoaded,
|
||||
isSidebarOpen,
|
||||
isSidebarCollapsed,
|
||||
areCurrenciesLoading,
|
||||
downloadReport,
|
||||
// Getters
|
||||
menuGroups,
|
||||
// Actions
|
||||
bootstrap,
|
||||
fetchCurrencies,
|
||||
fetchConfig,
|
||||
fetchDateFormats,
|
||||
fetchTimeFormats,
|
||||
fetchTimeZones,
|
||||
fetchCountries,
|
||||
fetchPlaceholders,
|
||||
setSidebarVisibility,
|
||||
toggleSidebarCollapse,
|
||||
setIsAppLoaded,
|
||||
updateGlobalSettings,
|
||||
}
|
||||
})
|
||||
18
resources/scripts-v2/stores/index.ts
Normal file
18
resources/scripts-v2/stores/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export { useAuthStore } from './auth.store'
|
||||
export type { LoginData, ForgotPasswordData, ResetPasswordData } from './auth.store'
|
||||
|
||||
export { useGlobalStore } from './global.store'
|
||||
|
||||
export { useCompanyStore } from './company.store'
|
||||
|
||||
export { useUserStore } from './user.store'
|
||||
export type { UserForm } from './user.store'
|
||||
|
||||
export { useNotificationStore } from './notification.store'
|
||||
export type { NotificationType, Notification, ShowNotificationPayload } from './notification.store'
|
||||
|
||||
export { useDialogStore } from './dialog.store'
|
||||
export type { DialogVariant, DialogSize, OpenDialogPayload } from './dialog.store'
|
||||
|
||||
export { useModalStore } from './modal.store'
|
||||
export type { ModalSize, OpenModalPayload } from './modal.store'
|
||||
98
resources/scripts-v2/stores/modal.store.ts
Normal file
98
resources/scripts-v2/stores/modal.store.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
export type ModalSize = 'sm' | 'md' | 'lg' | 'xl'
|
||||
|
||||
export interface OpenModalPayload {
|
||||
componentName: string
|
||||
title: string
|
||||
id?: string | number
|
||||
content?: string
|
||||
data?: unknown
|
||||
refreshData?: (() => void) | null
|
||||
variant?: string
|
||||
size?: ModalSize
|
||||
}
|
||||
|
||||
export const useModalStore = defineStore('modal', () => {
|
||||
// State
|
||||
const active = ref<boolean>(false)
|
||||
const content = ref<string>('')
|
||||
const title = ref<string>('')
|
||||
const componentName = ref<string>('')
|
||||
const id = ref<string | number>('')
|
||||
const size = ref<ModalSize>('md')
|
||||
const data = ref<unknown>(null)
|
||||
const refreshData = ref<(() => void) | null>(null)
|
||||
const variant = ref<string>('')
|
||||
|
||||
// Getters
|
||||
const isEdit = computed<boolean>(() => {
|
||||
return id.value !== '' && id.value !== 0
|
||||
})
|
||||
|
||||
// Actions
|
||||
function openModal(payload: OpenModalPayload): void {
|
||||
componentName.value = payload.componentName
|
||||
active.value = true
|
||||
|
||||
if (payload.id) {
|
||||
id.value = payload.id
|
||||
}
|
||||
|
||||
title.value = payload.title
|
||||
|
||||
if (payload.content) {
|
||||
content.value = payload.content
|
||||
}
|
||||
|
||||
if (payload.data) {
|
||||
data.value = payload.data
|
||||
}
|
||||
|
||||
if (payload.refreshData) {
|
||||
refreshData.value = payload.refreshData
|
||||
}
|
||||
|
||||
if (payload.variant) {
|
||||
variant.value = payload.variant
|
||||
}
|
||||
|
||||
if (payload.size) {
|
||||
size.value = payload.size
|
||||
}
|
||||
}
|
||||
|
||||
function resetModalData(): void {
|
||||
content.value = ''
|
||||
title.value = ''
|
||||
componentName.value = ''
|
||||
id.value = ''
|
||||
data.value = null
|
||||
refreshData.value = null
|
||||
}
|
||||
|
||||
function closeModal(): void {
|
||||
active.value = false
|
||||
|
||||
setTimeout(() => {
|
||||
resetModalData()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
return {
|
||||
active,
|
||||
content,
|
||||
title,
|
||||
componentName,
|
||||
id,
|
||||
size,
|
||||
data,
|
||||
refreshData,
|
||||
variant,
|
||||
isEdit,
|
||||
openModal,
|
||||
resetModalData,
|
||||
closeModal,
|
||||
}
|
||||
})
|
||||
44
resources/scripts-v2/stores/notification.store.ts
Normal file
44
resources/scripts-v2/stores/notification.store.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type NotificationType = 'success' | 'error' | 'warning' | 'info'
|
||||
|
||||
export interface Notification {
|
||||
id: string
|
||||
type: NotificationType
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface ShowNotificationPayload {
|
||||
type: NotificationType
|
||||
message: string
|
||||
}
|
||||
|
||||
export const useNotificationStore = defineStore('notification', () => {
|
||||
// State
|
||||
const active = ref<boolean>(false)
|
||||
const autoHide = ref<boolean>(true)
|
||||
const notifications = ref<Notification[]>([])
|
||||
|
||||
// Actions
|
||||
function showNotification(notification: ShowNotificationPayload): void {
|
||||
notifications.value.push({
|
||||
...notification,
|
||||
id: (Math.random().toString(36) + Date.now().toString(36)).substring(2),
|
||||
})
|
||||
}
|
||||
|
||||
function hideNotification(data: { id: string }): void {
|
||||
notifications.value = notifications.value.filter(
|
||||
(notification) => notification.id !== data.id
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
active,
|
||||
autoHide,
|
||||
notifications,
|
||||
showNotification,
|
||||
hideNotification,
|
||||
}
|
||||
})
|
||||
164
resources/scripts-v2/stores/user.store.ts
Normal file
164
resources/scripts-v2/stores/user.store.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { userService } from '../api/services/user.service'
|
||||
import type { UpdateProfilePayload, UserSettingsPayload } from '../api/services/user.service'
|
||||
import { useNotificationStore } from './notification.store'
|
||||
import { handleApiError } from '../utils/error-handling'
|
||||
import type { User } from '../types/domain/user'
|
||||
import type { Ability } from '../types/domain/role'
|
||||
import type { ApiResponse } from '../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: '',
|
||||
}
|
||||
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: '',
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user