npx create-react-app codercom --template redux
- install libraries
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/icons-material @mui/lab
npm install react-router-dom@6 react-hook-form @hookform/resolvers yup
npm install axios numeral lodash jwt-decode change-case
npm install react-markdown rehype-raw date-fns react-dropzone react-toastify
-
clear src recreate src folder
-
create env file outside of src folder
REACT_APP_BACKEND_API =" "
- create src/config.js
export const BASE_URL = process.env.REACT_APP_BACKEND_API;
-create app/apiService.js
import axios from "axios";
import { BASE_URL } from "./config";
const apiService = axios.create({
baseURL: BASE_URL,
});
apiService.interceptors.request.use(
(request) => {
console.log("request", request);
return request;
},
(error) => {
console.log("error", error);
return Promise.reject(error);
}
);
apiService.interceptors.response.use(
(response) => {
console.log("response", response);
return response;
},
(error) => {
console.log("error", error);
return Promise.reject(error);
}
);
export default apiService;
- create src/components/form/FormProvider.js
import { FormProvider as RHFormProvider } from "react-hook-form";
function FormProvider({ children, onSubmit, methods }) {
return (
<RHFormProvider {...methods}>
<form onSubmit={onSubmit}>{children}</form>
</RHFormProvider>
);
}
export default FormProvider;
- create src/components/form/FTextField.js
import { TextField } from "@mui/material";
function FTextField({ name, ...other }) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState: { error } }) => (
<TextField
{...field}
fullWidth
error={!!error}
helperText={error?.message}
{...other}
/>
)}
/>
);
}
export default FTextField;
- create src/components/form/FSwitch.js
import { useFormContext, Controller } from "react-hook-form";
import { Switch, FormControlLabel } from "@mui/material";
function FSwitch({ name, ...other }) {
const { control } = useFormContext();
return (
<FormControlLabel
control={
<Controller
name={name}
control={control}
render={({ field }) => <Switch {...field} checked={field.value} />}
/>
}
{...other}
/>
);
}
export default FSwitch;
- create src/components/form/FSelect.js
import { useFormContext, Controller } from "react-hook-form";
import { TextField } from "@mui/material";
function FSelect({ name, children, ...other }) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState: { error } }) => (
<TextField
{...field}
select
fullWidth
SelectProps={{ native: true }}
error={!!error}
helperText={error?.message}
{...other}
>
{children}
</TextField>
)}
/>
);
}
export default FSelect;
- create src/components/form/FRadioGroup.js
import { useFormContext, Controller } from "react-hook-form";
import {
Radio,
RadioGroup,
FormHelperText,
FormControlLabel,
} from "@mui/material";
function FRadioGroup({ name, options, getOptionLabel, ...other }) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field, fieldState: { error } }) => (
<div>
<RadioGroup {...field} row {...other}>
{options.map((option, index) => (
<FormControlLabel
key={option}
value={option}
control={<Radio />}
label={getOptionLabel?.length ? getOptionLabel[index] : option}
/>
))}
</RadioGroup>
{!!error && (
<FormHelperText error sx={{ px: 2 }}>
{error.message}
</FormHelperText>
)}
</div>
)}
/>
);
}
export default FRadioGroup;
- create src/components/form/FMultiCheckbox.js
import { useFormContext, Controller } from "react-hook-form";
import { Checkbox, FormGroup, FormControlLabel } from "@mui/material";
function FMultiCheckbox({ name, options, ...other }) {
const { control } = useFormContext();
return (
<Controller
name={name}
control={control}
render={({ field }) => {
const onSelected = (option) =>
field.value.includes(option)
? field.value.filter((value) => value !== option)
: [...field.value, option];
return (
<FormGroup>
{options.map((option) => (
<FormControlLabel
key={option}
control={
<Checkbox
checked={field.value.includes(option)}
onChange={() => field.onChange(onSelected(option))}
/>
}
label={option}
{...other}
/>
))}
</FormGroup>
);
}}
/>
);
}
export default FMultiCheckbox;
- create src/components/form/FCheckbox.js
import { useFormContext, Controller } from "react-hook-form";
import { Checkbox, FormControlLabel } from "@mui/material";
function FCheckbox({ name, ...other }) {
const { control } = useFormContext();
return (
<FormControlLabel
control={
<Controller
name={name}
control={control}
render={({ field }) => <Checkbox {...field} checked={field.value} />}
/>
}
{...other}
/>
);
}
export default FCheckbox;
- create utils/numberFormat.js
import numeral from "numeral";
export function fCurrency(number) {
return numeral(number).format(Number.isInteger(number) ? "$0,0" : "$0,0.00");
}
export function fPercent(number) {
return numeral(number / 100).format("0.0%");
}
export function fNumber(number) {
return numeral(number).format();
}
export function fShortenNumber(number) {
return numeral(number).format("0.00a").replace(".00", "");
}
export function fData(number) {
return numeral(number).format("0.0 b");
}```
- create utils/formatTime.js
import { format, getTime, formatDistanceToNow } from "date-fns";
export function fDate(date) {
return format(new Date(date), "dd MMMM yyyy");
}
export function fDateTime(date) {
return format(new Date(date), "dd MMM yyyy HH:mm");
}
export function fTimestamp(date) {
return getTime(new Date(date));
}
export function fDateTimeSuffix(date) {
return format(new Date(date), "dd/MM/yyyy hh:mm p");
}
export function fToNow(date) {
return formatDistanceToNow(new Date(date), {
addSuffix: true,
});
}
- create utils/jwt.js
import jwtDecode from 'jwt-decode';
export const isValidToken = (accessToken) => {
if (!accessToken) {
return false;
}
const decoded = jwtDecode(accessToken);
const currentTime = Date.now() / 1000;
return decoded.exp > currentTime;
};```
- create simple pages/....
import React from "react";
function HomePage() {
return (
<div>
<h1>HomePage</h1>
</div>
);
}
export default HomePage;
create simple page like this to check the router feature
- create layouts/...
BlankLayouts
- create routes/index.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import MainLayout from "../layouts/MainLayout";
import HomePage from "../pages/HomePage";
import AccountPage from "../pages/AccountPage";
import UserProfilePage from "../pages/UserProfilePage";
import NotFoundPage from "../pages/NotFoundPage";
import LoginPage from "../pages/LoginPage";
import RegisterPage from "../pages/RegisterPage";
function Router() {
return (
<Routes>
<Route path="/" element={<MainLayout />}>
<Route index element={<HomePage />} />
<Route path="account" element={<AccountPage />} />
<Route path="user/:userid" element={<UserProfilePage />} />
</Route>
{/* userProfilePage hien thi profile cua nguoi khac */}
<Route>
<Route path="*" element={<NotFoundPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
</Route>
</Routes>
);
}
export default Router;
- change code in App.js
import React from "react";
import Router from "./routes";
import { BrowserRouter } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Router />
</BrowserRouter>
);
}
export default App;
- create Auth
- we have api docs at link
https://app.swaggerhub.com/apis-docs/dhminh1024/CoderComm/1.0.0#/
- create src/context/AuthContext.js
import { createContext, useReducer, useEffect } from "react";
import apiService from "../app/apiService";
import { isValidToken } from "../utils/jwt";
const initialState = {
isInitialized: false,
isAuthenticated: false,
user: null,
};
const INITIALIZE = "AUTH.INITIALIZE";
const LOGIN_SUCCESS = "AUTH.LOGIN_SUCCESS";
const REGISTER_SUCCESS = "AUTH.REGISTER_SUCCESS";
const LOGOUT = "AUTH.LOGOUT";
const UPDATE_PROFILE = "AUTH.UPDATE_PROFILE";
const reducer = (state, action) => {
switch (action.type) {
case INITIALIZE:
return { ...state, isInitialized: true };
case LOGIN_SUCCESS:
return {
...state,
isAuthenticated: true,
user: action.payload.user,
};
default:
return state;
}
};
const AuthContext = createContext({ initialState });
const setSession = (accessToken) => {
if (accessToken) {
window.localStorage.setItem("accessToken", accessToken);
apiService.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
} else {
window.localStorage.removeItem("accessToken");
delete apiService.defaults.headers.common.Authorization;
}
};
function AuthProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
const login = async ({ email, password }, callback) => {
const response = await apiService.post("/auth/login", { email, password });
const { user, accessToken } = response.data;
setSession(accessToken);
dispatch({
type: LOGIN_SUCCESS,
payload: { user },
});
};
return (
<AuthContext.Provider value={{ ...state, login }}>
{children}
</AuthContext.Provider>
);
}
export { AuthProvider, AuthContext };
- create custom hook useAuth in src/hooks/useAuth.js
import React, { useContext } from "react";
import { AuthContext } from "../contexts/AuthContext";
function useAuth() {
return useContext(AuthContext);
}
export default useAuth;
- change LoginPage
import React, { useState } from "react";
import { FCheckbox, FormProvider, FTextField } from "../components/form";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";
import { useLocation, useNavigate, Link as RouterLink } from "react-router-dom";
import useAuth from "../hooks/useAuth";
import {
Alert,
Container,
Stack,
Link,
InputAdornment,
IconButton,
} from "@mui/material";
import VisibilityIcon from "@mui/icons-material/Visibility";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import { LoadingButton } from "@mui/lab";
const LoginSchema = Yup.object().shape({
email: Yup.string().email("Invalid Email").required("Email is required"),
password: Yup.string().required("Password is required"),
});
const defaultValues = {
email: "",
password: "",
remember: true,
};
function LoginPage() {
const auth = useAuth();
const methods = useForm({
resolver: yupResolver(LoginSchema),
defaultValues,
});
const {
handleSubmit,
reset,
setError,
formState: { errors, isSubmitting },
} = methods;
const navigate = useNavigate();
const location = useLocation();
const [showPassword, setShowPassword] = useState(false);
const onSubmit = async (data) => {
const from = location.state?.from?.pathname || "/";
const { email, password } = data;
try {
await auth.login({ email, password }, () => {
navigate(from, { replace: true });
});
} catch (error) {
reset();
setError("responseError", error);
}
};
return (
<Container maxWidth="xs">
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={5}>
{!!errors.responseError && (
<Alert severity="error">{errors.responseError.message}</Alert>
)}
<Alert severity="info">
Dont have an account?
<Link variant="subtitle2" component={RouterLink} to="/register">
Get Started
</Link>
</Alert>
<FTextField name="email" label="Email address" />
<FTextField
name="password"
label="Password"
type={showPassword ? "text" : "password"}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
>
{showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
</InputAdornment>
),
}}
/>
</Stack>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
sx={{ my: 2 }}
>
<FCheckbox name="remember" label="Remember me" />
<Link component={RouterLink} variant="subtitle2" to="/">
Forgor password
</Link>
</Stack>
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
>
Login
</LoadingButton>
</FormProvider>
</Container>
);
}
export default LoginPage;
- import AuthProvider to App.js
import React from "react";
import Router from "./routes";
import { BrowserRouter } from "react-router-dom";
import { AuthProvider } from "./contexts/AuthContext";
function App() {
return (
<AuthProvider>
<BrowserRouter>
<Router />
</BrowserRouter>
</AuthProvider>
);
}
export default App;
-create AuthRequire in routes
import useAuth from "../hooks/useAuth";
import { Navigate } from "react-router-dom";
import { useLocation } from "react-router-dom";
function AuthRequire({ children }) {
const { isAuthenticated } = useAuth();
const location = useLocation();
if (!isAuthenticated)
return <Navigate to="login" state={{ from: location }} replace />;
// save the current location for later navigating after authentication
return children;
}
export default AuthRequire;
- update contexts/Authcontext.js
import { createContext, useReducer, useEffect } from "react";
import apiService from "../app/apiService";
import { isValidToken } from "../utils/jwt";
const initialState = {
isInitialized: false,
isAuthenticated: false,
user: null,
};
const INITIALIZE = "AUTH.INITIALIZE";
const LOGIN_SUCCESS = "AUTH.LOGIN_SUCCESS";
const REGISTER_SUCCESS = "AUTH.REGISTER_SUCCESS";
const LOGOUT = "AUTH.LOGOUT";
const UPDATE_PROFILE = "AUTH.UPDATE_PROFILE";
const reducer = (state, action) => {
switch (action.type) {
case INITIALIZE:
const { isAuthenticated, user } = action.payload;
return { ...state, isInitialized: true, isAuthenticated, user };
case LOGIN_SUCCESS:
return {
...state,
isAuthenticated: true,
user: action.payload.user,
};
case REGISTER_SUCCESS:
return { ...state, isAuthenticated: true, user: action.payload.user };
case LOGOUT:
return { ...state, isAuthenticated: false, user: null };
default:
return state;
}
};
const setSession = (accessToken) => {
if (accessToken) {
window.localStorage.setItem("accessToken", accessToken);
apiService.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
} else {
window.localStorage.removeItem("accessToken");
delete apiService.defaults.headers.common.Authorization;
}
};
const AuthContext = createContext({ ...initialState });
function AuthProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
// const updatedProfile = useSelector((state) => state.user.updatedProfile);
useEffect(() => {
const initialize = async () => {
try {
const accessToken = window.localStorage.getItem("accessToken");
if (accessToken && isValidToken(accessToken)) {
setSession(accessToken);
const response = await apiService.get("/users/me");
const user = response.data.data;
dispatch({
type: INITIALIZE,
payload: { isAuthenticated: true, user },
});
} else {
setSession(null);
dispatch({
type: INITIALIZE,
payload: { isAuthenticated: false, user: null },
});
}
} catch (err) {
console.error(err);
setSession(null);
dispatch({
type: INITIALIZE,
payload: {
isAuthenticated: false,
user: null,
},
});
}
};
initialize();
}, []);
// useEffect(() => {
// if (updatedProfile)
// dispatch({ type: UPDATE_PROFILE, payload: updatedProfile });
// }, [updatedProfile]);
const login = async ({ email, password }, callback) => {
const response = await apiService.post("/auth/login", { email, password });
const { user, accessToken } = response.data.data;
setSession(accessToken);
dispatch({
type: LOGIN_SUCCESS,
payload: { user },
});
callback();
};
const register = async ({ name, email, password }, callback) => {
const response = await apiService.post("/users", {
name,
email,
password,
});
const { user, accessToken } = response.data.data;
setSession(accessToken);
dispatch({
type: REGISTER_SUCCESS,
payload: { user },
});
callback();
};
const logout = async (callback) => {
setSession(null);
dispatch({ type: LOGOUT });
callback();
};
return (
<AuthContext.Provider
value={{
...state,
login,
register,
logout,
}}
>
{children}
</AuthContext.Provider>
);
}
export { AuthContext, AuthProvider };
- build pages/RegisterPage.js
import React, { useState } from "react";
import useAuth from "../hooks/useAuth";
import { FormProvider, FTextField } from "../components/form";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as Yup from "yup";
import { useNavigate } from "react-router-dom";
import {
Alert,
Container,
Link,
Stack,
InputAdornment,
IconButton,
} from "@mui/material";
import { Link as RouterLink } from "react-router-dom";
import VisibilityIcon from "@mui/icons-material/Visibility";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import { LoadingButton } from "@mui/lab";
const RegisterSchema = Yup.object().shape({
name: Yup.string().required("Name is required"),
email: Yup.string().email("Invalid email").required("Email is required"),
password: Yup.string().required("Password is required"),
passwordConfirmation: Yup.string()
.required("Please confirm your password")
.oneOf([Yup.ref("password")], "passwords must match"),
});
const defaultValues = {
name: "",
email: "",
password: "",
passwordConfirmation: "",
};
function RegisterPage() {
const auth = useAuth();
const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false);
const [showPasswordConfirmation, setShowPasswordConfirmation] =
useState(false);
const methods = useForm({
resolver: yupResolver(RegisterSchema),
defaultValues,
});
const {
handleSubmit,
reset,
setError,
formState: { errors, isSubmitting },
} = methods;
const onSubmit = async (data) => {
const { name, email, password } = data;
try {
await auth.register({ name, email, password }, () => {
navigate("/", { replace: true });
});
} catch (error) {
reset();
setError("responseError", error);
}
};
return (
<Container maxWidth="xs">
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}>
{!!errors.responseError && (
<Alert severity="error">{errors.responseError.message}</Alert>
)}
<Alert severity="info">
Already have an account?
<Link component={RouterLink} variant="subtitle2" to="/login">
Sign in
</Link>
</Alert>
<FTextField name="name" label="Full name" />
<FTextField name="email" label="Email address" />
<FTextField
name="password"
label="Password"
type={showPassword ? "text" : "password"}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
>
{showPassword ? <VisibilityIcon /> : <VisibilityOffIcon />}
</IconButton>
</InputAdornment>
),
}}
/>
<FTextField
name="passwordConfirmation"
label="Password Confirmation"
type={showPasswordConfirmation ? "text" : "password"}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() =>
setShowPasswordConfirmation(!showPasswordConfirmation)
}
edge="end"
>
{showPasswordConfirmation ? (
<VisibilityIcon />
) : (
<VisibilityOffIcon />
)}
</IconButton>
</InputAdornment>
),
}}
/>
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
>
Login
</LoadingButton>
</Stack>
</FormProvider>
</Container>
);
}
export default RegisterPage;
- create components/LoadingScreen.js
import { Box, CircularProgress } from "@mui/material";
import React from "react";
function LoadingScreen() {
return (
<Box
sx={{
position: "absolute",
width: "100%",
height: "100%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<CircularProgress />
</Box>
);
}
export default LoadingScreen;
- update AuthRequire.js
import React from "react";
import useAuth from "../hooks/useAuth";
import { Navigate } from "react-router-dom";
import { useLocation } from "react-router-dom";
import LoadingScreen from "../components/LoadingScreen";
function AuthRequire({ children }) {
const { isAuthenticated, isInitialized } = useAuth();
const location = useLocation();
if (!isInitialized) return <LoadingScreen />;
if (!isAuthenticated)
return <Navigate to="login" state={{ from: location }} replace />;
// save the current location for later navigating after authentication
return children;
}
export default AuthRequire;
- persistent Login => after refesh page we need to generate initialization
- update Authcontext
import { createContext, useReducer, useEffect } from "react";
import apiService from "../app/apiService";
import { isValidToken } from "../utils/jwt";
const initialState = {
isInitialized: false,
isAuthenticated: false,
user: null,
};
const INITIALIZE = "AUTH.INITIALIZE";
const LOGIN_SUCCESS = "AUTH.LOGIN_SUCCESS";
const REGISTER_SUCCESS = "AUTH.REGISTER_SUCCESS";
const LOGOUT = "AUTH.LOGOUT";
const UPDATE_PROFILE = "AUTH.UPDATE_PROFILE";
const reducer = (state, action) => {
switch (action.type) {
case INITIALIZE:
const { isAuthenticated, user } = action.payload;
return { ...state, isInitialized: true, isAuthenticated, user };
case LOGIN_SUCCESS:
return {
...state,
isAuthenticated: true,
user: action.payload.user,
};
case REGISTER_SUCCESS:
return { ...state, isAuthenticated: true, user: action.payload.user };
case LOGOUT:
return { ...state, isAuthenticated: false, user: null };
default:
return state;
}
};
const setSession = (accessToken) => {
if (accessToken) {
window.localStorage.setItem("accessToken", accessToken);
apiService.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
} else {
window.localStorage.removeItem("accessToken");
delete apiService.defaults.headers.common.Authorization;
}
};
const AuthContext = createContext({ ...initialState });
function AuthProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const initialize = async () => {
try {
const accessToken = window.localStorage.getItem("accessToken");
// check the accesstoken is still valid- this access token is JWT token
if (accessToken && isValidToken(accessToken)) {
setSession(accessToken);
const response = apiService.get("/users/me");
const user = response.data;
dispatch({
type: INITIALIZE,
payload: { isAuthenticated: true, user },
});
} else {
setSession(null);
dispatch({
type: INITIALIZE,
payload: { isAuthenticated: false, user: null },
});
}
} catch (error) {
setSession(null);
dispatch({
type: INITIALIZE,
payload: { isAuthenticated: false, user: null },
});
}
};
initialize();
}, []);
const login = async ({ email, password }, callback) => {
const response = await apiService.post("/auth/login", { email, password });
const { user, accessToken } = response.data.data;
setSession(accessToken);
dispatch({
type: LOGIN_SUCCESS,
payload: { user },
});
callback();
};
const register = async ({ name, email, password }, callback) => {
const response = await apiService.post("/users", {
name,
email,
password,
});
const { user, accessToken } = response.data.data;
setSession(accessToken);
dispatch({
type: REGISTER_SUCCESS,
payload: { user },
});
callback();
};
const logout = async (callback) => {
setSession(null);
dispatch({ type: LOGOUT });
callback();
};
return (
<AuthContext.Provider value={{ ...state, login, register, logout }}>
{children}
</AuthContext.Provider>
);
}
export { AuthProvider, AuthContext };
- build MainHeader.js
import * as React from "react";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import MenuItem from "@mui/material/MenuItem";
import Menu from "@mui/material/Menu";
import Logo from "../components/Logo";
import { Avatar, Divider } from "@mui/material";
import useAuth from "../hooks/useAuth";
import { useNavigate, Link as RouterLink } from "react-router-dom";
function MainHeader() {
const { user, logout } = useAuth();
const navigate = useNavigate();
const [anchorEl, setAnchorEl] = React.useState(null);
const isMenuOpen = Boolean(anchorEl);
const handleProfileMenuOpen = (event) => {
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const handleLogout = async () => {
try {
handleMenuClose();
await logout(() => {
navigate("/login");
});
} catch (error) {
console.error(error);
}
};
const menuId = "primary-search-account-menu";
const renderMenu = (
<Menu
anchorEl={anchorEl}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
id={menuId}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
open={isMenuOpen}
onClose={handleMenuClose}
>
<Box sx={{ my: 1.5, px: 2.5 }}>
<Typography variant="subtitle2" noWrap>
{user?.name}
</Typography>
<Typography variant="body2" sx={{ color: "text.secondary" }} noWrap>
{user?.email}
</Typography>
</Box>
<Divider sx={{ borderStyle: "dashed" }} />
<MenuItem
onClick={handleMenuClose}
to="/"
component={RouterLink}
sx={{ mx: 1 }}
>
My Profile
</MenuItem>
<MenuItem
onClick={handleMenuClose}
to="/account"
component={RouterLink}
sx={{ mx: 1 }}
>
Account Settings
</MenuItem>
<Divider sx={{ borderStyle: "dashed" }} />
<MenuItem onClick={handleLogout} sx={{ m: 1 }}>
Logout
</MenuItem>
</Menu>
);
return (
<Box sx={{ mb: 3 }}>
<AppBar position="static" color="transparent">
<Toolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="open drawer"
sx={{ mr: 2 }}
>
<Logo />
</IconButton>
<Typography
variant="h6"
noWrap
component="div"
sx={{ display: { xs: "none", sm: "block" } }}
>
CoderComm
</Typography>
<Box sx={{ flexGrow: 1 }} />
<Box>
<Avatar
onClick={handleProfileMenuOpen}
src={user.avatarUrl}
alt={user.name}
sx={{ width: 32, height: 32 }}
/>
</Box>
</Toolbar>
</AppBar>
{renderMenu}
</Box>
);
}
export default MainHeader;
- build MainFooter.js
import React from "react";
import { Link, Typography } from "@mui/material";
function MainFooter() {
return (
<Typography variant="body2" color="text.secondary" align="center" p={1}>
{"Copyright © "}
<Link color="inherit" href="https://www.coderschool.vn">
MyCV
</Link>{" "}
{new Date().getFullYear()}
{"."}
</Typography>
);
}
export default MainFooter;
-create features/user/Profile.js
- create HomePage.js
import React, { useState } from "react";
import useAuth from "../hooks/useAuth";
import AccountBoxIcon from "@mui/icons-material/AccountBox";
import ContactMailIcon from "@mui/icons-material/ContactMail";
import PersonAddRoundedIcon from "@mui/icons-material/PersonAddRounded";
import PeopleAltIcon from "@mui/icons-material/PeopleAlt";
import Profile from "../features/user/Profile";
import FriendRequests from "../features/friend/FriendRequests";
import FriendList from "../features/friend/FriendList";
import AddFriend from "../features/friend/AddFriend";
import { Box, Card, Container, Tab, Tabs } from "@mui/material";
import { capitalCase } from "change-case"; //this library for solving probmlem with string
import ProfileCover from "../features/user/ProfileCover";
import { styled } from "@mui/material/styles";
const TabsWrapperStyle = styled("div")(({ theme }) => ({
zIndex: 9,
bottom: 0,
width: "100%",
display: "flex",
position: "absolute",
backgroundColor: "#fff",
[theme.breakpoints.up("sm")]: { justifyContent: "center" },
[theme.breakpoints.up("md")]: {
justifyContent: "flex-end",
paddingRight: theme.spacing(3),
},
}));
function HomePage() {
const { user } = useAuth();
const [currentTab, setCurrentTab] = useState("profile");
const handleChangeTab = (newValue) => {
setCurrentTab(newValue);
};
const PROFILE_TABS = [
{
value: "profile",
icon: <AccountBoxIcon sx={{ fontSize: 24 }} />,
component: <Profile profile={user} />,
},
{
value: "friends",
icon: <PeopleAltIcon sx={{ fontSize: 24 }} />,
component: <FriendList />,
},
{
value: "requests",
icon: <ContactMailIcon sx={{ fontSize: 24 }} />,
component: <FriendRequests />,
},
{
value: "add_friend",
icon: <PersonAddRoundedIcon sx={{ fontSize: 24 }} />,
component: <AddFriend />,
},
];
return (
<Container>
<Card sx={{ mb: 3, height: 280, position: "relative" }}>
<ProfileCover profile={user} />
<TabsWrapperStyle>
<Tabs
value={currentTab}
scrollButtons="auto"
variant="scrollable"
allowScrollButtonsMobile
onChange={(e, value) => handleChangeTab(value)}
>
{PROFILE_TABS.map((tab) => (
<Tab
disableRipple
key={tab.value}
value={tab.value}
icon={tab.icon}
label={capitalCase(tab.value)}
/>
))}
</Tabs>
</TabsWrapperStyle>
</Card>
{PROFILE_TABS.map((tab) => {
const isMatched = tab.value === currentTab;
return isMatched && <Box key={tab.value}>{tab.component}</Box>;
})}
</Container>
);
}
export default HomePage;