106 lines
3.0 KiB
TypeScript
106 lines
3.0 KiB
TypeScript
import { createEffect, createEvent, createStore, sample } from 'effector'
|
|
import { apiClient } from '@/shared/api/client'
|
|
|
|
export type AuthUser = { id: string; email: string; name?: string | null; phone?: string | null }
|
|
|
|
const TOKEN_KEY = 'craftshop_auth_token'
|
|
|
|
export const tokenSet = createEvent<string | null>()
|
|
export const logout = createEvent()
|
|
|
|
export const $token = createStore<string | null>(null)
|
|
.on(tokenSet, (_, t) => t)
|
|
.reset(logout)
|
|
|
|
export const $user = createStore<AuthUser | null>(null).reset(logout)
|
|
|
|
export const changePasswordFx = createEffect(async (params: { currentPassword?: string; newPassword: string }) => {
|
|
const { data } = await apiClient.patch<{ user: AuthUser }>('me/password', params)
|
|
return data.user
|
|
})
|
|
|
|
export const requestEmailChangeCodeFx = createEffect(async (newEmail: string) => {
|
|
await apiClient.post('me/change-email/request-code', { newEmail })
|
|
})
|
|
|
|
export const verifyEmailChangeFx = createEffect(async (params: { newEmail: string; code: string }) => {
|
|
const { data } = await apiClient.post<{ user: AuthUser }>('me/change-email/verify', params)
|
|
return data.user
|
|
})
|
|
|
|
export type UpdateProfileParams = { name: string | null; phone?: string | null }
|
|
|
|
export const updateProfileFx = createEffect(async (params: UpdateProfileParams) => {
|
|
const { data } = await apiClient.patch<{ user: AuthUser }>('me/profile', params)
|
|
return data.user
|
|
})
|
|
|
|
export const $changePasswordError = createStore<unknown | null>(null)
|
|
.on(changePasswordFx.failData, (_, e) => e)
|
|
.reset(changePasswordFx, logout)
|
|
|
|
export const $requestEmailChangeCodeError = createStore<unknown | null>(null)
|
|
.on(requestEmailChangeCodeFx.failData, (_, e) => e)
|
|
.reset(requestEmailChangeCodeFx, logout)
|
|
|
|
export const $verifyEmailChangeError = createStore<unknown | null>(null)
|
|
.on(verifyEmailChangeFx.failData, (_, e) => e)
|
|
.reset(verifyEmailChangeFx, logout)
|
|
|
|
export const $updateProfileError = createStore<unknown | null>(null)
|
|
.on(updateProfileFx.failData, (_, e) => e)
|
|
.reset(updateProfileFx, logout)
|
|
|
|
export const meFx = createEffect(async (token: string) => {
|
|
const { data } = await apiClient.get<{ user: AuthUser | null }>('me', {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
return data.user
|
|
})
|
|
|
|
sample({
|
|
clock: tokenSet,
|
|
filter: (t): t is string => Boolean(t),
|
|
target: meFx,
|
|
})
|
|
|
|
sample({
|
|
clock: meFx.doneData,
|
|
target: $user,
|
|
})
|
|
|
|
sample({
|
|
clock: [changePasswordFx.doneData, verifyEmailChangeFx.doneData, updateProfileFx.doneData],
|
|
target: $user,
|
|
})
|
|
|
|
let tokenPersistInitialized = false
|
|
$token.watch((t) => {
|
|
try {
|
|
if (!tokenPersistInitialized) {
|
|
tokenPersistInitialized = true
|
|
return
|
|
}
|
|
if (!t) localStorage.removeItem(TOKEN_KEY)
|
|
else localStorage.setItem(TOKEN_KEY, t)
|
|
} catch {
|
|
// ignore
|
|
}
|
|
})
|
|
|
|
logout.watch(() => {
|
|
try {
|
|
localStorage.removeItem(TOKEN_KEY)
|
|
} catch {
|
|
// ignore
|
|
}
|
|
})
|
|
|
|
export function readStoredToken(): string | null {
|
|
try {
|
|
return localStorage.getItem(TOKEN_KEY)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|