diff --git a/public/index.html b/index.html similarity index 100% rename from public/index.html rename to index.html diff --git a/src/components/Auth/Subscription.tsx b/src/components/Auth/Subscription.tsx index 628b540fd..796b3ff3f 100644 --- a/src/components/Auth/Subscription.tsx +++ b/src/components/Auth/Subscription.tsx @@ -4,6 +4,7 @@ import { useClient } from '@vocdoni/react-providers' import { dotobject, ensure0x } from '@vocdoni/sdk' import { ReactNode, useMemo } from 'react' import { useAuth } from '~components/Auth/useAuth' +import type { Plan } from '~components/Pricing/Plans' import { ApiEndpoints } from './api' type PermissionsContextType = { @@ -27,27 +28,7 @@ type SubscriptionType = { subOrgs: number members: number } - plan: { - id: number - name: string - stripeID: string - default: boolean - organization: { - memberships: number - subOrgs: number - censusSize: number - } - votingTypes: { - approval: boolean - ranked: boolean - weighted: boolean - } - features: { - personalization: boolean - emailReminder: boolean - smsNotification: boolean - } - } + plan: Plan } const [SubscriptionProvider, useSubscription] = createContext({ diff --git a/src/components/Layout/Form/DetailedCheckbox.tsx b/src/components/Layout/Form/DetailedCheckbox.tsx new file mode 100644 index 000000000..482d47508 --- /dev/null +++ b/src/components/Layout/Form/DetailedCheckbox.tsx @@ -0,0 +1,28 @@ +import { Checkbox, CheckboxProps, Text, useMultiStyleConfig } from '@chakra-ui/react' +import { cloneElement, ReactElement } from 'react' +import { useFormContext } from 'react-hook-form' + +export type DetailedBoxProps = CheckboxProps & { + badge?: ReactElement + description?: string + icon?: ReactElement + name: string + title: string +} + +export const DetailedBox = ({ icon, badge, title, description, name, ...props }: DetailedBoxProps) => { + const styles = useMultiStyleConfig('DetailedBox', props) + const { register } = useFormContext() + + return ( + + + {icon && cloneElement(icon, { sx: styles.icon })} + {title} + + {description} + + {badge && cloneElement(badge, { sx: styles.badge })} + + ) +} diff --git a/src/components/Pricing/Features.tsx b/src/components/Pricing/Features.tsx index 4181b777c..5dd325d6e 100644 --- a/src/components/Pricing/Features.tsx +++ b/src/components/Pricing/Features.tsx @@ -2,15 +2,7 @@ import { dotobject } from '@vocdoni/sdk' import { useTranslation } from 'react-i18next' import type { Plan } from './Plans' -export type FeaturesKeys = - | 'anonymous' - | 'secretUntilTheEnd' - | 'overwrite' - | 'personalization' - | 'emailReminder' - | 'smsNotification' - | 'whiteLabel' - | 'liveStreaming' +export type FeaturesKeys = 'personalization' | 'emailReminder' | 'smsNotification' | 'whiteLabel' | 'liveStreaming' // Translation keys for the subscription features export const PlanFeaturesTranslationKeys = { @@ -24,6 +16,15 @@ export const PlanFeaturesTranslationKeys = { 'features.smsNotification': 'features.sms_notification', } +/** + * Checks if the specified feature exists in the plan. + * + * @param plan - The plan object to check. + * @param featurePath - Dot notation path to the feature (e.g., 'organization.memberships'). + * @returns boolean - `true` if the feature exists, `false` otherwise. + */ +export const hasFeature = (plan: Plan, featurePath: string) => dotobject(plan, featurePath) !== 'undefined' + /** * Checks if a given feature exists and meets the required condition in a plan. * diff --git a/src/components/Pricing/utils.ts b/src/components/Pricing/utils.ts index ceca5afcf..19f4f8e6f 100644 --- a/src/components/Pricing/utils.ts +++ b/src/components/Pricing/utils.ts @@ -16,15 +16,12 @@ export const isFeatureAvailable = ( featurePath: string, expectedValue?: number | { operator: '===' | '>' | '>=' | '<' | '<='; value: number } ): boolean => { - const featureValue = dotobject(plan, featurePath) // Get the feature value using dot notation + // Get the feature value using dot notation + const featureValue = dotobject(plan, featurePath) + // If the feature doesn't exist, return false if (typeof featureValue === 'undefined') { - return false // If the feature doesn't exist, return false - } - - // If no expected value is provided, return true if the feature exists - if (typeof expectedValue === 'undefined') { - return true + return false } // Handle exact match or comparison @@ -32,6 +29,11 @@ export const isFeatureAvailable = ( return featureValue >= expectedValue // Default to "greater than or equal to" for numbers } + // Booleans are treated as exact matches + if (typeof featureValue === 'boolean') { + return featureValue + } + if (typeof expectedValue === 'object' && expectedValue.operator && expectedValue.value !== undefined) { const { operator, value } = expectedValue diff --git a/src/components/ProcessCreate/Questions/useVotingType.ts b/src/components/ProcessCreate/Questions/useVotingType.ts index cb72942f2..834f35263 100644 --- a/src/components/ProcessCreate/Questions/useVotingType.ts +++ b/src/components/ProcessCreate/Questions/useVotingType.ts @@ -5,13 +5,13 @@ import SingleChoice from '~components/ProcessCreate/Questions/SingleChoice' import { GenericFeatureObject } from '~components/ProcessCreate/Steps/TabsPage' export const VotingTypeSingle = 'single' -export const UnimplementedVotingTypeApproval = 'approval' +export const VotingTypeApproval = 'approval' export const MultiQuestionTypes = [VotingTypeSingle] -export type VotingType = typeof VotingTypeSingle | typeof UnimplementedVotingTypeApproval +export type VotingType = typeof VotingTypeSingle | typeof VotingTypeApproval -export const VotingTypes = [VotingTypeSingle as VotingType, UnimplementedVotingTypeApproval as VotingType] +export const VotingTypes = [VotingTypeSingle as VotingType, VotingTypeApproval as VotingType] export const useVotingType = (): GenericFeatureObject => { const { t } = useTranslation() @@ -24,7 +24,7 @@ export const useVotingType = (): GenericFeatureObject => { icon: GiChoice, component: SingleChoice, }, - [UnimplementedVotingTypeApproval]: { + [VotingTypeApproval]: { title: t('process_create.question.approval_voting.title'), description: t('process_create.question.approval_voting.description'), icon: GiChoice, diff --git a/src/components/ProcessCreate/Settings/SaasFeatures.tsx b/src/components/ProcessCreate/Settings/Features.tsx similarity index 58% rename from src/components/ProcessCreate/Settings/SaasFeatures.tsx rename to src/components/ProcessCreate/Settings/Features.tsx index 46f31f1ac..c78257e13 100644 --- a/src/components/ProcessCreate/Settings/SaasFeatures.tsx +++ b/src/components/ProcessCreate/Settings/Features.tsx @@ -1,10 +1,11 @@ -import { Box, Checkbox, CheckboxProps, Flex, Icon, Text } from '@chakra-ui/react' +import { Badge, Flex, Icon } from '@chakra-ui/react' import { useMemo } from 'react' import { useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' -import { IconType } from 'react-icons' import { BiCheckDouble } from 'react-icons/bi' import { useSubscription } from '~components/Auth/Subscription' +import { DetailedBox } from '~components/Layout/Form/DetailedCheckbox' +import { usePricingModal } from '~components/Pricing/Modals' const useProcessFeatures = () => { const { t } = useTranslation() @@ -13,86 +14,93 @@ const useProcessFeatures = () => { anonymous: { title: t('anonymous.title', { defaultValue: 'Anonymous' }), description: t('anonymous.description', { defaultValue: 'Voters will remain anonymous' }), - boxIcon: BiCheckDouble, - formKey: 'electionType.anonymous', + icon: BiCheckDouble, + name: 'electionType.anonymous', + permission: 'features.anonymous', }, secretUntilTheEnd: { title: t('secret_until_the_end.title', { defaultValue: 'Secret until the end' }), description: t('secret_until_the_end.description', { defaultValue: 'Vote contents will be encrypted till the end of the voting', }), - boxIcon: BiCheckDouble, - formKey: 'electionType.secretUntilTheEnd', + icon: BiCheckDouble, + name: 'electionType.secretUntilTheEnd', }, overwrite: { title: t('overwrite.title', { defaultValue: 'Vote overwrite' }), description: t('overwrite.description', { defaultValue: 'Voters will be able to overwrite their vote once' }), - formKey: 'maxVoteOverwrites', - boxIcon: BiCheckDouble, + name: 'maxVoteOverwrites', + icon: BiCheckDouble, + permission: 'features.voteOverwrite', }, // non implemented features... // personalization: { // title: t('personalization.title', { defaultValue: 'personalization' }), // description: t('personalization.description', { defaultValue: 'personalization' }), - // boxIcon: BiCheckDouble, + // icon: BiCheckDouble, // }, // emailReminder: { // title: t('email_reminder.title', { defaultValue: 'Email reminder' }), // description: t('email_reminder.description', { defaultValue: 'Remind by email' }), - // boxIcon: BiCheckDouble, + // icon: BiCheckDouble, // }, // smsNotification: { // title: t('sms_notification.title', { defaultValue: 'SMS Notification' }), // description: t('sms_notification.description', { defaultValue: 'Notify users somehow (?)' }), - // boxIcon: BiCheckDouble, + // icon: BiCheckDouble, // }, // whiteLabel: { // title: t('white_label.title', { defaultValue: 'White label' }), // description: t('white_label.description', { defaultValue: 'Customize the process layout entirely adding your own logos and color palette' }), - // boxIcon: BiCheckDouble, + // icon: BiCheckDouble, // }, // liveStreaming: { // title: t('live_streaming.title', { defaultValue: 'Live Streaming' }), // description: t('live_streaming.description', { defaultValue: 'IDK what\'s this about' }), - // boxIcon: BiCheckDouble, + // icon: BiCheckDouble, // }, }), [t] ) } + export const Features = () => { - const { subscription } = useSubscription() + const { t } = useTranslation() const translations = useProcessFeatures() + const { permission } = useSubscription() + const { openModal } = usePricingModal() + const { setValue, getValues } = useFormContext() return ( - {Object.entries(translations).map(([feature, card], i) => ( - - ))} - - ) -} + {Object.entries(translations).map(([feature, card], i) => { + const needsUpgrade = 'permission' in card && !permission(card.permission) -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 ( + {t('upgrade')}} + icon={card.icon && } + isChecked={getValues(card.name)} + onChange={(e) => { + if (!needsUpgrade) { + setValue(card.name, e.target.checked) + return + } - return ( - - {isPro && Pro} - - - {title} - - - {description} - + openModal('planUpgrade', { + feature: 'permission' in card && card.permission, + text: translations[feature].title, + }) + // reset the value to false + setValue(card.name, false) + return false + }} + /> + ) + })} + ) } diff --git a/src/components/ProcessCreate/StepForm/SaasFeatures.tsx b/src/components/ProcessCreate/StepForm/Features.tsx similarity index 85% rename from src/components/ProcessCreate/StepForm/SaasFeatures.tsx rename to src/components/ProcessCreate/StepForm/Features.tsx index de24caa9b..af8676ba9 100644 --- a/src/components/ProcessCreate/StepForm/SaasFeatures.tsx +++ b/src/components/ProcessCreate/StepForm/Features.tsx @@ -1,14 +1,14 @@ import { Flex } from '@chakra-ui/react' import { FormProvider, SubmitHandler, useForm } from 'react-hook-form' import { FeaturesKeys } from '~components/Pricing/Features' -import { Features as SaasFeaturesComponent } from '~components/ProcessCreate/Settings/SaasFeatures' +import { Features as SaasFeaturesComponent } from '~components/ProcessCreate/Settings/Features' import { ConfigurationValues } from '~components/ProcessCreate/StepForm/Info' import { StepsNavigation } from '~components/ProcessCreate/Steps/Navigation' import Wrapper from '~components/ProcessCreate/Steps/Wrapper' import { useProcessCreationSteps } from '../Steps/use-steps' -export type SaasFeaturesValues = { saasFeatures: Record } -interface FeaturesForm extends SaasFeaturesValues, ConfigurationValues {} +export type FeaturesValues = { features: Record } +interface FeaturesForm extends FeaturesValues, ConfigurationValues {} export const SaasFeatures = () => { const { form, setForm, next } = useProcessCreationSteps() diff --git a/src/components/ProcessCreate/StepForm/Questions.tsx b/src/components/ProcessCreate/StepForm/Questions.tsx index b2e570f46..bbc7ea0c3 100644 --- a/src/components/ProcessCreate/StepForm/Questions.tsx +++ b/src/components/ProcessCreate/StepForm/Questions.tsx @@ -41,6 +41,7 @@ const QuestionsTabs = () => { { const newQuestionType = definedVotingTypes.defined[index] // If the question type not accepts multiquestion and there are multiple questions selcted store only the first diff --git a/src/components/ProcessCreate/Steps/Form.tsx b/src/components/ProcessCreate/Steps/Form.tsx index 1cc91d71e..f6cb50649 100644 --- a/src/components/ProcessCreate/Steps/Form.tsx +++ b/src/components/ProcessCreate/Steps/Form.tsx @@ -37,13 +37,11 @@ export const StepsForm = ({ steps, activeStep, next, prev, setActiveStep }: Step weightedVote: false, questions: [{ options: [{}, {}] }], addresses: [], + // these do not end up in the election process object, but are required for other purposes gpsWeighted: false, passportScore: 20, stampsUnionType: 'OR', - saasFeatures: { - anonymous: false, - secretUntilTheEnd: true, - overwrite: false, + features: { personalization: false, emailReminder: false, smsNotification: false, diff --git a/src/components/ProcessCreate/Steps/TabsPage.tsx b/src/components/ProcessCreate/Steps/TabsPage.tsx index 503a9d052..5d0d68a3b 100644 --- a/src/components/ProcessCreate/Steps/TabsPage.tsx +++ b/src/components/ProcessCreate/Steps/TabsPage.tsx @@ -1,8 +1,24 @@ -import { Box, Flex, Icon, Tab, TabList, TabPanel, TabPanels, Tabs, Text, useDisclosure } from '@chakra-ui/react' +import { + Badge, + Box, + Flex, + Icon, + Tab, + TabList, + TabPanel, + TabPanels, + Tabs, + Text, + useDisclosure, + useMultiStyleConfig, +} from '@chakra-ui/react' import { TabProps } from '@chakra-ui/tabs/dist/tab' -import { useState } from 'react' -import { useTranslation } from 'react-i18next' +import { useMemo, useState } from 'react' +import { Trans, useTranslation } from 'react-i18next' import { CgMoreO } from 'react-icons/cg' +import { useSubscription } from '~components/Auth/Subscription' +import { hasFeature } from '~components/Pricing/Features' +import { usePricingModal } from '~components/Pricing/Modals' import ModalPro from '~components/ProcessCreate/ModalPro' import { StepsNavigation } from '~components/ProcessCreate/Steps/Navigation' import Wrapper from '~components/ProcessCreate/Steps/Wrapper' @@ -25,6 +41,7 @@ export interface ITabsPageProps unimplementedList: GenericFeatureObject } @@ -36,18 +53,32 @@ export const TabsPage = ) => { const { t } = useTranslation() const { isOpen, onOpen, onClose } = useDisclosure() - + const { permission, subscription } = useSubscription() + const { openModal } = usePricingModal() const { defined, details } = definedList const { defined: definedUnim, details: detailsUnim } = unimplementedList - + const [tab, setTab] = useState(defined.findIndex((val) => val === selected)) const [reason, setReason] = useState('') const [showProCards, setShowProCards] = useState(false) if (!defined || !definedUnim) return null + // Store upgrade requirements per feature for later use + const requireUpgrade = useMemo( + () => + defined.map((ct: Implemented) => { + if (permissionsPath) { + return (hasFeature(subscription.plan, permissionsPath) && permission(`${permissionsPath}.${ct}`)) === false + } + return false + }), + [subscription] + ) + return ( @@ -56,19 +87,48 @@ export const TabsPage = {title} {description} - val === selected)} onChange={onTabChange} variant='card' isLazy> + val === selected)} + index={tab} + onChange={(index: number) => { + // Check if the feature requires an account upgrade + if (permissionsPath) { + const requiresUpgrade = requireUpgrade[index] || false + const key = defined[index] + + if (requiresUpgrade) { + openModal('planUpgrade', { + feature: `${permissionsPath}.${key}`, + text: details[key].title, + }) + return false + } + } + + setTab(index) + onTabChange(index) + }} + variant='card' + isLazy + > <> - {defined.map((ct: Implemented, index: number) => ( - setShowProCards(false)} - icon={details[ct].icon} - title={details[ct].title} - description={details[ct].description} - check - /> - ))} + {defined.map((ct: Implemented, index: number) => { + const needsUpgrade = requireUpgrade[index] || false + return ( + { + setShowProCards(false) + }} + icon={details[ct].icon} + title={details[ct].title} + description={details[ct].description} + check + needsUpgrade={needsUpgrade} + /> + ) + })} {!!definedUnim.length && ( setShowProCards((prev) => !prev)} @@ -93,7 +153,7 @@ export const TabsPage = ))} @@ -120,7 +180,7 @@ interface ITabsCardSkeletonProps { title: string description: string check?: boolean - pro?: boolean + needsUpgrade?: boolean } const TabCardSkeleton = ({ @@ -129,25 +189,43 @@ const TabCardSkeleton = ({ title, description, check = false, - pro = false, + needsUpgrade = false, ...props -}: ITabsCardSkeletonProps & TabProps) => ( - - {check && ( - <> - - - - )} - {pro && ( - - Pro +}: ITabsCardSkeletonProps & TabProps) => { + const styles = useMultiStyleConfig('DetailedBox', props) + + return ( + + {check && ( + <> + + + + )} + + + + {convertParagraphs(title)} - )} - - - {convertParagraphs(title)} - - {description} - -) + {description} + {needsUpgrade && ( + + Upgrade + + )} + + ) +} diff --git a/src/components/ProcessCreate/Steps/use-steps.tsx b/src/components/ProcessCreate/Steps/use-steps.tsx index e5f9e7f5e..ad0c4b5c7 100644 --- a/src/components/ProcessCreate/Steps/use-steps.tsx +++ b/src/components/ProcessCreate/Steps/use-steps.tsx @@ -1,7 +1,7 @@ import { createContext, Dispatch, SetStateAction, useContext } from 'react' import { useTranslation } from 'react-i18next' import { CensusGitcoinValues } from '~components/ProcessCreate/StepForm/CensusGitcoin' -import { SaasFeatures, SaasFeaturesValues } from '~components/ProcessCreate/StepForm/SaasFeatures' +import { FeaturesValues, SaasFeatures } from '~components/ProcessCreate/StepForm/Features' import { CensusCspValues } from '../StepForm/CensusCsp' import { CensusSpreadsheetValues } from '../StepForm/CensusSpreadsheet' import { CensusTokenValues } from '../StepForm/CensusToken' @@ -21,7 +21,7 @@ export interface StepsFormValues CensusCspValues, CensusGitcoinValues, CensusSpreadsheetValues, - SaasFeaturesValues {} + FeaturesValues {} export interface StepsState { title: string diff --git a/src/elements/dashboard/processes/create.tsx b/src/elements/dashboard/processes/create.tsx index 78ebe7924..4a38474ad 100644 --- a/src/elements/dashboard/processes/create.tsx +++ b/src/elements/dashboard/processes/create.tsx @@ -1,5 +1,10 @@ +import { PricingModalProvider } from '~components/Pricing/Modals' import Steps from '~components/ProcessCreate/Steps' -const ProcessCreate = () => +const ProcessCreate = () => ( + + + +) export default ProcessCreate diff --git a/src/importmeta.d.ts b/src/importmeta.d.ts index 8867b5590..01951439c 100644 --- a/src/importmeta.d.ts +++ b/src/importmeta.d.ts @@ -1,6 +1,7 @@ interface ImportMeta { env: { NODE_ENV: string + DEV: boolean BASE_URL: string VOCDONI_ENVIRONMENT: string CUSTOM_FAUCET_URL: string diff --git a/src/theme/components/Tabs.ts b/src/theme/components/Tabs.ts index d3d01f511..5b274efdf 100644 --- a/src/theme/components/Tabs.ts +++ b/src/theme/components/Tabs.ts @@ -31,59 +31,13 @@ const card = definePartsStyle({ borderRadius: 'xl', w: 'full', - '& > #description': { - color: 'tab.variant.card.description.light', - textAlign: 'start', - fontSize: 'xs', - _dark: { - color: 'tab.variant.card.description.dark', - }, - }, - - '& #pro-badge': { - bgColor: 'tab.variant.card.badge_bg', - borderRadius: '10px', - position: 'absolute', - top: '3px', - right: '3px', - px: 4, - color: 'badge.pro_color', - pt: '2px', - fontSize: '12px', - }, - - '& > #title': { - display: 'flex', - alignItems: 'center', - w: 'full', - gap: 3, - fontSize: 'sm', - - '& p': { - fontWeight: 'bold', - textAlign: 'start', - }, - }, - - // Empty checkbox - '& #empty-check': { - position: 'absolute', - top: 2.5, - right: 2.5, - w: 4, - h: 4, - borderRadius: 'full', - border: `1px solid`, - borderColor: 'tab.variant.card.border', - }, - '& > svg': { w: 5, h: 5, color: 'tab.variant.card.svg', position: 'absolute', - top: 2, - right: 2, + top: '1rem', + right: '1rem', display: 'none', }, @@ -93,7 +47,7 @@ const card = definePartsStyle({ display: 'block', }, - '& > #empty-check': { + '& > .empty': { display: 'none', }, diff --git a/src/theme/components/checkbox.ts b/src/theme/components/checkbox.ts index 92c5b872d..8b1094692 100644 --- a/src/theme/components/checkbox.ts +++ b/src/theme/components/checkbox.ts @@ -2,30 +2,36 @@ import { checkboxAnatomy } from '@chakra-ui/anatomy' import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react' const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(checkboxAnatomy.keys) +const { definePartsStyle: defineDetailedPartsStyle, defineMultiStyleConfig: defineDetailedMultiStyleConfig } = + createMultiStyleConfigHelpers(['icon', 'title', 'badge', 'description', 'checkbox']) -export const Checkbox = defineMultiStyleConfig({ - baseStyle: { - control: { - borderRadius: 'full', - _checked: { - borderColor: 'checkbox.checked.border', - bgColor: 'checkbox.checked.bg', - - _hover: { - borderColor: 'checkbox.checked.border', - bgColor: 'checkbox.checked.bg', - }, - }, - _focus: { - boxShadow: 'none', - }, +export const DetailedBox = defineDetailedMultiStyleConfig({ + baseStyle: defineDetailedPartsStyle((props) => ({ + title: { + display: 'flex', + alignItems: 'center', + gap: 2, + fontWeight: 'bold', + fontSize: 'sm', + }, + badge: { + position: 'absolute', + top: '.9rem', + right: '.9rem', }, - icon: { - color: 'checkbox.icon', + description: { + textAlign: 'start', + fontSize: 'sm', }, + })), +}) + +export const Checkbox = defineMultiStyleConfig({ + defaultProps: { + colorScheme: 'brand', }, variants: { - radiobox: definePartsStyle({ + detailed: definePartsStyle({ container: defineStyle({ position: 'relative', display: 'flex', @@ -47,53 +53,13 @@ export const Checkbox = defineMultiStyleConfig({ }), control: defineStyle({ position: 'absolute', - right: 1, - top: 1, + right: '1rem', + top: '1rem', rounded: 'full', - ml: 'auto', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - - _checked: { - border: 'none', - }, }), label: defineStyle({ fontSize: 'sm', alignSelf: 'start', - - '& > div:first-of-type': { - display: 'flex', - alignItems: 'center', - gap: 2, - fontWeight: 'bold', - mb: 2, - }, - - '& > p': { - fontSize: 'sm', - textAlign: 'start', - }, - - //pro plan, it allows opening the modal - '& > span': { - borderRadius: 'xl', - position: 'absolute', - top: 1, - right: 1, - px: 2, - bgColor: 'checkbox.bg', - fontSize: 'sm', - color: 'white', - }, - '& div:nth-of-type(2)': { - position: 'absolute', - h: '100%', - w: '100%', - top: 0, - left: 0, - }, }), }), }, diff --git a/src/theme/index.ts b/src/theme/index.ts index e90b045a5..13198f5ab 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -7,7 +7,7 @@ import { Accordion } from './components/accordion' import { Badge } from './components/badge' import { Button } from './components/button' import { Card } from './components/card' -import { Checkbox } from './components/checkbox' +import { Checkbox, DetailedBox } from './components/checkbox' import { Form } from './components/Form' import { ElectionTitle, Heading } from './components/heading' import { Input } from './components/input' @@ -83,6 +83,7 @@ export const theme = extendTheme(vtheme, { Button, Card, Checkbox, + DetailedBox, ElectionTitle, ElectionQuestions, ElectionResults, diff --git a/vite.config.ts b/vite.config.ts index 4bfa96557..ed798ff71 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -56,7 +56,7 @@ const viteconfig = ({ mode }) => { react(), svgr(), createHtmlPlugin({ - template: `public/index.html`, + template: `index.html`, minify: { removeComments: false, collapseWhitespace: true,