Skip to content

Commit

Permalink
feat(locations): added a residents avatars on locations grid card
Browse files Browse the repository at this point in the history
  • Loading branch information
ruddenchaux committed Apr 16, 2021
1 parent 25fccf2 commit 221130e
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 42 deletions.
14 changes: 11 additions & 3 deletions src/components/locations/LocationCard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Card, CardContent, makeStyles } from '@material-ui/core';
import { Card, CardContent, Grid, makeStyles } from '@material-ui/core';
import React from 'react';
import { Location } from '../../models/Location';
import LocationCardDimension from './LocationCardDimension';
import LocationCardResidents from './LocationCardResidents';
import LocationCardTitle from './LocationCardTitle';
import LocationCardType from './LocationCardType';

Expand All @@ -28,9 +29,16 @@ export default function LocationCard({ item, isLoading }: { item: Location; isLo
<CardContent className={classes.cardDetails}>
<LocationCardTitle isLoading={isLoading} location={item} />

<LocationCardType isLoading={isLoading} location={item} />
<Grid container>
<Grid item xs={12} sm={5} md={5} lg={5} xl={5}>
<LocationCardType isLoading={isLoading} location={item} />
<LocationCardDimension isLoading={isLoading} location={item} />
</Grid>

<LocationCardDimension isLoading={isLoading} location={item} />
<Grid item xs={12} sm={7} md={7} lg={7} xl={7}>
<LocationCardResidents isLoading={isLoading} location={item} />
</Grid>
</Grid>
</CardContent>
</Card>
);
Expand Down
29 changes: 29 additions & 0 deletions src/components/locations/LocationCardResidentAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Avatar, makeStyles } from '@material-ui/core';
import { Skeleton } from '@material-ui/lab';
import React from 'react';
import { Character } from '../../models/Character';

// component style
const useStyles = makeStyles((theme) => ({
large: {
width: theme.spacing(7),
height: theme.spacing(7),
margin: theme.spacing(1)
}
}));

export default function LocationCardResidentAvatar({
resident,
isLoading
}: {
resident: Character;
isLoading: boolean;
}) {
const classes = useStyles();

if (isLoading) {
return <Skeleton className={classes.large} variant="circle" />;
}

return <Avatar alt={resident.name} src={resident.image} className={classes.large} />;
}
99 changes: 99 additions & 0 deletions src/components/locations/LocationCardResidents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
/* eslint-disable react/no-array-index-key */
import { Grid, Link, makeStyles, Typography } from '@material-ui/core';
import { Skeleton } from '@material-ui/lab';
import React from 'react';
import { useDispatch } from 'react-redux';
import useMediaQueries from '../../hooks/useMediaQueries';
import { Location } from '../../models/Location';
import { setDialogCharacters, setDialogOpen, setDialogTitle } from '../../store/charactersDialog';
import LocationCardResidentAvatar from './LocationCardResidentAvatar';

// component style
const useStyles = makeStyles((theme) => ({
container: {
paddingTop: theme.spacing(3)
},
avatarsContainer: {
paddingTop: theme.spacing(1)
},
viewMore: {
paddingTop: theme.spacing(1)
}
}));

export default function LocationCardResidents({ location, isLoading }: { location: Location; isLoading: boolean }) {
let avatarsCount = 5;
const classes = useStyles();
const { xs, sm, md, lg, xl } = useMediaQueries();
const dispatch = useDispatch();

const openFullScreenDialog = () => {
dispatch(setDialogCharacters(location.residents));
dispatch(setDialogTitle(`Residents of ${location.name}`));
dispatch(setDialogOpen(true));
};

if (xs) {
avatarsCount = 10;
}

if (sm) {
avatarsCount = 6;
}

if (md) {
avatarsCount = 10;
}

if (lg) {
avatarsCount = 3;
}

if (xl) {
avatarsCount = 4;
}

return (
<>
<div className={classes.container}>
{isLoading ? (
<Skeleton height={20} />
) : (
<>
<Typography variant="subtitle2" component="label" color="textSecondary">
Residents
</Typography>

{location.residents.length > avatarsCount ? (
<>
&nbsp;
<Link
component="button"
variant="body2"
onClick={() => {
openFullScreenDialog();
}}
>
View more
</Link>
</>
) : null}
</>
)}

<div className={classes.avatarsContainer}>
<Grid container>
{(isLoading ? Array.from(new Array(avatarsCount)) : location.residents.slice(0, avatarsCount)).map(
(resident, index) => (
<Grid item key={index}>
<LocationCardResidentAvatar resident={resident} isLoading={isLoading} />
</Grid>
)
)}
</Grid>
</div>
</div>
</>
);
}
29 changes: 5 additions & 24 deletions src/components/locations/LocationCardTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,16 @@
import { makeStyles, Typography } from '@material-ui/core';
import { Typography } from '@material-ui/core';
import { Skeleton } from '@material-ui/lab';
import React from 'react';
import { Location } from '../../models/Location';

// component style
const useStyles = makeStyles(() => ({
cardTitle: {
width: '320px',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}
}));

export default function LocationCardTitle({ location, isLoading }: { location: Location; isLoading: boolean }) {
const classes = useStyles();

if (isLoading) {
return (
<>
<Skeleton height={44} />
<Skeleton height={20} />
</>
);
return <Skeleton height={44} />;
}

return (
<>
<Typography className={classes.cardTitle} component="h2" variant="h4" data-cy="location-name">
{location.name}
</Typography>
</>
<Typography component="h2" variant="h4" data-cy="location-name">
{location.name}
</Typography>
);
}
30 changes: 30 additions & 0 deletions src/hooks/useGridStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { makeStyles } from '@material-ui/core';

export default makeStyles((theme) => ({
title: {
marginLeft: theme.spacing(3)
},
cardGrid: {
paddingTop: theme.spacing(4),
paddingLeft: theme.spacing(6),
paddingRight: theme.spacing(6),

[theme.breakpoints.down('lg')]: {
paddingLeft: '8rem',
paddingRight: '8rem'
},

[theme.breakpoints.down('md')]: {
paddingLeft: '12rem',
paddingRight: '12rem'
},
[theme.breakpoints.down('sm')]: {
paddingLeft: theme.spacing(6),
paddingRight: theme.spacing(6)
}
},
loadingText: {
textAlign: 'center',
color: theme.palette.text.hint
}
}));
15 changes: 15 additions & 0 deletions src/hooks/useMediaQueries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useMediaQuery, useTheme } from '@material-ui/core';

/**
* Hook for handle media queries
*/
export default () => {
const theme = useTheme();
const xs = useMediaQuery(theme.breakpoints.only('xs'));
const sm = useMediaQuery(theme.breakpoints.only('sm'));
const md = useMediaQuery(theme.breakpoints.only('md'));
const lg = useMediaQuery(theme.breakpoints.only('lg'));
const xl = useMediaQuery(theme.breakpoints.only('xl'));

return { xs, sm, md, lg, xl };
};
22 changes: 13 additions & 9 deletions src/pages/Locations.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { useDispatch } from 'react-redux';
import CharactersFullScreenDialog from '../components/characters/CharactersFullScreenDialog';
import ItemsGrid from '../components/ItemsGrid';
import LocationCard from '../components/locations/LocationCard';
import { LocationsWrapperResponse, useGetAllQuery } from '../services/locations';
Expand All @@ -11,14 +12,17 @@ export default function Locations() {
dispatch(setHeaderTitle('Locations'));

return (
<ItemsGrid<Location, LocationsWrapperResponse>
ComponentCard={LocationCard}
useGetAllQuery={useGetAllQuery}
xs={12}
sm={6}
md={6}
lg={4}
xl={3}
/>
<>
<ItemsGrid<Location, LocationsWrapperResponse>
ComponentCard={LocationCard}
useGetAllQuery={useGetAllQuery}
xs={12}
sm={12}
md={12}
lg={6}
xl={4}
/>
<CharactersFullScreenDialog />
</>
);
}
20 changes: 14 additions & 6 deletions src/services/locations.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { createApi } from '@rtk-incubator/rtk-query';
import { gql, request } from 'graphql-request';

const BASE_URL = 'https://rickandmortyapi.com/graphql';

export interface LocationsWrapperResponse {
locations: {
results: Location[];
};
}

// create a basic `baseQuery` util
const graphqlBaseQuery = ({ baseUrl }: { baseUrl: string }) => async ({ body }: { body: string }) => {
const result = await request<LocationsWrapperResponse>(baseUrl, body);
const graphqlBaseQuery = <T>({ baseUrl }: { baseUrl: string }) => async ({ body }: { body: string }) => {
const result = await request<T>(baseUrl, body);
return { data: result };
};

// Define a service using a base URL and expected endpoints
export const locationsApi = createApi({
reducerPath: 'locationsApi',
baseQuery: graphqlBaseQuery({
baseUrl: 'https://rickandmortyapi.com/graphql'
baseQuery: graphqlBaseQuery<LocationsWrapperResponse>({
baseUrl: BASE_URL
}),
endpoints: (builder) => ({
getAll: builder.query({
Expand All @@ -33,7 +35,15 @@ export const locationsApi = createApi({
residents {
id
name
status
species
image
location {
name
}
episode {
name
}
}
}
}
Expand All @@ -47,6 +57,4 @@ export const locationsApi = createApi({
})
});

// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetAllQuery } = locationsApi;

0 comments on commit 221130e

Please sign in to comment.