diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..8bc0bbf Binary files /dev/null and b/.DS_Store differ diff --git a/web/package-lock.json b/web/package-lock.json index 743d152..5e24af3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,11 +8,14 @@ "name": "mantine-vite-template", "version": "0.0.0", "dependencies": { - "@mantine/core": "7.8.1", - "@mantine/hooks": "7.8.1", + "@mantine/carousel": "^7.11.2", + "@mantine/core": "7.11.2", + "@mantine/hooks": "7.11.2", "@reduxjs/toolkit": "^2.2.5", "@tabler/icons-react": "^3.7.0", + "embla-carousel-react": "^8.1.7", "jwt-decode": "^4.0.0", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^9.1.2", @@ -49,7 +52,7 @@ "identity-obj-proxy": "^3.0.0", "jsdom": "^24.0.0", "postcss": "^8.4.35", - "postcss-preset-mantine": "1.15.0", + "postcss-preset-mantine": "1.17.0", "postcss-simple-vars": "^7.0.1", "prettier": "^3.2.5", "prop-types": "^15.8.1", @@ -2402,6 +2405,8 @@ }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { @@ -2411,6 +2416,8 @@ }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -2493,6 +2500,8 @@ }, "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { @@ -2546,6 +2555,8 @@ }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "license": "MIT", "engines": { @@ -2668,28 +2679,40 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "node_modules/@mantine/carousel": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/carousel/-/carousel-7.11.2.tgz", + "integrity": "sha512-SSK8VRMNhSzKV+icfjgBpc0XyN/SlwWZMw3bZ8/pg4kfhl8mzX21omG6ZaIaDdB3hs8CPLg1jZiQDjDAMniFpA==", + "peerDependencies": { + "@mantine/core": "7.11.2", + "@mantine/hooks": "7.11.2", + "embla-carousel-react": ">=7.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/@mantine/core": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.8.1.tgz", - "integrity": "sha512-dttbP2BhBzFJYBqgAQedJRca5+MXlJbppRhRsufnFeO7YC/UpZutOoHQ9dxGEQnhAWJ/d+wuRvYWG/gXex+wYQ==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.11.2.tgz", + "integrity": "sha512-T64RjdgY8UPAv249miW1lQyPPot1JbCcKKsAZMNQHgcttcxLhrFpKVvglc4/48hdSoxI4LYJPNvqp7zciZmucQ==", "dependencies": { "@floating-ui/react": "^0.26.9", - "clsx": "2.1.0", + "clsx": "^2.1.1", "react-number-format": "^5.3.1", "react-remove-scroll": "^2.5.7", "react-textarea-autosize": "8.5.3", "type-fest": "^4.12.0" }, "peerDependencies": { - "@mantine/hooks": "7.8.1", + "@mantine/hooks": "7.11.2", "react": "^18.2.0", "react-dom": "^18.2.0" } }, "node_modules/@mantine/hooks": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.8.1.tgz", - "integrity": "sha512-zCLqnxTUR2N6Awbt4rv/26UKTc75dXTVmCPsWUb6wdQExuC28fucG6kMoNYHVmOBDq9f3KP9zWOGDelHM2ogZA==", + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.11.2.tgz", + "integrity": "sha512-jhyVe/sbDEG2U8rr2lMecUPgQxcfr5hh9HazqGfkS7ZRIMDO7uJ947yAcTMGGkp5Lxtt5TBFt1Cb6tiB2/1agg==", "peerDependencies": { "react": "^18.2.0" } @@ -7569,8 +7592,9 @@ } }, "node_modules/clsx": { - "version": "2.1.0", - "license": "MIT", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "engines": { "node": ">=6" } @@ -8324,6 +8348,31 @@ "integrity": "sha512-7L8fC2Ey/b6SePDFKR2zHAy4mbdp1/38Yk5TsARO66W3hC5KEaeKMMHoxwtuH+jcu2AYLSn9QX04i95t6Fl1Hg==", "dev": true }, + "node_modules/embla-carousel": { + "version": "8.1.7", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.1.7.tgz", + "integrity": "sha512-b3kBr2H+S1gx4neki0P+aqN6cA5Ibjqy4CR3Ufi3X+Q3JpoNXJgOmJMSPkoP9DKcDREwADN6UWZzRwF2oo0y9Q==" + }, + "node_modules/embla-carousel-react": { + "version": "8.1.7", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.1.7.tgz", + "integrity": "sha512-ermMKzQ46LhXE4f81VBCVGxCJWvZfsu504dkyiUDO+cnEEPW8NlC2PpKULmiOWugusYWRLhQLjmyQs3b8vvOjA==", + "dependencies": { + "embla-carousel": "8.1.7", + "embla-carousel-reactive-utils": "8.1.7" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.1.7", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.1.7.tgz", + "integrity": "sha512-FDPcWjNtW04KSuvSfGbVeoB8yl5no3E0++HikO/uW12cNkMnWt68C4OBOakZQZlpUdRQSA9KCYoBuQzfpVGvZQ==", + "peerDependencies": { + "embla-carousel": "8.1.7" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "dev": true, @@ -9147,6 +9196,8 @@ }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { @@ -9156,6 +9207,8 @@ }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", "dependencies": { @@ -9949,6 +10002,8 @@ }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { @@ -11359,8 +11414,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -12628,9 +12682,10 @@ } }, "node_modules/postcss-preset-mantine": { - "version": "1.15.0", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.17.0.tgz", + "integrity": "sha512-ji1PMDBUf2Vsx/HE5faMSs1+ff6qE6YRulTr4Ja+6HD3gop8rSMTCYdpN7KrdsEg079kfBKkO/PaKhG9uR0zwQ==", "dev": true, - "license": "MIT", "dependencies": { "postcss-mixins": "^9.0.4", "postcss-nested": "^6.0.1" @@ -14350,11 +14405,15 @@ }, "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "license": "MIT", "engines": { @@ -14465,6 +14524,8 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -16459,6 +16520,8 @@ }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, @@ -16477,6 +16540,8 @@ }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "license": "MIT", "engines": { diff --git a/web/package.json b/web/package.json index afb8459..234c57c 100644 --- a/web/package.json +++ b/web/package.json @@ -19,11 +19,15 @@ "storybook:build": "storybook build" }, "dependencies": { - "@mantine/core": "7.8.1", - "@mantine/hooks": "7.8.1", + "@mantine/carousel": "^7.11.2", + "@mantine/core": "7.11.2", + "@mantine/dropzone": "^7.11.2", + "@mantine/hooks": "^7.11.2", "@reduxjs/toolkit": "^2.2.5", "@tabler/icons-react": "^3.7.0", + "embla-carousel-react": "^8.1.7", "jwt-decode": "^4.0.0", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^9.1.2", @@ -60,7 +64,7 @@ "identity-obj-proxy": "^3.0.0", "jsdom": "^24.0.0", "postcss": "^8.4.35", - "postcss-preset-mantine": "1.15.0", + "postcss-preset-mantine": "1.17.0", "postcss-simple-vars": "^7.0.1", "prettier": "^3.2.5", "prop-types": "^15.8.1", diff --git a/web/src/app/App.tsx b/web/src/app/App.tsx index 36adc57..978cbfc 100644 --- a/web/src/app/App.tsx +++ b/web/src/app/App.tsx @@ -5,10 +5,11 @@ import { theme } from './theme'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import './global.css'; +import '@mantine/carousel/styles.css'; export default function App() { return ( - + void; + filterFields: string[]; + setFilterFields: (filterFields: string[]) => void; +} +const JobFilter: FC = ({ + filterRoles, + setFilterRoles, + filterFields, + setFilterFields, +}) => { + const roles = [ + { value: 'internship', label: 'Internship' }, + { value: 'graduate', label: 'Graduate Roles' }, + { value: 'junior', label: 'Junior Roles' }, + ]; + + const fields = [ + { value: 'auto', label: 'Automation' }, + { value: 'electrical', label: 'Electrical' }, + { value: 'mechanical', label: 'Mechanical' }, + { value: 'mechatronics', label: 'Mechatronics' }, + { value: 'software', label: 'Software' }, + { value: 'other', label: 'Other' }, + ]; + + return ( + + Filters + + + {roles.map((role) => ( + + ))} + + + + + {fields.map((role) => ( + + ))} + + + + ); +}; + +export default JobFilter; diff --git a/web/src/app/components/JobBoard/JobListing.tsx b/web/src/app/components/JobBoard/JobListing.tsx new file mode 100644 index 0000000..2e5f7fc --- /dev/null +++ b/web/src/app/components/JobBoard/JobListing.tsx @@ -0,0 +1,104 @@ +import { Pagination, Stack } from '@mantine/core'; +import classes from './JobBoard.module.css'; +import JobListingItem from './JobListingItem'; +import { FC, useEffect, useState } from 'react'; +import { chunk } from 'lodash'; +import JobSearch from './JobSearch'; + +interface JobListingProps { + filterRoles: string[]; + filterFields: string[]; +} +const JobListing: FC = ({ filterRoles, filterFields }) => { + const [activePage, setPage] = useState(1); + const [search, setSearch] = useState(''); + + const [itemsPerPage, setItemsPerPage] = useState(4); + + const updateItemsPerPage = () => { + if (window.innerWidth > 1080) { + setItemsPerPage(6); + } else { + setItemsPerPage(4); + } + }; + + useEffect(() => { + updateItemsPerPage(); // Set initial value + window.addEventListener('resize', updateItemsPerPage); + + // Cleanup listener on component unmount + return () => { + window.removeEventListener('resize', updateItemsPerPage); + }; + }, []); + + // TODO: change this into actual data from backend, and apply filters & search + const jobListings = [ + { + title: 'Junior Software Developer', + company: 'WDCC', + location: 'Auckland CBD, Auckland', + description: + 'In this role, you will work to design, develop, and maintain software solutions using .NET, Typescript, and JavaScript.', + }, + { + title: 'Junior Software Developer', + company: 'WDCC', + location: 'Auckland CBD, Auckland', + description: + 'In this role, you will work to design, develop, and maintain software solutions using .NET, Typescript, and JavaScript.', + }, + { + title: 'Junior Software Developer', + company: 'WDCC', + location: 'Auckland CBD, Auckland', + description: + 'In this role, you will work to design, develop, and maintain software solutions using .NET, Typescript, and JavaScript.', + }, + { + title: 'Junior Software Developer', + company: 'WDCC', + location: 'Auckland CBD, Auckland', + description: + 'In this role, you will work to design, develop, and maintain software solutions using .NET, Typescript, and JavaScript.', + }, + { + title: 'Junior Software Developer', + company: 'WDCC', + location: 'Auckland CBD, Auckland', + description: + 'In this role, you will work to design, develop, and maintain software solutions using .NET, Typescript, and JavaScript.', + }, + ]; + + // chunk all listings into four for per page display + // TODO: filter the jobListings before chunking + const chunkedJobListings = chunk(jobListings, itemsPerPage); + + const jobListingItems = chunkedJobListings[activePage - 1].map((jobListingItem) => ( + + )); + return ( + + +
{jobListingItems}
+
+ +
+
+ ); +}; + +export default JobListing; diff --git a/web/src/app/components/JobBoard/JobListingItem.tsx b/web/src/app/components/JobBoard/JobListingItem.tsx new file mode 100644 index 0000000..486ea18 --- /dev/null +++ b/web/src/app/components/JobBoard/JobListingItem.tsx @@ -0,0 +1,26 @@ +import { FC } from 'react'; +import classes from './JobBoard.module.css'; +import { Text } from '@mantine/core'; + +interface JobListingItemProps { + title: string; + company: string; + location: string; + description: string; +} +const JobListingItem: FC = ({ title, company, location, description }) => { + return ( +
+ {title} +
+ {company} +
+ {location} + + {description} + +
+ ); +}; + +export default JobListingItem; diff --git a/web/src/app/components/JobBoard/JobSearch.tsx b/web/src/app/components/JobBoard/JobSearch.tsx new file mode 100644 index 0000000..94d3464 --- /dev/null +++ b/web/src/app/components/JobBoard/JobSearch.tsx @@ -0,0 +1,32 @@ +import { Flex, TextInput, Title } from '@mantine/core'; +import classes from './JobBoard.module.css'; +import { IconSearch } from '@tabler/icons-react'; +import { FC } from 'react'; + +interface JobSearchProps { + search: string; + setSearch: (search: string) => void; +} + +const JobSearch: FC = ({ search, setSearch }) => { + const handleChange = (event: React.ChangeEvent) => { + setSearch(event.target.value); + }; + + return ( + +
+ FIND JOBS + } + size="md" + value={search} + onChange={handleChange} + /> +
+
+ ); +}; + +export default JobSearch; diff --git a/web/src/app/components/JobCardCarousel/JobCard.module.css b/web/src/app/components/JobCardCarousel/JobCard.module.css new file mode 100644 index 0000000..cbc608a --- /dev/null +++ b/web/src/app/components/JobCardCarousel/JobCard.module.css @@ -0,0 +1,4 @@ +.text { + position: relative; + color: var(--mantine-color-customWhite-1); +} diff --git a/web/src/app/components/JobCardCarousel/JobCard.tsx b/web/src/app/components/JobCardCarousel/JobCard.tsx new file mode 100644 index 0000000..41e4e42 --- /dev/null +++ b/web/src/app/components/JobCardCarousel/JobCard.tsx @@ -0,0 +1,149 @@ +import { ActionIcon, Text, Button, Paper, Flex, Stack } from '@mantine/core'; +import classes from './JobCard.module.css'; +import { useState } from 'react'; +import { UserType } from '@/app/features/user/userSlice'; +import { IconTrash } from '@tabler/icons-react'; +// dummy data -- change later when we have real data +export interface JobCardProps { + title: string; + subtitle: string; + description: string; + jobLink: string; + jobID: string; +} + +export function JobCard({ data }: { data: JobCardProps }) { + // const userType = useSelector((state: RootState) => state.user.userType); + const [userType, setUserType] = useState('sponsor'); + + console.log( + 'Change this JobCard component to use real userType from Redux store once user integration is implemented' + ); + + const handleDeleteJob = () => { + console.log('Delete job with ID: ', data.jobID); + }; + + const handleEditJob = () => { + console.log('Edit job with ID: ', data.jobID); + }; + + const handleViewJob = () => { + console.log('View job with ID: ', data.jobID); + }; + + // methods to get elements based on user type + const getElementBasedOnUserType = (element: string) => { + switch (userType) { + case 'sponsor': + return getSponsorElements(element); + case 'student': + return getStudentElements(element); + case 'alumni': + return getAlumniElements(element); + case 'admin': + return getSponsorElements(element); + } + }; + + const getSponsorElements = (element: string) => { + switch (element) { + case 'deleteBtn': + return ( + + + + ); + case 'jobBtn': + return ( + + ); + } + }; + + const getStudentElements = (element: string) => { + switch (element) { + case 'deleteBtn': + return null; + case 'jobBtn': + return ( + + ); + } + }; + + const getAlumniElements = (element: string) => { + switch (element) { + case 'deleteBtn': + return null; + case 'jobBtn': + return ( + + ); + } + }; + + return ( + + + {/* Job Title */} + + + + {data.title} + + {getElementBasedOnUserType('deleteBtn')} + + + {/* Job Subtite */} + + {data.subtitle} + + + {/* Job Description */} + + {data.description} + + + + + {getElementBasedOnUserType('jobBtn')} + + + {/* Job ID */} + + + #{data.jobID} + + + + + ); +} diff --git a/web/src/app/components/JobCardCarousel/JobCarousel.module.css b/web/src/app/components/JobCardCarousel/JobCarousel.module.css new file mode 100644 index 0000000..0c2ba15 --- /dev/null +++ b/web/src/app/components/JobCardCarousel/JobCarousel.module.css @@ -0,0 +1,6 @@ +.control { + &[data-inactive] { + opacity: 0; + cursor: default; + } +} diff --git a/web/src/app/components/JobCardCarousel/JobCarousel.tsx b/web/src/app/components/JobCardCarousel/JobCarousel.tsx new file mode 100644 index 0000000..53894ce --- /dev/null +++ b/web/src/app/components/JobCardCarousel/JobCarousel.tsx @@ -0,0 +1,38 @@ +import { Carousel } from '@mantine/carousel'; +import { JobCard, JobCardProps } from './JobCard'; +import classes from './JobCarousel.module.css'; +import { IconChevronLeft, IconChevronRight } from '@tabler/icons-react'; +import { rem } from '@mantine/core'; + +export interface JobCarouselProps { + jobs: JobCardProps[]; +} + +export function JobCarousel(data: JobCarouselProps) { + // Map over the job data and create a JobCard for each job + const jobCards = data.jobs.map((job, idx) => ( + + )); + + const slides = jobCards.map((jobCard, idx) => ( + {jobCard} + )); + + return ( + } + previousControlIcon={} + align="start" + w="95%" + classNames={classes} + > + {/* Render each JobCard as a slide */} + {slides} + + ); +} diff --git a/web/src/app/components/Modal/EditAvatar.tsx b/web/src/app/components/Modal/EditAvatar.tsx new file mode 100644 index 0000000..2bc8c55 --- /dev/null +++ b/web/src/app/components/Modal/EditAvatar.tsx @@ -0,0 +1,45 @@ +import { Box, Avatar, Divider, Flex, ActionIcon, Text, FileButton } from '@mantine/core'; +import styles from './Modal.module.css'; +import { IconCameraPlus } from '@tabler/icons-react'; +import { IconTrash } from '@tabler/icons-react'; +import { useState } from 'react'; + +interface EditAvatarProps { + avatar: string; +} + +export const EditAvatar = ({ avatar }: EditAvatarProps) => { + const [file, setFile] = useState(null); + + return ( + + + + + + + + {(props) => ( + + )} + + + + + + ); +}; diff --git a/web/src/app/components/Modal/EditBannerModal.tsx b/web/src/app/components/Modal/EditBannerModal.tsx new file mode 100644 index 0000000..cb5f8c0 --- /dev/null +++ b/web/src/app/components/Modal/EditBannerModal.tsx @@ -0,0 +1,43 @@ +import { Box, Divider, Flex, ActionIcon, Text, Image, FileButton } from '@mantine/core'; +import styles from './Modal.module.css'; +import { IconPencil } from '@tabler/icons-react'; +import { IconTrash } from '@tabler/icons-react'; +import { useState } from 'react'; + +interface EditBannerModalProps { + banner: string; +} + +export const EditBannerModal = ({ banner }: EditBannerModalProps) => { + const [file, setFile] = useState(null); + return ( + + + + + + + + {(props) => ( + + )} + + + + + ); +}; diff --git a/web/src/app/components/Modal/EditModal.tsx b/web/src/app/components/Modal/EditModal.tsx new file mode 100644 index 0000000..ff8835e --- /dev/null +++ b/web/src/app/components/Modal/EditModal.tsx @@ -0,0 +1,39 @@ +import { Modal, Box, Button } from '@mantine/core'; +import { IconXboxX } from '@tabler/icons-react'; +import { ReactNode } from 'react'; +import styles from './Modal.module.css'; + +type ModalProp = { + opened: boolean; + close: () => void; + content: ReactNode; + title: string; +}; + +export default function EditModal({ opened, close, content, title }: ModalProp) { + return ( + , + }} + centered + size="100%" + classNames={{ + content: styles.content, + body: styles.body, + title: styles.title, + }} + title={title} + > + {content} + + + + + + ); +} diff --git a/web/src/app/components/Modal/EditStudentProfile.tsx b/web/src/app/components/Modal/EditStudentProfile.tsx new file mode 100644 index 0000000..0d0d5a5 --- /dev/null +++ b/web/src/app/components/Modal/EditStudentProfile.tsx @@ -0,0 +1,89 @@ +import { Tabs, Box, Button, Select, Divider, Modal } from '@mantine/core'; +import { AboutTab } from '../Tabs/AboutTab'; +import { EducationTab } from '../Tabs/EducationTab'; +import { SkillsTab } from '../Tabs/SkillsTab'; +import { CVTab } from '../Tabs/CVTab'; +import styles from './Modal.module.css'; +import { useMediaQuery } from '@mantine/hooks'; +import { useState } from 'react'; + +export const EditStudentProfile = () => { + const [activeTab, setActiveTab] = useState('about'); + const [isModalOpen, setIsModalOpen] = useState(true); + const tabOptions = [ + { value: 'about', label: 'About Me' }, + { value: 'education', label: 'Education' }, + { value: 'skills', label: 'Skills' }, + { value: 'cv', label: 'CV' }, + ]; + + const isMobile = useMediaQuery('(max-width: 430px)'); //mobile screen + + const renderContent = () => { + switch (activeTab) { + case 'about': + return ; + case 'education': + return ; + case 'skills': + return ; + case 'cv': + return ; + default: + return null; + } + }; + + return ( + + {isMobile ? ( //conditionally render mobile tab dropdown + <> +