-
+
)}
);
diff --git a/react/src/webshop/components/pages/products-show/elements/ProductFundsCard.tsx b/react/src/webshop/components/pages/products-show/elements/ProductFundsCard.tsx
index 21a605acf..2cfab472e 100644
--- a/react/src/webshop/components/pages/products-show/elements/ProductFundsCard.tsx
+++ b/react/src/webshop/components/pages/products-show/elements/ProductFundsCard.tsx
@@ -118,7 +118,7 @@ export default function ProductFundsCard({
{listFunds.map((fund) => (
-
+
![]()
{fund.external_link_text}
@@ -258,7 +259,8 @@ export default function ProductFundsCard({
+ className="button button-primary"
+ dataDusk="fundRequest">
{fund.request_btn_text}
@@ -269,7 +271,8 @@ export default function ProductFundsCard({
+ className="button button-primary-outline"
+ dataDusk="fundRequests">
{translate('funds.buttons.is_pending')}
@@ -279,7 +282,8 @@ export default function ProductFundsCard({
@@ -291,6 +295,7 @@ export default function ProductFundsCard({
{translate(
`funds.buttons.${fund.key}.already_received`,
@@ -305,7 +310,7 @@ export default function ProductFundsCard({
{translate('product.funds.not_available_label')}
-
+
{translate('product.funds.not_available_value')}
diff --git a/react/src/webshop/components/pages/products/Products.tsx b/react/src/webshop/components/pages/products/Products.tsx
index 5c2158df7..ffc18d5d3 100644
--- a/react/src/webshop/components/pages/products/Products.tsx
+++ b/react/src/webshop/components/pages/products/Products.tsx
@@ -24,6 +24,9 @@ import { BooleanParam, NumberParam, StringParam } from 'use-query-params';
import { clickOnKeyEnter } from '../../../../dashboard/helpers/wcag';
import useSetTitle from '../../../hooks/useSetTitle';
import UIControlText from '../../../../dashboard/components/elements/forms/ui-controls/UIControlText';
+import RangeControl from '../../elements/forms/RangeControl';
+import useShowProductPaymentOptionsInfoModal from '../../../hooks/useShowProductPaymentOptionsInfoModal';
+import ProductsFilterOptions from './elements/ProductsFilterOptions';
export default function Products({ fundType = 'budget' }: { fundType: 'budget' | 'subsidies' }) {
const appConfigs = useAppConfigs();
@@ -37,12 +40,14 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
const setTitle = useSetTitle();
const translate = useTranslate();
const setProgress = useSetProgress();
+ const showProductIconsInfoModal = useShowProductPaymentOptionsInfoModal();
const [sortByOptions] = useState(productService.getSortOptions(translate));
const [errors, setErrors] = useState<{ [key: string]: string | Array
}>({});
const [funds, setFunds] = useState>>(null);
+ const [toMax, setToMax] = useState(0);
const [organizations, setOrganizations] = useState>>(null);
const [productCategories, setProductCategories] = useState>>(null);
const [productSubCategories, setProductSubCategories] = useState>>(null);
@@ -69,6 +74,11 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
product_sub_category_id: number;
postcode: string;
distance: number;
+ from: number;
+ to: number;
+ qr?: boolean;
+ reservation?: boolean;
+ extra_payment?: boolean;
bookmarked: boolean;
display_type: 'list' | 'grid';
order_by: 'created_at' | 'price' | 'most_popular' | 'name';
@@ -83,13 +93,18 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
product_sub_category_id: null,
postcode: '',
distance: null,
+ from: 0,
+ to: null,
+ qr: false,
+ reservation: false,
+ extra_payment: false,
bookmarked: false,
display_type: 'list',
order_by: sortByOptions[0]?.value.order_by,
order_dir: sortByOptions[0]?.value.order_dir,
},
{
- throttledValues: ['q'],
+ throttledValues: ['q', 'from', 'to', 'qr', 'reservation', 'extra_payment'],
queryParams: {
q: StringParam,
page: NumberParam,
@@ -99,6 +114,11 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
product_sub_category_id: NumberParam,
postcode: StringParam,
distance: NumberParam,
+ from: NumberParam,
+ to: NumberParam,
+ qr: BooleanParam,
+ reservation: BooleanParam,
+ extra_payment: BooleanParam,
bookmarked: BooleanParam,
display_type: StringParam,
order_by: StringParam,
@@ -121,7 +141,7 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
return filterValuesActive?.fund_id && funds?.find((item) => item.id === filterValuesActive?.fund_id);
}, [filterValuesActive?.fund_id, funds]);
- const [products, setProducts] = useState>(null);
+ const [products, setProducts] = useState>(null);
const buildQuery = useCallback(
(
@@ -134,13 +154,19 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
product_sub_category_id: number;
postcode: string;
distance: number;
+ from: number;
+ to: number;
bookmarked: boolean;
+ qr: boolean;
+ reservation: boolean;
+ extra_payment: boolean;
display_type: 'list' | 'grid';
order_by: 'created_at' | 'price' | 'most_popular' | 'name';
order_dir: 'asc' | 'desc';
}>,
) => {
const isSortingByPrice = values.order_by === 'price';
+ const hasFilters = values.qr || values.extra_payment || values.reservation;
return {
q: values.q,
@@ -151,6 +177,11 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
fund_type: fundType,
postcode: values.postcode || '',
distance: values.distance || null,
+ from: values.from || null,
+ to: values.to || null,
+ qr: hasFilters ? (values.qr ? 1 : 0) : 0,
+ reservation: hasFilters ? (values.reservation ? 1 : 0) : 0,
+ extra_payment: hasFilters ? (values.extra_payment ? 1 : 0) : 0,
bookmarked: values.bookmarked ? 1 : 0,
order_by: isSortingByPrice ? (fundType === 'budget' ? 'price' : 'price_min') : values.order_by,
order_dir: values.order_dir,
@@ -166,8 +197,11 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
productService
.list({ fund_type: fundType, ...query })
- .then((res) => setProducts(res.data))
- .catch((e: ResponseError) => setErrors(e.data.errors))
+ .then((res) => {
+ setProducts(res.data);
+ setToMax((max) => Math.max(res.data?.meta?.price_max, max));
+ })
+ .catch((e: ResponseError) => setErrors(e.data?.errors))
.finally(() => setProgress(100));
},
[fundType, productService, setProgress],
@@ -396,6 +430,91 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
+
+
+
{translate('products.filters.price')}
+
+
+
+
+
+ filterUpdate({
+ from: Math.min(Math.max(parseFloat(e.target.value) || 0, 0), toMax),
+ })
+ }
+ type="number"
+ aria-label={translate('products.filters.price_from')}
+ />
+
+
+
+
+
+
+
+
+ filterUpdate({
+ to: Math.min(Math.max(parseFloat(e.target.value) || toMax, 0), toMax),
+ })
+ }
+ type="number"
+ aria-label={translate('products.filters.price_to')}
+ />
+
+
+
+
+
+
+ filterUpdate({ from })}
+ setTo={(to) => filterUpdate({ to })}
+ prefix={'€ '}
+ />
+
+
+
+
+ {translate('products.filters.payment_options')}
+
+
+
+
filterUpdate(value)} />
)
}>
@@ -473,12 +592,7 @@ export default function Products({ fundType = 'budget' }: { fundType: 'budget' |
{appConfigs.pages.products &&
}
{products?.meta?.total > 0 && (
-
+
)}
{products?.meta?.total == 0 && (
diff --git a/react/src/webshop/components/pages/products/elements/ProductsFilterOptions.tsx b/react/src/webshop/components/pages/products/elements/ProductsFilterOptions.tsx
new file mode 100644
index 000000000..88ab61560
--- /dev/null
+++ b/react/src/webshop/components/pages/products/elements/ProductsFilterOptions.tsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import classNames from 'classnames';
+import { clickOnKeyEnter } from '../../../../../dashboard/helpers/wcag';
+import useAssetUrl from '../../../../hooks/useAssetUrl';
+import useTranslate from '../../../../../dashboard/hooks/useTranslate';
+
+type ProductsFilterOptionsValue = {
+ qr?: boolean;
+ reservation?: boolean;
+ extra_payment?: boolean;
+};
+
+export default function ProductsFilterOptions({
+ value,
+ setValue,
+}: {
+ value: ProductsFilterOptionsValue;
+ setValue?: (value: ProductsFilterOptionsValue) => void;
+}) {
+ const assetUrl = useAssetUrl();
+ const translate = useTranslate();
+
+ return (
+
+
setValue({ qr: !value.qr })}
+ onKeyDown={(e) => clickOnKeyEnter(e, true)}
+ className={classNames('showcase-aside-block-option', value.qr && 'showcase-aside-block-option-active')}>
+
+
+
+
+ {translate('products.filters.payment_option_qr')}
+
+
+
+
+
+
setValue({ reservation: !value.reservation })}
+ onKeyDown={(e) => clickOnKeyEnter(e, true)}
+ className={classNames(
+ 'showcase-aside-block-option',
+ value.reservation && 'showcase-aside-block-option-active',
+ )}>
+
+
+
+
+ {translate('products.filters.payment_option_reservation')}
+
+
+
+
+
+
setValue({ extra_payment: !value.extra_payment })}
+ onKeyDown={(e) => clickOnKeyEnter(e, true)}
+ className={classNames(
+ 'showcase-aside-block-option',
+ value.extra_payment && 'showcase-aside-block-option-active',
+ )}>
+
+
+
+
+ {translate('products.filters.payment_option_ideal')}
+
+
+
})
+
+
+
+ );
+}
diff --git a/react/src/webshop/components/pages/profile/Profile.tsx b/react/src/webshop/components/pages/profile/Profile.tsx
index ba97d1dd1..468700f7b 100644
--- a/react/src/webshop/components/pages/profile/Profile.tsx
+++ b/react/src/webshop/components/pages/profile/Profile.tsx
@@ -12,6 +12,7 @@ import IdentityContactInformationCard from './cards/IdentityContactInformationCa
import { useProfileService } from '../../../../dashboard/services/ProfileService';
import { differenceInYears } from 'date-fns';
import { dateParse } from '../../../../dashboard/helpers/dates';
+import IdentityAddressCard from './cards/IdentityAddressCard';
export default function Profile() {
const translate = useTranslate();
@@ -184,6 +185,12 @@ export default function Profile() {
setProfile={setProfile}
/>
+
+
{profile?.bank_accounts?.length > 0 && (
diff --git a/react/src/webshop/components/pages/profile/cards/IdentityAddressCard.tsx b/react/src/webshop/components/pages/profile/cards/IdentityAddressCard.tsx
new file mode 100644
index 000000000..8c164636e
--- /dev/null
+++ b/react/src/webshop/components/pages/profile/cards/IdentityAddressCard.tsx
@@ -0,0 +1,215 @@
+import BlockKeyValueList from '../../../elements/block-key-value-list/BlockKeyValueList';
+import IdentityRecordKeyValueListHistory from '../elements/IdentityRecordKeyValueListHistory';
+import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
+import { ProfileRecords, ProfileRecordValues } from '../../../../../dashboard/props/models/Sponsor/SponsorIdentity';
+import useFormBuilder from '../../../../../dashboard/hooks/useFormBuilder';
+import Profile from '../../../../../dashboard/props/models/Profile';
+import { useProfileService } from '../../../../../dashboard/services/ProfileService';
+import { ResponseError } from '../../../../../dashboard/props/ApiResponses';
+import useSetProgress from '../../../../../dashboard/hooks/useSetProgress';
+import usePushDanger from '../../../../../dashboard/hooks/usePushDanger';
+import usePushSuccess from '../../../../../dashboard/hooks/usePushSuccess';
+import FormError from '../../../../../dashboard/components/elements/forms/errors/FormError';
+import useTranslate from '../../../../../dashboard/hooks/useTranslate';
+
+export default function IdentityAddressCard({
+ profile,
+ setProfile,
+ recordTypesByKey,
+}: {
+ profile: Profile;
+ setProfile?: Dispatch
>;
+ recordTypesByKey: ProfileRecords;
+}) {
+ const [fields] = useState(['city', 'street', 'house_number', 'house_number_addition', 'postal_code']);
+
+ const [editAddress, setEditAddress] = useState(false);
+
+ const translate = useTranslate();
+ const pushDanger = usePushDanger();
+ const pushSuccess = usePushSuccess();
+ const setProgress = useSetProgress();
+ const profileService = useProfileService();
+
+ const form = useFormBuilder>(
+ {
+ city: '',
+ street: '',
+ house_number: '',
+ house_number_addition: '',
+ postal_code: '',
+ },
+ (values) => {
+ setProgress(0);
+
+ profileService
+ .update(values)
+ .then((res) => {
+ setProfile(res.data);
+ setEditAddress(false);
+ pushSuccess(translate('push.success'), translate('push.profile.updated'));
+ })
+ .catch((err: ResponseError) => {
+ pushDanger(translate('push.error'), err.data.message);
+ form.setErrors(err.data.errors);
+ })
+ .finally(() => {
+ setProgress(100);
+ form.setIsLocked(false);
+ });
+ },
+ );
+
+ const { update: formUpdate } = form;
+
+ const initFormValues = useCallback(() => {
+ formUpdate({
+ city: profile.records.city?.[0]?.value || '',
+ street: profile.records.street?.[0]?.value || '',
+ house_number: profile.records.house_number?.[0]?.value || '',
+ house_number_addition: profile.records.house_number_addition?.[0]?.value || '',
+ postal_code: profile.records.postal_code?.[0]?.value || '',
+ });
+ }, [
+ formUpdate,
+ profile.records.city,
+ profile.records.street,
+ profile.records.house_number,
+ profile.records.house_number_addition,
+ profile.records.postal_code,
+ ]);
+
+ if (editAddress) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
{translate('profile.address.title')}
+
+
+
+
+
+
+
+ ,
+ },
+ {
+ label: recordTypesByKey?.street?.name,
+ value: ,
+ },
+ {
+ label: recordTypesByKey?.house_number?.name,
+ value: ,
+ },
+ {
+ label: recordTypesByKey?.house_number_addition?.name,
+ value: (
+
+ ),
+ },
+ {
+ label: recordTypesByKey?.postal_code?.name,
+ value: ,
+ },
+ {
+ label: recordTypesByKey?.neighborhood_name?.name,
+ value: (
+
+ ),
+ },
+ {
+ label: recordTypesByKey?.municipality_name?.name,
+ value: (
+
+ ),
+ },
+ ]}
+ />
+
+
+
+
+
+ );
+}
diff --git a/react/src/webshop/components/pages/profile/cards/IdentityContactInformationCard.tsx b/react/src/webshop/components/pages/profile/cards/IdentityContactInformationCard.tsx
index ddbd72a44..92d02d408 100644
--- a/react/src/webshop/components/pages/profile/cards/IdentityContactInformationCard.tsx
+++ b/react/src/webshop/components/pages/profile/cards/IdentityContactInformationCard.tsx
@@ -24,15 +24,7 @@ export default function IdentityContactInformationCard({
setProfile?: Dispatch>;
recordTypesByKey: ProfileRecords;
}) {
- const [fields] = useState([
- 'telephone',
- 'mobile',
- 'city',
- 'street',
- 'house_number',
- 'house_number_addition',
- 'postal_code',
- ]);
+ const [fields] = useState(['telephone', 'mobile']);
const [editContacts, setEditContacts] = useState(false);
@@ -50,11 +42,6 @@ export default function IdentityContactInformationCard({
{
telephone: '',
mobile: '',
- city: '',
- street: '',
- house_number: '',
- house_number_addition: '',
- postal_code: '',
},
(values) => {
setProgress(0);
@@ -83,22 +70,8 @@ export default function IdentityContactInformationCard({
formUpdate({
telephone: profile.records.telephone?.[0]?.value || '',
mobile: profile.records.mobile?.[0]?.value || '',
- city: profile.records.city?.[0]?.value || '',
- street: profile.records.street?.[0]?.value || '',
- house_number: profile.records.house_number?.[0]?.value || '',
- house_number_addition: profile.records.house_number_addition?.[0]?.value || '',
- postal_code: profile.records.postal_code?.[0]?.value || '',
});
- }, [
- formUpdate,
- profile.records.city,
- profile.records.mobile,
- profile.records.street,
- profile.records.telephone,
- profile.records.house_number,
- profile.records.house_number_addition,
- profile.records.postal_code,
- ]);
+ }, [formUpdate, profile.records.mobile, profile.records.telephone]);
if (editContacts) {
return (
@@ -143,22 +116,6 @@ export default function IdentityContactInformationCard({
))}
-
-
-
-
-
-
-
-
@@ -218,42 +175,6 @@ export default function IdentityContactInformationCard({
label: recordTypesByKey?.mobile?.name,
value:
,
},
- {
- label: recordTypesByKey?.city?.name,
- value:
,
- },
- {
- label: recordTypesByKey?.street?.name,
- value:
,
- },
- {
- label: recordTypesByKey?.house_number?.name,
- value:
,
- },
- {
- label: recordTypesByKey?.house_number_addition?.name,
- value: (
-
- ),
- },
- {
- label: recordTypesByKey?.postal_code?.name,
- value:
,
- },
- {
- label: recordTypesByKey?.neighborhood_name?.name,
- value: (
-
- ),
- },
- {
- label: recordTypesByKey?.municipality_name?.name,
- value: (
-
- ),
- },
]}
/>
diff --git a/react/src/webshop/components/pages/providers-office/ProvidersOffice.tsx b/react/src/webshop/components/pages/providers-office/ProvidersOffice.tsx
index 7a8061894..7fddb6b67 100644
--- a/react/src/webshop/components/pages/providers-office/ProvidersOffice.tsx
+++ b/react/src/webshop/components/pages/providers-office/ProvidersOffice.tsx
@@ -96,7 +96,6 @@ export default function ProvidersOffice() {
return (
}>
{provider && office && (
diff --git a/react/src/webshop/components/pages/providers-show/ProvidersShow.tsx b/react/src/webshop/components/pages/providers-show/ProvidersShow.tsx
index f5d9fc241..8deae870a 100644
--- a/react/src/webshop/components/pages/providers-show/ProvidersShow.tsx
+++ b/react/src/webshop/components/pages/providers-show/ProvidersShow.tsx
@@ -19,6 +19,7 @@ import useTranslate from '../../../../dashboard/hooks/useTranslate';
import BlockShowcase from '../../elements/block-showcase/BlockShowcase';
import BlockLoader from '../../elements/block-loader/BlockLoader';
import useSetProgress from '../../../../dashboard/hooks/useSetProgress';
+import Section from '../../elements/sections/Section';
export default function ProvidersShow() {
const { id } = useParams();
@@ -81,7 +82,6 @@ export default function ProvidersShow() {
return (
}>
{provider && (
@@ -111,164 +110,175 @@ export default function ProvidersShow() {
)}
+