Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
333 changes: 333 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@mui/icons-material": "^5.11.9",
"@mui/joy": "^5.0.0-alpha.67",
"@mui/material": "^5.11.9",
"axios": "^1.3.4",
"node-sass": "^8.0.0",
"@mui/icons-material": "^5.11.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.43.1",
"react-query": "^3.39.3",
"react-router": "^6.8.1",
"react-router-dom": "^6.8.1",
"react-toastify": "^9.1.1"
Expand All @@ -30,4 +32,4 @@
"typescript": "^4.6.4",
"vite": "^3.2.3"
}
}
}
25 changes: 16 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { Route, Routes } from "react-router";
import AuthForm from "./features/auth/components/AuthForm";
Expand All @@ -10,35 +10,41 @@ import NotFound from "./pages/NotFound";
import Profile from "./pages/Profile";
import Project from "./pages/Project";
import { AuthContext } from "./features/auth/context/AuthContext";
import { User } from "./types/User";
import CreateProject from "./pages/CreateProject";
import { ToastContainer } from "react-toastify";
import { usePersistentUser } from './features/auth/hooks/usePersistentUser';
import "react-toastify/dist/ReactToastify.css";

function App() {
const [isOpen, setIsOpen] = useState(false);
const [user, setUser] = useState< User | null>(null);
const { user, setUser } = usePersistentUser()

const closeModal = () => {
setIsOpen(false);
};

const openAuthModal = () => {
setIsOpen(true);
};

return (
<AuthContext.Provider value={{ user, setUser }}>
<Navbar />
<Navbar openAuthModal={openAuthModal} />
<Routes>
{/* Home Page */}
<Route path='/' element={<Home />} />
<Route path="/" element={<Home />} />

{/* Project Page */}
<Route path='/projects/:projectId' element={<Project />} />
<Route path="/projects/:projectId" element={<Project />} />

{/* Project Page */}
<Route path='/create-project' element={<CreateProject />} />
<Route path="/create-project" element={<CreateProject />} />

{/* Profile Page */}
<Route path='/profile/:userId' element={<Profile />} />
<Route path="/profile/:userId" element={<Profile />} />

{/* Not Found Page */}
<Route path='*' element={<NotFound />} />
<Route path="*" element={<NotFound />} />
</Routes>
<Footer />

Expand All @@ -50,6 +56,7 @@ function App() {
</Dialog>,
document.body
)}
<ToastContainer />
</AuthContext.Provider>
);
}
Expand Down
44 changes: 15 additions & 29 deletions src/features/auth/components/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,39 @@
import { useForm, SubmitHandler } from "react-hook-form";
import { toast, ToastContainer } from "react-toastify";
import { toast } from "react-toastify";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import { Box } from "@mui/system";
import { AuthContext } from "../context/AuthContext";
import { useContext, useState } from "react";
import { User } from "../../../types/User";
import "react-toastify/dist/ReactToastify.css";

interface ILoginFormInput {
import { useContext } from "react";
import { login } from "../services/auth";
import { useMutation } from "react-query";
interface ILoginForm {
email: string;
password: string;
}


function LoginForm() {
const { setUser } = useContext(AuthContext);
const {
register,
formState: { errors },
handleSubmit,
} = useForm<ILoginFormInput>({
} = useForm<ILoginForm>({
defaultValues: {
email: "",
password: "",
},
});

const onSubmit: SubmitHandler<ILoginFormInput> = async (data) => {
const userPromise = new Promise<User>((resolve, reject) => {
setTimeout(() => {
// resolve({
// id: 1,
// username: "John Doe",
// email: data.email,
// avatar: "https://i.pravatar.cc/150?img=1",
// description:
// "Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod.",
// projects: [],
// });
reject(new Error());
}, 2000);
});
const loginMutation = useMutation(({ email, password }: ILoginForm) =>
login(email, password)
);

const onSubmit: SubmitHandler<ILoginForm> = (data) => {
toast
.promise(
userPromise,
loginMutation.mutateAsync(data),
{
pending: "Logging in...",
success: "Logged in successfully",
Expand All @@ -62,9 +51,7 @@ function LoginForm() {
}
)
.then((user) => {
setTimeout(() => {
setUser(user);
}, 3000);
setUser(user);
});
};

Expand Down Expand Up @@ -93,10 +80,10 @@ function LoginForm() {
type="password"
{...register("password", {
required: true,
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/,
// pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,}$/,
})}
error={errors.password ? true : false}
helperText="Must be at least 8 characters(1 uppercase, 1 lowercase, 1 number)"
helperText="Must be at least 6 characters(1 uppercase, 1 lowercase, 1 number)"
sx={{ width: "100%" }}
/>
<Button
Expand All @@ -118,7 +105,6 @@ function LoginForm() {
</Button>
{/* TODO: Google Auth */}
</Box>
<ToastContainer />
</form>
);
}
Expand Down
46 changes: 39 additions & 7 deletions src/features/auth/components/RegisterForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ import { useForm, SubmitHandler } from "react-hook-form";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import { Box } from "@mui/system";

interface IRegisterFormInput {
import { toast } from "react-toastify";
import { register as registerApi } from "../services/auth";
import { useContext } from "react";
import { AuthContext } from "../context/AuthContext";
import { useMutation } from 'react-query';
interface IRegisterForm {
username: string;
email: string;
password: string;
passwordRepeat: string;
}

function RegisterForm() {
const { setUser } = useContext(AuthContext);
const {
register,
formState: { errors },
getValues,
handleSubmit,
} = useForm<IRegisterFormInput>({
} = useForm<IRegisterForm>({
defaultValues: {
username: "",
email: "",
Expand All @@ -24,8 +30,34 @@ function RegisterForm() {
},
});

const onSubmit: SubmitHandler<IRegisterFormInput> = (data) => {
console.log(data);
const registerMutation = useMutation(
({ username, email, password }: IRegisterForm) =>
registerApi(username, email, password)
);

const onSubmit: SubmitHandler<IRegisterForm> = (data) => {
toast
.promise(
registerMutation.mutateAsync(data),
{
pending: "Register....",
success: "Registered successfully",
error: "Wrong credentials",
},
{
position: "top-right",
autoClose: 3000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
theme: "light",
}
)
.then((user) => {
setUser(user);
});
};

return (
Expand Down Expand Up @@ -70,8 +102,8 @@ function RegisterForm() {
label="Password Repeat"
type="password"
{...register("passwordRepeat", { required: true })}
error={errors.passwordRepeat ? true : false}
helperText="Must be at least 8 characters"
error={getValues("password") !== getValues("passwordRepeat")}
helperText="Must be the same as password"
sx={{ width: "100%" }}
/>
<Button
Expand Down
2 changes: 1 addition & 1 deletion src/features/auth/context/AuthContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { User } from "../../../types/User";

export interface AuthContextProps {
user: User | null;
setUser: (user: User) => void;
setUser: (user: User | null) => void;
}

export const AuthContext = createContext<AuthContextProps>({
Expand Down
24 changes: 24 additions & 0 deletions src/features/auth/hooks/usePersistentUser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useEffect, useState } from "react";
import { User } from "../../../types/User";

export const usePersistentUser = () => {
const [user, setUser] = useState<User | null>(null);

useEffect(() => {
const user = localStorage.getItem("user");
if (user) {
setUser(JSON.parse(user));
}
}, []);

const setPersistentUser = (user: User | null) => {
if (user) {
localStorage.setItem("user", JSON.stringify(user));
} else {
localStorage.removeItem("user");
}
setUser(user);
};

return { user, setUser: setPersistentUser };
};
21 changes: 21 additions & 0 deletions src/features/auth/services/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import axios from "axios";
import { User } from "../../../types/User";

export const login = (email: string, password: string): Promise<User> => {
return axios.post("/api/v1/user/login", {
email,
password,
});
};

export const register = (
username: string,
email: string,
password: string
): Promise<User> => {
return axios.post("/api/v1/user/new", {
email,
username,
password,
});
};
Loading