From fa27318e4cc3f3a98b07cdaf32525771ed596b84 Mon Sep 17 00:00:00 2001 From: crssstha Date: Wed, 7 Jan 2026 14:02:00 +0545 Subject: [PATCH 01/25] feat(blog): Add blog listing and edit add form --- app/components/FormSection/index.tsx | 34 + app/components/FormSection/styles.module.css | 15 + app/components/Navigation/styles.module.css | 5 + app/components/Page/styles.module.css | 2 +- app/components/RichTextEditor/index.tsx | 54 + .../RichTextEditor/styles.module.css | 59 + app/index.css | 21 +- app/index.tsx | 1 + app/root/config/routes.tsx | 23 + app/root/index.tsx | 4 +- app/views/Blog/BlogForm/index.tsx | 396 ++++++ app/views/Blog/BlogForm/styles.module.css | 37 + app/views/Blog/BlogList/index.tsx | 195 +++ app/views/Blog/BlogList/styles.module.css | 14 + app/views/RootLayout/styles.module.css | 3 +- package.json | 6 + pnpm-lock.yaml | 1264 ++++++++++++++++- 17 files changed, 2119 insertions(+), 14 deletions(-) create mode 100644 app/components/FormSection/index.tsx create mode 100644 app/components/FormSection/styles.module.css create mode 100644 app/components/RichTextEditor/index.tsx create mode 100644 app/components/RichTextEditor/styles.module.css create mode 100644 app/views/Blog/BlogForm/index.tsx create mode 100644 app/views/Blog/BlogForm/styles.module.css create mode 100644 app/views/Blog/BlogList/index.tsx create mode 100644 app/views/Blog/BlogList/styles.module.css diff --git a/app/components/FormSection/index.tsx b/app/components/FormSection/index.tsx new file mode 100644 index 0000000..86fd755 --- /dev/null +++ b/app/components/FormSection/index.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { Heading } from '@ifrc-go/ui'; +import { + _cs, + isDefined, +} from '@togglecorp/fujs'; + +import styles from './styles.module.css'; + +interface FormSectionProps { + label?: string; + description?: string; + children?: React.ReactNode; + headingLevel?: 1 | 2 | 3 | 4 | 5 | 6; + className?: string; + inputClassName?: string +} +function FormSection({ + label, description, headingLevel = 6, children, className, inputClassName, +}: FormSectionProps) { + return ( +
+ {isDefined(label) && ( +
+ {label} + {description &&

{description}

} +
+ )} +
{children}
+
+ ); +} + +export default FormSection; diff --git a/app/components/FormSection/styles.module.css b/app/components/FormSection/styles.module.css new file mode 100644 index 0000000..63f1bc5 --- /dev/null +++ b/app/components/FormSection/styles.module.css @@ -0,0 +1,15 @@ +.section { + display: flex; + flex-direction: row; + padding: var(--go-ui-spacing-lg) var(--go-ui-spacing-xl); + gap: var(--go-ui-spacing-xl); + .label { + p { + font-size: var(--go-ui-font-size-xs); + margin-top: var(--go-ui-spacing-2xs); + } + } +} +.section > * { + flex: 1; +} diff --git a/app/components/Navigation/styles.module.css b/app/components/Navigation/styles.module.css index 3a52e39..f18754c 100644 --- a/app/components/Navigation/styles.module.css +++ b/app/components/Navigation/styles.module.css @@ -1,8 +1,12 @@ .nav { border-right: 2px solid var(--go-ui-color-gray-20); height: 100%; + min-width: 320px; + overflow-x: hidden; + padding-right: 2px; .nav { border: none; + padding-right: 0; } .navHeaderContainer { border-bottom: 2px solid var(--go-ui-color-gray-20); @@ -18,6 +22,7 @@ display: flex; flex-direction: column; .routeLink { + border-right: 2px solid var(--go-ui-color-gray-20); border-bottom: 2px solid var(--go-ui-color-gray-20); &:hover { color: var(--go-ui-color-primary-red); diff --git a/app/components/Page/styles.module.css b/app/components/Page/styles.module.css index fc76975..294cffc 100644 --- a/app/components/Page/styles.module.css +++ b/app/components/Page/styles.module.css @@ -2,8 +2,8 @@ display: flex; flex-direction: row; flex-grow: 1; + height: 100%; overflow: hidden; - .leftPane { display: flex; flex-direction: column; diff --git a/app/components/RichTextEditor/index.tsx b/app/components/RichTextEditor/index.tsx new file mode 100644 index 0000000..eee713e --- /dev/null +++ b/app/components/RichTextEditor/index.tsx @@ -0,0 +1,54 @@ +import { Heading } from '@ifrc-go/ui'; +import { Editor } from '@tinymce/tinymce-react'; +import DOMPurify from 'dompurify'; +import parse from 'html-react-parser'; + +import styles from './styles.module.css'; + +interface Props { + value?: string; + onChange?: (value: string) => void; +} + +const API_KEY = import.meta.env.APP_TINYMCE_API_KEY; + +export default function RichTextEditor({ value, onChange }: Props) { + const editorOptions = { + height: 600, + menubar: false, + statusbar: false, + paste_data_images: false, + plugins: 'advlist autolink code help link lists fullscreen', + toolbar: + 'bold italic subscript superscript link | blocks ' + + 'alignleft aligncenter alignright alignjustify | ' + + 'bullist numlist outdent indent | fullscreen | help', + contextmenu: 'link', + block_formats: + 'Paragraph=p; Heading 1=h1; Heading 2=h2; Heading 3=h3; Heading 4=h4;', + branding: false, + advlist_bullet_styles: 'default', + advlist_number_styles: 'default', + content_style: 'body { font-size: 14px}', + }; + + return ( +
+ { + onChange?.(content); + }} + init={editorOptions} + + /> +
+ Preview +
+ {parse(DOMPurify.sanitize(value ?? ''))} +
+
+
+ ); +} diff --git a/app/components/RichTextEditor/styles.module.css b/app/components/RichTextEditor/styles.module.css new file mode 100644 index 0000000..135bb81 --- /dev/null +++ b/app/components/RichTextEditor/styles.module.css @@ -0,0 +1,59 @@ +.editorContainer { + display: flex; + flex-direction: row; + background-color: var(--color-background); + font-size: var(--font-size-lg); + gap: var(--go-ui-spacing-md); + overflow: hidden; + padding: var(--go-ui-spacing-md) var(--go-ui-spacing-lg); + .preview { + display: flex; + flex-direction: column; + line-height: 1.5; + border: 1px solid var(--go-ui-color-gray-30); + border-radius: var(--go-ui-spacing-sm); + .previewHeader { + padding: var(--go-ui-spacing-sm); + box-shadow: 0 2px 2px -2px rgba(34, 47, 62, 0.1), + 0 8px 8px -4px rgba(34, 47, 62, 0.07); + } + } +} +.editorContainer > * { + flex: 1; + overflow: hidden; +} +.previewContent { + max-height: 33rem; + overflow: auto; + padding: 0 var(--go-ui-spacing-lg); +} + +.previewContent p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: var(--go-ui-spacing-md) 0; +} + +.previewContent ul, +.previewContent ol { + padding-left: var(--go-ui-spacing-xl); +} +.previewContent li { + margin: 4px 0; +} + +.previewContent a { + text-decoration: underline; + color: var(--go-ui-color-dark-blue-20); + &:hover { + color: var(--go-ui-color-dark-blue-30); + } +} +.tox-statusbar__branding { + display: none !important; +} diff --git a/app/index.css b/app/index.css index a0e2996..b9d60fb 100644 --- a/app/index.css +++ b/app/index.css @@ -33,4 +33,23 @@ p { a { text-decoration: none; color: inherit; -} \ No newline at end of file +} + +::-webkit-scrollbar { + width: 4px; + height: 4px; +} + +::-webkit-scrollbar-track { + background: transparent; + border-radius: 10px; +} + +::-webkit-scrollbar-thumb { + background-color: var(--go-ui-color-gray-20); + border-radius: 10px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: var(--go-ui-color-gray-30); +} diff --git a/app/index.tsx b/app/index.tsx index 652bb3a..232635d 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,3 +1,4 @@ +import '@ifrc-go/ui/index.css'; import './index.css'; import { StrictMode } from 'react'; diff --git a/app/root/config/routes.tsx b/app/root/config/routes.tsx index f2ce82c..2896e02 100644 --- a/app/root/config/routes.tsx +++ b/app/root/config/routes.tsx @@ -13,6 +13,26 @@ const home: RouteConfig = { visibility: 'is-authenticated', }; +const blog: RouteConfig = { + index: true, + path: 'cm/blog', + load: () => import('#views/Blog/BlogList'), + visibility: 'is-authenticated', +}; +const editBlog: RouteConfig = { + index: true, + path: 'cm/blog/:id/edit', + load: () => import('#views/Blog/BlogForm'), + visibility: 'is-authenticated', +}; + +const addBlog: RouteConfig = { + index: true, + path: 'cm/blog/add', + load: () => import('#views/Blog/BlogForm'), + visibility: 'is-authenticated', +}; + const login: RouteConfig = { path: '/login/', load: () => import('#views/Login'), @@ -22,6 +42,9 @@ const login: RouteConfig = { const routes = { login, home, + blog, + addBlog, + editBlog, }; export type RouteKeys = keyof typeof routes; diff --git a/app/root/index.tsx b/app/root/index.tsx index 7497582..a8ee686 100644 --- a/app/root/index.tsx +++ b/app/root/index.tsx @@ -6,8 +6,8 @@ import { Cookies } from 'react-cookie'; import { Outlet } from 'react-router'; import { AlertContainer } from '@ifrc-go/ui'; import { AlertContext } from '@ifrc-go/ui/contexts'; +import { cacheExchange } from '@urql/exchange-graphcache'; import { - cacheExchange, Client, fetchExchange, Provider as UrqlProvider, @@ -25,7 +25,7 @@ const cookies = new Cookies(); const gqlClient = new Client({ url: GRAPHQL_ENDPOINT, exchanges: [ - cacheExchange, + cacheExchange({}), fetchExchange, ], fetchOptions: () => ({ diff --git a/app/views/Blog/BlogForm/index.tsx b/app/views/Blog/BlogForm/index.tsx new file mode 100644 index 0000000..946b04b --- /dev/null +++ b/app/views/Blog/BlogForm/index.tsx @@ -0,0 +1,396 @@ +import { + useCallback, + useEffect, +} from 'react'; +import { + useNavigate, + useParams, +} from 'react-router'; +import { + Button, + Checkbox, + Container, + DateInput, + Heading, + RawFileInput, + SelectInput, + TextInput, +} from '@ifrc-go/ui'; +import { + createSubmitHandler, + getErrorObject, + ObjectSchema, + PartialForm, + requiredStringCondition, + useForm, +} from '@togglecorp/toggle-form'; +import { gql } from 'urql'; + +import FormSection from '#components/FormSection'; +import Page from '#components/Page'; +import RichTextEditor from '#components/RichTextEditor'; +import { + BlogCreateInput, + StatusEnum, + useBlogDetailQueryQuery, + useCreateBlogMutation, + useDepartmentAndDirectiveQuery, + useUpdateBlogMutation, +} from '#generated/types/graphql'; + +import styles from './styles.module.css'; + +type PartialFormType = PartialForm & +{ createdBy: string, modifiedBy: string, slug: string | null }; + +type FormSchema = ObjectSchema; +type FormSchemaFields = ReturnType; + +const EditBlogSchema: FormSchema = { + fields: (): FormSchemaFields => ({ + title: { + required: true, + requiredValidation: requiredStringCondition, + }, + author: { + required: true, + requiredValidation: requiredStringCondition, + }, + content: { + required: false, + requiredValidation: requiredStringCondition, + }, + coverImage: { + required: false, + }, + department: { + required: false, + requiredValidation: requiredStringCondition, + }, + directive: { + required: false, + requiredValidation: requiredStringCondition, + }, + featured: { + required: true, + }, + publishedDate: { + required: true, + requiredValidation: requiredStringCondition, + }, + status: { + required: false, + requiredValidation: requiredStringCondition, + }, + createdBy: { + required: false, + requiredValidation: requiredStringCondition, + }, + modifiedBy: { + required: false, + requiredValidation: requiredStringCondition, + }, + slug: { + required: true, + requiredValidation: requiredStringCondition, + }, + + }), +}; + +const defaultEditFormValue: PartialFormType = { + createdBy: '', + modifiedBy: '', + slug: '', +}; + +const BLOG_DETAIL_QUERY = gql` + query BlogDetailQuery($id: ID!) { + blog(id: $id) { + author + content + coverImage { + name + size + url + } + departmentId + directiveId + featured + id + modifiedBy { + firstName + lastName + } + publishedDate + slug + status + title + createdBy { + firstName + lastName + } + } + } +`; + +const DEPARTMENT_AND_DIRECTIVE = gql` + query DepartmentAndDirective { + departments { + id + description + title + strategicDirectiveId + } + strategicDirectives { + id + title + } + } +`; + +const CREATE_BLOG_MUTATION = gql` + mutation CreateBlog($data: BlogCreateInput!) { + createBlog(data: $data) { + ... on BlogTypeMutationResponseType { + errors + ok + } + } + } +`; + +export const UPDATE_BLOG_MUTATION = gql` + mutation UpdateBlog($pk: ID!, $data: BlogUpdateInput!) { + updateBlog(pk: $pk, data: $data) { + ... on BlogTypeMutationResponseType { + errors + ok + } + } + } +`; + +function BlogForm() { + const { id } = useParams(); + const navigate = useNavigate(); + const [{ data }] = useBlogDetailQueryQuery({ + variables: { id: id || '' }, pause: !id, + }); + const [{ data: departmentAndDirective }] = useDepartmentAndDirectiveQuery(); + const [{ fetching: createPending }, createBlogMutate] = useCreateBlogMutation(); + const [{ fetching: updatePending }, updateBlogMutate] = useUpdateBlogMutation(); + const { + setFieldValue, + error: formError, + value, + validate, + setError, + } = useForm(EditBlogSchema, { value: defaultEditFormValue }); + + const error = getErrorObject(formError); + + const handleFormSubmit = useCallback(() => { + const handler = createSubmitHandler( + validate, + setError, + async (val) => { + const mutateData = { + author: val.author ?? '', + content: val.content ?? '', + department: val.department, + directive: val.directive, + featured: val.featured, + status: val.status, + publishedDate: val.publishedDate, + title: val.title ?? '', + ...(val.coverImage instanceof File && { coverImage: val.coverImage }), + }; + if (id) { + const res = await updateBlogMutate({ + pk: id, + data: mutateData, + }); + + if (res.data?.updateBlog?.ok) { + navigate('/cm/blog'); + } else if (res.data?.updateBlog?.errors) { + setError(res.data.updateBlog.errors); + } + } else { + const res = await createBlogMutate({ + data: mutateData, + }); + + if (!res.data?.createBlog.ok) { + navigate('/cm/blog'); + } else if (res.data?.createBlog?.errors) { + setError(res.data.createBlog.errors); + } + } + }, + ); + handler(); + }, [setError, validate, id, updateBlogMutate, createBlogMutate, navigate]); + + const departmentOptions = departmentAndDirective?.departments.map( + (dept) => ({ + id: dept.id, + name: dept.title, + }), + ) ?? []; + + const directiveOptions = departmentAndDirective?.strategicDirectives.map( + (directive) => ({ + id: directive.id, + name: directive.title, + }), + ) ?? []; + + const statusOptions = Object.values(StatusEnum).map((status) => ({ + value: status, + label: status, + })); + + useEffect(() => { + if (data?.blog) { + const { blog } = data; + setFieldValue(blog.author, 'author'); + setFieldValue(blog.title, 'title'); + setFieldValue(blog.content, 'content'); + setFieldValue(blog.coverImage, 'coverImage'); + setFieldValue(blog.status, 'status'); + setFieldValue(blog.publishedDate, 'publishedDate'); + setFieldValue(blog.departmentId, 'department'); + setFieldValue(blog.directiveId, 'directive'); + setFieldValue(blog.featured, 'featured'); + setFieldValue(`${blog.modifiedBy.firstName} ${blog.modifiedBy.lastName}`, 'modifiedBy'); + setFieldValue(blog.slug, 'slug'); + setFieldValue(`${blog.createdBy.firstName} ${blog.createdBy.lastName}`, 'createdBy'); + } + }, [data, setFieldValue]); + return ( + + + + {(value.createdBy && value.modifiedBy) && ( + +
+ Created by: + {value.createdBy} +
+
+ Modified by: + {value.createdBy} +
+
+ )} + + + + + + + + + + + setFieldValue(files, 'coverImage')} + variant="secondary" + > + Upload + + {value.coverImage?.name &&

{value.coverImage.name}

} +
+ + + + + o.label} + labelSelector={(o) => o.value} + onChange={setFieldValue} + placeholder="Select Status" + error={error?.status} + /> + + + setFieldValue(val || null, 'slug')} + error={error?.slug} + /> + + + option.id} + labelSelector={(option) => option.name} + onChange={setFieldValue} + placeholder="Select Directive" + error={error?.directive} + /> + + + option.id} + labelSelector={(option) => option.name} + onChange={setFieldValue} + placeholder="Select Department" + error={error?.department} + /> + + +
+ setFieldValue(val, 'content')} + /> +
+
+ +
+
+
+ ); +} + +export default BlogForm; diff --git a/app/views/Blog/BlogForm/styles.module.css b/app/views/Blog/BlogForm/styles.module.css new file mode 100644 index 0000000..b80caf4 --- /dev/null +++ b/app/views/Blog/BlogForm/styles.module.css @@ -0,0 +1,37 @@ +.container { + width: 100%; +} +.containerChild { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + gap: var(--go-ui-spacing-sm); + background: var(--go-ui-color-gray-10); + overflow: auto; +} +.containerChild > * { + background: var(--go-ui-color-white); +} + +.inputClassName { + display: flex; + flex-direction: row; + width: 100%; + gap: var(--go-ui-spacing-xl); +} +.fileUpload { + display: flex; + align-items: center; + gap: var(--go-ui-spacing-sm); +} +.submitBtn { + display: flex; + align-items: center; + justify-content: center; + padding: var(--go-ui-spacing-lg) var(--go-ui-spacing-xl); +} + +.inputClassName > * { + flex: 1; +} diff --git a/app/views/Blog/BlogList/index.tsx b/app/views/Blog/BlogList/index.tsx new file mode 100644 index 0000000..d156961 --- /dev/null +++ b/app/views/Blog/BlogList/index.tsx @@ -0,0 +1,195 @@ +import { useMemo } from 'react'; +import { useNavigate } from 'react-router'; +import { + DeleteBinLineIcon, + EditTwoLineIcon, +} from '@ifrc-go/icons'; +import { + Button, + Container, + Table, +} from '@ifrc-go/ui'; +import { + createBooleanColumn, + createDateColumn, + createElementColumn, + createNumberColumn, + createStringColumn, +} from '@ifrc-go/ui/utils'; +import { gql } from 'urql'; + +import { + BlogQueryQuery, + useBlogQueryQuery, +} from '#generated/types/graphql'; + +import styles from './styles.module.css'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const BLOG_QUERY = gql` + query BlogQuery { + blogs { + title + status + slug + publishedDate + modifiedAt + id + featured + directiveId + author + content + coverImage { + name + size + url + } + createdAt + createdBy { + firstName + id + lastName + } + modifiedBy { + firstName + id + lastName + } + } + } +`; +type EventListItem = NonNullable[number]; + +function Actions({ id }: { id: string }) { + const navigate = useNavigate(); + + return ( +
+ + +
+ ); +} + +function BlogList() { + const [{ fetching, data }] = useBlogQueryQuery(); + const navigate = useNavigate(); + const tableData = data?.blogs?.map((blog, i) => ({ ...blog, sn: i + 1 })); + + const columns = useMemo( + () => ([ + // Serial Number + createNumberColumn( + 'sn', + 'S.N.', + (item) => item.sn, + { columnWidth: 60 }, + ), + // Title + createStringColumn( + 'title', + 'Title', + (blog) => blog.title, + { + sortable: true, + // columnStretch: true, + }, + ), + + // Published Date + createDateColumn( + 'publishedDate', + 'Published Date', + (blog) => blog.publishedDate, + { + sortable: true, + // columnWidth: 140, + }, + ), + + // Author + createStringColumn( + 'author', + 'Author', + (blog) => blog.author, + { + sortable: true, + // columnWidth: 160, + }, + ), + + // Featured + createBooleanColumn( + 'featured', + 'Featured', + (blog) => blog.featured, + { + sortable: true, + // columnWidth: 100, + }, + ), + + // Status + createStringColumn( + 'status', + 'Status', + (blog) => blog.status, + { + sortable: true, + // columnWidth: 120, + }, + ), + + createElementColumn( + 'actions', + 'Actions', + Actions, + (_, datum) => ({ + id: datum.id, + }), + { + columnWidth: 150, + }, + ), + + ]), + [], + ); + + return ( + navigate('add')}> + Add blogs + + )} + > + item.id} + className={styles.table} + columns={columns} + data={tableData} + filtered={false} + pending={fetching} + headerRowClassName={styles.headerRow} + + /> + + ); +} + +export default BlogList; diff --git a/app/views/Blog/BlogList/styles.module.css b/app/views/Blog/BlogList/styles.module.css new file mode 100644 index 0000000..9b1456c --- /dev/null +++ b/app/views/Blog/BlogList/styles.module.css @@ -0,0 +1,14 @@ +.blog { + flex: 1; + padding: var(--go-ui-spacing-lg) var(--go-ui-spacing-xl); + .header { + display: flex; + justify-content: space-between; + } + + .headerRow { + th { + background: var(--go-ui-color-gray-20); + } + } +} diff --git a/app/views/RootLayout/styles.module.css b/app/views/RootLayout/styles.module.css index e68adce..abe04d7 100644 --- a/app/views/RootLayout/styles.module.css +++ b/app/views/RootLayout/styles.module.css @@ -2,5 +2,6 @@ display: flex; position: relative; flex-direction: column; - min-height: 100vh; + height: 100vh; + overflow: hidden; } diff --git a/package.json b/package.json index 8633e87..c5271d0 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,16 @@ "knip": "knip" }, "dependencies": { + "@ifrc-go/icons": "^2.0.1", "@ifrc-go/ui": "^1.3.0", + "@tinymce/tinymce-react": "^6.3.0", "@togglecorp/fujs": "^2.2.0", "@togglecorp/toggle-form": "^2.0.4", + "@uiw/react-md-editor": "^4.0.11", + "@urql/exchange-graphcache": "^8.1.0", + "dompurify": "^3.3.1", "graphql": "^16.12.0", + "html-react-parser": "^5.2.11", "react": "^19.2.0", "react-cookie": "^8.0.1", "react-dom": "^19.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 041ce44..c004117 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,18 +8,36 @@ overrides: vite: npm:rolldown-vite@7.2.5 dependencies: + '@ifrc-go/icons': + specifier: ^2.0.1 + version: 2.0.1(react@19.2.3) '@ifrc-go/ui': specifier: ^1.3.0 - version: 1.3.0(@ifrc-go/icons@1.3.4)(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3) + version: 1.3.0(@ifrc-go/icons@2.0.1)(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3) + '@tinymce/tinymce-react': + specifier: ^6.3.0 + version: 6.3.0(react-dom@19.2.3)(react@19.2.3) '@togglecorp/fujs': specifier: ^2.2.0 version: 2.2.0 '@togglecorp/toggle-form': specifier: ^2.0.4 version: 2.0.4(react-dom@19.2.3)(react@19.2.3) + '@uiw/react-md-editor': + specifier: ^4.0.11 + version: 4.0.11(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3) + '@urql/exchange-graphcache': + specifier: ^8.1.0 + version: 8.1.0(@urql/core@6.0.1)(graphql@16.12.0) + dompurify: + specifier: ^3.3.1 + version: 3.3.1 graphql: specifier: ^16.12.0 version: 16.12.0 + html-react-parser: + specifier: ^5.2.11 + version: 5.2.11(@types/react@19.2.7)(react@19.2.3) react: specifier: ^19.2.0 version: 19.2.3 @@ -2376,22 +2394,22 @@ packages: engines: {node: '>=18.18'} dev: true - /@ifrc-go/icons@1.3.4(react@19.2.3): - resolution: {integrity: sha512-TpHchp3YaYebtpPNRYJNUg4Iw0jeHf43sYvM0/UghBuxaA+WwQflxOIBr4/uB7SkNGhY33bAb3Ru8skjohPuVg==} + /@ifrc-go/icons@2.0.1(react@19.2.3): + resolution: {integrity: sha512-j0KXz5UgUgVBfSljx6XATBWchj1tDckaGX+uI86QTO63teP5zCd6osR9q/e6H3jv8bzil1sdUmVt4VOwSX6xhw==} peerDependencies: react: '>= 16' dependencies: react: 19.2.3 dev: false - /@ifrc-go/ui@1.3.0(@ifrc-go/icons@1.3.4)(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3): + /@ifrc-go/ui@1.3.0(@ifrc-go/icons@2.0.1)(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3): resolution: {integrity: sha512-9Zv3qMMEVOH995YgBgL0PiJEgRZ5bN+recCEtGHTQqilduQ8z8uUJsRN1oz18YZ/xXE/TGIiVCrLz79E7C1xEQ==} peerDependencies: '@ifrc-go/icons': ^1.3.1 react: ^18.2.0 react-dom: ^18.2.0 dependencies: - '@ifrc-go/icons': 1.3.4(react@19.2.3) + '@ifrc-go/icons': 2.0.1(react@19.2.3) '@togglecorp/fujs': 2.2.0 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -3358,6 +3376,21 @@ packages: - supports-color dev: true + /@tinymce/tinymce-react@6.3.0(react-dom@19.2.3)(react@19.2.3): + resolution: {integrity: sha512-E++xnn0XzDzpKr40jno2Kj7umfAE6XfINZULEBBeNjTMvbACWzA6CjiR6V8eTDc9yVmdVhIPqVzV4PqD5TZ/4g==} + peerDependencies: + react: ^19.0.0 || ^18.0.0 || ^17.0.1 || ^16.7.0 + react-dom: ^19.0.0 || ^18.0.0 || ^17.0.1 || ^16.7.0 + tinymce: ^8.0.0 || ^7.0.0 || ^6.0.0 || ^5.5.1 + peerDependenciesMeta: + tinymce: + optional: true + dependencies: + prop-types: 15.8.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + dev: false + /@togglecorp/fujs@2.2.0: resolution: {integrity: sha512-OuoQ9Bj7SiI2sTLpaM/HivU6HpSbZ3ANBIn7f9KUz5eFcfwBBEDvjI+4ah6WktJEYTUKY4RxX37z64qOrTJSwA==} dependencies: @@ -3397,9 +3430,32 @@ packages: dev: true optional: true + /@types/debug@4.1.12: + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + dependencies: + '@types/ms': 2.1.0 + dev: false + + /@types/estree-jsx@1.0.5: + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + dependencies: + '@types/estree': 1.0.8 + dev: false + /@types/estree@1.0.8: resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - dev: true + + /@types/hast@2.3.10: + resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + dependencies: + '@types/unist': 2.0.11 + dev: false + + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + dependencies: + '@types/unist': 3.0.3 + dev: false /@types/hoist-non-react-statics@3.3.7(@types/react@19.2.7): resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==} @@ -3418,12 +3474,26 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/mdast@4.0.4: + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /@types/ms@2.1.0: + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + dev: false + /@types/node@24.10.4: resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} dependencies: undici-types: 7.16.0 dev: true + /@types/prismjs@1.26.5: + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + dev: false + /@types/react-dom@19.2.3(@types/react@19.2.7): resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -3437,6 +3507,20 @@ packages: dependencies: csstype: 3.2.3 + /@types/trusted-types@2.0.7: + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + requiresBuild: true + dev: false + optional: true + + /@types/unist@2.0.11: + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + dev: false + + /@types/unist@3.0.3: + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + dev: false + /@types/ws@8.18.1: resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} dependencies: @@ -3583,6 +3667,57 @@ packages: eslint-visitor-keys: 4.2.1 dev: true + /@uiw/copy-to-clipboard@1.0.19: + resolution: {integrity: sha512-AYxzFUBkZrhtExb2QC0C4lFH2+BSx6JVId9iqeGHakBuosqiQHUQaNZCvIBeM97Ucp+nJ22flOh8FBT2pKRRAA==} + dev: false + + /@uiw/react-markdown-preview@5.1.5(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3): + resolution: {integrity: sha512-DNOqx1a6gJR7Btt57zpGEKTfHRlb7rWbtctMRO2f82wWcuoJsxPBrM+JWebDdOD0LfD8oe2CQvW2ICQJKHQhZg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@babel/runtime': 7.28.4 + '@uiw/copy-to-clipboard': 1.0.19 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-markdown: 9.0.3(@types/react@19.2.7)(react@19.2.3) + rehype-attr: 3.0.3 + rehype-autolink-headings: 7.1.0 + rehype-ignore: 2.0.3 + rehype-prism-plus: 2.0.0 + rehype-raw: 7.0.0 + rehype-rewrite: 4.0.4 + rehype-slug: 6.0.0 + remark-gfm: 4.0.1 + remark-github-blockquote-alert: 1.3.1 + unist-util-visit: 5.0.0 + transitivePeerDependencies: + - '@types/react' + - supports-color + dev: false + + /@uiw/react-md-editor@4.0.11(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3): + resolution: {integrity: sha512-F0OR5O1v54EkZYvJj3ew0I7UqLiPeU34hMAY4MdXS3hI86rruYi5DHVkG/VuvLkUZW7wIETM2QFtZ459gKIjQA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@babel/runtime': 7.28.4 + '@uiw/react-markdown-preview': 5.1.5(@types/react@19.2.7)(react-dom@19.2.3)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + rehype: 13.0.2 + rehype-prism-plus: 2.0.1 + transitivePeerDependencies: + - '@types/react' + - supports-color + dev: false + + /@ungap/structured-clone@1.3.0: + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + dev: false + /@unrs/resolver-binding-android-arm-eabi@1.11.1: resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -3746,6 +3881,18 @@ packages: - graphql dev: false + /@urql/exchange-graphcache@8.1.0(@urql/core@6.0.1)(graphql@16.12.0): + resolution: {integrity: sha512-KxdDFcGAWhYOkpCz/NLNHb8SaWSxE2G1iv+jlFty2f/ZlCkD+FjtzwQuieGl7fk1MVR8DG1zLmxGNlwfCNksfQ==} + peerDependencies: + '@urql/core': ^6.0.0 + dependencies: + '@0no-co/graphql.web': 1.2.0(graphql@16.12.0) + '@urql/core': 6.0.1(graphql@16.12.0) + wonka: 6.3.5 + transitivePeerDependencies: + - graphql + dev: false + /@vitejs/plugin-react-swc@4.2.2(rolldown-vite@7.2.5): resolution: {integrity: sha512-x+rE6tsxq/gxrEJN3Nv3dIV60lFflPj94c90b+NNo6n1QV1QQUTLoL0MpaOVasUZ0zqVBn7ead1B5ecx1JAGfA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4127,6 +4274,10 @@ packages: - supports-color dev: true + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true @@ -4140,12 +4291,20 @@ packages: hasBin: true dev: true + /bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + dev: false + /bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} dependencies: tweetnacl: 0.14.5 dev: true + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: false + /brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} dependencies: @@ -4262,6 +4421,10 @@ packages: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} dev: true + /ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + dev: false + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -4302,6 +4465,22 @@ packages: tslib: 2.8.1 dev: true + /character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + dev: false + + /character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + dev: false + + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + dev: false + + /character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + dev: false + /chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} dev: true @@ -4397,6 +4576,10 @@ packages: delayed-stream: 1.0.0 dev: true + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: false + /common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} @@ -4528,6 +4711,10 @@ packages: postcss: 8.5.6 dev: true + /css-selector-parser@3.3.0: + resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + dev: false + /css-tree@3.1.0: resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -4645,13 +4832,18 @@ packages: optional: true dependencies: ms: 2.1.3 - dev: true /decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} dev: true + /decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + dependencies: + character-entities: 2.0.2 + dev: false + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true @@ -4698,6 +4890,11 @@ packages: engines: {node: '>=4'} dev: true + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + dev: false + /detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -4712,6 +4909,12 @@ packages: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} dev: false + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dependencies: + dequal: 2.0.3 + dev: false + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -4719,6 +4922,11 @@ packages: path-type: 4.0.0 dev: true + /direction@2.0.1: + resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} + hasBin: true + dev: false + /doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -4752,6 +4960,12 @@ packages: domelementtype: 2.3.0 dev: false + /dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + optionalDependencies: + '@types/trusted-types': 2.0.7 + dev: false + /domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} dependencies: @@ -4835,6 +5049,11 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} + /entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + dev: false + /env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -4985,6 +5204,11 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: false + /escodegen@1.14.3: resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} engines: {node: '>=4.0'} @@ -5348,6 +5572,10 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + dev: false + /estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true @@ -5363,7 +5591,6 @@ packages: /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - dev: true /extsprintf@1.3.0: resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} @@ -5683,6 +5910,10 @@ packages: assert-plus: 1.0.0 dev: true + /github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + dev: false + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5901,6 +6132,180 @@ packages: function-bind: 1.1.2 dev: true + /hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + dev: false + + /hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + dev: false + + /hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /hast-util-heading-rank@3.0.0: + resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /hast-util-parse-selector@3.1.1: + resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} + dependencies: + '@types/hast': 2.3.10 + dev: false + + /hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + dev: false + + /hast-util-select@6.0.4: + resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + dev: false + + /hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + dev: false + + /hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + dev: false + + /hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + dev: false + + /hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /hastscript@7.2.0: + resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} + dependencies: + '@types/hast': 2.3.10 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 3.1.1 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + dev: false + + /hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + dev: false + /header-case@2.0.4: resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} dependencies: @@ -5928,17 +6333,58 @@ packages: resolution: {integrity: sha512-pi1ynXIMFx/uIIwpWJ/5CEtOHLGtnUB0WhGeeYT+fKcQ+WCQbm3/rrkAXnpfph++PgepNqPdTC2WTj8A6k6zoQ==} dev: true + /html-dom-parser@5.1.2: + resolution: {integrity: sha512-9nD3Rj3/FuQt83AgIa1Y3ruzspwFFA54AJbQnohXN+K6fL1/bhcDQJJY5Ne4L4A163ADQFVESd/0TLyNoV0mfg==} + dependencies: + domhandler: 5.0.3 + htmlparser2: 10.0.0 + dev: false + /html-encoding-sniffer@1.0.2: resolution: {integrity: sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==} dependencies: whatwg-encoding: 1.0.5 dev: true + /html-react-parser@5.2.11(@types/react@19.2.7)(react@19.2.3): + resolution: {integrity: sha512-WnSQVn/D1UTj64nSz5y8MriL+MrbsZH80Ytr1oqKqs8DGZnphWY1R1pl3t7TY3rpqTSu+FHA21P80lrsmrdNBA==} + peerDependencies: + '@types/react': 0.14 || 15 || 16 || 17 || 18 || 19 + react: 0.14 || 15 || 16 || 17 || 18 || 19 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 19.2.7 + domhandler: 5.0.3 + html-dom-parser: 5.1.2 + react: 19.2.3 + react-property: 2.0.2 + style-to-js: 1.1.21 + dev: false + /html-tags@3.3.1: resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} engines: {node: '>=8'} dev: true + /html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + dev: false + + /html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + dev: false + + /htmlparser2@10.0.0: + resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 6.0.1 + dev: false + /htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} dependencies: @@ -6020,6 +6466,10 @@ packages: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true + /inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + dev: false + /internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -6048,6 +6498,17 @@ packages: is-windows: 1.0.2 dev: true + /is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + dev: false + + /is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + dev: false + /is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -6122,6 +6583,10 @@ packages: has-tostringtag: 1.0.2 dev: true + /is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + dev: false + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -6164,6 +6629,10 @@ packages: is-extglob: 2.1.1 dev: true + /is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + dev: false + /is-lower-case@2.0.2: resolution: {integrity: sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==} dependencies: @@ -6193,6 +6662,11 @@ packages: engines: {node: '>=0.12.0'} dev: true + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + dev: false + /is-plain-object@5.0.0: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} @@ -6742,6 +7216,10 @@ packages: wrap-ansi: 9.0.2 dev: true + /longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + dev: false + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -6771,6 +7249,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + dev: false + /math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -6780,6 +7262,189 @@ packages: resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} dev: true + /mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + dev: false + + /mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + dev: false + + /mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + dev: false + + /mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + dev: false + + /mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + dev: false + + /mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + dependencies: + '@types/mdast': 4.0.4 + dev: false + /mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} dev: true @@ -6806,6 +7471,253 @@ packages: '@types/node': 24.10.4 dev: true + /micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + dependencies: + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + dependencies: + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + dependencies: + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + dev: false + + /micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + dev: false + + /micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + dependencies: + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + dependencies: + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + dev: false + + /micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + dev: false + + /micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + dev: false + + /micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + dev: false + + /micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -6850,7 +7762,6 @@ packages: /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true /mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} @@ -6934,6 +7845,12 @@ packages: unicorn-magic: 0.3.0 dev: true + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: false + /nullthrows@1.1.1: resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} dev: true @@ -7133,6 +8050,18 @@ packages: callsites: 3.1.0 dev: true + /parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + dev: false + /parse-filepath@1.0.2: resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} engines: {node: '>=0.8'} @@ -7152,6 +8081,10 @@ packages: lines-and-columns: 1.2.4 dev: true + /parse-numeric-range@1.3.0: + resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + dev: false + /parse-srcset@1.0.2: resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} dev: false @@ -7160,6 +8093,12 @@ packages: resolution: {integrity: sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==} dev: true + /parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + dependencies: + entities: 6.0.1 + dev: false + /pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: @@ -7710,6 +8649,14 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + dev: false + + /property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + dev: false + /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: true @@ -7826,6 +8773,32 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + /react-markdown@9.0.3(@types/react@19.2.7)(react@19.2.3): + resolution: {integrity: sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + dependencies: + '@types/hast': 3.0.4 + '@types/react': 19.2.7 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.3 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + dev: false + + /react-property@2.0.2: + resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==} + dev: false + /react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.3): resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -7917,6 +8890,15 @@ packages: which-builtin-type: 1.2.1 dev: true + /refractor@4.9.0: + resolution: {integrity: sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og==} + dependencies: + '@types/hast': 2.3.10 + '@types/prismjs': 1.26.5 + hastscript: 7.2.0 + parse-entities: 4.0.2 + dev: false + /regexp.prototype.flags@1.5.4: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} @@ -7929,6 +8911,108 @@ packages: set-function-name: 2.0.2 dev: true + /rehype-attr@3.0.3: + resolution: {integrity: sha512-Up50Xfra8tyxnkJdCzLBIBtxOcB2M1xdeKe1324U06RAvSjYm7ULSeoM+b/nYPQPVd7jsXJ9+39IG1WAJPXONw==} + engines: {node: '>=16'} + dependencies: + unified: 11.0.5 + unist-util-visit: 5.0.0 + dev: false + + /rehype-autolink-headings@7.1.0: + resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==} + dependencies: + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.3.0 + hast-util-heading-rank: 3.0.0 + hast-util-is-element: 3.0.0 + unified: 11.0.5 + unist-util-visit: 5.0.0 + dev: false + + /rehype-ignore@2.0.3: + resolution: {integrity: sha512-IzhP6/u/6sm49sdktuYSmeIuObWB+5yC/5eqVws8BhuGA9kY25/byz6uCy/Ravj6lXUShEd2ofHM5MyAIj86Sg==} + engines: {node: '>=16'} + dependencies: + hast-util-select: 6.0.4 + unified: 11.0.5 + unist-util-visit: 5.0.0 + dev: false + + /rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + dev: false + + /rehype-prism-plus@2.0.0: + resolution: {integrity: sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ==} + dependencies: + hast-util-to-string: 3.0.1 + parse-numeric-range: 1.3.0 + refractor: 4.9.0 + rehype-parse: 9.0.1 + unist-util-filter: 5.0.1 + unist-util-visit: 5.0.0 + dev: false + + /rehype-prism-plus@2.0.1: + resolution: {integrity: sha512-Wglct0OW12tksTUseAPyWPo3srjBOY7xKlql/DPKi7HbsdZTyaLCAoO58QBKSczFQxElTsQlOY3JDOFzB/K++Q==} + dependencies: + hast-util-to-string: 3.0.1 + parse-numeric-range: 1.3.0 + refractor: 4.9.0 + rehype-parse: 9.0.1 + unist-util-filter: 5.0.1 + unist-util-visit: 5.0.0 + dev: false + + /rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + dev: false + + /rehype-rewrite@4.0.4: + resolution: {integrity: sha512-L/FO96EOzSA6bzOam4DVu61/PB3AGKcSPXpa53yMIozoxH4qg1+bVZDF8zh1EsuxtSauAhzt5cCnvoplAaSLrw==} + engines: {node: '>=16.0.0'} + dependencies: + hast-util-select: 6.0.4 + unified: 11.0.5 + unist-util-visit: 5.0.0 + dev: false + + /rehype-slug@6.0.0: + resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} + dependencies: + '@types/hast': 3.0.4 + github-slugger: 2.0.0 + hast-util-heading-rank: 3.0.0 + hast-util-to-string: 3.0.1 + unist-util-visit: 5.0.0 + dev: false + + /rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + dev: false + + /rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + dev: false + /relay-runtime@12.0.0: resolution: {integrity: sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug==} dependencies: @@ -7939,6 +9023,55 @@ packages: - encoding dev: true + /remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-github-blockquote-alert@1.3.1: + resolution: {integrity: sha512-OPNnimcKeozWN1w8KVQEuHOxgN3L4rah8geMOLhA5vN9wITqU4FWD+G26tkEsCGHiOVDbISx+Se5rGZ+D1p0Jg==} + engines: {node: '>=16'} + dependencies: + unist-util-visit: 5.0.0 + dev: false + + /remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + dev: false + + /remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + dev: false + + /remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + dev: false + /remedial@1.0.8: resolution: {integrity: sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==} dev: true @@ -8380,6 +9513,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: false + /sponge-case@1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} dependencies: @@ -8521,6 +9658,13 @@ packages: es-object-atoms: 1.1.1 dev: true + /stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + dev: false + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -8550,6 +9694,18 @@ packages: engines: {node: '>=14.16'} dev: true + /style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + dependencies: + style-to-object: 1.0.14 + dev: false + + /style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + dependencies: + inline-style-parser: 0.2.7 + dev: false + /stylelint-config-concentric@2.0.2(stylelint@16.26.1): resolution: {integrity: sha512-R0d3GMB3FWyqNfhBlUiOXhOjzEzEbz2lBT/Kp8CMwbcB24rKtYB0Ot0jyIaCUqjjFcW05J2l3w2J9Oolwc9xyg==} peerDependencies: @@ -8792,6 +9948,14 @@ packages: punycode: 2.3.1 dev: true + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: false + + /trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + dev: false + /ts-api-utils@2.1.0(typescript@5.9.3): resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -8982,6 +10146,59 @@ packages: engines: {node: '>=18'} dev: true + /unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + dev: false + + /unist-util-filter@5.0.1: + resolution: {integrity: sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + dev: false + + /unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + dependencies: + '@types/unist': 3.0.3 + dev: false + + /unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + dev: false + + /unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + dev: false + /universal-cookie@8.0.1: resolution: {integrity: sha512-B6ks9FLLnP1UbPPcveOidfvB9pHjP+wekP2uRYB9YDfKVpvcjKgy1W5Zj+cEXJ9KTPnqOKGfVDQBmn8/YCQfRg==} dependencies: @@ -9116,6 +10333,27 @@ packages: extsprintf: 1.3.0 dev: true + /vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + dev: false + + /vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + dev: false + + /vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + dev: false + /vite-plugin-checker@0.11.0(eslint@9.39.2)(rolldown-vite@7.2.5)(stylelint@16.26.1)(typescript@5.9.3): resolution: {integrity: sha512-iUdO9Pl9UIBRPAragwi3as/BXXTtRu4G12L3CMrjx+WVTd9g/MsqNakreib9M/2YRVkhZYiTEwdH2j4Dm0w7lw==} engines: {node: '>=16.11'} @@ -9246,6 +10484,10 @@ packages: engines: {node: 20 || >=22} dev: true + /web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + dev: false + /web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -9527,3 +10769,7 @@ packages: /zod@4.2.1: resolution: {integrity: sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==} dev: true + + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: false From aaec1763408ec8ee6a28fe6867e8cf655b03f762 Mon Sep 17 00:00:00 2001 From: crssstha Date: Thu, 8 Jan 2026 13:56:23 +0545 Subject: [PATCH 02/25] fixup! feat(blog): Add blog listing and edit add form --- app/components/ConfirmationModal/index.tsx | 55 ++++++++ .../ConfirmationModal/styles.module.css | 4 + app/components/TableAction/index.tsx | 60 +++++++++ app/components/TableAction/styles.module.css | 4 + app/root/config/routes.tsx | 6 +- app/views/Blog/BlogForm/index.tsx | 72 +---------- app/views/Blog/BlogList/index.tsx | 101 ++++----------- app/views/Blog/query.ts | 120 ++++++++++++++++++ 8 files changed, 275 insertions(+), 147 deletions(-) create mode 100644 app/components/ConfirmationModal/index.tsx create mode 100644 app/components/ConfirmationModal/styles.module.css create mode 100644 app/components/TableAction/index.tsx create mode 100644 app/components/TableAction/styles.module.css create mode 100644 app/views/Blog/query.ts diff --git a/app/components/ConfirmationModal/index.tsx b/app/components/ConfirmationModal/index.tsx new file mode 100644 index 0000000..7a4afc3 --- /dev/null +++ b/app/components/ConfirmationModal/index.tsx @@ -0,0 +1,55 @@ +import { + Button, + Modal, +} from '@ifrc-go/ui'; + +import styles from './styles.module.css'; + +interface Props { + onClose: () => void; + handleConfirmButtonChange: () => void; + confirmPending?: boolean; + type: 'save' | 'delete'; +} + +function ConfirmationModal(props: Props) { + const { + onClose, + handleConfirmButtonChange, + confirmPending = false, + type, + } = props; + + const confirmationModalDescription = type === 'delete' + ? 'Are you sure you want to delete this item? This action cannot be undone.' + : 'Are you sure you want to save the changes?'; + + return ( + + + + + )} + /> + ); +} + +export default ConfirmationModal; diff --git a/app/components/ConfirmationModal/styles.module.css b/app/components/ConfirmationModal/styles.module.css new file mode 100644 index 0000000..caf2edd --- /dev/null +++ b/app/components/ConfirmationModal/styles.module.css @@ -0,0 +1,4 @@ +.confirmationBtn { + display: flex; + gap: var(--go-ui-spacing-md); +} diff --git a/app/components/TableAction/index.tsx b/app/components/TableAction/index.tsx new file mode 100644 index 0000000..81ec728 --- /dev/null +++ b/app/components/TableAction/index.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router'; +import { + DeleteBinLineIcon, + EditTwoLineIcon, +} from '@ifrc-go/icons'; +import { Button } from '@ifrc-go/ui'; + +import ConfirmationModal from '#components/ConfirmationModal'; + +import styles from './styles.module.css'; + +export interface TableActionsProps { + id: string; + handleConfirmButtonChange: (id: string, closeModal: () => void) => void + confirmPending?: boolean +} + +function TableActions(props: TableActionsProps) { + const navigate = useNavigate(); + const { id, handleConfirmButtonChange, confirmPending } = props; + const [openDeleteModal, setOpenDeleteModal] = useState(false); + + const handleClose = () => { + setOpenDeleteModal(false); + }; + + const handleConfirmButton = () => { + handleConfirmButtonChange(id, handleClose); + }; + + return ( +
+ {(openDeleteModal) && ( + + )} + + +
+ ); +} + +export default TableActions; diff --git a/app/components/TableAction/styles.module.css b/app/components/TableAction/styles.module.css new file mode 100644 index 0000000..66f30f8 --- /dev/null +++ b/app/components/TableAction/styles.module.css @@ -0,0 +1,4 @@ +.tableAction { + display: flex; + gap: var(--go-ui-spacing-md); +} diff --git a/app/root/config/routes.tsx b/app/root/config/routes.tsx index 2896e02..bb2960e 100644 --- a/app/root/config/routes.tsx +++ b/app/root/config/routes.tsx @@ -15,20 +15,20 @@ const home: RouteConfig = { const blog: RouteConfig = { index: true, - path: 'cm/blog', + path: 'blog', load: () => import('#views/Blog/BlogList'), visibility: 'is-authenticated', }; const editBlog: RouteConfig = { index: true, - path: 'cm/blog/:id/edit', + path: 'blog/:id/edit', load: () => import('#views/Blog/BlogForm'), visibility: 'is-authenticated', }; const addBlog: RouteConfig = { index: true, - path: 'cm/blog/add', + path: 'blog/add', load: () => import('#views/Blog/BlogForm'), visibility: 'is-authenticated', }; diff --git a/app/views/Blog/BlogForm/index.tsx b/app/views/Blog/BlogForm/index.tsx index 946b04b..2657fd0 100644 --- a/app/views/Blog/BlogForm/index.tsx +++ b/app/views/Blog/BlogForm/index.tsx @@ -24,7 +24,6 @@ import { requiredStringCondition, useForm, } from '@togglecorp/toggle-form'; -import { gql } from 'urql'; import FormSection from '#components/FormSection'; import Page from '#components/Page'; @@ -104,73 +103,6 @@ const defaultEditFormValue: PartialFormType = { slug: '', }; -const BLOG_DETAIL_QUERY = gql` - query BlogDetailQuery($id: ID!) { - blog(id: $id) { - author - content - coverImage { - name - size - url - } - departmentId - directiveId - featured - id - modifiedBy { - firstName - lastName - } - publishedDate - slug - status - title - createdBy { - firstName - lastName - } - } - } -`; - -const DEPARTMENT_AND_DIRECTIVE = gql` - query DepartmentAndDirective { - departments { - id - description - title - strategicDirectiveId - } - strategicDirectives { - id - title - } - } -`; - -const CREATE_BLOG_MUTATION = gql` - mutation CreateBlog($data: BlogCreateInput!) { - createBlog(data: $data) { - ... on BlogTypeMutationResponseType { - errors - ok - } - } - } -`; - -export const UPDATE_BLOG_MUTATION = gql` - mutation UpdateBlog($pk: ID!, $data: BlogUpdateInput!) { - updateBlog(pk: $pk, data: $data) { - ... on BlogTypeMutationResponseType { - errors - ok - } - } - } -`; - function BlogForm() { const { id } = useParams(); const navigate = useNavigate(); @@ -233,14 +165,14 @@ function BlogForm() { handler(); }, [setError, validate, id, updateBlogMutate, createBlogMutate, navigate]); - const departmentOptions = departmentAndDirective?.departments.map( + const departmentOptions = departmentAndDirective?.departments.results.map( (dept) => ({ id: dept.id, name: dept.title, }), ) ?? []; - const directiveOptions = departmentAndDirective?.strategicDirectives.map( + const directiveOptions = departmentAndDirective?.strategicDirectives.results.map( (directive) => ({ id: directive.id, name: directive.title, diff --git a/app/views/Blog/BlogList/index.tsx b/app/views/Blog/BlogList/index.tsx index d156961..5fb6573 100644 --- a/app/views/Blog/BlogList/index.tsx +++ b/app/views/Blog/BlogList/index.tsx @@ -1,9 +1,8 @@ -import { useMemo } from 'react'; -import { useNavigate } from 'react-router'; import { - DeleteBinLineIcon, - EditTwoLineIcon, -} from '@ifrc-go/icons'; + useCallback, + useMemo, +} from 'react'; +import { useNavigate } from 'react-router'; import { Button, Container, @@ -16,78 +15,35 @@ import { createNumberColumn, createStringColumn, } from '@ifrc-go/ui/utils'; -import { gql } from 'urql'; +import TableActions, { TableActionsProps } from '#components/TableAction'; import { BlogQueryQuery, useBlogQueryQuery, + useDeleteBlogMutation, } from '#generated/types/graphql'; import styles from './styles.module.css'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const BLOG_QUERY = gql` - query BlogQuery { - blogs { - title - status - slug - publishedDate - modifiedAt - id - featured - directiveId - author - content - coverImage { - name - size - url - } - createdAt - createdBy { - firstName - id - lastName - } - modifiedBy { - firstName - id - lastName - } - } - } -`; -type EventListItem = NonNullable[number]; - -function Actions({ id }: { id: string }) { - const navigate = useNavigate(); - - return ( -
- - -
- ); -} +type EventListItem = NonNullable['results'][number]; function BlogList() { - const [{ fetching, data }] = useBlogQueryQuery(); const navigate = useNavigate(); - const tableData = data?.blogs?.map((blog, i) => ({ ...blog, sn: i + 1 })); - + const [{ fetching, data }, reexecuteQuery] = useBlogQueryQuery(); + const [{ fetching: deletePending }, deleteBlog] = useDeleteBlogMutation(); + const tableData = data?.blogs?.results.map((blog, i) => ({ ...blog, sn: i + 1 })); + + const handleDelete = useCallback( + (id: string, closeModal: () => void) => { + deleteBlog({ id }).then((resp) => { + if (resp.data?.deleteBlog) { + reexecuteQuery(); + closeModal(); + } + }); + }, + [deleteBlog, reexecuteQuery], + ); const columns = useMemo( () => ([ // Serial Number @@ -104,7 +60,6 @@ function BlogList() { (blog) => blog.title, { sortable: true, - // columnStretch: true, }, ), @@ -115,7 +70,6 @@ function BlogList() { (blog) => blog.publishedDate, { sortable: true, - // columnWidth: 140, }, ), @@ -126,7 +80,6 @@ function BlogList() { (blog) => blog.author, { sortable: true, - // columnWidth: 160, }, ), @@ -137,7 +90,6 @@ function BlogList() { (blog) => blog.featured, { sortable: true, - // columnWidth: 100, }, ), @@ -148,16 +100,17 @@ function BlogList() { (blog) => blog.status, { sortable: true, - // columnWidth: 120, }, ), - createElementColumn( + createElementColumn( 'actions', 'Actions', - Actions, + TableActions, (_, datum) => ({ id: datum.id, + handleConfirmButtonChange: handleDelete, + confirmPending: deletePending, }), { columnWidth: 150, @@ -165,7 +118,7 @@ function BlogList() { ), ]), - [], + [handleDelete, deletePending], ); return ( diff --git a/app/views/Blog/query.ts b/app/views/Blog/query.ts new file mode 100644 index 0000000..a0cb5b1 --- /dev/null +++ b/app/views/Blog/query.ts @@ -0,0 +1,120 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { gql } from 'urql'; + +const BLOG_QUERY = gql` + query BlogQuery { + blogs { + totalCount + results { + author + content + createdAt + departmentId + directiveId + featured + id + modifiedAt + coverImage { + name + size + url + } + createdBy { + firstName + lastName + } + publishedDate + slug + status + title + } + } + } +`; + +const BLOG_DETAIL_QUERY = gql` + query BlogDetailQuery($id: ID!) { + blog(id: $id) { + author + content + coverImage { + name + size + url + } + departmentId + directiveId + featured + id + modifiedBy { + firstName + lastName + } + publishedDate + slug + status + title + createdBy { + firstName + lastName + } + } + } +`; + +const DEPARTMENT_AND_DIRECTIVE = gql` + query DepartmentAndDirective { + departments { + results { + id + description + title + strategicDirectiveId + } + } + strategicDirectives { + results { + id + title + } + } + } +`; + +const CREATE_BLOG_MUTATION = gql` + mutation CreateBlog($data: BlogCreateInput!) { + createBlog(data: $data) { + ... on BlogTypeMutationResponseType { + errors + ok + } + } + } +`; + +const UPDATE_BLOG_MUTATION = gql` + mutation UpdateBlog($pk: ID!, $data: BlogUpdateInput!) { + updateBlog(pk: $pk, data: $data) { + ... on BlogTypeMutationResponseType { + errors + ok + } + } + } +`; + +const DELETE_BLOG = gql` + mutation DeleteBlog($id: ID!) { + deleteBlog(data: { id: $id }) { + ... on BlogType { + title + } + ... on OperationInfo { + __typename + messages { + message + } + } + } + } +`; From 90ea349bbd9cfddfce95bcf96b783c1581944d51 Mon Sep 17 00:00:00 2001 From: crssstha Date: Fri, 9 Jan 2026 10:53:33 +0545 Subject: [PATCH 03/25] feat(department): Add department list and edit add form --- app/components/ConfirmationModal/index.tsx | 8 +- .../ConfirmationModal/styles.module.css | 11 +- app/components/TableAction/index.tsx | 19 +- app/hooks/usePagination.ts | 51 +++ app/root/config/routes.tsx | 23 ++ app/utils/routes.tsx | 293 ------------------ app/views/Blog/BlogForm/index.tsx | 8 +- app/views/Blog/BlogList/index.tsx | 45 ++- app/views/Blog/query.ts | 8 +- app/views/Department/DepartmentForm/index.tsx | 241 ++++++++++++++ .../DepartmentForm/styles.module.css | 37 +++ app/views/Department/DepartmentList/index.tsx | 112 +++++++ .../DepartmentList/styles.module.css | 17 + app/views/Department/query.ts | 89 ++++++ 14 files changed, 639 insertions(+), 323 deletions(-) create mode 100644 app/hooks/usePagination.ts delete mode 100644 app/utils/routes.tsx create mode 100644 app/views/Department/DepartmentForm/index.tsx create mode 100644 app/views/Department/DepartmentForm/styles.module.css create mode 100644 app/views/Department/DepartmentList/index.tsx create mode 100644 app/views/Department/DepartmentList/styles.module.css create mode 100644 app/views/Department/query.ts diff --git a/app/components/ConfirmationModal/index.tsx b/app/components/ConfirmationModal/index.tsx index 7a4afc3..8e59aa2 100644 --- a/app/components/ConfirmationModal/index.tsx +++ b/app/components/ConfirmationModal/index.tsx @@ -10,6 +10,8 @@ interface Props { handleConfirmButtonChange: () => void; confirmPending?: boolean; type: 'save' | 'delete'; + itemTitle?: string; + } function ConfirmationModal(props: Props) { @@ -18,16 +20,18 @@ function ConfirmationModal(props: Props) { handleConfirmButtonChange, confirmPending = false, type, + itemTitle, } = props; const confirmationModalDescription = type === 'delete' - ? 'Are you sure you want to delete this item? This action cannot be undone.' - : 'Are you sure you want to save the changes?'; + ? `Are you sure you want to delete ${itemTitle || 'this item'}? This action cannot be undone.` + : `Are you sure you want to save changes to ${itemTitle || 'this item'}?`; return ( void) => void confirmPending?: boolean + itemTitle: string } function TableActions(props: TableActionsProps) { const navigate = useNavigate(); - const { id, handleConfirmButtonChange, confirmPending } = props; + const { + id, handleConfirmButtonChange, confirmPending, itemTitle, + } = props; const [openDeleteModal, setOpenDeleteModal] = useState(false); - const handleClose = () => { + const handleClose = useCallback(() => { setOpenDeleteModal(false); - }; + }, []); - const handleConfirmButton = () => { + const handleConfirmButton = useCallback(() => { handleConfirmButtonChange(id, handleClose); - }; + }, [id, handleClose, handleConfirmButtonChange]); return (
@@ -37,6 +43,7 @@ function TableActions(props: TableActionsProps) { type="delete" handleConfirmButtonChange={handleConfirmButton} confirmPending={confirmPending} + itemTitle={itemTitle} /> )} )} + footerActions={( + + )} >
item.id} diff --git a/app/views/Blog/query.ts b/app/views/Blog/query.ts index a0cb5b1..4fb31c8 100644 --- a/app/views/Blog/query.ts +++ b/app/views/Blog/query.ts @@ -2,8 +2,12 @@ import { gql } from 'urql'; const BLOG_QUERY = gql` - query BlogQuery { - blogs { + query BlogQuery($pagination: OffsetPaginationInput) { + blogs(pagination: $pagination) { + pageInfo { + limit + offset + } totalCount results { author diff --git a/app/views/Department/DepartmentForm/index.tsx b/app/views/Department/DepartmentForm/index.tsx new file mode 100644 index 0000000..d5b2b56 --- /dev/null +++ b/app/views/Department/DepartmentForm/index.tsx @@ -0,0 +1,241 @@ +import { + useCallback, + useEffect, +} from 'react'; +import { + useNavigate, + useParams, +} from 'react-router'; +import { + Button, + Container, + Heading, + SelectInput, + TextInput, +} from '@ifrc-go/ui'; +import { + createSubmitHandler, + getErrorObject, + ObjectSchema, + PartialForm, + requiredStringCondition, + useForm, +} from '@togglecorp/toggle-form'; + +import FormSection from '#components/FormSection'; +import Page from '#components/Page'; +import RichTextEditor from '#components/RichTextEditor'; +import { + DepartmentCreateInput, + useCreateDepartmentMutation, + useDepartmentDetailQuery, + useDirectiveQuery, + useUpdateDepartmentMutation, +} from '#generated/types/graphql'; + +import styles from './styles.module.css'; + +type PartialFormType = PartialForm & +{ createdBy: string, modifiedBy: string, slug: string | null } + +type FormSchema = ObjectSchema; +type FormSchemaFields = ReturnType; + +const EditBlogSchema: FormSchema = { + fields: (): FormSchemaFields => ({ + title: { + required: true, + requiredValidation: requiredStringCondition, + }, + contactPersonEmail: { + required: true, + requiredValidation: requiredStringCondition, + }, + description: { + required: false, + requiredValidation: requiredStringCondition, + }, + contactPersonName: { + required: false, + }, + strategicDirective: { + required: false, + requiredValidation: requiredStringCondition, + }, + createdBy: { + required: false, + requiredValidation: requiredStringCondition, + }, + modifiedBy: { + required: false, + requiredValidation: requiredStringCondition, + }, + slug: { + required: false, + requiredValidation: requiredStringCondition, + }, + + }), +}; + +const defaultEditFormValue: PartialFormType = { + createdBy: '', + modifiedBy: '', + slug: '', +}; +function DepartmentForm() { + const { id } = useParams(); + const navigate = useNavigate(); + const [{ data }] = useDepartmentDetailQuery({ + variables: { id: id || '' }, pause: !id, + }); + const [{ data: directive }] = useDirectiveQuery(); + const [{ fetching: createPending }, createDepartmentMutate] = useCreateDepartmentMutation(); + const [{ fetching: updatePending }, updateDepartmentMutate] = useUpdateDepartmentMutation(); + const { + setFieldValue, + error: formError, + value, + validate, + setError, + } = useForm(EditBlogSchema, { value: defaultEditFormValue }); + + const error = getErrorObject(formError); + + const handleFormSubmit = useCallback(() => { + const handler = createSubmitHandler( + validate, + setError, + async (val) => { + const mutateData = { + contactPersonName: val.contactPersonName ?? '', + contactPersonEmail: val.contactPersonEmail ?? '', + strategicDirective: val.strategicDirective ?? '', + title: val.title ?? '', + description: val.description ?? '', + }; + if (id) { + const res = await updateDepartmentMutate({ + pk: id, + data: mutateData, + }); + if (res.data?.updateDepartment?.ok) { + navigate('/departments'); + } else if (res.data?.updateDepartment) { + setError(res.data.updateDepartment.errors); + } + } else { + const res = await createDepartmentMutate({ + data: mutateData, + }); + + if (res.data?.createDepartment.ok) { + navigate('/departments'); + } else if (res.data?.createDepartment?.errors) { + setError(res.data.createDepartment.errors); + } + } + }, + ); + handler(); + }, [setError, validate, id, createDepartmentMutate, updateDepartmentMutate, navigate]); + + const directiveOptions = directive?.strategicDirectives.results.map( + (dir) => ({ + id: dir.id, + name: dir.title, + }), + ) ?? []; + + useEffect(() => { + if (data?.department) { + const { department } = data; + setFieldValue(department.title, 'title'); + setFieldValue(department.contactPersonEmail, 'contactPersonEmail'); + setFieldValue(department.contactPersonName, 'contactPersonName'); + setFieldValue(department.description, 'description'); + setFieldValue(department.strategicDirectiveId ?? '', 'strategicDirective'); + setFieldValue(department.slug ?? '', 'slug'); + } + }, [data, setFieldValue]); + return ( + + + + {(value.createdBy && value.modifiedBy) && ( + +
+ Created by: + {value.createdBy} +
+
+ Modified by: + {value.createdBy} +
+
+ )} + + + +
+ setFieldValue(val, 'description')} + /> +
+ + + + + + + + option.id} + labelSelector={(option) => option.name} + onChange={setFieldValue} + placeholder="Select Directive" + error={error?.strategicDirective} + /> + + + setFieldValue(val || null, 'slug')} + error={error?.slug} + disabled + /> + +
+ +
+
+
+ ); +} + +export default DepartmentForm; diff --git a/app/views/Department/DepartmentForm/styles.module.css b/app/views/Department/DepartmentForm/styles.module.css new file mode 100644 index 0000000..b80caf4 --- /dev/null +++ b/app/views/Department/DepartmentForm/styles.module.css @@ -0,0 +1,37 @@ +.container { + width: 100%; +} +.containerChild { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + gap: var(--go-ui-spacing-sm); + background: var(--go-ui-color-gray-10); + overflow: auto; +} +.containerChild > * { + background: var(--go-ui-color-white); +} + +.inputClassName { + display: flex; + flex-direction: row; + width: 100%; + gap: var(--go-ui-spacing-xl); +} +.fileUpload { + display: flex; + align-items: center; + gap: var(--go-ui-spacing-sm); +} +.submitBtn { + display: flex; + align-items: center; + justify-content: center; + padding: var(--go-ui-spacing-lg) var(--go-ui-spacing-xl); +} + +.inputClassName > * { + flex: 1; +} diff --git a/app/views/Department/DepartmentList/index.tsx b/app/views/Department/DepartmentList/index.tsx new file mode 100644 index 0000000..2b924fd --- /dev/null +++ b/app/views/Department/DepartmentList/index.tsx @@ -0,0 +1,112 @@ +import { + useCallback, + useMemo, +} from 'react'; +import { useNavigate } from 'react-router'; +import { + Button, + Container, + Pager, + Table, +} from '@ifrc-go/ui'; +import { + createElementColumn, + createNumberColumn, + createStringColumn, +} from '@ifrc-go/ui/utils'; + +import TableActions, { TableActionsProps } from '#components/TableAction'; +import { + DepartmentsQuery, + useDeleteDepartmentMutation, + useDepartmentsQuery, +} from '#generated/types/graphql'; +import usePagination from '#hooks/usePagination'; + +import styles from './styles.module.css'; + +type EventListItem = NonNullable['results'][number]; + +function DepartmentList() { + const navigate = useNavigate(); + const { + page, + setPage, + pageSize, + variables, + getFormattedData, + } = usePagination(); + + const [{ fetching, data }, reExecuteQuery] = useDepartmentsQuery({ variables }); + const [{ fetching: deletePending }, deleteDepartment] = useDeleteDepartmentMutation(); + + const tableData = useMemo( + () => getFormattedData(data?.departments.results), + [data, getFormattedData], + ); + + const handleDelete = useCallback( + (id: string, closeModal: () => void) => { + deleteDepartment({ id }).then((resp) => { + if (resp.data?.deleteDepartment) { + reExecuteQuery(); + closeModal(); + } + }); + }, + [deleteDepartment, reExecuteQuery], + ); + const columns = useMemo(() => [ + createNumberColumn('sn', 'S.N.', (item) => item.sn, { columnWidth: 60 }), + createStringColumn('title', 'Title', (dept) => dept.title), + createStringColumn('strategicDirective', 'Strategic Directive', (dept) => dept?.strategicDirective?.title), + createStringColumn('contactPersonName', 'Contact Person Name', (dept) => dept.contactPersonName), + createStringColumn('contactPersonEmail', 'Contact Person Email', (dept) => dept.contactPersonEmail), + createElementColumn( + 'actions', + 'Actions', + TableActions, + (_, datum) => ({ + id: datum.id, + handleConfirmButtonChange: handleDelete, + confirmPending: deletePending, + itemTitle: datum.title, + }), + { columnWidth: 150 }, + ), + ], [handleDelete, deletePending]); + + return ( + navigate('add')}> + Add Department + + )} + footerActions={( + + )} + > +
item.id} + className={styles.table} + columns={columns} + data={tableData} + filtered={false} + pending={fetching} + headerRowClassName={styles.headerRow} + + /> + + ); +} + +export default DepartmentList; diff --git a/app/views/Department/DepartmentList/styles.module.css b/app/views/Department/DepartmentList/styles.module.css new file mode 100644 index 0000000..0a3a86b --- /dev/null +++ b/app/views/Department/DepartmentList/styles.module.css @@ -0,0 +1,17 @@ +.department { + flex: 1; + padding: var(--go-ui-spacing-lg) var(--go-ui-spacing-xl); + .header { + display: flex; + justify-content: space-between; + } + .content { + overflow: auto; + } + + .headerRow { + th { + background: var(--go-ui-color-gray-20); + } + } +} diff --git a/app/views/Department/query.ts b/app/views/Department/query.ts new file mode 100644 index 0000000..2541fee --- /dev/null +++ b/app/views/Department/query.ts @@ -0,0 +1,89 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { gql } from 'urql'; + +const DEPARTMENT_QUERY = gql` + query Departments($pagination: OffsetPaginationInput) { + departments(pagination: $pagination) { + pageInfo { + limit + offset + } + totalCount + results { + contactPersonName + contactPersonEmail + title + id + strategicDirective { + title + } + } + } + } +`; + +const DEPARTMENT_DETAIL = gql` + query DepartmentDetail($id: ID!) { + department(id: $id) { + contactPersonEmail + contactPersonName + id + description + slug + title + strategicDirectiveId + } + } +`; + +const DIRECTIVE = gql` + query Directive { + strategicDirectives { + results { + id + title + } + } + } +`; + +const CREATE_DEPARTMENT_MUTATION = gql` + mutation CreateDepartment($data: DepartmentCreateInput!) { + createDepartment(data: $data) { + ... on DepartmentTypeMutationResponseType { + __typename + errors + ok + } + } + } +`; + +const UPDATE_DEPARTMENT_MUTATION = gql` + mutation UpdateDepartment($pk: ID!, $data: DepartmentUpdateInput!) { + updateDepartment(pk: $pk, data: $data) { + ... on DepartmentTypeMutationResponseType { + __typename + errors + ok + } + } + } +`; + +const DELETE_DEPARTMENT = gql` + mutation DeleteDepartment($id: ID!) { + deleteDepartment(data: { id: $id }) { + ... on OperationInfo { + __typename + messages { + message + } + } + ... on DepartmentType { + id + title + } + } + } +`; From 699f99a0a21dcbbd23581c2a1169a579b72fad4e Mon Sep 17 00:00:00 2001 From: crssstha Date: Fri, 9 Jan 2026 11:56:19 +0545 Subject: [PATCH 04/25] feat(faq): Added Faq listig and edit add form --- app/components/ConfirmationModal/index.tsx | 4 +- app/root/config/routes.tsx | 24 +++ app/views/Blog/BlogForm/styles.module.css | 2 + app/views/Department/DepartmentForm/index.tsx | 2 + .../DepartmentForm/styles.module.css | 2 + app/views/Department/query.ts | 8 + app/views/FAQs/FAQsForm/index.tsx | 191 ++++++++++++++++++ app/views/FAQs/FAQsForm/styles.module.css | 39 ++++ app/views/FAQs/FAQsList/index.tsx | 110 ++++++++++ app/views/FAQs/FAQsList/styles.module.css | 17 ++ app/views/FAQs/query.ts | 78 +++++++ 11 files changed, 475 insertions(+), 2 deletions(-) create mode 100644 app/views/FAQs/FAQsForm/index.tsx create mode 100644 app/views/FAQs/FAQsForm/styles.module.css create mode 100644 app/views/FAQs/FAQsList/index.tsx create mode 100644 app/views/FAQs/FAQsList/styles.module.css create mode 100644 app/views/FAQs/query.ts diff --git a/app/components/ConfirmationModal/index.tsx b/app/components/ConfirmationModal/index.tsx index 8e59aa2..e5a8d95 100644 --- a/app/components/ConfirmationModal/index.tsx +++ b/app/components/ConfirmationModal/index.tsx @@ -24,8 +24,8 @@ function ConfirmationModal(props: Props) { } = props; const confirmationModalDescription = type === 'delete' - ? `Are you sure you want to delete ${itemTitle || 'this item'}? This action cannot be undone.` - : `Are you sure you want to save changes to ${itemTitle || 'this item'}?`; + ? `Are you sure you want to delete ${`"${itemTitle}"` || 'this item'}? This action cannot be undone.` + : `Are you sure you want to save changes to ${`"${itemTitle}"` || 'this item'}?`; return ( import('#views/FAQs/FAQsList'), + visibility: 'is-authenticated', +}; + +const editFaq: RouteConfig = { + index: true, + path: 'faqs/:id/edit', + load: () => import('#views/FAQs/FAQsForm'), + visibility: 'is-authenticated', +}; + +const addFaq: RouteConfig = { + index: true, + path: 'faqs/add', + load: () => import('#views/FAQs/FAQsForm'), + visibility: 'is-authenticated', +}; + const routes = { login, home, @@ -68,6 +89,9 @@ const routes = { department, addDepartment, editDepartment, + faqs, + addFaq, + editFaq, }; export type RouteKeys = keyof typeof routes; diff --git a/app/views/Blog/BlogForm/styles.module.css b/app/views/Blog/BlogForm/styles.module.css index b80caf4..230565c 100644 --- a/app/views/Blog/BlogForm/styles.module.css +++ b/app/views/Blog/BlogForm/styles.module.css @@ -34,4 +34,6 @@ .inputClassName > * { flex: 1; + display: flex; + gap: var(--go-ui-spacing-sm); } diff --git a/app/views/Department/DepartmentForm/index.tsx b/app/views/Department/DepartmentForm/index.tsx index d5b2b56..c8c0646 100644 --- a/app/views/Department/DepartmentForm/index.tsx +++ b/app/views/Department/DepartmentForm/index.tsx @@ -156,6 +156,8 @@ function DepartmentForm() { setFieldValue(department.description, 'description'); setFieldValue(department.strategicDirectiveId ?? '', 'strategicDirective'); setFieldValue(department.slug ?? '', 'slug'); + setFieldValue(`${department.modifiedBy.firstName} ${department.modifiedBy.lastName}`, 'modifiedBy'); + setFieldValue(`${department.createdBy.firstName} ${department.createdBy.lastName}`, 'createdBy'); } }, [data, setFieldValue]); return ( diff --git a/app/views/Department/DepartmentForm/styles.module.css b/app/views/Department/DepartmentForm/styles.module.css index b80caf4..230565c 100644 --- a/app/views/Department/DepartmentForm/styles.module.css +++ b/app/views/Department/DepartmentForm/styles.module.css @@ -34,4 +34,6 @@ .inputClassName > * { flex: 1; + display: flex; + gap: var(--go-ui-spacing-sm); } diff --git a/app/views/Department/query.ts b/app/views/Department/query.ts index 2541fee..0fca55c 100644 --- a/app/views/Department/query.ts +++ b/app/views/Department/query.ts @@ -32,6 +32,14 @@ const DEPARTMENT_DETAIL = gql` slug title strategicDirectiveId + createdBy { + firstName + lastName + } + modifiedBy { + firstName + lastName + } } } `; diff --git a/app/views/FAQs/FAQsForm/index.tsx b/app/views/FAQs/FAQsForm/index.tsx new file mode 100644 index 0000000..86eaf6c --- /dev/null +++ b/app/views/FAQs/FAQsForm/index.tsx @@ -0,0 +1,191 @@ +import { + useCallback, + useEffect, +} from 'react'; +import { + useNavigate, + useParams, +} from 'react-router'; +import { + Button, + Container, + Heading, + NumberInput, + TextArea, + TextInput, +} from '@ifrc-go/ui'; +import { + createSubmitHandler, + getErrorObject, + integerCondition, + ObjectSchema, + PartialForm, + requiredStringCondition, + useForm, +} from '@togglecorp/toggle-form'; + +import FormSection from '#components/FormSection'; +import Page from '#components/Page'; +import { + FaqCreateInput, + useCreateFaqMutation, + useFaqDetailQuery, + useUpdateFaqMutation, +} from '#generated/types/graphql'; + +import styles from './styles.module.css'; + +type PartialFormType = PartialForm & +{ createdBy: string, modifiedBy: string } + +type FormSchema = ObjectSchema; +type FormSchemaFields = ReturnType; + +const EditBlogSchema: FormSchema = { + fields: (): FormSchemaFields => ({ + question: { + required: true, + requiredValidation: requiredStringCondition, + }, + answer: { + required: true, + requiredValidation: requiredStringCondition, + }, + orderIndex: { + required: true, + requiredValidation: integerCondition, + }, + createdBy: { + required: false, + requiredValidation: requiredStringCondition, + }, + modifiedBy: { + required: false, + requiredValidation: requiredStringCondition, + }, + + }), +}; + +const defaultEditFormValue: PartialFormType = { + createdBy: '', + modifiedBy: '', +}; +function FAQsForm() { + const { id } = useParams(); + const navigate = useNavigate(); + const [{ data }] = useFaqDetailQuery({ + variables: { id: id || '' }, pause: !id, + }); + const [{ fetching: createPending }, createFaqMutate] = useCreateFaqMutation(); + const [{ fetching: updatePending }, updateFaqMutate] = useUpdateFaqMutation(); + const { + setFieldValue, + error: formError, + value, + validate, + setError, + } = useForm(EditBlogSchema, { value: defaultEditFormValue }); + + const error = getErrorObject(formError); + + const handleFormSubmit = useCallback(() => { + const handler = createSubmitHandler( + validate, + setError, + async (val) => { + const mutateData = { + answer: val.answer ?? '', + question: val.question ?? '', + orderIndex: val.orderIndex ?? 0, + }; + if (id) { + const res = await updateFaqMutate({ + pk: id, + data: mutateData, + }); + if (res.data?.updateFaq?.ok) { + navigate('/faqs'); + } else if (res.data?.updateFaq.errors) { + setError(res.data.updateFaq.errors); + } + } else { + const res = await createFaqMutate({ + data: mutateData, + }); + + if (res.data?.createFaq.ok) { + navigate('/faqs'); + } else if (res.data?.createFaq?.errors) { + setError(res.data.createFaq.errors); + } + } + }, + ); + handler(); + }, [setError, validate, id, createFaqMutate, updateFaqMutate, navigate]); + + useEffect(() => { + if (data?.faq) { + const { faq } = data; + setFieldValue(faq.answer, 'answer'); + setFieldValue(faq.question, 'question'); + setFieldValue(faq.orderIndex, 'orderIndex'); + setFieldValue(`${faq.modifiedBy.firstName} ${faq.modifiedBy.lastName}`, 'modifiedBy'); + setFieldValue(`${faq.createdBy.firstName} ${faq.createdBy.lastName}`, 'createdBy'); + } + }, [data, setFieldValue]); + return ( + + + + {(value.createdBy && value.modifiedBy) && ( + +
+ Created by: + {value.createdBy} +
+
+ Modified by: + {value.createdBy} +
+
+ )} + +