| @@ -0,0 +1,47 @@ | |||
| import Card from '@mui/material/Card'; | |||
| import CardContent from '@mui/material/CardContent'; | |||
| import Typography from '@mui/material/Typography'; | |||
| import Image from 'next/image'; | |||
| const DataDetailsCard = ({ data }) => { | |||
| return ( | |||
| <Card | |||
| sx={{ | |||
| maxWidth: 600, | |||
| height: 200, | |||
| marginX: 'auto', | |||
| marginY: 20, | |||
| boxShadow: 10, | |||
| display: 'flex', | |||
| }} | |||
| > | |||
| <Image | |||
| src="https://www.business2community.com/wp-content/uploads/2017/08/blank-profile-picture-973460_640.png" | |||
| alt="profile picture" | |||
| width={600} | |||
| height={500} | |||
| /> | |||
| <CardContent> | |||
| <Typography | |||
| gutterBottom | |||
| variant="h5" | |||
| component="div" | |||
| sx={{ | |||
| textAlign: 'center', | |||
| marginTop: 1, | |||
| marginBottom: 3, | |||
| }} | |||
| > | |||
| {data.name}, {data.age} | |||
| </Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur | |||
| quis odio in libero fringilla pellentesque aliquet et mi. Quisque | |||
| maximus lectus a neque luctus, tempus auctor ipsum ultrices. | |||
| </Typography> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| }; | |||
| export default DataDetailsCard; | |||
| @@ -0,0 +1,7 @@ | |||
| const base = { | |||
| profileData: { name: 'John Doe' }, | |||
| }; | |||
| export const mockDataDetailsCardProps = { | |||
| base, | |||
| }; | |||
| @@ -0,0 +1,20 @@ | |||
| import DataDetailsCard from './DataDetailsCard'; | |||
| import { mockDataDetailsCardProps } from './ProfileCard.mock'; | |||
| const obj = { | |||
| title: 'cards/DataDetailsCard', | |||
| component: DataDetailsCard, | |||
| // More on argTypes: https://storybook.js.org/docs/react/api/argtypes | |||
| argTypes: {}, | |||
| }; //eslint-disable-line | |||
| export default obj; | |||
| // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args | |||
| const Template = (args) => <DataDetailsCard {...args} />; | |||
| export const Base = Template.bind({}); | |||
| // More on args: https://storybook.js.org/docs/react/writing-stories/args | |||
| Base.args = { | |||
| ...mockDataDetailsCardProps.base, | |||
| }; | |||
| @@ -1,6 +1,8 @@ | |||
| import { Box, Button, Grid, Paper, Typography } from '@mui/material'; | |||
| import { useTranslation } from 'next-i18next'; | |||
| import { useRouter } from 'next/router'; | |||
| import { useState } from 'react'; | |||
| import { SINGLE_DATA_PAGE } from '../../../constants/pages'; | |||
| import useDebounce from '../../../hooks/use-debounce'; | |||
| import { usePagination } from '../../../hooks/use-pagination'; | |||
| import { compare } from '../../../utils/helpers/sortHelpers'; | |||
| @@ -13,7 +15,7 @@ const PaginationComponentRQ = () => { | |||
| const [sort, setSort] = useState(''); | |||
| const { t } = useTranslation('pagination'); | |||
| const { data: paginationData } = usePagination(pageIndex); | |||
| const router = useRouter(); | |||
| const debouncedFilter = useDebounce(filter, 500); | |||
| const handleFilterChange = (event) => { | |||
| @@ -26,6 +28,10 @@ const PaginationComponentRQ = () => { | |||
| setSort(sort); | |||
| }; | |||
| const loadSingleDataHandler = (id) => { | |||
| router.push(`${SINGLE_DATA_PAGE}${id}`); | |||
| }; | |||
| const dataToDisplay = paginationData?.data | |||
| .filter((item) => | |||
| item.name.toLowerCase().startsWith(debouncedFilter.toLowerCase()) | |||
| @@ -33,7 +39,16 @@ const PaginationComponentRQ = () => { | |||
| .sort((a, b) => compare(a.name, b.name, sort)) | |||
| .map((item, index) => ( | |||
| // ! DON'T USE index for key, this is for example only | |||
| <Grid item sx={{ p: 2 }} xs={12} sm={6} md={4} lg={3} key={index}> | |||
| <Grid | |||
| item | |||
| sx={{ p: 2 }} | |||
| xs={12} | |||
| sm={6} | |||
| md={4} | |||
| lg={3} | |||
| key={index} | |||
| onClick={loadSingleDataHandler.bind(null, item.customID)} | |||
| > | |||
| <DataCard data={item} t={t} /> | |||
| </Grid> | |||
| )); | |||
| @@ -1,5 +1,6 @@ | |||
| export const BASE_PAGE = "/"; | |||
| export const LOGIN_PAGE = "/auth"; | |||
| export const PROFILE_PAGE = "/profile"; | |||
| export const REGISTER_PAGE = "/auth/register"; | |||
| export const FORGOT_PASSWORD_PAGE = "/auth/forgot-password"; | |||
| export const BASE_PAGE = '/'; | |||
| export const LOGIN_PAGE = '/auth'; | |||
| export const PROFILE_PAGE = '/profile'; | |||
| export const REGISTER_PAGE = '/auth/register'; | |||
| export const FORGOT_PASSWORD_PAGE = '/auth/forgot-password'; | |||
| export const SINGLE_DATA_PAGE = '/single-data/'; | |||
| @@ -0,0 +1,40 @@ | |||
| import { connectToDatabase } from '../../../utils/helpers/dbHelpers'; | |||
| async function handler(req, res) { | |||
| if (req.method !== 'GET') { | |||
| return; | |||
| } | |||
| const dataId = req.query.dataId; | |||
| let client; | |||
| try { | |||
| client = await connectToDatabase(); | |||
| } catch (error) { | |||
| res.status(500).json({ message: 'Connecting to the database failed!' }); | |||
| return; | |||
| } | |||
| const db = client.db(); | |||
| const singleData = await db | |||
| .collection('randomData') | |||
| .findOne({ customID: dataId }); | |||
| if (!singleData) { | |||
| res.status(422).json({ message: 'No data!' }); | |||
| client.close(); | |||
| return; | |||
| } | |||
| res.status(201).json({ | |||
| message: 'Fetch single data successfull!', | |||
| singleData: singleData, | |||
| }); | |||
| setTimeout(() => { | |||
| client.close(); | |||
| }, 1500); | |||
| } | |||
| export default handler; | |||
| @@ -0,0 +1,35 @@ | |||
| import { connectToDatabase } from '../../../utils/helpers/dbHelpers'; | |||
| async function handler(req, res) { | |||
| if (req.method !== 'GET') { | |||
| return; | |||
| } | |||
| const client = await connectToDatabase(); | |||
| const db = client.db(); | |||
| const dataFromDB = await db | |||
| .collection('randomData') | |||
| .find() | |||
| .limit(4) | |||
| .toArray(); | |||
| if (!dataFromDB) { | |||
| res.status(422).json({ message: 'No data!' }); | |||
| client.close(); | |||
| return; | |||
| } | |||
| const dataIds = dataFromDB.map((item) => item.customID); | |||
| res.status(201).json({ | |||
| message: 'Fetch ids successfull!', | |||
| dataIds: dataIds, | |||
| }); | |||
| setTimeout(() => { | |||
| client.close(); | |||
| }, 1500); | |||
| } | |||
| export default handler; | |||
| @@ -0,0 +1,49 @@ | |||
| import DataDetailsCard from '../../components/cards/data-details-card/DataDetailsCard'; | |||
| import { getDataIds } from '../../requests/dataIdsRequest'; | |||
| import { getSingleData } from '../../requests/singleDataRequest'; | |||
| function SignelDataPage(props) { | |||
| const data = props.selectedData; | |||
| if (!data) { | |||
| return <h1>No data!</h1>; | |||
| } | |||
| return <DataDetailsCard data={data.singleData} />; | |||
| } | |||
| export async function getStaticProps(context) { | |||
| const dataId = context.params.dataId; | |||
| try { | |||
| const data = await getSingleData(dataId); | |||
| return { | |||
| props: { | |||
| selectedData: data, | |||
| }, | |||
| revalidate: 60, | |||
| }; | |||
| } catch (error) { | |||
| return { | |||
| props: { | |||
| selectedData: null, | |||
| }, | |||
| revalidate: 60, | |||
| }; | |||
| } | |||
| } | |||
| export async function getStaticPaths() { | |||
| const firstPageDataIds = await getDataIds(); | |||
| console.log(firstPageDataIds); | |||
| const paths = firstPageDataIds.dataIds.map((id) => ({ | |||
| params: { dataId: id }, | |||
| })); | |||
| return { | |||
| paths: paths, | |||
| fallback: 'blocking', | |||
| }; | |||
| } | |||
| export default SignelDataPage; | |||
| @@ -3,4 +3,6 @@ export default { | |||
| createUser: '/api/auth/signup', | |||
| }, | |||
| data: '/api/data', | |||
| dataIds: '/api/single-data', | |||
| singleData: '/api/single-data/', | |||
| }; | |||
| @@ -0,0 +1,13 @@ | |||
| import apiEndpoints from './apiEndpoints'; | |||
| export const getDataIds = async () => { | |||
| const response = await fetch(`http://localhost:3000/${apiEndpoints.dataIds}`); | |||
| const data = await response.json(); | |||
| if (!response.ok) { | |||
| throw new Error(data.message || 'Something went wrong!'); | |||
| } | |||
| return data; | |||
| }; | |||
| @@ -0,0 +1,15 @@ | |||
| import apiEndpoints from './apiEndpoints'; | |||
| export const getSingleData = async (dataId) => { | |||
| const response = await fetch( | |||
| `http://localhost:3000/${apiEndpoints.singleData}${dataId}` | |||
| ); | |||
| const data = await response.json(); | |||
| if (!response.ok) { | |||
| throw new Error(data.message || 'Something went wrong!'); | |||
| } | |||
| return data; | |||
| }; | |||