Add help modal and expand product tour

This commit is contained in:
akhisud3195 2025-05-07 19:28:54 +05:30
parent f91e3bab48
commit 8981345ba8
6 changed files with 301 additions and 146 deletions

View file

@ -4,6 +4,7 @@ import { UserProvider } from '@auth0/nextjs-auth0/client';
import { Inter } from "next/font/google";
import { Providers } from "./providers";
import { Metadata } from "next";
import { HelpModalProvider } from "./providers/help-modal-provider";
const inter = Inter({ subsets: ["latin"] });
@ -24,7 +25,9 @@ export default function RootLayout({
<ThemeProvider>
<body className={`${inter.className} h-full text-base [scrollbar-width:thin] bg-background`}>
<Providers className='h-full flex flex-col'>
{children}
<HelpModalProvider>
{children}
</HelpModalProvider>
</Providers>
</body>
</ThemeProvider>

View file

@ -911,6 +911,7 @@ export function WorkflowEditor({
onPress={handlePublishWorkflow}
className="gap-2 px-4 bg-green-600 hover:bg-green-700 text-white font-semibold text-sm"
startContent={<RocketIcon size={16} />}
data-tour-target="deploy"
>
Deploy
</Button>

View file

@ -19,6 +19,7 @@ import {
import { getProjectConfig } from "@/app/actions/project_actions";
import { useTheme } from "@/app/providers/theme-provider";
import { USE_TESTING_FEATURE, USE_PRODUCT_TOUR } from '@/app/lib/feature_flags';
import { useHelpModal } from "@/app/providers/help-modal-provider";
interface SidebarProps {
projectId: string;
@ -36,6 +37,7 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
const [projectName, setProjectName] = useState<string>("Select Project");
const isProjectsRoute = pathname === '/projects' || pathname === '/projects/select';
const { theme, toggleTheme } = useTheme();
const { showHelpModal } = useHelpModal();
useEffect(() => {
async function fetchProjectName() {
@ -79,116 +81,137 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
}
];
return (
<aside className={`${collapsed ? 'w-16' : 'w-60'} bg-transparent flex flex-col h-full transition-all duration-300`}>
<div className="flex flex-col flex-grow">
{!isProjectsRoute && (
<>
{/* Project Selector */}
<div className="p-3 border-b border-zinc-100 dark:border-zinc-800">
<Tooltip content={collapsed ? projectName : "Change project"} showArrow placement="right">
<Link
href="/projects"
className={`
flex items-center rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800/50 transition-all
${collapsed ? 'justify-center py-4' : 'gap-3 px-4 py-2.5'}
`}
>
<FolderOpenIcon
size={collapsed ? COLLAPSED_ICON_SIZE : EXPANDED_ICON_SIZE}
className="text-zinc-500 dark:text-zinc-400 transition-all duration-200"
/>
{!collapsed && (
<span className="text-sm font-medium truncate">
{projectName}
</span>
)}
</Link>
</Tooltip>
</div>
const handleStartTour = () => {
localStorage.removeItem('user_product_tour_completed');
window.location.reload();
};
{/* Navigation Items */}
<nav className="p-3 space-y-4">
{navItems.map((item) => {
const Icon = item.icon;
const fullPath = `/projects/${projectId}/${item.href}`;
const isActive = pathname.startsWith(fullPath);
const isDisabled = isProjectsRoute && item.requiresProject;
return (
<Tooltip
key={item.href}
content={collapsed ? item.label : ""}
showArrow
placement="right"
return (
<>
<aside className={`${collapsed ? 'w-16' : 'w-60'} bg-transparent flex flex-col h-full transition-all duration-300`}>
<div className="flex flex-col flex-grow">
{!isProjectsRoute && (
<>
{/* Project Selector */}
<div className="p-3 border-b border-zinc-100 dark:border-zinc-800">
<Tooltip content={collapsed ? projectName : "Change project"} showArrow placement="right">
<Link
href="/projects"
className={`
flex items-center rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800/50 transition-all
${collapsed ? 'justify-center py-4' : 'gap-3 px-4 py-2.5'}
`}
>
<Link
href={isDisabled ? '#' : fullPath}
className={isDisabled ? 'pointer-events-none' : ''}
<FolderOpenIcon
size={collapsed ? COLLAPSED_ICON_SIZE : EXPANDED_ICON_SIZE}
className="text-zinc-500 dark:text-zinc-400 transition-all duration-200"
/>
{!collapsed && (
<span className="text-sm font-medium truncate">
{projectName}
</span>
)}
</Link>
</Tooltip>
</div>
{/* Navigation Items */}
<nav className="p-3 space-y-4">
{navItems.map((item) => {
const Icon = item.icon;
const fullPath = `/projects/${projectId}/${item.href}`;
const isActive = pathname.startsWith(fullPath);
const isDisabled = isProjectsRoute && item.requiresProject;
return (
<Tooltip
key={item.href}
content={collapsed ? item.label : ""}
showArrow
placement="right"
>
<button
className={`
relative w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
${collapsed ? 'justify-center py-4' : 'px-4 py-4 gap-3'}
${isActive
? 'bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-l-2 border-indigo-600 dark:border-indigo-400'
: isDisabled
? 'text-zinc-300 dark:text-zinc-600 cursor-not-allowed'
: 'text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800/50 hover:text-zinc-900 dark:hover:text-zinc-300'
}
`}
disabled={isDisabled}
data-tour-target={item.href === 'config' ? 'settings' : undefined}
<Link
href={isDisabled ? '#' : fullPath}
className={isDisabled ? 'pointer-events-none' : ''}
>
<Icon
size={collapsed ? COLLAPSED_ICON_SIZE : EXPANDED_ICON_SIZE}
<button
className={`
transition-all duration-200
${isDisabled
? 'text-zinc-300 dark:text-zinc-600'
: isActive
? 'text-indigo-600 dark:text-indigo-400'
: 'text-zinc-500 dark:text-zinc-400'
relative w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
${collapsed ? 'justify-center py-4' : 'px-4 py-4 gap-3'}
${isActive
? 'bg-indigo-50 dark:bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-l-2 border-indigo-600 dark:border-indigo-400'
: isDisabled
? 'text-zinc-300 dark:text-zinc-600 cursor-not-allowed'
: 'text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800/50 hover:text-zinc-900 dark:hover:text-zinc-300'
}
`}
/>
{!collapsed && <span>{item.label}</span>}
</button>
</Link>
</Tooltip>
);
})}
</nav>
</>
)}
</div>
{/* Bottom section */}
<div className="mt-auto">
{/* Collapse Toggle Button */}
<div className="p-3 border-t border-zinc-100 dark:border-zinc-800">
<button
onClick={onToggleCollapse}
className="w-full flex items-center justify-center p-2 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800/50 transition-all"
>
{collapsed ? (
<ChevronRightIcon size={20} className="text-zinc-500 dark:text-zinc-400" />
) : (
<ChevronLeftIcon size={20} className="text-zinc-500 dark:text-zinc-400" />
)}
</button>
disabled={isDisabled}
data-tour-target={item.href === 'config' ? 'settings' : undefined}
>
<Icon
size={collapsed ? COLLAPSED_ICON_SIZE : EXPANDED_ICON_SIZE}
className={`
transition-all duration-200
${isDisabled
? 'text-zinc-300 dark:text-zinc-600'
: isActive
? 'text-indigo-600 dark:text-indigo-400'
: 'text-zinc-500 dark:text-zinc-400'
}
`}
/>
{!collapsed && <span>{item.label}</span>}
</button>
</Link>
</Tooltip>
);
})}
</nav>
</>
)}
</div>
{/* Theme and Auth Controls */}
<div className="p-3 border-t border-zinc-100 dark:border-zinc-800 space-y-2">
{USE_PRODUCT_TOUR && !isProjectsRoute && (
<Tooltip content={collapsed ? "Take Tour" : ""} showArrow placement="right">
{/* Bottom section */}
<div className="mt-auto">
{/* Collapse Toggle Button */}
<div className="p-3 border-t border-zinc-100 dark:border-zinc-800">
<button
onClick={onToggleCollapse}
className="w-full flex items-center justify-center p-2 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800/50 transition-all"
>
{collapsed ? (
<ChevronRightIcon size={20} className="text-zinc-500 dark:text-zinc-400" />
) : (
<ChevronLeftIcon size={20} className="text-zinc-500 dark:text-zinc-400" />
)}
</button>
</div>
{/* Theme and Auth Controls */}
<div className="p-3 border-t border-zinc-100 dark:border-zinc-800 space-y-2">
{USE_PRODUCT_TOUR && !isProjectsRoute && (
<Tooltip content={collapsed ? "Help" : ""} showArrow placement="right">
<button
onClick={showHelpModal}
className={`
w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
${collapsed ? 'justify-center py-4' : 'px-4 py-4 gap-3'}
hover:bg-zinc-100 dark:hover:bg-zinc-800/50
text-zinc-600 dark:text-zinc-400
`}
data-tour-target="tour-button"
>
<HelpCircle size={COLLAPSED_ICON_SIZE} />
{!collapsed && <span>Help</span>}
</button>
</Tooltip>
)}
<Tooltip content={collapsed ? "Appearance" : ""} showArrow placement="right">
<button
onClick={() => {
localStorage.removeItem('user_product_tour_completed');
window.location.reload();
}}
onClick={toggleTheme}
className={`
w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
@ -197,45 +220,29 @@ export default function Sidebar({ projectId, useRag, useAuth, collapsed = false,
text-zinc-600 dark:text-zinc-400
`}
>
<HelpCircle size={COLLAPSED_ICON_SIZE} />
{!collapsed && <span>Take Tour</span>}
{ theme == "light" ? <Moon size={COLLAPSED_ICON_SIZE} /> : <Sun size={COLLAPSED_ICON_SIZE} /> }
{!collapsed && <span>Appearance</span>}
</button>
</Tooltip>
)}
<Tooltip content={collapsed ? "Appearance" : ""} showArrow placement="right">
<button
onClick={toggleTheme}
className={`
w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
${collapsed ? 'justify-center py-4' : 'px-4 py-4 gap-3'}
hover:bg-zinc-100 dark:hover:bg-zinc-800/50
text-zinc-600 dark:text-zinc-400
`}
>
{ theme == "light" ? <Moon size={COLLAPSED_ICON_SIZE} /> : <Sun size={COLLAPSED_ICON_SIZE} /> }
{!collapsed && <span>Appearance</span>}
</button>
</Tooltip>
{useAuth && (
<Tooltip content={collapsed ? "Account" : ""} showArrow placement="right">
<div
className={`
w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
${collapsed ? 'justify-center py-4' : 'px-4 py-4 gap-3'}
hover:bg-zinc-100 dark:hover:bg-zinc-800/50
`}
>
<UserButton />
{!collapsed && <span>Account</span>}
</div>
</Tooltip>
)}
{useAuth && (
<Tooltip content={collapsed ? "Account" : ""} showArrow placement="right">
<div
className={`
w-full rounded-md flex items-center
text-[15px] font-medium transition-all duration-200
${collapsed ? 'justify-center py-4' : 'px-4 py-4 gap-3'}
hover:bg-zinc-100 dark:hover:bg-zinc-800/50
`}
>
<UserButton />
{!collapsed && <span>Account</span>}
</div>
</Tooltip>
)}
</div>
</div>
</div>
</aside>
</aside>
</>
);
}

View file

@ -0,0 +1,42 @@
'use client';
import { createContext, useContext, useState, ReactNode } from 'react';
import { HelpModal } from '@/components/common/help-modal';
interface HelpModalContextType {
showHelpModal: () => void;
hideHelpModal: () => void;
}
const HelpModalContext = createContext<HelpModalContextType | undefined>(undefined);
export function HelpModalProvider({ children }: { children: ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
const showHelpModal = () => setIsOpen(true);
const hideHelpModal = () => setIsOpen(false);
const handleStartTour = () => {
localStorage.removeItem('user_product_tour_completed');
window.location.reload();
};
return (
<HelpModalContext.Provider value={{ showHelpModal, hideHelpModal }}>
{children}
<HelpModal
isOpen={isOpen}
onClose={hideHelpModal}
onStartTour={handleStartTour}
/>
</HelpModalContext.Provider>
);
}
export function useHelpModal() {
const context = useContext(HelpModalContext);
if (context === undefined) {
throw new Error('useHelpModal must be used within a HelpModalProvider');
}
return context;
}

View file

@ -0,0 +1,92 @@
import { Button } from "@heroui/react";
import { HelpCircle, BookOpen, MessageCircle } from "lucide-react";
interface HelpModalProps {
isOpen: boolean;
onClose: () => void;
onStartTour: () => void;
}
export function HelpModal({ isOpen, onClose, onStartTour }: HelpModalProps) {
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-[100] flex items-center justify-center">
<div className="bg-white dark:bg-zinc-800 rounded-lg shadow-lg p-6 w-[480px] max-w-[90vw] animate-in fade-in duration-200">
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-6">
Need Help?
</h2>
<div className="space-y-4">
<Button
className="w-full justify-start gap-4 text-left py-6 px-4 hover:bg-indigo-50 dark:hover:bg-indigo-500/10 transition-all duration-200 group hover:scale-[1.02] hover:shadow-md"
variant="light"
onPress={onStartTour}
>
<div className="bg-indigo-100 dark:bg-indigo-500/20 p-2 rounded-lg group-hover:bg-indigo-200 dark:group-hover:bg-indigo-500/30 transition-colors">
<HelpCircle className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
</div>
<div>
<div className="font-medium text-base text-gray-900 dark:text-gray-100">Take Product Tour</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Learn about RowBoat's features
</div>
</div>
</Button>
<a
href="https://docs.rowboatlabs.com/"
target="_blank"
rel="noopener noreferrer"
className="block"
>
<Button
className="w-full justify-start gap-4 text-left py-6 px-4 hover:bg-indigo-50 dark:hover:bg-indigo-500/10 transition-all duration-200 group hover:scale-[1.02] hover:shadow-md"
variant="light"
>
<div className="bg-indigo-100 dark:bg-indigo-500/20 p-2 rounded-lg group-hover:bg-indigo-200 dark:group-hover:bg-indigo-500/30 transition-colors">
<BookOpen className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
</div>
<div>
<div className="font-medium text-base text-gray-900 dark:text-gray-100">View Documentation</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Read our detailed guides
</div>
</div>
</Button>
</a>
<a
href="https://discord.gg/gtbGcqF4"
target="_blank"
rel="noopener noreferrer"
className="block"
>
<Button
className="w-full justify-start gap-4 text-left py-6 px-4 hover:bg-indigo-50 dark:hover:bg-indigo-500/10 transition-all duration-200 group hover:scale-[1.02] hover:shadow-md"
variant="light"
>
<div className="bg-indigo-100 dark:bg-indigo-500/20 p-2 rounded-lg group-hover:bg-indigo-200 dark:group-hover:bg-indigo-500/30 transition-colors">
<MessageCircle className="w-6 h-6 text-indigo-600 dark:text-indigo-400" />
</div>
<div>
<div className="font-medium text-base text-gray-900 dark:text-gray-100">Join Discord</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
Get help from the community
</div>
</div>
</Button>
</a>
</div>
<div className="mt-8 flex justify-end">
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300 px-4 py-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors"
>
Close
</button>
</div>
</div>
</div>
);
}

View file

@ -12,32 +12,42 @@ const TOUR_STEPS: TourStep[] = [
{
target: 'copilot',
content: 'Build agents with the help of copilot.\nThis might take a minute.',
title: 'Step 1/6'
title: 'Step 1/8'
},
{
target: 'playground',
content: 'Test your assistant in the playground.\nDebug tool calls and responses.',
title: 'Step 2/6'
title: 'Step 2/8'
},
{
target: 'entity-agents',
content: 'Manage your agents.\nSpecify instructions, examples and tool usage.',
title: 'Step 3/6'
title: 'Step 3/8'
},
{
target: 'entity-tools',
content: 'Create your own tools, import MCP tools or use existing ones.\nMock tools for quick testing.',
title: 'Step 4/6'
title: 'Step 4/8'
},
{
target: 'entity-prompts',
content: 'Manage prompts which will be used by agents.\nConfigure greeting message.',
title: 'Step 5/6'
title: 'Step 5/8'
},
{
target: 'settings',
content: 'Configure project settings\nGet API keys, configure tool webhooks.',
title: 'Step 6/6'
title: 'Step 6/8'
},
{
target: 'deploy',
content: 'Deploy your workflow version to make it live.\nThis will make your workflow available for use via the API and SDK.\n\nLearn more:\n• <a href="https://docs.rowboatlabs.com/using_the_api/" target="_blank" class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">Using the API</a>\n• <a href="https://docs.rowboatlabs.com/using_the_sdk/" target="_blank" class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">Using the SDK</a>',
title: 'Step 7/8'
},
{
target: 'tour-button',
content: 'Come back here anytime to restart the tour.\nStill have questions? See our <a href="https://docs.rowboatlabs.com/" target="_blank" class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">docs</a> or reach out on <a href="https://discord.gg/gtbGcqF4" target="_blank" class="text-indigo-600 hover:text-indigo-700 dark:text-indigo-400 dark:hover:text-indigo-300">discord</a>.',
title: 'Step 8/8'
}
];
@ -222,9 +232,9 @@ export function ProductTour({
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
{TOUR_STEPS[currentStep].title}
</div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3 whitespace-pre-line">
{TOUR_STEPS[currentStep].content}
</div>
<div className="text-sm font-medium text-gray-900 dark:text-gray-100 mb-3 whitespace-pre-line [&>a]:underline"
dangerouslySetInnerHTML={{ __html: TOUR_STEPS[currentStep].content }}
/>
<div className="flex justify-between items-center">
<button
onClick={handleSkip}