diff --git a/src/modules/project/forms/ProjectRegion.tsx b/src/modules/project/forms/ProjectRegion.tsx index 6a0b072f5..0baee39c6 100644 --- a/src/modules/project/forms/ProjectRegion.tsx +++ b/src/modules/project/forms/ProjectRegion.tsx @@ -1,5 +1,6 @@ import { useQuery } from '@apollo/client' import { HStack, IconButton, StackProps, useDisclosure, VStack } from '@chakra-ui/react' +import { useAtom } from 'jotai' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { PiX } from 'react-icons/pi' @@ -15,6 +16,7 @@ import { FieldContainer } from '../../../shared/components/form/FieldContainer' import { SkeletonLayout } from '../../../shared/components/layouts' import { Country, Location, Maybe, Project, ProjectCountriesGetResult, ProjectRegionsGetResult } from '../../../types' import { ProjectState } from '../state/projectAtom' +import { projectFormErrorAtom } from '../state/projectFormAtom' const useStyles = createUseStyles(({ colors }: AppTheme) => ({ container: { @@ -51,6 +53,8 @@ export const ProjectRegion = ({ location, updateProject, ...rest }: ProjectRegio const { t } = useTranslation() const classes = useStyles() + const [projectFormError, setProjectFormError] = useAtom(projectFormErrorAtom) + const [inputValue, setInputValue] = useState('') const { isOpen, onOpen, onClose } = useDisclosure() @@ -98,7 +102,12 @@ export const ProjectRegion = ({ location, updateProject, ...rest }: ProjectRegio } } + const clearLocationError = () => { + setProjectFormError((prev) => ({ ...prev, location: undefined })) + } + const removeRegion = () => { + clearLocationError() updateProject({ location: { region: '', country: { code: '', name: '' } }, }) @@ -114,7 +123,7 @@ export const ProjectRegion = ({ location, updateProject, ...rest }: ProjectRegio return ( {t('Get found more easily by putting your project on the map. Select a country or region')} } @@ -139,8 +148,11 @@ export const ProjectRegion = ({ location, updateProject, ...rest }: ProjectRegio inputValue={inputValue} onMenuOpen={onOpen} onMenuClose={onClose} + isInvalid={Boolean(projectFormError.location)} + onFocus={clearLocationError} /> )} + {displayLocation && ( @@ -160,6 +172,11 @@ export const ProjectRegion = ({ location, updateProject, ...rest }: ProjectRegio )} + {projectFormError.location && ( + + {projectFormError.location} + + )} ) } diff --git a/src/modules/project/pages1/projectCreation/constants.ts b/src/modules/project/pages1/projectCreation/constants.ts index cbde28d3e..ece74d6e5 100644 --- a/src/modules/project/pages1/projectCreation/constants.ts +++ b/src/modules/project/pages1/projectCreation/constants.ts @@ -327,3 +327,5 @@ export const tagToRewardCategoryMapping: { [key: string]: RewardCategory[] } = { homeschooling: [RewardCategory.Shoutout, RewardCategory.Course, RewardCategory.Experience], minecraft: [RewardCategory.Game], } + +export const ProjectCountryCodesThatAreRestricted = ['UA', 'PSE', 'YE', 'RU'] diff --git a/src/modules/project/pages1/projectCreation/hooks/useLocationMandatoryRedirect.tsx b/src/modules/project/pages1/projectCreation/hooks/useLocationMandatoryRedirect.tsx new file mode 100644 index 000000000..5c79791df --- /dev/null +++ b/src/modules/project/pages1/projectCreation/hooks/useLocationMandatoryRedirect.tsx @@ -0,0 +1,16 @@ +import { useEffect } from 'react' +import { useNavigate } from 'react-router' + +import { useProjectAtom } from '@/modules/project/hooks/useProjectAtom' +import { getPath } from '@/shared/constants' + +export const useLocationMandatoryRedirect = () => { + const navigate = useNavigate() + const { project, loading } = useProjectAtom() + + useEffect(() => { + if (project && !loading && !project.location?.country?.code) { + navigate(getPath('launchProjectDetails', project?.id)) + } + }, [loading, project, navigate]) +} diff --git a/src/modules/project/pages1/projectCreation/views/ProjectCreateStory.tsx b/src/modules/project/pages1/projectCreation/views/ProjectCreateStory.tsx index 8d5d01ae8..38657e701 100644 --- a/src/modules/project/pages1/projectCreation/views/ProjectCreateStory.tsx +++ b/src/modules/project/pages1/projectCreation/views/ProjectCreateStory.tsx @@ -10,6 +10,7 @@ import { dimensions, getPath } from '../../../../../shared/constants' import { ProjectStoryForm } from '../../../forms/ProjectStoryForm' import { FormContinueButton } from '../components/FormContinueButton' import { ProjectCreateLayout } from '../components/ProjectCreateLayout' +import { useLocationMandatoryRedirect } from '../hooks/useLocationMandatoryRedirect' import { useProjectStoryForm } from '../hooks/useProjectStoryForm' export const ProjectCreateStory = () => { @@ -25,6 +26,8 @@ export const ProjectCreateStory = () => { const form = useProjectStoryForm({ project }) + useLocationMandatoryRedirect() + const onLeave = () => { if (!project) { return navigate(-1) diff --git a/src/modules/project/pages1/projectCreation/views/ProjectCreationWalletConnectionPage.tsx b/src/modules/project/pages1/projectCreation/views/ProjectCreationWalletConnectionPage.tsx index 35a02add1..5d1e1f45c 100644 --- a/src/modules/project/pages1/projectCreation/views/ProjectCreationWalletConnectionPage.tsx +++ b/src/modules/project/pages1/projectCreation/views/ProjectCreationWalletConnectionPage.tsx @@ -14,6 +14,7 @@ import { useNotification } from '../../../../../utils' import { ProjectCreationWalletConnectionForm } from '..' import { FormContinueButton } from '../components/FormContinueButton' import { ProjectCreateLayout } from '../components/ProjectCreateLayout' +import { useLocationMandatoryRedirect } from '../hooks/useLocationMandatoryRedirect' import { ConnectionOption, useWalletForm } from '../hooks/useWalletForm' import { ProjectCreateCompletionPage } from './ProjectCreateCompletionPage' @@ -27,6 +28,8 @@ export const ProjectCreationWalletConnectionPage = () => { const { project, loading } = useProjectAtom() const { createWallet } = useProjectWalletAPI() + useLocationMandatoryRedirect() + const [isReadyForLaunch, setReadyForLaunch] = useState(false) const handleNext = (createWalletInput: CreateWalletInput | null) => { diff --git a/src/modules/project/pages1/projectCreation/views/ProjectDetails.tsx b/src/modules/project/pages1/projectCreation/views/ProjectDetails.tsx index 9648157a3..cf3b1466e 100644 --- a/src/modules/project/pages1/projectCreation/views/ProjectDetails.tsx +++ b/src/modules/project/pages1/projectCreation/views/ProjectDetails.tsx @@ -1,8 +1,10 @@ import { VStack } from '@chakra-ui/react' +import { useSetAtom } from 'jotai' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { useProjectDetailsAPI } from '@/modules/project/API/useProjectDetailsAPI' +import { projectFormErrorAtom } from '@/modules/project/state/projectFormAtom' import TitleWithProgressBar from '../../../../../components/molecules/TitleWithProgressBar' import { getPath } from '../../../../../shared/constants' @@ -12,27 +14,47 @@ import { ProjectRegion } from '../../../forms/ProjectRegion' import { ProjectTagsCreateEdit } from '../../../forms/ProjectTagsCreateEdit' import { FormContinueButton } from '../components/FormContinueButton' import { ProjectCreateLayout } from '../components/ProjectCreateLayout' +import { ProjectCountryCodesThatAreRestricted } from '../constants' import { useProjectDetailsForm } from '../hooks/useProjectDetailsForm' export const ProjectDetails = () => { const { t } = useTranslation() const navigate = useNavigate() - const { toast, unexpected } = useNotification() + const toast = useNotification() const { queryProjectDetails } = useProjectDetailsAPI(true) - const { updateProject, saveProject, saveTags, setLinks, setTags, project, tags, linkError, tagsLoading, isDirty } = + const { updateProject, saveProject, saveTags, setLinks, setTags, project, tags, linkError, tagsLoading } = useProjectDetailsForm() + const setProjectFormError = useSetAtom(projectFormErrorAtom) + const onSubmit = async () => { if (!project) { return } + if (!project.location || !project.location.country || !project.location.country.code) { + toast.error({ + title: 'Please select a region', + description: 'Project region is required to proceed', + }) + setProjectFormError((prev) => ({ ...prev, location: 'Project region is required' })) + return + } + + if (ProjectCountryCodesThatAreRestricted.includes(project.location.country.code)) { + toast.error({ + title: 'Country not supported', + description: 'We are not able to support projects from this country', + }) + setProjectFormError((prev) => ({ ...prev, location: 'Country not supported' })) + return + } + if (linkError.includes(true)) { - toast({ - status: 'warning', + toast.warning({ title: 'failed to update project', description: 'please enter a valid url for project links', }) @@ -45,7 +67,7 @@ export const ProjectDetails = () => { navigate(getPath('launchProjectStory', project.id)) } catch (e) { - unexpected() + toast.unexpected() } } @@ -66,7 +88,7 @@ export const ProjectDetails = () => { return ( <> } + continueButton={} onBackClick={onBackClick} title={} > diff --git a/src/modules/project/pages1/projectCreation/views/rewards/ProjectCreateRewards.tsx b/src/modules/project/pages1/projectCreation/views/rewards/ProjectCreateRewards.tsx index adb83a2dc..da1fd2e97 100644 --- a/src/modules/project/pages1/projectCreation/views/rewards/ProjectCreateRewards.tsx +++ b/src/modules/project/pages1/projectCreation/views/rewards/ProjectCreateRewards.tsx @@ -8,6 +8,7 @@ import TitleWithProgressBar from '../../../../../../components/molecules/TitleWi import { getPath } from '../../../../../../shared/constants' import { FormContinueButton } from '../../components/FormContinueButton' import { ProjectCreateLayout } from '../../components/ProjectCreateLayout' +import { useLocationMandatoryRedirect } from '../../hooks/useLocationMandatoryRedirect' export const ProjectCreateRewards = () => { const { t } = useTranslation() @@ -18,6 +19,8 @@ export const ProjectCreateRewards = () => { const { project } = useProjectAtom() const { rewards } = useRewardsAtom() + useLocationMandatoryRedirect() + const isNew = useMatch(getPath('launchProjectRewardsCreate', project?.id)) const isEdit = useMatch(getPath('launchProjectRewardsEdit', project?.id, ':rewardId')) const isCreatingOrEditing = isNew || isEdit diff --git a/src/modules/project/pages1/projectDashboard/views/ProjectDashboardDetails.tsx b/src/modules/project/pages1/projectDashboard/views/ProjectDashboardDetails.tsx index e9301a10e..c7c224668 100644 --- a/src/modules/project/pages1/projectDashboard/views/ProjectDashboardDetails.tsx +++ b/src/modules/project/pages1/projectDashboard/views/ProjectDashboardDetails.tsx @@ -1,12 +1,15 @@ import { Button, ButtonProps, VStack } from '@chakra-ui/react' +import { useSetAtom } from 'jotai' import { useTranslation } from 'react-i18next' import { useProjectDetailsAPI } from '@/modules/project/API/useProjectDetailsAPI' import { ProjectLinks } from '@/modules/project/forms/ProjectLinks' import { ProjectRegion } from '@/modules/project/forms/ProjectRegion' import { ProjectTagsCreateEdit } from '@/modules/project/forms/ProjectTagsCreateEdit' +import { projectFormErrorAtom } from '@/modules/project/state/projectFormAtom' import { useNotification } from '@/utils' +import { ProjectCountryCodesThatAreRestricted } from '../../projectCreation/constants' import { useProjectDetailsForm } from '../../projectCreation/hooks/useProjectDetailsForm' import { DashboardLayout } from '../common' import { ProjectUnsavedModal, useProjectUnsavedModal } from '../common/ProjectUnsavedModal' @@ -19,12 +22,31 @@ export const ProjectDashboardDetails = () => { const { project, isDirty, linkError, saveProject, saving, saveTags, setLinks, setTags, tags, updateProject } = useProjectDetailsForm() + const setProjectFormError = useSetAtom(projectFormErrorAtom) const unsavedModal = useProjectUnsavedModal({ hasUnsaved: isDirty, }) const onSubmit = async () => { + if (!project.location || !project.location.country || !project.location.country.code) { + toast.error({ + title: 'Please select a region', + description: 'Project region is required to proceed', + }) + setProjectFormError((prev) => ({ ...prev, location: 'Project region is required' })) + return + } + + if (ProjectCountryCodesThatAreRestricted.includes(project.location.country.code)) { + toast.error({ + title: 'Country not supported', + description: 'We are not able to support projects from this country', + }) + setProjectFormError((prev) => ({ ...prev, location: 'Country not supported' })) + return + } + if (linkError.includes(true)) { toast.warning({ title: 'failed to update project', diff --git a/src/modules/project/state/projectFormAtom.ts b/src/modules/project/state/projectFormAtom.ts index 2010e1e5b..9a87a8fc5 100644 --- a/src/modules/project/state/projectFormAtom.ts +++ b/src/modules/project/state/projectFormAtom.ts @@ -38,7 +38,12 @@ export const diffProjectAtom = atom((get) => { ]) }) +type ProjectFormError = { [key in keyof ProjectState]: string } +/** Project form error state */ +export const projectFormErrorAtom = atom({} as ProjectFormError) + /** Reset all real-atoms in this file to it's initial State */ export const projectFormAtomReset = atom(null, (get, set) => { set(formProjectAtom, {} as ProjectState) + set(projectFormErrorAtom, {} as ProjectFormError) })