add dropdown to navbars

This commit is contained in:
vincanger
2023-11-15 11:48:34 +01:00
parent 1a999bb257
commit 9a37ece4b5
10 changed files with 183 additions and 221 deletions

View File

@@ -142,12 +142,7 @@ psl=}
* https://wasp-lang.dev/docs/tutorial/pages
*/
route RootRoute { path: "/", to: MainPage }
page MainPage {
component: import Main from "@client/MainPage"
}
route LandingPageRoute { path: "/landing-page", to: LandingPage }
route LandingPageRoute { path: "/", to: LandingPage }
page LandingPage {
component: import LandingPage from "@client/landing-page/LandingPage"
}

View File

@@ -16,7 +16,7 @@ export default function App({ children }: { children: ReactNode }) {
const [referrer, setReferrer] = useReferrer();
const shouldDisplayAppNavBar = useMemo(() => {
return !location.pathname.startsWith('/landing-page');
return location.pathname !== '/';
}, [location]);
const isAdminDashboard = useMemo(() => {

View File

@@ -1,95 +1,65 @@
import { Disclosure } from '@headlessui/react';
import { useState } from 'react';
import { AiOutlineBars, AiOutlineClose, AiOutlineUser } from 'react-icons/ai';
import { BiLogIn } from 'react-icons/bi';
import { HiBars3 } from 'react-icons/hi2';
import useAuth from '@wasp/auth/useAuth';
import logo from './static/logo.png'
import logo from './static/logo.png';
import DropdownUser from './common/DropdownUser';
const active = 'inline-flex items-center border-b-2 border-indigo-300 px-1 pt-1 text-sm font-medium text-gray-900';
const inactive = 'inline-flex items-center border-b-2 border-transparent px-1 pt-1 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700'
const current = window.location.pathname;
const navigation = [
{ name: 'GPT Wrapper', href: '/gpt' },
{ name: 'Documentation', href: 'https://saas-template.gitbook.io/test' },
{ name: 'Blog', href: 'https://saas-template.gitbook.io/posts/' },
];
export default function NavBar() {
const { data: user } = useAuth();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const { data: user, isLoading: isUserLoading } = useAuth();
return (
<Disclosure as='nav' className='bg-white shadow sticky top-0 z-50 '>
{({ open }) => (
<>
<div className='mx-auto max-w-7xl px-4 sm:px-6 lg:px-16'>
<div className='flex h-16 justify-between'>
<div className='flex'>
<div className='flex flex-shrink-0 items-center'>
<a href='/'>
<img className='h-8 w-8' src={logo} alt='My SaaS App' />
</a>
</div>
<div className='hidden sm:ml-6 sm:flex sm:space-x-8'>
<a href='/' className={current === '/' ? active : inactive}>
Landing Page
</a>
<a href='/pricing' className={current.includes('pricing') ? active : inactive}>
Pricing
</a>
<a href='/gpt' className={current.includes('gpt') ? active : inactive}>
GPT
</a>
</div>
<header className='absolute inset-x-0 top-0 z-50 shadow sticky bg-white bg-opacity-50 backdrop-blur-lg backdrop-filter'>
<nav className='flex items-center justify-between p-6 lg:px-8' aria-label='Global'>
<div className='flex lg:flex-1'>
<a href='/' className='-m-1.5 p-1.5'>
<img className='h-8 w-8' src={logo} alt='My SaaS App' />
</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'
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'
>
{item.name}
</a>
))}
</div>
<div className='hidden lg:flex lg:flex-1 lg:justify-end lg:align-end'>
<a
href={!user ? '/login' : '/account'}
className='flex justify-end items-center text-sm font-semibold leading-6 '
>
{isUserLoading ? null : !user ? (
<div className='duration-300 ease-in-out text-gray-900 hover:text-yellow-500'>
Log in <BiLogIn size='1.1rem' className='ml-1 mt-[0.1rem]' />
</div>
<div className='hidden sm:ml-6 sm:flex sm:space-x-8'>
<a href={!!user ? '/account' : '/login'} className={current === '/account' ? active : inactive}>
<AiOutlineUser className='h-6 w-6 mr-2' />
Account
</a>
</div>
<div className='-mr-2 flex items-center sm:hidden'>
{/* Mobile menu */}
<Disclosure.Button className='inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-300'>
<span className='sr-only'>Open menu</span>
{open ? (
<AiOutlineClose className='block h-6 w-6' aria-hidden='true' />
) : (
<AiOutlineBars className='block h-6 w-6' aria-hidden='true' />
)}
</Disclosure.Button>
</div>
</div>
</div>
<Disclosure.Panel className='sm:hidden'>
<div className='space-y-1 pt-2 pb-3'>
<Disclosure.Button
as='a'
href='/'
className='block border-l-4 border-indigo-300 bg-indigo-50 py-2 pl-3 pr-4 text-base font-medium text-indigo-500'
>
Landing Page
</Disclosure.Button>
<Disclosure.Button
as='a'
href='/pricing'
className='block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-500 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-700'
>
Pricing
</Disclosure.Button>
<Disclosure.Button
as='a'
href='/gpt'
className='block border-l-4 border-transparent py-2 pl-3 pr-4 text-base font-medium text-gray-500 hover:border-gray-300 hover:bg-gray-50 hover:text-gray-700'
>
GPT
</Disclosure.Button>
<Disclosure.Button
as='a'
href='/account'
className='block px-4 py-2 text-base font-medium text-gray-500 hover:bg-gray-100 hover:text-gray-800'
>
Account
</Disclosure.Button>
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
) : (
<DropdownUser username={user.email?.split('@')[0]} />
)}
</a>
</div>
</nav>
</header>
);
}

View File

@@ -1,28 +1,38 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import Logo from '../images/logo/logo-icon.svg';
import DarkModeSwitcher from './DarkModeSwitcher';
import DropdownMessage from './DropdownMessage';
import DropdownUser from './DropdownUser';
import DropdownUser from '../../common/DropdownUser';
import type { User } from '@wasp/entities'
const Header = (props: {
sidebarOpen: string | boolean | undefined;
setSidebarOpen: (arg0: boolean) => void;
user?: Omit<User, 'password'> | null | undefined;
}) => {
// const [username, setUsername] = useState<string | undefined>(undefined)
// useEffect(() => {
// if (props.user) {
// setUsername(props.user?.email?.split('@')[0])
// }
// }, [props.user])
return (
<header className="sticky top-0 z-999 flex w-full bg-white drop-shadow-1 dark:bg-boxdark dark:drop-shadow-none">
<div className="flex flex-grow items-center justify-between py-4 px-4 shadow-2 md:px-6 2xl:px-11">
<div className="flex items-center gap-2 sm:gap-4 lg:hidden">
<header className='sticky top-0 z-999 flex w-full bg-white dark:bg-boxdark dark:drop-shadow-none'>
<div className='flex flex-grow items-center justify-between px-8 py-5 shadow '>
<div className='flex items-center gap-2 sm:gap-4 lg:hidden'>
{/* <!-- Hamburger Toggle BTN --> */}
<button
aria-controls="sidebar"
aria-controls='sidebar'
onClick={(e) => {
e.stopPropagation();
props.setSidebarOpen(!props.sidebarOpen);
}}
className="z-99999 block rounded-sm border border-stroke bg-white p-1.5 shadow-sm dark:border-strokedark dark:bg-boxdark lg:hidden"
className='z-99999 block rounded-sm border border-stroke bg-white p-1.5 shadow-sm dark:border-strokedark dark:bg-boxdark lg:hidden'
>
<span className="relative block h-5.5 w-5.5 cursor-pointer">
<span className="du-block absolute right-0 h-full w-full">
<span className='relative block h-5.5 w-5.5 cursor-pointer'>
<span className='du-block absolute right-0 h-full w-full'>
<span
className={`relative top-0 left-0 my-1 block h-0.5 w-0 rounded-sm bg-black delay-[0] duration-200 ease-in-out dark:bg-white ${
!props.sidebarOpen && '!w-full delay-300'
@@ -39,7 +49,7 @@ const Header = (props: {
}`}
></span>
</span>
<span className="absolute right-0 h-full w-full rotate-45">
<span className='absolute right-0 h-full w-full rotate-45'>
<span
className={`absolute left-2.5 top-0 block h-full w-0.5 rounded-sm bg-black delay-300 duration-200 ease-in-out dark:bg-white ${
!props.sidebarOpen && '!h-0 !delay-[0]'
@@ -55,49 +65,49 @@ const Header = (props: {
</button>
{/* <!-- Hamburger Toggle BTN --> */}
<Link className="block flex-shrink-0 lg:hidden" to="/">
<img src={Logo} alt="Logo" />
<Link className='block flex-shrink-0 lg:hidden' to='/'>
<img src={Logo} alt='Logo' />
</Link>
</div>
<div className="hidden sm:block">
<form action="https://formbold.com/s/unique_form_id" method="POST">
<div className="relative">
<button className="absolute top-1/2 left-0 -translate-y-1/2">
<div className='hidden sm:block'>
<form action='https://formbold.com/s/unique_form_id' method='POST'>
<div className='relative'>
<button className='absolute top-1/2 left-0 -translate-y-1/2'>
<svg
className="fill-body hover:fill-primary dark:fill-bodydark dark:hover:fill-primary"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className='fill-body hover:fill-primary dark:fill-bodydark dark:hover:fill-primary'
width='20'
height='20'
viewBox='0 0 20 20'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.16666 3.33332C5.945 3.33332 3.33332 5.945 3.33332 9.16666C3.33332 12.3883 5.945 15 9.16666 15C12.3883 15 15 12.3883 15 9.16666C15 5.945 12.3883 3.33332 9.16666 3.33332ZM1.66666 9.16666C1.66666 5.02452 5.02452 1.66666 9.16666 1.66666C13.3088 1.66666 16.6667 5.02452 16.6667 9.16666C16.6667 13.3088 13.3088 16.6667 9.16666 16.6667C5.02452 16.6667 1.66666 13.3088 1.66666 9.16666Z"
fill=""
fillRule='evenodd'
clipRule='evenodd'
d='M9.16666 3.33332C5.945 3.33332 3.33332 5.945 3.33332 9.16666C3.33332 12.3883 5.945 15 9.16666 15C12.3883 15 15 12.3883 15 9.16666C15 5.945 12.3883 3.33332 9.16666 3.33332ZM1.66666 9.16666C1.66666 5.02452 5.02452 1.66666 9.16666 1.66666C13.3088 1.66666 16.6667 5.02452 16.6667 9.16666C16.6667 13.3088 13.3088 16.6667 9.16666 16.6667C5.02452 16.6667 1.66666 13.3088 1.66666 9.16666Z'
fill=''
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.2857 13.2857C13.6112 12.9603 14.1388 12.9603 14.4642 13.2857L18.0892 16.9107C18.4147 17.2362 18.4147 17.7638 18.0892 18.0892C17.7638 18.4147 17.2362 18.4147 16.9107 18.0892L13.2857 14.4642C12.9603 14.1388 12.9603 13.6112 13.2857 13.2857Z"
fill=""
fillRule='evenodd'
clipRule='evenodd'
d='M13.2857 13.2857C13.6112 12.9603 14.1388 12.9603 14.4642 13.2857L18.0892 16.9107C18.4147 17.2362 18.4147 17.7638 18.0892 18.0892C17.7638 18.4147 17.2362 18.4147 16.9107 18.0892L13.2857 14.4642C12.9603 14.1388 12.9603 13.6112 13.2857 13.2857Z'
fill=''
/>
</svg>
</button>
<input
type="text"
placeholder="Type to search..."
className="w-full bg-transparent pr-4 pl-9 focus:outline-none"
type='text'
placeholder='Type to search...'
className='w-full bg-transparent pr-4 pl-9 focus:outline-none'
/>
</div>
</form>
</div>
<div className="flex items-center gap-3 2xsm:gap-7">
<ul className="flex items-center gap-2 2xsm:gap-4">
<div className='flex items-center gap-3 2xsm:gap-7'>
<ul className='flex items-center gap-2 2xsm:gap-4'>
{/* <!-- Dark Mode Toggler --> */}
<DarkModeSwitcher />
{/* <!-- Dark Mode Toggler --> */}
@@ -108,7 +118,7 @@ const Header = (props: {
</ul>
{/* <!-- User Area --> */}
<DropdownUser />
<DropdownUser username={props.user?.email?.split('@')[0]} />
{/* <!-- User Area --> */}
</div>
</div>

View File

@@ -109,7 +109,7 @@ interface ChartOneState {
}[];
}
const DailyActiveUsersChart = ({ weeklyStats, isLoading }: DailyStatsProps) => {
const RevenueAndProfitChart = ({ weeklyStats, isLoading }: DailyStatsProps) => {
const dailyRevenueArray = useMemo(() => {
if (!!weeklyStats && weeklyStats?.length > 0) {
const sortedWeeks = weeklyStats?.sort((a, b) => {
@@ -235,4 +235,4 @@ const DailyActiveUsersChart = ({ weeklyStats, isLoading }: DailyStatsProps) => {
);
};
export default DailyActiveUsersChart;
export default RevenueAndProfitChart;

View File

@@ -1,6 +1,7 @@
import { useState, ReactNode, FC } from 'react';
import Header from '../components/Header';
import Sidebar from '../components/Sidebar';
import useAuth from '@wasp/auth/useAuth';
interface Props {
children?: ReactNode;
@@ -8,6 +9,7 @@ interface Props {
const DefaultLayout: FC<Props> = ({ children }) => {
const [sidebarOpen, setSidebarOpen] = useState(false);
const { data: user } = useAuth();
return (
<div className="dark:bg-boxdark-2 dark:text-bodydark">
@@ -20,7 +22,7 @@ const DefaultLayout: FC<Props> = ({ children }) => {
{/* <!-- ===== Content Area Start ===== --> */}
<div className="relative flex flex-1 flex-col overflow-y-auto overflow-x-hidden">
{/* <!-- ===== Header Start ===== --> */}
<Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} />
<Header sidebarOpen={sidebarOpen} setSidebarOpen={setSidebarOpen} user={user} />
{/* <!-- ===== Header End ===== --> */}
{/* <!-- ===== Main Content Start ===== --> */}

View File

@@ -2,7 +2,7 @@ import TotalSignupsCard from '../../components/TotalSignupsCard';
import TotalPageViewsCard from '../../components/TotalPaidViewsCard';
import TotalPayingUsersCard from '../../components/TotalPayingUsersCard';
import TotalRevenueCard from '../../components/TotalRevenueCard';
import DailyActiveUsersChart from '../../components/DailyActiveUsersChart';
import RevenueAndProfitChart from '../../components/RevenueAndProfitChart';
import ReferrerTable from '../../components/ReferrerTable';
import DefaultLayout from '../../layout/DefaultLayout';
import { useQuery } from '@wasp/queries';
@@ -12,7 +12,7 @@ const ECommerce = () => {
const { data: stats, isLoading, error } = useQuery(getDailyStats);
return (
<DefaultLayout>
<DefaultLayout >
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 xl:grid-cols-4 2xl:gap-7.5'>
<TotalPageViewsCard />
<TotalRevenueCard dailyStats={stats?.dailyStats} weeklyStats={stats?.weeklyStats} isLoading={isLoading}/>
@@ -21,7 +21,7 @@ const ECommerce = () => {
</div>
<div className='mt-4 grid grid-cols-12 gap-4 md:mt-6 md:gap-6 2xl:mt-7.5 2xl:gap-7.5'>
<DailyActiveUsersChart weeklyStats={stats?.weeklyStats} isLoading={isLoading} />
<RevenueAndProfitChart weeklyStats={stats?.weeklyStats} isLoading={isLoading} />
<div className='col-span-12 xl:col-span-8'>
<ReferrerTable />

View File

@@ -6,6 +6,7 @@ import DefaultLayout from '../layout/DefaultLayout';
const Settings = () => {
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
// TODO add toast provider / wrapper
event.preventDefault();
const confirmed = confirm("Are you sure you want to save the changes?");
if (confirmed) {

File diff suppressed because one or more lines are too long

View File

@@ -3,16 +3,16 @@ import { Dialog } from '@headlessui/react';
import { AiFillCheckCircle, AiFillCloseCircle } from 'react-icons/ai';
import { HiBars3 } from 'react-icons/hi2';
import { BiLogIn } from 'react-icons/bi';
import { CgProfile } from 'react-icons/cg';
import logo from '../static/logo.png';
import daBoi from '../static/magic-app-gen-logo.png';
import { features, navigation, tiers, faqs, footerNavigation } from './contentSections';
import useAuth from '@wasp/auth/useAuth';
import DropdownUser from '../common/DropdownUser';
export default function LandingPage() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const { data: user } = useAuth();
const { data: user, isLoading: isUserLoading } = useAuth();
return (
<div className='bg-white'>
@@ -39,7 +39,7 @@ export default function LandingPage() {
<a
key={item.name}
href={item.href}
className='text-sm font-semibold leading-6 text-gray-900 hover:text-yellow-500'
className='text-sm font-semibold leading-6 text-gray-900 duration-300 ease-in-out hover:text-yellow-500'
>
{item.name}
</a>
@@ -48,17 +48,14 @@ export default function LandingPage() {
<div className='hidden lg:flex lg:flex-1 lg:justify-end lg:align-end'>
<a
href={!user ? '/login' : '/account'}
className='flex justify-end items-center text-sm font-semibold leading-6 text-gray-900 hover:text-yellow-500'
className='flex justify-end items-center text-sm font-semibold leading-6 '
>
{!user ? (
<>
{isUserLoading ? null : !user ? (
<div className='duration-300 ease-in-out text-gray-900 hover:text-yellow-500'>
Log in <BiLogIn size='1.1rem' className='ml-1 mt-[0.1rem]' />
</>
</div>
) : (
<>
{user.email?.split('@')[0]}
<CgProfile size='1.1rem' className='ml-1 mt-[0.1rem]' />
</>
<DropdownUser username={user.email?.split('@')[0]} />
)}
</a>
</div>
@@ -100,7 +97,7 @@ export default function LandingPage() {
<div className='py-6'>
<a
href='#'
className='-mx-3 block rounded-lg px-3 py-2.5 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-50'
className='-mx-3 block rounded-lg px-3 py-2.5 text-base font-semibold leading-7 text-gray-900 duration-300 ease-in-out hover:bg-gray-50'
>
Log in
</a>
@@ -168,31 +165,8 @@ export default function LandingPage() {
</div>
</div>
</div>
{/* <div
className='absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-45rem)]'
aria-hidden='true'
>
<div
className='relative aspect-[1020/880] left-3/4 -translate-x-1/4 bg-gradient-to-tr from-yellow-400 to-amber-300 opacity-50 w-[72.1875rem]'
style={{
clipPath: 'ellipse(80% 25% at 30% 40%)',
}}
/>
</div> */}
{/* <div
className='absolute inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]'
aria-hidden='true'
>
<div
className='relative left-[calc(50%+3rem)] aspect-[1155/678] w-[36.125rem] -translate-x-1/2 bg-gradient-to-tr from-[#ff80b5] to-[#9089fc] opacity-30 sm:left-[calc(50%+36rem)] sm:w-[72.1875rem]'
style={{
clipPath:
'polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)',
}}
/>
</div> */}
</div>
{/* Logo cloud section */}
<div className='mt-12 mx-auto max-w-7xl px-6 lg:px-8 flex flex-col items-between gap-y-6'>