committed by
GitHub
33 changed files with 472 additions and 313 deletions
@ -1,42 +0,0 @@ |
|||
#root { |
|||
max-width: 1280px; |
|||
margin: 0 auto; |
|||
padding: 2rem; |
|||
text-align: center; |
|||
} |
|||
|
|||
.logo { |
|||
height: 6em; |
|||
padding: 1.5em; |
|||
will-change: filter; |
|||
transition: filter 300ms; |
|||
} |
|||
.logo:hover { |
|||
filter: drop-shadow(0 0 2em #646cffaa); |
|||
} |
|||
.logo.react:hover { |
|||
filter: drop-shadow(0 0 2em #61dafbaa); |
|||
} |
|||
|
|||
@keyframes logo-spin { |
|||
from { |
|||
transform: rotate(0deg); |
|||
} |
|||
to { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
|||
|
|||
@media (prefers-reduced-motion: no-preference) { |
|||
a:nth-of-type(2) .logo { |
|||
animation: logo-spin infinite 20s linear; |
|||
} |
|||
} |
|||
|
|||
.card { |
|||
padding: 2em; |
|||
} |
|||
|
|||
.read-the-docs { |
|||
color: #888; |
|||
} |
@ -1,61 +0,0 @@ |
|||
import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; |
|||
|
|||
import { ChakraProvider, extendTheme } from '@chakra-ui/react'; |
|||
|
|||
import Layout from './pages/Layout'; |
|||
import NotFound from './pages/NotFound'; |
|||
import Login from './pages/auth/Login'; |
|||
import RecoverPassword from './pages/auth/RecoverPassword'; |
|||
import Admin from './pages/main/Admin'; |
|||
import Dashboard from './pages/main/Dashboard'; |
|||
import Items from './pages/main/Items'; |
|||
import Profile from './pages/main/Profile'; |
|||
|
|||
// Theme
|
|||
const theme = extendTheme({ |
|||
colors: { |
|||
ui: { |
|||
main: "#009688", |
|||
secondary: "#EDF2F7", |
|||
success: '#48BB78', |
|||
danger: '#E53E3E', |
|||
} |
|||
}, |
|||
components: { |
|||
Tabs: { |
|||
variants: { |
|||
enclosed: { |
|||
tab: { |
|||
_selected: { |
|||
color: 'ui.main', |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
function App() { |
|||
return ( |
|||
<> |
|||
<Router> |
|||
<ChakraProvider theme={theme}> |
|||
<Routes> |
|||
<Route path="/login" element={<Login />} /> |
|||
<Route path="/recover-password" element={<RecoverPassword />} /> |
|||
<Route element={<Layout />}> |
|||
<Route path="/" element={<Dashboard />} /> |
|||
<Route path="/settings" element={<Profile />} /> |
|||
<Route path="/items" element={<Items />} /> |
|||
<Route path="/admin" element={<Admin />} /> |
|||
</Route> |
|||
<Route path="*" element={<NotFound />} /> |
|||
</Routes> |
|||
</ ChakraProvider> |
|||
</Router> |
|||
</> |
|||
) |
|||
} |
|||
|
|||
export default App |
Before Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 4.9 KiB |
@ -1,29 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Avatar, Flex, Skeleton, Text } from '@chakra-ui/react'; |
|||
import { FaUserAstronaut } from 'react-icons/fa'; |
|||
|
|||
import { useUserStore } from '../store/user-store'; |
|||
|
|||
|
|||
const UserInfo: React.FC = () => { |
|||
const { user } = useUserStore(); |
|||
|
|||
|
|||
return ( |
|||
<> |
|||
{user ? ( |
|||
<Flex gap={2} maxW="180px"> |
|||
<Avatar bg="ui.main" icon={<FaUserAstronaut fontSize="18px" />} size='sm' alignSelf="center" /> |
|||
{/* TODO: Conditional tooltip based on email length */} |
|||
<Text color='gray' alignSelf={"center"} noOfLines={1} fontSize="14px">{user.email}</Text> |
|||
</Flex> |
|||
) : |
|||
<Skeleton height='20px' /> |
|||
} |
|||
</> |
|||
); |
|||
|
|||
} |
|||
|
|||
export default UserInfo; |
@ -0,0 +1,45 @@ |
|||
import React from 'react'; |
|||
|
|||
import { IconButton } from '@chakra-ui/button'; |
|||
import { Box } from '@chakra-ui/layout'; |
|||
import { Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/menu'; |
|||
import { FaUserAstronaut } from 'react-icons/fa'; |
|||
import { FiLogOut, FiUser } from 'react-icons/fi'; |
|||
import { useNavigate } from 'react-router'; |
|||
import { Link } from 'react-router-dom'; |
|||
|
|||
const UserMenu: React.FC = () => { |
|||
const navigate = useNavigate(); |
|||
|
|||
const handleLogout = async () => { |
|||
localStorage.removeItem("access_token"); |
|||
navigate("/login"); |
|||
// TODO: reset all Zustand states
|
|||
}; |
|||
|
|||
return ( |
|||
<> |
|||
<Box position="fixed" top={4} right={4}> |
|||
<Menu> |
|||
<MenuButton |
|||
as={IconButton} |
|||
aria-label='Options' |
|||
icon={<FaUserAstronaut color="white" fontSize="18px" />} |
|||
bg="ui.main" |
|||
isRound |
|||
/> |
|||
<MenuList> |
|||
<MenuItem icon={<FiUser fontSize="18px" />} as={Link} to="settings"> |
|||
My profile |
|||
</MenuItem> |
|||
<MenuItem icon={<FiLogOut fontSize="18px" />} onClick={handleLogout} color="ui.danger"> |
|||
Log out |
|||
</MenuItem> |
|||
</MenuList> |
|||
</Menu> |
|||
</Box> |
|||
</> |
|||
); |
|||
}; |
|||
|
|||
export default UserMenu; |
@ -0,0 +1,22 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Box, Text } from '@chakra-ui/react'; |
|||
|
|||
import { useUserStore } from '../store/user-store'; |
|||
|
|||
|
|||
const Dashboard: React.FC = () => { |
|||
const { user } = useUserStore(); |
|||
|
|||
return ( |
|||
<> |
|||
<Box width="100%" p={8}> |
|||
<Text fontSize="2xl">Hi, {user?.full_name || user?.email} 👋🏼</Text> |
|||
<Text>Welcome back, nice to see you again!</Text> |
|||
</Box> |
|||
</> |
|||
|
|||
) |
|||
} |
|||
|
|||
export default Dashboard; |
@ -0,0 +1,26 @@ |
|||
import { Button, Container, Text } from "@chakra-ui/react"; |
|||
|
|||
import { Link, useRouteError } from "react-router-dom"; |
|||
|
|||
const ErrorPage: React.FC = () => { |
|||
const error = useRouteError(); |
|||
console.log(error); |
|||
|
|||
return ( |
|||
<> |
|||
<Container h="100vh" |
|||
alignItems="stretch" |
|||
justifyContent="center" textAlign="center" maxW="xs" centerContent> |
|||
<Text fontSize="8xl" color="ui.main" fontWeight="bold" lineHeight="1" mb={4}>Oops!</Text> |
|||
<Text fontSize="md">Houston, we have a problem.</Text> |
|||
<Text fontSize="md">An unexpected error has occurred.</Text> |
|||
<Text color="ui.danger"><i>{error.statusText || error.message}</i></Text> |
|||
<Button as={Link} to="/" color="ui.main" borderColor="ui.main" variant="outline" mt={4}>Go back to Home</Button> |
|||
</Container> |
|||
</> |
|||
); |
|||
} |
|||
|
|||
export default ErrorPage; |
|||
|
|||
|
@ -1,15 +0,0 @@ |
|||
import { Outlet } from 'react-router-dom'; |
|||
import Sidebar from '../components/Sidebar'; |
|||
|
|||
import { Flex } from '@chakra-ui/react'; |
|||
|
|||
const Layout = () => { |
|||
return ( |
|||
<Flex maxW="large" h="auto" position="relative"> |
|||
<Sidebar /> |
|||
<Outlet /> |
|||
</Flex> |
|||
); |
|||
}; |
|||
|
|||
export default Layout; |
@ -1,18 +0,0 @@ |
|||
import { Button, Container, Text } from "@chakra-ui/react"; |
|||
|
|||
import { Link } from "react-router-dom"; |
|||
|
|||
const NotFound = () => ( |
|||
<> |
|||
<Container h="100vh" |
|||
alignItems="stretch" |
|||
justifyContent="center" textAlign="center" maxW="xs" centerContent> |
|||
<Text fontSize="8xl" color="ui.main" fontWeight="bold" lineHeight="1" mb={4}>404</Text> |
|||
<Text fontSize="md">Houston, we have a problem.</Text> |
|||
<Text fontSize="md">It looks like the page you're looking for doesn't exist.</Text> |
|||
<Button as={Link} to="/" color="ui.main" borderColor="ui.main" variant="outline" mt={4}>Go back to Home</Button> |
|||
</Container> |
|||
</> |
|||
); |
|||
|
|||
export default NotFound; |
@ -0,0 +1,63 @@ |
|||
import React from "react"; |
|||
|
|||
import { Button, Container, FormControl, Heading, Input, Text, useToast } from "@chakra-ui/react"; |
|||
import { SubmitHandler, useForm } from "react-hook-form"; |
|||
|
|||
import { LoginService } from "../client"; |
|||
|
|||
interface FormData { |
|||
email: string; |
|||
} |
|||
|
|||
const RecoverPassword: React.FC = () => { |
|||
const { register, handleSubmit } = useForm<FormData>(); |
|||
const toast = useToast(); |
|||
|
|||
const onSubmit: SubmitHandler<FormData> = async (data) => { |
|||
const response = await LoginService.recoverPassword({ |
|||
email: data.email, |
|||
}); |
|||
console.log(response); |
|||
|
|||
toast({ |
|||
title: "Email sent.", |
|||
description: "We sent an email with a link to get back into your account.", |
|||
status: "success", |
|||
isClosable: true, |
|||
}); |
|||
}; |
|||
|
|||
return ( |
|||
<Container |
|||
as="form" |
|||
onSubmit={handleSubmit(onSubmit)} |
|||
h="100vh" |
|||
maxW="sm" |
|||
alignItems="stretch" |
|||
justifyContent="center" |
|||
gap={4} |
|||
centerContent |
|||
> |
|||
<Heading size="xl" color="ui.main" textAlign="center" mb={2}> |
|||
Password Recovery |
|||
</Heading> |
|||
<FormControl id="username"> |
|||
<Text align="center" color="gray.600"> |
|||
A password recovery email will be sent to the registered account. |
|||
</Text> |
|||
<Input |
|||
{...register("email")} |
|||
|
|||
mt={4} |
|||
placeholder="Enter your email" |
|||
type="text" |
|||
/> |
|||
</FormControl> |
|||
<Button bg="ui.main" color="white" _hover={{ opacity: 0.8 }} type="submit"> |
|||
Continue |
|||
</Button> |
|||
</Container> |
|||
); |
|||
}; |
|||
|
|||
export default RecoverPassword; |
@ -0,0 +1,42 @@ |
|||
import { useEffect } from 'react'; |
|||
|
|||
import { Outlet } from 'react-router-dom'; |
|||
import Sidebar from '../components/Sidebar'; |
|||
|
|||
import { Flex, useToast } from '@chakra-ui/react'; |
|||
import { useUserStore } from '../store/user-store'; |
|||
import UserMenu from '../components/UserMenu'; |
|||
|
|||
const Root: React.FC = () => { |
|||
const toast = useToast(); |
|||
const { getUser } = useUserStore(); |
|||
|
|||
useEffect(() => { |
|||
const fetchUser = async () => { |
|||
const token = localStorage.getItem('access_token'); |
|||
if (token) { |
|||
try { |
|||
await getUser(); |
|||
} catch (err) { |
|||
toast({ |
|||
title: 'Something went wrong.', |
|||
description: 'Failed to fetch user. Please try again.', |
|||
status: 'error', |
|||
isClosable: true, |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
fetchUser(); |
|||
}, []); |
|||
|
|||
return ( |
|||
<Flex maxW="large" h="auto" position="relative"> |
|||
<Sidebar /> |
|||
<Outlet /> |
|||
<UserMenu /> |
|||
</Flex> |
|||
); |
|||
}; |
|||
|
|||
export default Root; |
@ -1,24 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Box, Text } from '@chakra-ui/react'; |
|||
|
|||
import { useUserStore } from '../../store/user-store'; |
|||
|
|||
|
|||
const Dashboard: React.FC = () => { |
|||
const { user } = useUserStore(); |
|||
|
|||
return ( |
|||
<> |
|||
{user ? ( |
|||
<Box width="100%" p={8}> |
|||
<Text fontSize="24px">Hi, {user.full_name || user.email} 👋🏼</Text> |
|||
<Text>Welcome back, nice to see you again!</Text> |
|||
</Box> |
|||
) : null} |
|||
</> |
|||
|
|||
) |
|||
} |
|||
|
|||
export default Dashboard; |
@ -0,0 +1,37 @@ |
|||
import { extendTheme } from "@chakra-ui/react" |
|||
|
|||
const theme = extendTheme({ |
|||
colors: { |
|||
ui: { |
|||
main: "#009688", |
|||
secondary: "#EDF2F7", |
|||
success: '#48BB78', |
|||
danger: '#E53E3E', |
|||
focus: 'red', |
|||
} |
|||
}, |
|||
components: { |
|||
Tabs: { |
|||
variants: { |
|||
enclosed: { |
|||
tab: { |
|||
_selected: { |
|||
color: 'ui.main', |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
Input: { |
|||
baseStyle: { |
|||
field: { |
|||
_focus: { |
|||
borderColor: 'ui.focus', |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
export default theme; |
Loading…
Reference in new issue