diff --git a/Dockerfile b/Dockerfile index 49db9de..a788998 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-bookworm AS dev +FROM node:22-bookworm AS dev RUN apt-get update -y \ && apt-get install -y --no-install-recommends \ @@ -51,4 +51,4 @@ COPY --from=nginx-build /code/build /code/build # NOTE: Used by apply-config.sh ENV APPLY_CONFIG__SOURCE_DIRECTORY=/code/build/ ENV APPLY_CONFIG__DESTINATION_DIRECTORY=/usr/share/nginx/html/ -ENV APPLY_CONFIG__OVERWRITE_DESTINATION=true \ No newline at end of file +ENV APPLY_CONFIG__OVERWRITE_DESTINATION=true diff --git a/app/components/EditDeleteActions/index.tsx b/app/components/EditDeleteActions/index.tsx new file mode 100644 index 0000000..50ed247 --- /dev/null +++ b/app/components/EditDeleteActions/index.tsx @@ -0,0 +1,58 @@ +import { useCallback } from 'react'; +import { + DeleteBinLineIcon, + EditTwoLineIcon, +} from '@ifrc-go/icons'; +import { + Button, + ConfirmButton, + TableActions, +} from '@ifrc-go/ui'; + +import useRouting, { RoutesMap } from '#hooks/useRouting'; + +export interface EditDeleteActionsProps { + id: string ; + onDelete: (id: string) => void; + itemTitle: string; + to: keyof RoutesMap ; +} + +function EditDeleteActions(props: EditDeleteActionsProps) { + const { + id, + onDelete, + itemTitle, + to, + } = props; + + const navigate = useRouting(); + + const handleEditClick = useCallback(() => { + navigate(to, { id }); + }, [navigate, to, id]); + + return ( + + + + + + + ); +} + +export default EditDeleteActions; diff --git a/app/components/EditDeleteActions/styles.module.css b/app/components/EditDeleteActions/styles.module.css new file mode 100644 index 0000000..66f30f8 --- /dev/null +++ b/app/components/EditDeleteActions/styles.module.css @@ -0,0 +1,4 @@ +.tableAction { + display: flex; + gap: var(--go-ui-spacing-md); +} diff --git a/app/components/FileUpload/index.tsx b/app/components/FileUpload/index.tsx new file mode 100644 index 0000000..5975e4b --- /dev/null +++ b/app/components/FileUpload/index.tsx @@ -0,0 +1,91 @@ +import { + Activity, + useCallback, +} from 'react'; +import { + DeleteBinLineIcon, + UploadFillIcon, +} from '@ifrc-go/icons'; +import { + IconButton, + InputError, + ListView, + RawFileInput, + SingleRawFileInputProps, +} from '@ifrc-go/ui'; +import { + isDefined, + isNotDefined, +} from '@togglecorp/fujs'; + +interface Props { + name: NAME; + label?: string; + value?: File; + accept?: string; + error?: string; + onChange: (value: File | undefined, name: NAME) => void; +} + +function FileUpload(props: Props) { + const { + value, + onChange, + name, + accept, + error, + label = 'Upload', + } = props; + + const handleClearButtonClick = useCallback(() => { + onChange(undefined, name); + }, [onChange, name]); + + const handleChange = useCallback['onChange']>((file) => { + if (isNotDefined(file)) { + return; + } + onChange(file, name); + }, [onChange, name]); + + return ( + <> + + } + > + {label} + +

+ {value?.name ? ( + value.name + ) : 'No document selected'} +

+ + + + + +
+ + + {error} + + + + ); +} + +export default FileUpload; diff --git a/app/components/MarkdownEditor/index.tsx b/app/components/MarkdownEditor/index.tsx new file mode 100644 index 0000000..1feb777 --- /dev/null +++ b/app/components/MarkdownEditor/index.tsx @@ -0,0 +1,115 @@ +import '@mdxeditor/editor/style.css'; + +import { + Activity, + memo, + useEffect, + useMemo, + useRef, +} from 'react'; +import { + InputError, + ListView, +} from '@ifrc-go/ui'; +import { + BlockTypeSelect, + BoldItalicUnderlineToggles, + CreateLink, + headingsPlugin, + imagePlugin, + InsertImage, + linkDialogPlugin, + linkPlugin, + listsPlugin, + ListsToggle, + markdownShortcutPlugin, + MDXEditor, + MDXEditorMethods, + quotePlugin, + thematicBreakPlugin, + toolbarPlugin, + UndoRedo, +} from '@mdxeditor/editor'; + +import useDebounce from '#hooks/useDebounce'; + +import styles from './styles.module.css'; + +interface Props{ + value?: string; + onChange: ( + value: string | undefined, + ) => void; + error?: string; + placeholder?:string +} + +function ToolbarContents() { + return ( + <> + + + + + + + + ); +} + +function MarkdownEditor(props: Props) { + const { + value = '', + onChange, + placeholder = 'Start writing here...', + error, + } = props; + + const ref = useRef(null); + const prevValueRef = useRef(value); + + const debouncedSearch = useDebounce(onChange, 300); + + useEffect(() => { + if (ref.current && !prevValueRef.current) { + ref.current.setMarkdown(value); + prevValueRef.current = value; + } + }, [value]); + + const plugins = useMemo(() => [ + headingsPlugin(), + listsPlugin({ enableOrdered: true, enableUnordered: true }), + imagePlugin(), + linkPlugin(), + linkDialogPlugin(), + quotePlugin(), + thematicBreakPlugin(), + markdownShortcutPlugin(), + toolbarPlugin({ + toolbarClassName: 'my-classname', + toolbarContents: ToolbarContents, + }), + ], []); + + return ( + +
+ +
+ + + {error} + + +
+ ); +} + +export default memo(MarkdownEditor); diff --git a/app/components/MarkdownEditor/styles.module.css b/app/components/MarkdownEditor/styles.module.css new file mode 100644 index 0000000..5f57e4d --- /dev/null +++ b/app/components/MarkdownEditor/styles.module.css @@ -0,0 +1,37 @@ +.editor { + z-index: 0; + width: 100%; + min-height: var(--go-ui-width-min-modal); + + >* { + flex: 1; + } +} + +.editor p, +h1, +h2, +h3, +h4, +h5, +h6 { + margin: var(--go-ui-spacing-md) 0; +} + +.editor ul, +.editor ol { + padding-left: var(--go-ui-spacing-xl); +} + +.editor li { + margin: 4px 0; +} + +.editor a { + text-decoration: underline; + color: var(--go-ui-color-dark-blue-20); + + &:hover { + color: var(--go-ui-color-dark-blue-30); + } +} \ No newline at end of file diff --git a/app/components/Navbar/index.tsx b/app/components/Navbar/index.tsx index bc03159..f55c240 100644 --- a/app/components/Navbar/index.tsx +++ b/app/components/Navbar/index.tsx @@ -2,7 +2,6 @@ import React, { use, useCallback, } from 'react'; -import { useNavigate } from 'react-router'; import { Button, DropdownMenu, @@ -13,6 +12,7 @@ import { gql } from 'urql'; import UserContext from '#contexts/UserContext'; import { useLogoutMutation } from '#generated/types/graphql'; import useAlert from '#hooks/useAlert'; +import useRouting from '#hooks/useRouting'; import styles from './styles.module.css'; @@ -26,7 +26,7 @@ const LOGOUT = gql` function Navbar() { const { user, setUser } = use(UserContext); const alert = useAlert(); - const navigate = useNavigate(); + const navigate = useRouting(); const [{ fetching: pendingLogout }, triggerLogout] = useLogoutMutation(); @@ -35,36 +35,43 @@ function Navbar() { const logoutResponse = res.data?.logout; if (logoutResponse) { setUser(undefined); - navigate('/login'); + navigate('login'); alert.show('Logout Successful', { variant: 'success' }); } }, [navigate, triggerLogout, setUser, alert]); return (