begin implementation of edit story functionality

This commit is contained in:
andrzej 2024-09-26 12:37:52 +02:00
parent b9d5cfc18d
commit 6e501aa75f
16 changed files with 198 additions and 93 deletions

Binary file not shown.

View File

@ -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<Story | boolean | undefined> {
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<S
}
})
revalidatePath("/story")
return res
return { success: `Created the story '${data.title}'.` }
} catch (error) {
console.error(error)
return false
@ -36,7 +36,7 @@ export async function createStory(data: Story & { genres: number[] }): Promise<S
}
export async function createPub(data: Pub & { genres: number[] }): Promise<Pub | boolean | undefined> {
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<Pub |
{ genres: { set: genresArray } }
})
revalidatePath("/publication")
return genresRes
return { success: `Created the publication '${data.title}'.` }
} catch (error) {
console.error(error)
return false
@ -77,7 +77,7 @@ export async function createPub(data: Pub & { genres: number[] }): Promise<Pub |
export async function createSub(data: Sub): Promise<Sub | undefined> {
export async function createSub(data: Sub): Promise<Sub | boolean> {
"use server"
try {
subSchema.parse(data)
@ -86,6 +86,6 @@ export async function createSub(data: Sub): Promise<Sub | undefined> {
return res
} catch (error) {
console.error(error)
return undefined
return false
}
}

View File

@ -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")
}

View File

@ -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<Sub | undefined> {
export async function updateSub(data: Sub): Promise<Sub | boolean> {
"use server"
try {
subSchema.parse(data)
@ -52,35 +53,62 @@ export async function updateSub(data: Sub): Promise<Sub | undefined> {
return res
} catch (error) {
console.error(error)
return undefined
return false
}
}
export async function updateStory(data: Story & { genres: number[] }): Promise<Story | undefined> {
export async function updateStory(data: Story & { genres: number[] }): Promise<Story | boolean> {
"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<Pub | boolean> {
"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
}
}

View File

@ -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<Pub | Boolean> {
const pubData = structuredClone(data)
delete pubData.genres
pubSchemaTrimmed.safeParse(pubData)
return pubData
}
export async function prepGenreData(data: number[]): Promise<{ id: number }[]> {
"use server"

View File

@ -29,7 +29,7 @@ export default function CreatePubDialog({ genres }: ComponentProps<"div"> & { ge
<DialogTitle>New publication</DialogTitle>
<DialogDescription>Create an entry for a new publication i.e. a place you intend to submit stories to.</DialogDescription>
</DialogHeader>
<PubForm createPub={createPub} genres={genres} closeDialog={closeDialog} />
<PubForm dbAction={createPub} genres={genres} closeDialog={closeDialog} />
<DialogFooter>
<Button form="pubform">Submit</Button>
</DialogFooter>

View File

@ -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 (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button>
<span className="hidden md:block">Create new publication</span>
<Plus className="block md:hidden" />
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit publication</DialogTitle>
<DialogDescription>Modify an entry for an existing publication.</DialogDescription>
</DialogHeader>
<PubForm dbAction={createPub} genres={genres} closeDialog={closeDialog} />
<DialogFooter>
<Button form="pubform">Submit</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@ -28,7 +28,7 @@ export default function CreateStoryDialog({ genres }: ComponentProps<"div"> & {
<DialogTitle>New story</DialogTitle>
<DialogDescription>Create an entry for a new story i.e. a thing you intend to submit for publication.</DialogDescription>
</DialogHeader>
<StoryForm createStory={createStory} genres={genres} className="" closeDialog={closeDialog} />
<StoryForm dbAction={createStory} genres={genres} className="" closeDialog={closeDialog} />
<DialogFooter>
<Button form="storyform">Submit</Button>
</DialogFooter>

28
src/app/story/edit.tsx Normal file
View File

@ -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 (
<>
<DialogHeader>
<DialogTitle>Edit story</DialogTitle>
<DialogDescription>Create an entry for a new story i.e. a thing you intend to submit for publication.</DialogDescription>
</DialogHeader>
<StoryForm dbAction={createStory} genres={genres} className="" closeDialog={closeDialog} defaults={defaults} />
<DialogFooter>
<Button form="storyform">Submit</Button>
</DialogFooter>
</>
)
}

View File

@ -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
<DialogTitle>New submission</DialogTitle>
<DialogDescription>Create an entry for a new story i.e. a thing you intend to submit for publication.</DialogDescription>
</DialogHeader>
<SubmissionForm pubs={pubs} responses={responses} stories={stories} defaults={defaults} closeDialog={closeDialog} />
<SubmissionForm pubs={pubs} responses={responses} stories={stories} closeDialog={closeDialog} />
<DialogFooter>
<Button form="subform">Submit</Button>
</DialogFooter>

View File

@ -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;
}

View File

@ -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<Genre>, createPub: (data: any) => void, closeDialog: () => void }) {
export default function PubForm({ genres, dbAction, className, closeDialog, defaults }: ComponentProps<"div"> & { genres: Array<Genre>, dbAction: (data: any) => Promise<{ success: string }>, closeDialog: () => void, defaults?: Pub }) {
const form = useForm<z.infer<typeof pubSchema>>({
resolver: zodResolver(pubSchema),
defaultValues: {
@ -38,9 +38,9 @@ export default function PubForm({ genres, createPub, className, closeDialog }: C
async function onSubmit(values: z.infer<typeof pubSchema>) {
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) {

View File

@ -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<Genre>, createStory: (data: any) => void, className: string, closeDialog: () => void }) {
export default function StoryForm({ genres, dbAction, className, closeDialog, defaults }: ComponentProps<"div"> & { genres: Array<Genre>, dbAction: (data: any) => Promise<{ success: string }>, className: string, closeDialog: () => void, defaults?: Story }) {
const form = useForm<z.infer<typeof formSchema>>({
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<typeof formSchema>) {
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) {

View File

@ -40,7 +40,7 @@ import { Ban } from "lucide-react"
export type SubForm = z.infer<typeof subSchema>
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<z.infer<typeof subSchema>>({
resolver: zodResolver(subSchema),
defaultValues: {

View File

@ -19,15 +19,9 @@ export default function FormContextMenu({ table, row, openEditDialog, openDelete
: ""
}
{
pathname === "/submission" ?
<>
<ContextMenuItem onClick={() => openEditDialog(row)}>
Edit
</ContextMenuItem>
</>
: ""
}
{
selectedRows.length > 0 ?

View File

@ -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<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
@ -167,6 +168,7 @@ export function DataTable<TData, TValue>({
<Dialog open={isEditDialogVisible} onOpenChange={setIsEditDialogVisible}>
<DialogContent>
{tableName === "sub" ?
<EditSubmissionDialog
stories={stories}
pubs={pubs}
@ -174,6 +176,15 @@ export function DataTable<TData, TValue>({
defaults={dialogRow?.original}
closeDialog={closeEditDialog}
/>
: tableName === "story" ?
< EditStoryDialog
genres={genres}
// TODO: prepare genre data so that it can be read by StoryForm
defaults={dialogRow?.original}
closeDialog={closeEditDialog}
/>
: ""
}
</DialogContent>
</Dialog>
@ -223,7 +234,7 @@ export function DataTable<TData, TValue>({
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)
}}>