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 };