Skip to content

Commit

Permalink
feat(dash/admin): ✨ FAQ List and Editor
Browse files Browse the repository at this point in the history
  • Loading branch information
Nudelsuppe42 committed Nov 11, 2024
1 parent 3752421 commit 66e2db9
Show file tree
Hide file tree
Showing 16 changed files with 761 additions and 248 deletions.
3 changes: 1 addition & 2 deletions apps/api/src/util/package.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export const LIB_VERSION = '1.1.0';
export const LIB_LICENSE = undefined;
export const LIB_VERSION = "1.1.0";export const LIB_LICENSE = undefined;
15 changes: 14 additions & 1 deletion apps/dashboard/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@ import type { NextConfig } from 'next';
import path from 'path';

const nextConfig: NextConfig = {
experimental: {},
experimental: {
turbo: {},
},
transpilePackages: [
'@tiptap/react',
'@tiptap/starter-kit',
'@tiptap/extension-underline',
'@tiptap/extension-link',
'@tiptap/extension-superscript',
'@tiptap/extension-subscript',
'@tiptap/extension-highlight',
'@tiptap/extension-text-align',
],
output: 'standalone',
poweredByHeader: false,
reactStrictMode: true,
outputFileTracingRoot: path.join(__dirname, '../../'),
};

Expand Down
6 changes: 4 additions & 2 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
"@mantine/notifications": "^7.13.2",
"@mantine/nprogress": "^7.13.2",
"@mantine/spotlight": "^7.13.2",
"@tabler/icons-react": "^3.9.0",
"@mantine/tiptap": "^7.13.2",
"@repo/db": "*",
"@tabler/icons-react": "^3.9.0",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
"mantine-contextmenu": "^7.11.0",
Expand All @@ -33,6 +34,7 @@
"moment-timezone": "^0.5.45",
"next": "15.0.2",
"next-auth": "^4.24.7",
"next-transpile-modules": "^10.0.1",
"react": "19.0.0-rc-02c0e824-20241028",
"react-dom": "19.0.0-rc-02c0e824-20241028",
"recharts": "2",
Expand Down Expand Up @@ -60,4 +62,4 @@
"@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
}
}
}
44 changes: 44 additions & 0 deletions apps/dashboard/src/app/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use server';

import prisma from '@/util/db';
import { revalidatePath } from 'next/cache';

export const addFaqQuestion = async (data: { question: string; answer: string }) => {
const { question, answer } = data;

const faq = await prisma.fAQQuestion.create({
data: {
question,
answer,
},
});

revalidatePath('/am/faq');
return faq;
};

export const editFaqQuestion = async (data: { question: string; answer: string; id: string }) => {
const faq = await prisma.fAQQuestion.update({
where: {
id: data.id,
},
data: {
question: data.question,
answer: data.answer,
},
});

revalidatePath('/am/faq');
return faq;
};

export const deleteFaqQuestion = async (id: any) => {
const faq = await prisma.fAQQuestion.delete({
where: {
id,
},
});

revalidatePath('/am/faq');
return faq;
};
9 changes: 4 additions & 5 deletions apps/dashboard/src/app/am/faq/datatable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import { ActionIcon, Code, Group } from '@mantine/core';
import { IconEdit, IconExternalLink } from '@tabler/icons-react';

import { DataTable } from 'mantine-datatable';
import { FAQQuestion } from '@repo/db';
import { DataTable } from 'mantine-datatable';
import Link from 'next/link';
import { EditFaqQuestionButton } from './interactivity';

export default function FAQDatatabe({ faq }: { faq: FAQQuestion[] }) {
return (
Expand All @@ -22,11 +23,9 @@ export default function FAQDatatabe({ faq }: { faq: FAQQuestion[] }) {
accessor: '',
title: '',
textAlign: 'right',
render: (question) => (
render: (question: FAQQuestion) => (
<Group gap={4} justify="right" wrap="nowrap">
<ActionIcon size="sm" variant="subtle" color="yellow" aria-label="Edit Question">
<IconEdit size={16} />
</ActionIcon>
<EditFaqQuestionButton id={question.id} question={question.question} answer={question.answer} links={question.links}/>
<ActionIcon
size="sm"
variant="subtle"
Expand Down
116 changes: 116 additions & 0 deletions apps/dashboard/src/app/am/faq/interactivity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
'use client';

import { addFaqQuestion, deleteFaqQuestion, editFaqQuestion } from '@/app/actions';
import { ActionIcon, Button, Group, InputWrapper, TextInput } from '@mantine/core';
import { IconDeviceFloppy, IconEdit, IconPlus, IconTrash } from '@tabler/icons-react';

import RichTextEditor from '@/components/input/RTE';
import { useFormActions } from '@/hooks/useFormAction';
import { useForm } from '@mantine/form';
import { modals } from '@mantine/modals';
import { FAQQuestion } from '@repo/db';

export function AddFaqQuestionButton() {
return (
<Button
color="green"
leftSection={<IconPlus size={14} />}
onClick={() =>
modals.open({
id: 'add-faq-question',
title: 'Add new FAQ Question',
centered: true,
size: 'lg',
children: <EditFaqQuestionModal isAdd id="" question="" answer="" links={[]} />,
})
}
>
Add New
</Button>
);
}

export function EditFaqQuestionButton(props: FAQQuestion) {
return (
<ActionIcon
size="sm"
variant="subtle"
color="yellow"
aria-label="Edit Question"
onClick={() =>
modals.open({
id: 'edit-faq-question',
title: 'Edit FAQ Question',
centered: true,
size: 'lg',
children: <EditFaqQuestionModal {...props} />,
})
}
>
<IconEdit size={16} />
</ActionIcon>
);
}

function EditFaqQuestionModal(
props: {
isAdd?: boolean;
} & FAQQuestion,
) {
const form = useForm({
initialValues: { id: props.id, question: props.question, answer: props.answer, links: props.links },
});
const [[addFaqQuestionAction,editFaqQuestionAction, deleteFaqQuestionAction], isPending] = useFormActions([
addFaqQuestion,
editFaqQuestion,
deleteFaqQuestion,
]);

const handleSubmit = (values: FAQQuestion) => {
if (props.isAdd) {
addFaqQuestionAction(values);
} else {
editFaqQuestionAction(values);
}
modals.closeAll();
};

return (
<form onSubmit={form.onSubmit(handleSubmit)}>
<TextInput
mt="md"
placeholder="How can i ... ?"
label="Question"
description="Question to display on top"
required
{...form.getInputProps('question')}
/>
<InputWrapper label="Answer" mt="md" required description="Answer to the question">
<RichTextEditor style={{ marginTop: '5px' }} {...form.getInputProps('answer')} />
</InputWrapper>
{!props.isAdd ? (
<Group mt="md">
<Button type="submit" leftSection={<IconDeviceFloppy size={14} />} loading={isPending}>
Save Changes
</Button>
<Button
variant="outline"
onClick={() => {
deleteFaqQuestionAction(props.id);
modals.closeAll();
}}
leftSection={<IconTrash size={14} />}
color="red"
loading={isPending}
>
Delete Question
</Button>
</Group>
) : (
<Button type="submit" mt="md" leftSection={<IconPlus size={14} />} loading={isPending}>
Add Question
</Button>
)}
</form>
);
}
21 changes: 8 additions & 13 deletions apps/dashboard/src/app/am/faq/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import {
Box,
Button,
Group,
Title
} from '@mantine/core';
import { IconExternalLink, IconPlus } from '@tabler/icons-react';
import { Box, Button, Group, Title } from '@mantine/core';

import prisma from '@/util/db';
import Link from 'next/link';
import { AddFaqQuestionButton } from './interactivity';
import FAQDatatabe from './datatable';
import { IconExternalLink } from '@tabler/icons-react';
import Link from 'next/link';
import prisma from '@/util/db';

export default async function Page() {
const faq = await prisma.fAQQuestion.findMany();
Expand All @@ -18,17 +14,16 @@ export default async function Page() {
<Group justify="space-between" w="100%" mt="xl" mb="md">
<Title order={1}>FAQ Questions</Title>
<Group gap="xs">
<Button color="green" leftSection={<IconPlus size={14} />}>
Add New
</Button>
<AddFaqQuestionButton />
<Button
variant="light"
color="cyan"
component={Link}
href={`https://buildtheearth.net/faq`}
target="_blank"
rightSection={<IconExternalLink size={14} />}
>Open Page
>
Open Page
</Button>
</Group>
</Group>
Expand Down
16 changes: 10 additions & 6 deletions apps/dashboard/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@ import '@mantine/dates/styles.layer.css';
import '@mantine/notifications/styles.layer.css';
import '@mantine/nprogress/styles.layer.css';
import '@mantine/spotlight/styles.layer.css';
import '@mantine/tiptap/styles.layer.css';
import 'mantine-datatable/styles.layer.css';

import { ColorSchemeScript, MantineProvider } from '@mantine/core';

import AppLayout from '@/components/layout';
import AuthProvider from '@/components/AuthProvider';
import { Inter } from 'next/font/google';
import { ModalsProvider } from '@mantine/modals';
import { Notifications } from '@mantine/notifications';
import SWRSetup from '@/components/core/SWRSetup';
import AppLayout from '@/components/layout';
import { getSession } from '@/util/auth';
import { Notifications } from '@mantine/notifications';
import { Inter } from 'next/font/google';
import localFont from 'next/font/local';

export const interFont = Inter({
Expand All @@ -36,14 +38,16 @@ export default async function RootLayout({ children }: { children: React.ReactNo
return (
<html lang="en" className={`${interFont.variable} ${minecraftFont.variable}`} suppressHydrationWarning>
<head>
<ColorSchemeScript/>
<ColorSchemeScript />
</head>
<body>
<MantineProvider>
<AuthProvider session={session}>
<SWRSetup>
<Notifications limit={3} />
<AppLayout>{children}</AppLayout>
<ModalsProvider>
<Notifications limit={3} />
<AppLayout>{children}</AppLayout>
</ModalsProvider>
</SWRSetup>
</AuthProvider>
</MantineProvider>
Expand Down
18 changes: 18 additions & 0 deletions apps/dashboard/src/components/input/FormButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use client"

import { Button, ButtonProps, PolymorphicComponentProps } from '@mantine/core';
import { useEffect, useState } from 'react';

import { useFormStatus } from 'react-dom';

export function FormButton(props: PolymorphicComponentProps<"button", ButtonProps>) {
const {pending} = useFormStatus();
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
console.log(new Date().toISOString(), "pending: "+pending)
setIsLoading(pending);
}, [pending]);

return <Button loading={isLoading} {...props} />;
}
Loading

0 comments on commit 66e2db9

Please sign in to comment.