diff --git a/src/component/form/form-field/form-field.tsx b/src/component/form/form-field/form-field.tsx index a17f057..fff5a45 100644 --- a/src/component/form/form-field/form-field.tsx +++ b/src/component/form/form-field/form-field.tsx @@ -20,6 +20,8 @@ interface ExtendedFormFieldProps extends FormFieldProps { errors?: any helperText?: string defaultValue?: string + isAccount?: boolean + disabled?: boolean setValue?: (value: string) => void } @@ -37,6 +39,8 @@ const FormField = forwardRef( helperText, isGithub, errors, + isAccount = false, + disabled = false, required = false }: ExtendedFormFieldProps, ref @@ -56,10 +60,10 @@ const FormField = forwardRef( return ( { - const theme = useTheme() - const { data: account, isLoading, refetch, error } = useAccountQuery({}) as any + const [isEditState, setIsEditState] = useState(false) + const { data: account, isLoading, error, refetch } = useAccountQuery({}) as any + const [updateAccount] = useUpdateUserMutation() const { Icon, iconSrc: editIconSrc } = useIcon(ICON_NAME.EDIT) const { iconSrc: downIconSrc } = useIcon(ICON_NAME.CHEVRON_DOWN) + const { iconSrc: saveSrc } = useIcon(ICON_NAME.SAVE) + const theme = useTheme() const date = new Date(account?.data?.createdAt) const year = date.getFullYear() - const fullName = account?.data?.firstname + ' ' + account?.data?.lastname + const fullname = account?.data?.firstname + ' ' + account?.data?.lastname + + const accountSchema = Yup.object().shape({ + firstname: Yup.string().required(), + lastname: Yup.string().required(), + fullname: Yup.string(), + location: Yup.string(), + email: Yup.string().email(), + password: Yup.string(), + confirmPassword: Yup.string().oneOf([Yup.ref('password'), ''], 'Passwords must match') + }) + + const methods = useForm({ + resolver: yupResolver(accountSchema), + defaultValues: { + firstname: account?.data?.firstname || '', + lastname: account?.data?.lastname || '', + email: account?.data?.email || '', + location: account?.data?.location || '', + fullname, + password: '', + confirmPassword: '' + } + }) + + const { + control, + handleSubmit, + reset, + setError, + formState: { errors, isSubmitting, isSubmitSuccessful } + } = methods + + const onSubmit = async (data: any) => { + try { + if (account) { + await updateAccount(data) + refetch() + } + } catch (error: any) { + setError('email', { + type: 'manual', + message: error.message + }) + } + } + + const handleEdit = () => { + setIsEditState(!isEditState) + } + + // refetch account data + useEffect(() => { + refetch() + }, [refetch]) return ( @@ -57,51 +120,100 @@ const UserAccount = () => { - - - + + + + + {isEditState ? ( + + + + + + + + ) : ( + + )} + @@ -120,21 +232,23 @@ const UserAccount = () => { - + + + diff --git a/src/page/course/course.tsx b/src/page/course/course.tsx index 29fa019..3022637 100644 --- a/src/page/course/course.tsx +++ b/src/page/course/course.tsx @@ -1,6 +1,6 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { m } from 'framer-motion' -import { useGetAllCourseQuery } from 'store/slice' +import { useGetAllBootcampQuery } from 'store/slice' import { MotionLazyContainer } from 'component/motion' import { Box, Grid, Pagination } from '@mui/material' import { CourseCard } from 'section/course' @@ -10,14 +10,22 @@ import { Typography } from 'component/typography' import { CourseSearch } from 'section/course' import { ASSET } from 'config' import { LABEL } from 'constant' + const Course = () => { const [currentPage, setCurrentPage] = useState(1) - const { data, error, isLoading } = useGetAllCourseQuery() + const { data, error, isLoading, refetch } = useGetAllBootcampQuery() const handlePageChange = (event: any, value: any) => { setCurrentPage(value) } + useEffect(() => { + if (!isLoading) { + window.scrollTo(0, 0) + refetch() + } + }, []) + return ( diff --git a/src/section/bootcamp/bootcamp-card.tsx b/src/section/bootcamp/bootcamp-card.tsx index 233f0b3..f1ebcdc 100644 --- a/src/section/bootcamp/bootcamp-card.tsx +++ b/src/section/bootcamp/bootcamp-card.tsx @@ -1,55 +1,21 @@ import { FC } from 'react' -import { Box, CardContent, CardMedia, Chip, Grid, Typography, Rating } from '@mui/material' -import { SScrollBox, SBadgeHeader } from 'theme/style' -import { ServerPath } from 'route/path' -import { KEY } from 'constant' -import { ASSET } from 'config' - -interface BootcampCardProps { - _id?: string - name: string - badge: string - description: string - address: string - photo: string - rating: number - careers?: string[] | null -} - -interface UploadLocationProps { - bootcamp: BootcampCardProps -} - -function photoLocation({ bootcamp }: UploadLocationProps) { - return bootcamp?.photo === KEY.PHOTO_DEFAULT - ? ServerPath.ORIGIN + `/upload/` + bootcamp?.photo - : ServerPath.ORIGIN + `/upload/${bootcamp._id}/` + bootcamp?.photo -} - -function badgeLocation({ bootcamp }: UploadLocationProps) { - return bootcamp?.badge === KEY.BADGE_DEFAULT - ? ServerPath.ORIGIN + `/upload/badge/` + bootcamp?.badge - : ServerPath.ORIGIN + `/upload/badge/${bootcamp._id}/` + bootcamp?.badge -} +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 { badgeLocation, photoLocation } from 'util/asset-loc' const BootcampCard: FC = ({ bootcamp }) => { return ( - + @@ -58,18 +24,18 @@ const BootcampCard: FC = ({ bootcamp }) => { - + {bootcamp?.name} - + @@ -83,19 +49,15 @@ const BootcampCard: FC = ({ bootcamp }) => { }} /> - {bootcamp.description} + {bootcamp.description} - - {bootcamp?.address} + + {bootcamp?.location?.city}, {bootcamp?.location?.state} - - {bootcamp?.careers?.map((chip: string, index: number) => ( - - ))} - + - + ) } diff --git a/src/section/bootcamp/bootcamp-career.tsx b/src/section/bootcamp/bootcamp-career.tsx new file mode 100644 index 0000000..26902b7 --- /dev/null +++ b/src/section/bootcamp/bootcamp-career.tsx @@ -0,0 +1,16 @@ +import { FC } from 'react' +import { Chip } from '@mui/material' +import { SScrollBox } from 'theme/style' +import { SIZE, VARIANT } from 'constant' + +const BootcampCareer: FC = ({ bootcamp }) => { + return ( + + {bootcamp?.careers?.map((chip: string, index: number) => ( + + ))} + + ) +} + +export default BootcampCareer diff --git a/src/section/bootcamp/index.ts b/src/section/bootcamp/index.ts index 11f5914..6ae88dc 100644 --- a/src/section/bootcamp/index.ts +++ b/src/section/bootcamp/index.ts @@ -1,3 +1,4 @@ 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' diff --git a/src/section/course/course-card.tsx b/src/section/course/course-card.tsx index 6287c8f..ed177d9 100644 --- a/src/section/course/course-card.tsx +++ b/src/section/course/course-card.tsx @@ -4,16 +4,18 @@ import { dispatch } from 'store' import { useGetBootcampQuery } from 'store/slice' import { Box, CardContent, CardMedia, Chip, Grid, Typography, Rating } from '@mui/material' import { ServerPath } from 'route/path' -import { KEY } from 'constant' +import { KEY, PLACEHOLDER } from 'constant' import { ASSET } from 'config' import { capitalize } from 'util/format' +import { badgeLocation } from 'util/asset-loc' interface CourseCardProps { course: Course } const CourseCard: FC = ({ course }) => { - const { data: bootcamp, error, isLoading } = useGetBootcampQuery(course?.bootcamp?._id) + const { data, error, isLoading, refetch } = useGetBootcampQuery(course?.bootcamp?._id) + const { data: bootcamp } = data let minimumSkill = '' @@ -21,11 +23,11 @@ const CourseCard: FC = ({ course }) => { minimumSkill = capitalize(course?.minimumSkill) } - function badgeLocation(bootcamp: Bootcamp) { - return bootcamp?.badge === KEY.BADGE_DEFAULT - ? ServerPath.ORIGIN + `/upload/badge/` + bootcamp?.badge - : ServerPath.ORIGIN + `/upload/badge/${bootcamp?._id}/` + bootcamp?.badge - } + useEffect(() => { + if (!isLoading) { + refetch() + } + }, []) return ( = ({ course }) => { - {course?.title.charAt(0).toUpperCase() + course?.title.slice(1)} + {course?.title?.charAt(0).toUpperCase() + course?.title?.slice(1)} - Duration: {course?.duration + ' weeks' || 'No duration specified'} - Tuition: {course?.tuition || 'No tuition specified'} + Duration: {course?.duration + ' weeks' || PLACEHOLDER.NO_DURATION} + Tuition: {course?.tuition || PLACEHOLDER.NO_TUITION} = ({ course }) => { - {minimumSkill || 'No minimum skill required'} + {minimumSkill || PLACEHOLDER.NO_MINIMUM_SKILL} {course.description.charAt(0).toUpperCase() + course.description.slice(1)} - {course?.bootcamp?.name.charAt(0).toUpperCase() + course?.bootcamp?.name.slice(1) || 'Solo'} + {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 24f5bbd..786cb48 100644 --- a/src/section/dashboard/dashboard-bootcamp-rd.tsx +++ b/src/section/dashboard/dashboard-bootcamp-rd.tsx @@ -1,28 +1,24 @@ -import { ChangeEvent, useState } from 'react' +import { ElementType, useEffect, useState } from 'react' import { m } from 'framer-motion' -import { Box, Grid, Divider, Container, Tab, Tabs } from '@mui/material' -import { Theme } from '@mui/material/styles' -import { SxProps } from '@mui/system' -import { ASSET } from 'config' +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 { CATEGORY } from 'config' import BootcampTile from './dashboard-bootcamp-tile' - -const item: SxProps = { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - px: 5 -} - -const number = { - fontSize: 24, - fontFamily: 'default', - color: 'secondary.main', - fontWeight: 'medium' -} +import { AuthPath } from 'route/path' function DashboardBootcampRundown() { + const [skValue, setSkValue] = useState([0, 1, 2, 3, 4, 5]) const [value, setValue] = useState(0) + const { data, error, isLoading } = useGetAllBootcampQuery() + + useEffect(() => { + if (data) { + window.scrollTo(0, 0) + } + }, []) const handleChange = (event: React.SyntheticEvent, newValue: any) => { setValue(newValue) @@ -30,97 +26,57 @@ function DashboardBootcampRundown() { return ( - + - - - New Bootcamps + + + {LABEL.NEW_BOOTCAMPS} - - - - - - - - + + + + + {Object.values(CATEGORY).map((category: string, index: number) => ( + + ))} - - - - - - - - - - - - - - - + + {!isLoading ? ( + data?.data?.map((bootcamp: any, index: number) => ( + + + + )) + ) : ( + + {skValue.map((value, index) => ( + + ))} + + )} + + - - - + ) } diff --git a/src/section/dashboard/dashboard-bootcamp-tile.tsx b/src/section/dashboard/dashboard-bootcamp-tile.tsx index b6dc5e4..d238dfc 100644 --- a/src/section/dashboard/dashboard-bootcamp-tile.tsx +++ b/src/section/dashboard/dashboard-bootcamp-tile.tsx @@ -1,83 +1,45 @@ -import { Card, CardContent, CardMedia, CardHeader, Typography, Box, Chip } from '@mui/material' +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 { photoLocation, badgeLocation } from 'util/asset-loc' +import { COMPONENT, FLEX, PLACEHOLDER, TYPOGRAPHY_VARIANT, VARIANT, SIZE } from 'constant' +import { OPhotoCardMedia, SBootcampCard } from './option' +import { BootcampCareer } from 'section/bootcamp' -interface BootcampTileProps { - title: string - institution: string - badge: string - imageUrl: string - chips?: string[] | null -} - -const SBadgeHeader = styled(CardHeader)({ - position: 'relative', -}) - -const SCard = styled(Card)(({ theme }) => ({ - width: '250px', - boxShadow: 'none', -})) - -const BootcampTile = ({ title, institution, badge, imageUrl, chips }: BootcampTileProps) => { +const BootcampTile: FC = ({ bootcamp }) => { const theme = useTheme() + return ( - - - } - /> - + + } /> + - - {title} - - - {institution} + + + {bootcamp.name} + + + + + {bootcamp.location ? bootcamp.location.city + ', ' + bootcamp.location.country : PLACEHOLDER.NO_LOCATION} - {chips && chips.length > 0 ? ( - chips.map((chip: any, index: number) => ( - - #{chip} - - )) + display: FLEX.FLEX, + justifyContent: FLEX.FLEX_END, + mt: 6 + }}> + {bootcamp.careers && bootcamp.careers.length > 0 ? ( + ) : ( - +   )} - + ) } diff --git a/src/section/dashboard/index.ts b/src/section/dashboard/index.ts index 941ade3..7b958d1 100644 --- a/src/section/dashboard/index.ts +++ b/src/section/dashboard/index.ts @@ -5,3 +5,4 @@ export { default as DashboardBootcampRundown } from './dashboard-bootcamp-rd' export { default as DashboardFeedbackSection } from './dashboard-feedback' export { default as ProductValue } from './dashboard-value' export { default as SDashboardSection } from './dashboard-section' +export * from './option' diff --git a/src/section/dashboard/option.tsx b/src/section/dashboard/option.tsx new file mode 100644 index 0000000..c1674d1 --- /dev/null +++ b/src/section/dashboard/option.tsx @@ -0,0 +1,36 @@ +import { Card } from '@mui/material' +import { styled } from '@mui/material/styles' +import { COMPONENT } from 'constant' + +export const OPhotoCardMedia = { + component: COMPONENT.IMG, + height: '230', + alt: 'bootcamp photo', + sx: { + objectFit: 'cover', + objectPosition: 'top', + marginTop: '-40px', + height: '230px' + } +} + +export const OBadgeHeaderImg = { + alt: 'bootcamp badge', + style: { + height: 40, + width: 40, + overflow: 'hidden', + objectFit: 'cover', + position: 'absolute', + top: 200, + right: '10%', + zIndex: 1, + transform: 'translateX(-50%)' + } +} + +export const SBootcampCard = styled(Card)(({ theme }) => ({ + width: '250px', + height: '400px', + boxShadow: 'none' +})) diff --git a/src/theme/style/main.ts b/src/theme/style/main.ts index d422a49..fefbdb5 100644 --- a/src/theme/style/main.ts +++ b/src/theme/style/main.ts @@ -1,7 +1,8 @@ -import { Drawer, ListItem, Box, Toolbar, Card, CardHeader } from '@mui/material' +import { Drawer, Container, ListItem, Box, Toolbar, Card, Grid, CardHeader, CardMedia } from '@mui/material' import { LoadingButton } from '@mui/lab' import { styled } from '@mui/material/styles' import { APP_NAVBAR } from 'config' +import { ASSET } from 'config' export const GSLoadingButton = styled(LoadingButton)(({ theme }) => ({ backgroundColor: theme.palette.primary.dark, @@ -25,7 +26,9 @@ export const SToolbar = styled(Toolbar)(({ theme }) => ({ // @bootcamp -- card export const SBadgeHeader = styled(CardHeader)({ - position: 'relative' + position: 'relative', + padding: 0, + paddingY: 2 }) export const SCard = styled(Card)(({ theme }) => ({ @@ -41,9 +44,53 @@ export const SScrollBox = styled(Box)(({ theme }) => ({ flexWrap: 'nowrap', '&::-webkit-scrollbar': { display: 'none' + }, + '&': { + scrollBehavior: 'smooth' } })) +export const SScrollGrid = styled(Grid)(({ theme }) => ({ + overflowX: 'auto', + display: 'flex', + marginRight: theme.spacing(2), + flexDirection: 'row', + flexWrap: 'nowrap', + '&::-webkit-scrollbar': { + display: 'none' + }, + '&': { + scrollBehavior: 'smooth' + } +})) + +export const GSContainerGrid = styled(Grid)(({ theme }) => ({ + marginBottom: theme.spacing(4), + borderRadius: theme.spacing(2), + bgcolor: theme.palette.grey[300], + backgroundImage: `url(${ASSET.DOT_MATRIX_BG})` +})) + +// @bootcamp : tile +export const GSPhotoCardMedia = styled(CardMedia)(({ theme }) => ({ + objectFit: 'cover', + objectPosition: 'top', + marginTop: '-40px', + height: '230px' +})) + +export const GSBadgeImg = styled('img')(({ theme }) => ({ + height: 40, + width: 40, + overflow: 'hidden', + objectFit: 'cover', + position: 'absolute', + top: 200, + right: '10%', + zIndex: 1, + transform: 'translateX(-50%)' +})) + // @dashboard -- drawer export const SDrawer = styled(Drawer)(({ theme }) => ({ marginTop: '64px', @@ -84,6 +131,30 @@ export const SBox = styled(Box)(({ theme }) => ({ transition: 'all 0.5s ease' })) +export const GSBox = styled(Box)(({ theme }) => ({ + pointerEvents: 'none', + position: 'absolute', + top: -190, + opacity: 0.9, + zIndex: -3 +})) + +export const GSDividerBox = styled(Box)(({ theme }) => ({ + display: 'flex-start', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + marginTop: theme.spacing(2) +})) + +export const GSRundownContainer = styled(Container)(({ theme }) => ({ + marginTop: theme.spacing(10), + marginBottom: theme.spacing(15), + position: 'relative', + flexDirection: 'column', + alignItems: 'center' +})) + // @fallback -- page export const FullBox = { maxWidth: 'sm' as const, diff --git a/src/types/global.d.ts b/src/types/global.d.ts index b99e6e2..86323e7 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -143,6 +143,33 @@ declare global { xl?: number } + interface BootcampCardProps { + _id?: string + name: string + badge: string + description: string + location?: Location + website: string + photo: string + rating: number + careers?: string[] | null + } + + interface Location { + type?: string + coordinates?: number[] + formattedAddress?: string + street?: string + state?: string | null + city?: string + zipcode?: string + country: string + } + + interface UploadLocationProps { + bootcamp: BootcampCardProps + } + interface IDefaultAvatar { color?: COLOR firstName?: string diff --git a/src/util/asset-loc.ts b/src/util/asset-loc.ts new file mode 100644 index 0000000..ca8ecef --- /dev/null +++ b/src/util/asset-loc.ts @@ -0,0 +1,14 @@ +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 function badgeLocation({ bootcamp }: UploadLocationProps) { + return bootcamp?.badge === KEY.BADGE_DEFAULT + ? ServerPath.ORIGIN + `/upload/badge/` + bootcamp?.badge + : ServerPath.ORIGIN + `/upload/badge/${bootcamp._id}/` + bootcamp?.badge +} diff --git a/src/util/index.ts b/src/util/index.ts index e557b74..72d684f 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -4,3 +4,4 @@ export { default as ErrorBoundary } from './error-boundary' export { default as localStorageSpace } from './local-storage-space' export * from './time' export * from './format' +export * from './asset-loc'