Creating & Edit profile

This commit is contained in:
2026-09-15 14:10:52 +03:30
parent 8dcf4ed5e9
commit 9ae176bec0
13 changed files with 767 additions and 77 deletions
+75
View File
@@ -6,12 +6,14 @@ import type {
LoginResponse,
RegisterChallengeResponse,
RegisterOrganizationRequest,
UpdateProfileRequest,
} from '@mento/shared';
import { api } from '@/lib/api';
const TOKEN_KEY = 'mento_token';
const USER_KEY = 'mento_user';
const LEGACY_KEYS = ['ment_token', 'ment_user'] as const;
const DEFAULT_AVATAR = '/brand/user-avatar.png';
function readStoredToken(): string | null {
return localStorage.getItem(TOKEN_KEY) ?? localStorage.getItem('ment_token');
@@ -34,8 +36,38 @@ function clearLegacy() {
export const useAuthStore = defineStore('auth', () => {
const token = ref<string | null>(readStoredToken());
const user = ref<AuthUserDto | null>(readStoredUser());
const avatarObjectUrl = ref<string | null>(null);
const isAuthenticated = computed(() => Boolean(token.value));
const avatarSrc = computed(() => avatarObjectUrl.value || DEFAULT_AVATAR);
function clearAvatarUrl() {
if (avatarObjectUrl.value) {
URL.revokeObjectURL(avatarObjectUrl.value);
avatarObjectUrl.value = null;
}
}
async function refreshAvatar() {
if (!token.value || !user.value?.hasAvatar) {
clearAvatarUrl();
return;
}
try {
const { data } = await api.get<Blob>('/auth/me/avatar', {
responseType: 'blob',
});
if (!data || data.type.includes('json') || data.size === 0) {
clearAvatarUrl();
return;
}
const next = URL.createObjectURL(data);
clearAvatarUrl();
avatarObjectUrl.value = next;
} catch {
clearAvatarUrl();
}
}
function persist(payload: LoginResponse) {
token.value = payload.accessToken;
@@ -43,6 +75,7 @@ export const useAuthStore = defineStore('auth', () => {
localStorage.setItem(TOKEN_KEY, payload.accessToken);
localStorage.setItem(USER_KEY, JSON.stringify(payload.user));
clearLegacy();
void refreshAvatar();
}
async function login(identifier: string, password: string) {
@@ -92,20 +125,62 @@ export const useAuthStore = defineStore('auth', () => {
function logout() {
token.value = null;
user.value = null;
clearAvatarUrl();
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
clearLegacy();
}
function setUser(next: AuthUserDto) {
user.value = next;
localStorage.setItem(USER_KEY, JSON.stringify(next));
void refreshAvatar();
}
async function hydrate() {
if (!token.value) return;
try {
const { data } = await api.get<AuthUserDto>('/auth/me');
setUser(data);
} catch {
/* interceptor handles 401 */
}
}
async function updateProfile(payload: UpdateProfileRequest) {
const { data } = await api.patch<AuthUserDto>('/auth/me', payload);
setUser(data);
}
async function uploadAvatar(file: File) {
const body = new FormData();
body.append('file', file);
const { data } = await api.post<AuthUserDto>('/auth/me/avatar', body);
setUser(data);
}
async function removeAvatar() {
const { data } = await api.delete<AuthUserDto>('/auth/me/avatar');
setUser(data);
}
if (token.value) void hydrate();
return {
token,
user,
isAuthenticated,
avatarSrc,
login,
requestLoginOtp,
verifyLoginOtp,
startRegister,
verifyRegister,
logout,
setUser,
hydrate,
updateProfile,
uploadAvatar,
removeAvatar,
};
});