Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions public/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 7 additions & 4 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Container } from '@mui/material';
import HeroSection from '../components/features/landing/HeroSection';

import Navbar from '../components/features/landing/Navbar';

export default function LandingPage() {
return (
<Container>
<HeroSection />
</Container>
<>
<Navbar />
<Container>
<HeroSection />
</Container>
</>
);
}
157 changes: 157 additions & 0 deletions src/components/features/landing/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
'use client';

import Image from 'next/image';
import Link from 'next/link';
import { useState } from 'react';
import {
AppBar,
Toolbar,
Box,
Stack,
IconButton,
Drawer,
useMediaQuery,
useTheme,
} from '@mui/material';
import { styled } from '@mui/material/styles';
import MenuIcon from '@mui/icons-material/Menu';
import CloseIcon from '@mui/icons-material/Close';
import { NavItemProps } from './navbar/NavItem';
import { MobileDrawer } from './navbar/MobileDrawer';
import { DesktopNavItems } from './navbar/DesktopNavItems';
import { AuthButton } from './navbar/AuthButton';

const navItems: NavItemProps[] = [
{ href: '/home', text: 'Home', width: 75, textWidth: 43 },
{ href: '/products', text: 'Products', width: 98, textWidth: 66 },
{ href: '/pricing', text: 'Pricing', width: 83, textWidth: 51 },
{ href: '/blogs', text: 'Blogs', width: 73, textWidth: 41 },
{ href: '/features', text: 'Features', width: 95, textWidth: 63 },
{ href: '/about', text: 'About Us', width: 98, textWidth: 66 },
];

const StyledAppBar = styled(AppBar)(({ theme }) => ({
position: 'fixed',
height: 80,
backgroundColor: theme.palette.background.default,
marginBottom: '100px',
zIndex: theme.zIndex.drawer + 1,
}));

const StyledToolbar = styled(Toolbar)(({ theme }) => ({
width: '100%',
maxWidth: 1920,
padding: '20px 20px',
margin: '0 auto',
[theme.breakpoints.up('md')]: {
paddingLeft: 240,
paddingRight: 240,
},
}));

const LogoBox = styled(Box)({
display: 'flex',
alignItems: 'center',
margin: '2px 0',
});

const DesktopButtonGroup = styled(Stack)({
alignItems: 'center',
marginLeft: 'auto',
});

const MobileMenuButton = styled(IconButton)(({ theme }) => ({
marginLeft: 'auto',
transition: 'transform 0.3s ease',
backgroundColor: theme.palette.background.paper,
borderRadius: 12,
width: 40,
height: 40,
'&:hover': { backgroundColor: theme.palette.background.paper },
}));

const StyledDrawer = styled(Drawer)(({ theme }) => ({
display: 'block',
[theme.breakpoints.up('md')]: { display: 'none' },
'& .MuiDrawer-paper': {
backgroundColor: theme.palette.background.default,
boxSizing: 'border-box',
width: 240,
padding: 20,
transition: 'transform 0.3s ease-in-out',
},
}));

export default function Navbar() {
const [mobileOpen, setMobileOpen] = useState(false);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));

const handleDrawerToggle = () => {
setMobileOpen(!mobileOpen);
};

return (
<StyledAppBar color="transparent" elevation={0}>
<StyledToolbar disableGutters>
{/* Logo */}
<LogoBox>
<Link href="/" aria-label="Dispatch AI Home">
<Image
src="/logo.svg"
alt="Dispatch AI logo"
width={152}
height={36}
priority
style={{ cursor: 'pointer', display: 'block' }}
/>
</Link>
</LogoBox>

{/* Desktop */}
{!isMobile && (
<>
<DesktopNavItems navItems={navItems} />
<DesktopButtonGroup direction="row" spacing={0}>
<AuthButton variant="login" />
<AuthButton variant="signup" />
</DesktopButtonGroup>
</>
)}

{/* Mobile */}
{isMobile && (
<MobileMenuButton
color="inherit"
aria-label="toggle drawer"
edge="start"
onClick={handleDrawerToggle}
style={{
transform: mobileOpen ? 'rotate(90deg)' : 'rotate(0deg)',
}}
>
{mobileOpen ? (
<CloseIcon fontSize="medium" />
) : (
<MenuIcon fontSize="medium" />
)}
</MobileMenuButton>
)}
</StyledToolbar>

{/* Mobile Drawer */}
<StyledDrawer
variant="temporary"
anchor="right"
open={mobileOpen}
onClose={handleDrawerToggle}
ModalProps={{ keepMounted: true }}
>
<MobileDrawer
handleDrawerToggle={handleDrawerToggle}
navItems={navItems}
/>
</StyledDrawer>
</StyledAppBar>
);
}
78 changes: 78 additions & 0 deletions src/components/features/landing/navbar/AuthButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use client';

import CommonButton from '@/components/ui/CommonButton';
import { styled } from '@mui/material/styles';

interface AuthButtonProps {
variant: 'login' | 'signup';
isMobile?: boolean;
onClick?: () => void;
}

const BaseAuthButton = styled(CommonButton, {
shouldForwardProp: (prop) => prop !== 'isMobile',
})<{ isMobile?: boolean }>({
'&&': { fontSize: 16 },
});

const LoginButton = styled(BaseAuthButton, {
shouldForwardProp: (prop) => prop !== 'isMobile',
})<{ isMobile?: boolean }>(({ theme, isMobile }) => ({
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
boxShadow: 'none',
border: 'none',
'&:hover': { backgroundColor: theme.palette.background.paper },

...(isMobile
? {
padding: theme.spacing(2),
borderRadius: 12,
fontWeight: 'bold',
textTransform: 'none',
}
: {
width: theme.spacing(9.125),
height: theme.spacing(5),
margin: '0 -2px',
}),
}));

const SignupButton = styled(BaseAuthButton, {
shouldForwardProp: (prop) => prop !== 'isMobile',
})<{ isMobile?: boolean }>(({ theme, isMobile }) => ({
whiteSpace: 'nowrap',

...(isMobile
? {
padding: theme.spacing(2),
borderRadius: 12,
fontWeight: 'bold',
textTransform: 'none',
}
: {
width: theme.spacing(11.125),
height: theme.spacing(5),
marginLeft: theme.spacing(1.5),
}),
}));

export function AuthButton({
variant,
isMobile = false,
onClick,
}: AuthButtonProps) {
const isLogin = variant === 'login';
const Btn = isLogin ? LoginButton : SignupButton;

return (
<Btn
buttonVariant={isLogin ? undefined : 'black'}
href={`/${variant}`}
isMobile={isMobile}
onClick={onClick}
>
{isLogin ? 'Login' : 'Sign Up'}
</Btn>
);
}
33 changes: 33 additions & 0 deletions src/components/features/landing/navbar/DesktopNavItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
'use client';

import { Stack } from '@mui/material';
import { styled } from '@mui/material/styles';
import { NavItem, NavItemProps } from './NavItem';

interface DesktopNavItemsProps {
navItems: NavItemProps[];
}

const DesktopNavContainer = styled(Stack)(({ theme }) => ({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginLeft: theme.spacing(3.5),
}));

export function DesktopNavItems({ navItems }: DesktopNavItemsProps) {
return (
<DesktopNavContainer direction="row" spacing={0}>
{navItems.map((item) => (
<NavItem
key={
typeof item.href === 'string'
? item.href
: item.href.toString()
}
{...item}
/>
))}
</DesktopNavContainer>
);
}
58 changes: 58 additions & 0 deletions src/components/features/landing/navbar/MobileDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use client';

import { Box, List, ListItem } from '@mui/material';
import { styled } from '@mui/material/styles';
import { NavItem, NavItemProps } from './NavItem';
import { AuthButton } from './AuthButton';

interface MobileDrawerProps {
handleDrawerToggle: () => void;
navItems: NavItemProps[];
}

const DrawerContainer = styled(Box)({
textAlign: 'center',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
paddingTop: 48,
paddingBottom: 48,
});

const ActionArea = styled(Box)({
display: 'flex',
flexDirection: 'column',
gap: 16,
alignItems: 'center',
marginTop: 32,
});

export function MobileDrawer({
handleDrawerToggle,
navItems,
}: MobileDrawerProps) {
return (
<DrawerContainer>
<List sx={{ flexGrow: 1 }}>
{navItems.map((item) => (
<ListItem
key={
typeof item.href === 'string'
? item.href
: item.href.toString()
}
sx={{ justifyContent: 'center', mb: 1.5 }}
>
<NavItem {...item} handleDrawerToggle={handleDrawerToggle} />
</ListItem>
))}
</List>

<ActionArea>
<AuthButton variant="login" isMobile onClick={handleDrawerToggle} />
<AuthButton variant="signup" isMobile onClick={handleDrawerToggle} />
</ActionArea>
</DrawerContainer>
);
}
Loading