mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Styling changes and refactor
Switch google fonts to use Next.js font optimization, animate comment fade in, and refactor beer like handler and comment submit handler.
This commit is contained in:
@@ -10,6 +10,7 @@ import { z } from 'zod';
|
||||
|
||||
import { KeyedMutator } from 'swr';
|
||||
import BeerCommentQueryResult from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
||||
import { useRouter } from 'next/router';
|
||||
import Button from '../ui/forms/Button';
|
||||
import FormError from '../ui/forms/FormError';
|
||||
import FormInfo from '../ui/forms/FormInfo';
|
||||
@@ -44,6 +45,7 @@ const BeerCommentForm: FunctionComponent<BeerCommentFormProps> = ({
|
||||
reset({ rating: 0, content: '' });
|
||||
}, [reset]);
|
||||
|
||||
const router = useRouter();
|
||||
const onSubmit: SubmitHandler<z.infer<typeof BeerCommentValidationSchema>> = async (
|
||||
data,
|
||||
) => {
|
||||
@@ -55,13 +57,20 @@ const BeerCommentForm: FunctionComponent<BeerCommentFormProps> = ({
|
||||
beerPostId: beerPost.id,
|
||||
});
|
||||
reset();
|
||||
await mutate();
|
||||
|
||||
const submitTasks: Promise<unknown>[] = [
|
||||
router.push(`/beers/${beerPost.id}`, undefined, { scroll: false }),
|
||||
mutate(),
|
||||
];
|
||||
|
||||
await Promise.all(submitTasks);
|
||||
};
|
||||
|
||||
const { errors } = formState;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
|
||||
<div>
|
||||
<FormInfo>
|
||||
<FormLabel htmlFor="content">Leave a comment</FormLabel>
|
||||
<FormError>{errors.content?.message}</FormError>
|
||||
@@ -73,6 +82,7 @@ const BeerCommentForm: FunctionComponent<BeerCommentFormProps> = ({
|
||||
placeholder="Comment"
|
||||
rows={5}
|
||||
error={!!errors.content?.message}
|
||||
disabled={formState.isSubmitting}
|
||||
/>
|
||||
</FormSegment>
|
||||
<FormInfo>
|
||||
@@ -92,7 +102,13 @@ const BeerCommentForm: FunctionComponent<BeerCommentFormProps> = ({
|
||||
<Rating.Item name="rating-1" className="mask mask-star" />
|
||||
<Rating.Item name="rating-1" className="mask mask-star" />
|
||||
</Rating>
|
||||
<Button type="submit">Submit</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button type="submit" isSubmitting={formState.isSubmitting}>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import Link from 'next/link';
|
||||
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa';
|
||||
|
||||
interface BeerCommentsPaginationBarProps {
|
||||
commentsPageNum: number;
|
||||
commentsPageCount: number;
|
||||
@@ -15,9 +17,9 @@ const BeerCommentsPaginationBar: FC<BeerCommentsPaginationBarProps> = ({
|
||||
beerPost,
|
||||
}) => (
|
||||
<div className="flex items-center justify-center" id="comments-pagination">
|
||||
<div className="btn-group grid w-6/12 grid-cols-2">
|
||||
<div className="btn-group">
|
||||
<Link
|
||||
className={`btn-outline btn ${
|
||||
className={`btn btn-ghost ${
|
||||
commentsPageNum === 1
|
||||
? 'btn-disabled pointer-events-none'
|
||||
: 'pointer-events-auto'
|
||||
@@ -28,10 +30,11 @@ const BeerCommentsPaginationBar: FC<BeerCommentsPaginationBarProps> = ({
|
||||
}}
|
||||
scroll={false}
|
||||
>
|
||||
Next Comments
|
||||
<FaArrowLeft />
|
||||
</Link>
|
||||
<button className="btn btn-ghost pointer-events-none">{commentsPageNum}</button>
|
||||
<Link
|
||||
className={`btn-outline btn ${
|
||||
className={`btn btn-ghost ${
|
||||
commentsPageNum === commentsPageCount
|
||||
? 'btn-disabled pointer-events-none'
|
||||
: 'pointer-events-auto'
|
||||
@@ -42,7 +45,7 @@ const BeerCommentsPaginationBar: FC<BeerCommentsPaginationBarProps> = ({
|
||||
}}
|
||||
scroll={false}
|
||||
>
|
||||
Previous Comments
|
||||
<FaArrowRight />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,7 @@ const BeerPostCommentsSection: FC<BeerPostCommentsSectionProps> = ({ beerPost })
|
||||
pageNum,
|
||||
pageSize,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full space-y-3 md:w-[60%]">
|
||||
<div className="card h-96 bg-base-300">
|
||||
@@ -43,7 +44,7 @@ const BeerPostCommentsSection: FC<BeerPostCommentsSectionProps> = ({ beerPost })
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{comments && !!commentsPageCount && !isLoading && (
|
||||
{comments && !!comments.length && !!commentsPageCount && !isLoading && (
|
||||
<div className="card bg-base-300 pb-6">
|
||||
{comments.map((comment) => (
|
||||
<CommentCardBody key={comment.id} comment={comment} mutate={mutate} />
|
||||
@@ -60,10 +61,16 @@ const BeerPostCommentsSection: FC<BeerPostCommentsSectionProps> = ({ beerPost })
|
||||
{!comments?.length && !isLoading && <NoCommentsCard />}
|
||||
|
||||
{isLoading && (
|
||||
<div className="card bg-base-300">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<div className="card bg-base-300 pb-6">
|
||||
{Array.from({ length: pageSize }).map((_, i) => (
|
||||
<CommentLoadingCardBody key={i} />
|
||||
))}
|
||||
|
||||
<BeerCommentsPaginationBar
|
||||
commentsPageNum={pageNum}
|
||||
commentsPageCount={20}
|
||||
beerPost={beerPost}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import useCheckIfUserLikesBeerPost from '@/hooks/useCheckIfUserLikesBeerPost';
|
||||
import sendLikeRequest from '@/requests/sendLikeRequest';
|
||||
import { FC, useState } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { FaThumbsUp, FaRegThumbsUp } from 'react-icons/fa';
|
||||
import { KeyedMutator } from 'swr';
|
||||
|
||||
@@ -9,14 +9,18 @@ const BeerPostLikeButton: FC<{
|
||||
mutateCount: KeyedMutator<number>;
|
||||
}> = ({ beerPostId, mutateCount }) => {
|
||||
const { isLiked, mutate: mutateLikeStatus } = useCheckIfUserLikesBeerPost(beerPostId);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(false);
|
||||
}, [isLiked]);
|
||||
|
||||
const handleLike = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await sendLikeRequest(beerPostId);
|
||||
mutateCount();
|
||||
mutateLikeStatus();
|
||||
await mutateCount();
|
||||
await mutateLikeStatus();
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
|
||||
@@ -10,7 +10,7 @@ const BeerRecommendations: FunctionComponent<BeerRecommendationsProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div className="card sticky top-2 h-full overflow-y-scroll bg-base-300">
|
||||
<div className="card-body">
|
||||
<div className="card-body space-y-3">
|
||||
{beerRecommendations.map((beerPost) => (
|
||||
<div key={beerPost.id} className="w-full">
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import UserContext from '@/contexts/userContext';
|
||||
import useTimeDistance from '@/hooks/useTimeDistance';
|
||||
import BeerCommentQueryResult from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
||||
import { format } from 'date-fns';
|
||||
import format from 'date-fns/format';
|
||||
import Link from 'next/link';
|
||||
import { useContext } from 'react';
|
||||
import { Rating } from 'react-daisyui';
|
||||
@@ -69,7 +69,7 @@ const CommentCardBody: React.FC<{
|
||||
const timeDistance = useTimeDistance(new Date(comment.createdAt));
|
||||
|
||||
return (
|
||||
<div className="card-body h-64">
|
||||
<div className="card-body h-64 animate-in fade-in-10">
|
||||
<div className="flex flex-col justify-between sm:flex-row">
|
||||
<div>
|
||||
<h3 className="font-semibold sm:text-2xl">
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const CommentLoadingCardBody = () => {
|
||||
return (
|
||||
<div className="card-body h-64">
|
||||
<div className="flex animate-pulse space-x-4">
|
||||
<div className="animate card-body h-64 fade-in-10">
|
||||
<div className="flex animate-pulse space-x-4 slide-in-from-top">
|
||||
<div className="flex-1 space-y-4 py-1">
|
||||
<div className="h-4 w-3/4 rounded bg-base-200" />
|
||||
<div className="h-4 w-3/4 rounded bg-base-100" />
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 rounded bg-base-200" />
|
||||
<div className="h-4 w-5/6 rounded bg-base-200" />
|
||||
<div className="h-4 rounded bg-base-100" />
|
||||
<div className="h-4 w-5/6 rounded bg-base-100" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { FaArrowLeft, FaArrowRight } from 'react-icons/fa';
|
||||
import { FC } from 'react';
|
||||
|
||||
interface PaginationProps {
|
||||
@@ -15,7 +15,7 @@ const BeerIndexPaginationBar: FC<PaginationProps> = ({ pageCount, pageNum }) =>
|
||||
href={{ pathname: '/beers', query: { page_num: pageNum - 1 } }}
|
||||
scroll={false}
|
||||
>
|
||||
«
|
||||
<FaArrowLeft />
|
||||
</Link>
|
||||
<button className="btn">Page {pageNum}</button>
|
||||
<Link
|
||||
@@ -23,7 +23,7 @@ const BeerIndexPaginationBar: FC<PaginationProps> = ({ pageCount, pageNum }) =>
|
||||
href={{ pathname: '/beers', query: { page_num: pageNum + 1 } }}
|
||||
scroll={false}
|
||||
>
|
||||
»
|
||||
<FaArrowRight />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,10 +4,10 @@ import Navbar from './Navbar';
|
||||
const Layout: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<div className="flex h-screen flex-col">
|
||||
<header className="top-0">
|
||||
<header className="sticky top-0 z-50">
|
||||
<Navbar />
|
||||
</header>
|
||||
<div className="animate-in fade-in top-0 h-full flex-1">{children}</div>
|
||||
<div className="relative top-0 h-full flex-1">{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,21 @@ import { GetServerSidePropsContext, GetServerSidePropsResult, PreviewData } from
|
||||
import { ParsedUrlQuery } from 'querystring';
|
||||
import { getLoginSession } from '../config/auth/session';
|
||||
|
||||
/**
|
||||
* Represents a type definition for a function that handles server-side rendering with
|
||||
* extended capabilities.
|
||||
*
|
||||
* @template P - A generic type that represents an object with string keys and any values.
|
||||
* It defaults to an empty object.
|
||||
* @template Q - A generic type that represents a parsed URL query object. It defaults to
|
||||
* the ParsedUrlQuery type.
|
||||
* @template D - A generic type that represents preview data. It defaults to the
|
||||
* PreviewData type.
|
||||
* @param context - The context object containing information about the incoming HTTP
|
||||
* request.
|
||||
* @param session - An awaited value of the return type of the getLoginSession function.
|
||||
* @returns - A promise that resolves to the result of the server-side rendering process.
|
||||
*/
|
||||
export type ExtendedGetServerSideProps<
|
||||
P extends { [key: string]: any } = { [key: string]: any },
|
||||
Q extends ParsedUrlQuery = ParsedUrlQuery,
|
||||
@@ -11,6 +26,20 @@ export type ExtendedGetServerSideProps<
|
||||
session: Awaited<ReturnType<typeof getLoginSession>>,
|
||||
) => Promise<GetServerSidePropsResult<P>>;
|
||||
|
||||
/**
|
||||
* A Higher Order Function that adds authentication requirement to a Next.js server-side
|
||||
* page component.
|
||||
*
|
||||
* @param fn An async function that receives the GetServerSidePropsContext and
|
||||
* authenticated session as arguments and returns a GetServerSidePropsResult with props
|
||||
* for the wrapped component.
|
||||
* @returns A promise that resolves to a GetServerSidePropsResult object with props for
|
||||
* the wrapped component. If authentication is successful, the GetServerSidePropsResult
|
||||
* will include props generated by the wrapped component's getServerSideProps method. If
|
||||
* authentication fails, the GetServerSidePropsResult will include a redirect to the
|
||||
* login page.
|
||||
*/
|
||||
|
||||
const withPageAuthRequired =
|
||||
<P extends { [key: string]: any } = { [key: string]: any }>(
|
||||
fn?: ExtendedGetServerSideProps<P>,
|
||||
|
||||
@@ -40,7 +40,7 @@ const useBeerPostComments = ({ pageNum, id, pageSize }: UseBeerPostCommentsProps
|
||||
throw new Error(parsedPayload.error.message);
|
||||
}
|
||||
|
||||
const pageCount = Math.ceil(parseInt(count as string, 10) / 10);
|
||||
const pageCount = Math.ceil(parseInt(count as string, 10) / pageSize);
|
||||
return { comments: parsedPayload.data, pageCount };
|
||||
},
|
||||
);
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"prettier-plugin-tailwindcss": "^0.2.3",
|
||||
"prisma": "^4.10.1",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"tailwindcss-animate": "^1.0.5",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
|
||||
@@ -3,12 +3,28 @@ import useUser from '@/hooks/useUser';
|
||||
import '@/styles/globals.css';
|
||||
import type { AppProps } from 'next/app';
|
||||
|
||||
import { Roboto } from 'next/font/google';
|
||||
|
||||
const roboto = Roboto({
|
||||
weight: ['100', '300', '400', '500', '700', '900'],
|
||||
subsets: ['latin'],
|
||||
});
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const { user, isLoading, error } = useUser();
|
||||
|
||||
return (
|
||||
<>
|
||||
<style jsx global>
|
||||
{`
|
||||
html {
|
||||
font-family: ${roboto.style.fontFamily};
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<UserContext.Provider value={{ user, isLoading, error }}>
|
||||
<Component {...pageProps} />
|
||||
</UserContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Exo+2:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
font-family: 'Exo 2', sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,29 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [require('daisyui')],
|
||||
plugins: [
|
||||
require('daisyui'),
|
||||
require('tailwindcss-animate')
|
||||
],
|
||||
|
||||
daisyui: {
|
||||
logs: false,
|
||||
themes: [
|
||||
{
|
||||
default: {
|
||||
primary: 'hsl(227, 46%, 25%)',
|
||||
secondary: 'hsl(47, 100%, 80%)',
|
||||
primary: 'hsl(227, 23%, 20%)',
|
||||
secondary: '#ABA9C3',
|
||||
error: '#c17c74',
|
||||
accent: '#fe3bd9',
|
||||
neutral: '#131520',
|
||||
info: '#0A7CFF',
|
||||
success: '#8ACE2B',
|
||||
warning: '#F9D002',
|
||||
error: '#CF1259',
|
||||
'primary-content': '#FAF9F6',
|
||||
'error-content': '#FAF9F6',
|
||||
'base-100': 'hsl(190, 4%, 11%)',
|
||||
'base-200': 'hsl(190, 4%, 9%)',
|
||||
'base-300': 'hsl(190, 4%, 8%)',
|
||||
'base-100': 'hsl(190, 4%, 9%)',
|
||||
'base-200': 'hsl(190, 4%, 8%)',
|
||||
'base-300': 'hsl(190, 4%, 5%)',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user