begin implementation of edit story functionality
This commit is contained in:
parent
b9d5cfc18d
commit
6e501aa75f
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -19,15 +19,9 @@ export default function FormContextMenu({ table, row, openEditDialog, openDelete
|
|||
: ""
|
||||
}
|
||||
|
||||
{
|
||||
pathname === "/submission" ?
|
||||
<>
|
||||
<ContextMenuItem onClick={() => openEditDialog(row)}>
|
||||
Edit
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
: ""
|
||||
}
|
||||
<ContextMenuItem onClick={() => openEditDialog(row)}>
|
||||
Edit
|
||||
</ContextMenuItem>
|
||||
|
||||
{
|
||||
selectedRows.length > 0 ?
|
||||
|
|
|
@ -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,13 +168,23 @@ export function DataTable<TData, TValue>({
|
|||
|
||||
<Dialog open={isEditDialogVisible} onOpenChange={setIsEditDialogVisible}>
|
||||
<DialogContent>
|
||||
<EditSubmissionDialog
|
||||
stories={stories}
|
||||
pubs={pubs}
|
||||
responses={responses}
|
||||
defaults={dialogRow?.original}
|
||||
closeDialog={closeEditDialog}
|
||||
/>
|
||||
{tableName === "sub" ?
|
||||
<EditSubmissionDialog
|
||||
stories={stories}
|
||||
pubs={pubs}
|
||||
responses={responses}
|
||||
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)
|
||||
}}>
|
||||
|
|
Loading…
Reference in New Issue