feat: major UI overhaul with new components and enhanced UX

- Add comprehensive Company Wiki feature with complete state management
  - CompanyWikiManager, empty states, invite modals
- Implement new Chat system with enhanced layout and components
  - ChatLayout, ChatSidebar, MessageThread, FileUploadInput
- Create modern Login and OTP verification flows
  - LoginNew page, OTPVerification component
- Add new Employee Forms system with enhanced controller
- Introduce Figma-based design components and multiple choice inputs
- Add new font assets (NeueMontreal) and robot images for onboarding
- Enhance existing components with improved styling and functionality
- Update build configuration and dependencies
- Remove deprecated ModernLogin component
This commit is contained in:
Ra
2025-08-20 03:30:04 -07:00
parent 1a9e92d7bd
commit cf565df13e
47 changed files with 6654 additions and 2007 deletions

View File

@@ -0,0 +1,150 @@
import React from 'react';
interface QAItem {
question: string;
answer: string;
}
interface CompanyWikiCompletedStateProps {
qaItems?: QAItem[];
activeSection?: number;
onSectionClick?: (section: number) => void;
onInviteEmployees?: () => void;
onCopyLink?: () => void;
}
const defaultQAItems: QAItem[] = [
{
question: "What is the mission of your company?",
answer: "To empower small businesses with AI-driven automation tools that increase efficiency and reduce operational overhead."
},
{
question: "How has your mission evolved in the last 13 years?",
answer: "We shifted from general SaaS tools to vertical-specific solutions, with deeper integrations and onboarding support."
},
{
question: "What is your 5-year vision for the company?",
answer: "To become the leading AI operations platform for SMBs in North America, serving over 100,000 customers."
},
{
question: "What are your company's top 3 strategic advantages?",
answer: "Fast product iteration enabled by in-house AI capabilities\nDeep customer understanding from vertical specialization\nHigh customer retention due to integrated onboarding"
},
{
question: "What are your biggest vulnerabilities or threats?",
answer: "Dependence on a single marketing channel, weak middle management, and rising customer acquisition costs."
}
];
const sections = [
"Company Overview & Vision",
"Leadership & Organizational Structure",
"Operations & Execution",
"Culture & Team Health",
"Sales, Marketing & Growth",
"Financial Health & Metrics",
"Innovation & Product/Service Strategy",
"Personal Leadership & Risk"
];
export const CompanyWikiCompletedState: React.FC<CompanyWikiCompletedStateProps> = ({
qaItems = defaultQAItems,
activeSection = 1,
onSectionClick,
onInviteEmployees,
onCopyLink
}) => {
return (
<div className="flex-1 self-stretch inline-flex justify-start items-center">
{/* Table of Contents */}
<div className="flex-1 self-stretch max-w-64 min-w-64 border-r border-Outline-Outline-Gray-200 dark:border-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start">
<div className="self-stretch p-5 inline-flex justify-start items-center gap-2.5">
<div className="flex-1 justify-start text-Neutrals-NeutralSlate950 dark:text-Neutrals-NeutralSlate50 text-base font-medium font-['Inter'] leading-normal">Table of contents</div>
</div>
<div className="self-stretch px-3 flex flex-col justify-start items-start gap-1.5">
{sections.map((section, index) => {
const sectionNumber = index + 1;
const isActive = sectionNumber === activeSection;
return (
<div
key={index}
onClick={() => onSectionClick?.(sectionNumber)}
className={`self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden cursor-pointer hover:bg-Main-BG-Gray-50 dark:hover:bg-Neutrals-NeutralSlate700 ${isActive ? 'bg-Main-BG-Gray-100 dark:bg-Neutrals-NeutralSlate800 shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)]' : ''}`}
>
<div className={`h-5 p-0.5 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden ${isActive ? 'bg-Brand-Orange' : 'bg-Text-Gray-100 dark:bg-Neutrals-NeutralSlate600'}`}>
<div className={`w-4 text-center justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-Neutrals-NeutralSlate0' : 'text-Text-Dark-950 dark:text-Neutrals-NeutralSlate200'}`}>
{sectionNumber}
</div>
</div>
<div className={`flex-1 justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-Neutrals-NeutralSlate800 dark:text-Neutrals-NeutralSlate100' : 'text-Text-Gray-500 dark:text-Neutrals-NeutralSlate400'}`}>
{section}
</div>
</div>
);
})}
</div>
</div>
{/* Main Content */}
<div className="flex-1 self-stretch inline-flex flex-col justify-start items-start">
<div className="self-stretch p-5 inline-flex justify-start items-center gap-2.5">
<div className="flex-1 justify-start text-Neutrals-NeutralSlate800 dark:text-Neutrals-NeutralSlate100 text-xl font-medium font-['Neue_Montreal'] leading-normal">
{sections[activeSection - 1]}
</div>
</div>
<div className="self-stretch px-5 flex flex-col justify-start items-start gap-4">
{qaItems.map((item, index) => (
<div key={index} className="self-stretch p-3 bg-Neutrals-NeutralSlate100 dark:bg-Neutrals-NeutralSlate800 rounded-2xl shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-2 overflow-hidden">
<div className="self-stretch px-3 py-px inline-flex justify-start items-center gap-2.5">
<div className="flex-1 flex justify-center items-center gap-3">
<div className="w-3 self-stretch justify-start text-Neutrals-NeutralSlate300 dark:text-Neutrals-NeutralSlate500 text-base font-semibold font-['Inter'] leading-normal">Q</div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate600 dark:text-Neutrals-NeutralSlate300 text-sm font-medium font-['Inter'] leading-tight">
{item.question}
</div>
</div>
</div>
<div className="self-stretch px-3 py-2 bg-Neutrals-NeutralSlate0 dark:bg-Neutrals-NeutralSlate900 rounded-[10px] inline-flex justify-between items-center">
<div className="flex-1 flex justify-start items-start gap-3">
<div className="w-3.5 h-6 justify-center text-Neutrals-NeutralSlate300 dark:text-Neutrals-NeutralSlate500 text-base font-semibold font-['Inter'] leading-normal">A</div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate800 dark:text-Neutrals-NeutralSlate100 text-base font-normal font-['Inter'] leading-normal whitespace-pre-line">
{item.answer}
</div>
</div>
</div>
</div>
))}
{/* Additional Questions */}
<div className="self-stretch pl-3 pr-6 pt-3 pb-6 bg-Text-Gray-100 rounded-2xl shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-2 overflow-hidden">
<div className="self-stretch inline-flex justify-center items-center gap-3">
<div className="flex-1 justify-start text-Text-Gray-600 text-sm font-medium font-['Inter'] leading-tight">What is the mission of your company?</div>
</div>
<div className="self-stretch px-3 py-2 bg-Light-Grays-l-gray08 rounded-[10px] outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 inline-flex justify-between items-center">
<div className="flex-1 flex justify-start items-start gap-3">
<div className="flex-1 justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">
Our mission is to not only create value but also to foster a collaborative environment where innovation thrives. We aim to empower our team members to contribute their unique skills and perspectives, ensuring that every project we undertake is a reflection of our collective creativity and dedication. By prioritizing both individual growth and teamwork, we strive to build a company culture that values excellence and continuous improvement. Together, we can achieve remarkable results that benefit not just our organization, but also our clients and the community at...
</div>
</div>
</div>
</div>
<div className="self-stretch p-3 bg-Neutrals-NeutralSlate100 rounded-2xl shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-2 overflow-hidden">
<div className="self-stretch px-3 py-px inline-flex justify-start items-center gap-2.5">
<div className="flex-1 flex justify-center items-center gap-3">
<div className="w-3 self-stretch justify-start text-Neutrals-NeutralSlate300 text-base font-semibold font-['Inter'] leading-normal">Q</div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate600 text-sm font-medium font-['Inter'] leading-tight">What is the mission of your company?</div>
</div>
</div>
<div className="self-stretch px-3 py-2 bg-Light-Grays-l-gray08 rounded-[10px] inline-flex justify-between items-center">
<div className="flex-1 flex justify-start items-start gap-3">
<div className="w-3.5 h-6 justify-center text-Neutrals-NeutralSlate300 text-base font-semibold font-['Inter'] leading-normal">A</div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate800 text-base font-normal font-['Inter'] leading-normal">The mission is to create value as well as working</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,119 @@
import React from 'react';
interface CompanyWikiEmptyStateProps {
progress?: number;
onCompleteOnboarding?: () => void;
onInviteEmployees?: () => void;
onCopyLink?: () => void;
}
export const CompanyWikiEmptyState: React.FC<CompanyWikiEmptyStateProps> = ({
progress = 60,
onCompleteOnboarding,
onInviteEmployees,
onCopyLink
}) => {
return (
<div className="flex-1 self-stretch inline-flex justify-start items-center">
<div className="flex-1 self-stretch max-w-64 min-w-64 border-r border-Outline-Outline-Gray-200 inline-flex flex-col justify-start items-start">
<div className="self-stretch p-5 inline-flex justify-start items-center gap-2.5">
<div className="flex-1 justify-start text-Neutrals-NeutralSlate950 text-base font-medium font-['Inter'] leading-normal">Table of contents</div>
</div>
<div className="self-stretch px-3 flex flex-col justify-start items-start gap-1.5">
<div className="self-stretch p-2 bg-Main-BG-Gray-100 rounded-full shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="h-5 p-0.5 bg-Brand-Orange rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden">
<div className="w-4 text-center justify-start text-Neutrals-NeutralSlate0 text-xs font-medium font-['Inter'] leading-none">1</div>
</div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate800 text-xs font-medium font-['Inter'] leading-none">Company Overview & Vision</div>
</div>
<div className="self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="h-5 p-0.5 bg-Text-Gray-100 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden">
<div className="w-4 text-center justify-start text-Text-Dark-950 text-xs font-normal font-['Inter'] leading-none">2</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-500 text-xs font-medium font-['Inter'] leading-none">Leadership & Organizational Structure</div>
</div>
<div className="self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="h-5 p-0.5 bg-Text-Gray-100 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden">
<div className="w-4 text-center justify-start text-Text-Dark-950 text-xs font-normal font-['Inter'] leading-none">3</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-500 text-xs font-medium font-['Inter'] leading-none">Operations & Execution</div>
</div>
<div className="self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="h-5 p-0.5 bg-Text-Gray-100 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden">
<div className="w-4 text-center justify-start text-Text-Dark-950 text-xs font-normal font-['Inter'] leading-none">4</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-500 text-xs font-medium font-['Inter'] leading-none">Culture & Team Health</div>
</div>
<div className="self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="h-5 p-0.5 bg-Text-Gray-100 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden">
<div className="w-4 text-center justify-start text-Text-Dark-950 text-xs font-normal font-['Inter'] leading-none">5</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-500 text-xs font-medium font-['Inter'] leading-none">Sales, Marketing & Growth</div>
</div>
<div className="self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="h-5 p-0.5 bg-Text-Gray-100 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden">
<div className="w-4 text-center justify-start text-Text-Dark-950 text-xs font-normal font-['Inter'] leading-none">6</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-500 text-xs font-medium font-['Inter'] leading-none">Financial Health & Metrics</div>
</div>
<div className="self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="h-5 p-0.5 bg-Text-Gray-100 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden">
<div className="w-4 text-center justify-start text-Text-Dark-950 text-xs font-normal font-['Inter'] leading-none">7</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-500 text-xs font-medium font-['Inter'] leading-none">Innovation & Product/Service Strategy</div>
</div>
<div className="self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div className="h-5 p-0.5 bg-Text-Gray-100 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden">
<div className="w-4 text-center justify-start text-Text-Dark-950 text-xs font-normal font-['Inter'] leading-none">8</div>
</div>
<div className="flex-1 justify-start text-Text-Gray-500 text-xs font-medium font-['Inter'] leading-none">Personal Leadership & Risk</div>
</div>
</div>
</div>
<div className="flex-1 self-stretch inline-flex flex-col justify-center items-center p-8">
{/* Empty State Illustration */}
<div className="w-80 h-64 mb-8 relative">
{/* Placeholder for illustration - would contain the actual empty state SVG */}
<div className="w-full h-full bg-Neutrals-NeutralSlate100 rounded-2xl flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 bg-Neutrals-NeutralSlate200 rounded-full mx-auto mb-4 flex items-center justify-center">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 30V18.6667C12 17.3867 12 16.7467 12.1453 16.248C12.2731 15.8071 12.5171 15.4109 12.848 15.1053C13.2133 14.7667 13.7066 14.6667 14.6933 14.6667H17.3067C18.2934 14.6667 18.7867 14.7667 19.152 15.1053C19.4829 15.4109 19.7269 15.8071 19.8547 16.248C20 16.7467 20 17.3867 20 18.6667V30M14.6903 3.68533L6.04715 11.5188C5.44269 12.0684 5.14047 12.3431 4.92271 12.6778C4.73015 12.9739 4.58613 13.3073 4.49871 13.6608C4.4 14.0575 4.4 14.4803 4.4 15.3261V23.7333C4.4 25.2267 4.4 25.9733 4.69065 26.544C4.94631 27.0458 5.35421 27.4537 5.85603 27.7093C6.42669 28 7.17323 28 8.66667 28H23.3333C24.8268 28 25.5733 28 26.144 27.7093C26.6458 27.4537 27.0537 27.0458 27.3093 26.544C27.6 25.9733 27.6 25.2267 27.6 23.7333V15.3261C27.6 14.4803 27.6 14.0575 27.5013 13.6608C27.4139 13.3073 27.2699 12.9739 27.0773 12.6778C26.8595 12.3431 26.5573 12.0684 25.9529 11.5188L17.3097 3.68533C16.8413 3.27241 16.6071 3.06595 16.3485 2.98821C16.1203 2.9184 15.8797 2.9184 15.6515 2.98821C15.3929 3.06595 15.1587 3.27241 14.6903 3.68533Z" stroke="var(--Neutrals-NeutralSlate400)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="text-Neutrals-NeutralSlate600 text-sm">Company Wiki Empty State</div>
</div>
</div>
</div>
{/* Progress and Call to Action */}
<div className="text-center max-w-md">
<div className="mb-6">
<div className="text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal mb-2">
You're {progress}% Done
</div>
<div className="text-Neutrals-NeutralSlate600 text-base leading-normal">
Complete your onboarding to unlock your Company Wiki
</div>
</div>
{/* Progress Bar */}
<div className="w-full bg-Neutrals-NeutralSlate200 rounded-full h-2 mb-8">
<div
className="bg-Brand-Orange h-2 rounded-full transition-all duration-300"
style={{ width: `${progress}%` }}
></div>
</div>
{/* Action Button */}
<button
onClick={onCompleteOnboarding}
className="px-8 py-3 bg-Brand-Orange text-white rounded-[999px] font-medium text-base hover:bg-Brand-Orange/90 transition-colors"
>
Complete Onboarding
</button>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,110 @@
import React from 'react';
interface CompanyWikiEmptyStateProps {
progress?: number;
onCompleteOnboarding?: () => void;
onInviteEmployees?: () => void;
onCopyLink?: () => void;
}
const sections = [
"Company Overview & Vision",
"Leadership & Organizational Structure",
"Operations & Execution",
"Culture & Team Health",
"Sales, Marketing & Growth",
"Financial Health & Metrics",
"Innovation & Product/Service Strategy",
"Personal Leadership & Risk"
];
export const CompanyWikiEmptyState: React.FC<CompanyWikiEmptyStateProps> = ({
progress = 60,
onCompleteOnboarding,
onInviteEmployees,
onCopyLink
}) => {
return (
<div className="flex-1 self-stretch inline-flex justify-start items-center">
{/* Table of Contents */}
<div className="flex-1 self-stretch max-w-64 min-w-64 border-r border-Outline-Outline-Gray-200 dark:border-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start">
<div className="self-stretch p-5 inline-flex justify-start items-center gap-2.5">
<div className="flex-1 justify-start text-Neutrals-NeutralSlate950 dark:text-Neutrals-NeutralSlate50 text-base font-medium font-['Inter'] leading-normal">Table of contents</div>
</div>
<div className="self-stretch px-3 flex flex-col justify-start items-start gap-1.5">
{sections.map((section, index) => {
const sectionNumber = index + 1;
const isActive = sectionNumber === 1; // First section is always active in empty state
return (
<div
key={index}
className={`self-stretch p-2 rounded-[10px] inline-flex justify-start items-center gap-2 overflow-hidden ${isActive ? 'bg-Main-BG-Gray-100 dark:bg-Neutrals-NeutralSlate800 shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)]' : 'hover:bg-Main-BG-Gray-50 dark:hover:bg-Neutrals-NeutralSlate700'}`}
>
<div className={`h-5 p-0.5 rounded-[999px] inline-flex flex-col justify-center items-center gap-0.5 overflow-hidden ${isActive ? 'bg-Brand-Orange' : 'bg-Text-Gray-100 dark:bg-Neutrals-NeutralSlate600'}`}>
<div className={`w-4 text-center justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-Neutrals-NeutralSlate0' : 'text-Text-Dark-950 dark:text-Neutrals-NeutralSlate200'}`}>
{sectionNumber}
</div>
</div>
<div className={`flex-1 justify-start text-xs font-medium font-['Inter'] leading-none ${isActive ? 'text-Neutrals-NeutralSlate800 dark:text-Neutrals-NeutralSlate100' : 'text-Text-Gray-500 dark:text-Neutrals-NeutralSlate400'}`}>
{section}
</div>
</div>
);
})}
</div>
</div>
{/* Main Content */}
<div className="flex-1 self-stretch inline-flex flex-col justify-start items-start">
<div className="self-stretch p-5 inline-flex justify-start items-center gap-2.5">
<div className="flex-1 justify-start text-Neutrals-NeutralSlate800 dark:text-Neutrals-NeutralSlate100 text-xl font-medium font-['Neue_Montreal'] leading-normal">
Company Overview & Vision
</div>
</div>
<div className="self-stretch flex-1 p-5 flex justify-center items-center">
<div className="w-[440px] flex flex-col justify-center items-center gap-8">
{/* Progress Illustration Placeholder */}
<div className="w-[280px] h-[200px] bg-Text-Gray-100 dark:bg-Neutrals-NeutralSlate700 rounded-2xl flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 mx-auto mb-4 bg-Brand-Orange rounded-full flex items-center justify-center">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" className="text-white">
<path d="M16 8v8l4 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="16" cy="16" r="12" stroke="currentColor" strokeWidth="2" />
</svg>
</div>
<div className="text-Text-Gray-600 dark:text-Neutrals-NeutralSlate300 text-sm">Progress Illustration</div>
</div>
</div>
{/* Progress Content */}
<div className="self-stretch flex flex-col justify-center items-center gap-4 text-center">
<div className="text-Neutrals-NeutralSlate800 dark:text-Neutrals-NeutralSlate100 text-2xl font-semibold font-['Neue_Montreal'] leading-8">
You're {progress}% Done
</div>
<div className="self-stretch text-Text-Gray-600 dark:text-Neutrals-NeutralSlate300 text-base font-normal font-['Inter'] leading-normal">
Complete your company onboarding to unlock your company wiki and comprehensive insights about your organization.
</div>
{/* Progress Bar */}
<div className="self-stretch h-2 bg-Text-Gray-100 dark:bg-Neutrals-NeutralSlate700 rounded-full overflow-hidden">
<div
className="h-full bg-Brand-Orange rounded-full transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
{/* Action Button */}
<button
onClick={onCompleteOnboarding}
className="w-full px-6 py-3 bg-Brand-Orange hover:bg-orange-600 text-Neutrals-NeutralSlate0 text-base font-medium font-['Inter'] leading-normal rounded-xl transition-colors"
>
Complete Onboarding
</button>
</div>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,116 @@
import React, { useState } from 'react';
import { CompanyWikiEmptyState } from './CompanyWikiEmptyState';
import { CompanyWikiCompletedState } from './CompanyWikiCompletedState';
import { InviteEmployeesModal } from './InviteEmployeesModal';
import { InviteMultipleEmployeesModal } from './InviteMultipleEmployeesModal';
export type WikiState = 'empty' | 'completed';
export type InviteModalState = 'none' | 'single' | 'multiple';
interface Employee {
id: string;
name: string;
email: string;
avatar?: string;
}
interface CompanyWikiManagerProps {
initialState?: WikiState;
onboardingProgress?: number;
onCompleteOnboarding?: () => void;
qaItems?: Array<{ question: string; answer: string }>;
suggestedEmployees?: Employee[];
}
export const CompanyWikiManager: React.FC<CompanyWikiManagerProps> = ({
initialState = 'empty',
onboardingProgress = 60,
onCompleteOnboarding,
qaItems,
suggestedEmployees
}) => {
const [wikiState, setWikiState] = useState<WikiState>(initialState);
const [inviteModalState, setInviteModalState] = useState<InviteModalState>('none');
const [activeSection, setActiveSection] = useState(1);
const handleCompleteOnboarding = () => {
onCompleteOnboarding?.();
setWikiState('completed');
};
const handleInviteEmployee = (email: string) => {
console.log('Inviting employee:', email);
// Here you would typically call an API to send the invitation
setInviteModalState('none');
// You could show a success toast here
};
const handleInviteMultipleEmployees = (employees: Employee[]) => {
console.log('Inviting multiple employees:', employees);
// Here you would typically call an API to send multiple invitations
setInviteModalState('none');
// You could show a success toast here
};
const handleSectionClick = (section: number) => {
setActiveSection(section);
};
const handleCopyLink = () => {
// Copy wiki link to clipboard
const wikiUrl = `${window.location.origin}/#/company-wiki`;
navigator.clipboard.writeText(wikiUrl).then(() => {
console.log('Wiki link copied to clipboard');
// You could show a success toast here
});
};
const renderWikiContent = () => {
switch (wikiState) {
case 'empty':
return (
<CompanyWikiEmptyState
progress={onboardingProgress}
onCompleteOnboarding={handleCompleteOnboarding}
onInviteEmployees={() => setInviteModalState('single')}
onCopyLink={handleCopyLink}
/>
);
case 'completed':
return (
<CompanyWikiCompletedState
qaItems={qaItems}
activeSection={activeSection}
onSectionClick={handleSectionClick}
onInviteEmployees={() => setInviteModalState('single')}
onCopyLink={handleCopyLink}
/>
);
default:
return null;
}
};
return (
<div className="w-full h-full flex flex-col">
{renderWikiContent()}
{/* Modals */}
<InviteEmployeesModal
isOpen={inviteModalState === 'single'}
onClose={() => setInviteModalState('none')}
onInvite={handleInviteEmployee}
onMultipleInvite={() => setInviteModalState('multiple')}
/>
<InviteMultipleEmployeesModal
isOpen={inviteModalState === 'multiple'}
onClose={() => setInviteModalState('none')}
onInviteSelected={handleInviteMultipleEmployees}
suggestedEmployees={suggestedEmployees}
/>
</div>
);
};

View File

@@ -0,0 +1,92 @@
import React, { useState } from 'react';
interface InviteEmployeesModalProps {
isOpen: boolean;
onClose: () => void;
onInvite: (email: string) => void;
onMultipleInvite?: () => void;
}
export const InviteEmployeesModal: React.FC<InviteEmployeesModalProps> = ({
isOpen,
onClose,
onInvite,
onMultipleInvite
}) => {
const [email, setEmail] = useState('');
if (!isOpen) return null;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (email.trim()) {
onInvite(email.trim());
setEmail('');
}
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="w-[420px] bg-Neutrals-NeutralSlate0 dark:bg-Neutrals-NeutralSlate900 rounded-3xl shadow-[0px_25px_50px_-12px_rgba(0,0,0,0.25)] flex flex-col justify-start items-start overflow-hidden">
{/* Header */}
<div className="self-stretch p-6 inline-flex justify-between items-center">
<div className="flex justify-start items-center gap-2.5">
<div className="justify-start text-Text-Dark-950 dark:text-Neutrals-NeutralSlate50 text-lg font-semibold font-['Inter'] leading-7">Invite employees</div>
</div>
<button
onClick={onClose}
className="w-6 h-6 flex justify-center items-center hover:bg-Text-Gray-100 dark:hover:bg-Neutrals-NeutralSlate700 rounded"
>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M13 1L1 13M1 1L13 13" stroke="#666D80" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="self-stretch px-6 flex flex-col justify-start items-start gap-4">
<div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 dark:text-Neutrals-NeutralSlate50 text-sm font-medium font-['Inter'] leading-tight">
Email
</div>
<div className="self-stretch h-10 px-3 py-2 bg-Neutrals-NeutralSlate0 dark:bg-Neutrals-NeutralSlate800 rounded-lg border border-Outline-Outline-Gray-300 dark:border-Neutrals-NeutralSlate600 inline-flex justify-start items-center gap-2">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email address"
className="flex-1 text-Neutrals-NeutralSlate950 dark:text-Neutrals-NeutralSlate50 text-sm font-normal font-['Inter'] leading-tight bg-transparent outline-none placeholder:text-Text-Gray-500 dark:placeholder:text-Neutrals-NeutralSlate400"
autoFocus
/>
</div>
</div>
</form>
{/* Footer */}
<div className="self-stretch p-6 inline-flex justify-between items-center">
<button
onClick={onMultipleInvite}
className="text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight hover:underline"
>
Invite multiple employees
</button>
<div className="flex justify-start items-start gap-3">
<button
onClick={onClose}
className="px-4 py-2 bg-Neutrals-NeutralSlate0 dark:bg-Neutrals-NeutralSlate800 rounded-lg border border-Outline-Outline-Gray-300 dark:border-Neutrals-NeutralSlate600 text-Neutrals-NeutralSlate700 dark:text-Neutrals-NeutralSlate200 text-sm font-medium font-['Inter'] leading-tight hover:bg-Text-Gray-50 dark:hover:bg-Neutrals-NeutralSlate700"
>
Cancel
</button>
<button
onClick={handleSubmit}
disabled={!email.trim()}
className="px-4 py-2 bg-Brand-Orange rounded-lg text-Neutrals-NeutralSlate0 text-sm font-medium font-['Inter'] leading-tight hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
Send Invite
</button>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,178 @@
import React, { useState } from 'react';
interface Employee {
id: string;
name: string;
email: string;
avatar?: string;
}
interface InviteMultipleEmployeesModalProps {
isOpen: boolean;
onClose: () => void;
onInviteSelected: (employees: Employee[]) => void;
suggestedEmployees?: Employee[];
}
const defaultSuggestedEmployees: Employee[] = [
{ id: '1', name: 'John Smith', email: 'john@company.com' },
{ id: '2', name: 'Sarah Johnson', email: 'sarah@company.com' },
{ id: '3', name: 'Mike Chen', email: 'mike@company.com' },
{ id: '4', name: 'Emily Davis', email: 'emily@company.com' },
{ id: '5', name: 'Alex Rodriguez', email: 'alex@company.com' },
];
export const InviteMultipleEmployeesModal: React.FC<InviteMultipleEmployeesModalProps> = ({
isOpen,
onClose,
onInviteSelected,
suggestedEmployees = defaultSuggestedEmployees
}) => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedEmployees, setSelectedEmployees] = useState<Employee[]>([]);
const [showDropdown, setShowDropdown] = useState(false);
if (!isOpen) return null;
const filteredEmployees = suggestedEmployees.filter(emp =>
(emp.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
emp.email.toLowerCase().includes(searchTerm.toLowerCase())) &&
!selectedEmployees.find(selected => selected.id === emp.id)
);
const handleEmployeeSelect = (employee: Employee) => {
setSelectedEmployees(prev => [...prev, employee]);
setSearchTerm('');
setShowDropdown(false);
};
const handleEmployeeRemove = (employeeId: string) => {
setSelectedEmployees(prev => prev.filter(emp => emp.id !== employeeId));
};
const handleInvite = () => {
if (selectedEmployees.length > 0) {
onInviteSelected(selectedEmployees);
setSelectedEmployees([]);
setSearchTerm('');
}
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="w-[480px] bg-Neutrals-NeutralSlate0 dark:bg-Neutrals-NeutralSlate900 rounded-3xl shadow-[0px_25px_50px_-12px_rgba(0,0,0,0.25)] flex flex-col justify-start items-start overflow-hidden">
{/* Header */}
<div className="self-stretch p-6 inline-flex justify-between items-center">
<div className="flex justify-start items-center gap-2.5">
<div className="justify-start text-Text-Dark-950 dark:text-Neutrals-NeutralSlate50 text-lg font-semibold font-['Inter'] leading-7">
Invite multiple employees
</div>
</div>
<button
onClick={onClose}
className="w-6 h-6 flex justify-center items-center hover:bg-Text-Gray-100 dark:hover:bg-Neutrals-NeutralSlate700 rounded"
>
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path d="M13 1L1 13M1 1L13 13" stroke="#666D80" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
{/* Search Input with Dropdown */}
<div className="self-stretch px-6 flex flex-col justify-start items-start gap-4">
<div className="self-stretch flex flex-col justify-start items-start gap-1 relative">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">
Search employees
</div>
<div className="self-stretch h-10 px-3 py-2 bg-Neutrals-NeutralSlate0 rounded-lg border border-Outline-Outline-Gray-300 inline-flex justify-start items-center gap-2">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="text-Text-Gray-400">
<path d="M15 15L11.15 11.15M12.6667 7.33333C12.6667 10.2789 10.2789 12.6667 7.33333 12.6667C4.38781 12.6667 2 10.2789 2 7.33333C2 4.38781 4.38781 2 7.33333 2C10.2789 2 12.6667 4.38781 12.6667 7.33333Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<input
type="text"
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setShowDropdown(e.target.value.length > 0);
}}
onFocus={() => setShowDropdown(searchTerm.length > 0)}
placeholder="Type name or email to search..."
className="flex-1 text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight bg-transparent outline-none placeholder:text-Text-Gray-500"
/>
</div>
{/* Dropdown */}
{showDropdown && filteredEmployees.length > 0 && (
<div className="absolute top-full left-0 right-0 mt-1 bg-Neutrals-NeutralSlate0 border border-Outline-Outline-Gray-200 rounded-lg shadow-lg z-10 max-h-48 overflow-y-auto">
{filteredEmployees.map((employee) => (
<button
key={employee.id}
onClick={() => handleEmployeeSelect(employee)}
className="w-full px-3 py-2 text-left hover:bg-Text-Gray-50 flex items-center gap-3 border-b border-Text-Gray-100 last:border-b-0"
>
<div className="w-8 h-8 bg-Brand-Orange rounded-full flex items-center justify-center text-white text-sm font-medium">
{employee.name.charAt(0)}
</div>
<div className="flex-1">
<div className="text-sm font-medium text-Neutrals-NeutralSlate950">{employee.name}</div>
<div className="text-xs text-Text-Gray-500">{employee.email}</div>
</div>
</button>
))}
</div>
)}
</div>
{/* Selected Employees */}
{selectedEmployees.length > 0 && (
<div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="text-sm font-medium text-Neutrals-NeutralSlate950">
Selected ({selectedEmployees.length})
</div>
<div className="self-stretch flex flex-wrap gap-2">
{selectedEmployees.map((employee) => (
<div
key={employee.id}
className="px-3 py-1.5 bg-Brand-Orange bg-opacity-10 rounded-full flex items-center gap-2"
>
<div className="w-5 h-5 bg-Brand-Orange rounded-full flex items-center justify-center text-white text-xs font-medium">
{employee.name.charAt(0)}
</div>
<span className="text-sm text-Neutrals-NeutralSlate950">{employee.name}</span>
<button
onClick={() => handleEmployeeRemove(employee.id)}
className="w-4 h-4 flex items-center justify-center hover:bg-Brand-Orange hover:bg-opacity-20 rounded-full"
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<path d="M9 3L3 9M3 3L9 9" stroke="#666D80" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
))}
</div>
</div>
)}
</div>
{/* Footer */}
<div className="self-stretch p-6 inline-flex justify-end items-center">
<div className="flex justify-start items-start gap-3">
<button
onClick={onClose}
className="px-4 py-2 bg-Neutrals-NeutralSlate0 rounded-lg border border-Outline-Outline-Gray-300 text-Neutrals-NeutralSlate700 text-sm font-medium font-['Inter'] leading-tight hover:bg-Text-Gray-50"
>
Cancel
</button>
<button
onClick={handleInvite}
disabled={selectedEmployees.length === 0}
className="px-4 py-2 bg-Brand-Orange rounded-lg text-Neutrals-NeutralSlate0 text-sm font-medium font-['Inter'] leading-tight hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed"
>
Send Invites ({selectedEmployees.length})
</button>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,6 @@
export { CompanyWikiEmptyState } from './CompanyWikiEmptyState';
export { CompanyWikiCompletedState } from './CompanyWikiCompletedState';
export { InviteEmployeesModal } from './InviteEmployeesModal';
export { InviteMultipleEmployeesModal } from './InviteMultipleEmployeesModal';
export { CompanyWikiManager } from './CompanyWikiManager';
export type { WikiState, InviteModalState } from './CompanyWikiManager';