diff --git a/README.md b/README.md
index 1135a62c3..9f530587a 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@
Copy the `.env.example` and rename it to `.env.local`.
Fill in the correct values for the variables.
+For running it in combination with [udb3-backend](https://github.com/cultuurnet/udb3-backend) on [Docker](https://www.docker.com),
+a sample `.env` is available in [appconfig](https://github.com/cultuurnet/appconfig/blob/main/files/udb3/docker/udb3-frontend/.env).
## Build Setup
diff --git a/src/hooks/useHandleWindowMessage.js b/src/hooks/useHandleWindowMessage.js
index 22baac276..ac1692056 100644
--- a/src/hooks/useHandleWindowMessage.js
+++ b/src/hooks/useHandleWindowMessage.js
@@ -11,6 +11,7 @@ const WindowMessageTypes = {
URL_UNKNOWN: 'URL_UNKNOWN',
JOB_ADDED: 'JOB_ADDED',
HTTP_ERROR_CODE: 'HTTP_ERROR_CODE',
+ OFFER_MODERATED: 'OFFER_MODERATED',
};
const useHandleWindowMessage = (eventsMap = {}) => {
diff --git a/src/i18n/de.json b/src/i18n/de.json
index 3ca03673a..d8b24c53e 100644
--- a/src/i18n/de.json
+++ b/src/i18n/de.json
@@ -293,7 +293,10 @@
"everyone": "Für alle",
"members": "Nur für Mitglieder",
"title": "Zugang",
- "help": "Ihr Artikel wird nur auf kulturellen Bildungskanälen wie cultuurkuur.be veröffentlicht. Nach der Veröffentlichung können Sie spezifische Informationen für Schulen hinzufügen."
+ "help": {
+ "education": "Ihr Artikel wird nur auf kulturellen Bildungskanälen wie cultuurkuur.be veröffentlicht. Nach der Veröffentlichung können Sie spezifische Informationen für Schulen hinzufügen.",
+ "members": "Ihr Item wird nur auf Kanälen für Vereinigungen und deren Mitglieder veröffentlicht."
+ }
},
"contact_info": {
"add_more_singular": "Kontaktdaten hinzufügen",
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index c6940f733..d1d1fbbcc 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -295,7 +295,10 @@
"everyone": "Pour tout le monde",
"members": "Seulement pour des membres",
"title": "Accès",
- "help": "Votre article ne sera publié que sur des chaînes d'éducation culturelle telles que cultuurkuur.be. Après la publication, vous pouvez ajouter des informations spécifiques pour les écoles."
+ "help": {
+ "education": "Votre article ne sera publié que sur des chaînes d'éducation culturelle telles que cultuurkuur.be. Après la publication, vous pouvez ajouter des informations spécifiques pour les écoles.",
+ "members": "Votre article est seulement publié sur des chaînes pour des associations et leurs membres."
+ }
},
"contact_info": {
"add_more_singular": "Ajouter des coordonnées",
diff --git a/src/i18n/nl.json b/src/i18n/nl.json
index b5fbdd122..5d20c72bc 100644
--- a/src/i18n/nl.json
+++ b/src/i18n/nl.json
@@ -295,7 +295,10 @@
"education": "Specifiek voor scholen",
"everyone": "Voor iedereen",
"members": "Enkel voor leden",
- "help": "Je item wordt enkel gepubliceerd op cultuureducatieve kanalen zoals cultuurkuur.be. Na het publiceren kan je nog specifieke informatie voor scholen toevoegen."
+ "help": {
+ "education": "Je item wordt enkel gepubliceerd op cultuureducatieve kanalen zoals cultuurkuur.be. Na het publiceren kan je nog specifieke informatie voor scholen toevoegen.",
+ "members": "Je item wordt enkel gepubliceerd op kanalen voor verenigingen en hun leden."
+ }
},
"contact_info": {
"add_more_singular": "Contactgegevens toevoegen",
diff --git a/src/layouts/Announcements.js b/src/layouts/Announcements.js
index c67d33b00..8fe1318f2 100644
--- a/src/layouts/Announcements.js
+++ b/src/layouts/Announcements.js
@@ -92,9 +92,11 @@ const AnnouncementContent = ({
alt={callToActionLabel ?? ''}
width="100%"
maxHeight="30vh"
+ objectFit="contain"
opacity={{ hover: 0.85 }}
/>
);
+
return (
{title}
diff --git a/src/layouts/Sidebar.tsx b/src/layouts/Sidebar.tsx
index be9861aaf..5c015fb9a 100644
--- a/src/layouts/Sidebar.tsx
+++ b/src/layouts/Sidebar.tsx
@@ -16,13 +16,13 @@ import {
} from '@/hooks/api/user';
import { useCookiesWithOptions } from '@/hooks/useCookiesWithOptions';
import { FeatureFlags, useFeatureFlag } from '@/hooks/useFeatureFlag';
+import {
+ useHandleWindowMessage,
+ WindowMessageTypes,
+} from '@/hooks/useHandleWindowMessage';
import { useLocalStorage } from '@/hooks/useLocalStorage';
import { useMatchBreakpoint } from '@/hooks/useMatchBreakpoint';
-import {
- Features,
- NewFeatureTooltip,
- QuestionCircleIcon,
-} from '@/pages/NewFeatureTooltip';
+import { QuestionCircleIcon } from '@/pages/NewFeatureTooltip';
import type { Values } from '@/types/Values';
import { Badge } from '@/ui/Badge';
import { Button, ButtonVariants } from '@/ui/Button';
@@ -317,6 +317,7 @@ const BetaVersionToggle = ({
const Sidebar = () => {
const { t, i18n } = useTranslation();
+ const queryClient = useQueryClient();
const storage = useLocalStorage();
const [isJobLoggerVisible, setIsJobLoggerVisible] = useState(true);
@@ -422,6 +423,11 @@ const Sidebar = () => {
// @ts-expect-error
}, [getRolesQuery.data]);
+ useHandleWindowMessage({
+ [WindowMessageTypes.OFFER_MODERATED]: () =>
+ queryClient.invalidateQueries(['events']),
+ });
+
const announcements = useMemo(
() =>
rawAnnouncements.map((announcement) => {
@@ -641,4 +647,4 @@ const Sidebar = () => {
];
};
-export { Sidebar };
+export { PermissionTypes, Sidebar };
diff --git a/src/pages/dashboard/index.page.tsx b/src/pages/dashboard/index.page.tsx
index 60169deb5..d114c2583 100644
--- a/src/pages/dashboard/index.page.tsx
+++ b/src/pages/dashboard/index.page.tsx
@@ -24,11 +24,13 @@ import {
useGetPlacesByCreatorQuery,
} from '@/hooks/api/places';
import {
+ useGetPermissionsQuery,
useGetUserQuery,
useGetUserQueryServerSide,
User,
} from '@/hooks/api/user';
import { FeatureFlags, useFeatureFlag } from '@/hooks/useFeatureFlag';
+import { PermissionTypes } from '@/layouts/Sidebar';
import { Footer } from '@/pages/Footer';
import type { Event } from '@/types/Event';
import { Offer } from '@/types/Offer';
@@ -360,6 +362,7 @@ const OrganizerRow = ({
const { t, i18n } = useTranslation();
const getUserQuery = useGetUserQuery();
+ const getPermissionsQuery = useGetPermissionsQuery();
// @ts-expect-error
const userId = getUserQuery.data?.sub;
// @ts-expect-error
@@ -372,6 +375,8 @@ const OrganizerRow = ({
const formattedAddress = address ? formatAddressInternal(address) : '';
const editUrl = `/organizer/${parseOfferId(organizer['@id'])}/edit`;
const previewUrl = `/organizer/${parseOfferId(organizer['@id'])}/preview`;
+ // @ts-expect-error
+ const permissions = getPermissionsQuery?.data ?? [];
return (
{t('dashboard.actions.edit')}
,
+ permissions?.includes(PermissionTypes.ORGANISATIES_BEHEREN) && (
+ onDelete(organizer)} key="delete">
+ {t('dashboard.actions.delete')}
+
+ ),
]}
status={{
isExternalCreator,
diff --git a/src/pages/steps/AdditionalInformationStep/BookingInfoStep.tsx b/src/pages/steps/AdditionalInformationStep/BookingInfoStep.tsx
index 0b1549c92..243f3891e 100644
--- a/src/pages/steps/AdditionalInformationStep/BookingInfoStep.tsx
+++ b/src/pages/steps/AdditionalInformationStep/BookingInfoStep.tsx
@@ -21,10 +21,10 @@ import { Text } from '@/ui/Text';
import { getGlobalBorderRadius, getValueFromTheme } from '@/ui/theme';
import { Title } from '@/ui/Title';
import { formatDateToISO } from '@/utils/formatDateToISO';
+import { isValidEmail, isValidPhone, isValidUrl } from '@/utils/isValidInfo';
import { prefixUrlWithHttps } from '@/utils/url';
import { TabContentProps, ValidationStatus } from './AdditionalInformationStep';
-import { isValidEmail, isValidPhone, isValidUrl } from './ContactInfoStep';
const schema = yup
.object({
diff --git a/src/pages/steps/AdditionalInformationStep/ContactInfoStep.test.js b/src/pages/steps/AdditionalInformationStep/ContactInfoStep.test.js
new file mode 100644
index 000000000..f88f83be7
--- /dev/null
+++ b/src/pages/steps/AdditionalInformationStep/ContactInfoStep.test.js
@@ -0,0 +1,16 @@
+import { isValidUrl } from '@/utils/isValidInfo';
+
+describe('isValidUrl', () => {
+ const tests = {
+ goobar: false,
+ 'goobar.com': false,
+ 'http://goobar.com': true,
+ 'https://speeltuin.vlaanderen/speeltuinen': true,
+ 'https://speeltuin.vlaanderen/speeltuinen?foo[]=bar&baz=50': true,
+ };
+
+ test.each(Object.entries(tests))(
+ 'can check if url %p being valid is %p',
+ (url, expected) => expect(isValidUrl(url)).toBe(expected),
+ );
+});
diff --git a/src/pages/steps/AdditionalInformationStep/ContactInfoStep.tsx b/src/pages/steps/AdditionalInformationStep/ContactInfoStep.tsx
index 28182f1a4..5daa73240 100644
--- a/src/pages/steps/AdditionalInformationStep/ContactInfoStep.tsx
+++ b/src/pages/steps/AdditionalInformationStep/ContactInfoStep.tsx
@@ -2,7 +2,6 @@ import { ChangeEvent, FormEvent, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
-import { EMAIL_REGEX, PHONE_REGEX, URL_REGEX } from '@/constants/Regex';
import { useAddContactPointMutation } from '@/hooks/api/offers';
import { useGetEntityByIdAndScope } from '@/hooks/api/scope';
import { Button, ButtonVariants } from '@/ui/Button';
@@ -12,6 +11,7 @@ import { Inline } from '@/ui/Inline';
import { Input } from '@/ui/Input';
import { Select } from '@/ui/Select';
import { getStackProps, Stack, StackProps } from '@/ui/Stack';
+import { isValidInfo } from '@/utils/isValidInfo';
import { prefixUrlWithHttps } from '@/utils/url';
import { TabContentProps, ValidationStatus } from './AdditionalInformationStep';
@@ -33,29 +33,6 @@ type NewContactInfo = {
value: string;
};
-const isValidEmail = (email: string) => {
- return (
- typeof email === 'undefined' || email === '' || EMAIL_REGEX.test(email)
- );
-};
-
-const isValidUrl = (url: string) => {
- return typeof url === 'undefined' || url === '' || URL_REGEX.test(url);
-};
-
-const isValidPhone = (phone: string) => {
- return (
- typeof phone === 'undefined' || phone === '' || PHONE_REGEX.test(phone)
- );
-};
-
-const isValidInfo = (type: string, value: string): boolean => {
- if (value === '') return true;
- if (type === 'email') return isValidEmail(value);
- if (type === 'url') return isValidUrl(value);
- if (type === 'phone') return isValidPhone(value);
-};
-
type Props = StackProps &
TabContentProps & {
isOrganizer?: boolean;
@@ -260,10 +237,12 @@ const ContactInfoStep = ({
))}
{
const newContactInfoState = [...contactInfoState];
@@ -277,7 +256,6 @@ const ContactInfoStep = ({
}}
/>
}
- id="contact-info-value"
error={
!isFieldFocused &&
!isValidInfo(info.type, info.value) &&
@@ -314,5 +292,5 @@ ContactInfoStep.defaultProps = {
isOrganizer: false,
};
-export { ContactInfoStep, isValidEmail, isValidPhone, isValidUrl };
+export { ContactInfoStep };
export type { ContactInfo };
diff --git a/src/pages/steps/AdditionalInformationStep/LabelsStep.tsx b/src/pages/steps/AdditionalInformationStep/LabelsStep.tsx
index 74abf845d..579c9cd74 100644
--- a/src/pages/steps/AdditionalInformationStep/LabelsStep.tsx
+++ b/src/pages/steps/AdditionalInformationStep/LabelsStep.tsx
@@ -27,7 +27,7 @@ import { Typeahead } from '@/ui/Typeahead';
type LabelsStepProps = StackProps & TabContentProps;
-const LABEL_PATTERN = /^[0-9a-zA-Z][0-9a-zA-Z-_\s]{1,49}$/;
+const LABEL_PATTERN = /^[0-9a-zA-ZÀ-ÿ][0-9a-zA-ZÀ-ÿ\-_\s]{1,49}$/;
function LabelsStep({
offerId,
diff --git a/src/pages/steps/AudienceStep.tsx b/src/pages/steps/AudienceStep.tsx
index 9ef7506bd..cd9534b35 100644
--- a/src/pages/steps/AudienceStep.tsx
+++ b/src/pages/steps/AudienceStep.tsx
@@ -1,5 +1,5 @@
import { yupResolver } from '@hookform/resolvers/yup';
-import { useEffect } from 'react';
+import { Fragment, useEffect } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import * as yup from 'yup';
@@ -87,25 +87,31 @@ const AudienceStep = ({
{t('create.additionalInformation.audience.title')}
- {Object.values(AudienceType).map((type, index) => (
- handleOnChangeAudience(type)}
+ {Object.values(AudienceType).map((type, index) => {
+ return (
+
+ handleOnChangeAudience(type)}
+ />
+ }
/>
- }
- />
- ))}
- {watchedAudienceType === AudienceType.EDUCATION && (
-
- {t('create.additionalInformation.audience.help')}
-
- )}
+ {watchedAudienceType === type &&
+ watchedAudienceType !== AudienceType.EVERYONE && (
+
+ {t(
+ `create.additionalInformation.audience.help.${watchedAudienceType}`,
+ )}
+
+ )}
+
+ );
+ })}
);
diff --git a/src/pages/steps/CalendarStep/CalendarStep.tsx b/src/pages/steps/CalendarStep/CalendarStep.tsx
index 24a2496e5..f383868c3 100644
--- a/src/pages/steps/CalendarStep/CalendarStep.tsx
+++ b/src/pages/steps/CalendarStep/CalendarStep.tsx
@@ -1,8 +1,10 @@
+import { useRouter } from 'next/router';
import { useEffect, useMemo, useRef } from 'react';
import { useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import * as yup from 'yup';
+import { BookingAvailabilityType } from '@/constants/BookingAvailabilityType';
import { CalendarType } from '@/constants/CalendarType';
import { eventTypesWithNoThemes } from '@/constants/EventTypes';
import { OfferStatus } from '@/constants/OfferStatus';
@@ -73,6 +75,39 @@ const useEditCalendar = ({ offerId, onSuccess }: UseEditArguments) => {
};
};
+const convertOfferToCalendarContext = (offer: Offer) => {
+ const initialContext = initialCalendarContext;
+
+ const days = (offer.subEvent ?? []).map((subEvent) => ({
+ id: createDayId(),
+ startDate: subEvent.startDate,
+ endDate: subEvent.endDate,
+ status: subEvent.status,
+ bookingAvailability: subEvent.bookingAvailability,
+ }));
+
+ const openingHours = (offer.openingHours ?? []).map((openingHour) => ({
+ id: createOpeninghoursId(),
+ opens: openingHour.opens,
+ closes: openingHour.closes,
+ dayOfWeek: openingHour.dayOfWeek,
+ }));
+
+ const newContext = {
+ ...initialContext,
+ ...(days.length > 0 && { days }),
+ ...(openingHours.length > 0 && { openingHours }),
+ ...(offer?.startDate && {
+ startDate: offer.startDate,
+ }),
+ ...(offer?.endDate && {
+ endDate: offer.endDate,
+ }),
+ };
+
+ return { newContext, calendarType: offer.calendarType };
+};
+
const convertStateToFormData = (
context: CalendarContext,
calendarType: Values,
@@ -127,6 +162,7 @@ const CalendarStep = ({
onChange,
...props
}: CalendarStepProps) => {
+ const router = useRouter();
const { t } = useTranslation();
const calendarStepContainer = useRef(null);
@@ -211,38 +247,20 @@ const CalendarStep = ({
const offer: Offer | undefined = getOfferByIdQuery.data;
useEffect(() => {
- const initialContext = initialCalendarContext;
+ const isOnDuplicatePage = router.pathname.endsWith('/duplicate');
if (!offer) return;
- const days = (offer.subEvent ?? []).map((subEvent) => ({
- id: createDayId(),
- startDate: subEvent.startDate,
- endDate: subEvent.endDate,
- status: subEvent.status,
- bookingAvailability: subEvent.bookingAvailability,
- }));
-
- const openingHours = (offer.openingHours ?? []).map((openingHour) => ({
- id: createOpeninghoursId(),
- opens: openingHour.opens,
- closes: openingHour.closes,
- dayOfWeek: openingHour.dayOfWeek,
- }));
-
- const newContext = {
- ...initialContext,
- ...(days.length > 0 && { days }),
- ...(openingHours.length > 0 && { openingHours }),
- ...(offer?.startDate && {
- startDate: offer.startDate,
- }),
- ...(offer?.endDate && {
- endDate: offer.endDate,
- }),
- };
+ const { newContext, calendarType } = convertOfferToCalendarContext(offer);
+ if (isOnDuplicatePage) {
+ newContext.days = newContext.days.map((day) => ({
+ ...day,
+ bookingAvailability: { type: BookingAvailabilityType.AVAILABLE },
+ status: { type: OfferStatus.AVAILABLE },
+ }));
+ }
- handleLoadInitialContext({ newContext, calendarType: offer.calendarType });
+ handleLoadInitialContext({ newContext, calendarType });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
handleLoadInitialContext,
diff --git a/src/pages/steps/LocationStep.tsx b/src/pages/steps/LocationStep.tsx
index 48ad6445e..217e1ba80 100644
--- a/src/pages/steps/LocationStep.tsx
+++ b/src/pages/steps/LocationStep.tsx
@@ -46,12 +46,12 @@ import { Text, TextVariants } from '@/ui/Text';
import { getValueFromTheme } from '@/ui/theme';
import { ToggleBox } from '@/ui/ToggleBox';
import { getLanguageObjectOrFallback } from '@/utils/getLanguageObjectOrFallback';
+import { isValidUrl } from '@/utils/isValidInfo';
import { parseOfferId } from '@/utils/parseOfferId';
import { prefixUrlWithHttps } from '@/utils/url';
import { CityPicker } from '../CityPicker';
import { Features, NewFeatureTooltip } from '../NewFeatureTooltip';
-import { isValidUrl } from './AdditionalInformationStep/ContactInfoStep';
import { CountryPicker } from './CountryPicker';
import { UseEditArguments } from './hooks/useEditField';
import { PlaceStep } from './PlaceStep';
diff --git a/src/test/e2e/create-movie.spec.ts b/src/test/e2e/create-movie.spec.ts
index c728df434..e6bea1736 100644
--- a/src/test/e2e/create-movie.spec.ts
+++ b/src/test/e2e/create-movie.spec.ts
@@ -50,18 +50,24 @@ test('create a movie', async ({ baseURL, page }) => {
const contactButton = await page.getByRole('button', {
name: 'Meer contactgegevens toevoegen',
});
- await page.locator('#contact-info-value').fill(faker.internet.email());
+ await page.getByTestId('contact-info-value').fill(faker.internet.email());
await contactButton.click();
await page.locator('select').nth(2).selectOption('phone');
await page
- .locator('#contact-info-value')
+ .getByTestId('contact-info-value')
.nth(1)
.fill(faker.phone.number('+336########'));
await contactButton.click();
await page.locator('select').nth(3).selectOption('url');
- await page.locator('#contact-info-value').nth(2).fill(faker.internet.url());
+ await page
+ .getByTestId('contact-info-value')
+ .nth(2)
+ .fill(faker.internet.url());
await contactButton.click();
- await page.locator('#contact-info-value').nth(3).fill(faker.internet.email());
+ await page
+ .getByTestId('contact-info-value')
+ .nth(3)
+ .fill(faker.internet.email());
await page.getByRole('tabpanel').getByRole('button').nth(3).click();
// Publish
diff --git a/src/test/e2e/create-place.spec.ts b/src/test/e2e/create-place.spec.ts
index d17c8c8d3..b7813286f 100644
--- a/src/test/e2e/create-place.spec.ts
+++ b/src/test/e2e/create-place.spec.ts
@@ -116,8 +116,10 @@ test('create a place', async ({ baseURL, page }) => {
// Contact
await page.getByRole('tab', { name: 'Contact' }).click();
await page.getByRole('button', { name: 'Contactgegevens toevoegen' }).click();
- await page.locator('#contact-info-value').click();
- await page.locator('#contact-info-value').fill(dummyPlace.contactInfo.email);
+ await page.getByTestId('contact-info-value').click();
+ await page
+ .getByTestId('contact-info-value')
+ .fill(dummyPlace.contactInfo.email);
await page
.getByRole('button', { name: 'Meer contactgegevens toevoegen' })
.click();
diff --git a/src/test/e2e/events/create-full-event.spec.ts b/src/test/e2e/events/create-full-event.spec.ts
index bb366c65d..e9b1c56c5 100644
--- a/src/test/e2e/events/create-full-event.spec.ts
+++ b/src/test/e2e/events/create-full-event.spec.ts
@@ -137,8 +137,10 @@ test('create event with all possible fields filled in', async ({
// Contact
await page.getByRole('tab', { name: 'Contact' }).click();
await page.getByRole('button', { name: 'Contactgegevens toevoegen' }).click();
- await page.locator('#contact-info-value').click();
- await page.locator('#contact-info-value').fill(dummyEvent.contactInfo.email);
+ await page.getByTestId('contact-info-value').click();
+ await page
+ .getByTestId('contact-info-value')
+ .fill(dummyEvent.contactInfo.email);
await page
.getByRole('button', { name: 'Meer contactgegevens toevoegen' })
.click();
diff --git a/src/ui/Input.tsx b/src/ui/Input.tsx
index 78aa4052c..3a0a5c314 100644
--- a/src/ui/Input.tsx
+++ b/src/ui/Input.tsx
@@ -92,6 +92,7 @@ const Input = forwardRef(
disabled={disabled}
onFocus={onFocus}
onKeyDown={onKeyDown}
+ data-testid={props['data-testid']}
{...getBoxProps(props)}
/>
),
diff --git a/src/ui/Pagination.tsx b/src/ui/Pagination.tsx
index 0ecd4f381..e7703b940 100644
--- a/src/ui/Pagination.tsx
+++ b/src/ui/Pagination.tsx
@@ -60,13 +60,26 @@ const Pagination = ({
forwardedAs="ul"
justifyContent="center"
css={`
+ .page-item:first-child .page-link,
+ .page-item:last-child .page-link {
+ border-radius: ${getGlobalBorderRadius};
+ }
+
+ .page-item:nth-child(2) .page-link {
+ border-top-left-radius: ${getGlobalBorderRadius};
+ border-bottom-left-radius: ${getGlobalBorderRadius};
+ }
+
+ .page-item:nth-last-child(2) .page-link {
+ border-top-right-radius: ${getGlobalBorderRadius};
+ border-bottom-right-radius: ${getGlobalBorderRadius};
+ }
+
.page-link {
color: ${getValue('color')};
border-color: ${getValue('borderColor')};
padding: ${getValue('paddingY')} ${getValue('paddingX')};
- border-radius: ${getGlobalBorderRadius};
-
&:hover {
background-color: ${getValue('hoverBackgroundColor')};
color: ${getValue('hoverColor')};
@@ -86,20 +99,10 @@ const Pagination = ({
.prev-btn {
margin-right: 0.8rem;
-
- .page-link {
- border-top-left-radius: 8px;
- border-bottom-left-radius: 8px;
- }
}
.next-btn {
margin-left: 0.8rem;
-
- .page-link {
- border-top-right-radius: 8px;
- border-bottom-right-radius: 8px;
- }
}
`}
{...getInlineProps(props)}
diff --git a/src/utils/isValidInfo.ts b/src/utils/isValidInfo.ts
new file mode 100644
index 000000000..666089702
--- /dev/null
+++ b/src/utils/isValidInfo.ts
@@ -0,0 +1,32 @@
+const EMAIL_REGEX = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/;
+const PHONE_REGEX = /^[0-9\/\-_.+ ]{0,15}$/;
+
+const isValidEmail = (email: string) => {
+ return (
+ typeof email === 'undefined' || email === '' || EMAIL_REGEX.test(email)
+ );
+};
+
+const isValidUrl = (url: string) => {
+ try {
+ new URL(url);
+ return true;
+ } catch (e) {
+ return false;
+ }
+};
+
+const isValidPhone = (phone: string) => {
+ return (
+ typeof phone === 'undefined' || phone === '' || PHONE_REGEX.test(phone)
+ );
+};
+
+const isValidInfo = (type: string, value: string): boolean => {
+ if (value === '') return true;
+ if (type === 'email') return isValidEmail(value);
+ if (type === 'url') return isValidUrl(value);
+ if (type === 'phone') return isValidPhone(value);
+};
+
+export { isValidEmail, isValidInfo, isValidPhone, isValidUrl };