-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement create process form logic (#774)
This PR implements some new necessary logic in order to follow new saas requirements for process creation. Related: vocdoni/interoperability#210 vocdoni/interoperability#229 - Adds a footer on process creation - Adds a store draft button - Implement new steps when is saas - Add saas extra features on the form - Mock `useAccountPlan`, simulating a call that gives for an organization which features a plan has activated. - Add some minimum styles to try it easier ## Important! This PR **does not** implement: - New styles - Some buttons logics (for example save draft or upgrade plan) - New modals - Logic to store the election on the saas backend (this need some meetings). For now, the SDK and the remote signer are able to create an election on the backend as before.
- Loading branch information
Showing
19 changed files
with
619 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { useQuery, UseQueryOptions } from '@tanstack/react-query' | ||
import { VotingType } from '~components/ProcessCreate/Questions/useVotingType' | ||
import { UnimplementedVotingType } from '~components/ProcessCreate/Questions/useUnimplementedVotingType' | ||
|
||
type PlanType = 'free' | 'pro' | 'custom' | ||
|
||
export type FeaturesKeys = | ||
| 'anonymous' | ||
| 'secretUntilTheEnd' | ||
| 'overwrite' | ||
| 'personalization' | ||
| 'emailReminder' | ||
| 'smsNotification' | ||
| 'whiteLabel' | ||
| 'liveStreaming' | ||
export type SaasVotingTypesKeys = VotingType & UnimplementedVotingType | ||
|
||
type SaasOrganizationInfo = { | ||
memberships: number | ||
subOrgs: number | ||
maxProcesses: number | ||
max_census_size: number | ||
customURL: boolean | ||
} | ||
|
||
type AccountPlanTypeResponse = { | ||
plan: PlanType | ||
stripePlanId: string | ||
organization: SaasOrganizationInfo | ||
votingTypes: Record<SaasVotingTypesKeys, boolean> | ||
features: Record<FeaturesKeys, boolean> | ||
} | ||
|
||
const accountPlanMock: AccountPlanTypeResponse = { | ||
plan: 'pro', | ||
stripePlanId: 'plan_xyz123', // Stripe plan ID for payment and plan management | ||
organization: { | ||
memberships: 5, // Maximum number of members or admins | ||
subOrgs: 3, // Maximum number of sub-organizations | ||
maxProcesses: 10, // Maximum number of voting processes | ||
max_census_size: 10000, // Maximum number of voters (census size) for this plan | ||
customURL: true, // Whether a custom URL for the voting page is allowed | ||
}, | ||
votingTypes: { | ||
single: true, // Simple single-choice voting allowed | ||
multiple: true, // Multiple-choice voting allowed | ||
approval: true, // Approval voting allowed | ||
cumulative: false, // Cumulative voting not allowed | ||
ranked: false, // Ranked voting not allowed | ||
weighted: false, // Weighted voting not allowed | ||
}, | ||
features: { | ||
personalization: true, // Voting page customization allowed | ||
emailReminder: true, // Email reminders allowed | ||
smsNotification: true, // SMS notifications allowed | ||
whiteLabel: true, // White-label voting page allowed | ||
liveStreaming: false, // Live results streaming not allowed | ||
anonymous: true, | ||
secretUntilTheEnd: true, | ||
overwrite: true, | ||
// ... Other feature controls | ||
}, | ||
} | ||
|
||
export const useAccountPlan = (options?: Omit<UseQueryOptions<AccountPlanTypeResponse>, 'queryKey' | 'queryFn'>) => { | ||
return useQuery({ | ||
queryKey: ['account', 'plan'], | ||
queryFn: async () => { | ||
// Simulate an API call | ||
return accountPlanMock | ||
}, | ||
...options, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
src/components/ProcessCreate/Questions/useSaasVotingType.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { useTranslation } from 'react-i18next' | ||
import { GenericFeatureObject, GenericFeatureObjectProps } from '~components/ProcessCreate/Steps/TabsPage' | ||
import { SaasVotingTypesKeys, useAccountPlan } from '~components/AccountSaas/useAccountPlan' | ||
import { GiChoice } from 'react-icons/gi' | ||
import SingleChoice from '~components/ProcessCreate/Questions/SingleChoice' | ||
import { useMemo } from 'react' | ||
|
||
const useVotingTypesTranslations = (): Record<SaasVotingTypesKeys, GenericFeatureObjectProps> => { | ||
const { t } = useTranslation() | ||
return useMemo( | ||
() => ({ | ||
single: { | ||
description: t('single', { defaultValue: 'single' }), | ||
title: t('single', { defaultValue: 'single' }), | ||
icon: GiChoice, | ||
component: SingleChoice, | ||
}, | ||
multiple: { | ||
description: t('multiple', { defaultValue: 'multiple' }), | ||
title: t('multiple', { defaultValue: 'multiple' }), | ||
icon: GiChoice, | ||
component: SingleChoice, | ||
}, | ||
approval: { | ||
description: t('approval', { defaultValue: 'approval' }), | ||
title: t('approval', { defaultValue: 'approval' }), | ||
icon: GiChoice, | ||
component: SingleChoice, | ||
}, | ||
cumulative: { | ||
description: t('cumulative', { defaultValue: 'cumulative' }), | ||
title: t('cumulative', { defaultValue: 'cumulative' }), | ||
icon: GiChoice, | ||
component: SingleChoice, | ||
}, | ||
ranked: { | ||
description: t('ranked', { defaultValue: 'ranked' }), | ||
title: t('ranked', { defaultValue: 'ranked' }), | ||
icon: GiChoice, | ||
component: SingleChoice, | ||
}, | ||
weighted: { | ||
description: t('weighted', { defaultValue: 'weighted' }), | ||
title: t('weighted', { defaultValue: 'weighted' }), | ||
icon: GiChoice, | ||
component: SingleChoice, | ||
}, | ||
}), | ||
[t] | ||
) | ||
} | ||
|
||
export const useSaasVotingType = (): { | ||
inPlan: GenericFeatureObject<Partial<SaasVotingTypesKeys>> | ||
pro: GenericFeatureObject<Partial<SaasVotingTypesKeys>> | ||
} => { | ||
const { data } = useAccountPlan() | ||
const translations = useVotingTypesTranslations() | ||
|
||
if (!data) return null | ||
const inPlanDetails = {} as Record<Partial<SaasVotingTypesKeys>, GenericFeatureObjectProps> | ||
const proDetails = {} as Record<Partial<SaasVotingTypesKeys>, GenericFeatureObjectProps> | ||
|
||
for (const [key, inPlan] of Object.entries(data.votingTypes)) { | ||
const _key = key as SaasVotingTypesKeys | ||
if (inPlan) { | ||
inPlanDetails[_key] = translations[_key] | ||
continue | ||
} | ||
proDetails[_key] = translations[_key] | ||
} | ||
|
||
return { | ||
inPlan: { | ||
defined: Object.keys(inPlanDetails) as Partial<SaasVotingTypesKeys>[], | ||
details: inPlanDetails, | ||
}, | ||
pro: { | ||
defined: Object.keys(proDetails) as Partial<SaasVotingTypesKeys>[], | ||
details: proDetails, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Box, Flex, HStack, Image, Stack, Text } from '@chakra-ui/react' | ||
import vcdLogo from '/assets/logo-classic.svg' | ||
import { Button } from '@vocdoni/chakra-components' | ||
import { useAccountPlan } from '~components/AccountSaas/useAccountPlan' | ||
|
||
const SaasFooter = () => { | ||
const { data } = useAccountPlan() | ||
const isCustom = data?.plan === 'custom' | ||
const isFree = data?.plan === 'free' | ||
|
||
return ( | ||
<Box as='footer' mt='auto'> | ||
<Flex justify={'space-around'} gap={6}> | ||
<Image src={vcdLogo} w='125px' mb='12px' /> | ||
<Text fontSize='sm'>Privacy Policy</Text> | ||
<Text fontSize='sm'>[email protected]</Text> | ||
{isFree && <Text fontSize='sm'>$0.00</Text>} | ||
{!isCustom && <Button>UPGRADE TO PREMIUM</Button>} | ||
</Flex> | ||
</Box> | ||
) | ||
} | ||
|
||
export default SaasFooter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { Box, Checkbox, CheckboxProps, Icon, Text } from '@chakra-ui/react' | ||
import { BiCheckDouble } from 'react-icons/bi' | ||
import { IconType } from 'react-icons' | ||
import { FeaturesKeys, useAccountPlan } from '~components/AccountSaas/useAccountPlan' | ||
import { Loading } from '~src/router/SuspenseLoader' | ||
import { useTranslation } from 'react-i18next' | ||
import { useFormContext } from 'react-hook-form' | ||
import { useMemo } from 'react' | ||
|
||
const useFeaturesTranslations = (): Record<FeaturesKeys, CheckBoxCardProps> => { | ||
const { t } = useTranslation() | ||
return useMemo( | ||
() => ({ | ||
anonymous: { | ||
description: t('anonymous', { defaultValue: 'anonymous' }), | ||
title: t('anonymous', { defaultValue: 'anonymous' }), | ||
boxIcon: BiCheckDouble, | ||
formKey: 'electionType.anonymous', | ||
}, | ||
secretUntilTheEnd: { | ||
description: t('secretUntilTheEnd', { defaultValue: 'secretUntilTheEnd' }), | ||
title: t('secretUntilTheEnd', { defaultValue: 'secretUntilTheEnd' }), | ||
boxIcon: BiCheckDouble, | ||
formKey: 'electionType.secretUntilTheEnd', | ||
}, | ||
overwrite: { | ||
description: t('overwrite', { defaultValue: 'overwrite' }), | ||
title: t('overwrite', { defaultValue: 'overwrite' }), | ||
boxIcon: BiCheckDouble, | ||
}, | ||
personalization: { | ||
description: t('personalization', { defaultValue: 'personalization' }), | ||
title: t('personalization', { defaultValue: 'personalization' }), | ||
boxIcon: BiCheckDouble, | ||
}, | ||
emailReminder: { | ||
description: t('emailReminder', { defaultValue: 'emailReminder' }), | ||
title: t('emailReminder', { defaultValue: 'emailReminder' }), | ||
boxIcon: BiCheckDouble, | ||
}, | ||
smsNotification: { | ||
description: t('smsNotification', { defaultValue: 'smsNotification' }), | ||
title: t('smsNotification', { defaultValue: 'smsNotification' }), | ||
boxIcon: BiCheckDouble, | ||
}, | ||
whiteLabel: { | ||
description: t('whiteLabel', { defaultValue: 'whiteLabel' }), | ||
title: t('whiteLabel', { defaultValue: 'whiteLabel' }), | ||
boxIcon: BiCheckDouble, | ||
}, | ||
liveStreaming: { | ||
description: t('liveStreaming', { defaultValue: 'liveStreaming' }), | ||
title: t('liveStreaming', { defaultValue: 'liveStreaming' }), | ||
boxIcon: BiCheckDouble, | ||
}, | ||
}), | ||
[t] | ||
) | ||
} | ||
export const SaasFeatures = () => { | ||
const { data, isLoading } = useAccountPlan() | ||
const translations = useFeaturesTranslations() | ||
|
||
if (isLoading) return <Loading /> | ||
if (!data) return null | ||
|
||
return ( | ||
<Box> | ||
{Object.entries(data.features).map(([feature, inPlan], i) => { | ||
const card = translations[feature as FeaturesKeys] | ||
if (!card) return null | ||
return <CheckBoxCard key={i} isPro={!inPlan} {...card} formKey={card.formKey ?? `saasFeatures.${feature}`} /> | ||
})} | ||
</Box> | ||
) | ||
} | ||
|
||
interface CheckBoxCardProps { | ||
title: string | ||
description: string | ||
boxIcon: IconType | ||
isPro?: boolean | ||
formKey?: string | ||
} | ||
|
||
const CheckBoxCard = ({ title, description, boxIcon, isPro, formKey, ...rest }: CheckBoxCardProps & CheckboxProps) => { | ||
const { register, watch } = useFormContext() | ||
|
||
return ( | ||
<Checkbox variant='radiobox' {...register(formKey)} {...rest}> | ||
<Box> | ||
<Icon as={boxIcon} /> | ||
<Text>{title}</Text> | ||
</Box> | ||
{isPro && <Text as='span'>Pro</Text>} | ||
<Text>{description}</Text> | ||
</Checkbox> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
7d305c7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Deployed on https://66fe80e8537e8d7971f92d09--vocdoni-app-stg.netlify.app
7d305c7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Deployed on https://66fe80e993a39177dba2c41b--vocdoni-app-dev.netlify.app