diff --git a/assets/images/astronaut.png b/assets/images/astronaut.png new file mode 100644 index 0000000..a43f441 Binary files /dev/null and b/assets/images/astronaut.png differ diff --git a/assets/images/astronautIcon.png b/assets/images/astronautIcon.png new file mode 100644 index 0000000..733033c Binary files /dev/null and b/assets/images/astronautIcon.png differ diff --git a/components/Card/Card.tsx b/components/Card/Card.tsx new file mode 100644 index 0000000..f0e261f --- /dev/null +++ b/components/Card/Card.tsx @@ -0,0 +1,32 @@ +import astronautPng from "@/assets/images/astronautIcon.png" +import Image from "next/image"; +interface CardProps { + fullName: string; + nationality: string; + age: number; + profession: string; + image:string +} +const Card: React.FC = ({ fullName, nationality, age, profession, image }) => { + return ( +
+
+ {"astronaut"} + cat +
+ +
+
+ {fullName} +
+
+

Nationality: {nationality}

+

Age: {age}

+

Profession: {profession}

+
+
+
+ ); +}; + +export default Card; \ No newline at end of file diff --git a/lib/crew.ts b/lib/crew.ts index b49024d..8887df6 100644 --- a/lib/crew.ts +++ b/lib/crew.ts @@ -1,4 +1,80 @@ -/** - * @todo Prepare a method to return a list of crew members - * @description The list should only include crew members aged 30 to 40 - */ +import fs from 'fs'; +import path from 'path'; +// @ts-ignore +import yaml from 'js-yaml'; + +type CrewMember = { + fullName: string; + nationality: string; + age: number; + profession: string; +}; + +type YamlCrewMember = { + name: string; + nationality: string; + years_old: number; + occupation: string; +}; + +type JsonCrewMember = { + firstName: string; + lastName: string; + nationality: string; + age: number; + profession: string; +}; + +type FileType = "json" | "yaml"; + +type DataParams = { + type:FileType | string + path:string +} + +const sortBetweenAge = (member: CrewMember) => member.age >= 30 && member.age <= 40; + +const paginateAndFilterData = (data: CrewMember[], page: number): CrewMember[] => { + const itemsPerPage = 8; + const startIndex = (page - 1) * itemsPerPage; + + return data.sort((a, b) => a.fullName.localeCompare(b.fullName)).filter(sortBetweenAge).slice(startIndex, startIndex + itemsPerPage); +} + +const getFileData = (item:DataParams, page:number, count:number) =>{ + switch (item.type) { + case "json": + const jsonFile = fs.readFileSync(path.resolve(item.path), 'utf8'); + const jsonData: JsonCrewMember[] = JSON.parse(jsonFile); + const serializedJsonData = jsonData.map((val)=>{ + return { + fullName:`${val.firstName} ${val.lastName}`, + nationality: val.nationality, + age: val.age, + profession: val.profession + } + }) + return serializedJsonData + case "yaml": + const yamlFile = fs.readFileSync(path.resolve(item.path), 'utf8'); + const yamlData: YamlCrewMember[] = yaml.load(yamlFile) as YamlCrewMember[]; + const serializedYamlData = yamlData.map((val)=>{ + return { + nationality:val.nationality, + fullName:val.name, + age:val.years_old, + profession:val.occupation + } + }) + return serializedYamlData + default: + return []; + } +} + +export const astronautsList = (data: DataParams[], page: number): CrewMember[] => { + const astronauts= data.reduce((acc: CrewMember[], val: DataParams) => { + return [...acc, ...getFileData(val, page, data.length)]; + }, []); + return paginateAndFilterData(astronauts,page) +} \ No newline at end of file diff --git a/next.config.js b/next.config.js index a843cbe..d493ce6 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + images:{ + domains:['cdn2.thecatapi.com'] + } } module.exports = nextConfig diff --git a/package-lock.json b/package-lock.json index fd319c8..b297c43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,11 @@ "name": "winter-camp-2024", "version": "0.1.0", "dependencies": { + "js-yaml": "^4.1.0", "next": "13.5.6", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "swr": "^2.2.4" }, "devDependencies": { "@types/node": "^20", @@ -636,8 +638,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-query": { "version": "5.3.0", @@ -2652,7 +2653,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -3956,6 +3956,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz", + "integrity": "sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tailwindcss": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", @@ -4238,6 +4250,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4783,8 +4803,7 @@ "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "aria-query": { "version": "5.3.0", @@ -6248,7 +6267,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "requires": { "argparse": "^2.0.1" } @@ -7128,6 +7146,15 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "swr": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.4.tgz", + "integrity": "sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==", + "requires": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + } + }, "tailwindcss": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", @@ -7332,6 +7359,12 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index dd54a2f..61322c7 100644 --- a/package.json +++ b/package.json @@ -9,19 +9,21 @@ "lint": "next lint" }, "dependencies": { + "js-yaml": "^4.1.0", + "next": "13.5.6", "react": "^18", "react-dom": "^18", - "next": "13.5.6" + "swr": "^2.2.4" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10", + "eslint": "^8", + "eslint-config-next": "13.5.6", "postcss": "^8", "tailwindcss": "^3", - "eslint": "^8", - "eslint-config-next": "13.5.6" + "typescript": "^5" } } diff --git a/pages/api/crew.ts b/pages/api/crew.ts index 0f56e1f..8bafe2a 100644 --- a/pages/api/crew.ts +++ b/pages/api/crew.ts @@ -1,10 +1,27 @@ -import type { NextApiRequest, NextApiResponse } from "next"; - -/** - * @todo Prepare an endpoint to return a list of crew members - * @description The endpoint should return a pagination of 8 users per page. The endpoint should accept a query parameter "page" to return the corresponding page. - */ +import type {NextApiRequest, NextApiResponse} from "next"; +import {astronautsList} from "@/lib/crew"; export default function handler(req: NextApiRequest, res: NextApiResponse) { - res.status(200).json([]); + switch (req.method) { + case "GET": + const {page} = req.query; + if (typeof page === "string" && typeof parseInt(page) !== "number") { + res.status(500).json({message: "Invalid page"}); + return + } + + const pageNumber = typeof page === "string" ? parseInt(page) : page; + + const data = astronautsList([{path: "./crew.json", type: "json"}, { + path: "./crew.yaml", + type: "yaml" + }], pageNumber as number) + + res.status(200).json({astronauts: data}); + return + default: + res.status(500).json({error: "Method not found"}) + + } + res.status(500).json({error: "Method not found"}) } diff --git a/pages/task/[page].tsx b/pages/task/[page].tsx index 86f1097..2f7049c 100644 --- a/pages/task/[page].tsx +++ b/pages/task/[page].tsx @@ -1,8 +1,92 @@ -/** - * @todo List crew members using the endpoint you created - * @description Use tanstack/react-query or swr to fetch data from the endpoint. Prepare pagination. - */ +import useSWR from "swr"; +import { useRouter } from "next/router"; +import Card from "@/components/Card/Card"; +import astronautPng from "@/assets/images/astronaut.png" +import Image from "next/image"; +type AstronautParams = { + fullName: string; + nationality: string; + age: number; + profession: string; +} + +type CatsParams = { + height: number + id: string + url:string + width:number +} + +const fetcher = (url: string, options?: RequestInit) => fetch(url, options).then(res => res.json()); export default function Task() { - return
Task
; + const router = useRouter(); + const { page } = router.query; + const apiUrl = page ? `/api/crew?page=${page}` : null + const catsApiUrl = page ? `https://api.thecatapi.com/v1/images/search?limit=8&width=200&page=${page}` : null + + const { data } = useSWR<{ astronauts: AstronautParams[] }>(apiUrl, fetcher); + const catsObject = useSWR< CatsParams[]>(catsApiUrl, fetcher); + + const cats= catsObject && catsObject.data ? catsObject.data : [] + + const handlePagination = (nextPage: number) => { + if (!page) { + return 1; + } + + const currentPage = typeof page === "string" ? parseInt(page) : 1; + const newPage = currentPage + nextPage; + + if (newPage <= 0) { + return 1; + } + + if (data && data.astronauts.length < 8 && nextPage > 0) { + return currentPage; + } + + return newPage; + }; + + return ( +
+
+ + +
+
Astronauts{"astronaut"}
+ {/*I hold both commercial and copyright rights to this photograph*/} +
+ +
+ +
+
{(data && data.astronauts.length === 0 ) && (

There are no more astronauts

)}
+ {data && !catsObject.isLoading && + data.astronauts.map((astronaut, index) => ( + + ))} +
+ + +
+ ); } diff --git a/tailwind.config.ts b/tailwind.config.ts index c7ead80..008e5da 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -12,6 +12,24 @@ const config: Config = { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + 'gradient-card': 'linear-gradient(71deg, #080509, #19213C, #080509)', + 'gradient-card-after': ' linear-gradient(71deg, #0c0a0e, #7b8197, #0c0a0e)', + }, + width: { + '74': '74%', + '63': '63%', + '100px': '100px', + '250px':'250px' + }, + borderRadius:{ + "999px":'999px' + }, + height: { + '100px': '100px', + '250px':'250px' + }, + backgroundColor: { + '19213C': '#19213C', }, }, },