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,