diff --git a/src/app.tsx b/src/app.tsx index 8f90578..17bbb82 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -24,11 +24,9 @@ function App() { - }> - diff --git a/src/auth/utility.ts b/src/auth/utility.ts index 68c5ac6..d267c1d 100644 --- a/src/auth/utility.ts +++ b/src/auth/utility.ts @@ -56,7 +56,6 @@ export const setSession = (token: string | null) => { localStorage.setItem(LOCAL_STORAGE.TOKEN, token) axios.defaults.headers.common.Authorization = `${token}` - console.log(axios.defaults.headers.common.Authorization, 'axios.defaults.headers.common.Authorization') const { exp } = jwtDecode(token) tokenExpired(exp) } else { diff --git a/src/component/button/back/back.tsx b/src/component/button/back/back.tsx new file mode 100644 index 0000000..4c69d04 --- /dev/null +++ b/src/component/button/back/back.tsx @@ -0,0 +1,86 @@ +import { useState, useEffect, useRef, Fragment, MouseEvent } from 'react' +import { m } from 'framer-motion' +import { useNavigate, useLocation } from 'react-router-dom' +import { ICON_NAME, useIcon } from 'hook' +import { useSettingContext } from 'component/setting' +import { Typography } from '@mui/material' +import { useTheme } from '@mui/material/styles' +import { KEY, LABEL, TYPOGRAPHY, VARIANT } from 'constant' +import { SPopover, SBackIconButton } from 'component/button' + +const BackButton = () => { + const [open, setOpen] = useState(false) + const [anchorEl, setAnchorEl] = useState(null) + + const { themeMode } = useSettingContext() + const { pathname } = useLocation() + const navigate = useNavigate() + const navRef = useRef(null) + const theme = useTheme() + + const { Icon, iconSrc: backIconSrc } = useIcon(ICON_NAME.BACK) + + const handleClick = (event: MouseEvent) => { + setAnchorEl(event.currentTarget) + navigate(-1) + } + + useEffect(() => { + if (open) { + handleClose() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pathname]) + + useEffect(() => { + const appBarEl = Array.from(document.querySelectorAll('.MuiAppBar-root')) as HTMLElement[] + + const styles = () => { + document.body.style.overflow = '' + document.body.style.padding = '' + + appBarEl.forEach((elem) => { + elem.style.padding = '' + }) + } + + if (open) { + styles() + } else { + styles() + } + }, [open]) + + const handleOpen = (event: MouseEvent) => { + setAnchorEl(event.currentTarget) + setOpen(true) + } + + const handleClose = () => { + setOpen(false) + } + + return ( + + + + + + + + {LABEL.GO_BACK} + + + + + ) +} + +export default BackButton diff --git a/src/component/button/back/index.ts b/src/component/button/back/index.ts new file mode 100644 index 0000000..7b1ccf6 --- /dev/null +++ b/src/component/button/back/index.ts @@ -0,0 +1 @@ +export { default as BackButton } from './back' diff --git a/src/component/button/index.ts b/src/component/button/index.ts index 4cd34f4..885be90 100644 --- a/src/component/button/index.ts +++ b/src/component/button/index.ts @@ -1,2 +1,4 @@ export { default as Button } from './button' +export { default as BackButton } from './back/back' export { default as AnimatedButton } from './button-animated' +export * from './style' diff --git a/src/component/button/style.tsx b/src/component/button/style.tsx new file mode 100644 index 0000000..a11d846 --- /dev/null +++ b/src/component/button/style.tsx @@ -0,0 +1,29 @@ +import { styled } from '@mui/material/styles' +import { Popover, IconButton } from '@mui/material' + +export const SPopover = styled(Popover)(({ theme }) => ({ + '& .MuiPaper-root': { + backgroundColor: 'transparent', + boxShadow: 'none', + size: '100%', + overflow: 'hidden', + borderRadius: 0 + }, + '& .MuiPopover-paper': { + overflow: 'hidden' + }, + '& .MuiPopover-paper .MuiList-root': { + padding: '0px' + }, + '& .MuiPopover-paper .MuiTypography-root': { + fontSize: '1rem' + }, + boxShadow: 'none', + pointerEvents: 'none' +})) + +export const SBackIconButton = styled(IconButton)(({ theme }) => ({ + borderRadius: 2, + padding: 1, + marginBottom: 2 +})) diff --git a/src/component/index.ts b/src/component/index.ts index 7af293a..4e9d755 100644 --- a/src/component/index.ts +++ b/src/component/index.ts @@ -10,6 +10,7 @@ export { default as Paper } from './paper/paper' export { default as Snackbar } from './snackbar/snackbar' // button export { default as Button } from './button/button' +export { default as BackButton } from './button/back/back' // typography export { default as Typography } from './typography/typography' // markdown diff --git a/src/component/navbar/index.ts b/src/component/navbar/index.ts index 9d6277d..24e3188 100644 --- a/src/component/navbar/index.ts +++ b/src/component/navbar/index.ts @@ -1,5 +1,6 @@ export { default as Navbar } from './navbar' -export { default as AppNavBar } from './appbar' +export { default as AppBar } from './appbar' +export { default as AppNavBar } from './app-appbar' export { default as AppFooter } from './footer' export { default as NavDrawer } from './nav-drawer' export { default as AccountPopover } from './account-popover' diff --git a/src/component/navbar/nav-drawer.tsx b/src/component/navbar/nav-drawer.tsx index ba8c7d2..0f8e08d 100644 --- a/src/component/navbar/nav-drawer.tsx +++ b/src/component/navbar/nav-drawer.tsx @@ -24,7 +24,7 @@ const NavDrawer = ({ open, onClose }: NavDrawerProps) => { sx={{ pl: 0.5 }} /> - + Find a Bootcamp} /> @@ -53,7 +53,7 @@ const NavDrawer = ({ open, onClose }: NavDrawerProps) => { - + Find a Course} /> diff --git a/src/config/icon-directory.ts b/src/config/icon-directory.ts index e6b852b..75ceffe 100644 --- a/src/config/icon-directory.ts +++ b/src/config/icon-directory.ts @@ -20,14 +20,19 @@ function _getWebIcon(icon: string) { const ICON_WEB = { ALERT_OUTLINE: _getWebIcon('alert-triangle-outline'), ARROW_FORWARD: _getWebIcon('arrow-ios-forward'), + BACK: _getWebIcon('arrow-ios-back-outline'), CHEVRON_RIGHT: _getWebIcon('chevron-right-outline'), CHEVRON_DOWN: _getWebIcon('chevron-down-outline'), CLOSE: _getWebIcon('close-fill'), CHECKMARK_CIRCLE: _getWebIcon('checkmark-circle-outline'), + CHECK_OUTLINE: _getWebIcon('checkmark-outline'), + CLOSE_OUTLINE: _getWebIcon('close-outline'), CONTRAST_BOX: _getWebIcon('contrast-box'), EDIT: _getWebIcon('edit-2-outline'), ERROR_OUTLINE: _getWebIcon('alert-circle-outline'), ERROR: _getWebIcon('alert-circle'), + PLUS: _getWebIcon('plus-outline'), + RETRACT: _getWebIcon('minus-outline'), INFO: _getWebIcon('info'), EYE_OFF: _getWebIcon('eye-off-outline'), @@ -35,6 +40,8 @@ const ICON_WEB = { FULLSCREEN: _getWebIcon('fullscreen'), FULLSCREEN_EXIT: _getWebIcon('fullscreen-exit'), + GLOBE: _getWebIcon('globe-2-outline'), + PHONE: _getWebIcon('phone-outline'), REFRESH: _getWebIcon('refresh'), MODE_LIGHT: _getWebIcon('sun'), @@ -69,7 +76,10 @@ export enum ICON_WEB_NAME { // @web ALERT_OUTLINE = 'ALERT_OUTLINE', ARROW_FORWARD = 'ARROW_FORWARD', + BACK = 'BACK', CHECKMARK_CIRCLE = 'CHECKMARK_CIRCLE', + CHECK_OUTLINE = 'CHECK_OUTLINE', + CLOSE_OUTLINE = 'CLOSE_OUTLINE', CHEVRON_RIGHT = 'CHEVRON_RIGHT', CHEVRON_DOWN = 'CHEVRON_DOWN', CLOSE = 'CLOSE', @@ -80,12 +90,15 @@ export enum ICON_WEB_NAME { EYE_OFF = 'EYE_OFF', EYE_HIDE = 'EYE_HIDE', INFO = 'INFO', - + PHONE = 'PHONE', + GLOBE = 'GLOBE', MODE_LIGHT = 'MODE_LIGHT', MODE_DARK = 'MODE_DARK', FULLSCREEN = 'FULLSCREEN', FULLSCREEN_EXIT = 'FULLSCREEN_EXIT', + PLUS = 'PLUS', + RETRACT = 'RETRACT', REFRESH = 'REFRESH', SAVE = 'SAVE', diff --git a/src/config/layout.ts b/src/config/layout.ts index 448da52..9fea9b9 100644 --- a/src/config/layout.ts +++ b/src/config/layout.ts @@ -1,3 +1,31 @@ +/** + * Layout configuration for the application + * + * Dimensions are in pixels + * + */ +export const HEADER = { + H_MOBILE: 64, + H_MAIN_DESKTOP: 88, + H_DASHBOARD_DESKTOP: 65, + H_DASHBOARD_DESKTOP_OFFSET: 92 - 32 +} + +export const FOOTER = { + H: 64 +} + +export const SPACING = { + XS: 4, + SM: 8, + MD: 16, + LG: 24, + XL: 32, + XXL: 40, + XXXL: 48, + DESKTOP: 80 +} + export const NAV = { W_BASE: 260, W_DASHBOARD: 300, diff --git a/src/constant/aria.ts b/src/constant/aria.ts index 59d587e..d1313ed 100644 --- a/src/constant/aria.ts +++ b/src/constant/aria.ts @@ -5,7 +5,8 @@ enum ARIA { LAST_NAME = 'Last name', TOGGLE_PASSWORD = 'toggle password visibility', TABS = 'tabs', - COMPANY_BADGE = 'company badge' + COMPANY_BADGE = 'company badge', + COURSE_TABLE = 'course table' } export default ARIA diff --git a/src/constant/index.ts b/src/constant/index.ts index d4637c2..1da3d85 100644 --- a/src/constant/index.ts +++ b/src/constant/index.ts @@ -12,7 +12,6 @@ export { default as MENU_POPOVER_ARROW } from './menu-popover-arrow' export { default as TITLE } from './title' export { default as ARIA } from './aria' export { BUTTON } from './button' -export * from './typography' export * from './size' export * from './color' export * from './component' diff --git a/src/constant/label.ts b/src/constant/label.ts index 0743237..81f5b19 100644 --- a/src/constant/label.ts +++ b/src/constant/label.ts @@ -1,6 +1,7 @@ import { KEY } from 'constant' const LABEL = { + GO_BACK: 'Go Back', LOG_IN: 'Log In', LOG_OUT: 'Log Out', DISPLAY_SETTINGS: 'Display Settings', @@ -12,20 +13,31 @@ const LABEL = { }, REGISTER: 'Register', RESET_PASSWORD: 'Reset Password', + COURSE_COVERED: 'Course certifications covered', HOME: 'Home', - + LOCATION: 'Location', + CAREER: 'Careers', + CONTACT: 'Contact', ALREADY_MEMBER: `Already a member? `, NOT_A_MEMBER: `Not a member yet? `, LOGIN_Sub: 'Log in here ', REGISTER_Sub: 'Register for free', FORGOT_PASSWORD: 'Forgot password?', - + RATING: 'Rating', SELECT_ROLE: 'Select your role', - + WEBSITE: 'Website', + VISIT_WEBSITE: 'Visit Website', ADMIN: 'Admin', STUDENT: 'Student', TRAINER: 'Trainer', + TITLE: 'Title', + DESCRIPTION: 'Description', + DURATION: 'Duration', + COST: 'Cost', + SKILL_REQUIRED: 'Skill Required', + SCHOLARSHIP: 'Scholarship', + BOOTCAMP_PAGE_TITLE: 'Worldclass Bootcamps Worldwide', COURSE_PAGE_TITLE: 'World Class Courses', diff --git a/src/constant/placeholder.ts b/src/constant/placeholder.ts index c815ec8..a17ccc9 100644 --- a/src/constant/placeholder.ts +++ b/src/constant/placeholder.ts @@ -14,6 +14,9 @@ enum PLACEHOLDER { NO_DURATION = 'No duration specified', NO_TUITION = 'No tuition specified', NO_MINIMUM_SKILL = 'No minimum skill required', + NO_COURSE_AVAILABLE = 'No course available', + NO_LOCATION = 'No location provided', + NO_CAREER = 'No career provided', // @bootcamp search form MILES_FROM = 'Miles From', diff --git a/src/constant/routing/routing.ts b/src/constant/routing/routing.ts index 01f9371..2a3926a 100644 --- a/src/constant/routing/routing.ts +++ b/src/constant/routing/routing.ts @@ -1,6 +1,8 @@ enum ROUTING { ROOT = '/', + ALL = 'all', ID = ':id', + VIEW = 'view', DASHBOARD = 'dashboard', // :bootcamp BOOTCAMP = 'bootcamp', diff --git a/src/constant/typography.ts b/src/constant/typography.ts deleted file mode 100644 index 5f9ab5d..0000000 --- a/src/constant/typography.ts +++ /dev/null @@ -1,27 +0,0 @@ -export enum TYPOGRAPHY { - H0 = 'h0', - H1 = 'h1', - H2 = 'h2', - H3 = 'h3', - H4 = 'h4', - H5 = 'h5', - H6 = 'h6', - H7 = 'h7', - BODY1 = 'body1', - BODY2 = 'body2', - SUBTITLE1 = 'subtitle1', - SUBTITLE2 = 'subtitle2', - OVERLINE = 'overline', - CAPTION = 'caption', - BUTTON = 'button', - PARAGRAPH = 'paragraph' -} - -export enum FONTWEIGHT { - NORMAL = 'normal', - LIGHT = 'light', - REGULAR = 'regular', - MEDIUM = 'medium', - BOLD = 'bold', - BLACK = 'black' -} diff --git a/src/constant/variant.ts b/src/constant/variant.ts index c9e7f7c..4d57e68 100644 --- a/src/constant/variant.ts +++ b/src/constant/variant.ts @@ -4,21 +4,6 @@ export enum BUTTON_VARIANT { TEXT = 'text' } -export enum TYPOGRAPHY_VARIANT { - H1 = 'h1', - H2 = 'h2', - H3 = 'h3', - H4 = 'h4', - H5 = 'h5', - H6 = 'h6', - SUBTITLE1 = 'subtitle1', - SUBTITLE2 = 'subtitle2', - BODY1 = 'body1', - BODY2 = 'body2', - CAPTION = 'caption', - OVERLINE = 'overline' -} - export enum SKELETON_VARIANT { RECTANGULAR = 'rectangular', CIRCULAR = 'circular', @@ -31,3 +16,34 @@ export enum VARIANT { OUTLINED = 'outlined', COVER = 'cover' } + +export enum TYPOGRAPHY { + H0 = 'h0', + H1 = 'h1', + H2 = 'h2', + H3 = 'h3', + H4 = 'h4', + H5 = 'h5', + H6 = 'h6', + H7 = 'h7', + BODY1 = 'body1', + BODY2 = 'body2', + SUBTITLE1 = 'subtitle1', + SUBTITLE2 = 'subtitle2', + OVERLINE0 = 'overline0', + OVERLINE = 'overline', + OVERLINE1 = 'overline1', + OVERLINE2 = 'overline2', + CAPTION = 'caption', + BUTTON = 'button', + PARAGRAPH = 'paragraph' +} + +export enum FONTWEIGHT { + NORMAL = 'normal', + LIGHT = 'light', + REGULAR = 'regular', + MEDIUM = 'medium', + BOLD = 'bold', + BLACK = 'black' +} diff --git a/src/hook/use-icon.tsx b/src/hook/use-icon.tsx index aa3d275..4cdf71d 100644 --- a/src/hook/use-icon.tsx +++ b/src/hook/use-icon.tsx @@ -8,7 +8,7 @@ * * @properties icon - The Icon component to render. * - * @param {ICON_NAME[]} iconName - The name of the icon to retrieve. + * @param iconName - The name of the icon to retrieve. * @returns {Object} - An object containing the Icon component and the icon source. */ import { FC, useState, useEffect } from 'react' diff --git a/src/page/app/app-layout.tsx b/src/page/app/app-layout.tsx new file mode 100644 index 0000000..18b7100 --- /dev/null +++ b/src/page/app/app-layout.tsx @@ -0,0 +1,19 @@ +import { Fragment } from 'react' +import { Outlet } from 'react-router-dom' +import { AppNavBar, AppFooter } from 'component/navbar' +import withRoot from 'withroot' +import Main from './main' + +function AppLayout() { + return ( + + +
+ +
+ +
+ ) +} + +export default withRoot(AppLayout) diff --git a/src/page/app/index.ts b/src/page/app/index.ts new file mode 100644 index 0000000..17eecc6 --- /dev/null +++ b/src/page/app/index.ts @@ -0,0 +1 @@ +export { default as AppLayout } from './app-layout' diff --git a/src/page/app/main.tsx b/src/page/app/main.tsx new file mode 100644 index 0000000..f3323b2 --- /dev/null +++ b/src/page/app/main.tsx @@ -0,0 +1,27 @@ +import { FC, ReactNode } from 'react' +import { Box } from '@mui/material' +import { HEADER, SPACING } from 'config/layout' + +interface MainProps { + children: ReactNode + isDesktop: boolean +} + +const Main: FC = ({ children, isDesktop }) => { + return ( + + {children} + + ) +} + +export default Main diff --git a/src/page/bootcamp/bootcamp.tsx b/src/page/bootcamp/bootcamp.tsx index 18168e0..eddb46f 100644 --- a/src/page/bootcamp/bootcamp.tsx +++ b/src/page/bootcamp/bootcamp.tsx @@ -8,7 +8,7 @@ import { MotionLazyContainer } from 'component/motion' import { SkeletonLoader } from 'component/skeleton' import { Typography } from 'component/typography' import { ASSET } from 'config' -import { LABEL, PLACEHOLDER } from 'constant' +import { FLEX, KEY, LABEL, PLACEHOLDER } from 'constant' function Bootcamp() { const [currentPage, setCurrentPage] = useState(1) @@ -29,11 +29,11 @@ function Bootcamp() { logo @@ -48,7 +48,7 @@ function Bootcamp() { { + return +} + +export default BootcampView diff --git a/src/page/course/course.tsx b/src/page/course/course.tsx index 3022637..971faf1 100644 --- a/src/page/course/course.tsx +++ b/src/page/course/course.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' import { m } from 'framer-motion' -import { useGetAllBootcampQuery } from 'store/slice' +import { useGetAllBootcampQuery, useGetAllCourseQuery } from 'store/slice' import { MotionLazyContainer } from 'component/motion' import { Box, Grid, Pagination } from '@mui/material' import { CourseCard } from 'section/course' @@ -13,7 +13,7 @@ import { LABEL } from 'constant' const Course = () => { const [currentPage, setCurrentPage] = useState(1) - const { data, error, isLoading, refetch } = useGetAllBootcampQuery() + const { data, error, isLoading, refetch } = useGetAllCourseQuery() const handlePageChange = (event: any, value: any) => { setCurrentPage(value) @@ -43,7 +43,7 @@ const Course = () => { { { return ( + + ) } diff --git a/src/page/index.ts b/src/page/index.ts index 07a552f..bf981bd 100644 --- a/src/page/index.ts +++ b/src/page/index.ts @@ -1,5 +1,6 @@ export { default as Home } from './home/home' export { default as Fallback } from './fallback/fallback' +export { default as AppLayout } from './app/app-layout' // :auth export { default as LogIn } from './auth/log-in/log-in' export { default as Register } from './auth/register/register' diff --git a/src/route/element.tsx b/src/route/element.tsx index b93f493..1ec5f0d 100644 --- a/src/route/element.tsx +++ b/src/route/element.tsx @@ -22,6 +22,7 @@ export const RegisterPage = Loadable(() => import('page/auth/register/register') export const UserAccountPage = Loadable(() => import('page/auth/account/user-account')) // bootcamp export const BootcampPage = Loadable(() => import('page/bootcamp/bootcamp')) +export const BootcampViewPage = Loadable(() => import('page/bootcamp/view/view')) // course export const CoursePage = Loadable(() => import('page/course/course')) // fallback diff --git a/src/route/index.tsx b/src/route/index.tsx index e754518..c6c4dae 100644 --- a/src/route/index.tsx +++ b/src/route/index.tsx @@ -1,6 +1,7 @@ import { CODE } from 'constant' import { Navigate, useRoutes } from 'react-router-dom' import { GuestGuard, AuthGuard } from 'auth' +import { AppLayout } from 'page' import { HomePage, DashboardPage, @@ -10,6 +11,7 @@ import { UserAccountPage, // :bootcamp BootcampPage, + BootcampViewPage, // :course CoursePage, FallbackPage @@ -21,16 +23,22 @@ import { FALLBACK } from 'constant' const { ACCOUNT, AUTH, BOOTCAMP, COURSE, LOG_IN, REGISTER, RESET_PASSWORD } = ROUTING function Router() { + /** + * @baseUrl {http://thecodecoachprojct.com} + * @path {baseUrl}/api/{apiVer} + */ return useRoutes([ { path: ROUTING.ROOT, element: }, { - // @auth + // /auth path: AUTH, + element: , children: [ { + // /auth/log-in path: LOG_IN, element: ( @@ -39,10 +47,12 @@ function Router() { ) }, { + // /auth/register path: REGISTER, element: }, { + // /auth/account path: ACCOUNT, element: ( @@ -51,55 +61,119 @@ function Router() { ) }, { + // /auth/reset-password path: RESET_PASSWORD // element: , } ] }, { + // /bootcamp path: BOOTCAMP, - element: , + element: , children: [ { - path: ROUTING.ID - // element: , + // /bootcamp/all + path: ROUTING.ALL, + element: + }, + { + // /bootcamp/:id + path: ':id/view', + element: + // children: [ + // { + // // /:id/view + // path: ROUTING.VIEW, + // element: + // } + // ] } ] }, { + // /course path: COURSE, - element: , + element: , children: [ { - path: ROUTING.ID - // element: , + // /course/all + path: ROUTING.ALL, + element: + }, + { + // /course/:id/view + path: ROUTING.ID, + children: [ + { + // /course/:id/view + path: ROUTING.VIEW, + element: + } + ] } ] }, // @fallback { path: FallbackPath.NOT_AUTHORIZED, - element: + element: , + children: [ + { + path: FallbackPath.NOT_AUTHORIZED, + element: + } + ] }, { path: FallbackPath.FORBIDDEN, - element: + element: , + children: [ + { + path: FallbackPath.FORBIDDEN, + element: + } + ] }, { path: FallbackPath.NOT_FOUND, - element: + element: , + children: [ + { + path: FallbackPath.NOT_FOUND, + element: + } + ] }, { path: FallbackPath.BAD_REQUEST, - element: + element: , + children: [ + { + path: FallbackPath.BAD_REQUEST, + element: + } + ] }, { path: FallbackPath.UNPROCESSABLE_ENTITY, - element: + element: , + children: [ + { + path: FallbackPath.UNPROCESSABLE_ENTITY, + element: + } + ] }, { path: FallbackPath.SERVER_ERROR, - element: + element: , + children: [ + { + path: FallbackPath.SERVER_ERROR, + element: + } + ] }, { path: '*', element: } ]) diff --git a/src/route/path.ts b/src/route/path.ts index b9bcdde..a2424a9 100644 --- a/src/route/path.ts +++ b/src/route/path.ts @@ -14,6 +14,7 @@ export class ServerPath { static BOOTCAMP_ID = (bootcampId: string) => conNex(ROUTING.BOOTCAMP, bootcampId) static COURSE = ROUTING.COURSE static COURSE_ID = (courseId: string) => conNex(ROUTING.COURSE, courseId) + static COURSE_BY_BOOTCAMP = (bootcampId: string) => conNex(ROUTING.BOOTCAMP, bootcampId, ROUTING.COURSE) static AUTH_LOG_IN = conNex(ROUTING.AUTH, ROUTING.LOG_IN) static AUTH_LOG_OUT = conNex(ROUTING.AUTH, ROUTING.LOG_OUT) static AUTH_REGISTER = conNex(ROUTING.AUTH, ROUTING.REGISTER) @@ -49,8 +50,8 @@ export class BootcampPath { throw new Error(RESPONSE.error.NotInstance) } - static BOOTCAMP = ROUTING.BOOTCAMP - static BOOTCAMP_ID = conNex(ROUTING.BOOTCAMP, ROUTING.ID) + static BOOTCAMP = conNex(ROUTING.BOOTCAMP, ROUTING.ALL) + static BOOTCAMP_ID = (bootcampId: string | undefined) => conNex(ROUTING.BOOTCAMP, bootcampId, ROUTING.VIEW) } export class FallbackPath { diff --git a/src/section/bootcamp/bootcamp-card.tsx b/src/section/bootcamp/bootcamp-card.tsx index f1ebcdc..d27e13c 100644 --- a/src/section/bootcamp/bootcamp-card.tsx +++ b/src/section/bootcamp/bootcamp-card.tsx @@ -1,8 +1,10 @@ import { FC } from 'react' +import { Link } from 'react-router-dom' import { Box, CardContent, CardMedia, Grid, Typography, Rating } from '@mui/material' import { GSContainerGrid, SBadgeHeader } from 'theme/style' import { BootcampCareer } from 'section/bootcamp' -import { ARIA, COMPONENT, FLEX, KEY, TYPOGRAPHY_VARIANT, VARIANT } from 'constant' +import { ARIA, COMPONENT, FLEX, KEY, TYPOGRAPHY, VARIANT } from 'constant' +import { BootcampPath } from 'route/path' import { badgeLocation, photoLocation } from 'util/asset-loc' const BootcampCard: FC = ({ bootcamp }) => { @@ -11,8 +13,8 @@ const BootcampCard: FC = ({ bootcamp }) => { = ({ bootcamp }) => { - - {bootcamp?.name} - + + + {bootcamp?.name} + + = ({ bootcamp }) => { } + avatar={} sx={{ p: 0, py: 2 }} /> - {bootcamp.description} + {bootcamp?.description} - + {bootcamp?.location?.city}, {bootcamp?.location?.state} diff --git a/src/section/bootcamp/bootcamp-career.tsx b/src/section/bootcamp/bootcamp-career.tsx index 26902b7..602cd09 100644 --- a/src/section/bootcamp/bootcamp-career.tsx +++ b/src/section/bootcamp/bootcamp-career.tsx @@ -2,6 +2,7 @@ import { FC } from 'react' import { Chip } from '@mui/material' import { SScrollBox } from 'theme/style' import { SIZE, VARIANT } from 'constant' +import { ChipPropsVariantOverrides } from '@mui/material' const BootcampCareer: FC = ({ bootcamp }) => { return ( diff --git a/src/section/bootcamp/index.ts b/src/section/bootcamp/index.ts index 6ae88dc..eda532f 100644 --- a/src/section/bootcamp/index.ts +++ b/src/section/bootcamp/index.ts @@ -2,3 +2,5 @@ export { default as Bootcamp } from './bootcamp' export { default as BootcampSearch } from './bootcamp-search' export { default as BootcampCard } from './bootcamp-card' export { default as BootcampCareer } from './bootcamp-career' +export { default as BootcampViewSection } from './view/view' +export * from './style' diff --git a/src/section/bootcamp/style.tsx b/src/section/bootcamp/style.tsx new file mode 100644 index 0000000..1551e8b --- /dev/null +++ b/src/section/bootcamp/style.tsx @@ -0,0 +1,40 @@ +import { styled } from '@mui/material/styles' +import { m } from 'framer-motion' +import { TableRow, TableContainer, TableCell, Collapse } from '@mui/material' + +export const SRoot = styled(m.div)({ + maxWidth: '100vw', + margin: '0 auto' +}) + +export const STableContainer = styled(TableContainer)({ + boxShadow: '0 0 20px rgba(0, 0, 0, 0.1)', + borderRadius: 2, + overflow: 'hidden' +}) + +export const STableHeader = styled(TableRow)(({ theme }) => ({ + backgroundColor: theme.palette.secondary.main, + color: theme.palette.common.white, + paddingRight: '1rem', + paddingLeft: '1rem' +})) + +export const STableRow = styled(TableRow)(({ theme }) => ({ + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover + } +})) + +export const SCollapse = styled(Collapse)(({ theme }) => ({ + '& .MuiTableCell-root': { + padding: '1rem', + backgroundColor: 'transparent' + } +})) + +export const STableHeadCell = styled(TableCell)(({ theme }) => ({ + fontWeight: 'bold', + fontSize: '1rem', + backgroundColor: 'transparent' +})) diff --git a/src/section/bootcamp/view/course-row.tsx b/src/section/bootcamp/view/course-row.tsx new file mode 100644 index 0000000..aa3f221 --- /dev/null +++ b/src/section/bootcamp/view/course-row.tsx @@ -0,0 +1,49 @@ +import { Fragment, FC, useState } from 'react' +import { TableRow, TableCell, Box, IconButton, Typography } from '@mui/material' +import { STableRow, SCollapse } from 'section/bootcamp' +import { useIcon } from 'hook' +import { ICON_WEB_NAME } from 'config' +import { KEY, TYPOGRAPHY } from 'constant' +import { capitalize } from 'util/format' + +interface CourseRowProps { + row: any +} + +const CourseRow: FC = ({ row }) => { + const [open, setOpen] = useState(false) + + const { Icon: WebIcon, iconSrc: checkSrc } = useIcon(ICON_WEB_NAME.CHECK_OUTLINE) + const { Icon, iconSrc: crossSrc } = useIcon(ICON_WEB_NAME.CLOSE_OUTLINE) + const { Icon: ExpandIcon, iconSrc: expandSrc } = useIcon(ICON_WEB_NAME.PLUS) + const { Icon: RetractIcon, iconSrc: retractSrc } = useIcon(ICON_WEB_NAME.RETRACT) + return ( + + + {row?.title} + {row?.duration} weeks + ${row?.tuition} + {capitalize(row?.minimumSkill)} + + {row?.scholarshipAvailable ? : } + + + + setOpen(!open)}>{open ? : } + + + + + + + + {row?.description} + + + + + + ) +} + +export default CourseRow diff --git a/src/section/bootcamp/view/course-table.tsx b/src/section/bootcamp/view/course-table.tsx new file mode 100644 index 0000000..bf4fc19 --- /dev/null +++ b/src/section/bootcamp/view/course-table.tsx @@ -0,0 +1,37 @@ +import { FC, Fragment, useState } from 'react' +import { Table, TableBody, TableCell, TableHead } from '@mui/material' +import { SRoot, STableContainer, STableHeader } from 'section/bootcamp' +import { ARIA, KEY, LABEL } from 'constant' +import CourseRow from './course-row' + +interface CourseTableProps { + course: any +} + +const CourseTable: FC = ({ course }) => { + return ( + + + + + + {LABEL.TITLE} + {LABEL.DURATION} + {LABEL.COST} + {LABEL.SKILL_REQUIRED} + {LABEL.SCHOLARSHIP} + + + + + {course?.map((row: any, index: any) => ( + + ))} + +
+
+
+ ) +} + +export default CourseTable diff --git a/src/section/bootcamp/view/index.ts b/src/section/bootcamp/view/index.ts new file mode 100644 index 0000000..26f050a --- /dev/null +++ b/src/section/bootcamp/view/index.ts @@ -0,0 +1,3 @@ +export { default as BootcampViewSection } from './view' +export { default as CourseTable } from './course-table' +export { default as CourseRow } from './course-row' diff --git a/src/section/bootcamp/view/view.tsx b/src/section/bootcamp/view/view.tsx new file mode 100644 index 0000000..8fc9661 --- /dev/null +++ b/src/section/bootcamp/view/view.tsx @@ -0,0 +1,250 @@ +import { FC, useEffect } from 'react' +import { useParams, Link } from 'react-router-dom' +import { ICON_NAME, useIcon } from 'hook' +import { useGetBootcampQuery, useGetAllCourseByBootcampQuery } from 'store/slice' +import { + Box, + Grid, + Typography, + Container, + CardMedia, + Chip, + CardContent, + Divider, + Accordion, + AccordionSummary, + AccordionDetails, + List, + ListItem, + ListItemText +} from '@mui/material' +import { BackButton } from 'component' +import { GSContainerGrid, SSpanBox } from 'theme/style' +import { MotionLazyContainer } from 'component/motion' +import { ICON_WEB_NAME } from 'config' +import { FLEX, KEY, LABEL, PLACEHOLDER, TYPOGRAPHY, COMPONENT, ARIA, VARIANT, SIZE } from 'constant' +import { badgeLocation, photoLocation } from 'util/asset-loc' +import CourseTable from './course-table' + +interface BootcampViewSectionProps { + bootcamp?: Bootcamp +} + +const BootcampViewSection: FC = () => { + const { id } = useParams<{ id: string }>() + const { data: bootcamp, error, isLoading } = useGetBootcampQuery(id) + + const { + _id, + name, + description, + badge, + photo, + location, + careers, + averageRating, + user, + duration, + phone, + website, + slug, + housing, + jobAssistance, + jobGuarantee + } = bootcamp?.data || {} + + const courseQueryResult = useGetAllCourseByBootcampQuery(_id) + + const { data: course, error: courseError, isLoading: courseLoading } = courseQueryResult || { data: null, error: null, isLoading: false } + + const { Icon: PhoneIcon, iconSrc: phoneSrc } = useIcon(ICON_WEB_NAME.PHONE) + const { Icon: RightIcon, iconSrc: rightSrc } = useIcon(ICON_WEB_NAME.CHEVRON_RIGHT) + const { Icon: GlobeIcon, iconSrc: globeSrc } = useIcon(ICON_WEB_NAME.GLOBE) + const { Icon, iconSrc: crossSrc } = useIcon(ICON_WEB_NAME.CLOSE_OUTLINE) + const { iconSrc: checkSrc } = useIcon(ICON_WEB_NAME.CHECK_OUTLINE) + + return ( + + + + + + + + + + + + + {name} + + {description} + {slug && ( + + + Tags:   + + + {slug || '5'} + + + )} + + + + + + + Duration:   + + {duration} + + + + + + Housing:   + + {housing ? : } + + + + + + Job Assistance:   + + {jobAssistance ? : } + + + + + + Job Guarantee:   + + {jobGuarantee ? : } + + + + + + + + {user?.organization || 'TCCP'} + + + + + + + + {LABEL.LOCATION} + + + {location?.city}, {location?.state} + + + + + {LABEL.CAREER} + + + {careers?.map((career: string, index: any) => ( + // chip for each career + {career}
} + size={SIZE.SMALL} + sx={{ + margin: 0.5 + }} + /> + )) || PLACEHOLDER.NO_CAREER} + + + + {LABEL.RATING} + + {averageRating || '5'} + + + + + + {LABEL.CONTACT} + + +   {phone || '0'} + + + + + {LABEL.WEBSITE} + + + +   + + + {LABEL.VISIT_WEBSITE} + + + + + + + + +
+ + + + {LABEL.COURSE_COVERED} + + + {course?.data?.length > 0 ? ( + + ) : ( + {PLACEHOLDER.NO_COURSE_AVAILABLE} + )} + + + + + + ) +} + +export default BootcampViewSection diff --git a/src/section/course/course-card.tsx b/src/section/course/course-card.tsx index ed177d9..d4641e5 100644 --- a/src/section/course/course-card.tsx +++ b/src/section/course/course-card.tsx @@ -1,6 +1,7 @@ import { FC, useEffect } from 'react' import { useSelector } from 'react-redux' import { dispatch } from 'store' +import { useParams } from 'react-router-dom' import { useGetBootcampQuery } from 'store/slice' import { Box, CardContent, CardMedia, Chip, Grid, Typography, Rating } from '@mui/material' import { ServerPath } from 'route/path' @@ -15,7 +16,8 @@ interface CourseCardProps { const CourseCard: FC = ({ course }) => { const { data, error, isLoading, refetch } = useGetBootcampQuery(course?.bootcamp?._id) - const { data: bootcamp } = data + + const { _id, badge } = data?.data || {} let minimumSkill = '' @@ -55,7 +57,7 @@ const CourseCard: FC = ({ course }) => { = ({ course }) => { {course.description.charAt(0).toUpperCase() + course.description.slice(1)} - {course?.bootcamp?.name.charAt(0).toUpperCase() + course?.bootcamp?.name.slice(1) || PLACEHOLDER.BOOTCAMP_NAME} + {(course && course?.bootcamp?.name.charAt(0).toUpperCase()) + course?.bootcamp?.name.slice(1) || PLACEHOLDER.BOOTCAMP_NAME} diff --git a/src/section/dashboard/dashboard-bootcamp-rd.tsx b/src/section/dashboard/dashboard-bootcamp-rd.tsx index 786cb48..e4522dd 100644 --- a/src/section/dashboard/dashboard-bootcamp-rd.tsx +++ b/src/section/dashboard/dashboard-bootcamp-rd.tsx @@ -4,7 +4,7 @@ import { Box, Grid, Divider, Tab, Tabs, Skeleton } from '@mui/material' import { SScrollGrid, GSBox, GSDividerBox, GSRundownContainer } from 'theme/style' import { useGetAllBootcampQuery } from 'store/slice' import { Button, Typography } from 'component' -import { BUTTON, LABEL, KEY, COLOR, COMPONENT, BUTTON_VARIANT, SIZE, VARIANT, FLEX, ARIA, TYPOGRAPHY_VARIANT, FONTWEIGHT } from 'constant' +import { BUTTON, LABEL, KEY, COLOR, COMPONENT, BUTTON_VARIANT, SIZE, VARIANT, FLEX, ARIA, TYPOGRAPHY, FONTWEIGHT } from 'constant' import { CATEGORY } from 'config' import BootcampTile from './dashboard-bootcamp-tile' import { AuthPath } from 'route/path' @@ -33,7 +33,7 @@ function DashboardBootcampRundown() { - + {LABEL.NEW_BOOTCAMPS} diff --git a/src/section/dashboard/dashboard-bootcamp-tile.tsx b/src/section/dashboard/dashboard-bootcamp-tile.tsx index d238dfc..6698f74 100644 --- a/src/section/dashboard/dashboard-bootcamp-tile.tsx +++ b/src/section/dashboard/dashboard-bootcamp-tile.tsx @@ -1,28 +1,30 @@ import { FC } from 'react' -import { Card, CardContent, CardMedia, Typography, Box, Chip } from '@mui/material' -import { useTheme, styled } from '@mui/material/styles' -import { SScrollBox, SBadgeHeader, GSBadgeImg } from 'theme/style' +import { CardContent, CardMedia, Typography, Box } from '@mui/material' +import { useTheme } from '@mui/material/styles' +import { SBadgeHeader, GSBadgeImg } from 'theme/style' import { photoLocation, badgeLocation } from 'util/asset-loc' -import { COMPONENT, FLEX, PLACEHOLDER, TYPOGRAPHY_VARIANT, VARIANT, SIZE } from 'constant' +import { COMPONENT, FLEX, PLACEHOLDER, TYPOGRAPHY } from 'constant' import { OPhotoCardMedia, SBootcampCard } from './option' import { BootcampCareer } from 'section/bootcamp' const BootcampTile: FC = ({ bootcamp }) => { const theme = useTheme() + const { badge, _id, photo } = bootcamp || {} + return ( - } /> - + } /> + - - {bootcamp.name} + + {bootcamp?.name} - - {bootcamp.location ? bootcamp.location.city + ', ' + bootcamp.location.country : PLACEHOLDER.NO_LOCATION} + + {bootcamp?.location ? bootcamp?.location?.city + ', ' + bootcamp?.location?.country : PLACEHOLDER.NO_LOCATION} = ({ bootcamp }) => { justifyContent: FLEX.FLEX_END, mt: 6 }}> - {bootcamp.careers && bootcamp.careers.length > 0 ? ( + {bootcamp?.careers && bootcamp?.careers.length > 0 ? ( ) : ( - +   )} diff --git a/src/section/index.ts b/src/section/index.ts index 693c483..7ad7671 100644 --- a/src/section/index.ts +++ b/src/section/index.ts @@ -1 +1,2 @@ export * from 'section/course' +export * from 'section/bootcamp' diff --git a/src/store/slice/course/endpoint.ts b/src/store/slice/course/endpoint.ts index 392757c..da76467 100644 --- a/src/store/slice/course/endpoint.ts +++ b/src/store/slice/course/endpoint.ts @@ -19,6 +19,13 @@ export const courseSlice = apiSlice.injectEndpoints({ method: GET }) }), + getAllCourseByBootcamp: builder.query({ + query: (id) => ({ + url: ServerPath.COURSE_BY_BOOTCAMP(id), + method: GET + }), + keepUnusedDataFor: 5 + }), createCourse: builder.mutation({ query: (data: any) => ({ url: ServerPath.COURSE, @@ -42,4 +49,11 @@ export const courseSlice = apiSlice.injectEndpoints({ }) }) -export const { useGetAllCourseQuery, useGetCourseQuery, useCreateCourseMutation, useUpdateCourseMutation, useDeleteCourseMutation } = courseSlice +export const { + useGetAllCourseQuery, + useGetCourseQuery, + useGetAllCourseByBootcampQuery, + useCreateCourseMutation, + useUpdateCourseMutation, + useDeleteCourseMutation +} = courseSlice diff --git a/src/store/store.ts b/src/store/store.ts index 4a4ae8e..0672feb 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -11,9 +11,8 @@ const store = configureStore({ reducer: rootReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ - serializableCheck: { - ignoredActions: [PERSIST, REHYDRATE] - } + serializableCheck: GLOBAL.APP_ENV === KEY.PRODUCTION ? undefined : false, + immutableCheck: false }).concat(apiSlice.middleware), devTools: GLOBAL.APP_ENV !== KEY.PRODUCTION diff --git a/src/theme/palette.ts b/src/theme/palette.ts index 395b818..10d818b 100644 --- a/src/theme/palette.ts +++ b/src/theme/palette.ts @@ -38,21 +38,23 @@ const ERROR = { contrastText: '#F2EED8' } +const ACTION = { + hover: alpha(GREY[500], 0.08), + selected: alpha(GREY[300], 0.16), + disabled: alpha(GREY[300], 0.8), + disabledBackground: alpha(GREY[400], 0.24), + focus: alpha(GREY[500], 0.24), + hoverOpacity: 0.08, + disabledOpacity: 0.48 +} + const COMMON = { common: COMMON_COLOR, primary: PRIMARY, error: ERROR, grey: GREY, divider: alpha(GREY[500], 0.24), - action: { - hover: alpha(GREY[500], 0.08), - selected: alpha(GREY[300], 0.16), - disabled: alpha(GREY[300], 0.8), - disabledBackground: alpha(GREY[400], 0.24), - focus: alpha(GREY[500], 0.24), - hoverOpacity: 0.08, - disabledOpacity: 0.48 - }, + action: ACTION, background: BRAND.background } diff --git a/src/theme/style/main.ts b/src/theme/style/main.ts index fefbdb5..369f6b1 100644 --- a/src/theme/style/main.ts +++ b/src/theme/style/main.ts @@ -24,6 +24,12 @@ export const SToolbar = styled(Toolbar)(({ theme }) => ({ } })) +export const SSpanBox = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center' +})) + // @bootcamp -- card export const SBadgeHeader = styled(CardHeader)({ position: 'relative', @@ -40,6 +46,7 @@ export const SScrollBox = styled(Box)(({ theme }) => ({ overflowX: 'auto', display: 'flex', marginRight: theme.spacing(2), + marginLeft: theme.spacing(2), flexDirection: 'row', flexWrap: 'nowrap', '&::-webkit-scrollbar': { diff --git a/src/theme/theme.ts b/src/theme/theme.ts index fd224ec..ae0e407 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -3,6 +3,7 @@ import { blue, green, grey, red } from '@mui/material/colors' import { Theme } from '@mui/material/styles' import ComponentOverride from './override' import { ASSET } from 'config' +import { KEY } from 'constant' export const COLORS = ['primary', 'secondary', 'info', 'success', 'warning', 'error'] @@ -75,6 +76,19 @@ const BRAND = { success: green[500] } +export const GREY = { + 0: '#F2EED8', + 100: '#F2EED8', + 200: '#F5F5F5', + 300: '#DFDFDF', + 400: '#DFE3E8', + 500: '#919EAB', + 600: '#637381', + 700: '#454F5B', + 800: '#212B36', + 900: '#161C24' +} + export const COMMON = { light: '#F2EED8', main: '#D9D9D9', @@ -83,6 +97,7 @@ export const COMMON = { red: red[500], green: green[500], dark: '#1E1E1F', + grey: GREY, black: '#000000', white: '#F2EED8' } @@ -101,7 +116,7 @@ const theme = { }, common: COMMON, brand: BRAND, - mode: 'light', + mode: KEY.LIGHT, backgroundImage: ASSET.PATTERN_BG }, shape: { @@ -156,7 +171,24 @@ const theme = { ...rawTheme.typography.subtitle1, fontSize: 18 }, + overline0: { + ...rawTheme.typography.overline, + fontSize: 10, + fontWeight: 'bold', + textTransform: 'uppercase' + }, overline: { + ...rawTheme.typography.overline, + fontSize: 12, + fontWeight: 'bold', + textTransform: 'uppercase' + }, + overline1: { + ...rawTheme.typography.overline, + fontSize: 14, + textTransform: 'uppercase' + }, + overline2: { ...rawTheme.typography.overline, fontSize: 16, textTransform: 'uppercase' diff --git a/src/theme/typography.ts b/src/theme/typography.ts index 0566f9d..6c9cde3 100644 --- a/src/theme/typography.ts +++ b/src/theme/typography.ts @@ -106,12 +106,24 @@ const typography = { lineHeight: 0.5, fontSize: pxToRem(12) }, + overline0: { + fontWeight: 900, + lineHeight: 1.5, + fontSize: pxToRem(10), + textTransform: 'uppercase' + }, overline: { fontWeight: 700, lineHeight: 1.5, fontSize: pxToRem(12), textTransform: 'uppercase' }, + overline1: { + fontWeight: 700, + lineHeight: 1.5, + fontSize: pxToRem(13), + textTransform: 'uppercase' + }, overline2: { fontWeight: 700, lineHeight: 1.5, diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 86323e7..29e3e11 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -90,36 +90,36 @@ declare global { } interface Bootcamp { - _id: Schema.Types.ObjectId + _id?: Schema.Types.ObjectId name: string - slug: string - description: string - website: string - phone: string - email: string - address: string - location: { - type: string - coordinates: [number | undefined, number | undefined] - formattedAddress: string - street: string - city: string - state: string - zipcode: string - country: string + slug?: string + description?: string + website?: string + phone?: string + email?: string + address?: string + location?: { + type?: string + coordinates?: [number | undefined, number | undefined] + formattedAddress?: string + street?: string + city?: string + state?: string + zipcode?: string + country?: string } - careers: [string] - duration: string - averageRating: number - averageCost: number - photo: string - badge: string - housing: boolean - jobAssistance: boolean - jobGuarantee: boolean - acceptGi: boolean - createdAt: Date - user: Schema.Types.ObjectId + careers?: [string] + duration?: string + averageRating?: number + averageCost?: number + photo?: string | undefined + badge?: string | undefined + housing?: boolean | undefined + jobAssistance?: boolean + jobGuarantee?: boolean + acceptGi?: boolean + createdAt?: Date + user?: Schema.Types.ObjectId } interface IResponse { @@ -151,7 +151,7 @@ declare global { location?: Location website: string photo: string - rating: number + rating?: number careers?: string[] | null } @@ -167,7 +167,7 @@ declare global { } interface UploadLocationProps { - bootcamp: BootcampCardProps + bootcamp?: Bootcamp | undefined } interface IDefaultAvatar { diff --git a/src/types/mui.d.ts b/src/types/mui.d.ts new file mode 100644 index 0000000..73172e7 --- /dev/null +++ b/src/types/mui.d.ts @@ -0,0 +1,12 @@ +import '@mui/material' + +declare module '@mui/material/Typography' { + interface TypographyPropsVariantOverrides { + overline0: true + overline1: true + overline2: true + h0: true + h7: true + body0: true + } +} diff --git a/src/util/asset-loc.ts b/src/util/asset-loc.ts index ca8ecef..14bfc66 100644 --- a/src/util/asset-loc.ts +++ b/src/util/asset-loc.ts @@ -1,14 +1,16 @@ import { ServerPath } from 'route/path' import { KEY } from 'constant' -export function photoLocation({ bootcamp }: UploadLocationProps) { - return bootcamp?.photo === KEY.PHOTO_DEFAULT - ? ServerPath.ORIGIN + `/upload/` + bootcamp?.photo - : ServerPath.ORIGIN + `/upload/${bootcamp._id}/` + bootcamp?.photo +export interface AssetLocation { + _id: string + photo?: string + badge?: string } -export function badgeLocation({ bootcamp }: UploadLocationProps) { - return bootcamp?.badge === KEY.BADGE_DEFAULT - ? ServerPath.ORIGIN + `/upload/badge/` + bootcamp?.badge - : ServerPath.ORIGIN + `/upload/badge/${bootcamp._id}/` + bootcamp?.badge +export function photoLocation({ _id, photo }: AssetLocation) { + return photo === KEY.PHOTO_DEFAULT ? ServerPath.ORIGIN + `/upload/` + photo : ServerPath.ORIGIN + `/upload/${_id}/` + photo +} + +export function badgeLocation({ _id, badge }: AssetLocation) { + return badge === KEY.BADGE_DEFAULT ? ServerPath.ORIGIN + `/upload/badge/` + badge : ServerPath.ORIGIN + `/upload/badge/${_id}/` + badge }