diff --git a/.gitignore b/.gitignore index 7ac5c0ce6..05ca19583 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ deepl.env.mjs deepl.env.*.mjs !deepl.env.example.mjs +forus-backend + # Compiled source # ################### *.com diff --git a/react/assets/forus-platform/scss/_common/blocks/block-attachments-list.scss b/react/assets/forus-platform/scss/_common/blocks/block-attachments-list.scss index 6c506ebfe..914932f0f 100644 --- a/react/assets/forus-platform/scss/_common/blocks/block-attachments-list.scss +++ b/react/assets/forus-platform/scss/_common/blocks/block-attachments-list.scss @@ -1,6 +1,7 @@ .block.block-attachments-list { display: flex; flex-direction: column; + width: 100%; gap: 10px; .attachment-item { @@ -8,31 +9,34 @@ display: flex; border: 1px solid #d4d9dd; flex-direction: row; - padding: 5px 15px 5px 10px; + padding: 5px 10px; border-radius: var(--border-radius); gap: 10px; + align-items: center; + min-height: 32px; .attachment-icon { display: flex; flex-direction: row; gap: 10px; + align-items: center; .mdi { font-size: 18px; - line-height: 25px; + line-height: 18px; color: #004195; } .attachment-size { color: #646f79; - font: 600 13px/24px var(--base-font); + font: 600 13px/18px var(--base-font); flex: 1 0 60px; } } .attachment-name { color: #262626; - font: 600 13px/24px var(--base-font); + font: 600 13px/18px var(--base-font); flex: 1 1 auto; min-width: 0; word-wrap: break-word; @@ -40,25 +44,39 @@ } .attachment-date { - font: 400 13px/24px var(--base-font); + font: 400 13px/18px var(--base-font); } - .attachment-preview { + .attachment-actions { display: flex; flex-direction: row; - color: #262626; - font: 500 12px/24px var(--base-font); - gap: 5px; + align-items: center; + gap: 6px; + } + + .attachment-action { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border: none; + background: transparent; + padding: 0; + cursor: pointer; + color: #646f79; + transition: color 0.15s ease-in-out; .mdi { font-size: 16px; - line-height: 24px; - height: 24px; - color: #555; + line-height: 18px; + height: 18px; + } - &:hover { - color: var(--color-primary); - } + &:hover, + &:focus-visible { + color: var(--color-primary); + outline: none; } } } diff --git a/react/assets/forus-platform/scss/_common/blocks/block-file-uploader.scss b/react/assets/forus-platform/scss/_common/blocks/block-file-uploader.scss new file mode 100644 index 000000000..7c5c604e0 --- /dev/null +++ b/react/assets/forus-platform/scss/_common/blocks/block-file-uploader.scss @@ -0,0 +1,211 @@ +.block.block-file-uploader { + display: flex; + flex-direction: column; + gap: 10px; + + .uploader-droparea { + display: flex; + flex-direction: column; + gap: 15px; + width: 100%; + background: #f8f8f8; + padding: 25px 30px; + text-align: center; + border: 1px dashed #e5e6ec; + border-radius: calc(var(--border-radius) / 2); + color: #515152; + + .droparea-icon { + margin: 0 auto; + color: inherit; + display: flex; + justify-content: center; + + .mdi { + width: 50px; + height: 50px; + font-size: 24px; + padding: 10px; + flex-direction: column; + justify-content: center; + border: 1px solid var(--color-primary); + color: var(--color-primary); + margin: 0 auto; + } + } + + .droparea-title { + font: 500 16px/26px var(--base-font); + justify-content: center; + color: inherit; + + strong { + font-weight: 700; + } + + small { + font-size: 14px; + } + + &.droparea-title-required { + strong { + &:after { + content: '*'; + color: #ff5548; + font: inherit; + margin-left: 2px; + } + } + } + } + + .droparea-button { + display: flex; + justify-content: center; + } + + .droparea-size { + display: flex; + justify-content: center; + font: 600 13px/18px var(--base-font); + color: inherit; + } + + .droparea-hidden-input { + display: none; + } + + &.is-dragover { + background: white; + border-color: silver; + } + + &:last-child { + margin-bottom: 0; + } + } + + .uploader-files { + display: flex; + flex-direction: column; + gap: 10px; + + .uploader-files-title { + display: flex; + font: 700 16px/24px var(--base-font); + flex-direction: row; + padding: 0 0 5px; + gap: 6px; + + .uploader-files-title-count { + padding: 2px 6px; + background: #e5e5e5; + border-radius: var(--border-radius); + font: 600 14px/20px var(--base-font); + } + } + } + + &.block-file-uploader-compact { + max-width: 100%; + display: flex; + flex-direction: column-reverse; + align-items: flex-start; + + .droparea-icon, + .droparea-title, + .droparea-hidden-input { + display: none; + } + + .uploader-droparea { + display: flex; + padding: 0; + background: none; + border: none; + flex-direction: row; + justify-content: flex-start; + align-items: center; + + .droparea-max-limit { + color: #353535; + font-size: 11px; + font-weight: 400; + line-height: 11px; + font-family: var(--base-font), serif; + + &::before { + content: '*'; + color: #ed133c; + padding-right: 3px; + } + } + } + + .uploader-files { + max-width: 100%; + } + } + + &.block-file-uploader-inline { + max-width: 100%; + display: flex; + + .droparea-icon, + .droparea-title, + .droparea-hidden-input { + display: none; + } + + .uploader-droparea { + display: flex; + padding: 0; + background: none; + border: none; + flex-direction: row; + justify-content: flex-start; + align-items: center; + + .droparea-max-limit { + color: #353535; + font-size: 11px; + font-weight: 400; + line-height: 11px; + font-family: var(--base-font), serif; + + &::before { + content: '*'; + color: #ed133c; + padding-right: 3px; + } + } + } + + .uploader-files { + padding: 15px 20px; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + width: 100%; + flex-direction: column; + gap: 5px; + background-color: #fbfbfb; + } + } + + .file-item-error { + font: 400 13px/20px var(--base-font); + } + + @media (max-width: 991px) { + .uploader-droparea { + border: none; + background: none; + padding: 0 0; + + .droparea-icon, + .droparea-title { + display: none; + } + } + } +} diff --git a/react/assets/forus-platform/scss/_common/dashboard.scss b/react/assets/forus-platform/scss/_common/dashboard.scss index ca8ebf181..44d2756d8 100644 --- a/react/assets/forus-platform/scss/_common/dashboard.scss +++ b/react/assets/forus-platform/scss/_common/dashboard.scss @@ -103,6 +103,7 @@ @import 'blocks/block-attachments-list.scss'; @import 'blocks/block-request-clarification.scss'; @import 'blocks/block-product-media-uploader.scss'; +@import 'blocks/block-file-uploader'; // Select controls @import 'select-controls/select-control-funds'; @@ -2311,72 +2312,6 @@ body { } } - &.card-block-requests { - box-shadow: 0 2px 5px rgba(25, 39, 52, 0.11); - border-radius: var(--border-radius); - margin: 20px 0; - color: #646f79; - background-color: #fff; - border: 1px solid #d4d9dd; - - .card-section { - padding: 10px 15px; - } - - .card-heading { - flex: 1; - margin: 0; - align-self: center; - font: 600 13px/13px var(--base-font); - letter-spacing: -0.2px; - color: #404040; - cursor: pointer; - } - - .button { - font: 600 12px/12px var(--base-font); - color: #134478; - } - - .mdi { - color: #134478; - margin-right: 6px; - } - - &.card-block-requests-danger { - color: #404040; - background-color: #fff5f0; - border: 1px solid #e63b3b; - - .mdi { - color: #e63b3b; - } - - .button { - .mdi { - margin-left: 3px; - color: #134478; - } - } - } - - &.card-block-requests-warning { - background-color: #fcfaf6; - border: 1px solid #f8e39a; - color: #404040; - - .mdi { - color: #ffb05a; - } - - .button { - .mdi { - color: #ffb05a; - } - } - } - } - &.card-block-request-record { .card-header { .card-title { diff --git a/react/assets/forus-webshop/scss/style-webshop-eemsdelta-vars.scss b/react/assets/forus-webshop/scss/style-webshop-eemsdelta-vars.scss index da485b297..07ce73978 100644 --- a/react/assets/forus-webshop/scss/style-webshop-eemsdelta-vars.scss +++ b/react/assets/forus-webshop/scss/style-webshop-eemsdelta-vars.scss @@ -25,12 +25,12 @@ $primaryColorLight: #0d4379; --navbar-menu-gap: 22px; --navbar-menu-order: 1; - --navbar-menu-background: #f5f5f5; + --navbar-menu-background: #ffffff; --navbar-search-gap: 20px; --navbar-search-order: 2; --navbar-search-padding: 20px 0px; - --navbar-search-background: #f5f5f5; + --navbar-search-background: #ffffff; --navbar-search-border-bottom: 1px solid var(--border-color); --navbar-menu-item-font: 400 18px/21px var(--base-font); @@ -62,17 +62,17 @@ $primaryColorLight: #0d4379; // navbar (auth) (default button) --tc-auth-menu: #ffffff; - --tc-auth-btn: #ffffff; - --btn-auth-bg: #0d4379; - --btn-auth-border: #0d4379; + --tc-auth-btn: #609609; + --btn-auth-bg: #ffffff; + --btn-auth-border: #609609; --auth-avater-bg: #305dfb; --auth-avater-svg: #fff; // navbar (start modal primary) --tc-start-btn: #ffffff; - --btn-start-bg: #609609; - --btn-start-btn: #609609; - --btn-start-border: transparent; + --btn-start-bg: #0d4379; + --btn-start-btn: #0d4379; + --btn-start-border: #0d4379; // header background --bg-values: url('../img/splash-top-b.jpg') no-repeat center 100%; @@ -119,8 +119,8 @@ $primaryColorLight: #0d4379; --footer-copyright-color: var(--footer_color_link); --footer-copyright-font: 400 14px/30px var(--base-font); - --footer-copyright-background: #0d4379; - --footer-copyright-border-color: var(--border-color); + --footer-copyright-background: #0A2E51; + --footer-copyright-border-color: #0A2E51; --footer-copyright-padding: 40px; // not displayed diff --git a/react/assets/forus-webshop/scss/style-webshop-hartvanwestbrabant-vars.scss b/react/assets/forus-webshop/scss/style-webshop-hartvanwestbrabant-vars.scss index 03b098424..6a023f7b5 100644 --- a/react/assets/forus-webshop/scss/style-webshop-hartvanwestbrabant-vars.scss +++ b/react/assets/forus-webshop/scss/style-webshop-hartvanwestbrabant-vars.scss @@ -98,7 +98,7 @@ $primaryColorLight: #1c7878; // heading font --heading-font-family: var(--base-font); --heading-font: 700 40px/60px var(--heading-font-family); - --heading-color: #24A1A1; + --heading-color: #1a1a1a; --heading-pane-font: 700 22px/30px var(--heading-font-family); // base color @@ -118,10 +118,10 @@ $primaryColorLight: #1c7878; --footer_social_border_color: var(--footer_color); --footer_social_border_radius: var(--border-radius); - --footer-copyright-color: var(--footer_color_link); + --footer-copyright-color: #1a1a1a; --footer-copyright-font: 400 14px/30px var(--base-font); - --footer-copyright-background: #1c7878; - --footer-copyright-border-color: var(--border-color); + --footer-copyright-background: #ffffff; + --footer-copyright-border-color: #ffffff; --footer-copyright-padding: 40px; // not displayed diff --git a/react/assets/forus-webshop/scss/style-webshop-heumen-vars.scss b/react/assets/forus-webshop/scss/style-webshop-heumen-vars.scss index 622315b41..3f3973710 100644 --- a/react/assets/forus-webshop/scss/style-webshop-heumen-vars.scss +++ b/react/assets/forus-webshop/scss/style-webshop-heumen-vars.scss @@ -25,7 +25,7 @@ $primaryColorLight: #22398e; --navbar-menu-gap: 22px; --navbar-menu-order: 1; - --navbar-menu-background: #ececec; + --navbar-menu-background: #ffffff; --navbar-search-gap: 20px; --navbar-search-order: 2; @@ -69,10 +69,10 @@ $primaryColorLight: #22398e; --auth-avater-svg: #fff; // navbar (start modal primary) - --tc-start-btn: #fff; - --btn-start-bg: #61ba90; - --btn-start-btn: #61ba90; - --btn-start-border: transparent; + --tc-start-btn: #22398e; + --btn-start-bg: #ffffff; + --btn-start-btn: #22398e; + --btn-start-border: #22398e; // header background --bg-values: url("../img/splash-top-b.jpg") no-repeat center 100%; @@ -119,8 +119,8 @@ $primaryColorLight: #22398e; --footer-copyright-color: var(--footer_color_link); --footer-copyright-font: 400 14px/30px var(--base-font); - --footer-copyright-background: #22398e; - --footer-copyright-border-color: var(--border-color); + --footer-copyright-background: #061756; + --footer-copyright-border-color: #061756; --footer-copyright-padding: 40px; // not displayed diff --git a/react/assets/forus-webshop/scss/style-webshop-schagen-vars.scss b/react/assets/forus-webshop/scss/style-webshop-schagen-vars.scss index 791dd869f..d1ac9d5a0 100644 --- a/react/assets/forus-webshop/scss/style-webshop-schagen-vars.scss +++ b/react/assets/forus-webshop/scss/style-webshop-schagen-vars.scss @@ -75,7 +75,7 @@ $primaryColorLight: #1c8fd7; // navbar (start modal primary) --tc-start-btn: #fff; - --btn-start-bg: #d13e41; + --btn-start-bg: #ba2f2f; --btn-start-btn: #305dfb; --btn-start-border: transparent; @@ -122,9 +122,9 @@ $primaryColorLight: #1c8fd7; --footer_social_border_color: var(--footer_color); --footer_social_border_radius: var(--border-radius); - --footer-copyright-color: var(--footer_color_link); + --footer-copyright-color: #545457; --footer-copyright-font: 400 16px/30px var(--base-font); - --footer-copyright-background: #306fb3; + --footer-copyright-background: #ffffff; --footer-copyright-border-color: transparent; --footer-copyright-padding: 25px; diff --git a/react/src/dashboard/components/elements/FileAttachmentsList.tsx b/react/src/dashboard/components/elements/FileAttachmentsList.tsx new file mode 100644 index 000000000..31a9230b9 --- /dev/null +++ b/react/src/dashboard/components/elements/FileAttachmentsList.tsx @@ -0,0 +1,72 @@ +import React, { useCallback } from 'react'; +import File from '../../props/models/File'; +import { useFileService } from '../../services/FileService'; +import { useFundRequestValidatorService } from '../../services/FundRequestValidatorService'; +import useFilePreview from '../../services/helpers/useFilePreview'; + +export default function FileAttachmentsList({ attachments }: { attachments: Array<{ file: File; date?: string }> }) { + const filePreview = useFilePreview(); + + const fileService = useFileService(); + const fundRequestService = useFundRequestValidatorService(); + + const hasFilePreview = useCallback((file) => fundRequestService.hasFilePreview(file), [fundRequestService]); + + const downloadFile = useCallback( + (e: React.MouseEvent, file: File) => { + e?.preventDefault(); + e?.stopPropagation(); + + fileService + .download(file) + .then((res) => fileService.downloadFile(file.original_name, res.data, res.headers['content-type'])) + .catch(console.error); + }, + [fileService], + ); + + const previewFile = useCallback( + (e: React.MouseEvent, file: File) => { + e?.preventDefault(); + e?.stopPropagation(); + + filePreview(file); + }, + [filePreview], + ); + + return ( +
+ {attachments.map((attachment) => ( +
+
+
+
{attachment.file.size}
+
+
{attachment.file.original_name}
+
{attachment.date || ''}
+
+ + {hasFilePreview(attachment.file) && ( + + )} +
+
+ ))} +
+ ); +} diff --git a/react/src/dashboard/components/elements/file-uploader/FileUploaderItemView.tsx b/react/src/dashboard/components/elements/file-uploader/FileUploaderItemView.tsx new file mode 100644 index 000000000..1fd3b51ab --- /dev/null +++ b/react/src/dashboard/components/elements/file-uploader/FileUploaderItemView.tsx @@ -0,0 +1,117 @@ +import React, { Fragment, useCallback, useMemo } from 'react'; +import { useFileService } from '../../../services/FileService'; +import { ResponseError } from '../../../props/ApiResponses'; +import useOpenModal from '../../../../dashboard/hooks/useOpenModal'; +import ModalImagePreview from '../../modals/ModalImagePreview'; +import ModalPdfPreview from '../../modals/ModalPdfPreview'; +import { FileUploaderItem } from '../../../../webshop/components/elements/file-uploader/FileUploader'; + +export default function FileUploaderItemView({ + item, + hidePreviewButton, + hideDownloadButton, + readOnly, + removeFile, +}: { + item: FileUploaderItem; + hidePreviewButton?: boolean; + hideDownloadButton?: boolean; + readOnly?: boolean; + removeFile?: (file: FileUploaderItem) => void; +}) { + const openModal = useOpenModal(); + const fileService = useFileService(); + + const name = useMemo(() => { + return item.file?.name || item.file_data?.original_name || ''; + }, [item.file?.name, item.file_data?.original_name]); + + const previewFile = useCallback( + (e: React.MouseEvent, file: Partial) => { + e.preventDefault(); + e.stopPropagation(); + + if (file.file_data.ext == 'pdf') { + fileService + .downloadBlob(file.file_data) + .then((res) => { + openModal((modal) => ); + }) + .catch((err: ResponseError) => console.error(err)); + } else if (['png', 'jpeg', 'jpg'].includes(file.file_data.ext)) { + openModal((modal) => ); + } + }, + [fileService, openModal], + ); + + const downloadFile = useCallback( + (e: React.MouseEvent, file: Partial) => { + e.preventDefault(); + e.stopPropagation(); + + fileService.download(file.file_data).then((res) => { + fileService.downloadFile(file.file_data.original_name, res.data); + }, console.error); + }, + [fileService], + ); + + return ( + +
+
+
+
+
{item.file_data?.size || ' - kB'}
+
+
{name}
+
+
+ {!item.uploading && !hideDownloadButton && ( + + )} + + {item.has_preview && !hidePreviewButton && ( + + )} + + {!readOnly && ( +
+ +
+ )} +
+
+
+ +
+ {item?.error?.map((error, index) => ( +
+ {error} +
+ ))} +
+ + ); +} diff --git a/react/src/dashboard/components/elements/info-box/InfoBox.tsx b/react/src/dashboard/components/elements/info-box/InfoBox.tsx index abed210fe..5f8a43af2 100644 --- a/react/src/dashboard/components/elements/info-box/InfoBox.tsx +++ b/react/src/dashboard/components/elements/info-box/InfoBox.tsx @@ -6,14 +6,17 @@ export default function InfoBox({ borderType = 'dashed', children, iconColor = 'primary', + dusk = null, }: { type?: 'default' | 'primary' | 'warning'; borderType?: 'dashed' | 'none'; children: ReactNode | ReactNode[]; iconColor?: 'primary' | 'warning'; + dusk?: string; }) { return (
void; className?: string; }) { - const [dateMin] = useState(addDays(new Date(), 1)); - const translate = useTranslate(); const pushSuccess = usePushSuccess(); const setProgress = useSetProgress(); const pushApiError = usePushApiError(); - const fundUnsubscribeService = useFundUnsubscribeService(); + const providerFundService = useProviderFundService(); - const form = useFormBuilder( - { - unsubscribe_at: null, - note: '', - }, - (values) => { - setProgress(0); + const [errorMessage, setErrorMessage] = React.useState(null); - fundUnsubscribeService - .store(organization.id, { fund_provider_id: providerFund.id, ...values }) - .then(() => { - pushSuccess('Gelukt!', 'Verzoek afmelding verstuurd.'); - modal.close(); - onUnsubscribe?.(); - }) - .catch((err: ResponseError) => { - pushApiError(err); - form.setErrors(err.data.errors); - }) - .finally(() => { - setProgress(100); - form.setIsLocked(false); - }); - }, - ); + const form = useFormBuilder({ note: '' }, (values) => { + setProgress(0); + setErrorMessage(null); + + providerFundService + .unsubscribe(organization.id, providerFund.id, values) + .then(() => { + pushSuccess('Gelukt!', 'Verzoek afmelding verstuurd.'); + modal.close(); + onUnsubscribe?.(); + }) + .catch((err: ResponseError) => { + pushApiError(err); + form.setErrors(err.data.errors); + + if (!err.data?.errors && err.data?.message) { + setErrorMessage(err.data.message); + } + }) + .finally(() => { + setProgress(100); + form.setIsLocked(false); + }); + }); return (
-
Request unsubscription
+
Afmelding voor de regeling
@@ -91,40 +89,46 @@ export default function ModalFundUnsubscribe({ Weet u zeker dat u zich wilt afmelden bij {providerFund.fund.name}?
- Als alles duidelijk is kunt u het onderstaande formulier invullen. + Optioneel kunt u een reden opgeven, zodat de gemeente weet waarom u zich heeft + afgemeld.
- - form.update({ unsubscribe_at: dateFormat(date) })} - /> - -
-
- +