Skip to content

Commit

Permalink
Implement dream modal
Browse files Browse the repository at this point in the history
  • Loading branch information
bombies committed Oct 26, 2023
1 parent d286b86 commit 4af070a
Show file tree
Hide file tree
Showing 18 changed files with 245 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {FC, useMemo} from "react";
import {FC, Fragment, useMemo} from "react";
import {useDreamsData} from "@/app/(site)/(internal)/dashboard/components/dreams/DreamsProvider";
import DreamCard from "@/app/(site)/(internal)/dashboard/components/dreams/DreamCard";
import DreamCard from "@/app/(site)/(internal)/dashboard/components/dreams/card/DreamCard";
import LogDreamCard from "@/app/(site)/(internal)/dashboard/components/dreams/LogDreamCard";
import useTodayTimeRange from "@/app/(site)/hooks/useTodayTimeRange";
import {Spinner} from "@nextui-org/react";
import DreamCardSkeleton from "@/app/(site)/(internal)/dashboard/components/dreams/card/DreamCardSkeleton";

const CurrentDreamsContainer: FC = () => {
const [startOfToday, endOfToday] = useTodayTimeRange()
Expand All @@ -18,13 +20,21 @@ const CurrentDreamsContainer: FC = () => {

return (
<div className="flex flex-col">
<h2 className="text-4xl phone:text-2xl font-semibold mb-8 phone:mb-4 tablet:text-center">Today - {startOfToday.toLocaleDateString("en-US", {
dateStyle: "medium"
})}
<h2 className="text-4xl phone:text-2xl font-semibold mb-8 phone:mb-4 tablet:text-center">Today
- {startOfToday.toLocaleDateString("en-US", {
dateStyle: "medium"
})}
</h2>
<div className="bg-primary/20 rounded-3xl py-6 px-4 phone:p-3 w-[36rem] tablet:w-[24rem] phone:w-[20rem] tablet:self-center flex flex-col gap-y-6 phone:gap-y-3">
<div
className="bg-primary/20 rounded-3xl py-6 px-4 phone:p-3 w-[36rem] tablet:w-[24rem] phone:w-[20rem] tablet:self-center flex flex-col gap-y-6 phone:gap-y-3">
<LogDreamCard/>
{dreamCards}
{dreams.loading ? (
<Fragment>
<DreamCardSkeleton />
<DreamCardSkeleton />
<DreamCardSkeleton />
</Fragment>
) : dreamCards}
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
"use client"

import {createContext, FC, PropsWithChildren, useContext} from "react";
import {FC, PropsWithChildren} from "react";
import useDreams, {DreamsState} from "@/app/(site)/(internal)/dashboard/components/dreams/hooks/useDreams";
import useDreamCharacters, {
DreamCharactersState
} from "@/app/(site)/(internal)/dashboard/components/dreams/hooks/useDreamCharacters";
import useDreamTags, {DreamTagsState} from "@/app/(site)/(internal)/dashboard/components/dreams/hooks/useDreamTags";
import {createDataContext, DataContextProps} from "@/utils/client/client-data-utils";

type DreamsContextProps = {
interface DreamsContextProps extends DataContextProps {
dreams: DreamsState,
characters: DreamCharactersState,
tags: DreamTagsState,
}

const DreamsContext = createContext<DreamsContextProps | undefined>(undefined)
const [DreamsContext, useHook] = createDataContext<DreamsContextProps>("useDreamsData must be used in a DreamsProvider!")

const DreamsProvider: FC<PropsWithChildren> = ({children}) => {
const dreams = useDreams()
Expand All @@ -28,10 +29,4 @@ const DreamsProvider: FC<PropsWithChildren> = ({children}) => {
}

export default DreamsProvider

export const useDreamsData = () => {
const dreams = useContext(DreamsContext)
if (!dreams)
throw new Error("useDreams can only be used in a DreamProvider!")
return dreams;
}
export const useDreamsData = useHook
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import {FC, Fragment} from "react";
"use client"

import {FC, Fragment, useState} from "react";
import {Dream} from "@prisma/client";
import {CardBody, CardHeader} from "@nextui-org/card";
import Card from "@/app/(site)/components/Card";
import DreamModal from "@/app/(site)/(internal)/dashboard/components/dreams/card/DreamModal";

type Props = {
dream: Dream
}

const DreamCard: FC<Props> = ({dream}) => {
const [modalOpen, setModalOpen] = useState(false)

return (
<Fragment>
<Card classNames={{
header: "bg-[#0C0015] pt-6 px-8 pb-0",
body: "bg-[#0C0015] px-8 pt-4",
footer: "bg-[#0C0015] px-8",
}}>
<DreamModal
dream={dream}
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
/>
<Card
isPressable
onPress={() => setModalOpen(true)}
classNames={{
header: "bg-[#0C0015] pt-6 px-8 pb-0",
body: "bg-[#0C0015] px-8 pt-4",
footer: "bg-[#0C0015] px-8",
}}>
<CardHeader className="flex justify-between">
<h2 className="text-3xl tablet:text-xl font-semibold whitespace-nowrap overflow-hidden overflow-ellipsis max-w-[80%] phone:max-w-[50%]">{dream.title}</h2>
<p className="text-subtext text-lg self-end">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client"

import {FC} from "react";
import {CardBody, CardHeader} from "@nextui-org/card";
import Card from "@/app/(site)/components/Card";
import {Skeleton} from "@nextui-org/react";

const DreamCardSkeleton: FC = () => {
return (
<Card
classNames={{
header: "bg-[#0C0015] pt-6 px-8 pb-0",
body: "bg-[#0C0015] px-8 pt-4",
footer: "bg-[#0C0015] px-8",
}}>
<CardHeader className="flex justify-between">
<Skeleton className="rounded-full w-[70%] phone:w-[50%] h-6" />
<Skeleton className="rounded-full self-end h-5 w-[20%]" />
</CardHeader>
<CardBody>
<Skeleton className="rounded-full w-[85%] h-3 my-4" />
</CardBody>
</Card>
)
}

export default DreamCardSkeleton
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client"

import {FC, Fragment, useEffect, useMemo} from "react";
import Modal from "@/app/(site)/components/Modal";
import {Dream} from "@prisma/client";
import useSWR from "swr";
import {calcEstimatedReadingTime, fetcher} from "@/utils/client/client-utils";
import {DreamWithRelations} from "@/app/api/me/dreams/dreams.dto";
import {Chip} from "@nextui-org/chip";

type Props = {
dream: Dream,
isOpen?: boolean,
onClose?: () => void
}

const FetchFullDream = (dream: Dream, modalOpen: boolean) => {
return useSWR(modalOpen && `/api/me/dreams/${dream.id}?tags=true&characters=true`, fetcher<DreamWithRelations | null>, {refreshInterval: 0})
}

const DreamModal: FC<Props> = ({dream, isOpen, onClose}) => {
const {data: fullDream, error: fullDreamError} = FetchFullDream(dream, isOpen ?? false)

const tagChips = useMemo(() => fullDream?.tags?.map(tag => (
<Chip key={tag.id} color="primary" variant="flat">
{tag.tag}
</Chip>
)), [fullDream?.tags])

useEffect(() => {
if (fullDreamError)
console.error(fullDreamError)
}, [fullDreamError])

return (
<Modal
size="2xl"
header={
<Fragment>
{(tagChips || fullDreamError) && (
<div className="flex flex-wrap gap-2 mb-3">
{tagChips ?? (fullDreamError &&
<Chip color="danger" variant="flat">
Error Loading Tags
</Chip>
)}
</div>
)}
<h1 className="text-4xl">{dream.title}</h1>
<h3 className="text-subtext text-sm font-semibold italic">{dream.comments}</h3>
<h3 className="text-subtext text-xs italic">~{calcEstimatedReadingTime(dream.description)} min. read</h3>
</Fragment>
}
isOpen={isOpen}
onClose={onClose}
>
<article
className="text-[#EAE0FF] whitespace-pre-wrap rounded-3xl border border-primary/40 bg-[#0C0015]/50 p-6">{dream.description}</article>
</Modal>
)
}

export default DreamModal
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import axios from "axios";
import useSWRMutation from "swr/mutation";
import {useDreamsData} from "@/app/(site)/(internal)/dashboard/components/dreams/DreamsProvider";
import {DreamCharacter} from "@prisma/client";
import {handleAxiosError} from "@/utils/client-utils";
import {handleAxiosError} from "@/utils/client/client-utils";
import {useSession} from "next-auth/react";
import toast from "react-hot-toast";
import Input from "@/app/(site)/components/Input";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import CloseIcon from "@/app/(site)/components/icons/CloseIcon";
import axios from "axios";
import useSWRMutation from "swr/mutation";
import {Dream} from "@prisma/client";
import {handleAxiosError} from "@/utils/client-utils";
import {handleAxiosError} from "@/utils/client/client-utils";
import {useSession} from "next-auth/react";
import toast from "react-hot-toast";
import AddTagModal from "@/app/(site)/(internal)/dashboard/components/dreams/forms/tags/AddTagModal";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import axios from "axios";
import useSWRMutation from "swr/mutation";
import {useDreamsData} from "@/app/(site)/(internal)/dashboard/components/dreams/DreamsProvider";
import {DreamTag} from "@prisma/client";
import {handleAxiosError} from "@/utils/client-utils";
import {handleAxiosError} from "@/utils/client/client-utils";
import {useSession} from "next-auth/react";
import toast from "react-hot-toast";
import Input from "@/app/(site)/components/Input";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import useSWR, {KeyedMutator} from "swr";
import {fetcher} from "@/utils/client-utils";
import {fetcher} from "@/utils/client/client-utils";
import {DreamCharacter} from "@prisma/client";
import {useCallback} from "react";
import {DreamContextState} from "@/app/(site)/(internal)/dashboard/components/dreams/hooks/useDreams";
import {DataContextState} from "@/utils/client/client-data-utils";

export type DreamCharactersState = DreamContextState<DreamCharacter[], DreamCharacter>
export type DreamCharactersState = DataContextState<DreamCharacter[], DreamCharacter>

const useDreamCharacters = (): DreamCharactersState => {
const {data: characters, isLoading: charactersLoading, mutate: mutateCharacters} = useSWR('/api/me/dreams/characters', fetcher<DreamCharacter[]>)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import useSWR, {KeyedMutator} from "swr";
import {fetcher} from "@/utils/client-utils";
import {fetcher} from "@/utils/client/client-utils";
import {DreamTag} from "@prisma/client";
import {useCallback} from "react";
import {DreamContextState} from "@/app/(site)/(internal)/dashboard/components/dreams/hooks/useDreams";
import {DataContextState} from "@/utils/client/client-data-utils";

export type DreamTagsState = DreamContextState<DreamTag[], DreamTag>
export type DreamTagsState = DataContextState<DreamTag[], DreamTag>

const useDreamTags = (): DreamTagsState => {
const {data: tags, isLoading: tagsLoading, mutate: mutateTags} = useSWR('/api/me/dreams/tags', fetcher<DreamTag[]>)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
import useSWR, {KeyedMutator} from "swr";
import {fetcher} from "@/utils/client-utils";
import {fetcher} from "@/utils/client/client-utils";
import {Dream} from "@prisma/client";
import {useCallback} from "react";
import {DataContextState} from "@/utils/client/client-data-utils";

export type DreamsState = DreamContextState<Dream[], Dream>

export type DreamContextState<T, O> = {
loading: boolean,
data: T,
mutateData?: KeyedMutator<T>,
optimisticData: {
addOptimisticData: (work: () => Promise<O | undefined | null>, optimisticData: O) => Promise<void>,
removeOptimisticData: (work: () => Promise<O | undefined | null>, removedData: O) => Promise<void>,
}
}
export type DreamsState = DataContextState<Dream[], Dream>

const useDreams = (): DreamsState => {
const {data: dreams, isLoading: dreamsLoading, mutate: mutateDreams} = useSWR('/api/me/dreams', fetcher<Dream[]>)
Expand Down
6 changes: 4 additions & 2 deletions src/app/(site)/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import {Modal as NextModal, ModalBody, ModalContent, ModalFooter, ModalHeader, M

type Props = {
subtitle?: string,
header?: ReactElement | ReactElement[]
footer?: ReactElement | ReactElement[]
} & ModalProps

const Modal: FC<Props> = ({footer, title, subtitle, children, ...props}) => {
const Modal: FC<Props> = ({header, footer, title, subtitle, children, ...props}) => {
return (
<NextModal
backdrop="blur"
Expand All @@ -19,11 +20,12 @@ const Modal: FC<Props> = ({footer, title, subtitle, children, ...props}) => {
{...props}
>
<ModalContent>
{(title || subtitle) && (
{(title || subtitle || header) && (
<ModalHeader>
<div>
{title && <h1 className="text-4xl">{title}</h1>}
{subtitle && <h3 className="text-sm text-subtext mt-3">{subtitle}</h3>}
{header}
</div>
</ModalHeader>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(site)/hooks/useMemberInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useSession} from "next-auth/react";
import useSWR from "swr";
import {fetcher} from "@/utils/client-utils";
import {fetcher} from "@/utils/client/client-utils";
import {Member} from "@prisma/client";


Expand Down
19 changes: 19 additions & 0 deletions src/app/api/me/dreams/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {authenticated} from "@/app/api/utils/api-utils";
import dreamsService from "@/app/api/me/dreams/dreams.service";

export type DreamRouteContext = {
params: {
id: string
}
}

const TAGS_SEARCH_PARAM = "tags"
const CHARACTERS_SEARCH_PARAM = "characters"

export const GET = async (request: Request, {params}: DreamRouteContext) =>
authenticated((session) => {
const searchParams = new URL(request.url).searchParams
const withTags = searchParams.get(TAGS_SEARCH_PARAM)?.toLowerCase() === "true"
const withCharacters = searchParams.get(CHARACTERS_SEARCH_PARAM)?.toLowerCase() === "true"
return dreamsService.fetchDream(session, params.id, {withTags, withCharacters})
})
9 changes: 8 additions & 1 deletion src/app/api/me/dreams/dreams.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {z} from "zod";
import {Dream, DreamCharacter, DreamTag, Member} from "@prisma/client";

export type PostDreamDto = {
title: string,
Expand Down Expand Up @@ -55,4 +56,10 @@ export const PostDreamCharacterSchema = z.object({
name: z.string()
.min(DREAM_CHARACTER_MIN, `The name can't be less than ${DREAM_CHARACTER_MIN} character!`)
.max(DREAM_CHARACTER_MAX, `The name can't be more than ${DREAM_CHARACTER_MAX} characters!`)
}).strict()
}).strict()

export type DreamWithRelations = Dream & {
tags?: DreamTag[]
characters?: DreamCharacter[]
user?: Member[]
}
Loading

0 comments on commit 4af070a

Please sign in to comment.