Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dashboard, menu and dashboard voting page #793

Merged
merged 8 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/components/Account/LogoutBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ import { Button, ButtonProps } from '@chakra-ui/react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { useAuth } from '~components/Auth/useAuth'
import useDarkMode from '~components/Layout/useDarkMode'
import { Routes } from '~src/router/routes'

const LogoutBtn = (props?: ButtonProps) => {
const { t } = useTranslation()
const { logout: authLogout } = useAuth()
const navigate = useNavigate()
const { textColorSecondary } = useDarkMode()

const logout = () => {
setTimeout(() => {
Expand All @@ -22,7 +20,9 @@ const LogoutBtn = (props?: ButtonProps) => {
<Button
variant='outline'
border='none'
color={textColorSecondary}
bg='transparent'
color='contents.color.light'
_dark={{ color: 'contents.color.dark' }}
textDecoration='underline'
_hover={{ textDecoration: 'none' }}
{...props}
Expand Down
35 changes: 35 additions & 0 deletions src/components/Dashboard/Menu/Item.tsx
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>
)
130 changes: 130 additions & 0 deletions src/components/Dashboard/Menu/Options.tsx
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>
)
}
60 changes: 60 additions & 0 deletions src/components/Dashboard/Menu/index.tsx
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
126 changes: 126 additions & 0 deletions src/components/Dashboard/PricingModal.tsx
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>
)
}
Loading
Loading