First Commit

This commit is contained in:
cesnimda
2026-03-21 11:55:27 +01:00
commit 2e8a29b4d0
1757 changed files with 166084 additions and 0 deletions
@@ -0,0 +1,181 @@
'use client';
import { useEffect, useState } from 'react';
// @mui
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
// @project
import ContainerWrapper from '@/components/ContainerWrapper';
import { SECTION_COMMON_PY } from '@/utils/constant';
// Helper functions for scrollspy
const clamp = (value) => Math.max(0, value);
const isBetween = (value, floor, ceil) => value >= floor && value <= ceil;
/*************************** HOOKS - SCROLLSPY ***************************/
function useScrollspy(ids, offset = 0) {
const [activeId, setActiveId] = useState('');
useEffect(() => {
const listener = () => {
const scroll = window.scrollY;
const position = ids
.map((id) => {
const element = document.getElementById(id);
if (!element) return { id, top: -1, bottom: -1 };
const rect = element.getBoundingClientRect();
const top = clamp(rect.top + scroll - offset);
const bottom = clamp(rect.bottom + scroll - offset);
return { id, top, bottom };
})
.find(({ top, bottom }) => isBetween(scroll, top, bottom));
setActiveId(position?.id || '');
};
window.addEventListener('scroll', listener);
window.addEventListener('resize', listener);
listener(); // Initial call to set the activeId
return () => {
window.removeEventListener('scroll', listener);
window.removeEventListener('resize', listener);
};
}, [ids, offset]);
return activeId;
}
/*************************** TERMS CONDITION - DATA ***************************/
const menuItems = [
{
id: 'acceptance-of-terms',
heading: 'Acceptance of terms',
caption:
'By accessing and using this website, you agree to be bound by these Terms and Conditions of Use. If you do not agree with any part of these terms, you must not use the website. shares information about you when you use our website or services. By accessing or using our website, you consent to the practices described in this policy.'
},
{
id: 'changes-to-terms',
heading: 'Changes to terms',
caption:
'We reserve the right to modify or replace these terms at our sole discretion. It is your responsibility to check these terms periodically for changes. Your continued use of the website after the posting of any changes constitutes acceptance of those changes.'
},
{
id: 'user-conduct',
heading: 'User conduct',
caption:
'You agree to use this website only for lawful purposes and in a manner consistent with all applicable local, national, and international laws and regulations.'
},
{
id: 'intellectual-property',
heading: 'Intellectual property',
caption:
'All content on this website, including but not limited to text, graphics, logos, images, audio clips, video clips, digital downloads, and data compilations, is the property of [Your Company Name] or its content suppliers and protected by international copyright laws.'
},
{
id: 'privacy-policy',
heading: 'Privacy policy',
caption:
'We do not sell, trade, or otherwise transfer your personal information to third parties. We may share information with trusted service providers who assist us in operating our website, conducting our business, or servicing you.'
},
{
id: 'user-generated-content',
heading: 'User-generated content',
caption:
'If you submit any material to this website, you grant [Your Company Name] a perpetual, royalty-free, worldwide license to use, reproduce, modify, adapt, publish, translate, create derivative works from, distribute, and display such material.'
},
{
id: 'limitation-of-liability',
heading: 'Limitation of liability',
caption:
'In no event shall [Your Company Name] or its affiliates be liable for any direct, indirect, incidental, special, or consequential damages resulting from the use or inability to use this website.'
},
{
id: 'indemnity',
heading: 'Indemnity',
caption:
"You agree to indemnify and hold harmless [Your Company Name] and its affiliates from any claims, actions, demands, damages, liabilities, costs, or expenses, including reasonable attorneys' fees, arising out of or related to your use of the website or any violation of these terms."
},
{
id: 'governing-law',
heading: 'Governing law',
caption:
'These terms are governed by and construed in accordance with the laws of [Your Country/State], without regard to its conflict of law principles.'
}
];
/*************************** SECTIONS - TERMS CONDITION ***************************/
/**
*
* Demos:
* - [Terms Condition](https://www.saasable.io/sections/terms-condition)
*/
export default function TermsConditionPage() {
const ids = menuItems.map((item) => item.id);
// Adjust offset as per header height
const activeId = useScrollspy(ids, 60);
const [selectedID, setSelectedID] = useState(activeId);
useEffect(() => {
if (activeId) {
setSelectedID(activeId);
}
}, [activeId]);
return (
<ContainerWrapper sx={{ pb: SECTION_COMMON_PY }}>
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid size={{ xs: 12, sm: 4, md: 3 }}>
<List component="div" sx={{ position: 'sticky', top: 20 }} disablePadding>
{menuItems.map((item, index) => (
<ListItemButton
key={index}
href={`#${item.id}`}
sx={{
py: 1.25,
px: 1.5,
borderRadius: 3,
mb: 0.75,
...(selectedID === item.id && { color: 'primary.main', bgcolor: 'grey.100' }),
'&:hover': { bgcolor: 'grey.50' }
}}
onClick={() => setSelectedID(item.id)}
>
<ListItemText primary={item.heading} sx={{ my: 0 }} slotProps={{ primary: { variant: 'subtitle1' } }} />
</ListItemButton>
))}
</List>
<Divider sx={{ display: { xs: 'block', sm: 'none' } }} />
</Grid>
<Grid size={{ xs: 12, sm: 8, md: 9 }}>
{menuItems.map((item, index) => (
<Stack
key={index}
id={item.id}
sx={{ py: { xs: 1, sm: 1.5, md: 3 }, px: { md: 3 }, gap: 1, '&:first-of-type': { pt: { sm: 0 } } }}
>
<Typography variant="h4">{item.heading}</Typography>
<Typography variant="body1" sx={{ color: 'text.secondary' }}>
{item.caption}
</Typography>
</Stack>
))}
</Grid>
</Grid>
</ContainerWrapper>
);
}
@@ -0,0 +1,246 @@
import PropTypes from 'prop-types';
// @mui
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @project
import { GraphicsCard } from '@/components/cards';
import Typeset from '@/components/Typeset';
/*************************** TYPOGRAPHY - DATA ***************************/
const typographyData = [
{
heading: 'Headings',
items: [
{
title: 'Heading 01',
sizeValue: { desktop: '57px', tablet: '45px', mobile: '36px' },
lineHeight: { desktop: '64px', tablet: '52px', mobile: '44px' },
letterSpacing: { desktop: '-0.25px', tablet: '0px', mobile: '0px' },
variant: 'h1',
label: 'h1 - Heading Large',
fontWeight: 'Regular (400)'
},
{
title: 'Heading 02',
sizeValue: { desktop: '45px', tablet: '36px', mobile: '24px' },
lineHeight: { desktop: '52px', tablet: '44px', mobile: '32px' },
letterSpacing: { desktop: '0px', tablet: '0px', mobile: '0px' },
variant: 'h2',
label: 'h2 - Heading Medium',
fontWeight: 'Regular (400)'
},
{
title: 'Heading 03',
sizeValue: { desktop: '28px', tablet: '24px', mobile: '20px' },
lineHeight: { desktop: '36px', tablet: '32px', mobile: '28px' },
letterSpacing: { desktop: '0px', tablet: '0px', mobile: '0px' },
variant: 'h3',
label: 'h3 - Display Medium',
fontWeight: 'Regular (400)'
},
{
title: 'Heading 04',
sizeValue: { desktop: '24px', tablet: '20px', mobile: '16px' },
lineHeight: { desktop: '32px', tablet: '28px', mobile: '24px' },
letterSpacing: { desktop: '0px', tablet: '0px', mobile: '0px' },
variant: 'h4',
label: 'h4 - Display Small',
fontWeight: 'Regular (400)'
},
{
title: 'Heading 05',
sizeValue: { desktop: '22px', tablet: '16px', mobile: '16px' },
lineHeight: { desktop: '28px', tablet: '24px', mobile: '24px' },
letterSpacing: { desktop: '0px', tablet: '0.15px', mobile: '0.15px' },
variant: 'h5',
label: 'h5 - Label Large',
fontWeight: 'Medium (500)'
},
{
title: 'Heading 06',
sizeValue: { desktop: '22px', tablet: '16px', mobile: '16px' },
lineHeight: { desktop: '30px', tablet: '24px', mobile: '24px' },
letterSpacing: { desktop: '0px', tablet: '0.5px', mobile: '0.5px' },
variant: 'h6',
label: 'h6 - Paragraph Large',
fontWeight: 'Regular (400)'
}
]
},
{
heading: 'Body / Paragraph',
items: [
{
title: 'Body 01',
sizeValue: { desktop: '16px', tablet: '14px', mobile: '14px' },
lineHeight: { desktop: '24px', tablet: '20px', mobile: '20px' },
letterSpacing: { desktop: '0.5px', tablet: '0.25px', mobile: '0.25px' },
variant: 'body1',
label: 'body1 - Paragraph Medium',
fontWeight: 'Regular (400)'
},
{
title: 'Body 02',
sizeValue: { desktop: '14px', tablet: '12px', mobile: '12px' },
lineHeight: { desktop: '20px', tablet: '18px', mobile: '18px' },
letterSpacing: { desktop: '0.25px', tablet: '0.25px', mobile: '0.25px' },
variant: 'body2',
label: 'body2 - Paragraph Small',
fontWeight: 'Regular (400)'
}
]
},
{
heading: 'Subtitle',
items: [
{
title: 'Subtitle 01',
sizeValue: { desktop: '16px', tablet: '14px', mobile: '14px' },
lineHeight: { desktop: '24px', tablet: '20px', mobile: '20px' },
letterSpacing: { desktop: '0.15px', tablet: '0.1px', mobile: '0.1px' },
variant: 'subtitle1',
label: 'subtitle1 - Label Medium',
fontWeight: 'Medium (500)'
},
{
title: 'Subtitle 02',
sizeValue: { desktop: '14px', tablet: '12px', mobile: '12px' },
lineHeight: { desktop: '20px', tablet: '18px', mobile: '18px' },
letterSpacing: { desktop: '0.1px', tablet: '0.1px', mobile: '0.1 px' },
variant: 'subtitle2',
label: 'subtitle2 - Label Small',
fontWeight: 'Semibold (600)'
}
]
},
{
heading: 'Caption',
items: [
{
title: 'Caption',
sizeValue: { desktop: '12px', tablet: '12px', mobile: '12px' },
lineHeight: { desktop: '16px', tablet: '16px', mobile: '16px' },
letterSpacing: { desktop: '0px', tablet: '0px', mobile: '0 px' },
variant: 'caption',
label: 'caption - Hyperlink Small',
fontWeight: 'Semibold (600)'
},
{
title: 'Caption 01',
sizeValue: { desktop: '16px', tablet: '16px', mobile: '16px' },
lineHeight: { desktop: '24px', tablet: '24px', mobile: '24px' },
letterSpacing: { desktop: '0.5px', tablet: '0.5px', mobile: '0.5px' },
variant: 'caption1',
label: 'caption1 - Hyperlink Large',
fontWeight: 'Regular (400)'
},
{
title: 'Caption 02',
sizeValue: { desktop: '14px', tablet: '14px', mobile: '14px' },
lineHeight: { desktop: '20px', tablet: '20px', mobile: '20px' },
letterSpacing: { desktop: '0.1px', tablet: '0.1 px', mobile: '0.1px' },
variant: 'caption2',
label: 'caption2 - Hyperlink Medium',
fontWeight: 'Medium (500)'
}
]
}
];
/*************************** TYPOGRAPHY - BLOCK ***************************/
function FontSizeBlock({ desktop, tablet, mobile, type }) {
let label1 = 'Desktop';
let label2 = 'Tablet';
let label3 = 'Mobile';
if (type === 'letter-spacing') {
label1 = label2 = label3 = 'Letter Spacing';
}
if (type === 'line-height') {
label1 = label2 = label3 = 'Line Height';
}
return (
<Grid container spacing={1}>
<Grid size={4}>
<Stack sx={{ gap: 0.5 }}>
<Typography variant="body2" sx={{ color: 'grey.700' }}>
{label1}
</Typography>
<Typography variant="subtitle1">{desktop}</Typography>
</Stack>
</Grid>
<Grid size={4}>
<Stack sx={{ gap: 0.5 }}>
<Typography variant="body2" sx={{ color: 'grey.700' }}>
{label2}
</Typography>
<Typography variant="subtitle1">{tablet}</Typography>
</Stack>
</Grid>
<Grid size={4}>
<Stack sx={{ gap: 0.5 }}>
<Typography variant="body2" sx={{ color: 'grey.700' }}>
{label3}
</Typography>
<Typography variant="subtitle1">{mobile}</Typography>
</Stack>
</Grid>
</Grid>
);
}
/*************************** SECTIONS - TYPOGRAPHY ***************************/
/**
*
* Demos:
* - [Typography](https://www.saasable.io/sections/typography)
*/
export default function TypographyPage() {
return (
<Grid container spacing={{ xs: 2.5, sm: 3, md: 4 }}>
{typographyData.map((item, index) => (
<Grid key={index} size={12}>
<Stack sx={{ gap: { xs: 1.5 } }}>
<Typeset {...{ heading: item.heading, stackProps: { sx: { mb: 1 } } }} />
{item.items.map((block, index) => (
<GraphicsCard key={index}>
<Box sx={{ p: { xs: 3, sm: 4, md: 5 } }}>
<Grid container spacing={2.5}>
<Grid size={{ xs: 12, sm: 3, md: 2 }}>
<Typography variant="h4">{block.title}</Typography>
<Typography variant="body2" sx={{ color: 'grey.700' }}>
{block.label}
</Typography>
</Grid>
<Grid size={{ xs: 11, sm: 6, md: 4 }}>
<Stack sx={{ gap: 2.5 }}>
<Typography variant="subtitle1">{block.fontWeight}</Typography>
<FontSizeBlock {...block.sizeValue} type="font-size" />
<FontSizeBlock {...block.lineHeight} type="line-height" />
<FontSizeBlock {...block.letterSpacing} type="letter-spacing" />
</Stack>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Typography variant={block.variant}>Quick brown fox jumps!</Typography>
</Grid>
</Grid>
</Box>
</GraphicsCard>
))}
</Stack>
</Grid>
))}
</Grid>
);
}
FontSizeBlock.propTypes = { desktop: PropTypes.string, tablet: PropTypes.string, mobile: PropTypes.string, type: PropTypes.string };
@@ -0,0 +1,125 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import { useTheme } from '@mui/material/styles';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
import Slider from 'react-slick';
// @project
import ContainerWrapper from '@/components/ContainerWrapper';
import GraphicsImage from '@/components/GraphicsImage';
import { withAlpha } from '@/utils/colorUtils';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** CLIENTELE - 3 ***************************/
/**
*
* Demos:
* - [Clientele3](https://www.saasable.io/blocks/clientele/clientele3)
*
* API:
* - [Clientele3 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/clientele/clientele3#props-details)
*/
export default function Clientele3({ title, clienteleList }) {
const theme = useTheme();
const settings = {
autoplay: true,
arrows: false,
dots: false,
infinite: true,
speed: 500,
slidesToShow: 5,
swipeToSlide: true,
initialSlide: 0,
responsive: [
{
breakpoint: theme.breakpoints.values.md,
settings: { slidesToShow: 4 }
},
{
breakpoint: theme.breakpoints.values.sm,
settings: { slidesToShow: 2, centerMode: true }
}
]
};
const shade = {
content: `' '`,
zIndex: 1,
position: 'absolute',
width: { sm: 60, xs: 40 },
height: 1,
top: 0,
background: `linear-gradient(90deg, ${theme.vars.palette.background.default} -8.54%, ${withAlpha(theme.vars.palette.background.default, 0)} 100%)`,
transform: null
};
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: 2.5 }}>
{title && (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<Typography variant="subtitle2" align="center" sx={{ color: 'text.secondary' }}>
{title}
</Typography>
</motion.div>
)}
<Box sx={{ position: 'relative', '&:before': { ...shade, left: 0 }, '&:after': { ...shade, right: 0, rotate: '180deg' } }}>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
<Slider {...settings}>
{clienteleList.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.5 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: 'easeOut', delay: index * 0.2 }}
>
<Box
sx={{
px: { xs: 0.25, sm: 0.5, md: 0.75 },
'& svg': { opacity: 0.4, transition: ' all 0.5s ease-in-out' },
'&:hover svg': { opacity: 1, transition: ' all 0.5s ease-in-out' }
}}
>
<Chip
label={<GraphicsImage {...item} />}
slotProps={{ label: { sx: { p: 0 } } }}
sx={{ bgcolor: 'grey.100', height: { xs: 40, sm: 46, md: 60 }, width: 1 }}
/>
</Box>
</motion.div>
))}
</Slider>
</motion.div>
</Box>
</Stack>
</ContainerWrapper>
);
}
Clientele3.propTypes = { title: PropTypes.string, clienteleList: PropTypes.array };
@@ -0,0 +1 @@
export { default as Clientele3 } from './Clientele3';
@@ -0,0 +1,131 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'framer-motion';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import { GraphicsCard } from '@/components/cards';
import { ContactUsForm2 } from '@/components/contact-us';
import ContainerWrapper from '@/components/ContainerWrapper';
import SvgIcon from '@/components/SvgIcon';
import Typeset from '@/components/Typeset';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** CONTACT US - CARD ***************************/
function ContactCard({ icon, title, content, link }) {
return (
<GraphicsCard sx={{ height: 1 }}>
<Stack direction={{ xs: 'row', sm: 'column' }} sx={{ gap: { xs: 2, sm: 4, md: 5 }, height: 1, p: { xs: 2, sm: 3, md: 4 } }}>
<Avatar sx={{ width: 60, height: 60, bgcolor: 'grey.300' }}>
<SvgIcon {...(typeof icon === 'string' ? { name: icon } : { ...icon })} />
</Avatar>
<Stack sx={{ gap: { xs: 2, md: 3 }, height: 1, alignItems: 'flex-start', justifyContent: 'space-between' }}>
<Typeset
{...{
heading: title,
caption: content,
stackProps: { sx: { gap: 1 } },
headingProps: { variant: 'h4' },
captionProps: { variant: 'body1' }
}}
/>
{link && (
<ButtonAnimationWrapper>
<Button color="primary" variant="outlined" {...link} />
</ButtonAnimationWrapper>
)}
</Stack>
</Stack>
</GraphicsCard>
);
}
/*************************** CONTACT US - 4 ***************************/
/**
*
* Demos:
* - [ContactUs4](https://www.saasable.io/blocks/contact-us/contact-us4)
*
* API:
* - [ContactUs4 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/contact-us/contactus4#props-details)
*/
export default function ContactUs4({ heading, caption, list, showForm = true }) {
const sectionPadding = { xs: 2, sm: 3, md: 5 };
const cardRadius = { xs: 6, sm: 8 };
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
<Grid container spacing={1.5}>
{showForm && (
<Grid size={12}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.4,
delay: 0.4
}}
style={{ height: '100%' }}
>
<GraphicsCard sx={{ height: 1, borderRadius: cardRadius }}>
<GraphicsCard sx={{ bgcolor: 'grey.200', borderRadius: cardRadius }}>
<Box sx={{ p: { xs: 2, sm: 4, md: 5 } }}>
{heading && (
<Typeset
{...{
heading,
caption,
stackProps: { sx: { alignItems: 'center', textAlign: 'center' } },
headingProps: { sx: { maxWidth: { xs: '85%', sm: '80%' } } },
captionProps: { sx: { maxWidth: { sm: '60%' } } }
}}
/>
)}
</Box>
</GraphicsCard>
<Box sx={{ p: sectionPadding, px: { md: 24 } }}>
<ContactUsForm2 />
</Box>
</GraphicsCard>
</motion.div>
</Grid>
)}
{list?.map((item, index) => (
<Grid key={index} size={{ xs: 12, sm: 4 }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.4,
delay: item.animationDelay
}}
style={{ height: '100%' }}
>
<ContactCard {...{ ...item }} />
</motion.div>
</Grid>
))}
</Grid>
</Stack>
</ContainerWrapper>
);
}
ContactCard.propTypes = { icon: PropTypes.any, title: PropTypes.any, content: PropTypes.any, link: PropTypes.any };
ContactUs4.propTypes = { heading: PropTypes.any, caption: PropTypes.any, list: PropTypes.any, showForm: PropTypes.bool };
@@ -0,0 +1 @@
export { default as ContactUs4 } from './ContactUs4';
@@ -0,0 +1,122 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import ContainerWrapper from '@/components/ContainerWrapper';
import { GraphicsCard } from '@/components/cards';
import { ProfileGroup } from '@/components/cards/profile-card';
import SvgIcon from '@/components/SvgIcon';
import { SECTION_COMMON_PY } from '@/utils/constant';
// @assets
import Arrow from '@/images/graphics/Arrow';
/*************************** CALL TO ACTION - 4 ***************************/
/**
*
* Demos:
* - [CTA4](https://www.saasable.io/blocks/cta/cta4)
*
* API:
* - [CTA4 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/cta/cta4#props-details)
*/
export default function Cta4({ headLine, primaryBtn, profileGroups, list, clientContent }) {
const transformValues = { xs: 'rotate(45deg)', sm: 'rotate(320deg)', md: 'unset' };
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<GraphicsCard>
<Box sx={{ p: { xs: 3, sm: 4, md: 5 } }}>
<Grid container spacing={{ xs: 5, sm: 0, md: 3 }} sx={{ alignItems: 'flex-end' }}>
<Grid size={{ xs: 12, sm: 9, md: 8 }}>
<Stack sx={{ gap: 5 }}>
<ProfileGroup {...profileGroups} />
<Stack sx={{ gap: { xs: 2, sm: 5 } }}>
{typeof headLine === 'string' ? <Typography variant="h2">{headLine}</Typography> : headLine}
{list && (
<Stack direction={{ sm: 'row' }} sx={{ columnGap: { xs: 1, sm: 3 }, rowGap: 1, flexWrap: 'wrap' }}>
{list.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.2, ease: 'easeInOut', delay: index * 0.6 }}
>
<Stack direction="row" sx={{ gap: 1, alignItems: 'center' }}>
<SvgIcon name="tabler-rosette-discount-check" color="text.secondary" stroke={1} />
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{item.primary}
</Typography>
</Stack>
</motion.div>
))}
</Stack>
)}
</Stack>
</Stack>
</Grid>
<Grid sx={{ position: 'relative', pl: { md: 3 }, pt: { md: 3 } }} size={{ sm: 3, md: 4 }}>
<Box
sx={{
position: 'absolute',
top: { xs: -36, sm: -98, md: -68 },
right: { xs: -70, sm: 40, md: 100 },
transform: transformValues
}}
>
<Arrow />
</Box>
<Typography
variant="subtitle1"
sx={{
color: 'primary.main',
width: 94,
position: 'absolute',
top: { xs: 6, sm: -160, md: -82 },
right: { xs: -160, sm: 0 }
}}
>
{clientContent}
</Typography>
<Box sx={{ textAlign: 'right' }}>
<motion.div
initial={{ scale: 0.9 }}
animate={{ opacity: 1, y: 0, scale: [1, 1.05, 1] }}
transition={{ duration: 1.1, delay: 0.1, ease: 'easeInOut', repeat: Infinity }}
whileHover={{ scale: 1, transition: { duration: 0.3 } }}
whileTap={{ scale: 0.95 }}
>
<ButtonAnimationWrapper>
<Button color="primary" size="large" variant="contained" sx={{ minWidth: { md: 263 } }} {...primaryBtn} />
</ButtonAnimationWrapper>
</motion.div>
</Box>
</Grid>
</Grid>
</Box>
</GraphicsCard>
</ContainerWrapper>
);
}
Cta4.propTypes = {
headLine: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
primaryBtn: PropTypes.any,
profileGroups: PropTypes.object,
list: PropTypes.array,
clientContent: PropTypes.string
};
@@ -0,0 +1,200 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import OutlinedInput from '@mui/material/OutlinedInput';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import ContainerWrapper from '@/components/ContainerWrapper';
import { GraphicsCard } from '@/components/cards';
import { ProfileGroup } from '@/components/cards/profile-card';
import LogoWatermark from '@/components/logo/LogoWatermark';
import Typeset from '@/components/Typeset';
import { SECTION_COMMON_PY } from '@/utils/constant';
// @assets
import Wave from '@/images/graphics/Wave';
/*************************** CALL TO ACTION - 5 ***************************/
/**
*
* Demos:
* - [CTA5](https://www.saasable.io/blocks/cta/cta5)
*
* API:
* - [CTA5 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/cta/cta5#props-details)
*/
export default function Cta5({ heading, caption, label, input = false, primaryBtn, secondaryBtn, description, saleData, profileGroups }) {
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
<Grid container spacing={1.5}>
<Grid size={{ xs: 12, sm: 8, md: 9 }}>
<motion.div initial={{ opacity: 0, y: -100 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.8, delay: 0.6 }}>
<GraphicsCard sx={{ position: 'relative' }}>
<Stack
sx={{ alignItems: 'flex-start', gap: { xs: 5.75, sm: 10 }, p: { xs: 3, sm: 4, md: 8 }, position: 'relative', zIndex: 1 }}
>
<Stack sx={{ gap: 5 }}>
<Stack direction="row" sx={{ alignItems: 'center', gap: 1 }}>
<Chip
label={label}
variant="outlined"
slotProps={{ label: { sx: { py: 0.75, px: 2, typography: 'caption', color: 'secondary.main' } } }}
sx={{ borderColor: 'grey.600' }}
/>
<Divider sx={{ width: 63, borderBottomWidth: 2 }} />
</Stack>
<Typeset {...{ heading, caption, captionProps: { sx: { maxWidth: 478 } } }} />
</Stack>
{input && typeof input === 'object' && (
<Stack sx={{ gap: 0.75, width: { sm: '100%', md: 'unset' } }}>
<OutlinedInput
placeholder={input.placeholder || 'Enter your email address'}
endAdornment={
<Button
color="primary"
variant="contained"
sx={{ px: 4, minWidth: { xs: 110, md: 120 } }}
{...input.adornmentBtn}
/>
}
slotProps={{
input: { 'aria-label': 'Email address', sx: { px: 2.5, py: 0.75 } },
notchedOutline: { sx: { borderRadius: 25 } }
}}
sx={{ typography: 'caption2', color: 'secondary.main', p: 0.5, whiteSpace: 'nowrap' }}
/>
{input.helpertext && (
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{input.helpertext}
</Typography>
)}
</Stack>
)}
{(primaryBtn || secondaryBtn || description) && (
<Stack sx={{ alignItems: 'flex-start', gap: 1.5, width: { sm: '100%', md: '60%' }, ...(input && { mt: -6 }) }}>
{(primaryBtn || secondaryBtn) && (
<Stack direction="row" spacing={1.5} sx={{ alignItems: 'flex-start', justifyContent: 'center' }}>
{secondaryBtn && (
<ButtonAnimationWrapper>
<Button variant="outlined" sx={{ minWidth: { sm: 170 } }} {...secondaryBtn} />
</ButtonAnimationWrapper>
)}
{primaryBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button variant="contained" sx={{ minWidth: { sm: 170 } }} {...primaryBtn} />
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
)}
{description && typeof description === 'string' ? (
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{description}
</Typography>
) : (
description
)}
</Stack>
)}
</Stack>
<Box
sx={{ position: 'absolute', right: -160, bottom: -160, display: { xs: 'none', md: 'block' }, transform: 'scaleX(-1)' }}
>
<LogoWatermark />
</Box>
</GraphicsCard>
</motion.div>
</Grid>
<Grid size={{ xs: 12, sm: 4, md: 3 }}>
<Grid container sx={{ height: 1, position: 'relative' }}>
<Grid size={{ xs: 6, sm: 12 }} sx={{ minHeight: { sm: '50%' } }}>
<GraphicsCard sx={{ height: 1 }}>
<Stack sx={{ alignItems: 'center', gap: 1, py: { xs: 2, sm: 6, md: 7.5 }, px: { xs: 2, sm: 3.5 }, textAlign: 'center' }}>
<Typography component="div" variant="h1">
{saleData.count}
<Typography variant="h2" component="span" sx={{ color: 'text.secondary' }}>
{saleData.defaultUnit}
</Typography>
</Typography>
<Typography sx={{ color: 'text.secondary' }}>{saleData.caption}</Typography>
</Stack>
</GraphicsCard>
</Grid>
<Box
sx={{
position: 'absolute',
left: '50%',
top: '50%',
transform: { xs: 'translate(-50%,-50%) rotate(90deg)', sm: 'translate(-50%,-50%)' },
'& .wave svg': { width: { xs: 70, sm: 122 } }
}}
>
<Wave />
</Box>
<Grid size={{ xs: 6, sm: 12 }} sx={{ minHeight: { sm: '50%' } }}>
<GraphicsCard sx={{ height: 1 }}>
<ProfileGroup
{...profileGroups}
sx={{
py: { xs: 2, sm: 4, md: 6.75 },
px: { xs: 2, sm: 1.5 },
height: 1,
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
'& .MuiAvatarGroup-root': { mb: 0.5 },
'& .MuiAvatar-root': { width: { xs: 40, sm: 58 }, height: { xs: 40, sm: 58 } },
'& .wave': { display: 'none' }
}}
/>
</GraphicsCard>
</Grid>
</Grid>
</Grid>
</Grid>
</motion.div>
</ContainerWrapper>
);
}
Cta5.propTypes = {
heading: PropTypes.string,
caption: PropTypes.string,
label: PropTypes.string,
input: PropTypes.oneOfType([PropTypes.bool, PropTypes.any]),
primaryBtn: PropTypes.any,
secondaryBtn: PropTypes.any,
description: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
saleData: PropTypes.any,
profileGroups: PropTypes.object
};
@@ -0,0 +1,2 @@
export { default as Cta4 } from './Cta4';
export { default as Cta5 } from './Cta5';
@@ -0,0 +1,211 @@
'use client';
import PropTypes from 'prop-types';
import { useState } from 'react';
// @next
import NextLink from 'next/link';
// @mui
import { useTheme } from '@mui/material/styles';
import Accordion from '@mui/material/Accordion';
import AccordionDetails from '@mui/material/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
// @third-party
import { motion } from 'motion/react';
import Slider from 'react-slick';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import ContainerWrapper from '@/components/ContainerWrapper';
import FaqDetails from '@/components/faq/FaqDetails';
import SvgIcon from '@/components/SvgIcon';
import Typeset from '@/components/Typeset';
import useFocusWithin from '@/hooks/useFocusWithin';
import { generateFocusVisibleStyles } from '@/utils/CommonFocusStyle';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** FAQ - 6 ***************************/
/**
*
* Demos:
* - [FAQ6](https://www.saasable.io/blocks/faq/faq6)
*
* API:
* - [FAQ6 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/faq/faq6#props-details)
*/
export default function Faq6({ heading, caption, defaultExpanded, faqList, getInTouch, categories, activeCategory }) {
const theme = useTheme();
const isFocusWithin = useFocusWithin();
const [expanded, setExpanded] = useState(defaultExpanded || false);
const [activeTopic, setActiveTopic] = useState(activeCategory || '');
const [filterFaqList, setFilterFaqList] = useState(activeCategory ? faqList.filter((item) => item.category === activeCategory) : faqList);
const cardRadius = { xs: 4, sm: 6 };
const accordionRadius = { xs: cardRadius.xs * 4, sm: cardRadius.sm * 4 };
const accordionPX = { xs: 2, sm: 3 };
const iconProps = { color: 'text.primary' };
// Handles the expansion of accordion panels
const handleChange = (panel) => (event, isExpanded) => setExpanded(isExpanded ? panel : false);
const slickStyle = { '& .slick-slide': { ' > div': { px: { xs: 0.5, md: 0.75 } } } };
const settings = {
arrows: false,
dots: false,
infinite: false,
speed: 500,
swipeToSlide: true,
initialSlide: 0,
variableWidth: true
};
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
{heading && (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<Stack direction={{ sm: 'row' }} sx={{ gap: 4, justifyContent: 'space-between', alignItems: { xs: 'flex-start', sm: 'end' } }}>
<Typeset {...{ heading, caption }} />
{getInTouch?.link && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="contained"
size="large"
{...getInTouch.link}
{...(getInTouch.link && getInTouch.link.href && { component: NextLink })}
sx={{ minWidth: 215, ...getInTouch.link.sx }}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
</motion.div>
)}
<Stack sx={{ gap: 2 }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
<Stack sx={slickStyle}>
<Slider {...settings}>
<Button
sx={{
minHeight: { xs: 40, sm: 48 },
color: 'text.primary',
borderColor: 'divider',
bgcolor: activeTopic === '' ? 'grey.100' : 'inherit',
'&.MuiButton-root:hover': { bgcolor: 'grey.100', borderColor: 'divider' }
}}
variant="outlined"
onClick={() => {
setActiveTopic('');
setFilterFaqList(faqList);
}}
>
All
</Button>
{categories.map((item, index) => (
<Button
key={index}
sx={{
minHeight: { xs: 40, sm: 48 },
color: 'text.primary',
borderColor: 'divider',
bgcolor: activeTopic === item ? 'grey.100' : 'inherit',
'&.MuiButton-root:hover': { bgcolor: 'grey.100', borderColor: 'divider' }
}}
variant="outlined"
onClick={() => {
setActiveTopic(item);
setFilterFaqList(faqList.filter((list) => list.category === item));
}}
>
{item}
</Button>
))}
</Slider>
</Stack>
</motion.div>
<Stack
sx={{
gap: 1.5,
'& .MuiAccordion-root:first-of-type': { borderTopLeftRadius: accordionRadius, borderTopRightRadius: accordionRadius },
'& .MuiAccordion-root:last-of-type': { borderBottomLeftRadius: accordionRadius, borderBottomRightRadius: accordionRadius }
}}
>
{filterFaqList.map((item, index) => (
<motion.div
key={index}
initial={{ y: 20, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.2, delay: index * 0.2 }}
>
<Accordion
key={index}
expanded={expanded === `panel${index}`}
onChange={handleChange(`panel${index}`)}
sx={{
borderRadius: cardRadius,
backgroundColor: 'grey.100',
...(isFocusWithin && { '&:focus-within': generateFocusVisibleStyles(theme.vars.palette.primary.main) })
}}
>
<AccordionSummary
expandIcon={<SvgIcon name={expanded === `panel${index}` ? 'tabler-minus' : 'tabler-plus'} {...iconProps} size={20} />}
sx={{
p: accordionPX,
'&.Mui-focusVisible': { bgcolor: 'transparent' },
'&:hover, &:hover svg': { color: 'primary.dark' }
}}
slotProps={{ content: { sx: { my: 0 } } }}
>
<Typography variant="h4">{item.question}</Typography>
</AccordionSummary>
<AccordionDetails sx={{ px: accordionPX, pt: 0, pb: accordionPX }} key={index}>
<FaqDetails answer={item.answer} />
</AccordionDetails>
</Accordion>
</motion.div>
))}
</Stack>
</Stack>
</Stack>
</ContainerWrapper>
);
}
Faq6.propTypes = {
heading: PropTypes.any,
caption: PropTypes.any,
defaultExpanded: PropTypes.any,
faqList: PropTypes.any,
getInTouch: PropTypes.any,
categories: PropTypes.array,
activeCategory: PropTypes.string
};
@@ -0,0 +1 @@
export { default as Faq6 } from './Faq6';
@@ -0,0 +1,268 @@
'use client';
import PropTypes from 'prop-types';
import { useState } from 'react';
// @mui
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Tab from '@mui/material/Tab';
import TabContext from '@mui/lab/TabContext';
import TabList from '@mui/lab/TabList';
import TabPanel from '@mui/lab/TabPanel';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ContainerWrapper from '@/components/ContainerWrapper';
import { GraphicsCard } from '@/components/cards';
import SvgIcon from '@/components/SvgIcon';
import Typeset from '@/components/Typeset';
import { SECTION_COMMON_PY } from '@/utils/constant';
// @assets
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import GraphicsImage from '@/components/GraphicsImage';
/*************************** FEATURE - 18 ***************************/
/**
*
* Demos:
* - [Feature18](https://www.saasable.io/blocks/feature/feature18)
*
* API
* - [Feature18 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/feature/feature18#props-details)
*/
export default function Feature18({ heading, caption, topics }) {
const boxPadding = { xs: 3, md: 5 };
const imagePadding = { xs: 3, sm: 4, md: 5 };
const [value, setValue] = useState('1');
// Handle tab change
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
<Typeset
{...{
heading,
caption,
stackProps: { sx: { alignItems: 'center', textAlign: 'center', maxWidth: { sm: 470, md: 615 }, mx: 'auto' } }
}}
/>
<Stack sx={{ gap: 1.5, alignItems: 'center' }}>
<TabContext value={value}>
<GraphicsCard sx={{ width: { xs: 1, sm: 'unset' } }}>
<Box sx={{ p: 0.25 }}>
<TabList
onChange={handleChange}
sx={{ minHeight: 'unset', p: 0.25 }}
slotProps={{ indicator: { sx: { display: 'none' } } }}
variant="scrollable"
>
{topics.map((item, index) => (
<Tab
label={item.title}
disableFocusRipple
{...(item.icon && {
icon: (
<SvgIcon
{...(typeof item.icon === 'string' ? { name: item.icon } : { ...item.icon })}
size={16}
stroke={2}
color="text.secondary"
/>
)
})}
value={String(index + 1)}
key={index}
iconPosition="start"
tabIndex={0}
sx={{
minHeight: 44,
minWidth: { xs: 112, md: 160, sm: 156 },
borderRadius: 10,
borderWidth: 1,
borderStyle: 'solid',
borderColor: 'transparent',
'& svg ': { mr: 1 },
'&.Mui-selected': {
bgcolor: 'grey.200',
borderColor: 'grey.400',
minWidth: { xs: 112, md: 160, sm: 156 },
color: 'text.primary',
'& svg': { stroke: 'text.primary' }
},
'&.Mui-focusVisible': { bgcolor: 'grey.300' },
'&:hover': { bgcolor: 'grey.200' }
}}
/>
))}
</TabList>
</Box>
</GraphicsCard>
{topics.map((item, index) => (
<TabPanel value={String(index + 1)} key={index} sx={{ p: 0, width: 1 }}>
<Grid container spacing={1.5}>
<Grid size={{ xs: 12, sm: 5 }}>
<GraphicsCard>
<motion.div
initial={{ opacity: 0, x: 100, y: 100 }}
whileInView={{ opacity: 1, x: 0, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<Box
sx={{
pl: item.isCoverImage ? 0 : imagePadding,
pt: item.isCoverImage ? 0 : imagePadding,
height: { xs: 260, sm: 396, md: 434 }
}}
>
<GraphicsImage
sx={{
height: 1,
backgroundPositionX: 'left',
backgroundPositionY: 'top',
...(item.isImageBorder && { borderTop: '5px solid', borderLeft: '5px solid', borderColor: 'grey.200' }),
...(item.isCoverImage && { backgroundSize: 'cover', border: 'none' }),
borderTopLeftRadius: { xs: 12 },
borderBottomRightRadius: { xs: 20, sm: 32, md: 40 }
}}
image={item.image}
/>
</Box>
</motion.div>
</GraphicsCard>
</Grid>
<Grid size={{ xs: 12, sm: 7 }} sx={{ display: 'flex' }}>
<GraphicsCard>
<Stack
sx={{
justifyContent: 'space-between',
gap: 5,
height: item.actionBtn || item.actionBtn2 ? { sm: 'calc(100% - 98px)', md: 'calc(100% - 114px)' } : 1,
pt: boxPadding,
px: boxPadding
}}
>
<motion.div
key={index}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -30 }}
transition={{ duration: 0.2, ease: 'linear', delay: index * 0.1 }}
>
<Stack direction="row" sx={{ gap: 1 }}>
{item.icon && (
<SvgIcon
{...(typeof item.icon === 'string' ? { name: item.icon } : { ...item.icon })}
size={16}
stroke={2}
color="text.primary"
/>
)}
<Typography variant="subtitle2" sx={{ color: 'text.secondary' }}>
{item.title}
</Typography>
</Stack>
</motion.div>
<Stack sx={{ gap: { xs: 2, md: 3 }, pb: boxPadding }}>
<Stack sx={{ gap: 0.5 }}>
<Typography variant="h4">{item.title2}</Typography>
{item.description && <Typography sx={{ color: 'text.secondary' }}>{item.description}</Typography>}
</Stack>
{item.list && (
<Grid container spacing={{ xs: 0.75, md: 1 }}>
{item.list.map((list, index) => (
<Grid key={index} size={{ xs: 12, md: 6 }}>
<motion.div
key={index}
initial={{ opacity: 0, x: -30 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -30 }}
transition={{ duration: 0.2, ease: 'linear', delay: index * 0.1 }}
>
<Stack
direction="row"
sx={{
gap: 0.5,
alignItems: 'center',
'& svg.tabler-rosette-discount-check': { width: { xs: 16, md: 24 }, height: { xs: 16, md: 24 } }
}}
>
<SvgIcon name="tabler-rosette-discount-check" stroke={1} color="text.secondary" />
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{list.primary}
</Typography>
</Stack>
</motion.div>
</Grid>
))}
</Grid>
)}
</Stack>
</Stack>
{(item.actionBtn || item.actionBtn2) && (
<GraphicsCard sx={{ bgcolor: 'grey.200' }}>
<Stack direction="row" sx={{ alignItems: 'flex-start', gap: 1.5, p: { xs: 2, sm: 3, md: 4 } }}>
{item.actionBtn2 && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="outlined"
color="primary"
startIcon={<SvgIcon name="tabler-help" size={16} stroke={3} />}
{...item.actionBtn2}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
{item.actionBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="contained"
color="primary"
startIcon={<SvgIcon name="tabler-link" size={16} stroke={3} color="background.default" />}
{...item.actionBtn}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
</GraphicsCard>
)}
</GraphicsCard>
</Grid>
</Grid>
</TabPanel>
))}
</TabContext>
</Stack>
</Stack>
</ContainerWrapper>
);
}
Feature18.propTypes = { heading: PropTypes.string, caption: PropTypes.string, topics: PropTypes.array };
@@ -0,0 +1,248 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import ContainerWrapper from '@/components/ContainerWrapper';
import { GraphicsCard } from '@/components/cards';
import GraphicsImage from '@/components/GraphicsImage';
import SvgIcon from '@/components/SvgIcon';
import Typeset from '@/components/Typeset';
import { withAlpha } from '@/utils/colorUtils';
import { SECTION_COMMON_PY } from '@/utils/constant';
// @assets
import Star from '@/images/graphics/Star';
/*************************** FEATURE - 20 ***************************/
/**
*
* Demos:
* - [Feature20](https://www.saasable.io/blocks/feature/feature20)
*
* API
* - [Feature20 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/feature/feature20#props-details)
*/
export default function Feature20({ heading, caption, image, features, actionBtn, secondaryBtn }) {
const theme = useTheme();
const downSM = useMediaQuery(theme.breakpoints.down('sm'));
const downMD = useMediaQuery(theme.breakpoints.down('md'));
const partitionInExtraSmall = 1;
const partitionInSmall = 2;
const partitionInLarge = 3;
const columns = downSM ? partitionInExtraSmall : downMD ? partitionInSmall : partitionInLarge;
const calculateElementsInLastRow = (dataArray, columns) => {
const totalItems = dataArray.length;
const elementsInLastRow = totalItems % columns || columns;
return elementsInLastRow;
};
const calculateIndexOfFirstElementInLastRow = (dataArray, elementsInLastRow) => {
const totalItems = dataArray.length;
const indexOfFirstElementInLastRow = totalItems - elementsInLastRow;
return indexOfFirstElementInLastRow;
};
const elementsInLastRow = calculateElementsInLastRow(features, columns);
const indexOfFirstElementInLastRow = calculateIndexOfFirstElementInLastRow(features, elementsInLastRow);
const calculateIndexOfLastElementOfEachRow = (dataArray, columns) => {
const indices = [];
const totalItems = dataArray.length;
const rows = Math.ceil(totalItems / columns);
for (let i = 1; i <= rows; i++) {
const lastIndexInRow = i * columns - 1;
indices.push(lastIndexInRow < totalItems ? lastIndexInRow : totalItems - 1);
}
return indices;
};
const indicesOfLastElements = calculateIndexOfLastElementOfEachRow(features, columns);
const gc = theme.vars.palette.background.default;
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4, md: 5 } }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.3
}}
>
<Typeset {...{ heading, stackProps: { sx: { maxWidth: { md: 500 }, ...(!image && { maxWidth: 1, textAlign: 'center' }) } } }} />
</motion.div>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
<GraphicsCard sx={{ position: 'relative', overflow: 'visible' }}>
{image && (
<GraphicsCard
sx={{
height: { md: 267 },
width: { md: 456 },
bgcolor: 'transparent',
position: 'absolute',
top: -190,
right: 45,
zIndex: -1,
display: { xs: 'none', md: 'block' }
}}
>
<GraphicsImage sx={{ height: 1, backgroundPositionX: 'right', backgroundPositionY: 'top' }} image={image}>
<Box
sx={{ width: 1, height: 1, background: `linear-gradient(180deg, ${withAlpha(gc, 0)} 0%, ${withAlpha(gc, 0.6)} 100%)` }}
/>
</GraphicsImage>
</GraphicsCard>
)}
<Box sx={{ p: 3 }}>
<Grid container>
{features.map((item, index) => (
<Grid
key={index}
size={{ xs: 12 / partitionInExtraSmall, sm: 12 / partitionInSmall, md: 12 / partitionInLarge }}
sx={{
position: 'relative',
...(index < indexOfFirstElementInLastRow && { borderBottom: `1px solid ${theme.vars.palette.grey[300]}` }),
...(!indicesOfLastElements.includes(index) && { borderRight: `1px solid ${theme.vars.palette.grey[300]}` })
}}
>
<Stack sx={{ gap: { xs: 3, sm: 4 }, height: 1, py: { xs: 1.5, sm: 3, md: 4 }, px: { xs: 0, sm: 3, md: 4 } }}>
<Avatar sx={{ width: 60, height: 60, bgcolor: 'grey.300' }}>
<motion.div
initial={{ opacity: 0, scale: 0.6 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 2, delay: index * 0.1 }}
>
<SvgIcon {...(typeof item.icon === 'string' ? { name: item.icon } : { ...item.icon })} />
</motion.div>
</Avatar>
<Stack sx={{ gap: { xs: 0.5, md: 1 } }}>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.2 }}
>
{item.title && <Typography variant="h4">{item.title}</Typography>}
</motion.div>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: index * 0.3 }}
>
{item.content && <Typography sx={{ color: 'text.secondary' }}>{item.content}</Typography>}
</motion.div>
</Stack>
</Stack>
{index < indexOfFirstElementInLastRow && !indicesOfLastElements.includes(index) && (
<Stack sx={{ position: 'absolute', bottom: -9, right: -9 }}>
<Star />
</Stack>
)}
</Grid>
))}
</Grid>
</Box>
</GraphicsCard>
</motion.div>
<Stack sx={{ alignItems: 'center', gap: 3 }}>
<Typography variant="h6" sx={{ color: 'text.secondary', maxWidth: { xs: '75%', sm: '45%' }, textAlign: 'center' }}>
<motion.div
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
{caption}
</motion.div>
</Typography>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.5
}}
>
<Stack direction="row" sx={{ alignItems: 'center', justifyContent: 'center', gap: 1.5 }}>
{secondaryBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button variant="outlined" {...secondaryBtn} />
</ButtonAnimationWrapper>
</motion.div>
)}
{actionBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="contained"
color="primary"
startIcon={<SvgIcon name="tabler-sparkles" size={16} stroke={3} color="background.default" />}
{...actionBtn}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
</motion.div>
</Stack>
</Stack>
</ContainerWrapper>
);
}
Feature20.propTypes = {
heading: PropTypes.string,
caption: PropTypes.string,
image: PropTypes.any,
features: PropTypes.array,
actionBtn: PropTypes.any,
secondaryBtn: PropTypes.any
};
@@ -0,0 +1,153 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import { GraphicsCard, IconCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import GraphicsImage from '@/components/GraphicsImage';
import Typeset from '@/components/Typeset';
import SvgIcon from '@/components/SvgIcon';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** FEATURE - 21 ***************************/
/**
*
* Demos:
* - [Feature21](https://www.saasable.io/blocks/feature/feature21)
*
* API
* - [Feature21 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/feature/feature21#props-details)
*/
export default function Feature21({ heading, caption, image, features, primaryBtn, secondaryBtn }) {
const imagePadding = { xs: 3, sm: 4, md: 5 };
const iconProps = { color: 'text.primary', stroke: 1 };
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
{(heading || caption) && (
<motion.div
initial={{ opacity: 0, y: 5 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: 0.3 }}
>
<Typeset {...{ heading, caption, stackProps: { sx: { textAlign: 'center' } } }} />
</motion.div>
)}
<Stack sx={{ gap: 1.5 }}>
{image && (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.3,
delay: 0.3
}}
>
<GraphicsCard>
<Box sx={{ pl: imagePadding, pt: imagePadding, height: { xs: 204, sm: 300, md: 360 } }}>
<GraphicsImage
image={image}
sx={{
height: 1,
backgroundPositionX: 'left',
backgroundPositionY: 'top',
borderTopLeftRadius: { xs: 12 },
borderBottomRightRadius: { xs: 20, sm: 32, md: 40 },
border: '5px solid',
borderColor: 'grey.200',
borderBottom: 'none',
borderRight: 'none'
}}
/>
</Box>
</GraphicsCard>
</motion.div>
)}
<Grid container spacing={1.5}>
{features.map((item, index) => (
<Grid key={index} size={{ xs: 12, sm: 6, md: 3 }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: index * 0.2 }}
style={{ height: '100%' }}
>
<IconCard
icon={{ ...(typeof item.icon === 'string' ? { name: item.icon, ...iconProps } : { ...iconProps, ...item.icon }) }}
title={item.title}
stackProps={{ sx: { gap: 2, height: 1 } }}
cardPadding={{ xs: 2, sm: 3 }}
/>
</motion.div>
</Grid>
))}
</Grid>
</Stack>
{(primaryBtn || secondaryBtn) && (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.3, delay: 0.4 }}
>
<Stack direction="row" sx={{ alignItems: 'center', justifyContent: 'center', gap: 1.5 }}>
{secondaryBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button variant="outlined" startIcon={<SvgIcon name="tabler-eye" size={16} stroke={3} />} {...secondaryBtn} />
</ButtonAnimationWrapper>
</motion.div>
)}
{primaryBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
variant="contained"
startIcon={<SvgIcon name="tabler-download" size={16} stroke={3} color="background.default" />}
{...primaryBtn}
/>
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
</motion.div>
)}
</Stack>
</ContainerWrapper>
);
}
Feature21.propTypes = {
heading: PropTypes.string,
caption: PropTypes.string,
image: PropTypes.any,
features: PropTypes.array,
primaryBtn: PropTypes.any,
secondaryBtn: PropTypes.any
};
@@ -0,0 +1,3 @@
export { default as Feature18 } from './Feature18';
export { default as Feature20 } from './Feature20';
export { default as Feature21 } from './Feature21';
@@ -0,0 +1,205 @@
'use client';
// @next
import NextLink from 'next/link';
// @mui
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
// @third-party
import { motion } from 'framer-motion';
// @project
import branding from '@/branding.json';
import { GraphicsCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import { Copyright, FollowUS, Sitemap } from '@/components/footer';
import LogoSection from '@/components/logo';
import SvgIcon from '@/components/SvgIcon';
import { CopyrightType } from '@/enum';
import { DOCS_URL, FREEBIES_URL } from '@/path';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** FOOTER - 7 DATA ***************************/
/**
*
* Demos:
* - [Footer7](https://www.saasable.io/blocks/footer/footer7)
*/
const linkProps = { target: '_blank', rel: 'noopener noreferrer' };
const data = [
{
id: 'resources',
grid: { size: { xs: 12, sm: 'auto' } },
title: 'Resources',
menu: [
{
label: 'Freebies',
link: { href: FREEBIES_URL, ...linkProps }
},
{
label: 'Documentation',
link: { href: DOCS_URL, ...linkProps }
},
{
label: 'Blog',
link: { href: 'https://blog.saasable.io/', ...linkProps }
},
{
label: 'Refund Policy',
link: { href: 'https://mui.com/store/customer-refund-policy/', ...linkProps }
}
]
},
{
id: 'support',
grid: { size: { xs: 6, sm: 'auto' } },
title: 'Support',
menu: [
{
label: 'Pricing',
link: { href: 'https://mui.com/store/items/saasable-multipurpose-ui-kit-and-dashboard/', ...linkProps }
},
{
label: 'FAQs',
link: { href: 'https://phoenixcoded.gitbook.io/saasable/faq', ...linkProps }
},
{
label: 'Support',
link: { href: branding.company.socialLink.support, ...linkProps }
},
{
label: 'License Terms',
link: { href: 'https://mui.com/store/license/', ...linkProps }
},
{
label: 'Discord',
link: { href: branding.company.socialLink.discord, ...linkProps }
}
]
},
{
id: 'company',
grid: { size: { xs: 6, sm: 'auto' } },
title: 'Company',
menu: [
{
label: 'Why Phoenixcoded?',
link: {
href: 'https://blog.saasable.io/a-decade-of-expertise-the-phoenixcoded-story-and-why-you-should-trust-us',
...linkProps
}
},
{
label: 'About',
link: { href: 'https://saasable.io/about', ...linkProps }
},
{
label: 'Contact Us',
link: { href: '/contact', ...linkProps }
}
]
}
];
const iconProps = { color: 'text.secondary' };
const usefullLinks = [
{
icon: <SvgIcon name="tabler-brand-figma" {...iconProps} />,
title: 'Figma Version 1.1.0',
href: 'https://www.figma.com/community/file/1425095061180549847'
},
{
icon: <SvgIcon name="tabler-route" {...iconProps} />,
title: 'React Material UI v7',
href: 'https://mui.com/material-ui/getting-started'
},
{
icon: <SvgIcon name="tabler-sparkles" {...iconProps} />,
title: 'Documentation',
href: DOCS_URL
}
];
/*************************** FOOTER - 7 ***************************/
export default function Footer7() {
const logoFollowContent = (
<Stack sx={{ alignItems: 'flex-start', gap: { xs: 1.5, sm: 3 } }}>
<LogoSection />
<Typography variant="h6" sx={{ maxWidth: { sm: 280 }, mb: { xs: -1, sm: -2.5 } }}>
{process.env.NEXT_PUBLIC_VERSION}
</Typography>
<Typography variant="body2" sx={{ maxWidth: { sm: 280 } }}>
Explore the different versions of our {branding.brandName} template.
</Typography>
</Stack>
);
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
<Stack id="footer-7" role="contentinfo" rel="noopener noreferrer" aria-label="Footer 7" sx={{ gap: { xs: 3, sm: 4, md: 5 } }}>
<Grid container spacing={{ xs: 4, md: 3 }}>
<Grid size={{ xs: 12, md: 6 }}>
<Stack direction={{ sm: 'row', md: 'column' }} sx={{ gap: 3, justifyContent: 'space-between', height: 1 }}>
{logoFollowContent}
<Stack sx={{ gap: { xs: 2, sm: 2.5, md: 3 } }}>
{usefullLinks.map((item, index) => (
<Stack direction="row" sx={{ gap: 1, alignItems: 'center' }} key={index}>
{item.icon}
<Link
component={NextLink}
variant="body2"
color="text.secondary"
href={item.href}
target="_blank"
rel="noopener noreferrer"
aria-label="Usefull Links"
>
{item.title}
</Link>
</Stack>
))}
</Stack>
</Stack>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Sitemap list={data} isMenuDesign />
</Grid>
</Grid>
<GraphicsCard sx={{ borderRadius: { xs: 6, sm: 8 } }}>
<Stack
direction={{ sm: 'row' }}
sx={{
alignItems: 'center',
justifyContent: { xs: 'center', sm: 'space-between' },
gap: 1.5,
py: { xs: 2, sm: 1.5 },
px: { xs: 2, sm: 3 }
}}
>
<Copyright type={CopyrightType.TYPE3} />
<FollowUS heading={false} color="grey.100" />
</Stack>
</GraphicsCard>
</Stack>
</motion.div>
</ContainerWrapper>
);
}
@@ -0,0 +1 @@
export { default as Footer7 } from './Footer7';
@@ -0,0 +1,250 @@
'use client';
import PropTypes from 'prop-types';
import { useEffect, useRef, useState } from 'react';
// @mui
import { useTheme } from '@mui/material/styles';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion, useScroll, useTransform } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import { GraphicsCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import GraphicsImage from '@/components/GraphicsImage';
import SvgIcon from '@/components/SvgIcon';
import { SECTION_COMMON_PY } from '@/utils/constant';
import { getBackgroundDots } from '@/utils/getBackgroundDots';
import { withAlpha } from '@/utils/colorUtils';
// @assets
import Wave from '@/images/graphics/Wave';
// threshold - adjust threshold as needed
const options = { root: null, rootMargin: '0px', threshold: 0.6 };
/*************************** HERO - 17 ***************************/
/**
*
* Demos:
* - [Hero17](https://www.saasable.io/blocks/hero/hero17)
*
* API:
* - [Hero17 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/hero/hero17#props-details)
*/
export default function Hero17({ chip, headLine, captionLine, primaryBtn, videoSrc, videoThumbnail, listData }) {
const theme = useTheme();
const boxRadius = { xs: 24, sm: 32, md: 40 };
const containerRef = useRef(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ['start start', 'end start']
});
const scale = useTransform(scrollYProgress, [0, 0.1, 0.2, 0.4, 0.6], [0.9, 0.92, 0.94, 0.96, 1]);
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
// Handle video play/pause based on intersection with the viewport
useEffect(() => {
const handleIntersection = (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
if (videoRef.current && !isPlaying) {
videoRef.current
.play()
.then(() => {
setIsPlaying(true);
})
.catch((error) => {
console.error('Autoplay was prevented:', error);
});
}
} else {
if (videoRef.current && isPlaying) {
videoRef.current.pause();
setIsPlaying(false);
}
}
});
};
const observer = new IntersectionObserver(handleIntersection, options);
const videoElement = videoRef.current;
if (videoElement) {
observer.observe(videoElement);
}
return () => {
if (videoElement) {
observer.unobserve(videoElement);
}
};
}, [isPlaying]);
return (
<>
<Box
sx={{
height: { xs: 592, sm: 738, md: 878 },
position: 'absolute',
top: 0,
left: 0,
width: 1,
zIndex: -1,
borderBottomLeftRadius: boxRadius,
borderBottomRightRadius: boxRadius,
...getBackgroundDots(theme.vars.palette.grey[300], 2, 35),
bgcolor: 'grey.100'
}}
/>
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Box ref={containerRef}>
<Box sx={{ pb: { xs: 3, sm: 4, md: 5 } }}>
<Stack sx={{ alignItems: 'center', gap: 1.5 }}>
<motion.div
initial={{ opacity: 0, scale: 0.6 }}
whileInView={{ opacity: 1, scale: [0.6, 1.15, 0.95, 1] }}
animate={{
boxShadow: [
`0 0 0px ${withAlpha(theme.vars.palette.primary.dark, 0)}`,
`0 0 20px ${withAlpha(theme.vars.palette.primary.main, 0.8)}`,
`0 0 0px ${withAlpha(theme.vars.palette.primary.dark, 0)}`
],
borderRadius: '74px'
}}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.8, ease: 'linear' }}
>
<Chip
variant="outlined"
label={chip.label}
slotProps={{
label: {
sx: { py: 0.5, px: 1.5, ...(typeof chip.label === 'string' && { typography: 'caption', color: 'text.secondary' }) }
}
}}
sx={{ bgcolor: 'grey.100' }}
/>
</motion.div>
<motion.div
initial={{ opacity: 0, scale: 0.6 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2, ease: 'linear' }}
>
<Typography variant="h1" align="center" sx={{ maxWidth: 800 }}>
{headLine}
</Typography>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1, delay: 0.2, ease: [0.215, 0.61, 0.355, 1] }}
>
<Box sx={{ pt: 0.5, pb: 0.75 }}>
<Wave />
</Box>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 1, delay: 0.3, ease: [0.215, 0.61, 0.355, 1] }}
>
<Typography variant="h6" align="center" sx={{ color: 'text.secondary', maxWidth: 650 }}>
{captionLine}
</Typography>
</motion.div>
</Stack>
<Stack sx={{ alignItems: 'center', gap: 2, mt: { xs: 3, sm: 4, md: 5 } }}>
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button
color="primary"
variant="contained"
startIcon={<SvgIcon name="tabler-sparkles" size={16} stroke={3} color="background.default" />}
{...primaryBtn}
/>
</ButtonAnimationWrapper>
</motion.div>
<Stack direction="row" sx={{ gap: 1, flexWrap: 'wrap', justifyContent: 'center' }}>
{listData.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.6 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.9, delay: index * 0.08, ease: 'linear' }}
>
<Chip
label={item.title}
variant="outlined"
icon={<GraphicsImage image={item.image} sx={{ width: 16, height: 16 }} />}
slotProps={{ label: { sx: { py: 0.75, px: 1, typography: 'caption2' } } }}
sx={{ height: 32, px: 1, bgcolor: 'grey.100' }}
/>
</motion.div>
))}
</Stack>
</Stack>
</Box>
<motion.div
initial={{ opacity: 0, scale: 0.6 }}
whileInView={{ opacity: 1, scale: 0.9 }}
viewport={{ once: true }}
transition={{ duration: 0.9, delay: 0.3 }}
style={{ scale }}
>
<GraphicsCard sx={{ border: '5px solid', borderColor: 'grey.300' }}>
<video
playsInline
ref={videoRef}
width="100%"
height="100%"
style={{ display: 'flex', objectFit: 'cover' }}
preload="metadata"
autoPlay={false}
loop={true}
muted={true}
poster={videoThumbnail}
>
<source src={videoSrc} type="video/mp4" />
</video>
</GraphicsCard>
</motion.div>
</Box>
</ContainerWrapper>
</>
);
}
Hero17.propTypes = {
chip: PropTypes.object,
headLine: PropTypes.string,
captionLine: PropTypes.string,
primaryBtn: PropTypes.any,
videoSrc: PropTypes.string,
videoThumbnail: PropTypes.string,
listData: PropTypes.array
};
@@ -0,0 +1 @@
export { default as Hero17 } from './Hero17';
@@ -0,0 +1,119 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import { GraphicsCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import SvgIcon from '@/components/SvgIcon';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** INTEGRATION - TAG ***************************/
function IntegrationTag({ label, icon }) {
const iconSize = { xs: 32, md: 40 };
return (
<Chip
label={label}
{...(icon && { icon: <SvgIcon {...(typeof icon === 'string' ? { name: icon } : { ...icon })} stroke={1} color="inherit" /> })}
slotProps={{ label: { sx: { p: 0, ...(icon && { pl: 1.25 }), typography: 'h5' } } }}
sx={{
px: { xs: 2.25, md: 3 },
py: icon ? { xs: 1.25, md: 1.75 } : { xs: 1.875, md: 2.5 },
m: 0.5,
bgcolor: 'background.default',
'& svg': { width: iconSize, height: iconSize }
}}
/>
);
}
/*************************** INTEGRATION - 2 ***************************/
/**
*
* Demos:
* - [Integration2](https://www.saasable.io/blocks/integration/integration2)
*
* API:
* - [Integration2 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/integration/integration2#props-details)
*/
export default function Integration2({ headLine, captionLine, primaryBtn, tagList }) {
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.4
}}
>
<GraphicsCard>
<Stack sx={{ alignItems: 'center', p: { xs: 3, sm: 4, md: 5 } }}>
<Stack sx={{ alignItems: 'center', gap: { xs: 1, sm: 1.5 } }}>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4, ease: [0.215, 0.61, 0.355, 1] }}
>
{headLine && (
<Typography align="center" variant="h2">
{headLine}
</Typography>
)}
{captionLine && (
<Typography align="center" variant="h6" sx={{ color: 'text.secondary' }}>
{captionLine}
</Typography>
)}
</motion.div>
</Stack>
<Stack direction="row" sx={{ py: { xs: 4, sm: 5, md: 6 }, justifyContent: 'center', alignItems: 'center', flexWrap: 'wrap' }}>
{tagList.map((integration, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.7 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: 'easeOut', delay: Math.random() * 0.6 }}
>
<IntegrationTag {...integration} />
</motion.div>
))}
</Stack>
{primaryBtn && (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button variant="contained" size="large" {...primaryBtn} />
</ButtonAnimationWrapper>
</motion.div>
)}
</Stack>
</GraphicsCard>
</motion.div>
</ContainerWrapper>
);
}
IntegrationTag.propTypes = { label: PropTypes.any, icon: PropTypes.any };
Integration2.propTypes = { headLine: PropTypes.string, captionLine: PropTypes.string, primaryBtn: PropTypes.any, tagList: PropTypes.array };
@@ -0,0 +1 @@
export { default as Integration2 } from './Integration2';
@@ -0,0 +1,55 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
// @project
import { GraphicsCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import { SECTION_COMMON_PY } from '@/utils/constant';
// @assets
import Error404 from '@/images/maintenance/Error404';
/*************************** ERROR 404 - PAGES ***************************/
/**
*
* Demos:
* - [Error404](https://www.saasable.io/blocks/error404)
*
* API
* - [Error404 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/maintenance/error404#props-details)
*/
export default function Error404Page({ primaryBtn, heading }) {
return (
<ContainerWrapper>
<Stack
sx={{
alignItems: 'center',
justifyContent: 'center',
width: 1,
height: '100vh',
py: SECTION_COMMON_PY,
minHeight: { xs: 450, sm: 600, md: 800 }
}}
>
<GraphicsCard sx={{ width: 1, height: 1, py: { xs: 3, sm: 4, md: 6 } }}>
<Stack sx={{ justifyContent: 'center', height: 1, gap: { xs: 4, sm: 1 } }}>
<Error404 />
<Stack sx={{ gap: 2.25, alignItems: 'center', mt: { sm: -5, lg: -6.25 } }}>
<Typography sx={{ width: { xs: 210, sm: 300 }, textAlign: 'center' }}>{heading}</Typography>
{primaryBtn && <Button variant="contained" size="medium" {...primaryBtn} />}
</Stack>
</Stack>
</GraphicsCard>
</Stack>
</ContainerWrapper>
);
}
Error404Page.propTypes = { primaryBtn: PropTypes.any, heading: PropTypes.string };
@@ -0,0 +1,48 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @project
import { GraphicsCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import { SECTION_COMMON_PY } from '@/utils/constant';
// @assets
import Error500 from '@/images/maintenance/Error500';
import Error500Server from '@/images/maintenance/Error500Server';
/*************************** ERROR 500 - PAGES ***************************/
/**
*
* Demos:
* - [Error500](https://www.saasable.io/blocks/error500)
*/
export default function Error500Page({ primaryBtn, heading }) {
return (
<ContainerWrapper>
<Stack sx={{ width: 1, height: '100vh', py: SECTION_COMMON_PY, minHeight: { xs: 450, sm: 600, md: 800 } }}>
<GraphicsCard sx={{ width: 1, height: 1, position: 'relative' }}>
<Stack sx={{ alignItems: 'center', justifyContent: 'center', gap: 2.25, height: '70%' }}>
<Box sx={{ width: 1, maxWidth: { xs: 340, sm: 486, md: 728 }, p: 2 }}>
<Error500 />
</Box>
<Typography sx={{ textAlign: 'center', width: { xs: 248, sm: 340, md: 448 } }}>{heading}</Typography>
{primaryBtn && <Button variant="contained" size="medium" {...primaryBtn} sx={{ zIndex: 1 }} />}
</Stack>
<Box sx={{ width: { xs: '95%', md: '90%' }, position: 'absolute', left: -2, bottom: -6 }}>
<Error500Server />
</Box>
</GraphicsCard>
</Stack>
</ContainerWrapper>
);
}
Error500Page.propTypes = { primaryBtn: PropTypes.any, heading: PropTypes.string };
@@ -0,0 +1,2 @@
export { default as Error404Page } from './Error404';
export { default as Error500Page } from './Error500';
@@ -0,0 +1,51 @@
'use client';
import PropTypes from 'prop-types';
// @next
import NextLink from 'next/link';
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import PreviewCard from '@/components/cards/PreviewCard';
/*************************** MEGA MENU - 4 ***************************/
/**
*
* Demos:
* - [MegaMenu4](https://www.saasable.io/blocks/megamenu/megamenu4)
*
* API
* - [MegaMenu4 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/megamenu/megamenu4#props-details)
*/
export default function MegaMenu4({ menuItems, footerData, popperWidth = 936, footerSX }) {
return (
<Box sx={{ maxWidth: { xs: 1, md: popperWidth } }}>
<Grid container spacing={1} sx={{ p: 1, bgcolor: 'background.paper' }}>
{menuItems.map((item, index) => (
<Grid key={index} size={{ xs: 6, sm: 4, md: 3 }}>
<motion.div
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: index * 0.2 }}
>
<Link {...(item.link && { component: NextLink, ...item.link, sx: { ...item.link?.sx } })} aria-label={item.title}>
<PreviewCard {...item} />
</Link>
</motion.div>
</Grid>
))}
</Grid>
{footerData && <Box sx={{ p: 2.5, bgcolor: 'grey.100', ...footerSX }}>{footerData}</Box>}
</Box>
);
}
MegaMenu4.propTypes = { menuItems: PropTypes.array, footerData: PropTypes.node, popperWidth: PropTypes.number, footerSX: PropTypes.any };
@@ -0,0 +1,123 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import { useTheme } from '@mui/material/styles';
import Chip from '@mui/material/Chip';
import Grid from '@mui/material/Grid';
import List from '@mui/material/List';
import ListSubheader from '@mui/material/ListSubheader';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import SvgIcon from '@/components/SvgIcon';
import { withAlpha } from '@/utils/colorUtils';
/*************************** MEGA MENU - 5 ***************************/
/**
*
* Demos:
* - [MegaMenu5](https://www.saasable.io/blocks/megamenu/megamenu5)
*
* API
* - [MegaMenu5 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/megamenu/megamenu5#props-details)
*/
export default function MegaMenu5({ menuItems, bannerData, popperWidth = 750 }) {
const theme = useTheme();
let gridItem = menuItems.length === 1 ? 12 : 6;
return (
<Grid container>
{bannerData && (
<Grid sx={{ py: 1, pl: { xs: 2, md: 1 }, pr: { xs: 2, sm: 1 } }} size={{ xs: 12, sm: 4.5 }}>
<Box sx={{ p: 2.5, bgcolor: 'grey.100', height: 1, borderRadius: 2 }}>{bannerData}</Box>
</Grid>
)}
<Grid size={{ xs: 12, sm: bannerData ? 7.5 : 12 }} sx={{ px: 1 }}>
<Grid container spacing={1}>
{menuItems.map((items, index) => (
<Grid key={index} size={{ xs: 12, sm: menuItems.length > 2 ? 4 : gridItem }}>
<List
component="nav"
sx={{ p: 1, width: '100%', maxWidth: { xs: 1, md: popperWidth }, display: 'flex', flexDirection: 'column' }}
{...(items.title && {
subheader: (
<ListSubheader component="div" sx={{ typography: 'subtitle1', p: 1, color: 'text.primary', bgcolor: 'transparent' }}>
{items.title}
</ListSubheader>
)
})}
>
{items?.itemsList &&
items?.itemsList.map((item, index) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -10 }}
whileInView={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: index * 0.3 }}
>
{/* @ts-ignore */}
<ListItemButton
key={index}
{...(item && item?.link && item?.link !== undefined && { ...item.link })}
sx={{
py: 0.5,
px: 1.25,
my: 0.25,
borderRadius: 2,
'&:hover': { bgcolor: 'grey.50' },
'&:focus-visible': { bgcolor: 'grey.200' }
}}
TouchRippleProps={{
style: {
color: withAlpha(theme.vars.palette.primary.main, 0.3)
}
}}
>
<ListItemText
primary={item.title}
secondary={item.content}
slotProps={{
primary: {
variant: 'body1',
sx: { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', mr: 0.5, color: 'text.primary' }
}
}}
/>
{item && item?.link && item?.link !== undefined && item?.link?.target === '_blank' ? (
<SvgIcon name="tabler-arrow-up-right" size={16} stroke={2} color={theme.vars.palette.grey[800]} />
) : (
<Chip
label={
<Typography variant="caption" sx={{ color: 'primary.main' }}>
{item.status}
</Typography>
}
size="small"
sx={{
bgcolor: 'primary.lighter',
'& .MuiChip-label': { px: 1, py: 0.25, minWidth: 20 }
}}
/>
)}
</ListItemButton>
</motion.div>
))}
</List>
</Grid>
))}
</Grid>
</Grid>
</Grid>
);
}
MegaMenu5.propTypes = { menuItems: PropTypes.array, bannerData: PropTypes.node, popperWidth: PropTypes.number };
@@ -0,0 +1,2 @@
export { default as MegaMenu4 } from './MegaMenu4';
export { default as MegaMenu5 } from './MegaMenu5';
@@ -0,0 +1,99 @@
'use client';
import PropTypes from 'prop-types';
import { useEffect } from 'react';
// @mui
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
// @third-party
import { animate, motion, useMotionValue, useTransform } from 'motion/react';
// @project
import { GraphicsCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import Typeset from '@/components/Typeset';
import { SECTION_COMMON_PY } from '@/utils/constant';
function AnimatedCounter({ startCount, endCount }) {
const countValue = useMotionValue(startCount);
const rounded = useTransform(countValue, (value) => Math.round(value));
useEffect(() => {
const controls = animate(countValue, endCount, { duration: 5, ease: 'linear' });
return () => controls.stop();
}, [countValue, endCount]);
return <motion.pre style={{ margin: 0 }}>{rounded}</motion.pre>;
}
/*************************** METRICS - 5 ***************************/
/**
*
* Demos:
* - [Metrics5](https://www.saasable.io/blocks/metrics/metrics5)
*
* API:
* - [Metrics5 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/metrics/metrics5#props-details)
*/
export default function Metrics5({ heading, caption, blockDetail }) {
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.3 }}
>
<Typeset
{...{
heading,
caption,
stackProps: { sx: { alignItems: 'center', textAlign: 'center' } },
captionProps: { sx: { width: { xs: 1, sm: '80%', md: '65%' } } }
}}
/>
</motion.div>
<Grid container spacing={1.5}>
{blockDetail.map((item, index) => (
<Grid key={index} size={{ xs: 6, md: 3 }}>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.9, delay: index * 0.4, ease: [0.215, 0.61, 0.355, 1] }}
style={{ height: '100%' }}
>
<GraphicsCard sx={{ p: { xs: 2, sm: 2.25, md: 3 }, height: 1 }}>
<Stack sx={{ gap: 0.5, alignItems: 'center' }}>
<Stack direction="row" sx={{ alignItems: 'flex-end' }}>
<Typography component="div" variant="h1">
<AnimatedCounter startCount={0} endCount={item.counter} />
</Typography>
<Typography component="div" variant="h3" sx={{ color: 'text.secondary', mb: { xs: 0.25, md: 0.625 } }}>
{item.defaultUnit}
</Typography>
</Stack>
<Typography align="center" sx={{ color: 'text.secondary' }}>
{item.caption}
</Typography>
</Stack>
</GraphicsCard>
</motion.div>
</Grid>
))}
</Grid>
</Stack>
</ContainerWrapper>
);
}
AnimatedCounter.propTypes = { startCount: PropTypes.number, endCount: PropTypes.number };
Metrics5.propTypes = { heading: PropTypes.any, caption: PropTypes.any, blockDetail: PropTypes.any };
@@ -0,0 +1 @@
export { default as Metrics5 } from './Metrics5';
@@ -0,0 +1,39 @@
import PropTypes from 'prop-types';
import { cloneElement } from 'react';
// @mui
import { useTheme } from '@mui/material/styles';
import { useScrollTrigger } from '@mui/material';
// @project
import { withAlpha } from '@/utils/colorUtils';
/*************************** NAVBAR - ELEVATION SCROLL ***************************/
export default function ElevationScroll({ children, window, isFixed, triggerSX }) {
const theme = useTheme();
const trigger = useScrollTrigger({
disableHysteresis: true,
threshold: 0,
target: window ? window() : undefined
});
if (!isFixed) {
return children;
}
const triggerStyles = {
boxShadow: `${withAlpha(theme.vars.palette.text.primary, 0.08)} 0px 12px 16px -4px, ${withAlpha(theme.vars.palette.text.primary, 0.03)} 0px 4px 6px -2px;`,
bgcolor: 'background.paper',
...triggerSX
};
return children
? cloneElement(children, {
sx: { boxShadow: 'none', bgcolor: 'transparent', backgroundImage: 'none', ...(trigger && { ...triggerStyles }) }
})
: null;
}
ElevationScroll.propTypes = { children: PropTypes.node, window: PropTypes.func, isFixed: PropTypes.bool, triggerSX: PropTypes.any };
@@ -0,0 +1,60 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import { styled } from '@mui/material/styles';
import AppBar from '@mui/material/AppBar';
import Toolbar from '@mui/material/Toolbar';
// @project
import ElevationScroll from './ElevationScroll';
import ContainerWrapper from '@/components/ContainerWrapper';
export const navbar10Height = { xs: 64, sm: 72, md: 84 };
// override media queries injected by theme.mixins.toolbar
const StyledToolbar = styled(Toolbar)(({ theme }) => ({
'@media all': {
minHeight: navbar10Height.md,
paddingLeft: 0,
paddingRight: 0
},
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
[theme.breakpoints.down('md')]: {
'@media all': { minHeight: navbar10Height.sm }
},
[theme.breakpoints.down('sm')]: {
'@media all': { minHeight: navbar10Height.xs },
paddingTop: theme.spacing(1.5),
paddingBottom: theme.spacing(1.5)
}
}));
/*************************** NAVBAR - 10 ***************************/
/**
*
* Demos:
* - [Navbar10](https://www.saasable.io/blocks/navbar/navbar10)
*
* API:
* - [Navbar10 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/navbar/navbar10#props-details)
*/
export default function Navbar10({ children, isFixed = true, ...props }) {
return (
<>
<ElevationScroll isFixed={isFixed} {...props}>
<AppBar {...(!isFixed && { position: 'static', elevation: 0 })} component="nav" color="inherit" sx={{ background: 'transparent' }}>
<StyledToolbar>
<ContainerWrapper>{children}</ContainerWrapper>
</StyledToolbar>
</AppBar>
</ElevationScroll>
{isFixed && <StyledToolbar />}
</>
);
}
Navbar10.propTypes = { children: PropTypes.any, isFixed: PropTypes.bool, props: PropTypes.any };
@@ -0,0 +1 @@
export { default as Navbar10 } from './Navbar10';
@@ -0,0 +1,119 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import { navbar10Height } from '../Navbar10';
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import ContainerWrapper from '@/components/ContainerWrapper';
import Logo from '@/components/logo';
import { MenuPopper, NavMenu, NavMenuDrawer, NavPrimaryButton, NavSecondaryButton } from '@/components/navbar';
import SvgIcon from '@/components/SvgIcon';
import { withAlpha } from '@/utils/colorUtils';
/*************************** NAVBAR - CONTENT 10 ***************************/
/**
*
* Demos:
* - [NavbarContent10](https://www.saasable.io/blocks/navbar/navbar10)
*
* API:
* - [NavbarContent10 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/navbar/navbar-content/navbarcontent10#props-details)
*/
export default function NavbarContent10({ landingBaseUrl, navItems, primaryBtn, secondaryBtn, animated }) {
const theme = useTheme();
const downMD = useMediaQuery(theme.breakpoints.down('md'));
const downSM = useMediaQuery(theme.breakpoints.down('sm'));
return (
<Stack direction="row" sx={{ alignItems: 'center', justifyContent: 'space-between', width: 1 }}>
<Logo to={landingBaseUrl} />
{!downMD && navItems && (
<Box sx={{ bgcolor: 'grey.200', borderRadius: 10 }}>
<NavMenu {...{ navItems }} />
</Box>
)}
<Stack direction="row" sx={{ gap: { xs: 1, md: 1.5 } }}>
{!downSM && (
<>
<NavSecondaryButton {...secondaryBtn} />
<ButtonAnimationWrapper>
{animated ? (
<motion.div
initial={{ borderRadius: '50px' }}
animate={{
boxShadow: [
`0px 0px 0px 0px ${withAlpha(theme.vars.palette.primary.main, 0.7)}`,
`0px 0px 0px 8px ${withAlpha(theme.vars.palette.primary.main, 0)}`,
`0px 0px 0px 0px ${withAlpha(theme.vars.palette.primary.main, 0)}`
],
borderRadius: '50px'
}}
transition={{ duration: 1.2, repeat: Infinity, ease: 'linear' }}
>
<NavPrimaryButton {...primaryBtn} />
</motion.div>
) : (
<NavPrimaryButton {...primaryBtn} />
)}
</ButtonAnimationWrapper>
</>
)}
{downMD && (
<Box sx={{ flexGrow: 1 }}>
<MenuPopper
offset={downSM ? 12 : 16}
toggleProps={{
children: <SvgIcon name="tabler-menu-2" color="text.primary" />,
color: 'inherit',
sx: { minWidth: 40, width: 40, height: 40, p: 0 }
}}
>
<ContainerWrapper
sx={{
height: 'auto',
maxHeight: { xs: `calc(100vh - ${navbar10Height.xs}px)`, sm: `calc(100vh - ${navbar10Height.sm}px)` },
overflowY: 'auto'
}}
>
{navItems && (
<Box sx={{ mx: -2 }}>
<NavMenuDrawer {...{ navItems }} />
</Box>
)}
{downSM && (
<Stack direction="row" sx={{ justifyContent: 'space-between', gap: 1, px: 2, py: 2.5, mx: -2, bgcolor: 'grey.100' }}>
<NavSecondaryButton {...secondaryBtn} />
<ButtonAnimationWrapper>
<NavPrimaryButton {...primaryBtn} />
</ButtonAnimationWrapper>
</Stack>
)}
</ContainerWrapper>
</MenuPopper>
</Box>
)}
</Stack>
</Stack>
);
}
NavbarContent10.propTypes = {
landingBaseUrl: PropTypes.any,
navItems: PropTypes.any,
primaryBtn: PropTypes.any,
secondaryBtn: PropTypes.any,
selectedTheme: PropTypes.any,
animated: PropTypes.any
};
@@ -0,0 +1 @@
export { default as NavbarContent10 } from './NavbarContent10';
@@ -0,0 +1,181 @@
'use client';
import PropTypes from 'prop-types';
// @next
import NextLink from 'next/link';
// @mui
import { useTheme } from '@mui/material/styles';
import Button from '@mui/material/Button';
import CardMedia from '@mui/material/CardMedia';
import Link from '@mui/material/Link';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import Stack from '@mui/material/Stack';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import ContainerWrapper from '@/components/ContainerWrapper';
import Typeset from '@/components/Typeset';
import { GraphicsCard } from '@/components/cards';
import useFocusWithin from '@/hooks/useFocusWithin';
import { withAlpha } from '@/utils/colorUtils';
import { generateFocusVisibleStyles } from '@/utils/CommonFocusStyle';
import { SECTION_COMMON_PY } from '@/utils/constant';
import GetImagePath from '@/utils/GetImagePath';
// @assets
import Background from '@/images/graphics/Background';
import Wave from '@/images/graphics/Wave';
/*************************** OTHER - 1 ***************************/
/**
*
* Demos:
* - [Other1](https://www.saasable.io/blocks/other/other1)
*
* API
* - [Other1 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/other/other1#props-details)
*/
export default function Other1({ heading, description, primaryBtn, sections }) {
const theme = useTheme();
const isFocusWithin = useFocusWithin();
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.3
}}
>
<Typeset {...{ heading, stackProps: { sx: { textAlign: 'center' } } }} />
</motion.div>
<Grid container spacing={1.5}>
{sections.map((item, index) => (
<Grid key={index} size={{ xs: 6, sm: 4, md: 4 }}>
<GraphicsCard sx={{ overflow: 'hidden' }}>
<motion.div
whileHover={{ scale: 1.02 }}
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: item.animationDelay
}}
>
<GraphicsCard
sx={{
height: { xs: 240, sm: 324, md: 380 },
position: 'relative',
overflow: 'hidden',
...(isFocusWithin && { '&:focus-within': generateFocusVisibleStyles(theme.vars.palette.primary.main) })
}}
>
<Link
href={item.link}
component={NextLink}
aria-label={item.title}
sx={{ position: 'absolute', top: 0, height: 1, width: 1, borderRadius: { xs: 6, sm: 8, md: 10 }, zIndex: 1 }}
/>
<Background />
<Box sx={{ position: 'absolute', top: 0, width: 1, height: 1, textAlign: 'center' }}>
<CardMedia
component="img"
image={GetImagePath(item.image)}
sx={{
px: '14.5%',
pt: '16%',
pb: { xs: 2, md: 1 },
objectFit: 'contain'
}}
alt="other sections"
loading="lazy"
/>
<Box sx={{ '& div': { alignItems: 'center', pt: 0.875 } }}>
<Wave />
</Box>
</Box>
<Stack
sx={{
height: 177,
bottom: 0,
width: 1,
position: 'absolute',
justifyContent: 'end',
textAlign: 'center',
gap: { xs: 0.25, md: 0.5, sm: 1 },
p: 3,
background: `linear-gradient(180deg, ${withAlpha(theme.vars.palette.grey[100], 0)} 0%, ${theme.vars.palette.grey[100]} 100%)`
}}
>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<Typography variant="h4" sx={{ color: 'primary.main' }}>
{item.title}
</Typography>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 25 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.6 }}
>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{item.subTitle}
</Typography>
</motion.div>
</Stack>
</GraphicsCard>
</motion.div>
</GraphicsCard>
</Grid>
))}
</Grid>
<Stack sx={{ gap: 2, alignItems: 'center' }}>
<Typography variant="h6" align="center" sx={{ color: 'text.secondary', width: { xs: 1, sm: '80%', md: '65%' } }}>
<motion.div
initial={{ opacity: 0, y: 15 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{
duration: 0.5,
delay: 0.3
}}
>
{description}
</motion.div>
</Typography>
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
whileHover={{ scale: 1.06 }}
>
<ButtonAnimationWrapper>
<Button variant="outlined" {...primaryBtn} />
</ButtonAnimationWrapper>
</motion.div>
</Stack>
</Stack>
</ContainerWrapper>
);
}
Other1.propTypes = { heading: PropTypes.string, description: PropTypes.string, primaryBtn: PropTypes.any, sections: PropTypes.array };
@@ -0,0 +1,153 @@
'use client';
// @mui
import { useTheme } from '@mui/material/styles';
import Grid from '@mui/material/Grid';
import Skeleton from '@mui/material/Skeleton';
import Stack from '@mui/material/Stack';
// @project
import ContainerWrapper from '@/components/ContainerWrapper';
import { SECTION_COMMON_PY } from '@/utils/constant';
import { GraphicsCard } from '@/components/cards';
import { withAlpha } from '@/utils/colorUtils';
/*************************** OTHER - 2 ***************************/
/**
*
* Demos:
* - [Other2](https://www.saasable.io/blocks/other/other2)
*
* API
* - [Other2 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/other/other2#props-details)
*/
export default function Other2() {
const theme = useTheme();
const lightColor = theme.vars.palette.secondary.light;
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4, md: 5 } }}>
<Stack sx={{ alignItems: 'center', gap: { xs: 2.5, sm: 3 } }}>
<Skeleton
variant="rounded"
sx={{ borderRadius: 5, bgcolor: `${withAlpha(theme.vars.palette.secondary.lighter, 0.4)}` }}
width="30%"
height={32}
animation={false}
/>
<Stack sx={{ alignItems: 'center', gap: 1.5, width: 1, height: 1 }}>
<Skeleton
variant="rounded"
sx={{ borderRadius: 2.5, bgcolor: `${withAlpha(lightColor, 0.4)}` }}
width="70%"
height={57}
animation={false}
/>
<Skeleton
variant="rounded"
sx={{ borderRadius: 2.5, bgcolor: `${withAlpha(lightColor, 0.4)}` }}
width="60%"
height={57}
animation={false}
/>
</Stack>
<Stack sx={{ alignItems: 'center', gap: 1, width: 1, height: 1 }}>
<Skeleton
variant="rounded"
sx={{ borderRadius: 1.5, bgcolor: `${withAlpha(lightColor, 0.3)}` }}
width="36%"
height={24}
animation={false}
/>
<Skeleton
variant="rounded"
sx={{ borderRadius: 1.5, bgcolor: `${withAlpha(lightColor, 0.3)}` }}
width="30%"
height={24}
animation={false}
/>
</Stack>
<Stack direction="row" sx={{ padding: 1.25, gap: 1.5, width: 1, justifyContent: 'center' }}>
<Skeleton
variant="rounded"
sx={{ borderRadius: 5, bgcolor: `${withAlpha(lightColor, 0.3)}` }}
width={183}
height={52}
animation={false}
/>
<Skeleton
variant="rounded"
sx={{ borderRadius: 5, bgcolor: `${withAlpha(lightColor, 0.6)}` }}
width={183}
height={52}
animation={false}
/>
</Stack>
</Stack>
<Stack>
<GraphicsCard sx={{ py: { xs: 5, sm: 7.5 }, px: { xs: 3, sm: 6.25 }, position: 'relative' }}>
<Grid container sx={{ mb: -10 }}>
<Grid size={8}>
<Skeleton
variant="rounded"
sx={{ borderRadius: 5, height: { xs: 280, sm: 380 }, bgcolor: `${withAlpha(lightColor, 0.4)}` }}
animation={false}
/>
</Grid>
<Grid size={8}>
<Skeleton
variant="rounded"
sx={{
fontSize: '1rem',
borderRadius: 5,
bgcolor: `${withAlpha(theme.vars.palette.background.default, 0.4)}`,
position: 'absolute',
top: '40%',
left: '35%'
}}
width="61%"
height="80%"
animation={false}
/>
</Grid>
</Grid>
</GraphicsCard>
</Stack>
<Stack>
<Grid container spacing={7.5}>
{[0, 1, 2, 3].map((item) => (
<Grid key={item} size={{ xs: 6, sm: 6, md: 3 }}>
<Stack sx={{ gap: 1.5 }}>
<Skeleton
variant="rounded"
sx={{ borderRadius: 3, bgcolor: `${withAlpha(lightColor, 0.4)}` }}
width="80%"
height={36}
animation={false}
/>
<Skeleton
variant="rounded"
sx={{ borderRadius: 1.5, bgcolor: `${withAlpha(lightColor, 0.25)}` }}
width="100%"
height={16}
animation={false}
/>
<Skeleton
variant="rounded"
sx={{ borderRadius: 1.5, bgcolor: `${withAlpha(lightColor, 0.25)}` }}
width="32%"
height={16}
animation={false}
/>
</Stack>
</Grid>
))}
</Grid>
</Stack>
</Stack>
</ContainerWrapper>
);
}
@@ -0,0 +1,2 @@
export { default as Other1 } from './Other1';
export { default as Other2 } from './Other2';
@@ -0,0 +1,181 @@
'use client';
import PropTypes from 'prop-types';
// @next
import NextLink from 'next/link';
// @mui
import { useTheme } from '@mui/material/styles';
import Avatar from '@mui/material/Avatar';
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @third-party
import { motion } from 'motion/react';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import { GraphicsCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import SvgIcon from '@/components/SvgIcon';
import Typeset from '@/components/Typeset';
import { IconType } from '@/enum';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** PRICING - 9 ***************************/
/**
*
* Demos:
* - [Pricing9](https://www.saasable.io/blocks/pricing/pricing9)
*
* API
* - [Pricing9 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/pricing/pricing9#props-details)
*/
export default function Pricing9({ heading, caption, features, plans }) {
const theme = useTheme();
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 6 } }}>
{heading && (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.4 }}
>
<Typeset {...{ heading, caption, stackProps: { sx: { textAlign: 'center' } } }} />
</motion.div>
)}
<Grid container spacing={1.5} sx={{ height: 1, justifyContent: 'center' }}>
{plans.map((plan, index) => (
<Grid key={index} size={{ xs: 12, sm: 6, md: 4 }}>
<motion.div
key={index}
initial={{ opacity: 0, x: index === 0 ? -60 : index === 2 ? 60 : 0, y: 0, scale: index === 1 ? 0.9 : 1 }}
whileInView={{ opacity: 1, x: 0, y: 0, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.4, ease: 'easeInOut', delay: plan.animationDelay }}
style={{ height: '100%' }}
>
<GraphicsCard sx={{ height: 1, ...(plan.active && { border: '1px solid', borderColor: 'primary.main' }) }}>
<Box sx={{ pt: { xs: 4, sm: 5, md: 8 }, px: { xs: 2, md: 5 }, pb: { xs: 2, sm: 3, md: 5.25 }, height: 1 }}>
<Stack sx={{ gap: 5, height: 1 }}>
<Stack sx={{ gap: { xs: 2, md: 3 } }}>
<Stack sx={{ gap: { xs: 1, sm: 1.5 }, textAlign: 'center' }}>
<Typography variant="subtitle1" sx={{ color: 'text.secondary' }}>
{plan.title}
</Typography>
<Stack>
<Typography component="div" variant="h1">
${plan.offerPrice && plan.offerPrice > 0 ? plan.offerPrice : plan.price}
</Typography>
{!!(plan.offerPrice && plan.offerPrice > 0) && (
<Typography variant="h3" sx={{ color: 'grey.700', textDecoration: 'line-through' }}>
${plan.price}
</Typography>
)}
</Stack>
</Stack>
</Stack>
<Stack sx={{ height: 1, justifyContent: 'space-between', gap: { xs: 3, sm: 4, md: 5 } }}>
<Stack sx={{ gap: 5 }}>
<Divider>
<Chip
label={plan.featureTitle}
size="small"
slotProps={{ label: { sx: { py: 0.5, px: 1.5, typography: 'caption', color: 'text.secondary' } } }}
sx={{ bgcolor: 'grey.200' }}
/>
</Divider>
<Stack sx={{ gap: { xs: 0.75, md: 1 } }}>
{features.map((item, index) => {
const active = plan.featuresID.includes(item.id);
return (
<Stack key={index} direction="row" sx={{ gap: 1.25, alignItems: 'center' }}>
<Avatar sx={{ bgcolor: 'grey.100', width: 24, height: 24 }}>
<SvgIcon
name={active ? 'tabler-check' : 'tabler-x'}
type={IconType.STROKE}
size={16}
twoToneColor={theme.vars.palette.grey[100]}
color={active ? 'secondary.darker' : 'text.secondary'}
stroke={2}
/>
</Avatar>
<Typography
variant={active ? 'subtitle1' : 'body1'}
sx={{ color: active ? 'secondary.darker' : 'text.secondary' }}
>
{item.label}
</Typography>
</Stack>
);
})}
</Stack>
</Stack>
<Stack sx={{ gap: 0.75 }}>
{plan.contentLink && (
<Typography variant="body2" sx={{ color: 'text.secondary', textAlign: 'center', mb: 1.25 }}>
{plan.content}&nbsp;
<Link component={NextLink} color="primary.main" sx={{ textDecoration: 'underline' }} {...plan.contentLink} />
</Typography>
)}
<motion.div
initial={{ scale: 0.9 }}
animate={{ scale: plan.active ? [1, 1.04, 1] : 1 }}
transition={{
duration: plan.active ? 0.9 : 1,
delay: 0.1,
ease: 'easeInOut',
...(plan.active && { repeat: Infinity, repeatType: 'loop' })
}}
whileHover={{ scale: 1.01, transition: { duration: 0.3 } }}
>
<ButtonAnimationWrapper>
<Button
variant={plan.active ? 'contained' : 'outlined'}
sx={{ ...(!plan.link && { mb: { sm: 3.25, md: 3.75 } }) }}
fullWidth
{...plan.exploreLink}
/>
</ButtonAnimationWrapper>
</motion.div>
{plan.link && (
<Typography variant="subtitle1" sx={{ textAlign: 'center', color: 'text.secondary' }}>
or
<Link
component={NextLink}
variant="subtitle1"
color="primary.main"
{...plan.link}
underline="hover"
sx={{ marginLeft: 0.5 }}
/>
</Typography>
)}
</Stack>
</Stack>
</Stack>
</Box>
</GraphicsCard>
</motion.div>
</Grid>
))}
</Grid>
</Stack>
</ContainerWrapper>
);
}
Pricing9.propTypes = { heading: PropTypes.string, caption: PropTypes.string, features: PropTypes.array, plans: PropTypes.array };
@@ -0,0 +1 @@
export { default as Pricing9 } from './Pricing9';
@@ -0,0 +1,181 @@
'use client';
import { useEffect, useState } from 'react';
// @mui
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
// @project
import ContainerWrapper from '@/components/ContainerWrapper';
import { SECTION_COMMON_PY } from '@/utils/constant';
// Helper functions for scrollspy
const clamp = (value) => Math.max(0, value);
const isBetween = (value, floor, ceil) => value >= floor && value <= ceil;
/*************************** HOOKS - SCROLLSPY ***************************/
function useScrollspy(ids, offset = 0) {
const [activeId, setActiveId] = useState('');
useEffect(() => {
const listener = () => {
const scroll = window.scrollY;
const position = ids
.map((id) => {
const element = document.getElementById(id);
if (!element) return { id, top: -1, bottom: -1 };
const rect = element.getBoundingClientRect();
const top = clamp(rect.top + scroll - offset);
const bottom = clamp(rect.bottom + scroll - offset);
return { id, top, bottom };
})
.find(({ top, bottom }) => isBetween(scroll, top, bottom));
setActiveId(position?.id || '');
};
window.addEventListener('scroll', listener);
window.addEventListener('resize', listener);
listener(); // Initial call to set the activeId
return () => {
window.removeEventListener('scroll', listener);
window.removeEventListener('resize', listener);
};
}, [ids, offset]);
return activeId;
}
/*************************** PRIVACY POLICY - DATA ***************************/
const menuItems = [
{
id: 'acceptance-of-terms',
heading: 'Acceptance of terms',
caption:
'By accessing and using this website, you agree to be bound by these Terms and Conditions of Use. If you do not agree with any part of these terms, you must not use the website. shares information about you when you use our website or services. By accessing or using our website, you consent to the practices described in this policy.'
},
{
id: 'changes-to-terms',
heading: 'Changes to terms',
caption:
'We reserve the right to modify or replace these terms at our sole discretion. It is your responsibility to check these terms periodically for changes. Your continued use of the website after the posting of any changes constitutes acceptance of those changes.'
},
{
id: 'user-conduct',
heading: 'User conduct',
caption:
'You agree to use this website only for lawful purposes and in a manner consistent with all applicable local, national, and international laws and regulations.'
},
{
id: 'intellectual-property',
heading: 'Intellectual property',
caption:
'All content on this website, including but not limited to text, graphics, logos, images, audio clips, video clips, digital downloads, and data compilations, is the property of [Your Company Name] or its content suppliers and protected by international copyright laws.'
},
{
id: 'privacy-policy',
heading: 'Privacy policy',
caption:
'We do not sell, trade, or otherwise transfer your personal information to third parties. We may share information with trusted service providers who assist us in operating our website, conducting our business, or servicing you.'
},
{
id: 'user-generated-content',
heading: 'User-generated content',
caption:
'If you submit any material to this website, you grant [Your Company Name] a perpetual, royalty-free, worldwide license to use, reproduce, modify, adapt, publish, translate, create derivative works from, distribute, and display such material.'
},
{
id: 'limitation-of-liability',
heading: 'Limitation of liability',
caption:
'In no event shall [Your Company Name] or its affiliates be liable for any direct, indirect, incidental, special, or consequential damages resulting from the use or inability to use this website.'
},
{
id: 'indemnity',
heading: 'Indemnity',
caption:
"You agree to indemnify and hold harmless [Your Company Name] and its affiliates from any claims, actions, demands, damages, liabilities, costs, or expenses, including reasonable attorneys' fees, arising out of or related to your use of the website or any violation of these terms."
},
{
id: 'governing-law',
heading: 'Governing law',
caption:
'These terms are governed by and construed in accordance with the laws of [Your Country/State], without regard to its conflict of law principles.'
}
];
/*************************** PRIVACY POLICY - 1 ***************************/
/**
*
* Demos:
* - [PrivacyPolicy1](https://www.saasable.io/blocks/privacy-policy/privacy-policy1)
*/
export default function PrivacyPolicy1() {
const ids = menuItems.map((item) => item.id);
// Adjust offset as per header height
const activeId = useScrollspy(ids, 60);
const [selectedID, setSelectedID] = useState(activeId);
useEffect(() => {
if (activeId) {
setSelectedID(activeId);
}
}, [activeId]);
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid size={{ xs: 12, sm: 4, md: 3 }}>
<List component="div" sx={{ position: 'sticky', top: 20 }} disablePadding>
{menuItems.map((item, index) => (
<ListItemButton
key={index}
href={`#${item.id}`}
sx={{
py: 1.25,
px: 1.5,
borderRadius: 3,
mb: 0.75,
...(selectedID === item.id && { color: 'primary.main', bgcolor: 'grey.100' }),
'&:hover': { bgcolor: 'grey.50' }
}}
onClick={() => setSelectedID(item.id)}
>
<ListItemText primary={item.heading} slotProps={{ primary: { variant: 'subtitle1' } }} sx={{ my: 0 }} />
</ListItemButton>
))}
</List>
<Divider sx={{ display: { xs: 'block', sm: 'none' } }} />
</Grid>
<Grid size={{ xs: 12, sm: 8, md: 9 }}>
{menuItems.map((item, index) => (
<Stack
key={index}
id={item.id}
sx={{ py: { xs: 1, sm: 1.5, md: 3 }, px: { md: 3 }, gap: 1, '&:first-of-type': { pt: { sm: 0 } } }}
>
<Typography variant="h4">{item.heading}</Typography>
<Typography variant="body1" sx={{ color: 'text.secondary' }}>
{item.caption}
</Typography>
</Stack>
))}
</Grid>
</Grid>
</ContainerWrapper>
);
}
@@ -0,0 +1 @@
export { default as PrivacyPolicy1 } from './PrivacyPolicy1';
@@ -0,0 +1,83 @@
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import CardMedia from '@mui/material/CardMedia';
import Grid from '@mui/material/Grid';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
// @project
import ButtonAnimationWrapper from '@/components/ButtonAnimationWrapper';
import { SECTION_COMMON_PY } from '@/utils/constant';
import ContainerWrapper from '@/components/ContainerWrapper';
import GraphicsImage from '@/components/GraphicsImage';
import SvgIcon from '@/components/SvgIcon';
import { NextLink } from '@/components/routes';
export default function ProPage({ image }) {
return (
<>
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Box
sx={{
bgcolor: 'background.default',
borderRadius: 7.5
}}
>
<Box sx={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}>
<GraphicsImage
image={image}
sx={{
width: { xs: '75px', md: '150px' },
height: { xs: '75px', md: '150px' },
borderRadius: 0
}}
/>
<Typography variant="h4" sx={{ fontWeight: 'bold', mb: 1, mt: { xs: 4, md: 7 }, textAlign: 'center' }}>
Discover the Components Not Available in the Free Version
</Typography>
<Typography variant="body1" color="textSecondary" sx={{ mb: 3, color: 'grey.700', textAlign: 'center' }}>
<Link href="https://www.saasable.io/" target="_blank" underline="hover" component={NextLink}>
Check out SaasAble PRO
</Link>{' '}
which offers updated components, auto layout, and Figma new variables, complete with dark mode!
</Typography>
<Grid container spacing={2} justifyContent="center">
<Grid>
<Button
variant="outlined"
component={NextLink}
href="https://www.figma.com/design/mlkXfeqxUKqIo0GQhPBqPb/SaasAble---UI-Kit---Preview-only?node-id=11-1833&t=JBHOIIEuYZpmN6v8-1"
target="_blank"
sx={{ minWidth: 215 }}
startIcon={<CardMedia component="img" src="/assets/images/shared/figma.svg" sx={{ width: 16, height: 16 }} alt="figma" />}
>
Preview Pro
</Button>
</Grid>
<Grid>
<ButtonAnimationWrapper>
<Button
variant="contained"
color="primary"
component={NextLink}
href={'https://www.saasable.io/sections'}
target="_blank"
startIcon={<SvgIcon name="tabler-sparkles" size={16} stroke={3} color="background.default" />}
>
View Pro Component
</Button>
</ButtonAnimationWrapper>
</Grid>
</Grid>
</Box>
</Box>
</ContainerWrapper>
</>
);
}
ProPage.propTypes = { image: PropTypes.any };
@@ -0,0 +1 @@
export { default as ProPage } from './ProPage';
@@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
// @mui
import Button from '@mui/material/Button';
import Chip from '@mui/material/Chip';
import Link from '@mui/material/Link';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
// @project
import ContainerWrapper from '@/components/ContainerWrapper';
import { NextLink } from '@/components/routes';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** SMALL HERO - 3 ***************************/
/**
*
* Demos:
* - [SmallHero3](https://www.saasable.io/blocks/small-hero/small-hero3)
*
* API
* - [SmallHero3 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/smallhero/smallhero3#props-details)
*/
export default function SmallHero3({ chip, headLine, captionLine, exploreBtn }) {
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ alignItems: 'start', gap: { xs: 1.5, md: 4 } }}>
{chip && (
<Chip
label={
typeof chip.label === 'string' ? (
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
{chip.label}
{chip.link && (
<Link
component={NextLink}
variant="caption"
color="primary.main"
{...chip.link}
underline="hover"
sx={{ '&:hover': { color: 'primary.dark' } }}
/>
)}
</Typography>
) : (
chip.label
)
}
sx={{ bgcolor: 'grey.100' }}
/>
)}
<Stack direction="row" sx={{ justifyContent: 'space-between', flexWrap: { xs: 'wrap', md: 'unset' }, width: 1, gap: 3 }}>
<Stack sx={{ gap: 1.5 }}>
{typeof headLine === 'string' ? <Typography variant="h1">{headLine}</Typography> : headLine}
{captionLine && (
<Typography variant="h6" sx={{ color: 'text.secondary', width: { md: '85%' } }}>
{captionLine}
</Typography>
)}
</Stack>
{exploreBtn && (
<Stack direction="row" sx={{ alignItems: 'end', justifyContent: 'center', minWidth: 'fit-content' }}>
<Button size="large" variant="outlined" {...exploreBtn} />
</Stack>
)}
</Stack>
</Stack>
</ContainerWrapper>
);
}
SmallHero3.propTypes = { chip: PropTypes.any, headLine: PropTypes.any, captionLine: PropTypes.any, exploreBtn: PropTypes.any };
@@ -0,0 +1 @@
export { default as SmallHero3 } from './SmallHero3';
@@ -0,0 +1,82 @@
'use client';
import PropTypes from 'prop-types';
// @mui
import { useTheme } from '@mui/material/styles';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Masonry from '@mui/lab/Masonry';
// @third-party
import { motion } from 'motion/react';
// @project
import { GraphicsCard } from '@/components/cards';
import ContainerWrapper from '@/components/ContainerWrapper';
import Rating from '@/components/Rating';
import Typeset from '@/components/Typeset';
import { withAlpha } from '@/utils/colorUtils';
import { SECTION_COMMON_PY } from '@/utils/constant';
/*************************** TESTIMONIAL - 10 ***************************/
/**
*
* Demos:
* - [Testimonial10](https://www.saasable.io/blocks/testimonial/testimonial10)
*
* API:
* - [Testimonial10 API](https://phoenixcoded.gitbook.io/saasable/ui-kit/development/components/testimonial/testimonial10#props-details)
*/
export default function Testimonial10({ heading, caption, testimonials }) {
const theme = useTheme();
const gc = theme.vars.palette.background.default;
const gradient = `radial-gradient(146.46% 68.12% at 50% 29.86%, ${withAlpha(gc, 0)} 0%, ${gc} 100%)`;
return (
<ContainerWrapper sx={{ py: SECTION_COMMON_PY }}>
<Stack sx={{ gap: { xs: 3, sm: 4 } }}>
<Typeset {...{ heading, caption, stackProps: { sx: { maxWidth: { xs: 550, md: 700 }, textAlign: 'center', mx: 'auto' } } }} />
<Masonry
columns={{ xs: 2, sm: 3 }}
spacing={{ xs: 1, sm: 1.5 }}
sx={{
position: 'relative',
'&:before': { position: 'absolute', content: `' '`, left: 0, bottom: 0, width: 1, height: 1, background: gradient }
}}
>
{testimonials.map((testimonial, index) => (
<motion.div
key={index}
initial={{ opacity: 0, scale: 0.5 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.4, ease: 'easeOut', delay: index * 0.1 }}
>
<GraphicsCard key={index} sx={{ borderRadius: { xs: 4, md: 6 } }}>
<Stack sx={{ justifyContent: 'space-between', height: 1, gap: 3, p: { xs: 1.5, md: 2 } }}>
<Rating {...{ rate: testimonial.ratings, starSize: 16 }} />
<Stack sx={{ gap: 1 }}>
<Typography variant="subtitle1">{testimonial.title}</Typography>
<Typography sx={{ color: 'text.secondary' }}>{testimonial.review}</Typography>
</Stack>
<Stack sx={{ gap: 0.5 }}>
<Typography variant="subtitle1">{testimonial.profile.name}</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
{testimonial.profile.role}
</Typography>
</Stack>
</Stack>
</GraphicsCard>
</motion.div>
))}
</Masonry>
</Stack>
</ContainerWrapper>
);
}
Testimonial10.propTypes = { heading: PropTypes.any, caption: PropTypes.any, testimonials: PropTypes.any };
@@ -0,0 +1 @@
export { default as Testimonial10 } from './Testimonial10';