From 883a07c6cc43049f1bee39fab3d9a2119296f9d6 Mon Sep 17 00:00:00 2001 From: Yaroslav Kosterin Date: Fri, 27 Feb 2026 15:28:45 +0200 Subject: [PATCH 1/9] remove image placeholder in cms blocks --- .../scss/includes/blocks/block-cms-funds.scss | 20 +++++++++---------- .../elements/cms-blocks/CmsBlocksItem.tsx | 14 +++++-------- .../elements/cms-blocks/CmsBlocksItemLink.tsx | 14 +++++-------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss b/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss index 7bc9760f6..26abddcea 100644 --- a/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss +++ b/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss @@ -7,12 +7,13 @@ .fund-item { width: 100%; display: flex; + gap: 100px; padding: 0 0 40px; border-bottom: 1px solid var(--border-color); transition: box-shadow 0.4s; .fund-media { - width: 52%; + width: 42%; order: 1; img { @@ -22,13 +23,12 @@ } .fund-information { - width: 50%; - padding-left: 100px; text-align: left; order: 2; display: flex; flex-direction: column; align-self: center; + flex: 1; .fund-label { margin: 0 0 5px; @@ -70,8 +70,6 @@ .fund-information { order: 1; - padding-left: 0; - padding-right: 100px; } } @@ -96,6 +94,7 @@ .fund-item { display: flex; + gap: 0; flex-wrap: wrap; flex-direction: column; flex: 0 1 calc((100% - (calc(var(--gap) * 2))) / 3); @@ -138,8 +137,7 @@ .fund-description { margin: 0 0 0; display: flex; - flex-grow: 1; - + .block.block-markdown ul, .block.block-markdown p { &:last-child { margin-bottom: 0; @@ -184,8 +182,11 @@ } &.block-cms-funds-2-in-row { + --gap: 60px; + gap: var(--gap); + .fund-item { - width: calc(calc(100% / 2) - var(--padding)); + flex: 0 1 calc((100% - (calc(var(--gap) * 2))) / 2); } } } @@ -195,6 +196,7 @@ gap: 30px; .fund-item { + gap: 0; padding: 0 0 30px; flex-direction: column; @@ -205,7 +207,6 @@ .fund-information { width: 100%; - padding: 0; .fund-title { font: 400 20px/26px var(--base-font); @@ -238,7 +239,6 @@ .fund-information { order: 2; - padding: 0; } } } diff --git a/react/src/webshop/components/elements/cms-blocks/CmsBlocksItem.tsx b/react/src/webshop/components/elements/cms-blocks/CmsBlocksItem.tsx index 953ebb4c1..fee5138b7 100644 --- a/react/src/webshop/components/elements/cms-blocks/CmsBlocksItem.tsx +++ b/react/src/webshop/components/elements/cms-blocks/CmsBlocksItem.tsx @@ -1,7 +1,6 @@ import React from 'react'; import Markdown from '../markdown/Markdown'; import ImplementationPageBlock from '../../../props/models/ImplementationPageBlock'; -import useAssetUrl from '../../../hooks/useAssetUrl'; import Label from '../label/Label'; export default function CmsBlocksItem({ @@ -11,16 +10,13 @@ export default function CmsBlocksItem({ block: ImplementationPageBlock; blocksPerRow: number; }) { - const assetUrl = useAssetUrl(); - return (
-
- -
+ {block.media?.sizes?.public && ( +
+ +
+ )}
{block.title &&

{block.title}

} {block.label && ( diff --git a/react/src/webshop/components/elements/cms-blocks/CmsBlocksItemLink.tsx b/react/src/webshop/components/elements/cms-blocks/CmsBlocksItemLink.tsx index 365901650..6c2fc3ebf 100644 --- a/react/src/webshop/components/elements/cms-blocks/CmsBlocksItemLink.tsx +++ b/react/src/webshop/components/elements/cms-blocks/CmsBlocksItemLink.tsx @@ -1,24 +1,20 @@ import React from 'react'; import Markdown from '../markdown/Markdown'; import ImplementationPageBlock from '../../../props/models/ImplementationPageBlock'; -import useAssetUrl from '../../../hooks/useAssetUrl'; import Label from '../label/Label'; export default function CmsBlocksItemLink({ block }: { block: ImplementationPageBlock }) { - const assetUrl = useAssetUrl(); - return ( -
- -
+ {block.media?.sizes?.public && ( +
+ +
+ )}
{block.title &&

{block.title}

} {block.label && ( From c8c7303eca0cc8b37be0e768914d5fe9384110a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:52:52 +0000 Subject: [PATCH 2/9] Bump the npm_and_yarn group across 1 directory with 2 updates Bumps the npm_and_yarn group with 2 updates in the / directory: [ajv](https://github.com/ajv-validator/ajv) and [minimatch](https://github.com/isaacs/minimatch). Updates `ajv` from 6.12.6 to 6.14.0 - [Release notes](https://github.com/ajv-validator/ajv/releases) - [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v6.14.0) Updates `minimatch` from 3.1.2 to 3.1.5 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: ajv dependency-version: 6.14.0 dependency-type: indirect dependency-group: npm_and_yarn - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] --- package-lock.json | 61 ++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 80f2fe463..b972a9fc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2014,9 +2014,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3823,12 +3823,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3901,12 +3902,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4034,12 +4036,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4798,9 +4801,10 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7511,10 +7515,11 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -10117,9 +10122,10 @@ "dev": true }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -13540,10 +13546,11 @@ } }, "node_modules/url-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", From 3d8b7cad3bdf039f1d6776587c5c48d3cd74d2f6 Mon Sep 17 00:00:00 2001 From: Yaroslav Kosterin Date: Tue, 3 Mar 2026 16:20:13 +0200 Subject: [PATCH 3/9] add share voucher from sponsor dashboard --- .../vouchers-view/VouchersViewComponent.tsx | 44 +++++++++++++++++++ .../i18n/nl/i18n-modals-danger_zone.js | 2 + .../danger-zone/send-voucher-by-email.js | 8 ++++ .../i18n/nl/pages/system-notifications.js | 20 ++++++++- react/src/dashboard/i18n/nl/pages/vouchers.js | 1 + .../src/dashboard/services/VoucherService.ts | 2 +- 6 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 react/src/dashboard/i18n/nl/modals/danger-zone/send-voucher-by-email.js diff --git a/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx b/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx index 74d474510..3368da401 100644 --- a/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx +++ b/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx @@ -269,6 +269,41 @@ export default function VouchersViewComponent() { [activeOrganization.id, openModal, pushApiError, pushSuccess, setProgress, translate, voucher, voucherService], ); + const sendVoucherByEmailToIdentity = useCallback(() => { + openModal((modal) => ( + { + modal.close(); + + voucherService + .sendToEmail(activeOrganization.id, voucher.id) + .then(() => pushSuccess('Opgeslagen!')) + .catch(pushApiError) + .finally(() => setProgress(100)); + }, + text: translate('modals.danger_zone.send_voucher_by_email.buttons.confirm'), + }} + /> + )); + }, [ + activeOrganization.id, + openModal, + pushApiError, + pushSuccess, + setProgress, + translate, + voucher?.id, + voucherService, + ]); + useEffect(() => { fetchVoucher(); }, [fetchVoucher]); @@ -321,6 +356,15 @@ export default function VouchersViewComponent() { {hasPermission(activeOrganization, Permission.MANAGE_VOUCHERS) && (
+ {voucher.granted && !voucher?.external && voucher?.fund?.show_qr_code && ( +
+ + {translate('vouchers.labels.send_to_email')} +
+ )} + {showMakeTransactionButton && fund?.allow_voucher_top_ups && (
diff --git a/react/src/dashboard/i18n/nl/i18n-modals-danger_zone.js b/react/src/dashboard/i18n/nl/i18n-modals-danger_zone.js index 81d590896..f4001f71c 100644 --- a/react/src/dashboard/i18n/nl/i18n-modals-danger_zone.js +++ b/react/src/dashboard/i18n/nl/i18n-modals-danger_zone.js @@ -18,6 +18,7 @@ import remove_mollie_connection from './modals/danger-zone/remove-mollie-connect import exclude_pre_check_fund from './modals/danger-zone/exclude_pre_check_fund'; import remove_prevalidation_request from './modals/danger-zone/remove-prevalidation-request'; import confirm_extra_payment_refund from './modals/danger-zone/confirm-extra-payment-refund'; +import send_voucher_by_email from './modals/danger-zone/send-voucher-by-email'; export default { remove_provider_application, @@ -40,4 +41,5 @@ export default { exclude_pre_check_fund, remove_prevalidation_request, confirm_extra_payment_refund, + send_voucher_by_email, }; diff --git a/react/src/dashboard/i18n/nl/modals/danger-zone/send-voucher-by-email.js b/react/src/dashboard/i18n/nl/modals/danger-zone/send-voucher-by-email.js new file mode 100644 index 000000000..cd8cf6714 --- /dev/null +++ b/react/src/dashboard/i18n/nl/modals/danger-zone/send-voucher-by-email.js @@ -0,0 +1,8 @@ +export default { + title: 'QR-code versturen per e-mail', + description: 'Weet u zeker dat u de QR-code naar de inwoner wilt versturen?', + buttons: { + cancel: 'Annuleren', + confirm: 'Bevestigen', + }, +}; diff --git a/react/src/dashboard/i18n/nl/pages/system-notifications.js b/react/src/dashboard/i18n/nl/pages/system-notifications.js index 659695efe..ec178c397 100644 --- a/react/src/dashboard/i18n/nl/pages/system-notifications.js +++ b/react/src/dashboard/i18n/nl/pages/system-notifications.js @@ -254,8 +254,24 @@ export default { description: 'Deelnemers ontvangen dit bericht wanneer ze een fysieke pas bestellen.', }, 'notifications_identities.voucher_shared_by_email': { - title: 'Tegoed verstuurd naar zelf', - description: 'Deelnemers ontvangen dit bericht wanneer ze een tegoed naar zichzelf sturen.', + title: 'Financieel-tegoed naar zichzelf verstuurd', + description: + 'Deelnemers ontvangen dit bericht wanneer ze de QR-code van een financieel-tegoed naar zichzelf hebben verstuurd.', + }, + 'notifications_identities.product_voucher_shared_by_email': { + title: 'Aanbod-tegoed naar zichzelf verstuurd', + description: + 'Deelnemers ontvangen dit bericht wanneer ze de QR-code van een aanbod-tegoed naar zichzelf hebben verstuurd.', + }, + 'notifications_identities.sponsor_voucher_shared_by_email': { + title: 'Financieel-tegoed opnieuw verstuurd', + description: + 'Deelnemers ontvangen dit bericht als de sponsor het financieel-tegoed opnieuw per e-mail verstuurt.', + }, + 'notifications_identities.sponsor_product_voucher_shared_by_email': { + title: 'Aanbod-tegoed opnieuw verstuurd', + description: + 'Deelnemers ontvangen dit bericht als de sponsor het aanbod-tegoed opnieuw per e-mail verstuurt.', }, 'notifications_identities.voucher_budget_transaction': { title: 'Transactie van financieel-tegoed', diff --git a/react/src/dashboard/i18n/nl/pages/vouchers.js b/react/src/dashboard/i18n/nl/pages/vouchers.js index 303ddb183..f0ceae791 100644 --- a/react/src/dashboard/i18n/nl/pages/vouchers.js +++ b/react/src/dashboard/i18n/nl/pages/vouchers.js @@ -46,6 +46,7 @@ export default { type_voucher: 'Soort tegoed', type_credit: 'Tegoed waarde', product: 'Aanbod', + send_to_email: 'Verstuur naar e-mail', }, events: { created_budget: 'Aangemaakt', diff --git a/react/src/dashboard/services/VoucherService.ts b/react/src/dashboard/services/VoucherService.ts index 18afcdcec..9b8dd951e 100644 --- a/react/src/dashboard/services/VoucherService.ts +++ b/react/src/dashboard/services/VoucherService.ts @@ -76,7 +76,7 @@ export class VoucherService { return this.apiRequest.patch(`${this.prefix}/${organizationId}/sponsor/vouchers/${voucher_id}`, query); } - public sendToEmail(organizationId: number, voucher_id: number, email: string): Promise> { + public sendToEmail(organizationId: number, voucher_id: number, email?: string): Promise> { return this.apiRequest.post(`${this.prefix}/${organizationId}/sponsor/vouchers/${voucher_id}/send`, { email, }); From 0f626d5965be72bac63922a7b2a7e908e402966b Mon Sep 17 00:00:00 2001 From: dev-rminds Date: Tue, 3 Mar 2026 20:09:16 +0200 Subject: [PATCH 4/9] webshop cms blocks minor refactoring --- .../scss/includes/blocks/block-cms-funds.scss | 19 +++++-------------- .../ImplementationsInlineBlockEditor.tsx | 2 +- .../ImplementationsBlockEditorItem.tsx | 2 +- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss b/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss index 26abddcea..6f33fdceb 100644 --- a/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss +++ b/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss @@ -29,9 +29,10 @@ flex-direction: column; align-self: center; flex: 1; + gap: 20px; .fund-label { - margin: 0 0 5px; + margin: 0 0 -10px; line-height: 20px; order: 1; font-size: var(--cms-funds-label-font-size); @@ -41,13 +42,13 @@ .fund-title { font: var(--cms-funds-title-font); color: var(--cms-funds-title-color); - margin: 0 0 20px; + margin: 0 0; order: 2; } .fund-description { font: 400 18px/28px var(--base-font); - margin: 0 0 20px; + margin: 0 0; order: 3; } @@ -121,8 +122,6 @@ flex-direction: column; .fund-label { - margin: 0 0 15px; - .label { font-size: 12px; } @@ -131,11 +130,9 @@ .fund-title { font: var(--cms-funds-compact-title-font); color: var(--cms-funds-compact-title-color); - margin: 0 0 15px; } .fund-description { - margin: 0 0 0; display: flex; .block.block-markdown ul, .block.block-markdown p { @@ -146,8 +143,6 @@ } .fund-button { - padding-top: 15px; - .fund-button-link { padding: 0; display: inline-block; @@ -207,14 +202,10 @@ .fund-information { width: 100%; + gap: 15px; .fund-title { font: 400 20px/26px var(--base-font); - margin: 0 0 10px; - } - - .fund-description { - margin: 0 0 10px; } .fund-button { diff --git a/react/src/dashboard/components/pages/implementation-pages/elements/ImplementationsInlineBlockEditor.tsx b/react/src/dashboard/components/pages/implementation-pages/elements/ImplementationsInlineBlockEditor.tsx index d4b96a861..fc169a8dd 100644 --- a/react/src/dashboard/components/pages/implementation-pages/elements/ImplementationsInlineBlockEditor.tsx +++ b/react/src/dashboard/components/pages/implementation-pages/elements/ImplementationsInlineBlockEditor.tsx @@ -140,7 +140,7 @@ export default function ImplementationsInlineBlockEditor({ id={id} placeholder={'Paragraaf'} className={'form-control'} - value={pageBlock?.description} + value={pageBlock?.description ?? ''} onChange={(e) => setPageBlock({ ...pageBlock, description: e.target.value })} /> ) diff --git a/react/src/dashboard/components/pages/implementations-edit/elements/ImplementationsBlockEditorItem.tsx b/react/src/dashboard/components/pages/implementations-edit/elements/ImplementationsBlockEditorItem.tsx index 303963a0c..b52d62433 100644 --- a/react/src/dashboard/components/pages/implementations-edit/elements/ImplementationsBlockEditorItem.tsx +++ b/react/src/dashboard/components/pages/implementations-edit/elements/ImplementationsBlockEditorItem.tsx @@ -95,7 +95,7 @@ export default function ImplementationsBlockEditorItem({ {!isCollapsed && (
-
+
Date: Wed, 4 Mar 2026 17:10:05 +0200 Subject: [PATCH 5/9] hide sponsor share voucher by email button for deactivated vouchers --- .../vouchers-view/VouchersViewComponent.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx b/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx index 3368da401..58498a48e 100644 --- a/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx +++ b/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx @@ -356,14 +356,17 @@ export default function VouchersViewComponent() { {hasPermission(activeOrganization, Permission.MANAGE_VOUCHERS) && (
- {voucher.granted && !voucher?.external && voucher?.fund?.show_qr_code && ( -
- - {translate('vouchers.labels.send_to_email')} -
- )} + {voucher.granted && + voucher.state !== 'deactivated' && + !voucher.external && + voucher.fund?.show_qr_code && ( +
+ + {translate('vouchers.labels.send_to_email')} +
+ )} {showMakeTransactionButton && fund?.allow_voucher_top_ups && (
From 5d4bfb61498714372bac138bfb817f4da655cb4d Mon Sep 17 00:00:00 2001 From: Yaroslav Kosterin Date: Thu, 5 Mar 2026 15:32:04 +0200 Subject: [PATCH 6/9] fix space in case two blocks in cms --- .../forus-webshop/scss/includes/blocks/block-cms-funds.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss b/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss index 6f33fdceb..f4ec38476 100644 --- a/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss +++ b/react/assets/forus-webshop/scss/includes/blocks/block-cms-funds.scss @@ -181,7 +181,7 @@ gap: var(--gap); .fund-item { - flex: 0 1 calc((100% - (calc(var(--gap) * 2))) / 2); + flex: 0 1 calc((100% - (calc(var(--gap)))) / 2); } } } From e7c986c4b99c1d3115126c1e45d65cdb55b1a427 Mon Sep 17 00:00:00 2001 From: Yaroslav Kosterin Date: Thu, 5 Mar 2026 19:45:55 +0200 Subject: [PATCH 7/9] hide send to email if voucher expired --- .../components/pages/vouchers-view/VouchersViewComponent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx b/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx index 58498a48e..c73dcbce7 100644 --- a/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx +++ b/react/src/dashboard/components/pages/vouchers-view/VouchersViewComponent.tsx @@ -357,6 +357,7 @@ export default function VouchersViewComponent() {
{voucher.granted && + !voucher.expired && voucher.state !== 'deactivated' && !voucher.external && voucher.fund?.show_qr_code && ( From e9f472d587e18e1651798c6ded49f83e7a5243f3 Mon Sep 17 00:00:00 2001 From: dev-rminds Date: Fri, 6 Mar 2026 01:38:06 +0200 Subject: [PATCH 8/9] voucher partial payouts --- react/src/dashboard/props/models/Fund.tsx | 1 + react/src/dashboard/props/models/Voucher.tsx | 1 + .../components/modals/ModalVoucherPayout.tsx | 66 +++++++++++++++++-- react/src/webshop/i18n/nl/pages/voucher.mjs | 1 + 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/react/src/dashboard/props/models/Fund.tsx b/react/src/dashboard/props/models/Fund.tsx index c4b0243e8..1c73cfb37 100644 --- a/react/src/dashboard/props/models/Fund.tsx +++ b/react/src/dashboard/props/models/Fund.tsx @@ -137,6 +137,7 @@ export default interface Fund { faq_title?: string; allow_reimbursements?: boolean; allow_voucher_payouts?: boolean; + allow_voucher_payouts_partial?: boolean; allow_voucher_payout_count?: number | null; allow_physical_cards?: boolean; allow_blocking_vouchers?: boolean; diff --git a/react/src/dashboard/props/models/Voucher.tsx b/react/src/dashboard/props/models/Voucher.tsx index 1f51cd9ab..477d5112e 100644 --- a/react/src/dashboard/props/models/Voucher.tsx +++ b/react/src/dashboard/props/models/Voucher.tsx @@ -116,4 +116,5 @@ export default interface Voucher { iban: string; iban_name: string; }; + voucher_payout_partial_amounts?: Array | null; } diff --git a/react/src/webshop/components/modals/ModalVoucherPayout.tsx b/react/src/webshop/components/modals/ModalVoucherPayout.tsx index 9a3e39402..bacd59e15 100644 --- a/react/src/webshop/components/modals/ModalVoucherPayout.tsx +++ b/react/src/webshop/components/modals/ModalVoucherPayout.tsx @@ -93,9 +93,33 @@ export default function ModalVoucherPayout({ const selectedVoucherId = selectedVoucherItem?.id; const updateForm = form.update; + const partialPayoutAmounts = useMemo(() => { + return selectedVoucherItem?.voucher_payout_partial_amounts; + }, [selectedVoucherItem?.voucher_payout_partial_amounts]); + + const partialPayoutOptions = useMemo(() => { + if (!Array.isArray(partialPayoutAmounts)) { + return []; + } + + return partialPayoutAmounts.map((amount) => { + const amountNumber = parseFloat(amount); + const name = isNaN(amountNumber) ? amount : currencyFormat(amountNumber); + + return { + id: amount, + name, + }; + }); + }, [partialPayoutAmounts]); + const fixedPayoutAmount = useMemo(() => { + if (Array.isArray(partialPayoutAmounts)) { + return null; + } + return selectedVoucherItem?.fund?.voucher_payout_fixed_amount; - }, [selectedVoucherItem?.fund?.voucher_payout_fixed_amount]); + }, [partialPayoutAmounts, selectedVoucherItem?.fund?.voucher_payout_fixed_amount]); const payoutCountWarning = useMemo(() => { if (!selectedVoucherItem) { @@ -117,11 +141,23 @@ export default function ModalVoucherPayout({ return null; }, [selectedVoucherItem, translate]); + const payoutPartialWarning = useMemo(() => { + if (!Array.isArray(partialPayoutAmounts)) { + return null; + } + + return partialPayoutAmounts.length === 0 ? translate('voucher.payout.warning_no_partial_amounts') : null; + }, [partialPayoutAmounts, translate]); + const payoutAmountWarning = useMemo(() => { if (!selectedVoucherItem) { return null; } + if (Array.isArray(partialPayoutAmounts)) { + return null; + } + const voucherAmount = parseFloat(selectedVoucherItem?.amount) || 0; if (fixedPayoutAmount !== null && fixedPayoutAmount !== undefined) { @@ -139,21 +175,26 @@ export default function ModalVoucherPayout({ min: currencyFormat(0.1), }) : null; - }, [fixedPayoutAmount, selectedVoucherItem, translate]); + }, [fixedPayoutAmount, partialPayoutAmounts, selectedVoucherItem, translate]); - const warningMessage = payoutCountWarning || payoutAmountWarning; + const warningMessage = payoutPartialWarning || payoutCountWarning || payoutAmountWarning; useEffect(() => { if (!selectedVoucherId) { return; } + if (Array.isArray(partialPayoutAmounts)) { + updateForm({ amount: partialPayoutAmounts[0] || '' }); + return; + } + if (fixedPayoutAmount !== null && fixedPayoutAmount !== undefined) { updateForm({ amount: fixedPayoutAmount }); } else { updateForm({ amount: '' }); } - }, [fixedPayoutAmount, selectedVoucherId, updateForm]); + }, [fixedPayoutAmount, partialPayoutAmounts, selectedVoucherId, updateForm]); useEffect(() => { if (!fundRequestAccounts?.length) { @@ -275,6 +316,23 @@ export default function ModalVoucherPayout({ required={true} error={form.errors?.amount} input={(id) => { + if (Array.isArray(partialPayoutAmounts)) { + return ( + form.update({ amount })} + disabled={partialPayoutOptions.length === 0} + dusk="voucherPayoutAmount" + /> + ); + } + if (fixedPayoutAmount !== null && fixedPayoutAmount !== undefined) { const amountNumber = parseFloat(fixedPayoutAmount); const displayValue = isNaN(amountNumber) diff --git a/react/src/webshop/i18n/nl/pages/voucher.mjs b/react/src/webshop/i18n/nl/pages/voucher.mjs index 1cfd34af1..4907059f0 100644 --- a/react/src/webshop/i18n/nl/pages/voucher.mjs +++ b/react/src/webshop/i18n/nl/pages/voucher.mjs @@ -115,6 +115,7 @@ export default { accept_compliance_rules_info: '

Ik verklaar dat:

  • ik het geld alleen gebruik voor deze activiteit;
  • ik het geld gebruik volgens de regels van de regeling;
  • ik begrijp dat ik het geld moet terugbetalen als ik het niet juist gebruik.
', warning_count_reached: 'Het maximale aantal uitbetalingen voor dit tegoed is bereikt.', + warning_no_partial_amounts: 'Er is geen uitbetalingsbedrag beschikbaar voor dit tegoed.', warning_not_enough_amount_min: 'Dit tegoed moet minimaal {{ min }} bevatten voor een uitbetaling.', warning_not_enough_amount_fixed: 'Dit tegoed moet minimaal {{ amount }} bevatten voor een uitbetaling.', submit: 'Overboeken', From b1897cef7e60e52c005c1d4d7ebbd54ac17b564f Mon Sep 17 00:00:00 2001 From: dev-rminds Date: Fri, 6 Mar 2026 01:46:29 +0200 Subject: [PATCH 9/9] update translations --- react/src/webshop/i18n/translated/ar.json | 5 +++-- react/src/webshop/i18n/translated/de.json | 5 +++-- react/src/webshop/i18n/translated/en-US.json | 5 +++-- react/src/webshop/i18n/translated/fr.json | 5 +++-- react/src/webshop/i18n/translated/pl.json | 5 +++-- react/src/webshop/i18n/translated/ru.json | 5 +++-- react/src/webshop/i18n/translated/tr.json | 5 +++-- react/src/webshop/i18n/translated/uk.json | 5 +++-- translations/cache/cache.json | 6 +++++- 9 files changed, 29 insertions(+), 17 deletions(-) diff --git a/react/src/webshop/i18n/translated/ar.json b/react/src/webshop/i18n/translated/ar.json index 74dcd10dd..3558e7c59 100644 --- a/react/src/webshop/i18n/translated/ar.json +++ b/react/src/webshop/i18n/translated/ar.json @@ -2154,7 +2154,8 @@ "warning_not_enough_amount_fixed": "يجب أن يتضمن هذا الرصيد {{ amount }} على الأقل للدفع.", "warning_not_enough_amount_min": "يجب أن يتضمن هذا الرصيد {{ min }} على الأقل للدفع.", "accept_compliance_rules_info": "

أقر بأنني:

  • أنا أستخدم الأموال لهذا النشاط فقط;
  • أنا أستخدم المال وفقاً لقواعد البرنامج;
  • أفهم أنني يجب أن أسدد الأموال إذا لم أستخدمها بشكل صحيح.
", - "accept_compliance_rules": "أوافق على الشروط والأحكام المذكورة أعلاه." + "accept_compliance_rules": "أوافق على الشروط والأحكام المذكورة أعلاه.", + "warning_no_partial_amounts": "لا يوجد مبلغ مدفوعات متاح لهذا الرصيد." } }, "vouchers": { @@ -2692,7 +2693,7 @@ "title": "العنوان" }, "title": "بيانات الإعلان", - "code": "الرقم: #: الرمز" + "code": "الرقم: #{{code}}" }, "empty": { "button": "إرسال القسيمة", diff --git a/react/src/webshop/i18n/translated/de.json b/react/src/webshop/i18n/translated/de.json index 3123a743f..cc7a9ff8e 100644 --- a/react/src/webshop/i18n/translated/de.json +++ b/react/src/webshop/i18n/translated/de.json @@ -2719,7 +2719,7 @@ "title": "Titel" }, "title": "Daten der Erklärung", - "code": "Nummer: #:code" + "code": "Nummer: #{{code}}" }, "empty": { "button": "Coupon einreichen", @@ -3510,7 +3510,8 @@ "warning_not_enough_amount_fixed": "Dieses Guthaben muss mindestens {{ amount }} für eine Auszahlung umfassen.", "warning_not_enough_amount_min": "Dieses Guthaben muss mindestens {{ min }} für eine Auszahlung umfassen.", "accept_compliance_rules_info": "

Ich erkläre, dass:

  • Ich verwende das Geld nur für diese Aktivität;
  • Ich verwende das Geld gemäß den Regeln des Programms;
  • ich verstehe, dass ich das Geld zurückzahlen muss, wenn ich es nicht ordnungsgemäß verwende.
", - "accept_compliance_rules": "Ich erkläre mich mit den oben genannten Bestimmungen und Bedingungen einverstanden." + "accept_compliance_rules": "Ich erkläre mich mit den oben genannten Bestimmungen und Bedingungen einverstanden.", + "warning_no_partial_amounts": "Für diesen Kredit ist kein Auszahlungsbetrag verfügbar." } }, "vouchers": { diff --git a/react/src/webshop/i18n/translated/en-US.json b/react/src/webshop/i18n/translated/en-US.json index d5a0ca2b9..0275da3b0 100644 --- a/react/src/webshop/i18n/translated/en-US.json +++ b/react/src/webshop/i18n/translated/en-US.json @@ -1855,7 +1855,8 @@ "warning_not_enough_amount_fixed": "This credit must include a minimum of {{ amount }} for a payout.", "warning_not_enough_amount_min": "This credit must include a minimum of {{ min }} for a payout.", "accept_compliance_rules_info": "

I declare that:

  • I am using the money only for this activity;
  • I am using the money according to the rules of the scheme;
  • I understand that I must repay the money if I do not use it correctly.
", - "accept_compliance_rules": "I agree to the above terms and conditions." + "accept_compliance_rules": "I agree to the above terms and conditions.", + "warning_no_partial_amounts": "There is no payout amount available for this credit." } }, "vouchers": { @@ -2389,7 +2390,7 @@ "title": "Title" }, "title": "Claim data", - "code": "Number: #:code" + "code": "Number: #{{code}}" }, "empty": { "button": "Submit coupon", diff --git a/react/src/webshop/i18n/translated/fr.json b/react/src/webshop/i18n/translated/fr.json index 3a8a6d667..9e328ef12 100644 --- a/react/src/webshop/i18n/translated/fr.json +++ b/react/src/webshop/i18n/translated/fr.json @@ -2719,7 +2719,7 @@ "title": "Titre" }, "title": "Données de la déclaration", - "code": "Nombre : #:code" + "code": "Nombre : #{{code}}" }, "empty": { "button": "Soumettre un coupon", @@ -3510,7 +3510,8 @@ "warning_not_enough_amount_fixed": "Ce crédit doit comprendre au moins {{ amount }} pour un paiement.", "warning_not_enough_amount_min": "Ce crédit doit comprendre au moins {{ min }} pour un paiement.", "accept_compliance_rules_info": "

Je déclare que :

  • J'utilise l'argent uniquement pour cette activité ;
  • J'utilise l'argent conformément aux règles du programme ;
  • Je comprends que je dois rembourser l'argent si je ne l'utilise pas correctement.
", - "accept_compliance_rules": "J'accepte les termes et conditions ci-dessus." + "accept_compliance_rules": "J'accepte les termes et conditions ci-dessus.", + "warning_no_partial_amounts": "Aucun montant de remboursement n'est disponible pour ce crédit." } }, "vouchers": { diff --git a/react/src/webshop/i18n/translated/pl.json b/react/src/webshop/i18n/translated/pl.json index d67406258..1ad2eded7 100644 --- a/react/src/webshop/i18n/translated/pl.json +++ b/react/src/webshop/i18n/translated/pl.json @@ -2719,7 +2719,7 @@ "title": "Tytuł" }, "title": "Dane deklaracji", - "code": "Numer: #:kod" + "code": "Numer: #{{code}}" }, "empty": { "button": "Prześlij kupon", @@ -3510,7 +3510,8 @@ "warning_not_enough_amount_fixed": "Kredyt ten musi obejmować co najmniej {{ amount }} do wypłaty.", "warning_not_enough_amount_min": "Kredyt ten musi obejmować co najmniej {{ min }} do wypłaty.", "accept_compliance_rules_info": "

Oświadczam, że:

  • Wykorzystuję pieniądze wyłącznie na tę działalność;
  • wykorzystuję pieniądze zgodnie z zasadami programu;
  • Rozumiem, że muszę zwrócić pieniądze, jeśli nie wykorzystam ich prawidłowo.
", - "accept_compliance_rules": "Zgadzam się na powyższe warunki." + "accept_compliance_rules": "Zgadzam się na powyższe warunki.", + "warning_no_partial_amounts": "Kwota wypłaty nie jest dostępna dla tego kredytu." } }, "vouchers": { diff --git a/react/src/webshop/i18n/translated/ru.json b/react/src/webshop/i18n/translated/ru.json index 0b529cf9c..bc9f7ef46 100644 --- a/react/src/webshop/i18n/translated/ru.json +++ b/react/src/webshop/i18n/translated/ru.json @@ -2719,7 +2719,7 @@ "title": "Название" }, "title": "Данные декларации", - "code": "Номер: #:код" + "code": "Номер: #{{code}}" }, "empty": { "button": "Отправить купон", @@ -3510,7 +3510,8 @@ "warning_not_enough_amount_fixed": "Этот кредит должен включать в себя как минимум {{ amount }} для выплаты.", "warning_not_enough_amount_min": "Этот кредит должен включать в себя как минимум {{ min }} для выплаты.", "accept_compliance_rules_info": "

Я заявляю, что:

  • Я использую деньги только для этой деятельности;
  • Я использую деньги в соответствии с правилами программы;
  • Я понимаю, что должен вернуть деньги, если использую их неправильно.
", - "accept_compliance_rules": "Я согласен с вышеуказанными условиями и положениями." + "accept_compliance_rules": "Я согласен с вышеуказанными условиями и положениями.", + "warning_no_partial_amounts": "Сумма выплаты по этому кредиту не предусмотрена." } }, "vouchers": { diff --git a/react/src/webshop/i18n/translated/tr.json b/react/src/webshop/i18n/translated/tr.json index 63983e181..6461fa7ff 100644 --- a/react/src/webshop/i18n/translated/tr.json +++ b/react/src/webshop/i18n/translated/tr.json @@ -2154,7 +2154,8 @@ "warning_not_enough_amount_fixed": "Bu kredi, ödeme için en az {{ amount }} adresini içermelidir.", "warning_not_enough_amount_min": "Bu kredi, ödeme için en az {{ min }} adresini içermelidir.", "accept_compliance_rules_info": "

Bunu beyan ederim:

  • Parayı sadece bu faaliyet için kullanıyorum;
  • Parayı programın kurallarına uygun olarak kullanıyorum;
  • Parayı doğru kullanmadığım takdirde geri ödemem gerektiğini anlıyorum.
", - "accept_compliance_rules": "Yukarıdaki hüküm ve koşulları kabul ediyorum." + "accept_compliance_rules": "Yukarıdaki hüküm ve koşulları kabul ediyorum.", + "warning_no_partial_amounts": "Bu kredi için herhangi bir ödeme tutarı mevcut değildir." } }, "vouchers": { @@ -2692,7 +2693,7 @@ "title": "Başlık" }, "title": "Beyan verileri", - "code": "Numara: #:code" + "code": "Numara: #{{code}}" }, "empty": { "button": "Kupon gönder", diff --git a/react/src/webshop/i18n/translated/uk.json b/react/src/webshop/i18n/translated/uk.json index 0c33ee1c8..d62100687 100644 --- a/react/src/webshop/i18n/translated/uk.json +++ b/react/src/webshop/i18n/translated/uk.json @@ -2719,7 +2719,7 @@ "title": "Назва" }, "title": "Дані декларації", - "code": "Номер: #:код" + "code": "Номер: #{{code}}" }, "empty": { "button": "Надіслати купон", @@ -3510,7 +3510,8 @@ "warning_not_enough_amount_fixed": "Цей кредит повинен містити щонайменше {{ amount }} для виплати.", "warning_not_enough_amount_min": "Цей кредит повинен містити щонайменше {{ min }} для виплати.", "accept_compliance_rules_info": "

Я заявляю про це:

  • Я використовую гроші тільки для цієї діяльності;
  • Я використовую гроші відповідно до правил схеми;
  • Я розумію, що повинен повернути гроші, якщо використаю їх не за призначенням.
", - "accept_compliance_rules": "Я погоджуюся з вищезазначеними умовами та положеннями." + "accept_compliance_rules": "Я погоджуюся з вищезазначеними умовами та положеннями.", + "warning_no_partial_amounts": "Сума виплат за цим кредитом не передбачена." } }, "vouchers": { diff --git a/translations/cache/cache.json b/translations/cache/cache.json index 15cc2bd93..1aaac7e04 100644 --- a/translations/cache/cache.json +++ b/translations/cache/cache.json @@ -7645,7 +7645,7 @@ ], [ "reimbursements.details.code", - "Nummer: #:code" + "Nummer: #{{code}}" ], [ "reimbursements.details.labels.amount", @@ -9407,6 +9407,10 @@ "voucher.payout.warning_count_reached", "Het maximale aantal uitbetalingen voor dit tegoed is bereikt." ], + [ + "voucher.payout.warning_no_partial_amounts", + "Er is geen uitbetalingsbedrag beschikbaar voor dit tegoed." + ], [ "voucher.payout.warning_not_enough_amount_fixed", "Dit tegoed moet minimaal {{ amount }} bevatten voor een uitbetaling."