registration and confirm email
This commit is contained in:
parent
b427c148af
commit
ed9fa887b3
@ -1,14 +1,14 @@
|
||||
import type {
|
||||
LoginPayload,
|
||||
LoginResponse,
|
||||
MeResponse,
|
||||
User,
|
||||
import {
|
||||
type RegistrationResponse,
|
||||
type LoginPayload,
|
||||
type LoginResponse,
|
||||
type MeResponse,
|
||||
type RegistrationPayload,
|
||||
type User,
|
||||
} from "../components/shared/types/AuthTypes";
|
||||
import axiosInstance from "./axiosInstance";
|
||||
|
||||
export const loginRequest = async (
|
||||
data: LoginPayload
|
||||
): Promise<LoginResponse> => {
|
||||
export const loginRequest = async (data: LoginPayload) => {
|
||||
const res = await axiosInstance.post<LoginResponse>("/api/auth/login", data);
|
||||
return res.data;
|
||||
};
|
||||
@ -17,3 +17,19 @@ export const fetchMe = async (): Promise<User> => {
|
||||
const res = await axiosInstance.get<MeResponse>("/api/auth/me");
|
||||
return res.data.user;
|
||||
};
|
||||
|
||||
export const registrationRequest = async (data: RegistrationPayload) => {
|
||||
const res = await axiosInstance.post<RegistrationResponse>(
|
||||
"/api/auth/register",
|
||||
data
|
||||
);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
export const activateAccount = async (token: string) => {
|
||||
const res = await axiosInstance.post<{ message: string }>(
|
||||
"/api/auth/activate-account",
|
||||
{ token }
|
||||
);
|
||||
return res.data;
|
||||
};
|
||||
|
||||
@ -14,7 +14,7 @@ import { useAuth } from "../../context/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
function Header() {
|
||||
const { user, logout } = useAuth();
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [anchorElNav, setAnchorElNav] = React.useState<null | HTMLElement>(
|
||||
|
||||
@ -22,3 +22,14 @@ export interface LoginPayload {
|
||||
export interface MeResponse {
|
||||
user: User;
|
||||
}
|
||||
|
||||
export interface RegistrationPayload {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
}
|
||||
|
||||
export interface RegistrationResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
@ -2,12 +2,17 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "react-toastify";
|
||||
import { useCurrentUser } from "../hooks/useCurrentUser";
|
||||
import { useLogin } from "../hooks/useLogin";
|
||||
import type { LoginPayload, User } from "../components/shared/types/AuthTypes";
|
||||
import { createContext, useContext, type ReactNode } from "react";
|
||||
import type {
|
||||
LoginPayload,
|
||||
LoginResponse,
|
||||
User,
|
||||
} from "../components/shared/types/AuthTypes";
|
||||
import { createContext, useContext } from "react";
|
||||
import type { OnlyChildrenProps } from "../components/shared/types/OnlyChildrenProps";
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
login: (data: LoginPayload) => Promise<void>;
|
||||
login: (data: LoginPayload) => Promise<LoginResponse>;
|
||||
logout: () => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
@ -20,24 +25,11 @@ export const useAuth = () => {
|
||||
return context;
|
||||
};
|
||||
|
||||
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
export const AuthProvider = ({ children }: OnlyChildrenProps) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: user, isLoading } = useCurrentUser();
|
||||
const loginMutation = useLogin();
|
||||
|
||||
const login = async (payload: LoginPayload) => {
|
||||
await loginMutation.mutateAsync(payload, {
|
||||
onSuccess: (data) => {
|
||||
localStorage.setItem("access_token", data.access_token);
|
||||
queryClient.setQueryData(["me"], data.user);
|
||||
toast.success(`Welcome ${data.user.username}`);
|
||||
},
|
||||
onError: (err: any) => {
|
||||
toast.error(err.response?.data?.message || "Login failed");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem("access_token");
|
||||
queryClient.removeQueries({ queryKey: ["me"] });
|
||||
@ -47,7 +39,12 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{ user: user ?? null, login, logout, isLoading }}
|
||||
value={{
|
||||
user: user ?? null,
|
||||
login: loginMutation.mutateAsync,
|
||||
logout,
|
||||
isLoading,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
|
||||
19
src/hooks/useActivateAccount.ts
Normal file
19
src/hooks/useActivateAccount.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { activateAccount } from "../api/authApi";
|
||||
import { toast } from "react-toastify";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const useActivateAccount = () => {
|
||||
const navigate = useNavigate();
|
||||
return useMutation({
|
||||
mutationFn: (token: string) => activateAccount(token),
|
||||
onSuccess: (data) => {
|
||||
toast.success(data.message);
|
||||
navigate("/login");
|
||||
},
|
||||
onError: (err: any) => {
|
||||
toast.error(err.response?.data?.message || "Activation failed");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import type { User } from "../components/shared/types/AuthTypes";
|
||||
import { fetchMe } from "../api/authApi";
|
||||
|
||||
export const useCurrentUser = () =>
|
||||
useQuery<User>({
|
||||
useQuery({
|
||||
queryKey: ["me"],
|
||||
queryFn: fetchMe,
|
||||
enabled: !!localStorage.getItem("access_token"),
|
||||
|
||||
@ -1,11 +1,22 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import type {
|
||||
LoginPayload,
|
||||
LoginResponse,
|
||||
} from "../components/shared/types/AuthTypes";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { loginRequest } from "../api/authApi";
|
||||
import { toast } from "react-toastify";
|
||||
import type { LoginPayload } from "../components/shared/types/AuthTypes";
|
||||
|
||||
export const useLogin = () =>
|
||||
useMutation<LoginResponse, any, LoginPayload>({
|
||||
mutationFn: loginRequest,
|
||||
export const useLogin = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (payload: LoginPayload) => loginRequest(payload),
|
||||
|
||||
onSuccess: (data) => {
|
||||
localStorage.setItem("access_token", data.access_token);
|
||||
queryClient.setQueryData(["me"], data.user);
|
||||
toast.success(`Welcome ${data.user.username}`);
|
||||
},
|
||||
|
||||
onError: (err: any) => {
|
||||
toast.error(err.response?.data?.message || "Login failed");
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
25
src/hooks/useRegistration.ts
Normal file
25
src/hooks/useRegistration.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { registrationRequest } from "../api/authApi";
|
||||
import type { RegistrationPayload } from "../components/shared/types/AuthTypes";
|
||||
import { toast } from "react-toastify";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
export const useRegistration = () => {
|
||||
const navigate = useNavigate();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
password_confirmation,
|
||||
}: RegistrationPayload) =>
|
||||
registrationRequest({ username, email, password, password_confirmation }),
|
||||
onSuccess: (data) => {
|
||||
toast.success(data.message);
|
||||
navigate("/login");
|
||||
},
|
||||
onError: (error: any) => {
|
||||
toast.error(error.response?.data?.message || "Registration failed");
|
||||
},
|
||||
});
|
||||
};
|
||||
26
src/pages/ActivateAccountPage/ActivateAccountPage.tsx
Normal file
26
src/pages/ActivateAccountPage/ActivateAccountPage.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useActivateAccount } from "../../hooks/useActivateAccount";
|
||||
import { CircularProgress, Box, Typography } from "@mui/material";
|
||||
|
||||
const ActivateAccountPage = () => {
|
||||
const { token } = useParams<{ token: string }>();
|
||||
const activateMutation = useActivateAccount();
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
activateMutation.mutate(token);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box sx={{ padding: 4, textAlign: "center" }}>
|
||||
{activateMutation.isPending ? (
|
||||
<CircularProgress />
|
||||
) : (
|
||||
<Typography variant="h5">Activating your account...</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActivateAccountPage;
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
import Container from "../../components/shared/Container";
|
||||
import { Box, Button, Paper, TextField, Typography } from "@mui/material";
|
||||
import { Box, Button, TextField, Typography } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const LoginPage = () => {
|
||||
|
||||
97
src/pages/RegistrationPage/RegistrationPage.tsx
Normal file
97
src/pages/RegistrationPage/RegistrationPage.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Container from "../../components/shared/Container";
|
||||
import { Box, Button, TextField, Typography } from "@mui/material";
|
||||
import { useRegistration } from "../../hooks/useRegistration";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
const RegistrationPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
|
||||
const registerMutation = useRegistration();
|
||||
|
||||
useEffect(() => {
|
||||
if (user) navigate("/");
|
||||
}, [user, navigate]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (password !== confirmPassword) {
|
||||
toast.error("Passwords do not match");
|
||||
return;
|
||||
}
|
||||
|
||||
registerMutation.mutate({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
password_confirmation: confirmPassword,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Box sx={{ padding: 4, maxWidth: 360, margin: "auto" }}>
|
||||
<Typography variant="h4" mb={3} textAlign="center">
|
||||
Register
|
||||
</Typography>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<TextField
|
||||
label="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Confirm Password"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
fullWidth
|
||||
sx={{ mt: 2 }}
|
||||
disabled={registerMutation.isPending}
|
||||
>
|
||||
{registerMutation.isPending ? "Registering..." : "Register"}
|
||||
</Button>
|
||||
</form>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegistrationPage;
|
||||
@ -4,6 +4,8 @@ import IndexPage from "../pages/IndexPage/IndexPage";
|
||||
import LoginPage from "../pages/LoginPage/LoginPage";
|
||||
import ProfilePage from "../pages/ProfilePage/Profilepage";
|
||||
import { ProtectedRoute } from "../components/ProtectedRoute/ProtectedRoute";
|
||||
import RegistrationPage from "../pages/RegistrationPage/RegistrationPage";
|
||||
import ActivateAccountPage from "../pages/ActivateAccountPage/ActivateAccountPage";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -26,6 +28,14 @@ const router = createBrowserRouter([
|
||||
</ProtectedRoute>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/register",
|
||||
element: <RegistrationPage />,
|
||||
},
|
||||
{
|
||||
path: "/auth/email-activation/:token",
|
||||
element: <ActivateAccountPage />,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user