Skip to content

Commit 4008e6f

Browse files
committed
fix: split login and landing
1 parent aec8a68 commit 4008e6f

6 files changed

Lines changed: 535 additions & 443 deletions

File tree

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import React from 'react'
2+
import type { Meta, StoryObj } from '@storybook/react'
3+
import { action } from '@storybook/addon-actions'
4+
import { LandingMenu } from './LandingMenu'
5+
6+
const meta: Meta<typeof LandingMenu> = {
7+
title: 'Foundary/LandingMenu',
8+
component: LandingMenu,
9+
parameters: {
10+
layout: 'fullscreen',
11+
docs: {
12+
description: {
13+
component:
14+
'A post-login landing menu component that allows users to choose their workspace. ' +
15+
'Features customizable workspace choices with support for internal URLs, external URLs, and custom callbacks. ' +
16+
'Includes smooth animations, hover effects, and configurable branding.'
17+
}
18+
}
19+
},
20+
argTypes: {
21+
className: {
22+
control: 'text',
23+
description: 'Additional CSS classes for the landing menu container'
24+
},
25+
welcomeTitle: {
26+
control: 'text',
27+
description: 'Welcome title text'
28+
},
29+
welcomeSubtitle: {
30+
control: 'text',
31+
description: 'Welcome subtitle text'
32+
},
33+
logoSrc: {
34+
control: 'text',
35+
description: 'Logo source URL'
36+
},
37+
logoAlt: {
38+
control: 'text',
39+
description: 'Logo alt text'
40+
},
41+
workspaceChoices: {
42+
control: 'object',
43+
description: 'Array of workspace choice options'
44+
}
45+
},
46+
tags: ['autodocs']
47+
}
48+
49+
export default meta
50+
type Story = StoryObj<typeof LandingMenu>
51+
52+
/**
53+
* Default landing menu with Ava and team collaboration options
54+
*/
55+
export const Default: Story = {
56+
args: {},
57+
parameters: {
58+
docs: {
59+
description: {
60+
story: 'The default landing menu with Ava (digital assistant) and team collaboration options.'
61+
}
62+
}
63+
}
64+
}
65+
66+
/**
67+
* Custom branding example
68+
*/
69+
export const CustomBranding: Story = {
70+
args: {
71+
welcomeTitle: "Welcome to Acme Corp",
72+
welcomeSubtitle: "Select your preferred working mode",
73+
logoAlt: "Acme Corporation Logo"
74+
},
75+
parameters: {
76+
docs: {
77+
description: {
78+
story: 'Example showing customized branding and messaging for different organizations.'
79+
}
80+
}
81+
}
82+
}
83+
84+
/**
85+
* Custom workspace choices with different options
86+
*/
87+
export const CustomWorkspaceChoices: Story = {
88+
args: {
89+
workspaceChoices: [
90+
{
91+
id: 'analytics',
92+
title: 'Analytics Dashboard',
93+
description: 'View reports, metrics, and business intelligence data.',
94+
icon: React.createElement('div', { className: 'h-12 w-12 bg-green-500 rounded-lg flex items-center justify-center text-white font-bold text-xl' }, 'A'),
95+
color: 'text-green-600',
96+
gradient: 'from-green-500 to-teal-600',
97+
url: '/dashboard/analytics'
98+
},
99+
{
100+
id: 'admin',
101+
title: 'Admin Panel',
102+
description: 'Manage users, settings, and system configuration.',
103+
icon: React.createElement('div', { className: 'h-12 w-12 bg-red-500 rounded-lg flex items-center justify-center text-white font-bold text-xl' }, 'S'),
104+
color: 'text-red-600',
105+
gradient: 'from-red-500 to-pink-600',
106+
url: '/admin'
107+
},
108+
{
109+
id: 'external',
110+
title: 'External Tool',
111+
description: 'Launch external application in new tab.',
112+
icon: React.createElement('div', { className: 'h-12 w-12 bg-blue-500 rounded-lg flex items-center justify-center text-white font-bold text-xl' }, 'E'),
113+
color: 'text-blue-600',
114+
gradient: 'from-blue-500 to-indigo-600',
115+
url: 'https://example.com'
116+
},
117+
{
118+
id: 'custom',
119+
title: 'Custom Action',
120+
description: 'Execute custom callback function.',
121+
icon: React.createElement('div', { className: 'h-12 w-12 bg-purple-500 rounded-lg flex items-center justify-center text-white font-bold text-xl' }, 'C'),
122+
color: 'text-purple-600',
123+
gradient: 'from-purple-500 to-violet-600',
124+
onClick: () => action('Custom workspace action executed')()
125+
}
126+
]
127+
},
128+
parameters: {
129+
docs: {
130+
description: {
131+
story:
132+
'Example showing custom workspace choices with different navigation options: ' +
133+
'internal URLs, external URLs, and custom callback functions.'
134+
}
135+
}
136+
}
137+
}
138+
139+
/**
140+
* Three workspace options layout
141+
*/
142+
export const ThreeOptions: Story = {
143+
args: {
144+
workspaceChoices: [
145+
{
146+
id: 'dashboard',
147+
title: 'Dashboard',
148+
description: 'Overview of your key metrics and activities.',
149+
icon: React.createElement('div', { className: 'h-12 w-12 bg-blue-500 rounded-lg flex items-center justify-center text-white font-bold text-xl' }, 'D'),
150+
color: 'text-blue-600',
151+
gradient: 'from-blue-500 to-cyan-600',
152+
url: '/dashboard'
153+
},
154+
{
155+
id: 'projects',
156+
title: 'Projects',
157+
description: 'Manage and collaborate on your projects.',
158+
icon: React.createElement('div', { className: 'h-12 w-12 bg-green-500 rounded-lg flex items-center justify-center text-white font-bold text-xl' }, 'P'),
159+
color: 'text-green-600',
160+
gradient: 'from-green-500 to-emerald-600',
161+
url: '/projects'
162+
},
163+
{
164+
id: 'settings',
165+
title: 'Settings',
166+
description: 'Configure your account and preferences.',
167+
icon: React.createElement('div', { className: 'h-12 w-12 bg-purple-500 rounded-lg flex items-center justify-center text-white font-bold text-xl' }, 'S'),
168+
color: 'text-purple-600',
169+
gradient: 'from-purple-500 to-violet-600',
170+
url: '/settings'
171+
}
172+
]
173+
},
174+
parameters: {
175+
docs: {
176+
description: {
177+
story: 'Example with three workspace options to show how the layout adapts.'
178+
}
179+
}
180+
}
181+
}
182+
183+
/**
184+
* Custom styling example
185+
*/
186+
export const CustomStyling: Story = {
187+
args: {
188+
className: 'bg-gradient-to-br from-indigo-50 to-cyan-50',
189+
welcomeTitle: "Choose Your Path",
190+
welcomeSubtitle: "Select the workspace that fits your workflow"
191+
},
192+
parameters: {
193+
docs: {
194+
description: {
195+
story: 'Example showing custom background styling and messaging.'
196+
}
197+
}
198+
}
199+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
'use client'
2+
3+
import { motion, AnimatePresence } from 'motion/react'
4+
import { Bot, Users } from 'lucide-react'
5+
import { Button } from '@/components/ui/button'
6+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
7+
import { Logo } from '@/components/Payload/Logo'
8+
import { cn } from '../../utils'
9+
10+
export interface WorkspaceChoice {
11+
id: string
12+
title: string
13+
description: string
14+
icon: React.ReactNode
15+
color: string
16+
gradient: string
17+
url?: string
18+
onClick?: () => void
19+
}
20+
21+
export interface LandingMenuProps {
22+
/**
23+
* Custom class name for the landing menu container
24+
*/
25+
className?: string
26+
/**
27+
* Welcome title text
28+
*/
29+
welcomeTitle?: string
30+
/**
31+
* Welcome subtitle text
32+
*/
33+
welcomeSubtitle?: string
34+
/**
35+
* Logo source URL
36+
*/
37+
logoSrc?: string
38+
/**
39+
* Logo alt text
40+
*/
41+
logoAlt?: string
42+
/**
43+
* Workspace choice options
44+
*/
45+
workspaceChoices?: WorkspaceChoice[]
46+
}
47+
48+
const defaultWorkspaceChoices: WorkspaceChoice[] = [
49+
{
50+
id: 'ava',
51+
title: 'Chat with Ava',
52+
description: 'Start a conversation with our digital assistant for instant help and guidance.',
53+
icon: <Bot className="h-12 w-12" />,
54+
color: 'text-purple-600',
55+
gradient: 'from-purple-500 to-blue-600',
56+
url: '/chat/ava'
57+
},
58+
{
59+
id: 'team',
60+
title: 'Collaborate with Team',
61+
description: 'Work together with your colleagues on projects and share knowledge.',
62+
icon: <Users className="h-12 w-12" />,
63+
color: 'text-blue-600',
64+
gradient: 'from-blue-500 to-green-600',
65+
url: '/workspace/team'
66+
}
67+
]
68+
69+
export function LandingMenu({
70+
className,
71+
welcomeTitle = "Welcome to Cortex",
72+
welcomeSubtitle = "Choose how you'd like to get started",
73+
logoSrc = "/logo.png",
74+
logoAlt = "Cortex Logo",
75+
workspaceChoices = defaultWorkspaceChoices
76+
}: LandingMenuProps) {
77+
const handleWorkspaceChoice = (choice: WorkspaceChoice) => {
78+
// Handle custom onClick callback first
79+
if (choice.onClick) {
80+
choice.onClick()
81+
return
82+
}
83+
84+
// Handle URL redirection
85+
if (choice.url) {
86+
if (choice.url.startsWith('http://') || choice.url.startsWith('https://')) {
87+
// External URL
88+
window.open(choice.url, '_blank')
89+
} else {
90+
// Internal navigation
91+
window.location.href = choice.url
92+
}
93+
} else {
94+
// Fallback - just log for demo
95+
console.log(`User selected: ${choice.title}`)
96+
}
97+
}
98+
99+
return (
100+
<div className={cn('min-h-screen bg-gradient-to-br from-slate-50 to-blue-50 flex items-center justify-center p-4', className)}>
101+
<motion.div
102+
initial={{ opacity: 0, scale: 0.9 }}
103+
animate={{ opacity: 1, scale: 1 }}
104+
transition={{ duration: 0.5, type: "spring", stiffness: 100 }}
105+
className="w-full max-w-4xl"
106+
>
107+
<div className="text-center mb-8">
108+
<motion.div
109+
initial={{ opacity: 0, scale: 0.8 }}
110+
animate={{ opacity: 1, scale: 1 }}
111+
transition={{ delay: 0.1, duration: 0.6 }}
112+
className="mb-6 flex justify-center"
113+
>
114+
<div className="w-32 h-32 bg-white rounded-full flex items-center justify-center shadow-lg">
115+
<Logo
116+
className="w-24 h-auto"
117+
loading="eager"
118+
priority="high"
119+
alt={logoAlt}
120+
src={logoSrc}
121+
/>
122+
</div>
123+
</motion.div>
124+
<motion.h1
125+
initial={{ opacity: 0, y: -20 }}
126+
animate={{ opacity: 1, y: 0 }}
127+
transition={{ delay: 0.2, duration: 0.6 }}
128+
className="text-4xl font-bold bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent mb-4"
129+
>
130+
{welcomeTitle}
131+
</motion.h1>
132+
<motion.p
133+
initial={{ opacity: 0, y: -10 }}
134+
animate={{ opacity: 1, y: 0 }}
135+
transition={{ delay: 0.3, duration: 0.6 }}
136+
className="text-lg text-muted-foreground"
137+
>
138+
{welcomeSubtitle}
139+
</motion.p>
140+
</div>
141+
142+
<div className="grid md:grid-cols-2 gap-6">
143+
<AnimatePresence>
144+
{workspaceChoices.map((choice, index) => (
145+
<motion.div
146+
key={choice.id}
147+
initial={{ opacity: 0, y: 20 }}
148+
animate={{ opacity: 1, y: 0 }}
149+
transition={{
150+
delay: 0.4 + (index * 0.1),
151+
duration: 0.6,
152+
type: "spring",
153+
stiffness: 100
154+
}}
155+
whileHover={{ scale: 1.02 }}
156+
whileTap={{ scale: 0.98 }}
157+
>
158+
<Card
159+
className="cursor-pointer transition-all duration-300 hover:shadow-xl border-2 hover:border-primary/30 group h-full"
160+
onClick={() => handleWorkspaceChoice(choice)}
161+
>
162+
<CardHeader className="text-center pb-4">
163+
<div className={cn(
164+
'mx-auto w-20 h-20 rounded-full flex items-center justify-center mb-4 transition-all duration-300',
165+
'bg-gradient-to-br', choice.gradient,
166+
'group-hover:scale-110'
167+
)}>
168+
<div className="text-white">
169+
{choice.icon}
170+
</div>
171+
</div>
172+
<CardTitle className="text-2xl font-semibold">
173+
{choice.title}
174+
</CardTitle>
175+
</CardHeader>
176+
<CardContent className="text-center">
177+
<CardDescription className="text-base leading-relaxed mb-6">
178+
{choice.description}
179+
</CardDescription>
180+
<Button
181+
className={cn(
182+
'w-full bg-gradient-to-r', choice.gradient,
183+
'text-white border-0 hover:opacity-90 transition-opacity duration-300'
184+
)}
185+
size="lg"
186+
>
187+
Get Started
188+
</Button>
189+
</CardContent>
190+
</Card>
191+
</motion.div>
192+
))}
193+
</AnimatePresence>
194+
</div>
195+
</motion.div>
196+
</div>
197+
)
198+
}

0 commit comments

Comments
 (0)