-
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.
Dashboard, menu and dashboard voting page (#793)
* dashboard and dashboard voting page * Minor dashboard theme fixes * Rename some elements following their actual routes * Remade dashboard menu * Minor style changes * Fix saas issues in process create theme * Removed onvote reminiscenses * Set DashboardContents in the expected views + set some section title
- Loading branch information
1 parent
9f1d5dc
commit 9629fe1
Showing
44 changed files
with
979 additions
and
606 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
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,35 @@ | ||
import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons' | ||
import { Button, Icon } from '@chakra-ui/react' | ||
import { Link as ReactRouterLink, generatePath } from 'react-router-dom' | ||
|
||
export const DashboardMenuItem = ({ | ||
label, | ||
route, | ||
icon, | ||
isOpen = false, | ||
isActive = false, | ||
onToggle, | ||
hasChildren = false, | ||
}: { | ||
label: string | ||
route?: string | ||
icon?: any | ||
isOpen?: boolean | ||
isActive?: boolean | ||
onToggle?: () => void | ||
hasChildren?: boolean | ||
}) => ( | ||
<Button | ||
as={route ? ReactRouterLink : undefined} | ||
to={route ? generatePath(route) : undefined} | ||
onClick={hasChildren ? onToggle : undefined} | ||
isActive={isActive} // Set active state | ||
justifyContent='start' | ||
variant='menu' | ||
w='full' | ||
leftIcon={icon ? <Icon as={icon} /> : undefined} | ||
rightIcon={hasChildren ? isOpen ? <ChevronUpIcon /> : <ChevronDownIcon /> : undefined} | ||
> | ||
{label} | ||
</Button> | ||
) |
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,130 @@ | ||
import { Box, Collapse } from '@chakra-ui/react' | ||
import { OrganizationName } from '@vocdoni/chakra-components' | ||
import { useEffect, useState } from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import { FaPhoneAlt } from 'react-icons/fa' | ||
import { FaHouse } from 'react-icons/fa6' | ||
import { GiHamburgerMenu } from 'react-icons/gi' | ||
import { HiMiniPencil, HiSquares2X2 } from 'react-icons/hi2' | ||
import { IoIosSettings } from 'react-icons/io' | ||
import { matchPath, useLocation } from 'react-router-dom' | ||
import { Routes } from '~src/router/routes' | ||
import { DashboardMenuItem } from './Item' | ||
|
||
type MenuItem = { | ||
label: string | ||
icon?: any | ||
route?: string | ||
children?: MenuItem[] | ||
} | ||
|
||
export const DashboardMenuOptions = () => { | ||
const { t } = useTranslation() | ||
const location = useLocation() | ||
const [openSection, setOpenSection] = useState<string | null>(null) | ||
|
||
const menuItems: MenuItem[] = [ | ||
{ | ||
label: t('organization.dashboard'), | ||
icon: FaHouse, | ||
route: Routes.dashboard.base, | ||
}, | ||
{ | ||
label: t('voting_processes'), | ||
icon: HiSquares2X2, | ||
children: [ | ||
{ label: t('all'), route: Routes.dashboard.processes }, | ||
{ label: t('active'), route: '#active' }, | ||
{ label: t('finished'), route: '#finished' }, | ||
{ label: t('draft'), route: '#draft' }, | ||
], | ||
}, | ||
{ | ||
label: t('organization.census'), | ||
icon: GiHamburgerMenu, | ||
route: '#census', | ||
}, | ||
{ | ||
label: t('user_management'), | ||
icon: HiMiniPencil, | ||
route: '#user-management', | ||
}, | ||
{ | ||
label: t('settings'), | ||
icon: IoIosSettings, | ||
children: [ | ||
{ label: t('organization.organization'), route: '#organization' }, | ||
{ label: t('team'), route: Routes.dashboard.team }, | ||
{ label: t('billing'), route: '#billing' }, | ||
{ label: t('subscription'), route: '#subscription' }, | ||
{ label: t('profile'), route: Routes.dashboard.profile }, | ||
], | ||
}, | ||
{ | ||
label: t('help_and_support'), | ||
icon: FaPhoneAlt, | ||
route: '#support', | ||
}, | ||
] | ||
|
||
// Check if any child route is active, and open its section | ||
useEffect(() => { | ||
menuItems.forEach((item) => { | ||
if ( | ||
item.children && | ||
item.children.some((child) => matchPath({ path: child.route || '', end: true }, location.pathname)) | ||
) { | ||
setOpenSection((prev) => (prev !== item.label ? item.label : prev)) | ||
} | ||
}) | ||
}, [location.pathname]) | ||
|
||
const handleToggle = (label: string) => { | ||
setOpenSection((prev) => (prev === label ? null : label)) | ||
} | ||
|
||
return ( | ||
<Box> | ||
<OrganizationName color='text.secondary' mb={2.5} /> | ||
{menuItems.map((item, index) => ( | ||
<Box key={index}> | ||
{item.children ? ( | ||
<> | ||
<DashboardMenuItem | ||
label={item.label} | ||
icon={item.icon} | ||
route={item.route} | ||
isOpen={openSection === item.label} | ||
isActive={item.children.some((child) => | ||
matchPath({ path: child.route || '', end: true }, location.pathname) | ||
)} | ||
onToggle={() => handleToggle(item.label)} | ||
hasChildren | ||
/> | ||
<Collapse in={openSection === item.label}> | ||
<Box pl={6}> | ||
{item.children.map((child, childIndex) => ( | ||
<DashboardMenuItem | ||
key={childIndex} | ||
label={child.label} | ||
route={child.route} | ||
icon={null} | ||
isActive={Boolean(matchPath({ path: child.route || '', end: true }, location.pathname))} | ||
/> | ||
))} | ||
</Box> | ||
</Collapse> | ||
</> | ||
) : ( | ||
<DashboardMenuItem | ||
label={item.label} | ||
route={item.route} | ||
icon={item.icon} | ||
isActive={Boolean(matchPath({ path: item.route || '', end: true }, location.pathname))} | ||
/> | ||
)} | ||
</Box> | ||
))} | ||
</Box> | ||
) | ||
} |
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,60 @@ | ||
import { AddIcon } from '@chakra-ui/icons' | ||
import { Box, Button, Drawer, DrawerContent, DrawerOverlay } from '@chakra-ui/react' | ||
import { Trans } from 'react-i18next' | ||
import { Link as ReactRouterLink, generatePath } from 'react-router-dom' | ||
import LogoutBtn from '~components/Account/LogoutBtn' | ||
import { HSeparator } from '~components/Auth/SignIn' | ||
import { ColorModeSwitcher } from '~components/Layout/ColorModeSwitcher' | ||
import { VocdoniLogo } from '~components/Layout/Logo' | ||
import { Routes } from '~src/router/routes' | ||
import { DashboardMenuOptions } from './Options' | ||
|
||
const DashboardMenu = ({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) => ( | ||
<> | ||
{/* Sidebar for large screens */} | ||
<Box | ||
gridArea='sidebar' | ||
p={4} | ||
display={{ base: 'none', lg: 'flex' }} | ||
flexDirection='column' | ||
gap={4} | ||
bg='dashboard.sidebar.bg.light' | ||
_dark={{ bg: 'dashboard.sidebar.bg.dark' }} | ||
> | ||
<DashboardMenuContent /> | ||
</Box> | ||
|
||
{/* Sidebar for small screens */} | ||
<Drawer isOpen={isOpen} placement='left' onClose={onClose}> | ||
<DrawerOverlay /> | ||
<DrawerContent bg='dashboard.sidebar.bg.light' _dark={{ bg: 'dashboard.sidebar.bg.dark' }}> | ||
<Box p={4} display='flex' flexDirection='column' gap={4}> | ||
<DashboardMenuContent /> | ||
</Box> | ||
</DrawerContent> | ||
</Drawer> | ||
</> | ||
) | ||
|
||
// Common menu contents | ||
const DashboardMenuContent = () => ( | ||
<> | ||
<VocdoniLogo w='180px' alignSelf='center' /> | ||
<HSeparator /> | ||
<DashboardMenuOptions /> | ||
<Button | ||
as={ReactRouterLink} | ||
to={generatePath(Routes.processes.create)} | ||
variant='box-shadow' | ||
w='full' | ||
my={5} | ||
leftIcon={<AddIcon />} | ||
> | ||
<Trans i18nKey='new_voting'>New voting</Trans> | ||
</Button> | ||
<LogoutBtn /> | ||
<ColorModeSwitcher ml='auto' /> | ||
</> | ||
) | ||
|
||
export default DashboardMenu |
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,126 @@ | ||
import { | ||
Box, | ||
Button, | ||
Modal, | ||
ModalBody, | ||
ModalCloseButton, | ||
ModalContent, | ||
ModalFooter, | ||
ModalHeader, | ||
ModalOverlay, | ||
Select, | ||
Text, | ||
} from '@chakra-ui/react' | ||
import { Trans, useTranslation } from 'react-i18next' | ||
import { Link as ReactRouterLink } from 'react-router-dom' | ||
import PricingCard from '~components/Organization/Dashboard/PricingCard' | ||
|
||
type CardProps = { | ||
popular: boolean | ||
title: string | ||
subtitle: string | ||
price: string | ||
features: string[] | ||
} | ||
|
||
export const PricingModal = ({ isOpenModal, onCloseModal }: { isOpenModal: boolean; onCloseModal: () => void }) => { | ||
const { t } = useTranslation() | ||
|
||
const cards: CardProps[] = [ | ||
{ | ||
popular: false, | ||
title: t('pricing_modal.essential_title', { defaultValue: 'Essential' }), | ||
subtitle: t('pricing_modal.essential_subtitle', { | ||
defaultValue: 'Small or medium-sized orgs or community groups with basic voting needs.', | ||
}), | ||
price: '99', | ||
features: [ | ||
t('pricing_modal.essential_feat.core_voting', { defaultValue: 'Core voting features' }), | ||
t('pricing_modal.essential_feat.up_to_admins', { defaultValue: 'Up to 3 Admins and 1 org' }), | ||
t('pricing_modal.essential_feat.yearly_processes', { defaultValue: '5 yearly voting processes' }), | ||
t('pricing_modal.essential_feat.report_analytitcs', { defaultValue: 'Basic reporting and analytics' }), | ||
t('pricing_modal.essential_feat.ticket_support', { defaultValue: 'Ticket support' }), | ||
t('pricing_modal.essential_feat.gpdr_compilance', { defaultValue: 'GDPR compliance' }), | ||
], | ||
}, | ||
{ | ||
popular: true, | ||
title: t('pricing_modal.premium_title', { defaultValue: 'Premium' }), | ||
subtitle: t('pricing_modal.premium_subtitle', { | ||
defaultValue: 'Larger amount that require more advanced features.', | ||
}), | ||
price: '389', | ||
features: [ | ||
t('pricing_modal.premium_feat.all_essential', { defaultValue: 'All essential plan features' }), | ||
t('pricing_modal.premium_feat.up_to_admins', { defaultValue: 'Up to 5 Admins' }), | ||
t('pricing_modal.premium_feat.yearly_processes', { defaultValue: '10 yearly voting processes' }), | ||
t('pricing_modal.premium_feat.custom_subdomain', { defaultValue: 'Custom subdomain & branding' }), | ||
t('pricing_modal.premium_feat.report_analytitcs', { defaultValue: 'Advanced reporting & analytics' }), | ||
t('pricing_modal.premium_feat.ticket_support', { defaultValue: 'Ticket and chat support' }), | ||
t('pricing_modal.premium_feat.gpdr_compilance', { defaultValue: 'GDPR compliance' }), | ||
], | ||
}, | ||
{ | ||
popular: false, | ||
title: t('pricing_modal.custom_title', { defaultValue: 'Custom Plan' }), | ||
subtitle: t('pricing_modal.custom_subtitle', { | ||
defaultValue: | ||
'Large organizations enterprises, and institutions requiring extensive customization and support.', | ||
}), | ||
price: '999', | ||
features: [ | ||
t('pricing_modal.custom_feat.all_votings', { defaultValue: 'All faetures & voting types' }), | ||
t('pricing_modal.custom_feat.unlimited_admins', { defaultValue: 'Unlimited Admins & suborgs' }), | ||
t('pricing_modal.custom_feat.unlimited_processes', { defaultValue: 'Unlimited yearly voting processes' }), | ||
t('pricing_modal.custom_feat.white_label_solution', { defaultValue: 'White-label solution' }), | ||
t('pricing_modal.custom_feat.advanced_security', { defaultValue: 'Advanced security features' }), | ||
t('pricing_modal.custom_feat.account_manager', { defaultValue: 'Dedicated account manager' }), | ||
t('pricing_modal.custom_feat.full_tech_support', { | ||
defaultValue: 'Full technical support (ticket, chat, email)', | ||
}), | ||
], | ||
}, | ||
] | ||
return ( | ||
<Modal isOpen={isOpenModal} onClose={onCloseModal} variant='dashboard-plans'> | ||
<ModalOverlay /> | ||
<ModalContent> | ||
<ModalHeader> | ||
<Trans i18nKey='pricing_modal.title'>You need to upgrade to use this feature</Trans> | ||
</ModalHeader> | ||
<ModalCloseButton fontSize='lg' /> | ||
<ModalBody> | ||
{cards.map((card, idx) => ( | ||
<PricingCard key={idx} {...card} /> | ||
))} | ||
</ModalBody> | ||
|
||
<ModalFooter> | ||
<Box> | ||
<Text> | ||
<Trans i18nKey='pricing_modal.more_voters'>If you need more voters, you can select it here:</Trans> | ||
</Text> | ||
<Select> | ||
<option>1-500 members</option> | ||
</Select> | ||
</Box> | ||
<Text> | ||
<Trans i18nKey='pricing_modal.your_plan'> | ||
Currently you are subscribed to the 'Your plan' subscription. If you upgrade, we will only charge the | ||
yearly difference. In the next billing period, starting on 'dd/mm/yy' you will pay for the new select | ||
plan. | ||
</Trans> | ||
</Text> | ||
<Box> | ||
<Text> | ||
<Trans i18nKey='pricing_modal.help'>Need some help?</Trans> | ||
</Text> | ||
<Button as={ReactRouterLink} colorScheme='white'> | ||
<Trans i18nKey='contact_us'>Contact us</Trans> | ||
</Button> | ||
</Box> | ||
</ModalFooter> | ||
</ModalContent> | ||
</Modal> | ||
) | ||
} |
Oops, something went wrong.
9629fe1
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.
π Published on https://vocdoni-app-stg.netlify.app as production
π Deployed on https://671f57ceee621cb6ed337c10--vocdoni-app-stg.netlify.app
9629fe1
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.
π Published on https://vocdoni-app-dev.netlify.app as production
π Deployed on https://671f57d174664d921fb72571--vocdoni-app-dev.netlify.app