diff --git a/public/icons/coin.svg b/public/icons/coin.svg new file mode 100644 index 0000000..70181ae --- /dev/null +++ b/public/icons/coin.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/illustrations/role-contributor.svg b/public/illustrations/role-contributor.svg new file mode 100644 index 0000000..cc2a417 --- /dev/null +++ b/public/illustrations/role-contributor.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/illustrations/role-organiser.svg b/public/illustrations/role-organiser.svg new file mode 100644 index 0000000..ef74771 --- /dev/null +++ b/public/illustrations/role-organiser.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/components/layout/app-nav.tsx b/src/components/layout/app-nav.tsx index fe212c9..924b40e 100644 --- a/src/components/layout/app-nav.tsx +++ b/src/components/layout/app-nav.tsx @@ -111,7 +111,7 @@ function KbdChip({ children, pill }: { children: string; pill?: boolean }) { return ( diff --git a/src/components/layout/site-header.tsx b/src/components/layout/site-header.tsx index b8eef77..ec5ead2 100644 --- a/src/components/layout/site-header.tsx +++ b/src/components/layout/site-header.tsx @@ -14,6 +14,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { headerMenus } from '@/config/marketing-nav'; +import { LaunchAppModal } from '@/features/marketing'; type HeaderVariant = 'site' | 'blog'; @@ -136,7 +137,13 @@ function MegaMenu() { ); } -function MobileMenu({ onNavigate }: { onNavigate: () => void }) { +function MobileMenu({ + onNavigate, + onLaunch, +}: { + onNavigate: () => void; + onLaunch: () => void; +}) { return (
void }) {
))} - - - Launch App - + { + onNavigate(); + onLaunch(); + }} + > + Launch App @@ -194,6 +206,7 @@ function MobileMenu({ onNavigate }: { onNavigate: () => void }) { */ export function SiteHeader() { const [open, setOpen] = useState(false); + const [launchOpen, setLaunchOpen] = useState(false); const pathname = usePathname(); const variant: HeaderVariant = (pathname ?? '').startsWith('/blog') ? 'blog' @@ -213,8 +226,8 @@ export function SiteHeader() { // Newsletter subscribe; wire to the newsletter when it ships. Subscribe ) : ( - - Launch App + setLaunchOpen(true)}> + Launch App )} @@ -229,8 +242,8 @@ export function SiteHeader() { ) : ( <> - - Launch App + setLaunchOpen(true)}> + Launch App - ); -} - /** Page header for a discovery view: title with result count, subtext, actions. */ export function DiscoverHeader({ heading, diff --git a/src/features/discover/components/opportunity-card.tsx b/src/features/discover/components/opportunity-card.tsx index ff7d72e..519cd95 100644 --- a/src/features/discover/components/opportunity-card.tsx +++ b/src/features/discover/components/opportunity-card.tsx @@ -115,7 +115,7 @@ export function OpportunityCard({ strokeWidth={1.75} aria-hidden /> - + {category} @@ -133,7 +133,7 @@ export function OpportunityCard({
- + void; +} + +/** + * Role gate shown when a visitor launches the app from the marketing header. + * They pick how they want to explore Boundless (Contributor or Organiser), + * then Continue lands them in the dashboard for that role. Full screen on + * mobile, centered card on larger viewports. The choice is a starting point, + * not a commitment, so it can be switched later in the app. + */ +export function LaunchAppModal({ open, onOpenChange }: LaunchAppModalProps) { + const router = useRouter(); + const [role, setRole] = useState(null); + + function handleContinue() { + if (!role) return; + onOpenChange(false); + router.push(`/dashboard?role=${role}`); + } + + return ( + + + + + + +
+ +
+ + Choose how you want to explore Boundless + + + You can always switch later. + +
+
+ +
+ {ROLES.map(option => ( + setRole(option.value)} + icon={ + + } + /> + ))} +
+ + + Continue + + +
+
+ ); +} diff --git a/src/features/marketing/index.ts b/src/features/marketing/index.ts index f432957..256d2cd 100644 --- a/src/features/marketing/index.ts +++ b/src/features/marketing/index.ts @@ -39,6 +39,7 @@ export { GetStartedModal } from './components/get-started-modal'; export { default as HeroBackground } from './components/hero-background'; export { HeroSection } from './components/hero-section'; export { IntroSection } from './components/intro-section'; +export { LaunchAppModal } from './components/launch-app-modal'; export { MarketingButton } from './components/marketing-button'; export { NewsSection } from './components/news-section'; export { PartnerLogos } from './components/partner-logos'; diff --git a/src/features/onboarding/components/avatar-upload.tsx b/src/features/onboarding/components/avatar-upload.tsx index c2caf40..e7510fd 100644 --- a/src/features/onboarding/components/avatar-upload.tsx +++ b/src/features/onboarding/components/avatar-upload.tsx @@ -57,7 +57,7 @@ export function AvatarUpload({ /> )}
- + diff --git a/src/features/onboarding/components/crop-photo-modal.tsx b/src/features/onboarding/components/crop-photo-modal.tsx index 758f139..16cc730 100644 --- a/src/features/onboarding/components/crop-photo-modal.tsx +++ b/src/features/onboarding/components/crop-photo-modal.tsx @@ -178,7 +178,7 @@ export function CropPhotoModal({
- + - +
diff --git a/src/features/onboarding/components/role-gate-modal.tsx b/src/features/onboarding/components/role-gate-modal.tsx index de9bcd6..2b40192 100644 --- a/src/features/onboarding/components/role-gate-modal.tsx +++ b/src/features/onboarding/components/role-gate-modal.tsx @@ -96,7 +96,7 @@ export function RoleGateModal({
{/* Usage */}
-

+

How are you planning to use Boundless?

@@ -131,7 +131,7 @@ export function RoleGateModal({ {/* Source */}
-

+

How did you hear about boundless?