130 lines
3.4 KiB
TypeScript
130 lines
3.4 KiB
TypeScript
import React, { createContext, ReactNode, useContext } from "react";
|
|
import {
|
|
useQuery,
|
|
useMutation,
|
|
UseMutationResult,
|
|
} from "@tanstack/react-query";
|
|
import { apiRequest, getQueryFn, queryClient } from "@/lib/queryClient";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { z } from "zod";
|
|
import { insertUserSchema, User, InsertUser, Login } from "@/lib/schema";
|
|
import { Loader2 } from "lucide-react";
|
|
|
|
type AuthContextType = {
|
|
user: User | null;
|
|
isLoading: boolean;
|
|
error: Error | null;
|
|
loginMutation: UseMutationResult<User, Error, Login>;
|
|
logoutMutation: UseMutationResult<void, Error, void>;
|
|
registerMutation: UseMutationResult<User, Error, InsertUser>;
|
|
};
|
|
|
|
export const registrationSchema = insertUserSchema.extend({
|
|
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
confirmPassword: z.string(),
|
|
}).refine((data) => data.password === data.confirmPassword, {
|
|
message: "Passwords do not match",
|
|
path: ["confirmPassword"],
|
|
});
|
|
|
|
export type RegistrationData = z.infer<typeof registrationSchema>;
|
|
|
|
export const AuthContext = createContext<AuthContextType | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const { toast } = useToast();
|
|
const {
|
|
data: user,
|
|
error,
|
|
isLoading,
|
|
} = useQuery<User | undefined, Error>({
|
|
queryKey: ["/api/user"],
|
|
queryFn: getQueryFn({ on401: "returnNull" }),
|
|
});
|
|
|
|
const loginMutation = useMutation({
|
|
mutationFn: async (credentials: Login) => {
|
|
const res = await apiRequest("POST", "/api/login", credentials);
|
|
return await res.json();
|
|
},
|
|
onSuccess: (user: User) => {
|
|
queryClient.setQueryData(["/api/user"], user);
|
|
toast({
|
|
title: "Login successful",
|
|
description: `Welcome back, ${user.name}!`,
|
|
});
|
|
},
|
|
onError: (error: Error) => {
|
|
toast({
|
|
title: "Login failed",
|
|
description: error.message,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const registerMutation = useMutation({
|
|
mutationFn: async (data: InsertUser) => {
|
|
const res = await apiRequest("POST", "/api/register", data);
|
|
return await res.json();
|
|
},
|
|
onSuccess: (user: User) => {
|
|
queryClient.setQueryData(["/api/user"], user);
|
|
toast({
|
|
title: "Registration successful",
|
|
description: "Your account has been created successfully.",
|
|
});
|
|
},
|
|
onError: (error: Error) => {
|
|
toast({
|
|
title: "Registration failed",
|
|
description: error.message,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const logoutMutation = useMutation({
|
|
mutationFn: async () => {
|
|
await apiRequest("POST", "/api/logout");
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.setQueryData(["/api/user"], null);
|
|
toast({
|
|
title: "Logged out",
|
|
description: "You have been logged out successfully.",
|
|
});
|
|
},
|
|
onError: (error: Error) => {
|
|
toast({
|
|
title: "Logout failed",
|
|
description: error.message,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
user: user ?? null,
|
|
isLoading,
|
|
error,
|
|
loginMutation,
|
|
logoutMutation,
|
|
registerMutation,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error("useAuth must be used within an AuthProvider");
|
|
}
|
|
return context;
|
|
}
|