Skip to content

Commit

Permalink
Start dream log implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
bombies committed Oct 26, 2023
1 parent 27fd87b commit 786f674
Show file tree
Hide file tree
Showing 31 changed files with 1,052 additions and 28 deletions.
9 changes: 9 additions & 0 deletions prisma/migrations/20231025194434_update_dream/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
Warnings:
- Added the required column `updatedAt` to the `Dream` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Dream" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
3 changes: 3 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ model Dream {
user Member @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

model DreamTag {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {FC, useMemo} from "react";
import {useDreamsData} from "@/app/(site)/(internal)/dashboard/components/dreams/DreamsProvider";
import DreamCard from "@/app/(site)/(internal)/dashboard/components/dreams/DreamCard";
import LogDreamCard from "@/app/(site)/(internal)/dashboard/components/dreams/LogDreamCard";
import useTodayTimeRange from "@/app/(site)/hooks/useTodayTimeRange";

const CurrentDreamsContainer: FC = () => {
const [startOfToday, endOfToday] = useTodayTimeRange()
const {dreams} = useDreamsData()
const dreamCards = useMemo(() => dreams.data
.filter(dream => {
const creationDate = new Date(dream.createdAt.toString());
return creationDate.getTime() >= startOfToday.getTime() && creationDate.getTime() <= endOfToday.getTime()
})
.map(dream => (
<DreamCard key={dream.id} dream={dream}/>
)), [dreams.data, endOfToday, startOfToday])

return (
<div className="w-3/4">
<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-full tablet:self-center flex flex-col gap-y-6">
<LogDreamCard/>
{dreamCards}
</div>
</div>

)
}

export default CurrentDreamsContainer
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {FC, Fragment} from "react";
import {Dream} from "@prisma/client";
import {CardBody, CardHeader} from "@nextui-org/card";
import Card from "@/app/(site)/components/Card";

type Props = {
dream: Dream
}

const DreamCard: FC<Props> = ({dream}) => {
return (
<Fragment>
<Card classNames={{
header: "bg-secondary",
body: "bg-secondary",
footer: "bg-secondary",
}}>
<CardHeader className="flex justify-between">
<h2>{dream.title}</h2>
<p>
{new Date(dream.createdAt.toString()).toLocaleTimeString("en-US", {
timeStyle: 'short'
})}
</p>
</CardHeader>
<CardBody>
<p>
{dream.description.substring(0, Math.min(dream.description.length, 100))}
</p>
</CardBody>
</Card>
</Fragment>
)
}

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

import {createContext, FC, PropsWithChildren, useContext} 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";

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

const DreamsContext = createContext<DreamsContextProps | undefined>(undefined)

const DreamsProvider: FC<PropsWithChildren> = ({children}) => {
const dreams = useDreams()
const characters = useDreamCharacters()
const tags = useDreamTags()

return (
<DreamsContext.Provider value={{dreams, characters, tags}}>
{children}
</DreamsContext.Provider>
)
}

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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use client"

import {FC, Fragment, useState} from "react";
import {CardBody} from "@nextui-org/card";
import CloudIcon from "@/app/(site)/components/icons/CloudIcon";
import Card from "@/app/(site)/components/Card";
import LogDreamModal from "@/app/(site)/(internal)/dashboard/components/dreams/forms/log/LogDreamModal";

const LogDreamCard: FC = () => {
const [modalOpen, setModalOpen] = useState(false)

return (
<Fragment>
<LogDreamModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
/>
<Card
onPress={() => setModalOpen(true)}
isPressable
classNames={{
body: "bg-primary py-8"
}}
>
<CardBody>
<div className="flex gap-4">
<CloudIcon width={36}/>
<h3 className="font-semibold text-2xl phone:text-medium self-center">Log A New Dream</h3>
</div>
</CardBody>
</Card>
</Fragment>
)
}

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

import {FC} from "react";
import {SubmitHandler, useForm} from "react-hook-form";
import {PostDreamCharacterDto} from "@/app/api/me/dreams/dreams.dto";
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 {useSession} from "next-auth/react";
import toast from "react-hot-toast";
import Input from "@/app/(site)/components/Input";
import {Button} from "@nextui-org/button";

type FormProps = PostDreamCharacterDto

type CreateTagsArgs = {
arg: {
dto: PostDreamCharacterDto
}
}

const CreateCharacter = () => {
const mutator = (url: string, {arg}: CreateTagsArgs) => axios.post<DreamCharacter | null>(url, arg.dto)
return useSWRMutation('/api/me/dreams/characters', mutator)
}

type Props = {
onSuccess?: (tag: DreamCharacter) => void
}

const AddCharacterForm: FC<Props> = ({onSuccess}) => {
const {data: session} = useSession()
const {register, handleSubmit} = useForm<FormProps>()
const {trigger: createCharacter, isMutating: isCreating} = CreateCharacter()
const {characters} = useDreamsData()

const handleCreation = async (dto: PostDreamCharacterDto) => (
createCharacter({dto})
.then(res => {
const character = res.data!!
if (onSuccess)
onSuccess(character)
return character
})
.catch(handleAxiosError)
)

const onSubmit: SubmitHandler<FormProps> = async (data) => {
if (!session?.user)
return;

await toast.promise(characters.optimisticData
.addOptimisticData(() => handleCreation(data), {
id: '',
...data,
createdAt: new Date(),
updatedAt: new Date(),
userId: session.user.id
}),
{
loading: "Adding new character...",
success: "Successfully added that character!",
error: "Could not add that character!"
}
)

}

return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-6">
<Input
isRequired
id="name"
label="Character Name"
register={register}
labelPlacement="outside"
placeholder="Enter the name of the character..."
isDisabled={isCreating}
maxLength={256}
/>
<Button
isLoading={isCreating}
isDisabled={isCreating}
type="submit"
color="primary"
variant="shadow"
>Create Character</Button>
</div>
</form>
)
}

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

import {FC} from "react";
import Modal from "@/app/(site)/components/Modal";
import AddTagForm from "@/app/(site)/(internal)/dashboard/components/dreams/forms/tags/AddTagForm";
import AddCharacterForm from "@/app/(site)/(internal)/dashboard/components/dreams/forms/characters/AddCharacterForm";

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

const AddCharacterModal: FC<Props> = ({isOpen, onClose}) => {
return (
<Modal
size="xl"
isOpen={isOpen}
onClose={onClose}
title="Add New Character"
>
<AddCharacterForm onSuccess={onClose} />
</Modal>
)
}

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

import {FC, Fragment, useState} from "react";
import Button from "@/app/(site)/components/Button";
import Select from "@/app/(site)/components/Select";
import {Chip} from "@nextui-org/chip";
import {SelectItem} from "@nextui-org/react";
import {DreamCharactersState} from "@/app/(site)/(internal)/dashboard/components/dreams/hooks/useDreamCharacters";
import {UseFormRegister} from "react-hook-form";
import AddCharacterModal from "@/app/(site)/(internal)/dashboard/components/dreams/forms/characters/AddCharacterModal";

type Props = {
register?: UseFormRegister<any>,
characters: DreamCharactersState
}

const DreamCharacterSelect: FC<Props> = ({characters, register}) => {
const [modalOpen, setModalOpen] = useState(false)

return (
<Fragment>
<AddCharacterModal
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
/>
<div className="flex gap-4 mb-2">
<label className="text-lg phone:text-medium text-[#EAE0FF] self-center">Dream Characters</label>
<Button
color="cta"
size="sm"
onPress={() => setModalOpen(true)}
>
Add New Character
</Button>
</div>
<Select
aria-label="Dream Characters"
register={register}
id="characters"
items={characters.data}
placeholder="Who was in your dream?"
selectionMode={"multiple"}
renderValue={(items) => {
return (
<div className="flex gap-2">
{items.map(character => (
<Chip
key={character.key}
color="primary"
variant="flat"
className="max-w-[6rem]"
classNames={{
content: "overflow-ellipsis whitespace-nowrap overflow-hidden"
}}
>
{character.data?.name}
</Chip>
))}
</div>
)
}}
>
{(character) => (
<SelectItem
key={character.id}
value={character.id}
classNames={{
title: "capitalize"
}}
>
{character.name}
</SelectItem>
)}
</Select>
</Fragment>
)
}

export default DreamCharacterSelect
Loading

0 comments on commit 786f674

Please sign in to comment.