Skip to content

Commit

Permalink
feat: add error and no data alerts
Browse files Browse the repository at this point in the history
  • Loading branch information
Valik3201 committed Feb 10, 2024
1 parent bfc6c0c commit a97830e
Show file tree
Hide file tree
Showing 8 changed files with 245 additions and 60 deletions.
50 changes: 50 additions & 0 deletions src/@/components/ui/alert.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react';
import { cva } from 'class-variance-authority';

import { cn } from '@/lib/utils';

const alertVariants = cva(
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
variant: 'default',
},
}
);

const Alert = React.forwardRef(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = 'Alert';

const AlertTitle = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
));
AlertTitle.displayName = 'AlertTitle';

const AlertDescription = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('text-sm [&_p]:leading-relaxed', className)}
{...props}
/>
));
AlertDescription.displayName = 'AlertDescription';

export { Alert, AlertTitle, AlertDescription };
53 changes: 53 additions & 0 deletions src/components/Alert.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
ExclamationTriangleIcon,
InfoCircledIcon,
} from '@radix-ui/react-icons';

import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';

export const AlertDestructive = ({ message }) => {
return (
<Alert variant="destructive" className="max-w-2xl">
<ExclamationTriangleIcon className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{message}</AlertDescription>
</Alert>
);
};

export const AlertInfo = () => {
return (
<Alert className="max-w-2xl">
<InfoCircledIcon className="h-4 w-4 text-blue-500" />
<AlertTitle>No Search Results</AlertTitle>
<AlertDescription>
Your search did not match any movies. Please try again with different
keywords or filters.
</AlertDescription>
</Alert>
);
};

export const AlertNoCast = () => {
return (
<Alert className="max-w-2xl mt-8">
<InfoCircledIcon className="h-4 w-4 text-blue-500" />
<AlertTitle>No Cast Information</AlertTitle>
<AlertDescription>
There is no information available about the cast for this movie.
</AlertDescription>
</Alert>
);
};

export const AlertNoReviews = () => {
return (
<Alert className="max-w-2xl mt-8">
<InfoCircledIcon className="h-4 w-4 text-blue-500" />
<AlertTitle>No Reviews Yet</AlertTitle>
<AlertDescription>
There are currently no reviews available for this movie.
</AlertDescription>
</Alert>
);
};
44 changes: 24 additions & 20 deletions src/components/Cast.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useQuery } from '@tanstack/react-query';
import { fetchCast } from 'services/fetchCast';

import { Loader } from './Loader';
import { AlertDestructive, AlertNoCast } from './Alert';

const Cast = () => {
const { movieId } = useParams();
Expand All @@ -18,29 +19,32 @@ const Cast = () => {
}

if (error) {
return <div>Error fetching data: {error.message}</div>;
return <AlertDestructive message={error.message} />;
}

return (
<div className="grid grid-cols-4 sm:grid-cols-6 gap-4 max-w-5xl py-4 text-xs">
{data.map(actor => (
<ul key={actor.id}>
<li>
<img
src={
actor.profile_path
? `https://image.tmdb.org/t/p/w185${actor.profile_path}`
: `https://placehold.co/185x278?text=${actor.name}`
}
alt={actor.name}
className="rounded-lg mb-2"
/>
<p className="font-bold">{actor.name}</p>
<p className="text-muted-foreground">{actor.character}</p>
</li>
</ul>
))}
</div>
<>
{!isLoading && data.length === 0 && <AlertNoCast />}
<div className="grid grid-cols-4 sm:grid-cols-6 gap-4 max-w-5xl py-4 text-xs">
{data.map(actor => (
<ul key={actor.id}>
<li>
<img
src={
actor.profile_path
? `https://image.tmdb.org/t/p/w185${actor.profile_path}`
: `https://placehold.co/185x278?text=${actor.name}`
}
alt={actor.name}
className="rounded-lg mb-2"
/>
<p className="font-bold">{actor.name}</p>
<p className="text-muted-foreground">{actor.character}</p>
</li>
</ul>
))}
</div>
</>
);
};

Expand Down
56 changes: 30 additions & 26 deletions src/components/Reviews.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useQuery } from '@tanstack/react-query';
import { fetchReviews } from 'services/fetchReviews';

import { Loader } from './Loader';
import { AlertDestructive, AlertNoReviews } from './Alert';

import { format } from 'date-fns';
import ReactMarkdown from 'react-markdown';
Expand All @@ -30,35 +31,38 @@ const Reviews = () => {
}

if (error) {
return <div>Error fetching data: {error.message}</div>;
return <AlertDestructive message={error.message} />;
}

return (
<div className="flex flex-col w-full max-w-5xl items-start gap-4 py-8">
{data.map(review => (
<ul key={review.id}>
<li>
<Card>
<CardHeader>
<CardTitle>{review.author}</CardTitle>
<CardDescription>
{format(new Date(review.created_at), 'HH:mm, MMMM d, yyyy')}
</CardDescription>
</CardHeader>
<CardContent>
{/<([A-Za-z][A-Za-z0-9]*)\b[^>]*>(.*?)<\/\1>/.test(
review.content
) ? (
<p>{parse(review.content)}</p>
) : (
<ReactMarkdown>{review.content}</ReactMarkdown>
)}
</CardContent>
</Card>
</li>
</ul>
))}
</div>
<>
{!isLoading && data.length === 0 && <AlertNoReviews />}
<div className="flex flex-col w-full max-w-5xl items-start gap-4 py-8">
{data.map(review => (
<ul key={review.id}>
<li>
<Card>
<CardHeader>
<CardTitle>{review.author}</CardTitle>
<CardDescription>
{format(new Date(review.created_at), 'HH:mm, MMMM d, yyyy')}
</CardDescription>
</CardHeader>
<CardContent>
{/<([A-Za-z][A-Za-z0-9]*)\b[^>]*>(.*?)<\/\1>/.test(
review.content
) ? (
<p>{parse(review.content)}</p>
) : (
<ReactMarkdown>{review.content}</ReactMarkdown>
)}
</CardContent>
</Card>
</li>
</ul>
))}
</div>
</>
);
};

Expand Down
69 changes: 69 additions & 0 deletions src/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,14 @@ body {
margin-top: 0.375rem;
}

.mb-1 {
margin-bottom: 0.25rem;
}

.mt-8 {
margin-top: 2rem;
}

.flex {
display: flex;
}
Expand Down Expand Up @@ -742,6 +750,12 @@ body {
width: max-content;
}

.w-fit {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
}

.max-w-2xl {
max-width: 42rem;
}
Expand Down Expand Up @@ -908,6 +922,10 @@ body {
border-color: transparent;
}

.border-destructive\/50 {
border-color: hsl(var(--destructive) / 0.5);
}

.bg-background {
background-color: hsl(var(--background));
}
Expand Down Expand Up @@ -1003,6 +1021,11 @@ body {
padding-bottom: 2rem;
}

.py-3 {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}

.pb-2 {
padding-bottom: 0.5rem;
}
Expand Down Expand Up @@ -1121,6 +1144,15 @@ body {
color: hsl(var(--secondary-foreground));
}

.text-destructive {
color: hsl(var(--destructive));
}

.text-blue-500 {
--tw-text-opacity: 1;
color: rgb(59 130 246 / var(--tw-text-opacity));
}

.underline-offset-4 {
text-underline-offset: 4px;
}
Expand Down Expand Up @@ -1447,6 +1479,10 @@ body {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

:is(.dark .dark\:border-destructive) {
border-color: hsl(var(--destructive));
}

@media (min-width: 640px) {
.sm\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
Expand Down Expand Up @@ -1502,3 +1538,36 @@ body {
line-height: 1;
}
}

.\[\&\>svg\+div\]\:translate-y-\[-3px\]>svg+div {
--tw-translate-y: -3px;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.\[\&\>svg\]\:absolute>svg {
position: absolute;
}

.\[\&\>svg\]\:left-4>svg {
left: 1rem;
}

.\[\&\>svg\]\:top-4>svg {
top: 1rem;
}

.\[\&\>svg\]\:text-destructive>svg {
color: hsl(var(--destructive));
}

.\[\&\>svg\]\:text-foreground>svg {
color: hsl(var(--foreground));
}

.\[\&\>svg\~\*\]\:pl-7>svg~* {
padding-left: 1.75rem;
}

.\[\&_p\]\:leading-relaxed p {
line-height: 1.625;
}
3 changes: 2 additions & 1 deletion src/pages/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { fetchTrendingData } from 'services/fetchTrendingData';

import MovieItem from 'components/MovieItem';
import { Loader } from 'components/Loader';
import { AlertDestructive } from 'components/Alert';

const Home = () => {
const { isPending, isError, data, error } = useQuery({
Expand All @@ -15,7 +16,7 @@ const Home = () => {
}

if (isError) {
return <div>Error fetching data: {error.message}</div>;
return <AlertDestructive message={error.message} />;
}

return (
Expand Down
Loading

0 comments on commit a97830e

Please sign in to comment.