same mde editor in all admin forms
This commit is contained in:
@@ -15,7 +15,7 @@ export default function FormScaffold<T extends FieldValues,>(params: {
|
||||
}) {
|
||||
const { form, onSubmit, title, id, className, children } = params
|
||||
return (
|
||||
<Card.Card className={className ? className : "w-5/6 lg:w-1/2"}>
|
||||
<Card.Card className={className ? className : "w-full"}>
|
||||
<Card.CardHeader>
|
||||
<Card.CardTitle>
|
||||
<DependentText bool={id ? true : false} true={`Update ${title}`} false={`Create ${title}`} />
|
||||
|
||||
98
src/app/admin/_components/MdxComponentReference.tsx
Normal file
98
src/app/admin/_components/MdxComponentReference.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ChevronsUpDown } from "lucide-react";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "~/components/ui/accordion";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
|
||||
|
||||
const examples = [
|
||||
{
|
||||
name: "Lead",
|
||||
description: "Intro paragraph with larger muted text.",
|
||||
code: `<Lead>
|
||||
Short opening summary.
|
||||
</Lead>`,
|
||||
},
|
||||
{
|
||||
name: "Callout",
|
||||
description: "Highlighted note, tip, or warning block.",
|
||||
code: `<Callout title="Heads up" variant="note">
|
||||
Important context for readers.
|
||||
</Callout>
|
||||
|
||||
<Callout title="Tip" variant="tip">
|
||||
A practical recommendation.
|
||||
</Callout>
|
||||
|
||||
<Callout title="Careful" variant="warning">
|
||||
A caveat or tradeoff.
|
||||
</Callout>`,
|
||||
},
|
||||
{
|
||||
name: "ButtonLink",
|
||||
description: "Button-styled internal or external link.",
|
||||
code: `<ButtonLink href="/projects">
|
||||
View projects
|
||||
</ButtonLink>
|
||||
|
||||
<ButtonLink href="https://example.com" variant="outline">
|
||||
External resource
|
||||
</ButtonLink>`,
|
||||
},
|
||||
{
|
||||
name: "Figure",
|
||||
description: "Image with optional caption.",
|
||||
code: `<Figure
|
||||
src="https://example.com/image.jpg"
|
||||
alt="Describe the image"
|
||||
caption="Optional caption"
|
||||
/>`,
|
||||
},
|
||||
{
|
||||
name: "PullQuote",
|
||||
description: "Large emphasized quote or takeaway.",
|
||||
code: `<PullQuote>
|
||||
A highlighted quote or strong takeaway.
|
||||
</PullQuote>`,
|
||||
},
|
||||
{
|
||||
name: "TagList",
|
||||
description: "Inline list of tag badges.",
|
||||
code: `<TagList tags={["nextjs", "mdx", "uploadthing"]} />`,
|
||||
},
|
||||
{
|
||||
name: "Badge",
|
||||
description: "Small inline label.",
|
||||
code: `<Badge variant="outline">Next.js</Badge>`,
|
||||
},
|
||||
];
|
||||
|
||||
export default function MdxComponentReference() {
|
||||
return (
|
||||
<Collapsible className="rounded-lg border">
|
||||
<CollapsibleTrigger className="flex w-full items-center justify-between gap-2 p-4 text-left">
|
||||
<h2 className="text-base font-semibold">MDX Components</h2>
|
||||
<ChevronsUpDown className="size-4 shrink-0 text-muted-foreground" />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="px-4 pb-4">
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Components available inside MDX content. Type <code className="rounded bg-muted px-1">[[</code> for internal links or <code className="rounded bg-muted px-1"><</code> for component snippets.
|
||||
</p>
|
||||
<Accordion type="single" collapsible className="mt-3">
|
||||
{examples.map((example) => (
|
||||
<AccordionItem key={example.name} value={example.name}>
|
||||
<AccordionTrigger>
|
||||
<span>
|
||||
<span className="block">{example.name}</span>
|
||||
<span className="text-muted-foreground block text-xs font-normal">{example.description}</span>
|
||||
</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<pre className="bg-muted overflow-x-auto rounded-md p-3 text-xs">
|
||||
<code>{example.code}</code>
|
||||
</pre>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { serialize } from 'next-mdx-remote/serialize'
|
||||
import type { MDXRemoteSerializeResult } from 'next-mdx-remote'
|
||||
import { mdxComponents } from '~/components/mdx-components'
|
||||
|
||||
export default function BlogMdxEditorPreview(params: { source: string }) {
|
||||
export default function MdxEditorPreview(params: { source: string }) {
|
||||
const [compiled, setCompiled] = useState<MDXRemoteSerializeResult | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
143
src/app/admin/_components/useMdxEditorFieldProps.tsx
Normal file
143
src/app/admin/_components/useMdxEditorFieldProps.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
'use client'
|
||||
|
||||
import { trpc } from '~/app/_trpc/Client'
|
||||
import type { RouterOutputs } from '~/server/routers/_app'
|
||||
import {
|
||||
AUTOCOMPLETE_CURSOR_MARKER,
|
||||
linkSuggestionsToAutocomplete,
|
||||
type AutocompleteTriggerConfig,
|
||||
type InternalLinkSuggestion,
|
||||
type MdeAutocompleteSuggestion,
|
||||
} from '~/app/_components/Form/Fields/InternalLinkTextarea'
|
||||
import MdxEditorPreview from './MdxEditorPreview'
|
||||
|
||||
function internalLinkSuggestions(params: {
|
||||
posts?: RouterOutputs['blog']['list'],
|
||||
projects?: RouterOutputs['projectv2']['listWithStack'],
|
||||
}): InternalLinkSuggestion[] {
|
||||
const postLinks = params.posts?.map((post) => ({
|
||||
label: post.title,
|
||||
href: `/blog/${post.slug}`,
|
||||
group: 'Blog',
|
||||
})) ?? []
|
||||
|
||||
const projectLinks = params.projects?.map((project) => ({
|
||||
label: project.title,
|
||||
href: `/projects#${project.id}`,
|
||||
group: 'Project',
|
||||
})) ?? []
|
||||
|
||||
return [...postLinks, ...projectLinks]
|
||||
}
|
||||
|
||||
const mdxAutocompleteSuggestions: MdeAutocompleteSuggestion[] = [
|
||||
{
|
||||
label: 'Lead',
|
||||
value: `<Lead>\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Lead>`,
|
||||
detail: 'Intro paragraph with larger muted text.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Callout note',
|
||||
value: `<Callout title="Heads up" variant="note">\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Callout>`,
|
||||
detail: 'Highlighted note block.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Callout tip',
|
||||
value: `<Callout title="Tip" variant="tip">\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Callout>`,
|
||||
detail: 'Highlighted tip block.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Callout warning',
|
||||
value: `<Callout title="Careful" variant="warning">\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Callout>`,
|
||||
detail: 'Highlighted warning block.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'ButtonLink',
|
||||
value: `<ButtonLink href="${AUTOCOMPLETE_CURSOR_MARKER}">\nView projects\n</ButtonLink>`,
|
||||
detail: 'Button-styled internal or external link.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Figure',
|
||||
value: `<Figure\n src="${AUTOCOMPLETE_CURSOR_MARKER}"\n alt="Describe the image"\n caption="Optional caption"\n/>`,
|
||||
detail: 'Image with optional caption.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'PullQuote',
|
||||
value: `<PullQuote>\n${AUTOCOMPLETE_CURSOR_MARKER}\n</PullQuote>`,
|
||||
detail: 'Large emphasized quote.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'TagList',
|
||||
value: `<TagList tags={[${AUTOCOMPLETE_CURSOR_MARKER}]} />`,
|
||||
detail: 'Inline list of tag badges.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Badge',
|
||||
value: `<Badge variant="outline">${AUTOCOMPLETE_CURSOR_MARKER}</Badge>`,
|
||||
detail: 'Small inline label.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Image',
|
||||
value: ``,
|
||||
detail: 'Markdown image',
|
||||
group: 'Markdown',
|
||||
trigger: '!',
|
||||
},
|
||||
]
|
||||
|
||||
const mdxTriggerConfigs: AutocompleteTriggerConfig[] = [
|
||||
{
|
||||
trigger: '[[',
|
||||
label: 'Internal links',
|
||||
isQueryValid: (query) => !query.includes(']'),
|
||||
},
|
||||
{
|
||||
trigger: '<',
|
||||
label: 'MDX components',
|
||||
isQueryValid: (query) => !/[\s>]/.test(query),
|
||||
},
|
||||
{
|
||||
trigger: '!',
|
||||
label: 'Markdown',
|
||||
isQueryValid: (query) => !/[\s\)]/.test(query),
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Shared props for an MDX-aware `MdeFormField`: internal-link + component
|
||||
* autocomplete, trigger configs, and a live MDX preview. Used by every admin
|
||||
* form that edits MDX content (blog, project, cv entry).
|
||||
*/
|
||||
export function useMdxEditorFieldProps() {
|
||||
const posts = trpc.blog.list.useQuery(undefined, { refetchInterval: 5000 })
|
||||
const projects = trpc.projectv2.listWithStack.useQuery()
|
||||
|
||||
const autocompleteSuggestions = [
|
||||
...linkSuggestionsToAutocomplete(internalLinkSuggestions({ posts: posts.data, projects: projects.data })),
|
||||
...mdxAutocompleteSuggestions,
|
||||
]
|
||||
|
||||
return {
|
||||
autocompleteSuggestions,
|
||||
triggerConfigs: mdxTriggerConfigs,
|
||||
renderPreview: (source: string) => <MdxEditorPreview source={source} />,
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,8 @@ import { TextInputFormField, MdeFormField } from '~/app/_components/Form/Fields'
|
||||
import { usePathname, useRouter } from 'next/navigation'
|
||||
import { useTheme } from 'next-themes'
|
||||
import type { RouterOutputs } from '~/server/routers/_app'
|
||||
import MdxComponentReference from './MdxComponentReference'
|
||||
import BlogMdxEditorPreview from './BlogMdxEditorPreview'
|
||||
import {
|
||||
AUTOCOMPLETE_CURSOR_MARKER,
|
||||
linkSuggestionsToAutocomplete,
|
||||
type AutocompleteTriggerConfig,
|
||||
type InternalLinkSuggestion,
|
||||
type MdeAutocompleteSuggestion,
|
||||
} from '~/app/_components/Form/Fields/InternalLinkTextarea'
|
||||
import MdxComponentReference from '~/app/admin/_components/MdxComponentReference'
|
||||
import { useMdxEditorFieldProps } from '~/app/admin/_components/useMdxEditorFieldProps'
|
||||
|
||||
type BlogPost = RouterOutputs['blog']['bySlug']
|
||||
|
||||
@@ -35,116 +28,6 @@ function parseTags(value: string | undefined): string[] {
|
||||
return value?.split(',').map((tag) => tag.trim()).filter(Boolean) ?? []
|
||||
}
|
||||
|
||||
function internalLinkSuggestions(params: {
|
||||
posts?: RouterOutputs['blog']['list'],
|
||||
projects?: RouterOutputs['projectv2']['listWithStack'],
|
||||
}): InternalLinkSuggestion[] {
|
||||
const postLinks = params.posts?.map((post) => ({
|
||||
label: post.title,
|
||||
href: `/blog/${post.slug}`,
|
||||
group: 'Blog',
|
||||
})) ?? []
|
||||
|
||||
const projectLinks = params.projects?.map((project) => ({
|
||||
label: project.title,
|
||||
href: `/projects#${project.id}`,
|
||||
group: 'Project',
|
||||
})) ?? []
|
||||
|
||||
return [...postLinks, ...projectLinks]
|
||||
}
|
||||
|
||||
const blogAutocompleteSuggestions: MdeAutocompleteSuggestion[] = [
|
||||
{
|
||||
label: 'Lead',
|
||||
value: `<Lead>\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Lead>`,
|
||||
detail: 'Intro paragraph with larger muted text.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Callout note',
|
||||
value: `<Callout title="Heads up" variant="note">\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Callout>`,
|
||||
detail: 'Highlighted note block.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Callout tip',
|
||||
value: `<Callout title="Tip" variant="tip">\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Callout>`,
|
||||
detail: 'Highlighted tip block.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Callout warning',
|
||||
value: `<Callout title="Careful" variant="warning">\n${AUTOCOMPLETE_CURSOR_MARKER}\n</Callout>`,
|
||||
detail: 'Highlighted warning block.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'ButtonLink',
|
||||
value: `<ButtonLink href="${AUTOCOMPLETE_CURSOR_MARKER}">\nView projects\n</ButtonLink>`,
|
||||
detail: 'Button-styled internal or external link.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Figure',
|
||||
value: `<Figure\n src="${AUTOCOMPLETE_CURSOR_MARKER}"\n alt="Describe the image"\n caption="Optional caption"\n/>`,
|
||||
detail: 'Image with optional caption.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'PullQuote',
|
||||
value: `<PullQuote>\n${AUTOCOMPLETE_CURSOR_MARKER}\n</PullQuote>`,
|
||||
detail: 'Large emphasized quote.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'TagList',
|
||||
value: `<TagList tags={[${AUTOCOMPLETE_CURSOR_MARKER}]} />`,
|
||||
detail: 'Inline list of tag badges.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Badge',
|
||||
value: `<Badge variant="outline">${AUTOCOMPLETE_CURSOR_MARKER}</Badge>`,
|
||||
detail: 'Small inline label.',
|
||||
group: 'Component',
|
||||
trigger: '<',
|
||||
},
|
||||
{
|
||||
label: 'Image',
|
||||
value: ``,
|
||||
detail: 'Markdown image',
|
||||
group: 'Markdown',
|
||||
trigger: '!',
|
||||
},
|
||||
]
|
||||
|
||||
const blogTriggerConfigs: AutocompleteTriggerConfig[] = [
|
||||
{
|
||||
trigger: '[[',
|
||||
label: 'Internal links',
|
||||
isQueryValid: (query) => !query.includes(']'),
|
||||
},
|
||||
{
|
||||
trigger: '<',
|
||||
label: 'MDX components',
|
||||
isQueryValid: (query) => !/[\s>]/.test(query),
|
||||
},
|
||||
{
|
||||
trigger: '!',
|
||||
label: 'Markdown',
|
||||
isQueryValid: (query) => !/[\s\)]/.test(query),
|
||||
},
|
||||
]
|
||||
|
||||
export default function CreateUpdateBlogForm(params: { className?: string, entity?: BlogPost }) {
|
||||
const [slug, setSlug] = useState<string | undefined>(params.entity?.slug)
|
||||
const [originalSlug, setOriginalSlug] = useState<string | undefined>(params.entity?.slug)
|
||||
@@ -162,12 +45,7 @@ export default function CreateUpdateBlogForm(params: { className?: string, entit
|
||||
})
|
||||
const path = usePathname()
|
||||
const router = useRouter()
|
||||
const posts = trpc.blog.list.useQuery(undefined, { refetchInterval: 5000 })
|
||||
const projects = trpc.projectv2.listWithStack.useQuery()
|
||||
const autocompleteSuggestions = [
|
||||
...linkSuggestionsToAutocomplete(internalLinkSuggestions({ posts: posts.data, projects: projects.data })),
|
||||
...blogAutocompleteSuggestions,
|
||||
]
|
||||
const mdxEditorProps = useMdxEditorFieldProps()
|
||||
|
||||
const createMutation = trpc.blog.insert.useMutation({
|
||||
onSuccess: (data) => {
|
||||
@@ -226,9 +104,7 @@ export default function CreateUpdateBlogForm(params: { className?: string, entit
|
||||
name='content'
|
||||
label='Content'
|
||||
dataColorMode={(theme as 'dark' | 'light') ?? 'dark'}
|
||||
autocompleteSuggestions={autocompleteSuggestions}
|
||||
triggerConfigs={blogTriggerConfigs}
|
||||
renderPreview={(source) => <BlogMdxEditorPreview source={source} />}
|
||||
{...mdxEditorProps}
|
||||
/>
|
||||
</FormScaffold>
|
||||
</div>
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "~/components/ui/accordion";
|
||||
|
||||
const examples = [
|
||||
{
|
||||
name: "Lead",
|
||||
description: "Intro paragraph with larger muted text.",
|
||||
code: `<Lead>
|
||||
Short opening summary for the post.
|
||||
</Lead>`,
|
||||
},
|
||||
{
|
||||
name: "Callout",
|
||||
description: "Highlighted note, tip, or warning block.",
|
||||
code: `<Callout title="Heads up" variant="note">
|
||||
Important context for readers.
|
||||
</Callout>
|
||||
|
||||
<Callout title="Tip" variant="tip">
|
||||
A practical recommendation.
|
||||
</Callout>
|
||||
|
||||
<Callout title="Careful" variant="warning">
|
||||
A caveat or tradeoff.
|
||||
</Callout>`,
|
||||
},
|
||||
{
|
||||
name: "ButtonLink",
|
||||
description: "Button-styled internal or external link.",
|
||||
code: `<ButtonLink href="/projects">
|
||||
View projects
|
||||
</ButtonLink>
|
||||
|
||||
<ButtonLink href="https://example.com" variant="outline">
|
||||
External resource
|
||||
</ButtonLink>`,
|
||||
},
|
||||
{
|
||||
name: "Figure",
|
||||
description: "Image with optional caption.",
|
||||
code: `<Figure
|
||||
src="https://example.com/image.jpg"
|
||||
alt="Describe the image"
|
||||
caption="Optional caption"
|
||||
/>`,
|
||||
},
|
||||
{
|
||||
name: "PullQuote",
|
||||
description: "Large emphasized quote or takeaway.",
|
||||
code: `<PullQuote>
|
||||
A highlighted quote or strong takeaway.
|
||||
</PullQuote>`,
|
||||
},
|
||||
{
|
||||
name: "TagList",
|
||||
description: "Inline list of tag badges inside the post body.",
|
||||
code: `<TagList tags={["nextjs", "mdx", "uploadthing"]} />`,
|
||||
},
|
||||
{
|
||||
name: "Badge",
|
||||
description: "Small inline label.",
|
||||
code: `<Badge variant="outline">Next.js</Badge>`,
|
||||
},
|
||||
];
|
||||
|
||||
export default function MdxComponentReference() {
|
||||
return (
|
||||
<section className="rounded-lg border p-4">
|
||||
<h2 className="text-base font-semibold">MDX Components</h2>
|
||||
<p className="text-muted-foreground mt-1 text-sm">
|
||||
Components available inside blog post content. Type <code className="rounded bg-muted px-1">[[</code> for internal links or <code className="rounded bg-muted px-1"><</code> for component snippets.
|
||||
</p>
|
||||
<Accordion type="single" collapsible className="mt-3">
|
||||
{examples.map((example) => (
|
||||
<AccordionItem key={example.name} value={example.name}>
|
||||
<AccordionTrigger>
|
||||
<span>
|
||||
<span className="block">{example.name}</span>
|
||||
<span className="text-muted-foreground block text-xs font-normal">{example.description}</span>
|
||||
</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<pre className="bg-muted overflow-x-auto rounded-md p-3 text-xs">
|
||||
<code>{example.code}</code>
|
||||
</pre>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import { useState } from 'react';
|
||||
import { SelectFormField, TextInputFormField, MdeFormField, CalenderFormField, BooleanFormField } from '~/app/_components/Form/Fields'
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import {FormMutationContextProvider, type FormCreateMutationInterface} from '~/app/_components/Form/Components/MutationProvider';
|
||||
import MdxComponentReference from '~/app/admin/_components/MdxComponentReference';
|
||||
import { useMdxEditorFieldProps } from '~/app/admin/_components/useMdxEditorFieldProps';
|
||||
export default function CreateUpdateCvEntryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['entry']['select']>, isUpdate?: boolean }) {
|
||||
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
|
||||
const { theme } = useTheme()
|
||||
@@ -34,6 +36,7 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
|
||||
})
|
||||
let path = usePathname()
|
||||
let router = useRouter()
|
||||
const mdxEditorProps = useMdxEditorFieldProps()
|
||||
const createMutation = trpc.entry.insert.useMutation({ onSuccess: makeOnSuccess('create', form, setId) })
|
||||
const updateMutation = trpc.entry.update.useMutation({ onSuccess: makeOnSuccess('update', form) })
|
||||
const deleteMutation = trpc.entry.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) })
|
||||
@@ -51,6 +54,8 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
|
||||
updateMutation:updateMutation,
|
||||
deleteMutation:deleteMutation
|
||||
}}>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<MdxComponentReference />
|
||||
<FormScaffold
|
||||
form={form}
|
||||
onSubmit={onSubmit}
|
||||
@@ -70,11 +75,12 @@ export default function CreateUpdateCvEntryForm(params: { className?: string, en
|
||||
}
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} name='title' label='Title' />
|
||||
<MdeFormField control={form.control} name='description' label='Description' dataColorMode={(theme as "dark" | "light") ? (theme as "dark" | "light") : "dark"} />
|
||||
<MdeFormField control={form.control} name='description' label='Description' dataColorMode={(theme as "dark" | "light") ? (theme as "dark" | "light") : "dark"} {...mdxEditorProps} />
|
||||
<CalenderFormField control={form.control} name='fromTime' label='From Date' />
|
||||
<CalenderFormField control={form.control} name='toTime' label='To Date' />
|
||||
<BooleanFormField control={form.control} name='hideDates' label='Hide Dates' />
|
||||
</FormScaffold>
|
||||
</div>
|
||||
</FormMutationContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { makeUseRelationShipWithNameIndex } from '~/lib/hooks';
|
||||
import { FormMutationContextProvider } from '~/app/_components/Form/Components/MutationProvider';
|
||||
import MdxComponentReference from '~/app/admin/_components/MdxComponentReference';
|
||||
import { useMdxEditorFieldProps } from '~/app/admin/_components/useMdxEditorFieldProps';
|
||||
export default function CreateUpdateProjectForm(params: { className?: string, entity?: IterableElement<RouterOutputs['project']['select']> }) {
|
||||
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
|
||||
const { theme } = useTheme()
|
||||
@@ -35,6 +37,7 @@ export default function CreateUpdateProjectForm(params: { className?: string, en
|
||||
})
|
||||
let path = usePathname()
|
||||
let router = useRouter()
|
||||
const mdxEditorProps = useMdxEditorFieldProps()
|
||||
const createMutation = trpc.project.insert.useMutation({ onSuccess: makeOnSuccess('create', form, setId) })
|
||||
const updateMutation = trpc.project.update.useMutation({ onSuccess: makeOnSuccess('update', form) })
|
||||
const deleteMutation = trpc.project.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) })
|
||||
@@ -50,6 +53,8 @@ export default function CreateUpdateProjectForm(params: { className?: string, en
|
||||
updateMutation: updateMutation,
|
||||
deleteMutation: deleteMutation
|
||||
}}>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<MdxComponentReference />
|
||||
<FormScaffold
|
||||
form={form}
|
||||
onSubmit={onSubmit}
|
||||
@@ -69,7 +74,7 @@ export default function CreateUpdateProjectForm(params: { className?: string, en
|
||||
}
|
||||
</SelectFormField>
|
||||
<TextInputFormField control={form.control} name='title' label='Title' />
|
||||
<MdeFormField control={form.control} name='description' label='Description' dataColorMode={(theme as "dark" | "light") ?? "dark"} />
|
||||
<MdeFormField control={form.control} name='description' label='Description' dataColorMode={(theme as "dark" | "light") ?? "dark"} {...mdxEditorProps} />
|
||||
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
|
||||
<SelectItem value="open"> open </SelectItem>
|
||||
<SelectItem value="closed"> closed </SelectItem>
|
||||
@@ -82,6 +87,7 @@ export default function CreateUpdateProjectForm(params: { className?: string, en
|
||||
<TextInputFormField control={form.control} label='Release Link' name='releaseLink' />
|
||||
<IntInputFormField control={form.control} label='Order Position' name='orderPos'/>
|
||||
</FormScaffold>
|
||||
</div>
|
||||
</FormMutationContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user