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 858972b5ca Add multi-provider login and guest pharmacist order codes.
Guests can generate a shareable GSH code for admin fulfillment; sign-in now supports Google, email, phone, and Telegram demos, with the backend spec updated.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 16:11:23 +03:00

250 lines
6.6 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 AuthProvider =
| "google"
| "email"
| "phone"
| "telegram"
| "guest";
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;
/** How the user signed in (demo providers included). */
authProvider?: AuthProvider;
};
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;
authProvider?: AuthProvider;
}
) => 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, authProvider }) =>
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,
authProvider:
authProvider ?? (isGuest ? "guest" : "phone"),
},
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: 4,
migrate: (persisted: unknown) => {
const state = (persisted ?? {}) as Partial<AuthState>;
if (!state.user) {
return {
...state,
user: {
...DEMO_USER,
referralCode: "GISHEN-KIRU",
activeVoucher: null,
authProvider: "phone" as const,
},
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,
authProvider:
state.user.authProvider ??
(state.user.isGuest ? ("guest" as const) : ("phone" as const)),
},
ledger: state.ledger?.length ? state.ledger : seedLedger,
};
},
}
)
);