Files
goodgo-platform/apps/web/lib/auth-store.ts
Ho Ngoc Hai 0b29fac35e feat(notifications): add multi-channel notification module with Email, FCM, templates, and event listeners
- Domain: NotificationLog/NotificationPreference entities, repositories, channel value object
- Infrastructure: EmailService (nodemailer/SMTP), FcmService (firebase-admin), TemplateService (Handlebars)
- Application: SendNotification CQRS command, UserRegistered + AgentVerified event listeners
- Presentation: NotificationsController with history, preferences, and templates endpoints
- Prisma: NotificationLog and NotificationPreference models with proper indexes
- Templates: Vietnamese notification templates for user.registered, agent.verified, listing.approved, inquiry.received, password.reset

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-08 01:42:17 +07:00

124 lines
3.2 KiB
TypeScript

import { create } from 'zustand';
import { authApi, type TokenPair, type UserProfile, type LoginPayload, type RegisterPayload } from './auth-api';
import { ApiError } from './api-client';
const TOKEN_KEY = 'goodgo_tokens';
function persistTokens(tokens: TokenPair | null) {
if (typeof window === 'undefined') return;
if (tokens) {
localStorage.setItem(TOKEN_KEY, JSON.stringify(tokens));
} else {
localStorage.removeItem(TOKEN_KEY);
}
}
function loadTokens(): TokenPair | null {
if (typeof window === 'undefined') return null;
const raw = localStorage.getItem(TOKEN_KEY);
if (!raw) return null;
try {
return JSON.parse(raw);
} catch {
return null;
}
}
interface AuthState {
tokens: TokenPair | null;
user: UserProfile | null;
isLoading: boolean;
error: string | null;
login: (data: LoginPayload) => Promise<void>;
register: (data: RegisterPayload) => Promise<void>;
logout: () => void;
refreshToken: () => Promise<boolean>;
fetchProfile: () => Promise<void>;
initialize: () => Promise<void>;
clearError: () => void;
}
export const useAuthStore = create<AuthState>((set, get) => ({
tokens: null,
user: null,
isLoading: false,
error: null,
login: async (data) => {
set({ isLoading: true, error: null });
try {
const tokens = await authApi.login(data);
persistTokens(tokens);
set({ tokens, isLoading: false });
await get().fetchProfile();
} catch (e) {
const message = e instanceof ApiError ? e.message : 'Đăng nhập thất bại';
set({ isLoading: false, error: message });
throw e;
}
},
register: async (data) => {
set({ isLoading: true, error: null });
try {
const tokens = await authApi.register(data);
persistTokens(tokens);
set({ tokens, isLoading: false });
await get().fetchProfile();
} catch (e) {
const message = e instanceof ApiError ? e.message : 'Đăng ký thất bại';
set({ isLoading: false, error: message });
throw e;
}
},
logout: () => {
persistTokens(null);
set({ tokens: null, user: null, error: null });
},
refreshToken: async () => {
const { tokens } = get();
if (!tokens?.refreshToken) return false;
try {
const newTokens = await authApi.refresh(tokens.refreshToken);
persistTokens(newTokens);
set({ tokens: newTokens });
return true;
} catch {
get().logout();
return false;
}
},
fetchProfile: async () => {
const { tokens } = get();
if (!tokens?.accessToken) return;
try {
const user = await authApi.getProfile(tokens.accessToken);
set({ user });
} catch (e) {
if (e instanceof ApiError && e.status === 401) {
const refreshed = await get().refreshToken();
if (refreshed) {
const newTokens = get().tokens;
if (newTokens) {
const user = await authApi.getProfile(newTokens.accessToken);
set({ user });
}
}
}
}
},
initialize: async () => {
const tokens = loadTokens();
if (!tokens) return;
set({ tokens });
await get().fetchProfile();
},
clearError: () => set({ error: null }),
}));