Break up LandingPage components into sub-components (#214)

* Break up LandingPage components into sub-components

* Resolve paths

* Fixed formatting, Seperate svgs in icons folder, added types

* Fixed formatting

* resolve formatting issue

* Update Header.tsx

* Update Hero.tsx

* Rename icons to logos, seperate components, remove types file, fix indentation

* Renamed components dir, used interface

* small fixes.

* fix

* Updated app_diff.

* fix

* Update contentSections.ts.diff

---------

Co-authored-by: Martin Sosic <sosic.martin@gmail.com>
Co-authored-by: vincanger <70215737+vincanger@users.noreply.github.com>
This commit is contained in:
Surendra Manjhi 2024-07-17 14:54:06 +05:30 committed by GitHub
parent 1abcdf762c
commit 5ea9f84064
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 770 additions and 742 deletions

View File

@ -1,3 +1,4 @@
public/public-banner.png
src/client/static/avatar-placeholder.png
src/client/static/open-saas-banner.png
src/landing-page/logos/SalesforceLogo.tsx

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,78 @@
--- template/app/src/landing-page/components/Clients.tsx
+++ opensaas-sh/app/src/landing-page/components/Clients.tsx
@@ -1,23 +1,64 @@
+import logo from '../../client/static/logo.png';
import AstroLogo from "../logos/AstroLogo";
-import OpenAILogo from "../logos/OpenAILogo";
import PrismaLogo from "../logos/PrismaLogo";
-import SalesforceLogo from "../logos/SalesforceLogo";
+import OpenAILogo from "../logos/OpenAILogo";
export default function Clients() {
return (
<div className='mt-12 mx-auto max-w-7xl px-6 lg:px-8 flex flex-col items-between gap-y-6'>
- <h2 className='mb-6 text-center font-semibold tracking-wide text-gray-500 dark:text-white'>
- Built with / Used by:
+ <h2 className='mb-6 text-center font-semibold tracking-wide text-gray-500'>
+ Built and Ships with
</h2>
<div className='mx-auto grid max-w-lg grid-cols-2 items-center gap-x-8 gap-y-12 sm:max-w-xl md:grid-cols-4 sm:gap-x-10 sm:gap-y-14 lg:mx-0 lg:max-w-none'>
- {
- [<SalesforceLogo />, <PrismaLogo />, <AstroLogo />, <OpenAILogo />].map((logo, index) => (
- <div key={index} className='flex justify-center col-span-1 max-h-12 w-full object-contain dark:opacity-80'>
- {logo}
- </div>
- ))
- }
+
+ <img
+ className='col-span-1 max-h-12 w-full object-contain grayscale opacity-100'
+ src='https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png'
+ alt='React'
+ height={48}
+ />
+
+ <img
+ className='col-span-1 max-h-12 w-full object-contain grayscale opacity-60 dark:filter dark:brightness-0 dark:invert'
+ src='https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Node.js_logo.svg/590px-Node.js_logo.svg.png'
+ alt='NodeJS'
+ height={48}
+ />
+
+ <img
+ className='col-span-1 max-h-12 w-full object-contain grayscale opacity-80'
+ src={logo}
+ alt='Wasp'
+ height={48}
+ />
+
+ <div className='flex justify-center col-span-1 max-h-12 w-full object-contain grayscale opacity-80'>
+ <PrismaLogo />
+ </div>
+
+ <img
+ className='col-span-1 max-h-12 w-full object-contain grayscale '
+ src='https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/Tailwind_CSS_Logo.svg/512px-Tailwind_CSS_Logo.svg.png'
+ alt='Tailwind CSS'
+ height={48}
+ />
+
+ <img
+ className='col-span-1 max-h-12 w-full object-contain grayscale opacity-80 dark:opacity-60 dark:filter dark:brightness-0 dark:invert'
+ src='https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Stripe_Logo%2C_revised_2016.svg/512px-Stripe_Logo%2C_revised_2016.svg.png'
+ alt='Stripe'
+ height={48}
+ />
+
+ <div className='flex justify-center col-span-1 w-full max-h-12 object-contain grayscale opacity-75'>
+ <AstroLogo />
+ </div>
+
+ <div className='flex justify-center col-span-1 w-full max-h-12 object-contain grayscale opacity-80'>
+ <OpenAILogo />
+ </div>
+
</div>
</div>
)

View File

@ -0,0 +1,28 @@
--- template/app/src/landing-page/components/FAQ.tsx
+++ opensaas-sh/app/src/landing-page/components/FAQ.tsx
@@ -7,20 +7,20 @@
export default function FAQ({ faqs }: { faqs: FAQ[] }) {
return (
- <div className='mt-32 mx-auto max-w-2xl divide-y divide-gray-900/10 dark:divide-gray-200/10 px-6 pb-8 sm:pb-24 sm:pt-12 lg:max-w-7xl lg:px-8 lg:py-32'>
+ <div className='mt-32 mx-auto max-w-2xl divide-y divide-gray-900/10 px-6 pb-8 sm:pb-24 sm:pt-12 lg:max-w-7xl lg:px-8 lg:py-32'>
<h2 className='text-2xl font-bold leading-10 tracking-tight text-gray-900 dark:text-white'>
Frequently asked questions
</h2>
- <dl className='mt-10 space-y-8 divide-y divide-gray-900/10'>
+ <dl className='mt-10 space-y-8 divide-y divide-gray-900/10 dark:divide-gray-100/10'>
{faqs.map((faq) => (
<div key={faq.id} className='pt-8 lg:grid lg:grid-cols-12 lg:gap-8'>
<dt className='text-base font-semibold leading-7 text-gray-900 lg:col-span-5 dark:text-white'>
{faq.question}
</dt>
- <dd className='flex items-center justify-start gap-2 mt-4 lg:col-span-7 lg:mt-0'>
- <p className='text-base leading-7 text-gray-600 dark:text-white'>{faq.answer}</p>
+ <dd className='mt-4 lg:col-span-7 lg:mt-0'>
+ <p className='text-base leading-7 text-gray-600 dark:text-gray-400'>{faq.answer}</p>
{faq.href && (
- <a href={faq.href} className='text-base leading-7 text-yellow-500 hover:text-yellow-600'>
+ <a href={faq.href} className='mt-4 text-base leading-7 text-yellow-500 hover:text-yellow-600'>
Learn more →
</a>
)}

View File

@ -0,0 +1,43 @@
--- template/app/src/landing-page/components/Features.tsx
+++ opensaas-sh/app/src/landing-page/components/Features.tsx
@@ -10,25 +10,27 @@
<div id='features' className='mx-auto mt-48 max-w-7xl px-6 lg:px-8'>
<div className='mx-auto max-w-2xl text-center'>
<p className='mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl dark:text-white'>
- The <span className='text-yellow-500'>Best</span> Features
+ <span className='text-yellow-500'>100%</span> Open-Source
</p>
- <p className='mt-6 text-lg leading-8 text-gray-600 dark:text-white'>
- Don't work harder.
- <br /> Work smarter.
+ <p className='mt-6 text-lg leading-8 text-gray-600 dark:text-gray-400'>
+ No vendor lock-in.
+ <br /> Deploy anywhere.
</p>
</div>
<div className='mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-4xl'>
<dl className='grid max-w-xl grid-cols-1 gap-x-8 gap-y-10 lg:max-w-none lg:grid-cols-2 lg:gap-y-16'>
{features.map((feature) => (
- <div key={feature.name} className='relative pl-16'>
- <dt className='text-base font-semibold leading-7 text-gray-900 dark:text-white'>
- <div className='absolute left-0 top-0 flex h-10 w-10 items-center justify-center border border-yellow-400 bg-yellow-100/50 dark:bg-boxdark rounded-lg'>
- <div className='text-2xl'>{feature.icon}</div>
- </div>
- {feature.name}
- </dt>
- <dd className='mt-2 text-base leading-7 text-gray-600 dark:text-white'>{feature.description}</dd>
- </div>
+ <a href={feature.href} className='group'>
+ <div key={feature.name} className='relative pl-16'>
+ <dt className='text-base font-semibold leading-7 text-gray-900 dark:text-white group-hover:underline'>
+ <div className='absolute left-0 top-0 flex h-10 w-10 items-center justify-center border border-yellow-400 bg-yellow-100/50 dark:bg-boxdark rounded-lg group-hover:border-yellow-500'>
+ <div className='text-2xl group-hover:opacity-80 '>{feature.icon}</div>
+ </div>
+ {feature.name}
+ </dt>
+ <dd className='mt-2 text-base leading-7 text-gray-600 dark:text-gray-400'>{feature.description}</dd>
+ </div>
+ </a>
))}
</dl>
</div>

View File

@ -0,0 +1,11 @@
--- template/app/src/landing-page/components/Footer.tsx
+++ opensaas-sh/app/src/landing-page/components/Footer.tsx
@@ -13,7 +13,7 @@
<div className='mx-auto mt-6 max-w-7xl px-6 lg:px-8 dark:bg-boxdark-2'>
<footer
aria-labelledby='footer-heading'
- className='relative border-t border-gray-900/10 dark:border-gray-200/10 py-24 sm:mt-32'
+ className='relative border-t border-gray-900/10 dark:border-gray-100/10 py-24 sm:mt-32'
>
<h2 id='footer-heading' className='sr-only'>
Footer

View File

@ -0,0 +1,59 @@
--- template/app/src/landing-page/components/Header.tsx
+++ opensaas-sh/app/src/landing-page/components/Header.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, useEffect } from 'react';
import { HiBars3 } from 'react-icons/hi2';
import { BiLogIn } from 'react-icons/bi';
import { AiFillCloseCircle } from 'react-icons/ai';
@@ -20,7 +20,7 @@
const { data: user, isLoading: isUserLoading } = useAuth();
- const NavLogo = () => <img className='h-8 w-8' src={logo} alt='Your SaaS App' />;
+ const NavLogo = () => <img className='h-8 w-8' src={logo} alt='Open SaaS App' />;
return (
<header className='absolute inset-x-0 top-0 z-50 dark:bg-boxdark-2'>
@@ -28,10 +28,10 @@
<div className='flex items-center lg:flex-1'>
<a
href='/'
- className='flex items-center -m-1.5 p-1.5 text-gray-900 duration-300 ease-in-out hover:text-yellow-500'
+ className='flex items-center -m-1.5 p-1.5 text-gray-900 duration-300 ease-in-out hover:text-yellow-500 dark:text-white'
>
<NavLogo />
- <span className='ml-2 text-sm font-semibold leading-6 dark:text-white'>Your Saas</span>
+ <span className='ml-2 text-sm font-semibold leading-6 dark:text-white'>Open Saas</span>
</a>
</div>
<div className='flex lg:hidden'>
@@ -57,14 +57,14 @@
</div>
<div className='hidden lg:flex lg:flex-1 lg:justify-end lg:align-end'>
{/* <!-- Dark Mode Toggler --> */}
- <div className='flex items-center gap-3 2xsm:gap-7'>
+ <div className='flex items-center gap-3 2xsm:gap-7 text-sm font-semibold leading-6'>
<ul className='flex justify-center items-center gap-2 2xsm:gap-4'>
<DarkModeSwitcher />
</ul>
{isUserLoading ? null : !user ? (
<Link to='/login'>
- <div className='flex justify-end items-center duration-300 ease-in-out text-gray-900 hover:text-yellow-500 dark:text-white'>
- Log in <BiLogIn size='1.1rem' className='ml-1' />
+ <div className='flex justify-end items-center duration-300 ease-in-out text-gray-900 hover:text-yellow-500 dark:text-white test-sm'>
+ Try the demo App <BiLogIn size='1.1rem' className='ml-1' />
</div>
</Link>
) : (
@@ -107,8 +107,8 @@
<div className='py-6'>
{isUserLoading ? null : !user ? (
<Link to='/login'>
- <div className='flex justify-start items-center duration-300 ease-in-out text-gray-900 hover:text-yellow-500 dark:text-white'>
- Log in <BiLogIn size='1.1rem' className='ml-1' />
+ <div className='flex justify-end items-center duration-300 ease-in-out text-gray-900 hover:text-yellow-500 dark:text-white'>
+ Try the Demo App{' '} <BiLogIn size='1.1rem' className='ml-1' />
</div>
</Link>
) : (

View File

@ -0,0 +1,91 @@
--- template/app/src/landing-page/components/Hero.tsx
+++ opensaas-sh/app/src/landing-page/components/Hero.tsx
@@ -1,7 +1,25 @@
-import openSaasBanner from '../../client/static/open-saas-banner.png';
-import { DocsUrl } from '../../shared/common';
+import { useState, useEffect } from 'react';
+import { AiFillGithub } from 'react-icons/ai';
+import { DocsUrl, GithubUrl } from '../../shared/common';
export default function Hero() {
+ const [repoInfo, setRepoInfo] = useState<null | any>(null);
+
+ useEffect(() => {
+ const fetchRepoInfo = async () => {
+ try {
+ const response = await fetch(
+ 'https://api.github.com/repos/wasp-lang/open-saas'
+ );
+ const data = await response.json();
+ setRepoInfo(data);
+ } catch (error) {
+ console.error('Error fetching repo info', error);
+ }
+ };
+ fetchRepoInfo();
+ }, []);
+
return (
<div className='relative pt-14 w-full '>
<div
@@ -29,30 +47,47 @@
<div className='py-24 sm:py-32'>
<div className='mx-auto max-w-8xl px-6 lg:px-8'>
<div className='lg:mb-18 mx-auto max-w-3xl text-center'>
- <h1 className='text-4xl font-bold text-gray-900 sm:text-6xl dark:text-white'>
- Some <span className='italic'>cool</span> words about your product
+ <h1 className='text-4xl font-bold text-gray-900 sm:text-6xl dark:text-white tracking-tight'>
+ The <span className='italic'>free</span> SaaS template with
+ superpowers
</h1>
- <p className='mt-6 mx-auto max-w-2xl text-lg leading-8 text-gray-600 dark:text-white'>
- With some more exciting words about your product!
+ <p className='mt-6 mx-auto max-w-2xl text-lg leading-8 text-gray-600 dark:text-gray-400'>
+ An open-source, feature-rich, full-stack React + NodeJS
+ template that manages features for you.
</p>
<div className='mt-10 flex items-center justify-center gap-x-6'>
<a
href={DocsUrl}
- className='rounded-md px-3.5 py-2.5 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-200 hover:ring-2 hover:ring-yellow-300 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:text-white'
+ className='rounded-md px-6 py-4 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-200 hover:ring-2 hover:ring-yellow-300 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:text-white'
>
Get Started <span aria-hidden='true'>→</span>
</a>
+ <a
+ href={GithubUrl}
+ className='group relative flex items-center justify-center rounded-md bg-gray-100 px-6 py-4 text-sm font-semibold shadow-sm ring-1 ring-inset ring-gray-200 dark:bg-gray-700 hover:ring-2 hover:ring-yellow-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600'
+ >
+ {/* <AiFillGithub size='1.25rem' className='mr-2' /> */}
+ View the Repo
+ {repoInfo!! && (
+ <>
+ <span className='absolute -top-3 -right-7 inline-flex items-center gap-x-1 rounded-full ring-1 group-hover:ring-2 ring-inset ring-yellow-300 bg-yellow-100 px-2 py-1 text-sm font-medium text-yellow-800'>
+ <AiFillGithub size='1rem' />
+ {repoInfo.stargazers_count}
+ </span>
+ </>
+ )}
+ </a>
</div>
</div>
<div className='mt-14 flow-root sm:mt-14 '>
- <div className='-m-2 rounded-xl lg:-m-4 lg:rounded-2xl lg:p-4'>
- <img
- src={openSaasBanner}
- alt='App screenshot'
- width={2432}
- height={1442}
- className='rounded-md shadow-2xl ring-1 ring-gray-900/10'
- />
+ <div className='-m-2 mx-auto rounded-xl lg:-m-4 lg:rounded-2xl lg:p-4'>
+ <iframe
+ className=' mx-auto w-full md:w-[85%] aspect-[4/3] shadow-2xl'
+ src='https://cards.producthunt.com/cards/posts/436467?v=1'
+ // width={850}
+ // height={689}
+ allowFullScreen
+ ></iframe>
</div>
</div>
</div>

View File

@ -131,7 +131,7 @@
+ {
+ name: 'Tim Skaggs',
+ role: 'Founder @ Antler US',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1486119226771480577/VptdEo6A_400x400.png',
+ avatarSrc: 'https://pbs.twimg.com/profile_images/1802196804236091392/ZG0OE_fO_400x400.jpg',
+ socialUrl: 'https://twitter.com/tskaggs',
+ quote: 'Nearly done with a MVP in 3 days of part-time work... and deployed on Fly.io in 10 minutes.',
+ },

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,24 @@
import AstroLogo from "../logos/AstroLogo";
import OpenAILogo from "../logos/OpenAILogo";
import PrismaLogo from "../logos/PrismaLogo";
import SalesforceLogo from "../logos/SalesforceLogo";
export default function Clients() {
return (
<div className='mt-12 mx-auto max-w-7xl px-6 lg:px-8 flex flex-col items-between gap-y-6'>
<h2 className='mb-6 text-center font-semibold tracking-wide text-gray-500 dark:text-white'>
Built with / Used by:
</h2>
<div className='mx-auto grid max-w-lg grid-cols-2 items-center gap-x-8 gap-y-12 sm:max-w-xl md:grid-cols-4 sm:gap-x-10 sm:gap-y-14 lg:mx-0 lg:max-w-none'>
{
[<SalesforceLogo />, <PrismaLogo />, <AstroLogo />, <OpenAILogo />].map((logo, index) => (
<div key={index} className='flex justify-center col-span-1 max-h-12 w-full object-contain dark:opacity-80'>
{logo}
</div>
))
}
</div>
</div>
)
}

View File

@ -0,0 +1,33 @@
interface FAQ {
id: number;
question: string;
answer: string;
href?: string;
};
export default function FAQ({ faqs }: { faqs: FAQ[] }) {
return (
<div className='mt-32 mx-auto max-w-2xl divide-y divide-gray-900/10 dark:divide-gray-200/10 px-6 pb-8 sm:pb-24 sm:pt-12 lg:max-w-7xl lg:px-8 lg:py-32'>
<h2 className='text-2xl font-bold leading-10 tracking-tight text-gray-900 dark:text-white'>
Frequently asked questions
</h2>
<dl className='mt-10 space-y-8 divide-y divide-gray-900/10'>
{faqs.map((faq) => (
<div key={faq.id} className='pt-8 lg:grid lg:grid-cols-12 lg:gap-8'>
<dt className='text-base font-semibold leading-7 text-gray-900 lg:col-span-5 dark:text-white'>
{faq.question}
</dt>
<dd className='flex items-center justify-start gap-2 mt-4 lg:col-span-7 lg:mt-0'>
<p className='text-base leading-7 text-gray-600 dark:text-white'>{faq.answer}</p>
{faq.href && (
<a href={faq.href} className='text-base leading-7 text-yellow-500 hover:text-yellow-600'>
Learn more
</a>
)}
</dd>
</div>
))}
</dl>
</div>
)
}

View File

@ -0,0 +1,37 @@
interface Feature {
name: string;
description: string;
icon: string;
href: string;
};
export default function Features({ features }: { features: Feature[] }) {
return (
<div id='features' className='mx-auto mt-48 max-w-7xl px-6 lg:px-8'>
<div className='mx-auto max-w-2xl text-center'>
<p className='mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl dark:text-white'>
The <span className='text-yellow-500'>Best</span> Features
</p>
<p className='mt-6 text-lg leading-8 text-gray-600 dark:text-white'>
Don't work harder.
<br /> Work smarter.
</p>
</div>
<div className='mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-4xl'>
<dl className='grid max-w-xl grid-cols-1 gap-x-8 gap-y-10 lg:max-w-none lg:grid-cols-2 lg:gap-y-16'>
{features.map((feature) => (
<div key={feature.name} className='relative pl-16'>
<dt className='text-base font-semibold leading-7 text-gray-900 dark:text-white'>
<div className='absolute left-0 top-0 flex h-10 w-10 items-center justify-center border border-yellow-400 bg-yellow-100/50 dark:bg-boxdark rounded-lg'>
<div className='text-2xl'>{feature.icon}</div>
</div>
{feature.name}
</dt>
<dd className='mt-2 text-base leading-7 text-gray-600 dark:text-white'>{feature.description}</dd>
</div>
))}
</dl>
</div>
</div>
)
}

View File

@ -0,0 +1,50 @@
interface NavigationItem {
name: string;
href: string;
};
export default function Footer({ footerNavigation }: {
footerNavigation: {
app: NavigationItem[]
company: NavigationItem[]
}
}) {
return (
<div className='mx-auto mt-6 max-w-7xl px-6 lg:px-8 dark:bg-boxdark-2'>
<footer
aria-labelledby='footer-heading'
className='relative border-t border-gray-900/10 dark:border-gray-200/10 py-24 sm:mt-32'
>
<h2 id='footer-heading' className='sr-only'>
Footer
</h2>
<div className='flex items-start justify-end mt-10 gap-20'>
<div>
<h3 className='text-sm font-semibold leading-6 text-gray-900 dark:text-white'>App</h3>
<ul role='list' className='mt-6 space-y-4'>
{footerNavigation.app.map((item) => (
<li key={item.name}>
<a href={item.href} className='text-sm leading-6 text-gray-600 hover:text-gray-900 dark:text-white'>
{item.name}
</a>
</li>
))}
</ul>
</div>
<div>
<h3 className='text-sm font-semibold leading-6 text-gray-900 dark:text-white'>Company</h3>
<ul role='list' className='mt-6 space-y-4'>
{footerNavigation.company.map((item) => (
<li key={item.name}>
<a href={item.href} className='text-sm leading-6 text-gray-600 hover:text-gray-900 dark:text-white'>
{item.name}
</a>
</li>
))}
</ul>
</div>
</div>
</footer>
</div>
)
}

View File

@ -0,0 +1,127 @@
import { useState } from 'react';
import { HiBars3 } from 'react-icons/hi2';
import { BiLogIn } from 'react-icons/bi';
import { AiFillCloseCircle } from 'react-icons/ai';
import { Dialog } from '@headlessui/react';
import { Link } from 'wasp/client/router';
import { useAuth } from 'wasp/client/auth';
import logo from '../../client/static/logo.png';
import DarkModeSwitcher from '../../client/components/DarkModeSwitcher';
import DropdownUser from '../../user/DropdownUser';
import { UserMenuItems } from '../../user/UserMenuItems';
interface NavigationItem {
name: string;
href: string;
};
export default function Header({ navigation }: { navigation: NavigationItem[] }) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const { data: user, isLoading: isUserLoading } = useAuth();
const NavLogo = () => <img className='h-8 w-8' src={logo} alt='Your SaaS App' />;
return (
<header className='absolute inset-x-0 top-0 z-50 dark:bg-boxdark-2'>
<nav className='flex items-center justify-between p-6 lg:px-8' aria-label='Global'>
<div className='flex items-center lg:flex-1'>
<a
href='/'
className='flex items-center -m-1.5 p-1.5 text-gray-900 duration-300 ease-in-out hover:text-yellow-500'
>
<NavLogo />
<span className='ml-2 text-sm font-semibold leading-6 dark:text-white'>Your Saas</span>
</a>
</div>
<div className='flex lg:hidden'>
<button
type='button'
className='-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700 dark:text-white'
onClick={() => setMobileMenuOpen(true)}
>
<span className='sr-only'>Open main menu</span>
<HiBars3 className='h-6 w-6' aria-hidden='true' />
</button>
</div>
<div className='hidden lg:flex lg:gap-x-12'>
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className='text-sm font-semibold leading-6 text-gray-900 duration-300 ease-in-out hover:text-yellow-500 dark:text-white'
>
{item.name}
</a>
))}
</div>
<div className='hidden lg:flex lg:flex-1 lg:justify-end lg:align-end'>
{/* <!-- Dark Mode Toggler --> */}
<div className='flex items-center gap-3 2xsm:gap-7'>
<ul className='flex justify-center items-center gap-2 2xsm:gap-4'>
<DarkModeSwitcher />
</ul>
{isUserLoading ? null : !user ? (
<Link to='/login'>
<div className='flex justify-end items-center duration-300 ease-in-out text-gray-900 hover:text-yellow-500 dark:text-white'>
Log in <BiLogIn size='1.1rem' className='ml-1' />
</div>
</Link>
) : (
<DropdownUser user={user} />
)}
</div>
</div>
</nav>
<Dialog as='div' className='lg:hidden' open={mobileMenuOpen} onClose={setMobileMenuOpen}>
<div className='fixed inset-0 z-50' />
<Dialog.Panel className='fixed inset-y-0 right-0 z-50 w-full overflow-y-auto bg-white px-6 py-6 sm:max-w-sm sm:ring-1 sm:ring-gray-900/10 dark:bg-boxdark dark:text-white'>
<div className='flex items-center justify-between'>
<a href='/' className='-m-1.5 p-1.5'>
<span className='sr-only'>Your SaaS</span>
<NavLogo />
</a>
<button
type='button'
className='-m-2.5 rounded-md p-2.5 text-gray-700 dark:text-gray-50'
onClick={() => setMobileMenuOpen(false)}
>
<span className='sr-only'>Close menu</span>
<AiFillCloseCircle className='h-6 w-6' aria-hidden='true' />
</button>
</div>
<div className='mt-6 flow-root'>
<div className='-my-6 divide-y divide-gray-500/10'>
<div className='space-y-2 py-6'>
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
onClick={() => setMobileMenuOpen(false)}
className='-mx-3 block rounded-lg px-3 py-2 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50 dark:text-white dark:hover:bg-boxdark-2'
>
{item.name}
</a>
))}
</div>
<div className='py-6'>
{isUserLoading ? null : !user ? (
<Link to='/login'>
<div className='flex justify-start items-center duration-300 ease-in-out text-gray-900 hover:text-yellow-500 dark:text-white'>
Log in <BiLogIn size='1.1rem' className='ml-1' />
</div>
</Link>
) : (
<UserMenuItems user={user} />
)}
</div>
<div className='py-6'>
<DarkModeSwitcher />
</div>
</div>
</div>
</Dialog.Panel>
</Dialog>
</header>
)
}

View File

@ -0,0 +1,62 @@
import openSaasBanner from '../../client/static/open-saas-banner.png';
import { DocsUrl } from '../../shared/common';
export default function Hero() {
return (
<div className='relative pt-14 w-full '>
<div
className='absolute top-0 right-0 -z-10 transform-gpu overflow-hidden w-full blur-3xl sm:top-0 '
aria-hidden='true'
>
<div
className='aspect-[1020/880] w-[55rem] flex-none sm:right-1/4 sm:translate-x-1/2 dark:hidden bg-gradient-to-tr from-amber-400 to-purple-300 opacity-40'
style={{
clipPath: 'polygon(80% 20%, 90% 55%, 50% 100%, 70% 30%, 20% 50%, 50% 0)',
}}
/>
</div>
<div
className='absolute inset-x-0 top-[calc(100%-40rem)] sm:top-[calc(100%-65rem)] -z-10 transform-gpu overflow-hidden blur-3xl'
aria-hidden='true'
>
<div
className='relative aspect-[1020/880] sm:-left-3/4 sm:translate-x-1/4 dark:hidden bg-gradient-to-br from-amber-400 to-purple-300 opacity-50 w-[72.1875rem]'
style={{
clipPath: 'ellipse(80% 30% at 80% 50%)',
}}
/>
</div>
<div className='py-24 sm:py-32'>
<div className='mx-auto max-w-8xl px-6 lg:px-8'>
<div className='lg:mb-18 mx-auto max-w-3xl text-center'>
<h1 className='text-4xl font-bold text-gray-900 sm:text-6xl dark:text-white'>
Some <span className='italic'>cool</span> words about your product
</h1>
<p className='mt-6 mx-auto max-w-2xl text-lg leading-8 text-gray-600 dark:text-white'>
With some more exciting words about your product!
</p>
<div className='mt-10 flex items-center justify-center gap-x-6'>
<a
href={DocsUrl}
className='rounded-md px-3.5 py-2.5 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-200 hover:ring-2 hover:ring-yellow-300 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:text-white'
>
Get Started <span aria-hidden='true'></span>
</a>
</div>
</div>
<div className='mt-14 flow-root sm:mt-14 '>
<div className='-m-2 rounded-xl lg:-m-4 lg:rounded-2xl lg:p-4'>
<img
src={openSaasBanner}
alt='App screenshot'
width={2432}
height={1442}
className='rounded-md shadow-2xl ring-1 ring-gray-900/10'
/>
</div>
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,39 @@
interface Testimonial {
name: string;
role: string;
avatarSrc: string;
socialUrl: string;
quote: string;
};
export default function Testimonials({ testimonials }: { testimonials: Testimonial[] }) {
return (
<div className='mx-auto mt-32 max-w-7xl sm:mt-56 sm:px-6 lg:px-8'>
<div className='relative sm:left-5 -m-2 rounded-xl bg-yellow-400/20 lg:ring-1 lg:ring-yellow-500/50 lg:-m-4 '>
<div className='relative sm:top-5 sm:right-5 bg-gray-900 dark:bg-boxdark px-8 py-20 shadow-xl sm:rounded-xl sm:px-10 sm:py-16 md:px-12 lg:px-20'>
<h2 className='text-left text-xl font-semibold tracking-wide leading-7 text-gray-500 dark:text-white'>
What Our Users Say
</h2>
<div className='relative flex flex-wrap gap-6 w-full mt-6 z-10 justify-between lg:mx-0'>
{testimonials.map((testimonial, idx) => (
<figure key={idx} className='w-full lg:w-1/4 box-content flex flex-col justify-between p-8 rounded-xl bg-gray-500/5 '>
<blockquote className='text-lg text-white sm:text-md sm:leading-8'>
<p>{testimonial.quote}</p>
</blockquote>
<figcaption className='mt-6 text-base text-white'>
<a href={testimonial.socialUrl} className='flex items-center gap-x-2'>
<img src={testimonial.avatarSrc} className='h-12 w-12 rounded-full' />
<div>
<div className='font-semibold hover:underline'>{testimonial.name}</div>
<div className='mt-1'>{testimonial.role}</div>
</div>
</a>
</figcaption>
</figure>
))}
</div>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,16 @@
export default function AstroLogo() {
return (
<svg viewBox='0 0 256 370' xmlns='http://www.w3.org/2000/svg'>
<path
className='dark:fill-white'
fill='#545454'
d='M182.022 9.147c2.982 3.702 4.502 8.697 7.543 18.687L256 246.074a276.467 276.467 0 0 0-79.426-26.891L133.318 73.008a5.63 5.63 0 0 0-10.802.017L79.784 219.11A276.453 276.453 0 0 0 0 246.04L66.76 27.783c3.051-9.972 4.577-14.959 7.559-18.654a24.541 24.541 0 0 1 9.946-7.358C88.67 0 93.885 0 104.314 0h47.683c10.443 0 15.664 0 20.074 1.774a24.545 24.545 0 0 1 9.95 7.373Z'
/>
<path
className='dark:fill-white'
fill='#545454'
d='M189.972 256.46c-10.952 9.364-32.812 15.751-57.992 15.751-30.904 0-56.807-9.621-63.68-22.56-2.458 7.415-3.009 15.903-3.009 21.324 0 0-1.619 26.623 16.898 45.14 0-9.615 7.795-17.41 17.41-17.41 16.48 0 16.46 14.378 16.446 26.043l-.001 1.041c0 17.705 10.82 32.883 26.21 39.28a35.685 35.685 0 0 1-3.588-15.647c0-16.886 9.913-23.173 21.435-30.48 9.167-5.814 19.353-12.274 26.372-25.232a47.588 47.588 0 0 0 5.742-22.735c0-5.06-.786-9.938-2.243-14.516Z'
/>
</svg>
)
}

View File

@ -0,0 +1,11 @@
export default function OpenAILogo() {
return (
<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMidYMid' viewBox='0 0 256 260'>
<path
className='dark:fill-white'
fill='#545454'
d='M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483Zm-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87 51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601Zm-104.466-44.61a48.345 48.345 0 0 1-5.781-32.589l1.534.921 51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803ZM23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405v.256Zm179.466 41.695-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213Zm21.742-32.69-1.535-.922-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391v.205ZM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367l-.051 72.697Zm11.868-25.58 28.138-16.217 28.188 16.218v32.434l-28.086 16.218-28.188-16.218-.052-32.434Z'
/>
</svg>
)
}

View File

@ -0,0 +1,11 @@
export default function PrismaLogo() {
return (
<svg width={48} height={48} viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'>
<path
className='dark:fill-white'
fill='#545454'
d='M25.21,24.21,12.739,27.928a.525.525,0,0,1-.667-.606L16.528,5.811a.43.43,0,0,1,.809-.094l8.249,17.661A.6.6,0,0,1,25.21,24.21Zm2.139-.878L17.8,2.883h0A1.531,1.531,0,0,0,16.491,2a1.513,1.513,0,0,0-1.4.729L4.736,19.648a1.592,1.592,0,0,0,.018,1.7l5.064,7.909a1.628,1.628,0,0,0,1.83.678l14.7-4.383a1.6,1.6,0,0,0,1-2.218Z'
/>
</svg>
)
}

File diff suppressed because one or more lines are too long