This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-Mob/lib/stores/authStore.ts
kirukib 7310a349c5 Add Gishen mobile app with complete demo shopping and care flows.
Ship Expo Router screens, seeded stores, and polished UX so auth, shop, checkout, prescriptions, loyalty, family, and account processes work end-to-end offline.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 01:21:32 +03:00

229 lines
6.1 KiB
TypeScript

import AsyncStorage from "@react-native-async-storage/async-storage";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import { DEMO_USER } from "@/lib/data/seed";
export type AuthUser = {
id: string;
name: string;
phone: string;
email?: string;
points: number;
tier: "bronze" | "silver" | "gold";
isGuest: boolean;
biometricEnabled: boolean;
avatarUri?: string | null;
referralCode?: string;
activeVoucher?: string | null;
};
export type PointsEntry = {
id: string;
at: string;
label: string;
delta: number;
balance: number;
};
type AuthState = {
user: AuthUser | null;
hasOnboarded: boolean;
ledger: PointsEntry[];
setHasOnboarded: (v: boolean) => void;
signIn: (payload: Partial<AuthUser> & { phone: string; name: string }) => void;
signOut: () => void;
setBiometric: (enabled: boolean) => void;
setAvatar: (uri: string | null) => void;
updateProfile: (
patch: Partial<Pick<AuthUser, "name" | "email" | "phone">>
) => void;
addPoints: (pts: number, label?: string) => void;
redeemPoints: (pts: number) => boolean;
setActiveVoucher: (code: string | null) => void;
ensureDemoUser: () => void;
};
function tierFor(points: number): AuthUser["tier"] {
if (points >= 1000) return "gold";
if (points >= 300) return "silver";
return "bronze";
}
const seedLedger: PointsEntry[] = [
{
id: "pts_1",
at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 20).toISOString(),
label: "Welcome bonus",
delta: 50,
balance: 50,
},
{
id: "pts_2",
at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 12).toISOString(),
label: "Order reward",
delta: 28,
balance: 78,
},
{
id: "pts_3",
at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5).toISOString(),
label: "Referral bonus",
delta: 100,
balance: 178,
},
{
id: "pts_4",
at: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2).toISOString(),
label: "Order reward",
delta: 242,
balance: 420,
},
];
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
user: {
...DEMO_USER,
email: DEMO_USER.email,
referralCode: "GISHEN-KIRU",
activeVoucher: null,
},
hasOnboarded: true,
ledger: seedLedger,
setHasOnboarded: (v) => set({ hasOnboarded: v }),
signIn: ({ phone, name, email, isGuest, avatarUri }) =>
set({
user: {
id: `u_${Date.now()}`,
name,
phone,
email,
points: isGuest ? 0 : 50,
tier: "bronze",
isGuest: !!isGuest,
biometricEnabled: false,
avatarUri: avatarUri ?? null,
referralCode: `GISHEN-${name.slice(0, 4).toUpperCase() || "USER"}`,
activeVoucher: null,
},
hasOnboarded: true,
ledger: isGuest
? []
: [
{
id: `pts_${Date.now()}`,
at: new Date().toISOString(),
label: "Welcome bonus",
delta: 50,
balance: 50,
},
],
}),
signOut: () => set({ user: null }),
setBiometric: (enabled) => {
const user = get().user;
if (!user) return;
set({ user: { ...user, biometricEnabled: enabled } });
},
setAvatar: (uri) => {
const user = get().user;
if (!user) return;
set({ user: { ...user, avatarUri: uri } });
},
updateProfile: (patch) => {
const user = get().user;
if (!user) return;
set({ user: { ...user, ...patch } });
},
addPoints: (pts, label = "Order reward") => {
const user = get().user;
if (!user || user.isGuest || pts === 0) return;
const points = user.points + pts;
const entry: PointsEntry = {
id: `pts_${Date.now()}`,
at: new Date().toISOString(),
label,
delta: pts,
balance: points,
};
set({
user: { ...user, points, tier: tierFor(points) },
ledger: [entry, ...get().ledger],
});
},
redeemPoints: (pts) => {
const user = get().user;
if (!user || user.isGuest || user.points < pts) return false;
const points = user.points - pts;
const entry: PointsEntry = {
id: `pts_${Date.now()}`,
at: new Date().toISOString(),
label: `Redeemed for voucher`,
delta: -pts,
balance: points,
};
set({
user: {
...user,
points,
tier: tierFor(points),
activeVoucher: "GISHEN10",
},
ledger: [entry, ...get().ledger],
});
return true;
},
setActiveVoucher: (code) => {
const user = get().user;
if (!user) return;
set({ user: { ...user, activeVoucher: code } });
},
ensureDemoUser: () => {
if (!get().user) {
set({
user: {
...DEMO_USER,
referralCode: "GISHEN-KIRU",
activeVoucher: null,
},
hasOnboarded: true,
ledger: seedLedger,
});
}
},
}),
{
name: "gishen-auth",
storage: createJSONStorage(() => AsyncStorage),
version: 3,
migrate: (persisted: unknown) => {
const state = (persisted ?? {}) as Partial<AuthState>;
if (!state.user) {
return {
...state,
user: {
...DEMO_USER,
referralCode: "GISHEN-KIRU",
activeVoucher: null,
},
hasOnboarded: true,
ledger: seedLedger,
};
}
return {
...state,
user: {
...DEMO_USER,
...state.user,
avatarUri: state.user.avatarUri ?? null,
referralCode: state.user.referralCode ?? "GISHEN-KIRU",
activeVoucher: state.user.activeVoucher ?? null,
},
ledger: state.ledger?.length ? state.ledger : seedLedger,
};
},
}
)
);