beta sign up sends an email with a magic token. then a separate email to login.
This commit is contained in:
+49
-29
@@ -30,6 +30,19 @@ type AuthContextValue = {
|
||||
}) => Promise<void>;
|
||||
logout: () => void;
|
||||
getAuthHeaders: () => HeadersInit;
|
||||
|
||||
/**
|
||||
* Used for non-password auth flows (ex: magic link).
|
||||
* Persists session exactly like login/register.
|
||||
*/
|
||||
setSession: (
|
||||
token: string,
|
||||
user: {
|
||||
email: string;
|
||||
displayName: string | null;
|
||||
role: string;
|
||||
}
|
||||
) => void;
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
|
||||
@@ -42,6 +55,25 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<AuthUser>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
const persistAuth = useCallback(
|
||||
(nextToken: string | null, nextUser: AuthUser) => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
if (nextToken) {
|
||||
window.localStorage.setItem(TOKEN_KEY, nextToken);
|
||||
} else {
|
||||
window.localStorage.removeItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
if (nextUser) {
|
||||
window.localStorage.setItem(USER_KEY, JSON.stringify(nextUser));
|
||||
} else {
|
||||
window.localStorage.removeItem(USER_KEY);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
// Hydrate from localStorage on first load
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
@@ -49,15 +81,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const storedToken = window.localStorage.getItem(TOKEN_KEY);
|
||||
const storedUser = window.localStorage.getItem(USER_KEY);
|
||||
|
||||
if (storedToken) {
|
||||
setToken(storedToken);
|
||||
}
|
||||
if (storedToken) setToken(storedToken);
|
||||
|
||||
if (storedUser) {
|
||||
try {
|
||||
setUser(JSON.parse(storedUser));
|
||||
} catch {
|
||||
// bad JSON? wipe it
|
||||
window.localStorage.removeItem(USER_KEY);
|
||||
}
|
||||
}
|
||||
@@ -65,21 +94,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
const persistAuth = useCallback((nextToken: string | null, nextUser: AuthUser) => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
if (nextToken) {
|
||||
window.localStorage.setItem(TOKEN_KEY, nextToken);
|
||||
} else {
|
||||
window.localStorage.removeItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
if (nextUser) {
|
||||
window.localStorage.setItem(USER_KEY, JSON.stringify(nextUser));
|
||||
} else {
|
||||
window.localStorage.removeItem(USER_KEY);
|
||||
}
|
||||
}, []);
|
||||
const setSession = useCallback(
|
||||
(
|
||||
nextToken: string,
|
||||
nextUser: { email: string; displayName: string | null; role: string }
|
||||
) => {
|
||||
setToken(nextToken);
|
||||
setUser(nextUser);
|
||||
persistAuth(nextToken, nextUser);
|
||||
},
|
||||
[persistAuth]
|
||||
);
|
||||
|
||||
const login = useCallback(
|
||||
async ({ email, password }: { email: string; password: string }) => {
|
||||
@@ -98,7 +123,6 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
// Adjust these to match your backend response shape
|
||||
const nextToken: string = data.token ?? data.accessToken;
|
||||
const nextUser: AuthUser =
|
||||
data.user ??
|
||||
@@ -108,14 +132,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
role: data.role ?? "USER",
|
||||
} as AuthUser);
|
||||
|
||||
setToken(nextToken);
|
||||
setUser(nextUser);
|
||||
persistAuth(nextToken, nextUser);
|
||||
setSession(nextToken, nextUser as NonNullable<AuthUser>);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[persistAuth]
|
||||
[setSession]
|
||||
);
|
||||
|
||||
const register = useCallback(
|
||||
@@ -152,14 +174,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
role: data.role ?? "USER",
|
||||
} as AuthUser);
|
||||
|
||||
setToken(nextToken);
|
||||
setUser(nextUser);
|
||||
persistAuth(nextToken, nextUser);
|
||||
setSession(nextToken, nextUser as NonNullable<AuthUser>);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[persistAuth]
|
||||
[setSession]
|
||||
);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
@@ -180,12 +200,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
register,
|
||||
logout,
|
||||
getAuthHeaders,
|
||||
setSession, // ✅ important
|
||||
};
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
}
|
||||
|
||||
// 🔑 This is what your useApi hook imports
|
||||
export function useAuth(): AuthContextValue {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) {
|
||||
|
||||
Reference in New Issue
Block a user