diff --git a/prisma/dev.db b/prisma/dev.db index 168826f..e870c4b 100644 Binary files a/prisma/dev.db and b/prisma/dev.db differ diff --git a/src/app/lib/create.ts b/src/app/lib/create.ts index d0f2d2b..fa99386 100644 --- a/src/app/lib/create.ts +++ b/src/app/lib/create.ts @@ -11,7 +11,7 @@ import { prepGenreData, prepStoryData } from "./validate" //TODO - data validation, error handling, unauthorized access handling -export async function createStory(data: Story & { genres: number[] }): Promise { +export async function createStory(data: Story & { genres: number[] }): Promise<{ success: string }> { // will return undefined if middleware authorization fails "use server" try { @@ -27,7 +27,7 @@ export async function createStory(data: Story & { genres: number[] }): Promise { +export async function createPub(data: Pub & { genres: number[] }): Promise<{ success: string }> { "use server" //prepare data const pubData = { @@ -67,7 +67,7 @@ export async function createPub(data: Pub & { genres: number[] }): Promise { +export async function createSub(data: Sub): Promise { "use server" try { subSchema.parse(data) @@ -86,6 +86,6 @@ export async function createSub(data: Sub): Promise { return res } catch (error) { console.error(error) - return undefined + return false } } diff --git a/src/app/lib/edit.ts b/src/app/lib/edit.ts deleted file mode 100644 index 273e5b3..0000000 --- a/src/app/lib/edit.ts +++ /dev/null @@ -1,15 +0,0 @@ -"use server" -import prisma from "./db" -import { revalidatePath } from "next/cache" -import { redirect } from "next/navigation" -import { SubForm } from "app/ui/forms/sub" - -export async function editSubmission(data: SubForm) { - const res = await prisma.sub.update({ - where: { id: data.id }, - data - }) - console.log(`updated ${data} to ${res}`) - revalidatePath("/submission") - redirect("/submission") -} diff --git a/src/app/lib/update.ts b/src/app/lib/update.ts index e9c38be..1ec01b5 100644 --- a/src/app/lib/update.ts +++ b/src/app/lib/update.ts @@ -1,5 +1,6 @@ "use server" -import { Genre, Story, Sub } from "@prisma/client" +import { prepGenreData, prepPubData, prepStoryData } from "./validate" +import { Genre, Pub, Story, Sub } from "@prisma/client" import prisma from "./db" import { revalidatePath } from "next/cache" import { redirect } from "next/navigation" @@ -21,7 +22,7 @@ export async function updateField({ datum, table, column, id, pathname }: { datu return res } catch (error) { console.error(error) - return undefined + return false } } @@ -39,11 +40,11 @@ export async function updateGenres({ genres, table, id, pathname }: { genres: { return res } catch (error) { console.error(error) - return undefined + return false } } -export async function updateSub(data: Sub): Promise { +export async function updateSub(data: Sub): Promise { "use server" try { subSchema.parse(data) @@ -52,35 +53,62 @@ export async function updateSub(data: Sub): Promise { return res } catch (error) { console.error(error) - return undefined + return false } } -export async function updateStory(data: Story & { genres: number[] }): Promise { +export async function updateStory(data: Story & { genres: number[] }): Promise { "use server" - //prepare data - const genresArray = data.genres.map((e) => { return { id: e } }) - const storyData = { - title: data.title, - word_count: data.word_count, - } - //prepare schemas - const schema = storySchema.omit({ id: true, genres: true }) - const genreSchema = z.object({ id: z.number() }) - const genresSchema = z.array(genreSchema) - try { - //validate - schema.safeParse(storyData) - genresSchema.safeParse(genresArray) + try { + //prep and validate + const storyData = await prepStoryData(data) + const genresArray = await prepGenreData(data.genres) + //submit + const res = prisma.story.update({ + where: { id: data.id }, + data: storyData + }) + await prisma.story.update({ + where: { id: data.id }, + data: { + genres: { set: genresArray } + } + }) + return res } catch (error) { console.error(error) - return undefined + return false + } +} + + +export async function updatePub(data: Pub & { genres: number[] }): Promise { + "use server" + + try { + //prep and validate + const pubData = await prepPubData + (data) + const genresArray = await prepGenreData(data.genres) + //submit + const res = prisma.pub.update({ + where: { id: data.id }, + data: pubData + }) + await prisma.pub.update({ + where: { id: data.id }, + data: { + genres: { set: genresArray } + } + }) + return res + } catch (error) { + console.error(error) + return false } } - - diff --git a/src/app/lib/validate.ts b/src/app/lib/validate.ts index 8b1bdb1..9aa04a7 100644 --- a/src/app/lib/validate.ts +++ b/src/app/lib/validate.ts @@ -1,26 +1,30 @@ import { z } from "zod"; import { storySchema } from "app/ui/forms/schemas"; -import { Story } from "@prisma/client"; +import { Pub, Story } from "@prisma/client"; +import { pubSchema } from "app/ui/forms/schemas"; //schemas -const schema = storySchema.omit({ id: true, genres: true }) +const storySchemaTrimmed = storySchema.omit({ genres: true }) +const pubSchemaTrimmed = pubSchema.omit({ genres: true }) const genreSchema = z.object({ id: z.number() }) const genresSchema = z.array(genreSchema) export async function prepStoryData(data: Story & { genres: number[] }): Promise<{ title: string, word_count: number }> { - const storyData = { - title: data.title, - word_count: data.word_count, - } - //prepare schemas - + const storyData = structuredClone(data) + delete storyData.genres //throw an error if validation fails - schema.safeParse(storyData) - + storySchemaTrimmed.safeParse(storyData) return storyData } +export async function prepPubData(data: Pub & { genres: number[] }): Promise { + const pubData = structuredClone(data) + delete pubData.genres + pubSchemaTrimmed.safeParse(pubData) + return pubData +} + export async function prepGenreData(data: number[]): Promise<{ id: number }[]> { "use server" diff --git a/src/app/publication/create.tsx b/src/app/publication/create.tsx index a50db64..be438a7 100644 --- a/src/app/publication/create.tsx +++ b/src/app/publication/create.tsx @@ -29,7 +29,7 @@ export default function CreatePubDialog({ genres }: ComponentProps<"div"> & { ge New publication Create an entry for a new publication i.e. a place you intend to submit stories to. - + diff --git a/src/app/publication/edit.tsx b/src/app/publication/edit.tsx new file mode 100644 index 0000000..d25c862 --- /dev/null +++ b/src/app/publication/edit.tsx @@ -0,0 +1,40 @@ +"use client" +import { Dialog, DialogHeader, DialogTrigger, DialogContent, DialogClose, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { ComponentProps } from "react"; +import { Genre } from "@prisma/client"; +import { createPub } from "app/lib/create"; +import PubForm from "app/ui/forms/pub"; +import { Plus } from "lucide-react"; +import { useState } from "react"; + +export default function CreatePubDialog({ genres }: ComponentProps<"div"> & { genres: Genre[] }) { + + const [isOpen, setIsOpen] = useState(false) + function closeDialog() { + setIsOpen(false) + } + + return ( + + + + + + + + Edit publication + Modify an entry for an existing publication. + + + + + + + + + ) +} diff --git a/src/app/story/create.tsx b/src/app/story/create.tsx index d3cba95..630b6a3 100644 --- a/src/app/story/create.tsx +++ b/src/app/story/create.tsx @@ -28,7 +28,7 @@ export default function CreateStoryDialog({ genres }: ComponentProps<"div"> & { New story Create an entry for a new story i.e. a thing you intend to submit for publication. - + diff --git a/src/app/story/edit.tsx b/src/app/story/edit.tsx new file mode 100644 index 0000000..d64b575 --- /dev/null +++ b/src/app/story/edit.tsx @@ -0,0 +1,28 @@ +"use client" +import { createStory } from "app/lib/create" +import { Dialog, DialogHeader, DialogTrigger, DialogContent, DialogClose, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { ComponentProps, useState } from "react"; +import { Genre, Story } from "@prisma/client"; +import StoryForm from "app/ui/forms/story"; +import { Plus } from "lucide-react"; + + +export default function EditStoryDialog({ genres, closeDialog, defaults }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: Story }) { + + + return ( + <> + + Edit story + Create an entry for a new story i.e. a thing you intend to submit for publication. + + + + + + + + ) +} + diff --git a/src/app/submission/create.tsx b/src/app/submission/create.tsx index 0c26631..0c4dd7c 100644 --- a/src/app/submission/create.tsx +++ b/src/app/submission/create.tsx @@ -10,16 +10,8 @@ import { Plus } from "lucide-react"; import { useState } from "react"; -type CreateSubDefaults = { - subId?: number, - storyId: number, - pubId: number, - submitted: Date, - responded: Date, - respoonseId: number -} -export default function CreateSubmissionDialog({ stories, pubs, responses, defaults }: ComponentProps<"div"> & { stories: Story[], pubs: Pub[], responses: Response[], defaults?: CreateSubDefaults }) { +export default function CreateSubmissionDialog({ stories, pubs, responses }: ComponentProps<"div"> & { stories: Story[], pubs: Pub[], responses: Response[] }) { const [isOpen, setIsOpen] = useState(false) function closeDialog() { @@ -39,7 +31,7 @@ export default function CreateSubmissionDialog({ stories, pubs, responses, defau New submission Create an entry for a new story i.e. a thing you intend to submit for publication. - + diff --git a/src/app/tailwind.css b/src/app/tailwind.css index 3e4b5f8..70a70ce 100644 --- a/src/app/tailwind.css +++ b/src/app/tailwind.css @@ -777,6 +777,14 @@ body { margin-top: 1.5rem; } +.mt-auto { + margin-top: auto; +} + +.mb-4 { + margin-bottom: 1rem; +} + .block { display: block; } @@ -879,6 +887,10 @@ body { height: 100vh; } +.h-5\/6 { + height: 83.333333%; +} + .max-h-96 { max-height: 24rem; } @@ -964,6 +976,10 @@ body { width: 100vw; } +.w-5\/6 { + width: 83.333333%; +} + .min-w-\[8rem\] { min-width: 8rem; } @@ -1251,6 +1267,11 @@ body { border-top-right-radius: 1.5rem; } +.rounded-l-3xl { + border-top-left-radius: 1.5rem; + border-bottom-left-radius: 1.5rem; +} + .rounded-tl-3xl { border-top-left-radius: 1.5rem; } diff --git a/src/app/ui/forms/pub.tsx b/src/app/ui/forms/pub.tsx index 139df87..c0e1c7f 100644 --- a/src/app/ui/forms/pub.tsx +++ b/src/app/ui/forms/pub.tsx @@ -17,13 +17,13 @@ import { toast } from "@/components/ui/use-toast" import { randomPublicationTitle } from "app/lib/shortStoryTitleGenerator" import { ComponentProps } from "react" -import { Genre } from "@prisma/client" +import { Genre, Pub } from "@prisma/client" import GenrePicker from "./genrePicker" import { pubSchema } from "./schemas" import { useRouter } from "next/navigation" import { Ban } from "lucide-react" -export default function PubForm({ genres, createPub, className, closeDialog }: ComponentProps<"div"> & { genres: Array, createPub: (data: any) => void, closeDialog: () => void }) { +export default function PubForm({ genres, dbAction, className, closeDialog, defaults }: ComponentProps<"div"> & { genres: Array, dbAction: (data: any) => Promise<{ success: string }>, closeDialog: () => void, defaults?: Pub }) { const form = useForm>({ resolver: zodResolver(pubSchema), defaultValues: { @@ -38,9 +38,9 @@ export default function PubForm({ genres, createPub, className, closeDialog }: C async function onSubmit(values: z.infer) { try { - const res = await createPub(values) + const res = await dbAction(values) if (!res) throw new Error("something went wrong") - toast({ title: "Successfully submitted:", description: values.title }) + toast({ title: "Success!", description: res.success }) router.refresh() closeDialog() } catch (error) { diff --git a/src/app/ui/forms/story.tsx b/src/app/ui/forms/story.tsx index e5e1a52..ea3354d 100644 --- a/src/app/ui/forms/story.tsx +++ b/src/app/ui/forms/story.tsx @@ -29,23 +29,25 @@ export const formSchema = z.object({ genres: z.array(z.number()) }) -export default function StoryForm({ genres, createStory, className, closeDialog }: ComponentProps<"div"> & { genres: Array, createStory: (data: any) => void, className: string, closeDialog: () => void }) { +export default function StoryForm({ genres, dbAction, className, closeDialog, defaults }: ComponentProps<"div"> & { genres: Array, dbAction: (data: any) => Promise<{ success: string }>, className: string, closeDialog: () => void, defaults?: Story }) { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - word_count: 500, + title: defaults?.title ?? "", + word_count: defaults?.word_count ?? 500, genres: [] }, }) + console.log("DEFAULTS: " + defaults) const router = useRouter() async function onSubmit(values: z.infer) { try { - const res = await createStory(values) + const res = await dbAction(values) //server actions return undefined if middleware authentication fails - if (!res) throw new Error("something went wrong") - toast({ title: "Sucessfully submitted:", description: values.title }) + if (!res.success) throw new Error("something went wrong") + toast({ title: "Success!", description: res.success }) router.refresh() closeDialog() } catch (error) { diff --git a/src/app/ui/forms/sub.tsx b/src/app/ui/forms/sub.tsx index 989b1d1..0dd2005 100644 --- a/src/app/ui/forms/sub.tsx +++ b/src/app/ui/forms/sub.tsx @@ -40,7 +40,7 @@ import { Ban } from "lucide-react" export type SubForm = z.infer -export default function SubmissionForm({ stories, pubs, responses, defaults, closeDialog }: { stories: any, pubs: any, responses: any, defaults: any, closeDialog: () => void }) { +export default function SubmissionForm({ stories, pubs, responses, defaults, closeDialog }: { stories: any, pubs: any, responses: any, defaults?: any, closeDialog: () => void }) { const form = useForm>({ resolver: zodResolver(subSchema), defaultValues: { diff --git a/src/app/ui/tables/contextMenu.tsx b/src/app/ui/tables/contextMenu.tsx index c12e13c..2a41b25 100644 --- a/src/app/ui/tables/contextMenu.tsx +++ b/src/app/ui/tables/contextMenu.tsx @@ -19,15 +19,9 @@ export default function FormContextMenu({ table, row, openEditDialog, openDelete : "" } - { - pathname === "/submission" ? - <> - openEditDialog(row)}> - Edit - - - : "" - } + openEditDialog(row)}> + Edit + { selectedRows.length > 0 ? diff --git a/src/app/ui/tables/data-table.tsx b/src/app/ui/tables/data-table.tsx index 70dec7e..5a9ed5b 100644 --- a/src/app/ui/tables/data-table.tsx +++ b/src/app/ui/tables/data-table.tsx @@ -52,6 +52,7 @@ import EditSubmissionDialog from "app/submission/edit" import { DialogTitle } from "@radix-ui/react-dialog" import { toast } from "@/components/ui/use-toast" import { useRouter } from "next/navigation" +import EditStoryDialog from "app/story/edit" export interface DataTableProps { columns: ColumnDef[] @@ -167,13 +168,23 @@ export function DataTable({ - + {tableName === "sub" ? + + : tableName === "story" ? + < EditStoryDialog + genres={genres} + // TODO: prepare genre data so that it can be read by StoryForm + defaults={dialogRow?.original} + closeDialog={closeEditDialog} + /> + : "" + } @@ -223,7 +234,7 @@ export function DataTable({ const recordIds = rowIds.map(id => Number(table.getRow(id).original.id)) const res = await deleteRecords(recordIds, pathname) if (!res) toast({ title: "Oh dear...", description: "Failed to delete." }) - if (res) toast({ title: "Sucessfully deleted records of id:", description: JSON.stringify(recordIds) }) + if (res) toast({ title: "Successfully deleted records of id:", description: JSON.stringify(recordIds) }) router.refresh() setIsDeleteDialogVisible(false) }}>