fix build errors
This commit is contained in:
parent
20d211bdc4
commit
ca097dfb65
|
@ -9,7 +9,7 @@ export async function getJWTSecretKey<Uint8Array>() {
|
||||||
const secret = process.env.JWT_SECRET
|
const secret = process.env.JWT_SECRET
|
||||||
if (!secret) throw new Error("There is no JWT secret key")
|
if (!secret) throw new Error("There is no JWT secret key")
|
||||||
try {
|
try {
|
||||||
const enc: Uint8Array = new TextEncoder().encode(secret)
|
const enc = new TextEncoder().encode(secret)
|
||||||
return enc
|
return enc
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error("failed to getJWTSecretKey", error.message)
|
throw new Error("failed to getJWTSecretKey", error.message)
|
||||||
|
|
|
@ -8,15 +8,16 @@ import { storySchema } from "app/ui/forms/schemas"
|
||||||
import { pubSchema } from "app/ui/forms/schemas"
|
import { pubSchema } from "app/ui/forms/schemas"
|
||||||
import { subSchema } from "app/ui/forms/schemas"
|
import { subSchema } from "app/ui/forms/schemas"
|
||||||
import { prepGenreData, prepStoryData } from "./validate"
|
import { prepGenreData, prepStoryData } from "./validate"
|
||||||
|
import { SubForm } from "app/ui/forms/sub"
|
||||||
|
|
||||||
//TODO - data validation, error handling, unauthorized access handling
|
//TODO - data validation, error handling, unauthorized access handling
|
||||||
|
|
||||||
export async function createStory(data: Story & { genres: number[] }): Promise<{ success: string }> {
|
export async function createStory({ story, genres }: { story: Story, genres: number[] }): Promise<{ success: string }> {
|
||||||
// will return undefined if middleware authorization fails
|
// will return undefined if middleware authorization fails
|
||||||
"use server"
|
"use server"
|
||||||
try {
|
try {
|
||||||
const storyData = await prepStoryData(data)
|
const storyData = await prepStoryData(story)
|
||||||
const genresArray = await prepGenreData(data.genres)
|
const genresArray = await prepGenreData(genres)
|
||||||
|
|
||||||
//submit
|
//submit
|
||||||
const res = await prisma.story.create({ data: storyData })
|
const res = await prisma.story.create({ data: storyData })
|
||||||
|
@ -27,24 +28,17 @@ export async function createStory(data: Story & { genres: number[] }): Promise<{
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
revalidatePath("/story")
|
revalidatePath("/story")
|
||||||
return { success: `Created the story '${data.title}'.` }
|
return { success: `Created the story '${story.title}'.` }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function createPub(data: Pub & { genres: number[] }): Promise<{ success: string }> {
|
export async function createPub({ pub, genres }: { pub: Pub, genres: number[] }): Promise<{ success: string }> {
|
||||||
"use server"
|
"use server"
|
||||||
//prepare data
|
const genresArray = genres.map(e => { return { id: e } })
|
||||||
const pubData = {
|
|
||||||
title: data.title,
|
|
||||||
link: data.link,
|
|
||||||
query_after_days: data.query_after_days
|
|
||||||
}
|
|
||||||
const genresArray = data.genres.map(e => { return { id: e } })
|
|
||||||
|
|
||||||
//prepare schemas
|
//prepare schemas
|
||||||
const schema = pubSchema.omit({ genres: true })
|
const schema = pubSchema.omit({ genres: true })
|
||||||
|
@ -54,12 +48,12 @@ export async function createPub(data: Pub & { genres: number[] }): Promise<{ suc
|
||||||
try {
|
try {
|
||||||
|
|
||||||
//validate
|
//validate
|
||||||
schema.parse(pubData)
|
schema.parse(pub)
|
||||||
genresSchema.safeParse(genresArray)
|
genresSchema.safeParse(genresArray)
|
||||||
|
|
||||||
//submit
|
//submit
|
||||||
const res = await prisma.pub.create({
|
const res = await prisma.pub.create({
|
||||||
data: pubData
|
data: pub
|
||||||
})
|
})
|
||||||
const genresRes = await prisma.pub.update({
|
const genresRes = await prisma.pub.update({
|
||||||
where: { id: res.id },
|
where: { id: res.id },
|
||||||
|
@ -67,10 +61,9 @@ export async function createPub(data: Pub & { genres: number[] }): Promise<{ suc
|
||||||
{ genres: { set: genresArray } }
|
{ genres: { set: genresArray } }
|
||||||
})
|
})
|
||||||
revalidatePath("/publication")
|
revalidatePath("/publication")
|
||||||
return { success: `Created the publication '${data.title}'.` }
|
return { success: `Created the publication '${pub.title}'.` }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,10 @@ const tableMap = {
|
||||||
"/submission": "sub"
|
"/submission": "sub"
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRecord(id: number, pathname: Pathname): Promise<undefined | boolean> {
|
export async function deleteRecord(id: number, pathname: string): Promise<undefined | boolean> {
|
||||||
const table = tableMap[pathname]
|
const table = tableMap[pathname]
|
||||||
try {
|
try {
|
||||||
|
//@ts-ignore
|
||||||
const res = await prisma[table].delete({ where: { id } })
|
const res = await prisma[table].delete({ where: { id } })
|
||||||
console.log(`deleted from ${table}: ${res.id}`)
|
console.log(`deleted from ${table}: ${res.id}`)
|
||||||
revalidatePath(pathname)
|
revalidatePath(pathname)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function pluralize(word: "story" | "publication" | "submission"): string {
|
export default function pluralize(word: string): string {
|
||||||
const map = {
|
const map = {
|
||||||
story: "stories",
|
story: "stories",
|
||||||
publication: "publications",
|
publication: "publications",
|
||||||
|
|
|
@ -3,11 +3,8 @@ import { prepGenreData, prepPubData, prepStoryData } from "./validate"
|
||||||
import { Genre, Pub, Story, Sub } from "@prisma/client"
|
import { Genre, Pub, Story, Sub } from "@prisma/client"
|
||||||
import prisma from "./db"
|
import prisma from "./db"
|
||||||
import { revalidatePath } from "next/cache"
|
import { revalidatePath } from "next/cache"
|
||||||
import { redirect } from "next/navigation"
|
import { subSchema } from "app/ui/forms/schemas"
|
||||||
import { storySchema, subSchema } from "app/ui/forms/schemas"
|
import { SubForm } from "app/ui/forms/sub"
|
||||||
import { z } from "zod"
|
|
||||||
import { StoryWithGenres } from "app/story/page"
|
|
||||||
import { PubWithGenres } from "app/publication/page"
|
|
||||||
|
|
||||||
|
|
||||||
export async function updateField({ datum, table, column, id, pathname }: { datum?: string | number | Genre[], table: string, column: string, id: number, pathname: string }) {
|
export async function updateField({ datum, table, column, id, pathname }: { datum?: string | number | Genre[], table: string, column: string, id: number, pathname: string }) {
|
||||||
|
@ -46,7 +43,7 @@ export async function updateGenres({ genres, table, id, pathname }: { genres: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateSub(data: Sub): Promise<Sub> {
|
export async function updateSub(data: SubForm): Promise<Sub> {
|
||||||
"use server"
|
"use server"
|
||||||
try {
|
try {
|
||||||
subSchema.parse(data)
|
subSchema.parse(data)
|
||||||
|
@ -60,7 +57,7 @@ export async function updateSub(data: Sub): Promise<Sub> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function updateStory(data: StoryWithGenres): Promise<{ success: string }> {
|
export async function updateStory(data: Story & { genres: number[] }): Promise<{ success: string }> {
|
||||||
"use server"
|
"use server"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -86,7 +83,7 @@ export async function updateStory(data: StoryWithGenres): Promise<{ success: str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function updatePub(data: PubWithGenres): Promise<{ success: string }> {
|
export async function updatePub(data: Pub & { genres: number[] }): Promise<{ success: string }> {
|
||||||
"use server"
|
"use server"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -11,17 +11,15 @@ const pubSchemaTrimmed = pubSchema.omit({ genres: true })
|
||||||
const genreSchema = z.object({ id: z.number() })
|
const genreSchema = z.object({ id: z.number() })
|
||||||
const genresSchema = z.array(genreSchema)
|
const genresSchema = z.array(genreSchema)
|
||||||
|
|
||||||
export async function prepStoryData(data: StoryWithGenres): Promise<{ title: string, word_count: number }> {
|
export async function prepStoryData(data: Story): Promise<{ title: string, word_count: number }> {
|
||||||
const storyData = structuredClone(data)
|
const storyData = structuredClone(data)
|
||||||
delete storyData.genres
|
|
||||||
//throw an error if validation fails
|
//throw an error if validation fails
|
||||||
storySchemaTrimmed.safeParse(storyData)
|
storySchemaTrimmed.safeParse(storyData)
|
||||||
return storyData
|
return storyData
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function prepPubData(data: Pub & { genres: number[] }): Promise<Pub> {
|
export async function prepPubData(data: Pub): Promise<Pub> {
|
||||||
const pubData = structuredClone(data)
|
const pubData = structuredClone(data)
|
||||||
delete pubData.genres
|
|
||||||
pubSchemaTrimmed.safeParse(pubData)
|
pubSchemaTrimmed.safeParse(pubData)
|
||||||
return pubData
|
return pubData
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default async function Page({ params }: { params: { id: string } }) {
|
||||||
<PageHeader>{pub.title}</PageHeader>
|
<PageHeader>{pub.title}</PageHeader>
|
||||||
<GenreBadges genres={pub.genres} className="my-6" />
|
<GenreBadges genres={pub.genres} className="my-6" />
|
||||||
<PageSubHeader>Submissions:</PageSubHeader>
|
<PageSubHeader>Submissions:</PageSubHeader>
|
||||||
<DataTable columns={columns} data={pubSubs} type="submission" />
|
<DataTable columns={columns} data={pubSubs} tableName="sub" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ export const columns: ColumnDef<PubWithGenres>[] = [
|
||||||
},
|
},
|
||||||
cell: cell => (
|
cell: cell => (
|
||||||
<>
|
<>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<p className="block text-xs max-w-24 break-words md:hidden">{cell.getValue()}</p>
|
<p className="block text-xs max-w-24 break-words md:hidden">{cell.getValue()}</p>
|
||||||
<TextInputCell cellContext={cell} className="hidden md:block" />
|
<TextInputCell cellContext={cell} className="hidden md:block" />
|
||||||
</>
|
</>
|
||||||
|
@ -49,6 +50,7 @@ export const columns: ColumnDef<PubWithGenres>[] = [
|
||||||
),
|
),
|
||||||
cell: cell => (
|
cell: cell => (
|
||||||
<>
|
<>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<p className="block text-xs max-w-16 truncate md:hidden">{cell.getValue()}</p>
|
<p className="block text-xs max-w-16 truncate md:hidden">{cell.getValue()}</p>
|
||||||
<TextInputCell cellContext={cell} className="hidden md:block" />
|
<TextInputCell cellContext={cell} className="hidden md:block" />
|
||||||
</>
|
</>
|
||||||
|
@ -78,6 +80,7 @@ export const columns: ColumnDef<PubWithGenres>[] = [
|
||||||
),
|
),
|
||||||
cell: cell => (
|
cell: cell => (
|
||||||
<>
|
<>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<p className="block md:hidden text-center">{cell.getValue()}</p>
|
<p className="block md:hidden text-center">{cell.getValue()}</p>
|
||||||
<NumberInputCell cellContext={cell} className="hidden md:block" />
|
<NumberInputCell cellContext={cell} className="hidden md:block" />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
import { Dialog, DialogHeader, DialogTrigger, DialogContent, DialogClose, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
import { Dialog, DialogHeader, DialogTrigger, DialogContent, DialogClose, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ComponentProps } from "react";
|
import { ComponentProps } from "react";
|
||||||
import { Genre } from "@prisma/client";
|
import { Genre, Pub } from "@prisma/client";
|
||||||
import { createPub } from "app/lib/create";
|
import { createPub } from "app/lib/create";
|
||||||
import PubForm from "app/ui/forms/pub";
|
import PubForm from "app/ui/forms/pub";
|
||||||
import { Plus } from "lucide-react";
|
import { Plus } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { PubWithGenres } from "./page";
|
import { PubWithGenres } from "./page";
|
||||||
|
|
||||||
export default function EditPubDialog({ genres, closeDialog, defaults, dbAction }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: PubWithGenres, dbAction: (data: PubWithGenres) => Promise<{ success: string }> }) {
|
export default function EditPubDialog({ genres, closeDialog, defaults, dbAction }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: PubWithGenres, dbAction: (data: Pub & { genres: number[] }) => Promise<{ success: string }> }) {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -31,6 +31,7 @@ export const columns: ColumnDef<StoryWithGenres>[] = [
|
||||||
},
|
},
|
||||||
cell: cell => (
|
cell: cell => (
|
||||||
<>
|
<>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<p className="block break-words max-w-28 md:hidden text-xs">{cell.getValue()}</p>
|
<p className="block break-words max-w-28 md:hidden text-xs">{cell.getValue()}</p>
|
||||||
<TextInputCell cellContext={cell} className="hidden md:block" />
|
<TextInputCell cellContext={cell} className="hidden md:block" />
|
||||||
</>
|
</>
|
||||||
|
@ -59,6 +60,7 @@ export const columns: ColumnDef<StoryWithGenres>[] = [
|
||||||
enableColumnFilter: false,
|
enableColumnFilter: false,
|
||||||
cell: cell => (
|
cell: cell => (
|
||||||
<>
|
<>
|
||||||
|
{/* @ts-ignore */}
|
||||||
<p className="block md:hidden text-center text-xs">{cell.getValue()}</p>
|
<p className="block md:hidden text-center text-xs">{cell.getValue()}</p>
|
||||||
<NumberInputCell cellContext={cell} className="hidden md:block" />
|
<NumberInputCell cellContext={cell} className="hidden md:block" />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -4,9 +4,10 @@ import prisma from "app/lib/db";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { CreateContainerContent, CreateContainerHeader, CreateContainer, CreateContainerDescription } from "app/ui/createContainer";
|
import { CreateContainerContent, CreateContainerHeader, CreateContainer, CreateContainerDescription } from "app/ui/createContainer";
|
||||||
|
import { Story } from "@prisma/client";
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const genres = await getGenres()
|
const genres = await getGenres()
|
||||||
async function createStory(data) {
|
async function createStory(data: Story & { genres: number[] }): Promise<{ success: string }> {
|
||||||
"use server"
|
"use server"
|
||||||
const genresArray = data.genres.map(e => { return { id: e } })
|
const genresArray = data.genres.map(e => { return { id: e } })
|
||||||
const res = await prisma.story.create({
|
const res = await prisma.story.create({
|
||||||
|
@ -26,12 +27,14 @@ export default async function Page() {
|
||||||
revalidatePath("/story")
|
revalidatePath("/story")
|
||||||
redirect("/story")
|
redirect("/story")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CreateContainer>
|
<CreateContainer>
|
||||||
<CreateContainerHeader>New story</CreateContainerHeader>
|
<CreateContainerHeader>New story</CreateContainerHeader>
|
||||||
<CreateContainerContent>
|
<CreateContainerContent>
|
||||||
<CreateContainerDescription>Make an entry for a new work of fiction i.e. a thing you intend to submit for publication.</CreateContainerDescription>
|
<CreateContainerDescription>Make an entry for a new work of fiction i.e. a thing you intend to submit for publication.</CreateContainerDescription>
|
||||||
<StoryForm genres={genres} createStory={createStory} className="mt-6" />
|
<StoryForm genres={genres} dbAction={createStory} className="mt-6" />
|
||||||
</CreateContainerContent>
|
</CreateContainerContent>
|
||||||
</CreateContainer>
|
</CreateContainer>
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
import { DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
import { DialogHeader, DialogTitle, DialogFooter, DialogDescription } from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ComponentProps, useState } from "react";
|
import { ComponentProps, useState } from "react";
|
||||||
import { Genre } from "@prisma/client";
|
import { Genre, Story } from "@prisma/client";
|
||||||
import StoryForm from "app/ui/forms/story";
|
import StoryForm from "app/ui/forms/story";
|
||||||
import { StoryWithGenres } from "./page";
|
import { StoryWithGenres } from "./page";
|
||||||
|
|
||||||
|
|
||||||
export default function EditStoryDialog({ genres, closeDialog, defaults, dbAction }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: StoryWithGenres, dbAction: (data: StoryWithGenres) => Promise<{ success: string }> }) {
|
export default function EditStoryDialog({ genres, closeDialog, defaults, dbAction }: ComponentProps<"div"> & { genres: Genre[], closeDialog: () => void, defaults: StoryWithGenres, dbAction: (data: Story & { genres: number[] }) => Promise<{ success: string }> }) {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -10,19 +10,12 @@ export default async function Page() {
|
||||||
const stories = await getStories()
|
const stories = await getStories()
|
||||||
const pubs = await getPubs()
|
const pubs = await getPubs()
|
||||||
const responses = await getResponses()
|
const responses = await getResponses()
|
||||||
async function createSub(data) {
|
|
||||||
"use server"
|
|
||||||
const res = await prisma.sub.create({ data })
|
|
||||||
console.log(res)
|
|
||||||
revalidatePath("/submission")
|
|
||||||
redirect("/submission")
|
|
||||||
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<CreateContainer>
|
<CreateContainer>
|
||||||
<CreateContainerHeader>New submission</CreateContainerHeader>
|
<CreateContainerHeader>New submission</CreateContainerHeader>
|
||||||
<CreateContainerContent>
|
<CreateContainerContent>
|
||||||
<SubmissionForm stories={stories} pubs={pubs} responses={responses} createSub={createSub} />
|
<SubmissionForm stories={stories} pubs={pubs} responses={responses} />
|
||||||
</CreateContainerContent>
|
</CreateContainerContent>
|
||||||
</CreateContainer>
|
</CreateContainer>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { CheckboxReactHookFormMultiple } from "app/ui/forms/Checkboxdemo";
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return (
|
|
||||||
<CheckboxReactHookFormMultiple />
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -56,13 +56,13 @@ export default function EditSubmissionForm({ stories, pubs, responses, defaults,
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
const pubsSelectItems = pubs.map(e => (
|
const pubsSelectItems = pubs.map(e => (
|
||||||
<SelectItem value={e.id}>
|
<SelectItem value={e.id} key={e.title}>
|
||||||
{e.title}
|
{e.title}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
|
|
||||||
const reponsesSelectItems = responses.map(e => (
|
const reponsesSelectItems = responses.map(e => (
|
||||||
<SelectItem value={e.id}>
|
<SelectItem value={e.id} key={e.title}>
|
||||||
{e.response}
|
{e.response}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
|
@ -106,7 +106,7 @@ export default function EditSubmissionForm({ stories, pubs, responses, defaults,
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="text-sm md:text-base">Story</FormLabel>
|
<FormLabel className="text-sm md:text-base">Story</FormLabel>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value.toString()}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select something">
|
<SelectValue placeholder="Select something">
|
||||||
|
@ -129,7 +129,7 @@ export default function EditSubmissionForm({ stories, pubs, responses, defaults,
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="text-sm md:text-base">Publication</FormLabel>
|
<FormLabel className="text-sm md:text-base">Publication</FormLabel>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value.toString()}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select something">
|
<SelectValue placeholder="Select something">
|
||||||
|
@ -173,9 +173,8 @@ export default function EditSubmissionForm({ stories, pubs, responses, defaults,
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
<Calendar
|
{/* @ts-ignore */}
|
||||||
mode="single"
|
<Calendar mode="single" selected={field.value}
|
||||||
selected={field.value}
|
|
||||||
onSelect={(e) => { field.onChange(e); setIsSubCalendarOpen(false); }}
|
onSelect={(e) => { field.onChange(e); setIsSubCalendarOpen(false); }}
|
||||||
disabled={(date) =>
|
disabled={(date) =>
|
||||||
date > new Date() || date < new Date("1900-01-01")
|
date > new Date() || date < new Date("1900-01-01")
|
||||||
|
@ -218,10 +217,8 @@ export default function EditSubmissionForm({ stories, pubs, responses, defaults,
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
<Calendar
|
{/* @ts-ignore */}
|
||||||
mode="single"
|
<Calendar selected={field.value} onSelect={(e) => { field.onChange(e); setIsRespCalendarOpen(false); }}
|
||||||
selected={field.value}
|
|
||||||
onSelect={(e) => { field.onChange(e); setIsRespCalendarOpen(false); }}
|
|
||||||
disabled={(date) =>
|
disabled={(date) =>
|
||||||
date > new Date() || date < new Date("1900-01-01")
|
date > new Date() || date < new Date("1900-01-01")
|
||||||
}
|
}
|
||||||
|
@ -244,7 +241,7 @@ export default function EditSubmissionForm({ stories, pubs, responses, defaults,
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="text-sm md:text-base">Response</FormLabel>
|
<FormLabel className="text-sm md:text-base">Response</FormLabel>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value.toString()}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue>
|
<SelectValue>
|
||||||
|
|
|
@ -40,7 +40,14 @@ export default function PubForm({ genres, dbAction, className, closeDialog, defa
|
||||||
|
|
||||||
async function onSubmit(values: z.infer<typeof pubSchema>) {
|
async function onSubmit(values: z.infer<typeof pubSchema>) {
|
||||||
try {
|
try {
|
||||||
const res = await dbAction(values)
|
const res = await dbAction({
|
||||||
|
pub: {
|
||||||
|
id: values?.id,
|
||||||
|
title: values.title,
|
||||||
|
link: values.link,
|
||||||
|
query_after_days: values.query_after_days
|
||||||
|
}, genres: values.genres
|
||||||
|
})
|
||||||
if (!res?.success) throw new Error("something went wrong")
|
if (!res?.success) throw new Error("something went wrong")
|
||||||
toast({ title: "Success!", description: res.success })
|
toast({ title: "Success!", description: res.success })
|
||||||
router.refresh()
|
router.refresh()
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const formSchema = z.object({
|
||||||
genres: z.array(z.number())
|
genres: z.array(z.number())
|
||||||
})
|
})
|
||||||
|
|
||||||
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?: StoryWithGenres }) {
|
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?: StoryWithGenres }) {
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
@ -40,13 +40,19 @@ export default function StoryForm({ genres, dbAction, className, closeDialog, de
|
||||||
genres: defaults?.genres.map(e => e.id) ?? []
|
genres: defaults?.genres.map(e => e.id) ?? []
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
console.log("DEFAULTS: " + defaults)
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||||
try {
|
try {
|
||||||
const res = await dbAction(values)
|
const res = await dbAction({
|
||||||
|
story: {
|
||||||
|
id: values?.id,
|
||||||
|
title: values.title,
|
||||||
|
word_count: values.word_count,
|
||||||
|
},
|
||||||
|
genres: values.genres
|
||||||
|
})
|
||||||
//server actions return undefined if middleware authentication fails
|
//server actions return undefined if middleware authentication fails
|
||||||
if (!res?.success) throw new Error("something went wrong")
|
if (!res?.success) throw new Error("something went wrong")
|
||||||
toast({ title: "Success!", description: res.success })
|
toast({ title: "Success!", description: res.success })
|
||||||
|
|
|
@ -36,11 +36,12 @@ import { createSub } from "app/lib/create"
|
||||||
import { subSchema } from "./schemas"
|
import { subSchema } from "./schemas"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { Ban } from "lucide-react"
|
import { Ban } from "lucide-react"
|
||||||
|
import { Story } from "@prisma/client"
|
||||||
|
|
||||||
export type SubForm = z.infer<typeof subSchema>
|
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>>({
|
const form = useForm<z.infer<typeof subSchema>>({
|
||||||
resolver: zodResolver(subSchema),
|
resolver: zodResolver(subSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
@ -50,19 +51,19 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
||||||
})
|
})
|
||||||
const [isSubCalendarOpen, setIsSubCalendarOpen] = useState(false);
|
const [isSubCalendarOpen, setIsSubCalendarOpen] = useState(false);
|
||||||
const [isRespCalendarOpen, setIsRespCalendarOpen] = useState(false);
|
const [isRespCalendarOpen, setIsRespCalendarOpen] = useState(false);
|
||||||
const storiesSelectItems = stories.map(e => (
|
const storiesSelectItems = stories.map((e: Story) => (
|
||||||
<SelectItem value={e.id.toString()} key={e.title}>
|
<SelectItem value={e.id?.toString()} key={e.title}>
|
||||||
{e.title}
|
{e.title}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
const pubsSelectItems = pubs.map(e => (
|
const pubsSelectItems = pubs.map(e => (
|
||||||
<SelectItem value={e.id}>
|
<SelectItem value={e.id} key={e.title}>
|
||||||
{e.title}
|
{e.title}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
|
|
||||||
const reponsesSelectItems = responses.map(e => (
|
const reponsesSelectItems = responses.map(e => (
|
||||||
<SelectItem value={e.id}>
|
<SelectItem value={e.id} key={e.title}>
|
||||||
{e.response}
|
{e.response}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
|
@ -71,6 +72,7 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
||||||
|
|
||||||
async function onSubmit(values: z.infer<typeof subSchema>) {
|
async function onSubmit(values: z.infer<typeof subSchema>) {
|
||||||
try {
|
try {
|
||||||
|
//@ts-ignore
|
||||||
const res = await createSub(values)
|
const res = await createSub(values)
|
||||||
if (!res) throw new Error("something went wrong")
|
if (!res) throw new Error("something went wrong")
|
||||||
toast({ title: "Successfully created new submission!" })
|
toast({ title: "Successfully created new submission!" })
|
||||||
|
@ -102,7 +104,7 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Story</FormLabel>
|
<FormLabel>Story</FormLabel>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value?.toString()}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select something">
|
<SelectValue placeholder="Select something">
|
||||||
|
@ -125,7 +127,7 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="text-sm md:text-base">Publication</FormLabel>
|
<FormLabel className="text-sm md:text-base">Publication</FormLabel>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value?.toString()}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Select something">
|
<SelectValue placeholder="Select something">
|
||||||
|
@ -169,9 +171,8 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
<Calendar
|
{/* @ts-ignore */}
|
||||||
mode="single"
|
<Calendar mode="single" selected={field.value}
|
||||||
selected={field.value}
|
|
||||||
onSelect={(e) => { field.onChange(e); setIsSubCalendarOpen(false); }}
|
onSelect={(e) => { field.onChange(e); setIsSubCalendarOpen(false); }}
|
||||||
disabled={(date) =>
|
disabled={(date) =>
|
||||||
date > new Date() || date < new Date("1900-01-01")
|
date > new Date() || date < new Date("1900-01-01")
|
||||||
|
@ -214,9 +215,8 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
<Calendar
|
{/* @ts-ignore */}
|
||||||
mode="single"
|
<Calendar mode="single" selected={field.value}
|
||||||
selected={field.value}
|
|
||||||
onSelect={(e) => { field.onChange(e); setIsRespCalendarOpen(false); }}
|
onSelect={(e) => { field.onChange(e); setIsRespCalendarOpen(false); }}
|
||||||
disabled={(date) =>
|
disabled={(date) =>
|
||||||
date > new Date() || date < new Date("1900-01-01")
|
date > new Date() || date < new Date("1900-01-01")
|
||||||
|
@ -240,7 +240,7 @@ export default function SubmissionForm({ stories, pubs, responses, defaults, clo
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="text-sm md:text-base">Response</FormLabel>
|
<FormLabel className="text-sm md:text-base">Response</FormLabel>
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
<Select onValueChange={field.onChange} defaultValue={field.value?.toString()}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue>
|
<SelectValue>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ComponentProps, useState } from "react"
|
||||||
import { Row, Table, TableState } from "@tanstack/react-table"
|
import { Row, Table, TableState } from "@tanstack/react-table"
|
||||||
|
|
||||||
export default function FormContextMenu({ table, row, openEditDialog, openDeleteDialog }: ComponentProps<"div"> & { table: Table<any>, row: Row<any>, openEditDialog: (row: Row<any>) => void, openDeleteDialog: (row: Row<any>) => void }) {
|
export default function FormContextMenu({ table, row, openEditDialog, openDeleteDialog }: ComponentProps<"div"> & { table: Table<any>, row: Row<any>, openEditDialog: (row: Row<any>) => void, openDeleteDialog: (row: Row<any>) => void }) {
|
||||||
|
//@ts-ignore
|
||||||
const pathname = table.options.meta.pathname
|
const pathname = table.options.meta.pathname
|
||||||
const selectedRows = table.getSelectedRowModel().flatRows
|
const selectedRows = table.getSelectedRowModel().flatRows
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ export function DataTable<TData, TValue>({
|
||||||
const [columnVisibility, setColumnVisibility] =
|
const [columnVisibility, setColumnVisibility] =
|
||||||
useState<VisibilityState>({})
|
useState<VisibilityState>({})
|
||||||
//
|
//
|
||||||
const pathname: Pathname = usePathname()
|
const pathname: string = usePathname()
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
|
@ -141,17 +141,13 @@ export function DataTable<TData, TValue>({
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
|
{/*@ts-ignore*/}
|
||||||
<DropdownMenuRadioGroup value={filterBy} onValueChange={setFilterBy} >
|
<DropdownMenuRadioGroup value={filterBy} onValueChange={setFilterBy} >
|
||||||
{table
|
{table
|
||||||
.getAllColumns()
|
.getAllColumns()
|
||||||
.filter((column) => column.getCanFilter())
|
.filter((column) => column.getCanFilter())
|
||||||
.map((column) => {
|
//@ts-ignore
|
||||||
return (
|
.map((column) => { return (<DropdownMenuRadioItem value={column} className="capitalize" key={column.id}> {column.id} </DropdownMenuRadioItem>) })}
|
||||||
<DropdownMenuRadioItem value={column} className="capitalize" key={column.id}>
|
|
||||||
{column.id}
|
|
||||||
</DropdownMenuRadioItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -234,6 +230,7 @@ export function DataTable<TData, TValue>({
|
||||||
{`Delete ${Object.keys(table.getState().rowSelection).length} ${pluralize(pathname.slice(1))}?`}
|
{`Delete ${Object.keys(table.getState().rowSelection).length} ${pluralize(pathname.slice(1))}?`}
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
|
{/* @ts-ignore */}
|
||||||
{`Deleting ${pluralize(tableNameToItemName(table.options.meta.tableName))} cannot be undone!`}
|
{`Deleting ${pluralize(tableNameToItemName(table.options.meta.tableName))} cannot be undone!`}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
@ -241,7 +238,9 @@ export function DataTable<TData, TValue>({
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const selectedRows = table.getState().rowSelection
|
const selectedRows = table.getState().rowSelection
|
||||||
const rowIds = Object.keys(selectedRows)
|
const rowIds = Object.keys(selectedRows)
|
||||||
|
//@ts-ignore
|
||||||
const recordIds = rowIds.map(id => Number(table.getRow(id).original.id))
|
const recordIds = rowIds.map(id => Number(table.getRow(id).original.id))
|
||||||
|
//@ts-ignore
|
||||||
const res = await deleteRecords(recordIds, pathname)
|
const res = await deleteRecords(recordIds, pathname)
|
||||||
if (!res) toast({ title: "Oh dear...", description: "Failed to delete." })
|
if (!res) toast({ title: "Oh dear...", description: "Failed to delete." })
|
||||||
if (res) toast({ title: "Successfully deleted records of id:", description: JSON.stringify(recordIds) })
|
if (res) toast({ title: "Successfully deleted records of id:", description: JSON.stringify(recordIds) })
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { FormField, FormItem, FormLabel, FormMessage, FormControl, Form } from "
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { ComponentProps, useState } from "react"
|
import { BaseSyntheticEvent, ComponentProps, EventHandler, useState } from "react"
|
||||||
import { EventType, useForm, UseFormReturn } from "react-hook-form"
|
import { EventType, useForm, UseFormReturn } from "react-hook-form"
|
||||||
import { CellContext } from "@tanstack/react-table"
|
import { CellContext } from "@tanstack/react-table"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
@ -16,16 +16,18 @@ import { useRouter } from "next/navigation"
|
||||||
export default function GenrePickerInputCell(props: CellContext<any, any>) {
|
export default function GenrePickerInputCell(props: CellContext<any, any>) {
|
||||||
|
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
const table = props.table.options.meta.tableName
|
const table = props.table.options.meta.tableName
|
||||||
|
//@ts-ignore
|
||||||
const pathname = props.table.options.meta.pathname
|
const pathname = props.table.options.meta.pathname
|
||||||
const id = props.row.original.id
|
const id = props.row.original.id
|
||||||
const column = props.column.id
|
const column = props.column.id
|
||||||
const value = props.cell.getValue()
|
const value = props.cell.getValue()
|
||||||
|
//@ts-ignore
|
||||||
const genres = props.table.options.meta.genres
|
const genres = props.table.options.meta.genres
|
||||||
const [isActive, setIsActive] = useState(false)
|
const [isActive, setIsActive] = useState(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
async function onSubmit({ genres }: { genres: number[] }, event: Event) {
|
async function onSubmit({ genres }: { genres: number[] }, event: BaseSyntheticEvent) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
try {
|
try {
|
||||||
const genresArray = genres.map((e) => { return { id: e } })
|
const genresArray = genres.map((e) => { return { id: e } })
|
||||||
|
|
|
@ -12,11 +12,14 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from "@/component
|
||||||
export default function NumberInputCell({ cellContext, className }: { cellContext: CellContext<any, any>, className: string }) {
|
export default function NumberInputCell({ cellContext, className }: { cellContext: CellContext<any, any>, className: string }) {
|
||||||
const [isActive, setIsActive] = useState(false)
|
const [isActive, setIsActive] = useState(false)
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
const table = cellContext.table.options.meta.tableName
|
const table = cellContext.table.options.meta.tableName
|
||||||
const id = cellContext.row.original.id
|
const id = cellContext.row.original.id
|
||||||
const column = cellContext.column.id
|
const column = cellContext.column.id
|
||||||
|
//@ts-ignore
|
||||||
const pathname = cellContext.table.options.meta.pathname
|
const pathname = cellContext.table.options.meta.pathname
|
||||||
const value = cellContext.cell.getValue()
|
const value = cellContext.cell.getValue()
|
||||||
|
//@ts-ignore
|
||||||
const formSchema = cellContext.column.columnDef.meta.formSchema.pick({ [column]: true })
|
const formSchema = cellContext.column.columnDef.meta.formSchema.pick({ [column]: true })
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
@ -77,13 +80,8 @@ export default function NumberInputCell({ cellContext, className }: { cellContex
|
||||||
>
|
>
|
||||||
<FormControl
|
<FormControl
|
||||||
>
|
>
|
||||||
<Input
|
{/* @ts-ignore */}
|
||||||
className="md:w-24"
|
<Input className="md:w-24" type="number" autoFocus={true} step={cellContext.column.columnDef.meta?.step} {...field} />
|
||||||
type="number"
|
|
||||||
autoFocus={true}
|
|
||||||
step={cellContext.column.columnDef.meta?.step}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
|
@ -13,15 +13,14 @@ import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
export function TextInputCell({ cellContext, className }: { className: string, cellContext: CellContext<any, any> }) {
|
export function TextInputCell({ cellContext, className }: { className: string, cellContext: CellContext<any, any> }) {
|
||||||
const [isActive, setIsActive] = useState(false)
|
const [isActive, setIsActive] = useState(false)
|
||||||
if (cellContext === undefined) {
|
//@ts-ignore
|
||||||
console.error("CELL CONTEXT UNDEFINED!")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const table = cellContext.table.options.meta.tableName
|
const table = cellContext.table.options.meta.tableName
|
||||||
const id = cellContext.row.original.id
|
const id = cellContext.row.original.id
|
||||||
const column = cellContext.column.id
|
const column = cellContext.column.id
|
||||||
|
//@ts-ignore
|
||||||
const pathname = cellContext.table.options.meta.pathname
|
const pathname = cellContext.table.options.meta.pathname
|
||||||
const value = cellContext.cell.getValue()
|
const value = cellContext.cell.getValue()
|
||||||
|
//@ts-ignore
|
||||||
const formSchema = cellContext.column.columnDef.meta.formSchema.pick({ [column]: true })
|
const formSchema = cellContext.column.columnDef.meta.formSchema.pick({ [column]: true })
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
|
Loading…
Reference in New Issue