diff --git a/src/components/Account/Teams.tsx b/src/components/Account/Teams.tsx index d083f03a..1e86017c 100644 --- a/src/components/Account/Teams.tsx +++ b/src/components/Account/Teams.tsx @@ -1,10 +1,11 @@ import { Avatar, Badge, Box, HStack, VStack } from '@chakra-ui/react' import { OrganizationName } from '@vocdoni/chakra-components' import { OrganizationProvider } from '@vocdoni/react-providers' +import { NoOrganizations } from '~components/Organization/NoOrganizations' import { UserRole } from '~src/queries/account' const Teams = ({ roles }: { roles: UserRole[] }) => { - if (!roles) return null + if (!roles || !roles.length) return return ( diff --git a/src/components/Auth/SignIn.tsx b/src/components/Auth/SignIn.tsx index 95a8db19..3c03afed 100644 --- a/src/components/Auth/SignIn.tsx +++ b/src/components/Auth/SignIn.tsx @@ -7,7 +7,7 @@ import { NavLink, useNavigate } from 'react-router-dom' import { api, ApiEndpoints, UnverifiedApiError } from '~components/Auth/api' import { ILoginParams } from '~components/Auth/authQueries' import { useAuth } from '~components/Auth/useAuth' -import { VerifyAccountNeeded } from '~components/Auth/Verify' +import { VerificationPending } from '~components/Auth/Verify' import FormSubmitMessage from '~components/Layout/FormSubmitMessage' import InputPassword from '~components/Layout/InputPassword' import { Routes } from '~src/router/routes' @@ -22,10 +22,9 @@ type FormData = { const useVerificationCodeStatus = () => useMutation({ mutationFn: async (email: string) => { - const response = await api<{ email: string; expiration: string; valid: boolean }>( + return await api<{ email: string; expiration: string; valid: boolean }>( `${ApiEndpoints.VerifyCode}?email=${encodeURIComponent(email)}` ) - return response }, }) @@ -92,7 +91,7 @@ const SignIn = ({ email: emailProp }: { email?: string }) => { } if (verifyNeeded) { - return + return } return ( diff --git a/src/components/Auth/SignUp.tsx b/src/components/Auth/SignUp.tsx index cd14e30c..a34c7acd 100644 --- a/src/components/Auth/SignUp.tsx +++ b/src/components/Auth/SignUp.tsx @@ -4,7 +4,7 @@ import { Trans, useTranslation } from 'react-i18next' import { Navigate, NavLink, Link as ReactRouterLink } from 'react-router-dom' import { IRegisterParams } from '~components/Auth/authQueries' import { useAuth } from '~components/Auth/useAuth' -import { VerifyAccountNeeded } from '~components/Auth/Verify' +import { VerificationPending } from '~components/Auth/Verify' import FormSubmitMessage from '~components/Layout/FormSubmitMessage' import InputPassword from '~components/Layout/InputPassword' import { useSignupFromInvite } from '~src/queries/account' @@ -59,7 +59,7 @@ const SignUp = ({ invite }: SignupProps) => { // normally registered accounts need verification if (register.isSuccess) { - return + return } // accounts coming from invites don't need verification diff --git a/src/components/Auth/Verify.tsx b/src/components/Auth/Verify.tsx index e7e9e383..7cb1372c 100644 --- a/src/components/Auth/Verify.tsx +++ b/src/components/Auth/Verify.tsx @@ -1,72 +1,14 @@ -import { Box, Button, Divider, Flex, FormControl, FormErrorMessage, Heading, Input, Text } from '@chakra-ui/react' +import { Button, Divider, Flex, Input, Text } from '@chakra-ui/react' import { useCallback, useEffect, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' -import { Link as ReactRouterLink, useNavigate, useOutletContext, useSearchParams } from 'react-router-dom' +import { Link as ReactRouterLink, useNavigate, useOutletContext } from 'react-router-dom' import { useResendVerificationMail } from '~components/Auth/authQueries' import { useAuth } from '~components/Auth/useAuth' import FormSubmitMessage from '~components/Layout/FormSubmitMessage' import { AuthOutletContextType } from '~elements/LayoutAuth' import { Routes } from '~src/router/routes' -import { Loading } from '~src/router/SuspenseLoader' -const Verify = () => { - const navigate = useNavigate() - const { t } = useTranslation() - const [searchParams] = useSearchParams() - const { - mailVerify: { isIdle, isPending, isError: isMutationError, error, mutateAsync }, - } = useAuth() - - const email = searchParams.get('email') - const code = searchParams.get('code') - const isLoading = isIdle || isPending - const isError = !email || isMutationError || (import.meta.env.VOCDONI_ENVIRONMENT !== 'dev' && !code) - - // Trigger email verification on component load - useEffect(() => { - mutateAsync({ email, code }).then(() => navigate(Routes.dashboard.base)) - }, []) - - let title = t('verify_mail.verifying_title', { email: email, defaultValue: 'Verifying {{ email }}' }) - let subTitle = t('verify_mail.verifying_subtitle', { - defaultValue: 'Await until we verify your email address. You will be redirect on success.', - }) - // dev enviorment permits empty code - if (isError) { - title = t('verify_mail.error_title', { email: email, defaultValue: 'Error verifying {{ email }}' }) - subTitle = t('verify_mail.error_subtitle', { - defaultValue: - 'We found an error verifying your email, please check verification mail to ensure all data is correct', - }) - } - - return ( - - - - {title} - - - {subTitle} - - - {isLoading && !isError && ( - - - - )} - - - {isError && ( - - {error?.message || t('error.error_doing_things', { defaultValue: 'Error al realizar la operación' })} - - )} - - - - ) -} +export const verificationSuccessRedirect = Routes.auth.organizationCreate interface IVerifyAccountProps { email: string @@ -81,7 +23,7 @@ const VerifyForm = ({ email }: IVerifyAccountProps) => { } = useAuth() const verify = useCallback(() => { - verifyAsync({ email, code }).then(() => navigate(Routes.dashboard.base)) + verifyAsync({ email, code }).then(() => navigate(verificationSuccessRedirect)) }, [code, email]) const handleInputChange = (event: React.ChangeEvent) => { @@ -105,7 +47,7 @@ const VerifyForm = ({ email }: IVerifyAccountProps) => { ) } -export const VerifyAccountNeeded = ({ email }: IVerifyAccountProps) => { +export const VerificationPending = ({ email }: IVerifyAccountProps) => { const { t } = useTranslation() const { setTitle, setSubTitle } = useOutletContext() const { @@ -168,5 +110,3 @@ export const VerifyAccountNeeded = ({ email }: IVerifyAccountProps) => { ) } - -export default Verify diff --git a/src/components/Home/Clients.tsx b/src/components/Home/Clients.tsx index 24b67e0e..a463f8c2 100644 --- a/src/components/Home/Clients.tsx +++ b/src/components/Home/Clients.tsx @@ -1,4 +1,4 @@ -import { Card, CardBody, CardHeader, Grid, Image, Text } from '@chakra-ui/react' +import { Card, CardBody, CardHeader, Grid, GridProps, Image, Text } from '@chakra-ui/react' import { useTranslation } from 'react-i18next' import barca from '/assets/barca.png' import bellpuig from '/assets/bellpuig.svg.png' @@ -26,104 +26,109 @@ const Clients = () => { > {t('home.clients_title')} - - - - - - - F.C. Barcelona - - - - - - - - Omnium Cultural - - - - - - - - Ajuntament Berga - - - - - - - - Ajuntament la Bisbal - - - - - - - - COEC - - - - - - - - Esquerra Republicana - - - - - - - - Ajuntament Bellpuig - - - - - - - - TIC Anoia - - - - - - - - Decidim - - - - - - - - Bloock - - - ) } + +export const ClientsGrid = (props: GridProps) => ( + + + + + + + F.C. Barcelona + + + + + + + + Omnium Cultural + + + + + + + + Ajuntament Berga + + + + + + + + Ajuntament la Bisbal + + + + + + + + COEC + + + + + + + + Esquerra Republicana + + + + + + + + Ajuntament Bellpuig + + + + + + + + TIC Anoia + + + + + + + + Decidim + + + + + + + + Bloock + + + +) + export default Clients diff --git a/src/components/Organization/Create.tsx b/src/components/Organization/Create.tsx index 6b2aac3f..cb9acaad 100644 --- a/src/components/Organization/Create.tsx +++ b/src/components/Organization/Create.tsx @@ -1,17 +1,19 @@ -import { Box, Flex, FlexProps, Heading, Text } from '@chakra-ui/react' -import { Button } from '@vocdoni/chakra-components' -import { FormProvider, useForm } from 'react-hook-form' -import { Trans, useTranslation } from 'react-i18next' +import { Flex, FlexProps, Stack, Text } from '@chakra-ui/react' import { useMutation, UseMutationOptions } from '@tanstack/react-query' +import { Button } from '@vocdoni/chakra-components' import { useClient } from '@vocdoni/react-providers' import { useState } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import { Trans, useTranslation } from 'react-i18next' +import { Link as ReactRouterLink, useNavigate } from 'react-router-dom' import { CreateOrgParams } from '~components/Account/AccountTypes' import LogoutBtn from '~components/Account/LogoutBtn' import { useAccountCreate } from '~components/Account/useAccountCreate' import { ApiEndpoints } from '~components/Auth/api' import { useAuth } from '~components/Auth/useAuth' import FormSubmitMessage from '~components/Layout/FormSubmitMessage' +import { Routes } from '~src/router/routes' import { PrivateOrgForm, PrivateOrgFormData, PublicOrgForm } from './Form' type FormData = PrivateOrgFormData & CreateOrgParams @@ -33,9 +35,17 @@ const useOrganizationCreate = (options?: Omit { +export const OrganizationCreate = ({ + canSkip, + onSuccessRoute = Routes.dashboard.base, + ...props +}: { + onSuccessRoute?: number | string + canSkip?: boolean +} & FlexProps) => { const { t } = useTranslation() + const navigate = useNavigate() const [isPending, setIsPending] = useState(false) const methods = useForm() @@ -61,11 +71,16 @@ export const OrganizationCreate = ({ children, ...props }: FlexProps) => { }) .then(() => signer.getAddress()) // Get the address of newly created signer .then(() => + // Create the new account on the vochain createAccount({ name: typeof values.name === 'object' ? values.name.default : values.name, description: typeof values.description === 'object' ? values.description.default : values.description, }) - ) // Create the new account on the vochain + ) + .then(() => { + // In case of success, redirect to the success route + navigate(onSuccessRoute as unknown) + }) .finally(() => setIsPending(false)) } @@ -87,24 +102,25 @@ export const OrganizationCreate = ({ children, ...props }: FlexProps) => { handleSubmit(onSubmit)(e) }} > - {children} - - - Create Your Organization - - - + + {canSkip && ( + + )} + + - + If your organization already have a profile, ask the admin to invite you to your organization. - + If you want to login from another account, please logout diff --git a/src/components/Organization/Dashboard/Create.tsx b/src/components/Organization/Dashboard/Create.tsx deleted file mode 100644 index 990f370a..00000000 --- a/src/components/Organization/Dashboard/Create.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Box, Button, Flex, Grid, Heading, Image, ListItem, Text, UnorderedList } from '@chakra-ui/react' -import { Trans } from 'react-i18next' -import { OrganizationCreate } from '../Create' -import AuthBanner from './AuthBanner' -import barca from '/assets/barca.png' -import bellpuig from '/assets/bellpuig.svg.png' -import berga from '/assets/berga.svg.png' -import bisbal from '/assets/bisbal.svg' -import bloock from '/assets/bloock.png' -import coec from '/assets/coec.png' -import decidim from '/assets/decidim.png' -import erc from '/assets/erc.svg' -import omnium from '/assets/omnium.png' -import ticanoia from '/assets/ticanoia.png' - -const CreateOrganization = () => { - return ( - - - - - - - Try Vocdoni for free for 7 days - - - - - Full access to basic features - - - Unlimited creation of voting processes - - - Multiple administrators - - - Up to 20 voters - - - Support during the trial period - - - - - No credit card, no automatic renewal. - - - - - - - - - Trust on us - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The global voting platform - - - Cut cost, Save Time: Secure, Private, and GDPR Compliant - Voting - - - - - - ) -} - -export default CreateOrganization diff --git a/src/components/Organization/NoOrganizations.tsx b/src/components/Organization/NoOrganizations.tsx new file mode 100644 index 00000000..7974d064 --- /dev/null +++ b/src/components/Organization/NoOrganizations.tsx @@ -0,0 +1,22 @@ +import { Box, Flex } from '@chakra-ui/react' +import { Button } from '@vocdoni/chakra-components' +import { Link as ReactRouterLink } from 'react-router-dom' +import { DashboardContents } from '~components/Layout/Dashboard' +import { Routes } from '~src/router/routes' + +export const NoOrganizations = () => { + return ( + + You don't belong to any organization yet! + + + ) +} + +export const NoOrganizationsPage = () => ( + + + +) diff --git a/src/elements/LayoutAuth.tsx b/src/elements/LayoutAuth.tsx index df0dd4e0..c23704b0 100644 --- a/src/elements/LayoutAuth.tsx +++ b/src/elements/LayoutAuth.tsx @@ -1,13 +1,17 @@ import { Box, Flex, Heading, Icon, Text } from '@chakra-ui/react' -import { useState } from 'react' +import { ReactNode, useState } from 'react' import { useTranslation } from 'react-i18next' import { FaChevronLeft } from 'react-icons/fa' -import { NavLink, Outlet } from 'react-router-dom' +import { NavLink, Outlet, To } from 'react-router-dom' import AuthBanner from '~components/Organization/Dashboard/AuthBanner' +export type NavigationFunctionParams = To | number + export type AuthOutletContextType = { setTitle: React.Dispatch> setSubTitle: React.Dispatch> + setBack: React.Dispatch> + setSidebar: React.Dispatch> } const LayoutAuth = () => { @@ -15,6 +19,8 @@ const LayoutAuth = () => { const [title, setTitle] = useState('') const [subTitle, setSubTitle] = useState('') + const [sidebar, setSidebar] = useState(null) + const [back, setBack] = useState('/') return ( { flexDirection={{ base: 'column', xl: 'row' }} > - + @@ -55,7 +61,7 @@ const LayoutAuth = () => { {subTitle} - + { + {sidebar} diff --git a/src/elements/account/createOrganization.tsx b/src/elements/account/createOrganization.tsx new file mode 100644 index 00000000..48d629f8 --- /dev/null +++ b/src/elements/account/createOrganization.tsx @@ -0,0 +1,57 @@ +import { Box, Button, Flex, Heading, ListItem, Text, UnorderedList } from '@chakra-ui/react' +import { useEffect } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { useOutletContext } from 'react-router-dom' +import { ClientsGrid } from '~components/Home/Clients' +import { OrganizationCreate } from '~components/Organization/Create' +import { AuthOutletContextType } from '~elements/LayoutAuth' +import { Routes } from '~src/router/routes' + +const CreateOrganization = () => { + const { t } = useTranslation() + const { setTitle, setBack: setBackBtn, setSidebar } = useOutletContext() + + // Set layout title and subtitle and back button + useEffect(() => { + setTitle(t('create_org.title', { defaultValue: 'Create your organization' })) + setSidebar(CreateOrganizationSidebar) + setBackBtn(Routes.dashboard.base) + }, []) + + return +} + +const CreateOrganizationSidebar = () => ( + + + Try Vocdoni for free for 7 days + + + + + Full access to basic features + + + Unlimited creation of voting processes + + + Multiple administrators + + + Up to 20 voters + + + Support during the trial period + + + + + No credit card, no automatic renewal. + + + + +) +export default CreateOrganization diff --git a/src/elements/account/verify.tsx b/src/elements/account/verify.tsx new file mode 100644 index 00000000..8ca37aec --- /dev/null +++ b/src/elements/account/verify.tsx @@ -0,0 +1,72 @@ +import { Box, Flex, FormControl, FormErrorMessage, Heading, Text } from '@chakra-ui/react' +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate, useSearchParams } from 'react-router-dom' +import { useAuth } from '~components/Auth/useAuth' +import { verificationSuccessRedirect } from '~components/Auth/Verify' +import { Loading } from '~src/router/SuspenseLoader' + +/** + * This page reads the email and code from the URL and triggers the email verification automatically + * @constructor + */ +const Verify = () => { + const navigate = useNavigate() + const { t } = useTranslation() + const [searchParams] = useSearchParams() + const { + mailVerify: { isIdle, isPending, isError: isMutationError, error, mutateAsync }, + } = useAuth() + + const email = searchParams.get('email') + const code = searchParams.get('code') + const isLoading = isIdle || isPending + const isError = !email || isMutationError || (import.meta.env.VOCDONI_ENVIRONMENT !== 'dev' && !code) + + // Trigger email verification on component load + useEffect(() => { + mutateAsync({ email, code }).then(() => navigate(verificationSuccessRedirect)) + }, []) + + let title = t('verify_mail.verifying_title', { email: email, defaultValue: 'Verifying {{ email }}' }) + let subTitle = t('verify_mail.verifying_subtitle', { + defaultValue: 'Await until we verify your email address. You will be redirect on success.', + }) + // dev enviorment permits empty code + if (isError) { + title = t('verify_mail.error_title', { email: email, defaultValue: 'Error verifying {{ email }}' }) + subTitle = t('verify_mail.error_subtitle', { + defaultValue: + 'We found an error verifying your email, please check verification mail to ensure all data is correct', + }) + } + + return ( + + + + {title} + + + {subTitle} + + + {isLoading && !isError && ( + + + + )} + + + {isError && ( + + {error?.message || t('error.error_doing_things', { defaultValue: 'Error al realizar la operación' })} + + )} + + + + ) +} + +export default Verify diff --git a/src/elements/dashboard/organization/createOrganization.tsx b/src/elements/dashboard/organization/createOrganization.tsx new file mode 100644 index 00000000..ef705262 --- /dev/null +++ b/src/elements/dashboard/organization/createOrganization.tsx @@ -0,0 +1,28 @@ +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useOutletContext } from 'react-router-dom' +import { DashboardContents } from '~components/Layout/Dashboard' +import { OrganizationCreate } from '~components/Organization/Create' +import { DashboardLayoutContext } from '~elements/LayoutDashboard' + +const DashBoardCreateOrg = () => { + const { t } = useTranslation() + const [onSuccessRoute, setOnSuccessRoute] = useState(null) + const { setTitle } = useOutletContext() + + // Set layout title and subtitle and back button + useEffect(() => { + setTitle(t('create_org.title', { defaultValue: 'Organization' })) + if (window.history.state.idx) { + setOnSuccessRoute(-1) + } + }, []) + + return ( + + + + ) +} + +export default DashBoardCreateOrg diff --git a/src/i18n/locales/ca.json b/src/i18n/locales/ca.json index 4b3d169e..01fefc3a 100644 --- a/src/i18n/locales/ca.json +++ b/src/i18n/locales/ca.json @@ -1034,6 +1034,7 @@ "signup_subtitle": "Introdueix el teu correu electrònic i contrasenya per registrar-te!", "signup_title": "Registra't", "single": "single", + "skip": "Ometre", "smsNotification": "smsNotification", "submit": "Submit", "subscribe": "Subscribe", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 60060b7d..8d59c282 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1023,6 +1023,7 @@ "signup_subtitle": "Enter your email and password to sign up!", "signup_title": "Sign Up", "single": "single", + "skip": "Skip", "smsNotification": "smsNotification", "submit": "Submit", "subscribe": "Subscribe", diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 52247a1c..802de1db 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -1034,6 +1034,7 @@ "signup_subtitle": "¡Introduce tu correo electrónico y contraseña para registrarte!", "signup_title": "Regístrate", "single": "single", + "skip": "Omitir", "smsNotification": "smsNotification", "submit": "Submit", "subscribe": "Subscribe", diff --git a/src/router/AccountProtectedRoute.tsx b/src/router/AccountProtectedRoute.tsx new file mode 100644 index 00000000..799a065f --- /dev/null +++ b/src/router/AccountProtectedRoute.tsx @@ -0,0 +1,26 @@ +import { useClient } from '@vocdoni/react-providers' +import { Navigate, Outlet, useOutletContext } from 'react-router-dom' +import { useAuth } from '~components/Auth/useAuth' +import { Loading } from '~src/router/SuspenseLoader' +import { Routes } from './routes' + +const AccountProtectedRoute = () => { + const context = useOutletContext() + const { + loaded: { account: fetchLoaded }, + loading: { account: fetchLoading }, + } = useClient() + const { isAuthenticated, isAuthLoading } = useAuth() + + if ((!fetchLoaded && fetchLoading) || isAuthLoading) { + return + } + + if (!isAuthenticated) { + return + } + + return +} + +export default AccountProtectedRoute diff --git a/src/router/OrganizationProtectedRoute.tsx b/src/router/OrganizationProtectedRoute.tsx index e4a9be5e..103006e2 100644 --- a/src/router/OrganizationProtectedRoute.tsx +++ b/src/router/OrganizationProtectedRoute.tsx @@ -1,32 +1,20 @@ -import { useClient } from '@vocdoni/react-providers' -import { Navigate, Outlet } from 'react-router-dom' +import { Outlet, useOutletContext } from 'react-router-dom' import { useAccountHealthTools } from '~components/Account/use-account-health-tools' import { useAuth } from '~components/Auth/useAuth' -import CreateOrganization from '~components/Organization/Dashboard/Create' -import { Loading } from '~src/router/SuspenseLoader' -import { Routes } from './routes' +import { NoOrganizationsPage } from '~components/Organization/NoOrganizations' // This protected routes are supposed to be inside of a AccountProtectedRoute +// This protected routes are supposed to be inside of a AccountProtectedRoute +// So no auth/loading checks are performed here const OrganizationProtectedRoute = () => { - const { - loaded: { account: fetchLoaded }, - loading: { account: fetchLoading }, - } = useClient() + const context = useOutletContext() const { exists } = useAccountHealthTools() - const { isAuthenticated, isAuthLoading, signerAddress } = useAuth() - - if ((!fetchLoaded && fetchLoading) || isAuthLoading) { - return - } - - if (!isAuthenticated) { - return - } + const { signerAddress } = useAuth() if (!exists && !signerAddress) { - return + return } - return + return } export default OrganizationProtectedRoute diff --git a/src/router/ProtectedRoutes.tsx b/src/router/ProtectedRoutes.tsx index df6baef6..b68ec721 100644 --- a/src/router/ProtectedRoutes.tsx +++ b/src/router/ProtectedRoutes.tsx @@ -1,10 +1,24 @@ -import { Navigate, Outlet } from 'react-router-dom' -import { useAuth } from '~components/Auth/useAuth' +import AccountProtectedRoute from '~src/router/AccountProtectedRoute' +import OrganizationProtectedRoute from '~src/router/OrganizationProtectedRoute' +import { RouteObject } from 'react-router-dom' +import { SuspenseLoader } from '~src/router/SuspenseLoader' -const ProtectedRoutes = () => { - const { signerAddress } = useAuth() - - return signerAddress ? : -} +const ProtectedRoutes = (children: RouteObject[]) => ({ + element: ( + + + + ), + children: [ + { + element: ( + + + + ), + children, + }, + ], +}) export default ProtectedRoutes diff --git a/src/router/Router.tsx b/src/router/Router.tsx index 984be6c2..4b9ea2a1 100644 --- a/src/router/Router.tsx +++ b/src/router/Router.tsx @@ -1,5 +1,5 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom' -import { useAuthRoutes } from './routes/auth' +import { useAuthRoutes, useCreateOrganizationRoutes } from './routes/auth' import { useDashboardRoutes } from './routes/dashboard' import { useProcessCreateRoutes } from './routes/process-create' import { useRootRoutes } from './routes/root' @@ -9,8 +9,9 @@ export const RoutesProvider = () => { const auth = useAuthRoutes() const processCreate = useProcessCreateRoutes() const dashboard = useDashboardRoutes() + const createOrganizationRoute = useCreateOrganizationRoutes() - const router = createBrowserRouter([root, auth, processCreate, dashboard]) + const router = createBrowserRouter([root, auth, processCreate, dashboard, createOrganizationRoute]) return } diff --git a/src/router/routes/auth.tsx b/src/router/routes/auth.tsx index 3e6254a3..d2b63192 100644 --- a/src/router/routes/auth.tsx +++ b/src/router/routes/auth.tsx @@ -3,14 +3,16 @@ import { lazy } from 'react' import LayoutAuth from '~elements/LayoutAuth' import { Routes } from '.' import NonLoggedRoute from '../NonLoggedRoute' +import AccountProtectedRoute from '../AccountProtectedRoute' import { SuspenseLoader } from '../SuspenseLoader' const AcceptInvite = lazy(() => import('~elements/account/invite')) const Signin = lazy(() => import('~elements/account/signin')) -const Signup = lazy(() => import('~elements/account/signup')) -const Verify = lazy(() => import('~components/Auth/Verify')) +const SignUp = lazy(() => import('~components/Auth/SignUp')) +const Verify = lazy(() => import('~elements/account/verify')) const PasswordForgot = lazy(() => import('~elements/account/password')) const PasswordReset = lazy(() => import('~elements/account/password/reset')) +const CreateOrganization = lazy(() => import('~elements/account/createOrganization')) const AuthElements = [ { @@ -30,7 +32,7 @@ const AuthElements = [ path: Routes.auth.signUp, element: ( - + ), }, @@ -78,3 +80,28 @@ export const useAuthRoutes = () => { children: AuthElements, } } + +export const useCreateOrganizationRoutes = () => { + return { + element: ( + + + + ), + children: [ + { + element: , + children: [ + { + path: Routes.auth.organizationCreate, + element: ( + + + + ), + }, + ], + }, + ], + } +} diff --git a/src/router/routes/dashboard.tsx b/src/router/routes/dashboard.tsx index d115fee6..b69ac87d 100644 --- a/src/router/routes/dashboard.tsx +++ b/src/router/routes/dashboard.tsx @@ -1,18 +1,20 @@ -import { useClient } from '@vocdoni/react-providers' -import { lazy } from 'react' // These aren't lazy loaded since they are main layouts and related components import { useQueryClient } from '@tanstack/react-query' +import { useClient } from '@vocdoni/react-providers' +import { lazy } from 'react' import { Params } from 'react-router-dom' import { Profile } from '~elements/dashboard/profile' import Error from '~elements/Error' import LayoutDashboard from '~elements/LayoutDashboard' import { paginatedElectionsQuery } from '~src/queries/organization' +import OrganizationProtectedRoute from '~src/router/OrganizationProtectedRoute' import { Routes } from '.' -import OrganizationProtectedRoute from '../OrganizationProtectedRoute' +import AccountProtectedRoute from '../AccountProtectedRoute' import { SuspenseLoader } from '../SuspenseLoader' // elements/pages const OrganizationEdit = lazy(() => import('~elements/dashboard/organization')) +const DashBoardCreateOrg = lazy(() => import('~elements/dashboard/organization/createOrganization')) const DashboardProcesses = lazy(() => import('~elements/dashboard/processes')) const DashboardProcessView = lazy(() => import('~elements/dashboard/processes/view')) const OrganizationTeam = lazy(() => import('~elements/dashboard/team')) @@ -27,7 +29,7 @@ export const useDashboardRoutes = () => { return { element: ( - + ), children: [ @@ -38,32 +40,6 @@ export const useDashboardRoutes = () => { ), children: [ - { - path: Routes.dashboard.base, - element: ( - - - - ), - }, - { - path: Routes.dashboard.process, - element: ( - - - - ), - loader: async ({ params }: { params: Params }) => client.fetchElection(params.id), - errorElement: , - }, - { - path: Routes.dashboard.organization, - element: ( - - - - ), - }, { path: Routes.dashboard.profile, element: ( @@ -73,23 +49,67 @@ export const useDashboardRoutes = () => { ), }, { - path: Routes.dashboard.processes, + path: Routes.dashboard.organizationCreate, element: ( - + ), - loader: async ({ params }) => - await queryClient.ensureQueryData(paginatedElectionsQuery(account, client, params)), - errorElement: , }, + // Protected routes if no account created without organization { - path: Routes.dashboard.team, element: ( - + ), + children: [ + { + path: Routes.dashboard.base, + element: ( + + + + ), + }, + { + path: Routes.dashboard.process, + element: ( + + + + ), + loader: async ({ params }: { params: Params }) => client.fetchElection(params.id), + errorElement: , + }, + { + path: Routes.dashboard.organization, + element: ( + + + + ), + }, + { + path: Routes.dashboard.processes, + element: ( + + + + ), + loader: async ({ params }) => + await queryClient.ensureQueryData(paginatedElectionsQuery(account, client, params)), + errorElement: , + }, + { + path: Routes.dashboard.team, + element: ( + + + + ), + }, + ], }, ], }, diff --git a/src/router/routes/index.ts b/src/router/routes/index.ts index 7e81aec3..70556dde 100644 --- a/src/router/routes/index.ts +++ b/src/router/routes/index.ts @@ -2,6 +2,7 @@ export const Routes = { root: '/', auth: { acceptInvite: '/account/invite', + organizationCreate: '/account/create-organization', // Organization create with account layout signIn: '/account/signin', signUp: '/account/signup', recovery: '/account/password', @@ -12,6 +13,7 @@ export const Routes = { dashboard: { base: '/admin', organization: '/admin/organization', + organizationCreate: '/admin/organization/create', // Organization create with dashboard layout process: '/admin/process/:id', processes: '/admin/processes/:page?/:status?', profile: '/admin/profile', diff --git a/src/router/routes/process-create.tsx b/src/router/routes/process-create.tsx index 65b08221..b3090e37 100644 --- a/src/router/routes/process-create.tsx +++ b/src/router/routes/process-create.tsx @@ -1,20 +1,15 @@ import { lazy } from 'react' // These aren't lazy loaded since they are main layouts and related components import LayoutProcessCreate from '~elements/LayoutProcessCreate' +import ProtectedRoutes from '~src/router/ProtectedRoutes' import { Routes } from '.' import { SuspenseLoader } from '../SuspenseLoader' -const ProtectedRoutes = lazy(() => import('../ProtectedRoutes')) const ProcessCreate = lazy(() => import('~elements/dashboard/processes/create')) const ProcessCreateElements = [ { - element: ( - - - - ), - children: [ + ...ProtectedRoutes([ { path: Routes.processes.create, element: ( @@ -23,7 +18,7 @@ const ProcessCreateElements = [ ), }, - ], + ]), }, ] diff --git a/src/router/routes/root.tsx b/src/router/routes/root.tsx index 72756d63..989e52f3 100644 --- a/src/router/routes/root.tsx +++ b/src/router/routes/root.tsx @@ -5,12 +5,10 @@ import { Params } from 'react-router-dom' // These aren't lazy loaded since they are main layouts and related components import Error from '~elements/Error' import Layout from '~elements/Layout' -import { StripeCheckout, StripeReturn } from '~elements/Stripe' import { Routes } from '.' import { SuspenseLoader } from '../SuspenseLoader' - -// Lazy loading helps splitting the final code, which helps downloading the app (theoretically) -const OrganizationProtectedRoute = lazy(() => import('../OrganizationProtectedRoute')) +import ProtectedRoutes from '~src/router/ProtectedRoutes' +import { StripeCheckout, StripeReturn } from '~elements/Stripe' // elements / pages const Faucet = lazy(() => import('~elements/Faucet')) @@ -74,12 +72,7 @@ const RootElements = (client: VocdoniSDKClient) => [ ), }, { - element: ( - - - - ), - children: [ + ...ProtectedRoutes([ { path: Routes.stripe.checkout, element: , @@ -89,7 +82,7 @@ const RootElements = (client: VocdoniSDKClient) => [ element: , errorElement: , }, - ], + ]), }, { path: Routes.faucet,