├── .eslintrc.json ├── .gitignore ├── .prettierrc.cjs ├── README.md ├── app ├── (auth) │ ├── login │ │ └── page.tsx │ └── signup │ │ └── page.tsx ├── (marketing) │ ├── layout.tsx │ └── page.tsx ├── layout.tsx └── provider.tsx ├── components ├── announcement-banner │ ├── announcement-banner.tsx │ └── index.ts ├── button-link │ ├── button-link.tsx │ └── index.ts ├── faq │ ├── faq.tsx │ └── index.ts ├── features │ ├── features.tsx │ └── index.ts ├── gradients │ └── background-gradient.tsx ├── hero │ ├── hero.tsx │ └── index.ts ├── highlights │ ├── highlights.tsx │ └── index.ts ├── layout │ ├── footer.tsx │ ├── header.tsx │ ├── index.ts │ ├── logo.tsx │ ├── marketing-layout.tsx │ ├── navigation.tsx │ └── theme-toggle.tsx ├── logos │ ├── chakra.tsx │ ├── index.ts │ ├── next.tsx │ └── react.tsx ├── mobile-nav │ ├── index.ts │ └── mobile-nav.tsx ├── motion │ ├── box.tsx │ ├── fall-in-place.tsx │ ├── float.tsx │ └── page-transition.tsx ├── nav-link │ ├── index.ts │ └── nav-link.tsx ├── pricing │ └── pricing.tsx ├── section │ ├── index.ts │ ├── section-title.tsx │ └── section.tsx ├── seo │ ├── index.ts │ └── seo.tsx ├── testimonials │ ├── index.ts │ ├── testimonial.tsx │ └── testimonials.tsx └── typography │ └── index.tsx ├── data ├── appulse.tsx ├── config.tsx ├── faq.tsx ├── logo.tsx ├── pricing.tsx └── testimonials.tsx ├── hooks ├── use-route-changed.ts └── use-scrollspy.ts ├── next-env.d.ts ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── posts └── post-01.mdx ├── public └── static │ ├── favicons │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── manifest.json │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ └── ms-icon-70x70.png │ ├── images │ ├── avatar.jpg │ ├── avatar2.jpg │ ├── avatar3.jpg │ └── eelco.jpg │ └── screenshots │ ├── billing.png │ ├── dashboard.png │ ├── landingspage.png │ └── list.png ├── theme ├── components │ ├── button.ts │ ├── cta.ts │ ├── features.ts │ ├── index.ts │ ├── section-title.ts │ └── section.ts ├── foundations │ └── typography.ts └── index.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "import/no-anonymous-default-export": "off" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .next -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 80, 6 | importOrder: ['^react$', '^react-dom$', '^#.(.*)$', '^[./]'], 7 | importOrderSeparation: true, 8 | importOrderSortSpecifiers: true, 9 | importOrderGroupNamespaceSpecifiers: true, 10 | plugins: ['@trivago/prettier-plugin-sort-imports'], 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Saas UI - Next.js - landing page. 2 | 3 | This is a free Next.js landing page template based on https://saas-ui.dev. 4 | Feel free to submit any feature requests. If you use this template please share what you've built [on Twitter](https://twitter.com/saas_js) 🚀. 5 | 6 | **[View demo](https://saas-ui-nextjs-landing-page.netlify.app/)** 7 | 8 | ## Tech 9 | 10 | - Next.js (App router) 11 | - Chakra UI 12 | - Saas UI 13 | - Typescript 14 | 15 | ## Features 16 | 17 | - Feature blocks 18 | - Testimonials 19 | - Pricing tables 20 | - Log in and Sign up pages 21 | - FAQ 22 | 23 | ## Getting Started 24 | 25 | First, clone this repo and run `pnpm i` 26 | 27 | To start the app run: 28 | 29 | ```bash 30 | pnpm dev 31 | ``` 32 | 33 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 34 | 35 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 36 | 37 | ## Configuration 38 | 39 | Configuration files to edit basic site information, add testimonials, faq and pricing table can be found in `/data`. 40 | 41 | ## Learn More 42 | 43 | Find out more about Saas UI. 44 | 45 | - [Saas UI Documentation](https://saas-ui.dev/docs). 46 | 47 | To learn more about Next.js, take a look at the following resources: 48 | 49 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 50 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 51 | 52 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 53 | 54 | ## Deploy on Vercel 55 | 56 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 57 | 58 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 59 | 60 | ## License 61 | 62 | MIT 63 | -------------------------------------------------------------------------------- /app/(auth)/login/page.tsx: -------------------------------------------------------------------------------- 1 | import { Center } from '@chakra-ui/react' 2 | import { Auth } from '@saas-ui/auth' 3 | import { Link } from '@saas-ui/react' 4 | import { BackgroundGradient } from 'components/gradients/background-gradient' 5 | import { PageTransition } from 'components/motion/page-transition' 6 | import { Section } from 'components/section' 7 | import { NextPage } from 'next' 8 | import { FaGithub, FaGoogle } from 'react-icons/fa' 9 | 10 | const providers = { 11 | google: { 12 | name: 'Google', 13 | icon: FaGoogle, 14 | }, 15 | github: { 16 | name: 'Github', 17 | icon: FaGithub, 18 | variant: 'solid', 19 | }, 20 | } 21 | 22 | const Login: NextPage = () => { 23 | return ( 24 |
25 | 26 | 27 |
28 | 29 | Sign up} 33 | /> 34 | 35 |
36 |
37 | ) 38 | } 39 | 40 | export default Login 41 | -------------------------------------------------------------------------------- /app/(auth)/signup/page.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Center, Stack, Text } from '@chakra-ui/react' 2 | import { Auth } from '@saas-ui/auth' 3 | import { Link } from '@saas-ui/react' 4 | import { NextPage } from 'next' 5 | import NextLink from 'next/link' 6 | import { FaGithub, FaGoogle } from 'react-icons/fa' 7 | 8 | import { Features } from '#components/features' 9 | import { BackgroundGradient } from '#components/gradients/background-gradient' 10 | import { PageTransition } from '#components/motion/page-transition' 11 | import { Section } from '#components/section' 12 | import siteConfig from '#data/config' 13 | 14 | const providers = { 15 | google: { 16 | name: 'Google', 17 | icon: FaGoogle, 18 | }, 19 | github: { 20 | name: 'Github', 21 | icon: FaGithub, 22 | variant: 'solid', 23 | }, 24 | } 25 | 26 | const Login: NextPage = () => { 27 | return ( 28 |
29 | 40 | 41 | 47 | 48 | 49 | 55 | 56 | ({ 65 | iconPosition: 'left', 66 | variant: 'left-icon', 67 | 68 | ...feature, 69 | }))} 70 | /> 71 | 72 |
73 | 74 | Log in} 79 | > 80 | 81 | By signing up you agree to our{' '} 82 | 83 | Terms of Service 84 | {' '} 85 | and{' '} 86 | 87 | Privacy Policy 88 | 89 | 90 | 91 | 92 |
93 |
94 |
95 |
96 | ) 97 | } 98 | 99 | export default Login 100 | -------------------------------------------------------------------------------- /app/(marketing)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { MarketingLayout } from '#components/layout' 2 | 3 | export default function Layout(props: { children: React.ReactNode }) { 4 | return {props.children} 5 | } 6 | -------------------------------------------------------------------------------- /app/(marketing)/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { 4 | Box, 5 | ButtonGroup, 6 | Container, 7 | Flex, 8 | HStack, 9 | Heading, 10 | Icon, 11 | IconButton, 12 | Stack, 13 | Tag, 14 | Text, 15 | VStack, 16 | Wrap, 17 | useClipboard, 18 | } from '@chakra-ui/react' 19 | import { Br, Link } from '@saas-ui/react' 20 | import type { Metadata, NextPage } from 'next' 21 | import Image from 'next/image' 22 | import { 23 | FiArrowRight, 24 | FiBox, 25 | FiCheck, 26 | FiCode, 27 | FiCopy, 28 | FiFlag, 29 | FiGrid, 30 | FiLock, 31 | FiSearch, 32 | FiSliders, 33 | FiSmile, 34 | FiTerminal, 35 | FiThumbsUp, 36 | FiToggleLeft, 37 | FiTrendingUp, 38 | FiUserPlus, 39 | } from 'react-icons/fi' 40 | 41 | import * as React from 'react' 42 | 43 | import { ButtonLink } from '#components/button-link/button-link' 44 | import { Faq } from '#components/faq' 45 | import { Features } from '#components/features' 46 | import { BackgroundGradient } from '#components/gradients/background-gradient' 47 | import { Hero } from '#components/hero' 48 | import { 49 | Highlights, 50 | HighlightsItem, 51 | HighlightsTestimonialItem, 52 | } from '#components/highlights' 53 | import { ChakraLogo, NextjsLogo } from '#components/logos' 54 | import { FallInPlace } from '#components/motion/fall-in-place' 55 | import { Pricing } from '#components/pricing/pricing' 56 | import { Testimonial, Testimonials } from '#components/testimonials' 57 | import { Em } from '#components/typography' 58 | import faq from '#data/faq' 59 | import pricing from '#data/pricing' 60 | import testimonials from '#data/testimonials' 61 | 62 | export const meta: Metadata = { 63 | title: 'Saas UI Landingspage', 64 | description: 'Free SaaS landingspage starter kit', 65 | } 66 | 67 | const Home: NextPage = () => { 68 | return ( 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | ) 83 | } 84 | 85 | const HeroSection: React.FC = () => { 86 | return ( 87 | 88 | 89 | 90 | 91 | 97 | Build beautiful 98 |
software faster 99 | 100 | } 101 | description={ 102 | 103 | Saas UI is a React component library 104 |
that doesn't get in your way and helps you
{' '} 105 | build intuitive SaaS products with speed. 106 |
107 | } 108 | > 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | Sign Up 117 | 118 | 133 | } 134 | > 135 | View demo 136 | 137 | 138 | 139 |
140 | 149 | 150 | 151 | Screenshot of a ListPage in Saas UI Pro 159 | 160 | 161 | 162 |
163 |
164 | 165 | 206 |
207 | ) 208 | } 209 | 210 | const HighlightsSection = () => { 211 | const { value, onCopy, hasCopied } = useClipboard('yarn add @saas-ui/react') 212 | 213 | return ( 214 | 215 | 216 | 217 | 218 | Get started for free with 30+ open source components. 219 | Including authentication screens with Clerk, Supabase and Magic. 220 | Fully functional forms with React Hook Form. Data tables with React 221 | Table. 222 | 223 | 224 | 235 | 236 | 237 | yarn add 238 | {' '} 239 | 240 | @saas-ui/react 241 | 242 | 243 | : } 245 | aria-label="Copy install command" 246 | onClick={onCopy} 247 | variant="ghost" 248 | ms="4" 249 | isRound 250 | color="white" 251 | /> 252 | 253 | 254 | 255 | 256 | 257 | We don't like to re-invent the wheel, neither should you. We 258 | selected the most productive and established tools in the scene and 259 | build Saas UI on top of it. 260 | 261 | 262 | 268 | “Saas UI helped us set up a beautiful modern UI in no time. It saved us 269 | hundreds of hours in development time and allowed us to focus on 270 | business logic for our specific use-case from the start.” 271 | 272 | 276 | 277 | We took care of all your basic frontend needs, so you can start 278 | building functionality that makes your product unique. 279 | 280 | 281 | {[ 282 | 'authentication', 283 | 'navigation', 284 | 'crud', 285 | 'settings', 286 | 'multi-tenancy', 287 | 'layouts', 288 | 'billing', 289 | 'a11y testing', 290 | 'server-side rendering', 291 | 'documentation', 292 | 'onboarding', 293 | 'storybooks', 294 | 'theming', 295 | 'upselling', 296 | 'unit testing', 297 | 'feature flags', 298 | 'responsiveness', 299 | ].map((value) => ( 300 | 307 | {value} 308 | 309 | ))} 310 | 311 | 312 | 313 | ) 314 | } 315 | 316 | const FeaturesSection = () => { 317 | return ( 318 | 327 | Not your standard 328 |
dashboard template. 329 | 330 | } 331 | description={ 332 | <> 333 | Saas UI Pro includes everything you need to build modern frontends. 334 |
335 | Use it as a template for your next product or foundation for your 336 | design system. 337 | 338 | } 339 | align="left" 340 | columns={[1, 2, 3]} 341 | iconSize={4} 342 | features={[ 343 | { 344 | title: '#components.', 345 | icon: FiBox, 346 | description: 347 | 'All premium components are available on a private NPM registery, no more copy pasting and always up-to-date.', 348 | variant: 'inline', 349 | }, 350 | { 351 | title: 'Starterkits.', 352 | icon: FiLock, 353 | description: 354 | 'Example apps in Next.JS, Electron. Including authentication, billing, example pages, everything you need to get started FAST.', 355 | variant: 'inline', 356 | }, 357 | { 358 | title: 'Documentation.', 359 | icon: FiSearch, 360 | description: 361 | 'Extensively documented, including storybooks, best practices, use-cases and examples.', 362 | variant: 'inline', 363 | }, 364 | { 365 | title: 'Onboarding.', 366 | icon: FiUserPlus, 367 | description: 368 | 'Add user onboarding flows, like tours, hints and inline documentation without breaking a sweat.', 369 | variant: 'inline', 370 | }, 371 | { 372 | title: 'Feature flags.', 373 | icon: FiFlag, 374 | description: 375 | "Implement feature toggles for your billing plans with easy to use hooks. Connect Flagsmith, or other remote config services once you're ready.", 376 | variant: 'inline', 377 | }, 378 | { 379 | title: 'Upselling.', 380 | icon: FiTrendingUp, 381 | description: 382 | '#components and hooks for upgrade flows designed to make upgrading inside your app frictionless.', 383 | variant: 'inline', 384 | }, 385 | { 386 | title: 'Themes.', 387 | icon: FiToggleLeft, 388 | description: 389 | 'Includes multiple themes with darkmode support, always have the perfect starting point for your next project.', 390 | variant: 'inline', 391 | }, 392 | { 393 | title: 'Generators.', 394 | icon: FiTerminal, 395 | description: 396 | 'Extend your design system while maintaininig code quality and consistency with built-in generators.', 397 | variant: 'inline', 398 | }, 399 | { 400 | title: 'Monorepo.', 401 | icon: FiCode, 402 | description: ( 403 | <> 404 | All code is available as packages in a high-performance{' '} 405 | Turborepo, you have full 406 | control to modify and adjust it to your workflow. 407 | 408 | ), 409 | variant: 'inline', 410 | }, 411 | ]} 412 | /> 413 | ) 414 | } 415 | 416 | const TestimonialsSection = () => { 417 | const columns = React.useMemo(() => { 418 | return testimonials.items.reduce>( 419 | (columns, t, i) => { 420 | columns[i % 3].push(t) 421 | 422 | return columns 423 | }, 424 | [[], [], []], 425 | ) 426 | }, []) 427 | 428 | return ( 429 | 434 | <> 435 | {columns.map((column, i) => ( 436 | 437 | {column.map((t, i) => ( 438 | 439 | ))} 440 | 441 | ))} 442 | 443 | 444 | ) 445 | } 446 | 447 | const PricingSection = () => { 448 | return ( 449 | 450 | 451 | VAT may be applicable depending on your location. 452 | 453 | 454 | ) 455 | } 456 | 457 | const FaqSection = () => { 458 | return 459 | } 460 | 461 | export default Home 462 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ColorModeScript, theme } from '@chakra-ui/react' 2 | 3 | import { Provider } from './provider' 4 | 5 | export default function Layout(props: { children: React.ReactNode }) { 6 | const colorMode = theme.config.initialColorMode 7 | 8 | return ( 9 | 10 | 11 | 16 | 22 | 28 | 29 | 30 | 31 | 32 | {props.children} 33 | 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /app/provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { AuthProvider } from '@saas-ui/auth' 4 | import { SaasProvider } from '@saas-ui/react' 5 | 6 | import { theme } from '#theme' 7 | 8 | export function Provider(props: { children: React.ReactNode }) { 9 | return ( 10 | 11 | {props.children} 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /components/announcement-banner/announcement-banner.tsx: -------------------------------------------------------------------------------- 1 | import NextLink from "next/link"; 2 | import { 3 | Box, 4 | Container, 5 | Flex, 6 | HStack, 7 | Icon, 8 | LinkBox, 9 | LinkOverlay, 10 | useColorModeValue, 11 | Button, 12 | } from "@chakra-ui/react"; 13 | import { 14 | Banner, 15 | BannerActions, 16 | BannerContent, 17 | BannerDescription, 18 | BannerTitle, 19 | } from "@saas-ui/react"; 20 | import { FiArrowRight } from "react-icons/fi"; 21 | import { FallInPlace } from "../motion/fall-in-place"; 22 | 23 | export interface AnnouncementBannerProps { 24 | title: string; 25 | description: string; 26 | href: string; 27 | action?: string; 28 | } 29 | 30 | export const AnnouncementBanner: React.FC = ( 31 | props 32 | ) => { 33 | const { title, description, href, action } = props; 34 | if (!title) { 35 | return null; 36 | } 37 | 38 | return ( 39 | 40 | 41 | 42 | 43 | 84 | 85 | 86 | {title} 87 | 88 | 92 | 93 | {action && ( 94 | 95 | 113 | 114 | )} 115 | 116 | 117 | 118 | 119 | 120 | 121 | ); 122 | }; 123 | -------------------------------------------------------------------------------- /components/announcement-banner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './announcement-banner' 2 | -------------------------------------------------------------------------------- /components/button-link/button-link.tsx: -------------------------------------------------------------------------------- 1 | import { Button, ButtonProps } from '@chakra-ui/react' 2 | import NextLink, { LinkProps } from 'next/link' 3 | 4 | export type ButtonLinkProps = LinkProps & ButtonProps 5 | 6 | export const ButtonLink: React.FC = ({ 7 | href, 8 | children, 9 | ...props 10 | }) => { 11 | return ( 12 | 13 | 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /components/button-link/index.ts: -------------------------------------------------------------------------------- 1 | export * from './button-link' 2 | -------------------------------------------------------------------------------- /components/faq/faq.tsx: -------------------------------------------------------------------------------- 1 | import { chakra, SimpleGrid } from '@chakra-ui/react' 2 | import { Section, SectionProps, SectionTitle } from 'components/section' 3 | 4 | interface FaqProps extends Omit { 5 | title?: React.ReactNode 6 | description?: React.ReactNode 7 | items: { q: React.ReactNode; a: React.ReactNode }[] 8 | } 9 | 10 | export const Faq: React.FC = (props) => { 11 | const { 12 | title = 'Frequently asked questions', 13 | description, 14 | items = [], 15 | } = props 16 | return ( 17 |
18 | 19 | 20 | 21 | {items?.map(({ q, a }, i) => { 22 | return 23 | })} 24 | 25 |
26 | ) 27 | } 28 | 29 | export interface FaqItemProps { 30 | question: React.ReactNode 31 | answer: React.ReactNode 32 | } 33 | 34 | const FaqItem: React.FC = ({ question, answer }) => { 35 | return ( 36 | 37 | 38 | {question} 39 | 40 | {answer} 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /components/faq/index.ts: -------------------------------------------------------------------------------- 1 | export * from './faq' 2 | -------------------------------------------------------------------------------- /components/features/features.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { 3 | Box, 4 | Stack, 5 | VStack, 6 | SimpleGrid, 7 | Heading, 8 | Text, 9 | Icon, 10 | Circle, 11 | ResponsiveValue, 12 | useMultiStyleConfig, 13 | ThemingProps, 14 | SystemProps, 15 | } from '@chakra-ui/react' 16 | 17 | import { Section, SectionTitle, SectionTitleProps } from 'components/section' 18 | 19 | const Revealer = ({ children }: any) => { 20 | return children 21 | } 22 | 23 | export interface FeaturesProps 24 | extends Omit, 25 | ThemingProps<'Features'> { 26 | title?: React.ReactNode 27 | description?: React.ReactNode 28 | features: Array 29 | columns?: ResponsiveValue 30 | spacing?: string | number 31 | aside?: React.ReactChild 32 | reveal?: React.FC 33 | iconSize?: SystemProps['boxSize'] 34 | innerWidth?: SystemProps['maxW'] 35 | } 36 | 37 | export interface FeatureProps { 38 | title?: React.ReactNode 39 | description?: React.ReactNode 40 | icon?: any 41 | iconPosition?: 'left' | 'top' 42 | iconSize?: SystemProps['boxSize'] 43 | ip?: 'left' | 'top' 44 | variant?: string 45 | delay?: number 46 | } 47 | 48 | export const Feature: React.FC = (props) => { 49 | const { 50 | title, 51 | description, 52 | icon, 53 | iconPosition, 54 | iconSize = 8, 55 | ip, 56 | variant, 57 | } = props 58 | const styles = useMultiStyleConfig('Feature', { variant }) 59 | 60 | const pos = iconPosition || ip 61 | const direction = pos === 'left' ? 'row' : 'column' 62 | 63 | return ( 64 | 65 | {icon && ( 66 | 67 | 68 | 69 | )} 70 | 71 | {title} 72 | {description} 73 | 74 | 75 | ) 76 | } 77 | 78 | export const Features: React.FC = (props) => { 79 | const { 80 | title, 81 | description, 82 | features, 83 | columns = [1, 2, 3], 84 | spacing = 8, 85 | align: alignProp = 'center', 86 | iconSize = 8, 87 | aside, 88 | reveal: Wrap = Revealer, 89 | ...rest 90 | } = props 91 | 92 | const align = !!aside ? 'left' : alignProp 93 | 94 | const ip = align === 'left' ? 'left' : 'top' 95 | 96 | return ( 97 |
98 | 99 | 100 | {(title || description) && ( 101 | 102 | 107 | 108 | )} 109 | 110 | {features.map((feature, i) => { 111 | return ( 112 | 113 | 114 | 115 | ) 116 | })} 117 | 118 | 119 | {aside && ( 120 | 121 | {aside} 122 | 123 | )} 124 | 125 |
126 | ) 127 | } 128 | -------------------------------------------------------------------------------- /components/features/index.ts: -------------------------------------------------------------------------------- 1 | export * from './features' 2 | -------------------------------------------------------------------------------- /components/gradients/background-gradient.tsx: -------------------------------------------------------------------------------- 1 | import { Box, useTheme, useColorModeValue } from '@chakra-ui/react' 2 | 3 | export const BackgroundGradient = ({ hideOverlay, ...props }: any) => { 4 | const theme = useTheme() 5 | const colors = [ 6 | theme.colors.primary['800'], 7 | theme.colors.secondary['500'], 8 | theme.colors.cyan['500'], 9 | theme.colors.teal['500'], 10 | ] 11 | 12 | let fallbackBackground = `radial-gradient(at top left, ${colors[0]} 30%, transparent 80%), radial-gradient(at bottom, ${colors[1]} 0%, transparent 60%), radial-gradient(at bottom left, var(--chakra-colors-cyan-500) 0%, transparent 50%), 13 | radial-gradient(at top right, ${colors[3]}, transparent), radial-gradient(at bottom right, ${colors[0]} 0%, transparent 50%);` 14 | 15 | let gradientOverlay = `linear-gradient(0deg, var(--chakra-colors-${useColorModeValue( 16 | 'white', 17 | 'gray-900' 18 | )}) 60%, rgba(0, 0, 0, 0) 100%);` 19 | 20 | return ( 21 | 35 | 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /components/hero/hero.tsx: -------------------------------------------------------------------------------- 1 | import { Container, Flex, FlexProps, Text, VStack } from '@chakra-ui/react' 2 | 3 | interface HeroProps extends Omit { 4 | title: string | React.ReactNode 5 | description?: string | React.ReactNode 6 | } 7 | 8 | export const Hero = ({ title, description, children, ...rest }: HeroProps) => { 9 | return ( 10 | 11 | 12 | 13 | 14 | {title} 15 | 16 | 23 | {description} 24 | 25 | 26 | {children} 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /components/hero/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hero' 2 | -------------------------------------------------------------------------------- /components/highlights/highlights.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Card, 4 | CardProps, 5 | Grid, 6 | GridItem, 7 | GridItemProps, 8 | Heading, 9 | useTheme, 10 | } from '@chakra-ui/react' 11 | import { transparentize } from '@chakra-ui/theme-tools' 12 | 13 | import { Section, SectionProps } from '#components/section' 14 | import { Testimonial, TestimonialProps } from '#components/testimonials' 15 | 16 | export interface HighlightBoxProps 17 | extends GridItemProps, 18 | Omit {} 19 | 20 | export const HighlightsItem: React.FC = (props) => { 21 | const { children, title, ...rest } = props 22 | return ( 23 | 36 | {title && ( 37 | 38 | {title} 39 | 40 | )} 41 | {children} 42 | 43 | ) 44 | } 45 | 46 | export const HighlightsTestimonialItem: React.FC< 47 | HighlightBoxProps & TestimonialProps & { gradient: [string, string] } 48 | > = (props) => { 49 | const { 50 | name, 51 | description, 52 | avatar, 53 | children, 54 | gradient = ['primary.500', 'secondary.500'], 55 | ...rest 56 | } = props 57 | const theme = useTheme() 58 | return ( 59 | 65 | 77 | 81 | {description} 82 | 83 | } 84 | avatar={avatar} 85 | border="0" 86 | bg="transparent" 87 | boxShadow="none" 88 | color="white" 89 | position="relative" 90 | > 91 | {children} 92 | 93 | 94 | ) 95 | } 96 | 97 | export const Highlights: React.FC = (props) => { 98 | const { children, ...rest } = props 99 | 100 | return ( 101 |
107 | 112 | {children} 113 | 114 |
115 | ) 116 | } 117 | -------------------------------------------------------------------------------- /components/highlights/index.ts: -------------------------------------------------------------------------------- 1 | export * from './highlights' 2 | -------------------------------------------------------------------------------- /components/layout/footer.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | BoxProps, 4 | Container, 5 | Flex, 6 | HStack, 7 | SimpleGrid, 8 | Stack, 9 | Text, 10 | } from '@chakra-ui/react' 11 | import { Link, LinkProps } from '@saas-ui/react' 12 | 13 | import siteConfig from '#data/config' 14 | 15 | export interface FooterProps extends BoxProps { 16 | columns?: number 17 | } 18 | 19 | export const Footer: React.FC = (props) => { 20 | const { columns = 2, ...rest } = props 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {siteConfig.seo.description} 32 | 33 | 34 | {siteConfig.footer.copyright} 35 | 36 | 37 | {siteConfig.footer?.links?.map(({ href, label }) => ( 38 | 39 | {label} 40 | 41 | ))} 42 | 43 | 44 | 45 | 46 | ) 47 | } 48 | 49 | export interface CopyrightProps { 50 | title?: React.ReactNode 51 | children: React.ReactNode 52 | } 53 | 54 | export const Copyright: React.FC = ({ 55 | title, 56 | children, 57 | }: CopyrightProps) => { 58 | let content 59 | if (title && !children) { 60 | content = `© ${new Date().getFullYear()} - ${title}` 61 | } 62 | return ( 63 | 64 | {content || children} 65 | 66 | ) 67 | } 68 | 69 | export const FooterLink: React.FC = (props) => { 70 | const { children, ...rest } = props 71 | return ( 72 | 82 | {children} 83 | 84 | ) 85 | } 86 | -------------------------------------------------------------------------------- /components/layout/header.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | BoxProps, 4 | Container, 5 | Flex, 6 | useColorModeValue, 7 | } from '@chakra-ui/react' 8 | import { useScroll } from 'framer-motion' 9 | 10 | import * as React from 'react' 11 | 12 | import { Logo } from './logo' 13 | import Navigation from './navigation' 14 | 15 | export interface HeaderProps extends Omit {} 16 | 17 | export const Header = (props: HeaderProps) => { 18 | const ref = React.useRef(null) 19 | const [y, setY] = React.useState(0) 20 | const { height = 0 } = ref.current?.getBoundingClientRect() ?? {} 21 | 22 | const { scrollY } = useScroll() 23 | React.useEffect(() => { 24 | return scrollY.on('change', () => setY(scrollY.get())) 25 | }, [scrollY]) 26 | 27 | const bg = useColorModeValue('whiteAlpha.700', 'rgba(29, 32, 37, 0.7)') 28 | 29 | return ( 30 | height ? bg : ''} 42 | boxShadow={y > height ? 'md' : ''} 43 | borderBottomWidth={y > height ? '1px' : ''} 44 | {...props} 45 | > 46 | 47 | 48 | { 50 | if (window.location.pathname === '/') { 51 | e.preventDefault() 52 | 53 | window.scrollTo({ 54 | top: 0, 55 | behavior: 'smooth', 56 | }) 57 | } 58 | }} 59 | /> 60 | 61 | 62 | 63 | 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /components/layout/index.ts: -------------------------------------------------------------------------------- 1 | export { MarketingLayout } from './marketing-layout' 2 | -------------------------------------------------------------------------------- /components/layout/logo.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Heading, VisuallyHidden } from '@chakra-ui/react' 2 | import { Link } from '@saas-ui/react' 3 | 4 | import * as React from 'react' 5 | 6 | import siteConfig from '#data/config' 7 | 8 | export interface LogoProps { 9 | href?: string 10 | onClick?: (e: React.MouseEvent) => void 11 | } 12 | 13 | export const Logo = ({ href = '/', onClick }: LogoProps) => { 14 | let logo 15 | if (siteConfig.logo) { 16 | logo = 17 | } else { 18 | logo = ( 19 | 20 | {siteConfig.seo?.title} 21 | 22 | ) 23 | } 24 | 25 | return ( 26 | 27 | 34 | {logo} 35 | {siteConfig.seo?.title} 36 | 37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /components/layout/marketing-layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { Box, SkipNavContent, SkipNavLink } from '@chakra-ui/react' 4 | 5 | import { ReactNode } from 'react' 6 | 7 | import { 8 | AnnouncementBanner, 9 | AnnouncementBannerProps, 10 | } from '../announcement-banner' 11 | import { Footer, FooterProps } from './footer' 12 | import { Header, HeaderProps } from './header' 13 | 14 | interface LayoutProps { 15 | children: ReactNode 16 | announcementProps?: AnnouncementBannerProps 17 | headerProps?: HeaderProps 18 | footerProps?: FooterProps 19 | } 20 | 21 | export const MarketingLayout: React.FC = (props) => { 22 | const { children, announcementProps, headerProps, footerProps } = props 23 | return ( 24 | 25 | Skip to content 26 | {announcementProps ? : null} 27 |
28 | 29 | 30 | {children} 31 | 32 |