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

3
.gitignore vendored
View File

@@ -56,3 +56,6 @@ dist-ssr
/schema.json /schema.json
/SECURITY_FIXES.md /SECURITY_FIXES.md
/.claude /.claude
/deprecated
/figma-code
/*ignore.*

56
App.tsx
View File

@@ -8,9 +8,11 @@ import { Layout } from './components/UiKit';
import CompanyWiki from './pages/CompanyWiki'; import CompanyWiki from './pages/CompanyWiki';
import EmployeeData from './pages/EmployeeData'; import EmployeeData from './pages/EmployeeData';
import Chat from './pages/Chat'; import Chat from './pages/Chat';
import ChatNew from './pages/ChatNew';
import HelpNew from './pages/HelpNew';
import SettingsNew from './pages/SettingsNew';
import HelpAndSettings from './pages/HelpAndSettings'; import HelpAndSettings from './pages/HelpAndSettings';
import Login from './pages/Login'; import ModernLogin from './pages/Login';
import ModernLogin from './pages/ModernLogin';
import OrgSelection from './pages/OrgSelection'; import OrgSelection from './pages/OrgSelection';
import Onboarding from './pages/Onboarding'; import Onboarding from './pages/Onboarding';
import EmployeeQuestionnaire from './pages/EmployeeQuestionnaire'; import EmployeeQuestionnaire from './pages/EmployeeQuestionnaire';
@@ -20,7 +22,6 @@ import FormsDashboard from './pages/FormsDashboard';
import DebugEmployee from './pages/DebugEmployee'; import DebugEmployee from './pages/DebugEmployee';
import QuestionnaireComplete from './pages/QuestionnaireComplete'; import QuestionnaireComplete from './pages/QuestionnaireComplete';
import SubscriptionSetup from './pages/SubscriptionSetup'; import SubscriptionSetup from './pages/SubscriptionSetup';
import { isFirebaseConfigured } from './services/firebase';
const RequireAuth: React.FC<{ children: React.ReactNode }> = ({ children }) => { const RequireAuth: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { user, loading } = useAuth(); const { user, loading } = useAuth();
@@ -112,7 +113,6 @@ function App() {
<Route path="/login" element={<ModernLogin />} /> <Route path="/login" element={<ModernLogin />} />
<Route path="/login/:inviteCode" element={<ModernLogin />} /> <Route path="/login/:inviteCode" element={<ModernLogin />} />
<Route path="/invite/:inviteCode" element={<InviteRedirect />} /> <Route path="/invite/:inviteCode" element={<InviteRedirect />} />
<Route path="/legacy-login" element={<Login />} />
{/* Employee questionnaire - no auth needed, uses invite code */} {/* Employee questionnaire - no auth needed, uses invite code */}
<Route path="/employee-form/:inviteCode" element={<EmployeeQuestionnaire />} /> <Route path="/employee-form/:inviteCode" element={<EmployeeQuestionnaire />} />
@@ -180,6 +180,54 @@ function App() {
<Route path="/questionnaire-complete" element={<QuestionnaireComplete />} /> <Route path="/questionnaire-complete" element={<QuestionnaireComplete />} />
{/* New Figma Chat Implementation - Standalone route */}
<Route
path="/chat-new"
element={
<RequireAuth>
<RequireOrgSelection>
<OrgProviderWrapper>
<RequireOnboarding>
<ChatNew />
</RequireOnboarding>
</OrgProviderWrapper>
</RequireOrgSelection>
</RequireAuth>
}
/>
{/* New Figma Help Implementation - Standalone route */}
<Route
path="/help-new"
element={
<RequireAuth>
<RequireOrgSelection>
<OrgProviderWrapper>
<RequireOnboarding>
<HelpNew />
</RequireOnboarding>
</OrgProviderWrapper>
</RequireOrgSelection>
</RequireAuth>
}
/>
{/* New Figma Settings Implementation - Standalone route */}
<Route
path="/settings-new"
element={
<RequireAuth>
<RequireOrgSelection>
<OrgProviderWrapper>
<RequireOnboarding>
<SettingsNew />
</RequireOnboarding>
</OrgProviderWrapper>
</RequireOrgSelection>
</RequireAuth>
}
/>
{/* Main app routes - require auth, org selection, and completed onboarding */} {/* Main app routes - require auth, org selection, and completed onboarding */}
<Route <Route
element={ element={

0
OnboardingQuestions.ts Normal file
View File

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';

View File

@@ -4,6 +4,7 @@ import { useTheme } from '../contexts/ThemeContext';
import { Theme, NavItem } from '../types'; import { Theme, NavItem } from '../types';
import { useOrg } from '../contexts/OrgContext'; import { useOrg } from '../contexts/OrgContext';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
import FigmaSidebar from './figma/Sidebar';
// ========== ICONS ========== // ========== ICONS ==========
@@ -265,14 +266,18 @@ const Sidebar = () => {
); );
}; };
export const Layout = () => ( export const Layout = () => {
<div className="flex h-screen bg-[--background-primary]"> const { org } = useOrg();
<Sidebar />
return (
<div className="flex h-screen bg-Neutrals-NeutralSlate0">
<FigmaSidebar companyName={org?.name || "Auditly"} />
<main className="flex-1 overflow-y-auto"> <main className="flex-1 overflow-y-auto">
<Outlet /> <Outlet />
</main> </main>
</div> </div>
); );
};
// ========== UI PRIMITIVES ========== // ========== UI PRIMITIVES ==========

View File

@@ -0,0 +1,180 @@
import React from 'react';
interface SuggestionCardProps {
category: string;
title: string;
description: string;
icon: React.ReactNode;
onClick?: () => void;
}
const SuggestionCard: React.FC<SuggestionCardProps> = ({ category, title, description, icon, onClick }) => (
<div
className="p-4 bg-Neutrals-NeutralSlate0 rounded-2xl border border-Neutrals-NeutralSlate200 hover:border-Brand-Orange hover:shadow-sm transition-all cursor-pointer group"
onClick={onClick}
>
<div className="flex items-start gap-3">
<div className="w-10 h-10 bg-Brand-Orange/10 rounded-xl flex items-center justify-center text-Brand-Orange group-hover:bg-Brand-Orange group-hover:text-white transition-colors">
{icon}
</div>
<div className="flex-1 min-w-0">
<div className="text-xs text-Brand-Orange font-medium mb-1">{category}</div>
<div className="text-sm font-medium text-Neutrals-NeutralSlate950 mb-1">{title}</div>
<div className="text-xs text-Neutrals-NeutralSlate500 leading-relaxed">{description}</div>
</div>
</div>
</div>
);
interface CategoryTabProps {
label: string;
isActive: boolean;
onClick: () => void;
}
const CategoryTab: React.FC<CategoryTabProps> = ({ label, isActive, onClick }) => (
<button
onClick={onClick}
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${isActive
? 'bg-Brand-Orange text-white'
: 'bg-Neutrals-NeutralSlate100 text-Neutrals-NeutralSlate600 hover:bg-Neutrals-NeutralSlate200'
}`}
>
{label}
</button>
);
const ChatEmptyState: React.FC = () => {
const [activeCategory, setActiveCategory] = React.useState('All');
const categories = ['All', 'Performance', 'Culture', 'Reports', 'Analysis'];
const suggestions = [
{
category: 'Performance',
title: 'Analyze team performance trends',
description: 'Get insights on productivity patterns and improvement areas across your organization.',
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 17.5013V5.83464C2.5 5.36793 2.5 5.13458 2.59083 4.95631C2.67072 4.79951 2.79821 4.67202 2.95501 4.59213C3.13327 4.5013 3.36662 4.5013 3.83333 4.5013H5.16667C5.63338 4.5013 5.86673 4.5013 6.04499 4.59213C6.20179 4.67202 6.32928 4.79951 6.40917 4.95631C6.5 5.13458 6.5 5.36793 6.5 5.83464V17.5013M17.5 17.5013V9.16797C17.5 8.70126 17.5 8.46791 17.4092 8.28965C17.3293 8.13285 17.2018 8.00536 17.045 7.92547C16.8667 7.83464 16.6334 7.83464 16.1667 7.83464H14.8333C14.3666 7.83464 14.1333 7.83464 13.955 7.92547C13.7982 8.00536 13.6707 8.13285 13.5908 8.28965C13.5 8.46791 13.5 8.70126 13.5 9.16797V17.5013M12.5 17.5013V2.5013C12.5 2.03459 12.5 1.80124 12.4092 1.62298C12.3293 1.46618 12.2018 1.33869 12.045 1.2588C11.8667 1.16797 11.6334 1.16797 11.1667 1.16797H9.83333C9.36662 1.16797 9.13327 1.16797 8.95501 1.2588C8.79821 1.33869 8.67072 1.46618 8.59083 1.62298C8.5 1.80124 8.5 2.03459 8.5 2.5013V17.5013M18.3333 17.5013H1.66667" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
},
{
category: 'Culture',
title: 'Assess company culture health',
description: 'Review employee satisfaction, engagement levels, and cultural alignment metrics.',
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 5.83464C7.5 6.752 7.5 7.21068 7.70552 7.54611C7.88497 7.84313 8.15687 8.11503 8.45389 8.29448C8.78932 8.5 9.248 8.5 10.1654 8.5H11.5013C12.4187 8.5 12.8774 8.5 13.2128 8.29448C13.5098 8.11503 13.7817 7.84313 13.9612 7.54611C14.1667 7.21068 14.1667 6.752 14.1667 5.83464V4.16797C14.1667 3.25061 14.1667 2.79193 13.9612 2.4565C13.7817 2.15948 13.5098 1.88758 13.2128 1.70813C12.8774 1.5026 12.4187 1.5026 11.5013 1.5026H10.1654C9.248 1.5026 8.78932 1.5026 8.45389 1.70813C8.15687 1.88758 7.88497 2.15948 7.70552 2.4565C7.5 2.79193 7.5 3.25061 7.5 4.16797V5.83464Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M2.5 14.168C2.5 15.0854 2.5 15.544 2.70552 15.8795C2.88497 16.1765 3.15687 16.4484 3.45389 16.6278C3.78932 16.8333 4.248 16.8333 5.16536 16.8333H6.50131C7.41867 16.8333 7.87735 16.8333 8.21278 16.6278C8.5098 16.4484 8.7817 16.1765 8.96115 15.8795C9.16667 15.544 9.16667 15.0854 9.16667 14.168V12.5013C9.16667 11.5839 9.16667 11.1253 8.96115 10.7898C8.7817 10.4928 8.5098 10.2209 8.21278 10.0415C7.87735 9.83594 7.41867 9.83594 6.50131 9.83594H5.16536C4.248 9.83594 3.78932 9.83594 3.45389 10.0415C3.15687 10.2209 2.88497 10.4928 2.70552 10.7898C2.5 11.1253 2.5 11.5839 2.5 12.5013V14.168Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.8346 14.168C10.8346 15.0854 10.8346 15.544 11.0401 15.8795C11.2196 16.1765 11.4915 16.4484 11.7885 16.6278C12.1239 16.8333 12.5826 16.8333 13.5 16.8333H14.8359C15.7533 16.8333 16.212 16.8333 16.5474 16.6278C16.8444 16.4484 17.1163 16.1765 17.2958 15.8795C17.5013 15.544 17.5013 15.0854 17.5013 14.168V12.5013C17.5013 11.5839 17.5013 11.1253 17.2958 10.7898C17.1163 10.4928 16.8444 10.2209 16.5474 10.0415C16.212 9.83594 15.7533 9.83594 14.8359 9.83594H13.5C12.5826 9.83594 12.1239 9.83594 11.7885 10.0415C11.4915 10.2209 11.2196 10.4928 11.0401 10.7898C10.8346 11.1253 10.8346 11.5839 10.8346 12.5013V14.168Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
},
{
category: 'Reports',
title: 'Generate executive summary',
description: 'Create comprehensive reports on organizational strengths, risks, and recommendations.',
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6666 9.16797H6.66659M8.33325 12.5013H6.66659M13.3333 5.83464H6.66659M16.6666 5.66797V14.3346C16.6666 15.7348 16.6666 16.4348 16.3941 16.9696C16.1544 17.44 15.772 17.8225 15.3016 18.0622C14.7668 18.3346 14.0667 18.3346 12.6666 18.3346H7.33325C5.93312 18.3346 5.23306 18.3346 4.69828 18.0622C4.22787 17.8225 3.84542 17.44 3.60574 16.9696C3.33325 16.4348 3.33325 15.7348 3.33325 14.3346V5.66797C3.33325 4.26784 3.33325 3.56777 3.60574 3.03299C3.84542 2.56259 4.22787 2.18014 4.69828 1.94045C5.23306 1.66797 5.93312 1.66797 7.33325 1.66797H12.6666C14.0667 1.66797 14.7668 1.66797 15.3016 1.94045C15.772 2.18014 16.1544 2.56259 16.3941 3.03299C16.6666 3.56777 16.6666 4.26784 16.6666 5.66797Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
},
{
category: 'Analysis',
title: 'Compare department metrics',
description: 'Analyze cross-departmental performance and identify areas for improvement.',
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0001 1.66797C11.0944 1.66797 12.1781 1.88352 13.1891 2.30231C14.2002 2.7211 15.1188 3.33493 15.8926 4.10875C16.6665 4.88257 17.2803 5.80123 17.6991 6.81228C18.1179 7.82332 18.3334 8.90696 18.3334 10.0013M10.0001 1.66797V10.0013M10.0001 1.66797C5.39771 1.66797 1.66675 5.39893 1.66675 10.0013C1.66675 14.6037 5.39771 18.3346 10.0001 18.3346C14.6025 18.3346 18.3334 14.6037 18.3334 10.0013M10.0001 1.66797C14.6025 1.66797 18.3334 5.39893 18.3334 10.0013M18.3334 10.0013L10.0001 10.0013M18.3334 10.0013C18.3334 11.3164 18.0222 12.6128 17.4251 13.7846C16.8281 14.9563 15.9622 15.9701 14.8983 16.7431L10.0001 10.0013" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
},
{
category: 'Performance',
title: 'Review individual performance',
description: 'Deep dive into specific employee performance data and development opportunities.',
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0013 12.5C11.3821 12.5 12.5013 11.3807 12.5013 10C12.5013 8.61929 11.3821 7.5 10.0013 7.5C8.62061 7.5 7.50132 8.61929 7.50132 10C7.50132 11.3807 8.62061 12.5 10.0013 12.5Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.0013 1.66797C14.6037 1.66797 18.3346 5.39893 18.3346 10.0013C18.3346 14.6037 14.6037 18.3346 10.0013 18.3346C5.39893 18.3346 1.66797 14.6037 1.66797 10.0013C1.66797 5.39893 5.39893 1.66797 10.0013 1.66797Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
},
{
category: 'Culture',
title: 'Identify team dynamics',
description: 'Understand collaboration patterns, communication effectiveness, and team cohesion.',
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.168 5.83464C14.168 7.67561 12.675 9.16797 10.8346 9.16797C8.99367 9.16797 7.50131 7.67561 7.50131 5.83464C7.50131 3.99367 8.99367 2.5013 10.8346 2.5013C12.675 2.5013 14.168 3.99367 14.168 5.83464Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10.8346 11.668C7.52292 11.668 4.83594 14.3549 4.83594 17.6666H16.8346C16.8346 14.3549 14.1477 11.668 10.8346 11.668Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M5.83464 9.16797C5.83464 10.5488 4.71536 11.668 3.33464 11.668C1.95393 11.668 0.834635 10.5488 0.834635 9.16797C0.834635 7.78725 1.95393 6.66797 3.33464 6.66797C4.71536 6.66797 5.83464 7.78725 5.83464 9.16797Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M3.33594 13.3346C1.49497 13.3346 0.00260794 14.827 0.00260794 16.668H6.66927C6.66927 15.7686 6.35594 14.9346 5.83594 14.2513" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
}
];
const filteredSuggestions = activeCategory === 'All'
? suggestions
: suggestions.filter(s => s.category === activeCategory);
const handleSuggestionClick = (suggestion: any) => {
// Handle suggestion click - could pass this up to parent component
console.log('Clicked suggestion:', suggestion.title);
};
return (
<div className="flex flex-col items-center justify-center min-h-[60vh] px-4">
{/* Welcome Message */}
<div className="text-center mb-8 max-w-2xl">
<h2 className="text-2xl font-semibold text-Neutrals-NeutralSlate950 mb-3">
Welcome to Auditly Chat
</h2>
<p className="text-Neutrals-NeutralSlate600 text-lg leading-relaxed">
Ask me anything about your team's performance, company culture, or organizational insights.
I can analyze employee data, generate reports, and provide actionable recommendations.
</p>
</div>
{/* Category Tabs */}
<div className="flex flex-wrap gap-2 mb-6">
{categories.map((category) => (
<CategoryTab
key={category}
label={category}
isActive={activeCategory === category}
onClick={() => setActiveCategory(category)}
/>
))}
</div>
{/* Suggestion Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 max-w-6xl w-full">
{filteredSuggestions.map((suggestion, index) => (
<SuggestionCard
key={index}
category={suggestion.category}
title={suggestion.title}
description={suggestion.description}
icon={suggestion.icon}
onClick={() => handleSuggestionClick(suggestion)}
/>
))}
</div>
{/* Additional Help Text */}
<div className="mt-8 text-center text-sm text-Neutrals-NeutralSlate500 max-w-xl">
<p>
You can also upload files, mention specific employees using @, or ask custom questions about your organization.
I'll provide insights based on your team's data and industry best practices.
</p>
</div>
</div>
);
};
export default ChatEmptyState;

View File

@@ -0,0 +1,171 @@
import React, { useState, useEffect, useRef } from 'react';
import { useOrg } from '../../contexts/OrgContext';
import { Employee } from '../../types';
import ChatSidebar from './ChatSidebar';
import MessageThread from './MessageThread';
import FileUploadInput from './FileUploadInput';
interface Message {
id: string;
text: string;
isUser: boolean;
timestamp: number;
files?: string[];
}
interface ChatLayoutProps {
children?: React.ReactNode;
}
const ChatLayout: React.FC<ChatLayoutProps> = ({ children }) => {
const { employees } = useOrg();
const [selectedEmployees, setSelectedEmployees] = useState<Employee[]>([]);
const [messages, setMessages] = useState<Message[]>([]);
const [inputValue, setInputValue] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
scrollToBottom();
}, [messages]);
const handleNavigation = (page: string) => {
// Handle navigation to different pages
console.log('Navigate to:', page);
};
const handleSendMessage = async () => {
if (!inputValue.trim() && uploadedFiles.length === 0) return;
const userMessage: Message = {
id: Date.now().toString(),
text: inputValue,
isUser: true,
timestamp: Date.now(),
files: uploadedFiles.length > 0 ? [...uploadedFiles] : undefined
};
setMessages(prev => [...prev, userMessage]);
setInputValue('');
setUploadedFiles([]);
setIsLoading(true);
// Simulate AI response
setTimeout(() => {
const aiMessage: Message = {
id: (Date.now() + 1).toString(),
text: "I understand you're asking about the employee data. Based on the information provided, I can help analyze the performance metrics and provide insights.\n\nHere are some key findings from your team's data:\n\n• **Performance Trends**: Overall team productivity has increased by 15% this quarter\n• **Cultural Health**: Employee satisfaction scores are above industry average\n• **Areas for Growth**: Communication and cross-team collaboration could be improved\n\nWould you like me to dive deeper into any of these areas?",
isUser: false,
timestamp: Date.now()
};
setMessages(prev => [...prev, aiMessage]);
setIsLoading(false);
}, 2000);
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
};
const handleRemoveFile = (index: number) => {
setUploadedFiles(prev => prev.filter((_, i) => i !== index));
};
const handleFilesSelected = (files: File[]) => {
// For demo purposes, we'll just add the file names
// In a real implementation, you'd upload the files and get URLs back
const fileNames = files.map(file => file.name);
setUploadedFiles(prev => [...prev, ...fileNames]);
};
const hasMessages = messages.length > 0;
return (
<div className="w-full h-screen bg-Neutrals-NeutralSlate0 inline-flex overflow-hidden">
{/* Sidebar */}
<ChatSidebar currentPage="chat" onNavigate={handleNavigation} />
{/* Main Content Area */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Header with Employee Selection */}
<div className="px-6 py-4 bg-Neutrals-NeutralSlate0 border-b border-Neutrals-NeutralSlate200 flex justify-between items-center">
<div className="flex items-center gap-3">
<h1 className="text-xl font-semibold text-Neutrals-NeutralSlate950">Chat</h1>
{selectedEmployees.length > 0 && (
<div className="flex items-center gap-2">
<span className="text-sm text-Neutrals-NeutralSlate500">Analyzing:</span>
<div className="flex items-center gap-1">
{selectedEmployees.slice(0, 3).map((emp, index) => (
<div key={emp.id} className="px-2 py-1 bg-Brand-Orange/10 rounded-full text-xs text-Brand-Orange">
{emp.name}
</div>
))}
{selectedEmployees.length > 3 && (
<div className="px-2 py-1 bg-Neutrals-NeutralSlate100 rounded-full text-xs text-Neutrals-NeutralSlate600">
+{selectedEmployees.length - 3} more
</div>
)}
</div>
</div>
)}
</div>
</div>
{/* Messages Area */}
<div className="flex-1 overflow-y-auto px-6 py-4">
{hasMessages ? (
<div className="max-w-4xl mx-auto">
<MessageThread
messages={messages}
isLoading={isLoading}
/>
<div ref={messagesEndRef} />
</div>
) : (
children
)}
</div>
{/* Input Area */}
<div className="px-6 py-4 bg-Neutrals-NeutralSlate0 border-t border-Neutrals-NeutralSlate200">
<div className="max-w-4xl mx-auto">
<div className="flex items-end gap-3">
<FileUploadInput
value={inputValue}
onChange={setInputValue}
onKeyDown={handleKeyPress}
placeholder="Ask about your team's performance, culture, or any insights..."
disabled={isLoading}
uploadedFiles={uploadedFiles}
onRemoveFile={handleRemoveFile}
onFilesSelected={handleFilesSelected}
/>
{/* Send Button */}
<button
onClick={handleSendMessage}
disabled={!inputValue.trim() && uploadedFiles.length === 0}
className="px-4 py-3 bg-Brand-Orange text-white rounded-xl hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex-shrink-0"
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.3346 1.66797L9.16797 10.8346M18.3346 1.66797L12.5013 18.3346L9.16797 10.8346M18.3346 1.66797L1.66797 7.5013L9.16797 10.8346" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default ChatLayout;

View File

@@ -0,0 +1,263 @@
import React from 'react';
import { useOrg } from '../../contexts/OrgContext';
interface NavItemProps {
icon: React.ReactNode;
label: string;
isActive?: boolean;
onClick?: () => void;
}
const NavItem: React.FC<NavItemProps> = ({ icon, label, isActive, onClick }) => (
<div
className={`w-60 px-4 py-2.5 ${isActive ? 'bg-Neutrals-NeutralSlate100' : ''} rounded-[34px] inline-flex justify-start items-center gap-2 cursor-pointer hover:bg-Neutrals-NeutralSlate50 transition-colors`}
onClick={onClick}
>
{icon}
<div className={`justify-start text-sm font-medium font-['Inter'] leading-tight ${isActive ? 'text-Neutrals-NeutralSlate950' : 'text-Neutrals-NeutralSlate500'}`}>
{label}
</div>
</div>
);
interface ChatSidebarProps {
currentPage?: string;
onNavigate?: (page: string) => void;
}
const ChatSidebar: React.FC<ChatSidebarProps> = ({ currentPage = 'chat', onNavigate }) => {
const { org } = useOrg();
const handleNavigation = (page: string) => {
if (onNavigate) {
onNavigate(page);
}
};
return (
<div className="w-64 self-stretch max-w-64 min-w-64 px-3 pt-4 pb-3 bg-Neutrals-NeutralSlate0 border-r border-Neutrals-NeutralSlate200 inline-flex flex-col justify-between items-center overflow-hidden">
{/* Company Selection */}
<div className="self-stretch flex flex-col justify-start items-start gap-5">
<div className="w-60 pl-2 pr-4 py-2 bg-Neutrals-NeutralSlate0 rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-between items-center overflow-hidden">
<div className="flex-1 flex justify-start items-center gap-2">
<div className="w-8 h-8 rounded-full flex justify-start items-center gap-2.5">
<div className="w-8 h-8 relative bg-Brand-Orange rounded-full outline outline-[1.60px] outline-offset-[-1.60px] outline-white/10 overflow-hidden">
<div className="w-8 h-8 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[8.80px] top-[7.20px] absolute">
{/* Company Icon SVG */}
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_1042_694)">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M4.34342 10.6855C4.66998 11.0162 4.66998 11.5524 4.34341 11.8831L4.32669 11.9C4.00012 12.2307 3.47065 12.2307 3.14409 11.9C2.81753 11.5693 2.81753 11.0331 3.1441 10.7024L3.16082 10.6855C3.48739 10.3548 4.01686 10.3548 4.34342 10.6855Z" fill="url(#paint0_linear_1042_694)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M8.27545 10.9405C8.60142 11.2718 8.60046 11.808 8.27331 12.1381L5.95697 14.4752C5.62981 14.8053 5.10035 14.8043 4.77437 14.473C4.4484 14.1417 4.44936 13.6056 4.77651 13.2755L7.09285 10.9383C7.42001 10.6082 7.94947 10.6092 8.27545 10.9405Z" fill="url(#paint1_linear_1042_694)" />
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M11.4179 14.9631C11.6741 14.574 12.1932 14.4688 12.5775 14.7282L12.6277 14.7621C13.012 15.0215 13.1158 15.5473 12.8596 15.9364C12.6034 16.3255 12.0842 16.4307 11.7 16.1713L11.6498 16.1374C11.2655 15.878 11.1617 15.3522 11.4179 14.9631Z" fill="url(#paint2_linear_1042_694)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M16.9376 10.6347C17.2642 10.9654 17.2642 11.5016 16.9376 11.8323L15.8003 12.9839C15.4738 13.3146 14.9443 13.3146 14.6177 12.9839C14.2912 12.6532 14.2912 12.1171 14.6177 11.7864L15.755 10.6347C16.0816 10.304 16.611 10.304 16.9376 10.6347Z" fill="url(#paint3_linear_1042_694)" />
<path fillRule="evenodd" clipRule="evenodd" d="M16.9544 6.37693C17.2809 6.70762 17.2809 7.24378 16.9544 7.57447L8.55033 16.0847C8.22376 16.4154 7.69429 16.4154 7.36773 16.0847C7.04116 15.754 7.04116 15.2179 7.36773 14.8872L15.7718 6.37693C16.0983 6.04623 16.6278 6.04623 16.9544 6.37693Z" fill="url(#paint4_linear_1042_694)" />
<path fillRule="evenodd" clipRule="evenodd" d="M15.3649 3.75974C15.6915 4.09043 15.6915 4.62659 15.3649 4.95728L10.5315 9.85174C10.205 10.1824 9.67549 10.1824 9.34893 9.85174C9.02236 9.52104 9.02236 8.98489 9.34893 8.65419L14.1823 3.75974C14.5089 3.42905 15.0383 3.42905 15.3649 3.75974Z" fill="url(#paint5_linear_1042_694)" />
<path fillRule="evenodd" clipRule="evenodd" d="M12.8146 2.09918C13.1414 2.42965 13.1417 2.96581 12.8154 3.29672L6.60224 9.59685C6.27589 9.92777 5.74642 9.92813 5.41964 9.59766C5.09285 9.26719 5.0925 8.73103 5.41884 8.40011L11.632 2.09998C11.9583 1.76907 12.4878 1.76871 12.8146 2.09918Z" fill="url(#paint6_linear_1042_694)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M6.66127 4.11624C6.98727 4.4475 6.98636 4.98366 6.65923 5.31378L4.22582 7.76948C3.89869 8.0996 3.36923 8.09868 3.04322 7.76741C2.71722 7.43615 2.71813 6.9 3.04526 6.56987L5.47867 4.11418C5.8058 3.78405 6.33526 3.78498 6.66127 4.11624Z" fill="url(#paint7_linear_1042_694)" />
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M8.15116 1.66406C8.613 1.66406 8.98739 2.04318 8.98739 2.51085V2.59553C8.98739 3.0632 8.613 3.44232 8.15116 3.44232C7.68933 3.44232 7.31494 3.0632 7.31494 2.59553V2.51085C7.31494 2.04318 7.68933 1.66406 8.15116 1.66406Z" fill="url(#paint8_linear_1042_694)" />
</g>
<defs>
<filter id="filter0_d_1042_694" x="0.399316" y="-0.400781" width="19.2006" height="22.4016" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feMorphology radius="1.2" operator="erode" in="SourceAlpha" result="effect1_dropShadow_1042_694" />
<feOffset dy="1.8" />
<feGaussianBlur stdDeviation="1.8" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix type="matrix" values="0 0 0 0 0.141176 0 0 0 0 0.141176 0 0 0 0 0.141176 0 0 0 0.1 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1042_694" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1042_694" result="shape" />
</filter>
<linearGradient id="paint0_linear_1042_694" x1="3.74376" y1="10.4375" x2="3.74376" y2="12.148" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint1_linear_1042_694" x1="6.52491" y1="10.6914" x2="6.52491" y2="14.7221" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint2_linear_1042_694" x1="12.1387" y1="14.5859" x2="12.1387" y2="16.3136" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint3_linear_1042_694" x1="15.7777" y1="10.3867" x2="15.7777" y2="13.2319" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint4_linear_1042_694" x1="12.161" y1="6.12891" x2="12.161" y2="16.3327" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint5_linear_1042_694" x1="12.3569" y1="3.51172" x2="12.3569" y2="10.0998" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint6_linear_1042_694" x1="9.11711" y1="1.85156" x2="9.11711" y2="9.84527" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint7_linear_1042_694" x1="4.85224" y1="3.86719" x2="4.85224" y2="8.01647" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint8_linear_1042_694" x1="8.15117" y1="1.66406" x2="8.15117" y2="3.44232" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
</div>
</div>
</div>
<div className="flex-1 inline-flex flex-col justify-start items-start gap-0.5">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-base font-medium font-['Inter'] leading-normal">
{org?.name || 'Zitlac Media'}
</div>
</div>
</div>
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.83325 12.4987L9.99992 16.6654L14.1666 12.4987M5.83325 7.4987L9.99992 3.33203L14.1666 7.4987" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
{/* Navigation Menu */}
<div className="self-stretch flex flex-col justify-start items-start gap-5">
<div className="self-stretch flex flex-col justify-start items-start gap-1.5">
<NavItem
icon={
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 17.5016V11.3349C7.5 10.8682 7.5 10.6348 7.59083 10.4566C7.67072 10.2998 7.79821 10.1723 7.95501 10.0924C8.13327 10.0016 8.36662 10.0016 8.83333 10.0016H11.1667C11.6334 10.0016 11.8667 10.0016 12.045 10.0924C12.2018 10.1723 12.3293 10.2998 12.4092 10.4566C12.5 10.6348 12.5 10.8682 12.5 11.3349V17.5016M9.18141 2.30492L3.52949 6.70086C3.15168 6.99471 2.96278 7.14163 2.82669 7.32563C2.70614 7.48862 2.61633 7.67224 2.56169 7.86746C2.5 8.08785 2.5 8.32717 2.5 8.8058V14.8349C2.5 15.7683 2.5 16.235 2.68166 16.5916C2.84144 16.9052 3.09641 17.1601 3.41002 17.3199C3.76654 17.5016 4.23325 17.5016 5.16667 17.5016H14.8333C15.7668 17.5016 16.2335 17.5016 16.59 17.3199C16.9036 17.1601 17.1586 16.9052 17.3183 16.5916C17.5 16.235 17.5 15.7683 17.5 14.8349V8.8058C17.5 8.32717 17.5 8.08785 17.4383 7.86746C17.3837 7.67224 17.2939 7.48862 17.1733 7.32563C17.0372 7.14163 16.8483 6.99471 16.4705 6.70086L10.8186 2.30492C10.5258 2.07721 10.3794 1.96335 10.2178 1.91959C10.0752 1.88097 9.92484 1.88097 9.78221 1.91959C9.62057 1.96335 9.47418 2.07721 9.18141 2.30492Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
}
label="Company Wiki"
isActive={currentPage === 'wiki'}
onClick={() => handleNavigation('wiki')}
/>
<NavItem
icon={
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6666 9.16797H6.66659M8.33325 12.5013H6.66659M13.3333 5.83464H6.66659M16.6666 5.66797V14.3346C16.6666 15.7348 16.6666 16.4348 16.3941 16.9696C16.1544 17.44 15.772 17.8225 15.3016 18.0622C14.7668 18.3346 14.0667 18.3346 12.6666 18.3346H7.33325C5.93312 18.3346 5.23306 18.3346 4.69828 18.0622C4.22787 17.8225 3.84542 17.44 3.60574 16.9696C3.33325 16.4348 3.33325 15.7348 3.33325 14.3346V5.66797C3.33325 4.26784 3.33325 3.56777 3.60574 3.03299C3.84542 2.56259 4.22787 2.18014 4.69828 1.94045C5.23306 1.66797 5.93312 1.66797 7.33325 1.66797H12.6666C14.0667 1.66797 14.7668 1.66797 15.3016 1.94045C15.772 2.18014 16.1544 2.56259 16.3941 3.03299C16.6666 3.56777 16.6666 4.26784 16.6666 5.66797Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
}
label="Submissions"
isActive={currentPage === 'submissions'}
onClick={() => handleNavigation('submissions')}
/>
<NavItem
icon={
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_720)">
<path d="M10.0001 1.66797C11.0944 1.66797 12.1781 1.88352 13.1891 2.30231C14.2002 2.7211 15.1188 3.33493 15.8926 4.10875C16.6665 4.88257 17.2803 5.80123 17.6991 6.81228C18.1179 7.82332 18.3334 8.90696 18.3334 10.0013M10.0001 1.66797V10.0013M10.0001 1.66797C5.39771 1.66797 1.66675 5.39893 1.66675 10.0013C1.66675 14.6037 5.39771 18.3346 10.0001 18.3346C14.6025 18.3346 18.3334 14.6037 18.3334 10.0013M10.0001 1.66797C14.6025 1.66797 18.3334 5.39893 18.3334 10.0013M18.3334 10.0013L10.0001 10.0013M18.3334 10.0013C18.3334 11.3164 18.0222 12.6128 17.4251 13.7846C16.8281 14.9563 15.9622 15.9701 14.8983 16.7431L10.0001 10.0013" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_1042_720">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
}
label="Reports"
isActive={currentPage === 'reports'}
onClick={() => handleNavigation('reports')}
/>
<NavItem
icon={
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.4996 9.58333C17.4996 13.4953 14.3283 16.6667 10.4163 16.6667C9.51896 16.6667 8.66061 16.4998 7.87057 16.1954C7.72612 16.1398 7.6539 16.112 7.59647 16.0987C7.53998 16.0857 7.49908 16.0803 7.44116 16.0781C7.38226 16.0758 7.31764 16.0825 7.18841 16.0958L2.92089 16.537C2.51402 16.579 2.31059 16.6001 2.19058 16.5269C2.08606 16.4631 2.01487 16.3566 1.99592 16.2356C1.97416 16.0968 2.07138 15.9168 2.2658 15.557L3.62885 13.034C3.7411 12.8262 3.79723 12.7223 3.82265 12.6225C3.84776 12.5238 3.85383 12.4527 3.8458 12.3512C3.83766 12.2484 3.79258 12.1147 3.70241 11.8472C3.46281 11.1363 3.33294 10.375 3.33294 9.58333C3.33294 5.67132 6.50426 2.5 10.4163 2.5C14.3283 2.5 17.4996 5.67132 17.4996 9.58333Z" stroke="var(--Brand-Orange, #3399FF)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
}
label="Chat"
isActive={currentPage === 'chat'}
onClick={() => handleNavigation('chat')}
/>
<NavItem
icon={
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_728)">
<path d="M7.57508 7.5013C7.771 6.94436 8.15771 6.47472 8.66671 6.17558C9.17571 5.87643 9.77416 5.76708 10.3561 5.8669C10.938 5.96671 11.4658 6.26924 11.846 6.72091C12.2262 7.17258 12.4343 7.74424 12.4334 8.33464C12.4334 10.0013 9.93342 10.8346 9.93342 10.8346M10.0001 14.168H10.0084M18.3334 10.0013C18.3334 14.6037 14.6025 18.3346 10.0001 18.3346C5.39771 18.3346 1.66675 14.6037 1.66675 10.0013C1.66675 5.39893 5.39771 1.66797 10.0001 1.66797C14.6025 1.66797 18.3334 5.39893 18.3334 10.0013Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_1042_728">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
}
label="Help"
isActive={currentPage === 'help'}
onClick={() => handleNavigation('help')}
/>
</div>
</div>
</div>
{/* Bottom Section with Settings and Company Report Builder */}
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<NavItem
icon={
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_733)">
<path d="M10.0001 12.5013C11.3808 12.5013 12.5001 11.382 12.5001 10.0013C12.5001 8.62059 11.3808 7.5013 10.0001 7.5013C8.61937 7.5013 7.50008 8.62059 7.50008 10.0013C7.50008 11.382 8.61937 12.5013 10.0001 12.5013Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.6061 12.274C15.5053 12.5025 15.4752 12.756 15.5198 13.0017C15.5643 13.2475 15.6815 13.4743 15.8561 13.6528L15.9016 13.6983C16.0425 13.839 16.1542 14.0061 16.2305 14.19C16.3067 14.374 16.346 14.5711 16.346 14.7702C16.346 14.9694 16.3067 15.1665 16.2305 15.3505C16.1542 15.5344 16.0425 15.7015 15.9016 15.8422C15.7609 15.9831 15.5938 16.0948 15.4098 16.1711C15.2259 16.2473 15.0287 16.2866 14.8296 16.2866C14.6305 16.2866 14.4334 16.2473 14.2494 16.1711C14.0655 16.0948 13.8984 15.9831 13.7577 15.8422L13.7122 15.7968C13.5337 15.6221 13.3069 15.505 13.0611 15.4604C12.8154 15.4158 12.5619 15.4459 12.3334 15.5468C12.1093 15.6428 11.9183 15.8022 11.7836 16.0055C11.649 16.2087 11.5768 16.4469 11.5758 16.6907V16.8195C11.5758 17.2213 11.4162 17.6067 11.1321 17.8909C10.8479 18.175 10.4625 18.3346 10.0607 18.3346C9.65884 18.3346 9.27346 18.175 8.98931 17.8909C8.70517 17.6067 8.54554 17.2213 8.54554 16.8195V16.7513C8.53967 16.5005 8.45851 16.2574 8.31259 16.0533C8.16667 15.8493 7.96276 15.6939 7.72735 15.6074C7.49886 15.5065 7.24539 15.4764 6.99964 15.521C6.75388 15.5656 6.52711 15.6827 6.34857 15.8574L6.30311 15.9028C6.1624 16.0437 5.99529 16.1554 5.81135 16.2317C5.62742 16.3079 5.43026 16.3472 5.23114 16.3472C5.03203 16.3472 4.83487 16.3079 4.65093 16.2317C4.46699 16.1554 4.29989 16.0437 4.15917 15.9028C4.0183 15.7621 3.90654 15.595 3.83029 15.4111C3.75405 15.2271 3.7148 15.03 3.7148 14.8308C3.7148 14.6317 3.75405 14.4346 3.83029 14.2506C3.90654 14.0667 4.0183 13.8996 4.15917 13.7589L4.20463 13.7134C4.37928 13.5349 4.49643 13.3081 4.54099 13.0624C4.58555 12.8166 4.55547 12.5631 4.45463 12.3346C4.35859 12.1106 4.19914 11.9195 3.99589 11.7849C3.79264 11.6503 3.55447 11.578 3.31069 11.5771H3.1819C2.78006 11.5771 2.39467 11.4174 2.11053 11.1333C1.82638 10.8491 1.66675 10.4638 1.66675 10.0619C1.66675 9.66007 1.82638 9.27468 2.11053 8.99053C2.39467 8.70639 2.78006 8.54676 3.1819 8.54676H3.25008C3.50083 8.54089 3.74402 8.45973 3.94804 8.31381C4.15205 8.1679 4.30744 7.96398 4.39402 7.72858C4.49487 7.50008 4.52495 7.24661 4.48039 7.00086C4.43583 6.7551 4.31867 6.52833 4.14402 6.34979L4.09857 6.30433C3.95769 6.16362 3.84594 5.99651 3.76969 5.81258C3.69344 5.62864 3.65419 5.43148 3.65419 5.23236C3.65419 5.03325 3.69344 4.83609 3.76969 4.65215C3.84594 4.46821 3.95769 4.30111 4.09857 4.16039C4.23928 4.01952 4.40639 3.90776 4.59032 3.83151C4.77426 3.75527 4.97142 3.71602 5.17054 3.71602C5.36965 3.71602 5.56681 3.75527 5.75075 3.83151C5.93469 3.90776 6.10179 4.01952 6.24251 4.16039L6.28796 4.20585C6.46651 4.3805 6.69328 4.49765 6.93903 4.54221C7.18478 4.58677 7.43825 4.55669 7.66675 4.45585H7.72735C7.95142 4.35982 8.14252 4.20036 8.27712 3.99711C8.41172 3.79386 8.48396 3.55569 8.48493 3.31191V3.18312C8.48493 2.78128 8.64456 2.39589 8.92871 2.11175C9.21285 1.8276 9.59824 1.66797 10.0001 1.66797C10.4019 1.66797 10.7873 1.8276 11.0715 2.11175C11.3556 2.39589 11.5152 2.78128 11.5152 3.18312V3.2513C11.5162 3.49508 11.5884 3.73325 11.723 3.9365C11.8576 4.13975 12.0487 4.29921 12.2728 4.39524C12.5013 4.49609 12.7548 4.52617 13.0005 4.48161C13.2463 4.43705 13.4731 4.31989 13.6516 4.14524L13.6971 4.09979C13.8378 3.95891 14.0049 3.84716 14.1888 3.77091C14.3727 3.69466 14.5699 3.65541 14.769 3.65541C14.9681 3.65541 15.1653 3.69466 15.3492 3.77091C15.5332 3.84716 15.7003 3.95891 15.841 4.09979C15.9819 4.2405 16.0936 4.40761 16.1699 4.59154C16.2461 4.77548 16.2854 4.97264 16.2854 5.17176C16.2854 5.37087 16.2461 5.56803 16.1699 5.75197C16.0936 5.93591 15.9819 6.10301 15.841 6.24373L15.7955 6.28918C15.6209 6.46773 15.5037 6.6945 15.4592 6.94025C15.4146 7.186 15.4447 7.43947 15.5455 7.66797V7.72858C15.6416 7.95264 15.801 8.14374 16.0043 8.27834C16.2075 8.41295 16.4457 8.48518 16.6895 8.48615H16.8183C17.2201 8.48615 17.6055 8.64578 17.8896 8.92993C18.1738 9.21407 18.3334 9.59946 18.3334 10.0013C18.3334 10.4031 18.1738 10.7885 17.8896 11.0727C17.6055 11.3568 17.2201 11.5165 16.8183 11.5165H16.7501C16.5063 11.5174 16.2681 11.5897 16.0649 11.7243C15.8616 11.8589 15.7022 12.05 15.6061 12.274Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_1042_733">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
}
label="Settings"
isActive={currentPage === 'settings'}
onClick={() => handleNavigation('settings')}
/>
{/* Company Report Builder Card */}
<div className="self-stretch bg-Neutrals-NeutralSlate0 rounded-[20px] shadow-[0px_1px_4px_0px_rgba(14,18,27,0.04)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 flex flex-col justify-start items-start overflow-hidden">
<div className="self-stretch h-24 relative">
<div className="w-60 h-32 left-0 top-[-0.50px] absolute bg-gradient-to-b from-black to-black/0" />
<div className="w-60 p-3 left-[18.12px] top-[42.52px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
<div className="w-60 p-3 left-[31.44px] top-[22px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
</div>
<div className="self-stretch p-3 flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-sm font-semibold font-['Inter'] leading-tight">
Build {org?.name || '[Company]'}'s Report
</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-xs font-normal font-['Inter'] leading-none">
Share this form with your team members to capture valuable info about your company to train Auditly.
</div>
</div>
<div className="self-stretch px-3 pb-3 flex flex-col justify-start items-start gap-8">
<div className="self-stretch inline-flex justify-start items-start gap-2">
<div className="flex-1 px-3 py-1.5 bg-Button-Secondary rounded-[999px] flex justify-center items-center gap-0.5 overflow-hidden">
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Invite</div>
</div>
<div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99992 3.33203V12.6654M3.33325 7.9987H12.6666" stroke="var(--Neutrals-NeutralSlate950, #0A0D12)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
<div className="flex-1 px-3 py-1.5 bg-Brand-Orange rounded-[999px] outline outline-1 outline-offset-[-1px] outline-blue-400 flex justify-center items-center gap-0.5 overflow-hidden">
<div className="relative">
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.97179 12.2442L8.02898 13.1871C6.72723 14.4888 4.61668 14.4888 3.31493 13.1871C2.01319 11.8853 2.01319 9.77476 3.31493 8.47301L4.25774 7.5302M12.743 8.47301L13.6858 7.5302C14.9876 6.22845 14.9876 4.1179 13.6858 2.81615C12.3841 1.51441 10.2735 1.51441 8.97179 2.81615L8.02898 3.75896M6.16705 10.3349L10.8337 5.66826" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Copy</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ChatSidebar;

View File

@@ -0,0 +1,244 @@
import React, { useState, useRef, useCallback } from 'react';
interface FileUploadPreviewProps {
files: string[];
onRemoveFile: (index: number) => void;
}
const FileUploadPreview: React.FC<FileUploadPreviewProps> = ({ files, onRemoveFile }) => {
if (files.length === 0) return null;
const getFileIcon = (fileName: string) => {
const extension = fileName.split('.').pop()?.toLowerCase();
switch (extension) {
case 'pdf':
return (
<div className="w-6 h-6 bg-red-500 rounded flex items-center justify-center">
<span className="text-white text-xs font-bold">P</span>
</div>
);
case 'doc':
case 'docx':
return (
<div className="w-6 h-6 bg-blue-500 rounded flex items-center justify-center">
<span className="text-white text-xs font-bold">W</span>
</div>
);
case 'xls':
case 'xlsx':
return (
<div className="w-6 h-6 bg-green-500 rounded flex items-center justify-center">
<span className="text-white text-xs font-bold">E</span>
</div>
);
case 'jpg':
case 'jpeg':
case 'png':
case 'gif':
return (
<div className="w-6 h-6 bg-purple-500 rounded flex items-center justify-center">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 9L3.5 6.5L5 8L8.5 4.5L11 7M1 1H11V11H1V1Z" stroke="white" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
);
default:
return (
<div className="w-6 h-6 bg-gray-500 rounded flex items-center justify-center">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1H2C1.44772 1 1 1.44772 1 2V10C1 10.5523 1.44772 11 2 11H10C10.5523 11 11 10.5523 11 10V5M7 1L11 5M7 1V5H11" stroke="white" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
);
}
};
return (
<div className="mb-3 flex flex-wrap gap-2">
{files.map((file, index) => (
<div
key={index}
className="inline-flex items-center gap-2 px-3 py-2 bg-Neutrals-NeutralSlate100 rounded-lg hover:bg-Neutrals-NeutralSlate150 transition-colors group"
>
{getFileIcon(file)}
<span className="text-sm text-Neutrals-NeutralSlate700 max-w-[150px] truncate">{file}</span>
<button
onClick={() => onRemoveFile(index)}
className="w-5 h-5 text-Neutrals-NeutralSlate400 hover:text-red-500 hover:bg-red-50 rounded transition-colors flex items-center justify-center"
title="Remove file"
>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 3L3 9M3 3L9 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
))}
</div>
);
};
interface FileUploadDropzoneProps {
onFilesSelected: (files: File[]) => void;
children: React.ReactNode;
accept?: string;
multiple?: boolean;
disabled?: boolean;
}
const FileUploadDropzone: React.FC<FileUploadDropzoneProps> = ({
onFilesSelected,
children,
accept = "*/*",
multiple = true,
disabled = false
}) => {
const [isDragOver, setIsDragOver] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
if (!disabled) {
setIsDragOver(true);
}
}, [disabled]);
const handleDragLeave = useCallback((e: React.DragEvent) => {
e.preventDefault();
setIsDragOver(false);
}, []);
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
setIsDragOver(false);
if (disabled) return;
const files = Array.from(e.dataTransfer.files);
if (files.length > 0) {
onFilesSelected(files);
}
}, [onFilesSelected, disabled]);
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
if (files.length > 0) {
onFilesSelected(files);
}
// Reset input value to allow selecting the same file again
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
}, [onFilesSelected]);
const handleClick = () => {
if (!disabled && fileInputRef.current) {
fileInputRef.current.click();
}
};
return (
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={handleClick}
className={`
relative cursor-pointer transition-all
${isDragOver ? 'opacity-80' : ''}
${disabled ? 'cursor-not-allowed opacity-50' : ''}
`}
>
<input
ref={fileInputRef}
type="file"
accept={accept}
multiple={multiple}
onChange={handleFileSelect}
className="hidden"
disabled={disabled}
/>
{children}
{/* Drag overlay */}
{isDragOver && (
<div className="absolute inset-0 bg-Brand-Orange/10 border-2 border-dashed border-Brand-Orange rounded-xl flex items-center justify-center">
<div className="text-Brand-Orange font-medium">Drop files here</div>
</div>
)}
</div>
);
};
interface FileUploadInputProps {
value: string;
onChange: (value: string) => void;
onKeyDown?: (e: React.KeyboardEvent) => void;
placeholder?: string;
disabled?: boolean;
uploadedFiles: string[];
onRemoveFile: (index: number) => void;
onFilesSelected: (files: File[]) => void;
}
const FileUploadInput: React.FC<FileUploadInputProps> = ({
value,
onChange,
onKeyDown,
placeholder = "Ask about your team's performance, culture, or any insights...",
disabled = false,
uploadedFiles,
onRemoveFile,
onFilesSelected
}) => {
const handleFilesSelected = (files: File[]) => {
// For demo purposes, we'll just add the file names
// In a real implementation, you'd upload the files and get URLs back
const fileNames = files.map(file => file.name);
onFilesSelected(files);
};
return (
<div className="w-full">
{/* File Upload Preview */}
<FileUploadPreview files={uploadedFiles} onRemoveFile={onRemoveFile} />
{/* Input Field with File Upload */}
<FileUploadDropzone
onFilesSelected={handleFilesSelected}
disabled={disabled}
accept=".pdf,.doc,.docx,.xls,.xlsx,.txt,.jpg,.jpeg,.png,.gif"
>
<div className="relative flex items-end gap-3">
<div className="flex-1 relative">
<textarea
value={value}
onChange={(e) => onChange(e.target.value)}
onKeyDown={onKeyDown}
placeholder={placeholder}
disabled={disabled}
className="w-full min-h-[44px] max-h-32 px-4 py-3 pr-12 border border-Neutrals-NeutralSlate200 rounded-xl resize-none focus:outline-none focus:border-Brand-Orange focus:ring-1 focus:ring-Brand-Orange disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
rows={1}
/>
{/* File Upload Button */}
<button
type="button"
disabled={disabled}
className="absolute right-3 top-3 w-6 h-6 text-Neutrals-NeutralSlate400 hover:text-Brand-Orange disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
title="Upload files"
>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 15V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V15M17 8L12 3M12 3L7 8M12 3V15" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
</div>
</div>
</FileUploadDropzone>
</div>
);
};
export default FileUploadInput;
export { FileUploadPreview, FileUploadDropzone };

View File

@@ -0,0 +1,118 @@
import React from 'react';
interface Message {
id: string;
text: string;
isUser: boolean;
timestamp: number;
files?: string[];
}
interface MessageBubbleProps {
message: Message;
}
const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
const formatTime = (timestamp: number) => {
return new Date(timestamp).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit'
});
};
if (message.isUser) {
return (
<div className="flex justify-end mb-4">
<div className="max-w-[70%] flex flex-col items-end">
<div className="bg-Brand-Orange text-white px-4 py-3 rounded-2xl rounded-br-md">
{message.files && message.files.length > 0 && (
<div className="mb-2 flex flex-wrap gap-2">
{message.files.map((file, index) => (
<div key={index} className="px-2 py-1 bg-white/20 rounded text-xs">
📎 {file}
</div>
))}
</div>
)}
<div className="text-sm leading-relaxed">{message.text}</div>
</div>
<div className="text-xs text-Neutrals-NeutralSlate400 mt-1">
{formatTime(message.timestamp)}
</div>
</div>
</div>
);
}
return (
<div className="flex justify-start mb-4">
<div className="max-w-[85%] flex items-start gap-3">
{/* AI Avatar */}
<div className="w-8 h-8 bg-gradient-to-br from-Brand-Orange to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2C8.73438 2 9.375 2.64062 9.375 3.375V4.5C9.375 5.23438 8.73438 5.875 8 5.875C7.26562 5.875 6.625 5.23438 6.625 4.5V3.375C6.625 2.64062 7.26562 2 8 2ZM8 10.125C8.73438 10.125 9.375 10.7656 9.375 11.5V12.625C9.375 13.3594 8.73438 14 8 14C7.26562 14 6.625 13.3594 6.625 12.625V11.5C6.625 10.7656 7.26562 10.125 8 10.125ZM12.625 6.625C13.3594 6.625 14 7.26562 14 8C14 8.73438 13.3594 9.375 12.625 9.375H11.5C10.7656 9.375 10.125 8.73438 10.125 8C10.125 7.26562 10.7656 6.625 11.5 6.625H12.625ZM5.875 8C5.875 8.73438 5.23438 9.375 4.5 9.375H3.375C2.64062 9.375 2 8.73438 2 8C2 7.26562 2.64062 6.625 3.375 6.625H4.5C5.23438 6.625 5.875 7.26562 5.875 8Z" fill="white" />
</svg>
</div>
<div className="flex flex-col">
<div className="bg-Neutrals-NeutralSlate100 text-Neutrals-NeutralSlate950 px-4 py-3 rounded-2xl rounded-bl-md">
<div className="text-sm leading-relaxed whitespace-pre-wrap">{message.text}</div>
</div>
<div className="text-xs text-Neutrals-NeutralSlate400 mt-1">
AI {formatTime(message.timestamp)}
</div>
</div>
</div>
</div>
);
};
interface LoadingIndicatorProps {
className?: string;
}
const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({ className = '' }) => (
<div className={`flex justify-start mb-4 ${className}`}>
<div className="flex items-start gap-3">
{/* AI Avatar */}
<div className="w-8 h-8 bg-gradient-to-br from-Brand-Orange to-orange-600 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2C8.73438 2 9.375 2.64062 9.375 3.375V4.5C9.375 5.23438 8.73438 5.875 8 5.875C7.26562 5.875 6.625 5.23438 6.625 4.5V3.375C6.625 2.64062 7.26562 2 8 2ZM8 10.125C8.73438 10.125 9.375 10.7656 9.375 11.5V12.625C9.375 13.3594 8.73438 14 8 14C7.26562 14 6.625 13.3594 6.625 12.625V11.5C6.625 10.7656 7.26562 10.125 8 10.125ZM12.625 6.625C13.3594 6.625 14 7.26562 14 8C14 8.73438 13.3594 9.375 12.625 9.375H11.5C10.7656 9.375 10.125 8.73438 10.125 8C10.125 7.26562 10.7656 6.625 11.5 6.625H12.625ZM5.875 8C5.875 8.73438 5.23438 9.375 4.5 9.375H3.375C2.64062 9.375 2 8.73438 2 8C2 7.26562 2.64062 6.625 3.375 6.625H4.5C5.23438 6.625 5.875 7.26562 5.875 8Z" fill="white" />
</svg>
</div>
<div className="bg-Neutrals-NeutralSlate100 px-4 py-3 rounded-2xl rounded-bl-md">
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<div className="w-2 h-2 bg-Neutrals-NeutralSlate400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
</div>
</div>
</div>
);
interface MessageThreadProps {
messages: Message[];
isLoading?: boolean;
className?: string;
}
const MessageThread: React.FC<MessageThreadProps> = ({
messages,
isLoading = false,
className = ''
}) => {
return (
<div className={`flex flex-col ${className}`}>
{messages.map((message) => (
<MessageBubble key={message.id} message={message} />
))}
{isLoading && <LoadingIndicator />}
</div>
);
};
export default MessageThread;
export { MessageBubble, LoadingIndicator };

5
components/chat/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export { default as ChatSidebar } from './ChatSidebar';
export { default as ChatLayout } from './ChatLayout';
export { default as ChatEmptyState } from './ChatEmptyState';
export { default as MessageThread } from './MessageThread';
export { default as FileUploadInput } from './FileUploadInput';

View File

@@ -1,106 +1,225 @@
import React from 'react'; import React, { ReactNode } from 'react';
import { EmployeeQuestion } from '../../employeeQuestions';
import { QuestionInput } from '../ui/QuestionInput';
interface EnhancedFigmaQuestionProps { interface FigmaQuestionProps {
questionNumber?: string | number; question: string;
question: EmployeeQuestion; description?: string;
answer?: string; children: ReactNode;
onAnswerChange?: (value: string) => void;
onBack?: () => void; onBack?: () => void;
onNext?: () => void; onNext?: () => void;
showNavigation?: boolean; nextDisabled?: boolean;
nextLabel?: string; backDisabled?: boolean;
backLabel?: string; nextText?: string;
backText?: string;
showBackButton?: boolean;
currentStep?: number;
totalSteps?: number;
stepTitle?: string;
className?: string; className?: string;
} }
export const EnhancedFigmaQuestion: React.FC<EnhancedFigmaQuestionProps> = ({ export const EnhancedFigmaQuestion: React.FC<FigmaQuestionProps> = ({
questionNumber = 'Q',
question, question,
answer = '', description,
onAnswerChange, children,
onBack, onBack,
onNext, onNext,
showNavigation = true, nextDisabled = false,
nextLabel = 'Next', backDisabled = false,
backLabel = 'Back', nextText = "Next",
className = '' backText = "Back",
showBackButton = true,
currentStep = 1,
totalSteps = 8,
stepTitle = "Company Overview & Mission.",
className = ""
}) => {
// Generate the progress indicator dots
const renderProgressDots = () => {
const dots = [];
for (let i = 0; i < totalSteps; i++) {
if (i < currentStep - 1) {
// Completed steps - elongated orange
dots.push(
<div key={i} className="w-6 h-1 bg-Brand-Orange rounded-3xl" />
);
} else if (i === currentStep - 1) {
// Current step - elongated orange
dots.push(
<div key={i} className="w-6 h-1 bg-Brand-Orange rounded-3xl" />
);
} else {
// Future steps - small gray circles
dots.push(
<div key={i} data-svg-wrapper>
<svg width="4" height="4" viewBox="0 0 4 4" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="4" height="4" rx="2" fill="var(--Neutrals-NeutralSlate300, #D5D7DA)" />
</svg>
</div>
);
}
}
return dots;
};
return (
<div className={`w-[1440px] h-[810px] py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9 ${className}`}>
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal">
{question}
</div>
{children}
</div>
<div className="self-stretch inline-flex justify-start items-start gap-2">
{showBackButton && (
<div
data-property-1="Secondary"
data-show-icon-left="false"
data-show-icon-right="false"
data-show-text="true"
data-size="Big"
className={`h-12 px-8 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden ${backDisabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:bg-Neutrals-NeutralSlate200'}`}
onClick={!backDisabled ? onBack : undefined}
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">
{backText}
</div>
</div>
</div>
)}
<div
data-property-1="Primiary"
data-show-icon-left="false"
data-show-icon-right="false"
data-show-text="true"
data-size="Big"
className={`flex-1 h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden ${nextDisabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer hover:opacity-90'}`}
onClick={!nextDisabled ? onNext : undefined}
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">
{nextText}
</div>
</div>
</div>
</div>
</div>
{/* Step indicator - top left */}
<div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none">
{currentStep} of {totalSteps}
</div>
</div>
{/* Skip button - top right */}
<div className="px-3 py-1.5 left-[1363px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden cursor-pointer hover:bg-Neutrals-NeutralSlate200">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-none">
Skip
</div>
</div>
{/* Progress indicator and title - top center */}
<div className="w-[464px] max-w-[464px] min-w-[464px] left-[488px] top-[24px] absolute flex flex-col justify-start items-center gap-4">
<div className="p-4 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
{renderProgressDots()}
</div>
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate500 text-base font-medium font-['Neue_Montreal'] leading-normal">
{stepTitle}
</div>
</div>
</div>
);
};
// Question Card Component (for Q&A style layout)
interface FigmaQuestionCardProps {
question: string;
description?: string;
children: ReactNode;
className?: string;
}
export const FigmaQuestionCard: React.FC<FigmaQuestionCardProps> = ({
question,
description,
children,
className = ""
}) => { }) => {
return ( return (
<div className={`w-full max-w-[600px] px-5 pt-5 pb-6 bg-white dark:bg-gray-800 rounded-2xl border border-gray-200 dark:border-gray-700 inline-flex flex-col justify-end items-end gap-4 ${className}`}> <div className={`w-full px-5 pt-5 pb-6 bg-Other-White rounded-2xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-end items-end gap-4 ${className}`}>
{/* Question Header */}
<div className="self-stretch inline-flex justify-start items-start gap-3"> <div className="self-stretch inline-flex justify-start items-start gap-3">
<div className="justify-start text-gray-400 text-xl font-medium font-['Inter'] leading-loose"> <div className="justify-start text-zinc-300 text-xl font-medium font-['Inter'] leading-loose">Q</div>
{questionNumber}
</div>
<div className="flex-1 inline-flex flex-col justify-start items-start gap-2"> <div className="flex-1 inline-flex flex-col justify-start items-start gap-2">
<div className="self-stretch justify-start text-[--text-primary] text-xl font-semibold font-['Inter'] leading-loose"> <div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-xl font-semibold font-['Inter'] leading-loose">
{question.prompt} {question}
</div> </div>
<div className="self-stretch justify-start text-[--text-secondary] text-sm font-normal font-['Inter'] leading-tight"> {description && (
{question.required ? 'Required' : 'Optional'} {question.category} <div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-sm font-normal font-['Inter'] leading-tight">
{question.type && `${question.type}`} {description}
</div>
)}
</div> </div>
</div> </div>
</div> <div className="self-stretch h-0 rotate-90 shadow-[0px_1.5px_1.5px_0px_rgba(255,255,255,1.00)] outline outline-1 outline-offset-[-0.50px] border-Neutrals-NeutralSlate200" />
<div className="self-stretch inline-flex justify-start items-center gap-3">
{/* Separator */} <div className="justify-start text-zinc-300 text-xl font-medium font-['Inter'] leading-loose">A</div>
<div className="self-stretch h-px bg-gray-200 dark:bg-gray-700"></div>
{/* Answer Section */}
<div className="self-stretch inline-flex justify-start items-start gap-3">
<div className="justify-start text-gray-400 text-xl font-medium font-['Inter'] leading-loose">A</div>
<div className="flex-1"> <div className="flex-1">
<QuestionInput {children}
question={question} </div>
value={answer} </div>
onChange={onAnswerChange || (() => { })} </div>
className="border-0 bg-transparent focus:ring-0 p-0" );
};
// Enhanced input that matches Figma designs
interface EnhancedFigmaInputProps {
placeholder?: string;
value?: string;
onChange?: (value: string) => void;
multiline?: boolean;
rows?: number;
className?: string;
}
export const EnhancedFigmaInput: React.FC<EnhancedFigmaInputProps> = ({
placeholder = "Type your answer....",
value = "",
onChange,
multiline = false,
rows = 4,
className = ""
}) => {
const baseClasses = "self-stretch min-h-40 p-5 relative bg-Neutrals-NeutralSlate100 rounded-xl inline-flex justify-start items-start gap-2.5 overflow-hidden";
const inputClasses = "flex-1 justify-start text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal bg-transparent border-none outline-none resize-none";
if (multiline) {
return (
<div className={`${baseClasses} ${className}`}>
<textarea
value={value}
onChange={(e) => onChange?.(e.target.value)}
placeholder={placeholder}
rows={rows}
className={inputClasses}
/> />
<div className="w-3 h-3 absolute right-[18px] bottom-[18px]">
<div className="w-2 h-2 left-[2px] top-[2px] absolute outline outline-1 outline-offset-[-0.50px] outline-Neutrals-NeutralSlate500" />
<div className="w-1 h-1 left-[7px] top-[7px] absolute outline outline-1 outline-offset-[-0.50px] outline-Neutrals-NeutralSlate500" />
</div> </div>
</div> </div>
);
}
{/* Navigation */} return (
{showNavigation && ( <div className={`${baseClasses} ${className}`}>
<div className="inline-flex justify-start items-center gap-3"> <input
{onBack && ( value={value}
<button onChange={(e) => onChange?.(e.target.value)}
onClick={onBack} placeholder={placeholder}
className="px-4 py-3.5 bg-gray-100 dark:bg-gray-700 rounded-full flex justify-center items-center gap-1 overflow-hidden hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors" className={inputClasses}
> />
<div className="relative">
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 15L8 10L13 5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-[--text-primary] text-sm font-medium font-['Inter'] leading-tight">
{backLabel}
</div>
</div>
</button>
)}
{onNext && (
<button
onClick={onNext}
className="px-4 py-3.5 bg-blue-500 rounded-full border-2 border-blue-400 flex justify-center items-center gap-1 overflow-hidden hover:bg-blue-600 transition-colors"
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-white text-sm font-medium font-['Inter'] leading-tight">
{nextLabel}
</div>
</div>
<div className="relative">
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 15L13 10L8 5" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</button>
)}
</div>
)}
</div> </div>
); );
}; };

View File

@@ -11,6 +11,7 @@ interface FigmaInputProps {
onButtonClick?: () => void; onButtonClick?: () => void;
className?: string; className?: string;
required?: boolean; required?: boolean;
showLabel?: boolean;
} }
export const FigmaInput: React.FC<FigmaInputProps> = ({ export const FigmaInput: React.FC<FigmaInputProps> = ({
@@ -23,20 +24,24 @@ export const FigmaInput: React.FC<FigmaInputProps> = ({
buttonText, buttonText,
onButtonClick, onButtonClick,
className = '', className = '',
required = false required = false,
showLabel = true
}) => { }) => {
return ( return (
<div className={`w-[464px] inline-flex flex-col justify-start items-start gap-2 ${className}`}> <div className={`self-stretch flex flex-col justify-start items-start gap-2 ${className}`}>
{label && ( {showLabel && label && (
<div className="self-stretch inline-flex justify-start items-center gap-0.5"> <div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate800 text-sm font-medium font-['Inter'] leading-tight"> <div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">
{label} {required && <span className="text-red-500">*</span>} {label}
</div> </div>
{required && (
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div>
)}
</div> </div>
)} )}
<div className="self-stretch inline-flex justify-start items-start gap-2"> <div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="flex-1 px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-start items-center gap-2 overflow-hidden"> <div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
{icon && ( {icon && (
<div data-svg-wrapper className="relative"> <div data-svg-wrapper className="relative">
{icon} {icon}
@@ -47,9 +52,10 @@ export const FigmaInput: React.FC<FigmaInputProps> = ({
value={value} value={value}
onChange={onChange} onChange={onChange}
placeholder={placeholder} placeholder={placeholder}
className="flex-1 bg-transparent outline-none text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500" className="flex-1 justify-start text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight bg-transparent border-none outline-none placeholder:text-Neutrals-NeutralSlate500"
/> />
</div> </div>
</div>
{buttonText && ( {buttonText && (
<button <button
@@ -62,6 +68,64 @@ export const FigmaInput: React.FC<FigmaInputProps> = ({
</button> </button>
)} )}
</div> </div>
);
};
// Select component for dropdowns
interface FigmaSelectProps {
label?: string;
value?: string;
onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
options: { value: string; label: string }[];
className?: string;
required?: boolean;
placeholder?: string;
}
export const FigmaSelect: React.FC<FigmaSelectProps> = ({
label,
value,
onChange,
options,
className = '',
required = false,
placeholder = 'Select option'
}) => {
return (
<div className={`self-stretch flex flex-col justify-start items-start gap-2 ${className}`}>
{label && (
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">
{label}
</div>
{required && (
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div>
)}
</div>
)}
<div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<select
value={value}
onChange={onChange}
className="flex-1 justify-start text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight bg-transparent border-none outline-none appearance-none"
>
<option value="" className="text-Neutrals-NeutralSlate500">{placeholder}</option>
{options.map((option, index) => (
<option key={index} value={option.value}>
{option.label}
</option>
))}
</select>
{/* Custom dropdown arrow */}
<div className="pointer-events-none">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 6L8 10L12 6" stroke="var(--Neutrals-NeutralSlate500)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
</div>
</div> </div>
); );
}; };

View File

@@ -0,0 +1,42 @@
import React from 'react';
interface FigmaMultipleChoiceProps {
options: string[];
selectedValue?: string;
onSelect?: (value: string) => void;
className?: string;
}
export const FigmaMultipleChoice: React.FC<FigmaMultipleChoiceProps> = ({
options,
selectedValue,
onSelect,
className = ""
}) => {
return (
<div className={`self-stretch inline-flex justify-center items-center gap-3 ${className}`}>
{options.map((option, index) => {
const isSelected = selectedValue === option;
return (
<div
key={index}
className={`flex-1 h-20 relative rounded-[999px] overflow-hidden cursor-pointer transition-colors ${isSelected
? 'bg-Neutrals-NeutralSlate800'
: 'bg-Neutrals-NeutralSlate100 hover:bg-Neutrals-NeutralSlate200'
}`}
onClick={() => onSelect?.(option)}
>
<div className={`absolute inset-0 flex items-center justify-center text-center text-base font-normal font-['Inter'] leading-normal ${isSelected
? 'text-Neutrals-NeutralSlate0'
: 'text-Neutrals-NeutralSlate950'
}`}>
{option}
</div>
</div>
);
})}
</div>
);
};
export default FigmaMultipleChoice;

View File

@@ -124,4 +124,155 @@ export const FigmaQuestion: React.FC<FigmaQuestionProps> = ({
); );
}; };
export default FigmaQuestion;
// Progress Bar Component
export const FigmaProgressBar: React.FC<{ currentStep: number; totalSteps: number }> = ({ currentStep, totalSteps }) => {
return (
<div className="p-4 bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
{Array.from({ length: totalSteps }, (_, index) => {
const isActive = index < currentStep;
const isFirst = index === 0;
return (
<div key={index}>
<svg width={isFirst ? "24" : "4"} height="4" viewBox={`0 0 ${isFirst ? "24" : "4"} 4`} fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width={isFirst ? "24" : "4"} height="4" rx="2" fill={isActive ? "var(--Brand-Orange, #5E48FC)" : "var(--Neutrals-NeutralSlate300, #D5D7DA)"} />
</svg>
</div>
);
})}
</div>
);
};
// Rating Scale Component (1-10)
export const FigmaRatingScale: React.FC<{
question: string;
leftLabel: string;
rightLabel: string;
value?: number;
onChange: (value: number) => void;
scale?: number;
}> = ({ question, leftLabel, rightLabel, value, onChange, scale = 10 }) => {
return (
<div className="w-[1440px] h-[810px] py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-center items-center gap-12">
<div className="flex flex-col justify-center items-center gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal">{question}</div>
<div className="inline-flex justify-center items-center gap-3">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">{leftLabel}</div>
{Array.from({ length: scale }, (_, index) => {
const number = index + 1;
const isSelected = value === number;
return (
<div
key={number}
onClick={() => onChange(number)}
className={`w-12 h-12 relative rounded-[576.35px] overflow-hidden cursor-pointer transition-colors ${isSelected ? 'bg-Brand-Orange' : 'bg-Neutrals-NeutralSlate100 hover:bg-Neutrals-NeutralSlate200'
}`}
>
<div className={`absolute inset-0 flex items-center justify-center text-xl font-medium font-['Inter'] leading-7 ${isSelected ? 'text-white' : 'text-Neutrals-NeutralSlate950'
}`}>
{number}
</div>
</div>
);
})}
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">{rightLabel}</div>
</div>
</div>
</div>
</div>
);
};
// Text Area Component
export const FigmaTextArea: React.FC<{
question: string;
value?: string;
onChange: (value: string) => void;
placeholder?: string;
}> = ({ question, value, onChange, placeholder = "Type your answer...." }) => {
return (
<div className="w-[1440px] h-[810px] py-6 relative bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-9">
<div className="w-full max-w-[464px] min-w-[464px] flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch text-center justify-start text-Neutrals-NeutralSlate950 text-2xl font-medium font-['Neue_Montreal'] leading-normal">{question}</div>
<div className="self-stretch min-h-40 p-5 relative bg-Neutrals-NeutralSlate100 rounded-xl inline-flex justify-start items-start gap-2.5">
<textarea
value={value}
onChange={(e) => onChange(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-base font-normal font-['Inter'] leading-normal placeholder:text-Neutrals-NeutralSlate500 outline-none resize-none"
placeholder={placeholder}
rows={6}
/>
<div className="w-3 h-3 absolute right-5 bottom-5">
<div className="w-2 h-2 absolute top-0.5 left-0.5 outline outline-1 outline-offset-[-0.50px] outline-Neutrals-NeutralSlate500" />
<div className="w-1 h-1 absolute bottom-0 right-0 outline outline-1 outline-offset-[-0.50px] outline-Neutrals-NeutralSlate500" />
</div>
</div>
</div>
</div>
</div>
);
};
// Navigation Buttons Component
export const FigmaNavigationButtons: React.FC<{
onBack?: () => void;
onNext: () => void;
onSkip?: () => void;
nextDisabled?: boolean;
currentStep?: number;
totalSteps?: number;
}> = ({ onBack, onNext, onSkip, nextDisabled = false, currentStep, totalSteps }) => {
return (
<>
{/* Progress indicator */}
{currentStep && totalSteps && (
<div className="px-3 py-1.5 left-[24px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] uppercase leading-none">{currentStep} of {totalSteps}</div>
</div>
)}
{/* Skip button */}
{onSkip && (
<div
onClick={onSkip}
className="px-3 py-1.5 left-[1363px] top-[24px] absolute bg-Neutrals-NeutralSlate100 rounded-[50px] inline-flex justify-center items-center gap-2 overflow-hidden cursor-pointer hover:bg-Neutrals-NeutralSlate200"
>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-none">Skip</div>
</div>
)}
{/* Progress bar */}
{currentStep && totalSteps && (
<div className="w-[464px] max-w-[464px] min-w-[464px] left-[488px] top-[24px] absolute flex flex-col justify-start items-center gap-4">
<FigmaProgressBar currentStep={currentStep} totalSteps={totalSteps} />
</div>
)}
{/* Navigation buttons */}
<div className="self-stretch inline-flex justify-start items-start gap-2">
{onBack && (
<button
onClick={onBack}
className="h-12 px-8 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-Neutrals-NeutralSlate200"
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Back</div>
</div>
</button>
)}
<button
onClick={onNext}
disabled={nextDisabled}
className="flex-1 h-12 px-4 py-3.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Next</div>
</div>
</button>
</div>
</>
);
};

View File

@@ -0,0 +1,241 @@
import React from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
interface SidebarProps {
companyName?: string;
collapsed?: boolean;
}
export default function Sidebar({ companyName = "Zitlac Media", collapsed = false }: SidebarProps) {
const location = useLocation();
const navigate = useNavigate();
const navItems = [
{
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 17.5V11.3333C7.5 10.8666 7.5 10.6333 7.59083 10.455C7.67072 10.2982 7.79821 10.1707 7.95501 10.0908C8.13327 9.99999 8.36662 9.99999 8.83333 9.99999H11.1667C11.6334 9.99999 11.8667 9.99999 12.045 10.0908C12.2018 10.1707 12.3293 10.2982 12.4092 10.455C12.5 10.6333 12.5 10.8666 12.5 11.3333V17.5M9.18141 2.30333L3.52949 6.69927C3.15168 6.99312 2.96278 7.14005 2.82669 7.32405C2.70614 7.48704 2.61633 7.67065 2.56169 7.86588C2.5 8.08627 2.5 8.32558 2.5 8.80421V14.8333C2.5 15.7667 2.5 16.2335 2.68166 16.59C2.84144 16.9036 3.09641 17.1585 3.41002 17.3183C3.76654 17.5 4.23325 17.5 5.16667 17.5H14.8333C15.7668 17.5 16.2335 17.5 16.59 17.3183C16.9036 17.1585 17.1586 16.9036 17.3183 16.59C17.5 16.2335 17.5 15.7667 17.5 14.8333V8.80421C17.5 8.32558 17.5 8.08627 17.4383 7.86588C17.3837 7.67065 17.2939 7.48704 17.1733 7.32405C17.0372 7.14005 16.8483 6.99312 16.4705 6.69927L10.8186 2.30333C10.5258 2.07562 10.3794 1.96177 10.2178 1.918C10.0752 1.87938 9.92484 1.87938 9.78221 1.918C9.62057 1.96177 9.47418 2.07562 9.18141 2.30333Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
),
label: "Company Wiki",
path: "/company-wiki",
active: location.pathname === "/company-wiki"
},
{
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6663 9.16666H6.66634M8.33301 12.5H6.66634M13.333 5.83332H6.66634M16.6663 5.66666V14.3333C16.6663 15.7335 16.6663 16.4335 16.3939 16.9683C16.1542 17.4387 15.7717 17.8212 15.3013 18.0608C14.7665 18.3333 14.0665 18.3333 12.6663 18.3333H7.33301C5.93288 18.3333 5.23281 18.3333 4.69803 18.0608C4.22763 17.8212 3.84517 17.4387 3.60549 16.9683C3.33301 16.4335 3.33301 15.7335 3.33301 14.3333V5.66666C3.33301 4.26653 3.33301 3.56646 3.60549 3.03168C3.84517 2.56128 4.22763 2.17882 4.69803 1.93914C5.23281 1.66666 5.93288 1.66666 7.33301 1.66666H12.6663C14.0665 1.66666 14.7665 1.66666 15.3013 1.93914C15.7717 2.17882 16.1542 2.56128 16.3939 3.03168C16.6663 3.56646 16.6663 4.26653 16.6663 5.66666Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
),
label: "Submissions",
path: "/submissions",
active: location.pathname === "/submissions"
},
{
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_914_468)">
<path d="M10.0003 1.66666C11.0947 1.66666 12.1783 1.8822 13.1894 2.30099C14.2004 2.71978 15.1191 3.33361 15.8929 4.10744C16.6667 4.88126 17.2805 5.79992 17.6993 6.81097C18.1181 7.82201 18.3337 8.90565 18.3337 10M10.0003 1.66666V9.99999M10.0003 1.66666C5.39795 1.66666 1.66699 5.39762 1.66699 9.99999C1.66699 14.6024 5.39795 18.3333 10.0003 18.3333C14.6027 18.3333 18.3337 14.6024 18.3337 10M10.0003 1.66666C14.6027 1.66666 18.3337 5.39762 18.3337 10M18.3337 10L10.0003 9.99999M18.3337 10C18.3337 11.3151 18.0224 12.6115 17.4254 13.7832C16.8283 14.955 15.9625 15.9688 14.8985 16.7418L10.0003 9.99999" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_914_468">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
),
label: "Reports",
path: "/reports",
active: location.pathname === "/reports" || location.pathname === "/"
},
{
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.4999 9.58333C17.4999 13.4953 14.3285 16.6667 10.4165 16.6667C9.5192 16.6667 8.66086 16.4998 7.87081 16.1954C7.72637 16.1398 7.65415 16.112 7.59671 16.0987C7.54022 16.0857 7.49933 16.0803 7.4414 16.0781C7.3825 16.0758 7.31789 16.0825 7.18865 16.0958L2.92113 16.537C2.51427 16.579 2.31083 16.6001 2.19083 16.5269C2.08631 16.4631 2.01512 16.3566 1.99617 16.2356C1.97441 16.0968 2.07162 15.9168 2.26605 15.557L3.62909 13.034C3.74135 12.8262 3.79747 12.7223 3.82289 12.6225C3.848 12.5238 3.85407 12.4527 3.84604 12.3512C3.83791 12.2484 3.79283 12.1147 3.70266 11.8472C3.46306 11.1363 3.33318 10.375 3.33318 9.58333C3.33318 5.67132 6.5045 2.5 10.4165 2.5C14.3285 2.5 17.4999 5.67132 17.4999 9.58333Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
),
label: "Chat",
path: "/chat",
active: location.pathname === "/chat"
},
{
icon: (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_914_474)">
<path d="M7.57533 7.50001C7.77125 6.94306 8.15795 6.47343 8.66695 6.17428C9.17596 5.87514 9.77441 5.76579 10.3563 5.8656C10.9382 5.96541 11.466 6.26794 11.8462 6.71961C12.2264 7.17128 12.4345 7.74294 12.4337 8.33334C12.4337 10 9.93366 10.8333 9.93366 10.8333M10.0003 14.1667H10.0087M18.3337 10C18.3337 14.6024 14.6027 18.3333 10.0003 18.3333C5.39795 18.3333 1.66699 14.6024 1.66699 10C1.66699 5.39763 5.39795 1.66667 10.0003 1.66667C14.6027 1.66667 18.3337 5.39763 18.3337 10Z" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_914_474">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
),
label: "Help",
path: "/help",
active: location.pathname === "/help"
}
];
const settingsIcon = (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_914_477)">
<path d="M10.0003 12.5C11.381 12.5 12.5003 11.3807 12.5003 10C12.5003 8.61929 11.381 7.5 10.0003 7.5C8.61961 7.5 7.50033 8.61929 7.50033 10C7.50033 11.3807 8.61961 12.5 10.0003 12.5Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M15.6064 12.2727C15.5055 12.5012 15.4755 12.7547 15.52 13.0004C15.5646 13.2462 15.6817 13.473 15.8564 13.6515L15.9018 13.697C16.0427 13.8377 16.1545 14.0048 16.2307 14.1887C16.307 14.3727 16.3462 14.5698 16.3462 14.7689C16.3462 14.9681 16.307 15.1652 16.2307 15.3492C16.1545 15.5331 16.0427 15.7002 15.9018 15.8409C15.7611 15.9818 15.594 16.0935 15.4101 16.1698C15.2261 16.246 15.029 16.2853 14.8299 16.2853C14.6308 16.2853 14.4336 16.246 14.2497 16.1698C14.0657 16.0935 13.8986 15.9818 13.7579 15.8409L13.7124 15.7955C13.5339 15.6208 13.3071 15.5036 13.0614 15.4591C12.8156 15.4145 12.5622 15.4446 12.3337 15.5455C12.1096 15.6415 11.9185 15.8009 11.7839 16.0042C11.6493 16.2074 11.5771 16.4456 11.5761 16.6894V16.8182C11.5761 17.22 11.4165 17.6054 11.1323 17.8896C10.8482 18.1737 10.4628 18.3333 10.0609 18.3333C9.65909 18.3333 9.2737 18.1737 8.98956 17.8896C8.70541 17.6054 8.54578 17.22 8.54578 16.8182V16.75C8.53991 16.4992 8.45875 16.2561 8.31283 16.052C8.16692 15.848 7.963 15.6926 7.7276 15.6061C7.4991 15.5052 7.24563 15.4751 6.99988 15.5197C6.75413 15.5643 6.52736 15.6814 6.34881 15.8561L6.30336 15.9015C6.16264 16.0424 5.99554 16.1541 5.8116 16.2304C5.62766 16.3066 5.4305 16.3459 5.23139 16.3459C5.03227 16.3459 4.83511 16.3066 4.65117 16.2304C4.46724 16.1541 4.30013 16.0424 4.15942 15.9015C4.01854 15.7608 3.90679 15.5937 3.83054 15.4098C3.75429 15.2258 3.71504 15.0287 3.71504 14.8295C3.71504 14.6304 3.75429 14.4333 3.83054 14.2493C3.90679 14.0654 4.01854 13.8983 4.15942 13.7576L4.20487 13.7121C4.37952 13.5336 4.49668 13.3068 4.54124 13.0611C4.5858 12.8153 4.55572 12.5618 4.45487 12.3333C4.35884 12.1093 4.19938 11.9182 3.99613 11.7836C3.79288 11.649 3.55471 11.5767 3.31093 11.5758H3.18214C2.7803 11.5758 2.39492 11.4161 2.11077 11.132C1.82662 10.8478 1.66699 10.4624 1.66699 10.0606C1.66699 9.65876 1.82662 9.27338 2.11077 8.98923C2.39492 8.70509 2.7803 8.54545 3.18214 8.54545H3.25033C3.50108 8.53959 3.74427 8.45842 3.94828 8.31251C4.15229 8.16659 4.30769 7.96268 4.39427 7.72727C4.49511 7.49878 4.52519 7.24531 4.48063 6.99955C4.43607 6.7538 4.31891 6.52703 4.14427 6.34848L4.09881 6.30303C3.95794 6.16231 3.84618 5.99521 3.76993 5.81127C3.69368 5.62734 3.65444 5.43018 3.65444 5.23106C3.65444 5.03195 3.69368 4.83478 3.76993 4.65085C3.84618 4.46691 3.95794 4.29981 4.09881 4.15909C4.23953 4.01822 4.40663 3.90646 4.59057 3.83021C4.7745 3.75396 4.97167 3.71472 5.17078 3.71472C5.36989 3.71472 5.56706 3.75396 5.75099 3.83021C5.93493 3.90646 6.10203 4.01822 6.24275 4.15909L6.2882 4.20455C6.46675 4.37919 6.69352 4.49635 6.93927 4.54091C7.18503 4.58547 7.4385 4.55539 7.66699 4.45455H7.7276C7.95167 4.35851 8.14276 4.19906 8.27737 3.99581C8.41197 3.79256 8.4842 3.55438 8.48517 3.31061V3.18182C8.48517 2.77998 8.64481 2.39459 8.92895 2.11044C9.2131 1.8263 9.59848 1.66667 10.0003 1.66667C10.4022 1.66667 10.7876 1.8263 11.0717 2.11044C11.3558 2.39459 11.5155 2.77998 11.5155 3.18182V3.25C11.5164 3.49378 11.5887 3.73195 11.7233 3.9352C11.8579 4.13845 12.049 4.29791 12.2731 4.39394C12.5016 4.49478 12.755 4.52487 13.0008 4.48031C13.2465 4.43575 13.4733 4.31859 13.6518 4.14394L13.6973 4.09848C13.838 3.95761 14.0051 3.84586 14.1891 3.76961C14.373 3.69336 14.5702 3.65411 14.7693 3.65411C14.9684 3.65411 15.1655 3.69336 15.3495 3.76961C15.5334 3.84586 15.7005 3.95761 15.8412 4.09848C15.9821 4.2392 16.0939 4.40631 16.1701 4.59024C16.2464 4.77418 16.2856 4.97134 16.2856 5.17045C16.2856 5.36957 16.2464 5.56673 16.1701 5.75067C16.0939 5.9346 15.9821 6.10171 15.8412 6.24242L15.7958 6.28788C15.6211 6.46642 15.504 6.69319 15.4594 6.93895C15.4149 7.1847 15.4449 7.43817 15.5458 7.66667V7.72727C15.6418 7.95134 15.8013 8.14244 16.0045 8.27704C16.2078 8.41164 16.4459 8.48388 16.6897 8.48485H16.8185C17.2204 8.48485 17.6057 8.64448 17.8899 8.92863C18.174 9.21277 18.3337 9.59816 18.3337 10C18.3337 10.4018 18.174 10.7872 17.8899 11.0714C17.6057 11.3555 17.2204 11.5152 16.8185 11.5152H16.7503C16.5065 11.5161 16.2684 11.5884 16.0651 11.723C15.8619 11.8576 15.7024 12.0487 15.6064 12.2727Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_914_477">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
);
const handleNavClick = (path: string) => {
navigate(path);
};
return (
<div className="w-64 h-[810px] max-w-64 min-w-64 px-3 pt-4 pb-3 bg-Neutrals-NeutralSlate0 border-r border-Neutrals-NeutralSlate200 inline-flex flex-col justify-between items-center overflow-hidden">
{/* Header Section */}
<div className="self-stretch flex flex-col justify-start items-start gap-5">
{/* Company Selector */}
<div className="w-60 pl-2 pr-4 py-2 bg-Neutrals-NeutralSlate0 rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-between items-center overflow-hidden">
<div className="flex-1 flex justify-start items-center gap-2">
<div className="w-8 h-8 rounded-full flex justify-start items-center gap-2.5">
<div className="w-8 h-8 relative bg-Brand-Orange rounded-full outline outline-[1.60px] outline-offset-[-1.60px] outline-white/10 overflow-hidden">
<div className="left-0 top-0 absolute">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" fill="url(#paint0_linear_731_19280)" />
<defs>
<linearGradient id="paint0_linear_731_19280" x1="16" y1="3.97364e-07" x2="17.3333" y2="32" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0" />
<stop offset="1" stopColor="white" stopOpacity="0.12" />
</linearGradient>
</defs>
</svg>
</div>
<div className="left-[8.80px] top-[7.20px] absolute">
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_731_19281)">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M4.34367 10.6873C4.67023 11.018 4.67022 11.5541 4.34366 11.8848L4.32693 11.9018C4.00036 12.2325 3.47089 12.2325 3.14433 11.9018C2.81777 11.5711 2.81778 11.0349 3.14434 10.7042L3.16107 10.6873C3.48764 10.3566 4.0171 10.3566 4.34367 10.6873Z" fill="url(#paint0_linear_731_19281)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M8.2752 10.9423C8.60118 11.2736 8.60022 11.8097 8.27306 12.1398L5.95673 14.477C5.62957 14.8071 5.1001 14.8061 4.77413 14.4748C4.44815 14.1435 4.44911 13.6074 4.77627 13.2773L7.09261 10.9401C7.41976 10.61 7.94923 10.611 8.2752 10.9423Z" fill="url(#paint1_linear_731_19281)" />
</g>
<defs>
<filter id="filter0_d_731_19281" x="0.398828" y="-0.399988" width="19.2014" height="22.4" filterUnits="userSpaceOnUse" colorInterpolationFilters="sRGB">
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
<feMorphology radius="1.2" operator="erode" in="SourceAlpha" result="effect1_dropShadow_731_19281" />
<feOffset dy="1.8" />
<feGaussianBlur stdDeviation="1.8" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix type="matrix" values="0 0 0 0 0.141176 0 0 0 0 0.141176 0 0 0 0 0.141176 0 0 0 0.1 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_731_19281" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_731_19281" result="shape" />
</filter>
<linearGradient id="paint0_linear_731_19281" x1="3.744" y1="10.4393" x2="3.744" y2="12.1498" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint1_linear_731_19281" x1="6.52467" y1="10.6932" x2="6.52467" y2="14.7239" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
</div>
</div>
</div>
<div className="flex-1 inline-flex flex-col justify-start items-start gap-0.5">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-base font-medium font-['Inter'] leading-normal">{companyName}</div>
</div>
</div>
<div>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.83301 12.5L9.99967 16.6667L14.1663 12.5M5.83301 7.50001L9.99967 3.33334L14.1663 7.50001" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
{/* Navigation Items */}
<div className="self-stretch flex flex-col justify-start items-start gap-5">
<div className="self-stretch flex flex-col justify-start items-start gap-1.5">
{navItems.map((item, index) => (
<div
key={index}
onClick={() => handleNavClick(item.path)}
className={`w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2 cursor-pointer ${item.active
? 'bg-Neutrals-NeutralSlate100'
: 'hover:bg-Neutrals-NeutralSlate50'
}`}
>
<div className="relative">
{React.cloneElement(item.icon, {
stroke: item.active ? "var(--Brand-Orange, #5E48FC)" : "var(--Neutrals-NeutralSlate400, #A4A7AE)"
})}
</div>
<div className={`justify-start text-sm font-medium font-['Inter'] leading-tight ${item.active
? 'text-Neutrals-NeutralSlate950'
: 'text-Neutrals-NeutralSlate500'
}`}>
{item.label}
</div>
</div>
))}
</div>
</div>
</div>
{/* Bottom Section */}
<div className="self-stretch flex flex-col justify-start items-start gap-3">
{/* Settings */}
<div
onClick={() => handleNavClick("/settings")}
className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2 cursor-pointer hover:bg-Neutrals-NeutralSlate50"
>
<div className="relative">
{settingsIcon}
</div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Settings</div>
</div>
{/* Build Report Card */}
<div className="self-stretch bg-Neutrals-NeutralSlate0 rounded-[20px] shadow-[0px_1px_4px_0px_rgba(14,18,27,0.04)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 flex flex-col justify-start items-start overflow-hidden">
<div className="self-stretch h-24 relative">
<div className="w-60 h-32 left-0 top-[-0.50px] absolute bg-gradient-to-b from-black to-black/0" />
<div className="w-60 p-3 left-[18.12px] top-[42.51px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
<div className="w-60 p-3 left-[31.44px] top-[22px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
</div>
<div className="self-stretch p-3 flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-sm font-semibold font-['Inter'] leading-tight">Build [Company]'s Report</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-xs font-normal font-['Inter'] leading-none">Share this form with your team members to capture valuable info about your company to train Auditly.</div>
</div>
<div className="self-stretch px-3 pb-3 flex flex-col justify-start items-start gap-8">
<div className="self-stretch inline-flex justify-start items-start gap-2">
<div className="flex-1 px-3 py-1.5 bg-Button-Secondary rounded-[999px] flex justify-center items-center gap-0.5 overflow-hidden">
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Invite</div>
</div>
<div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99967 3.33333V12.6667M3.33301 8H12.6663" stroke="var(--Neutrals-NeutralSlate950, #0A0D12)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
<div className="flex-1 px-3 py-1.5 bg-Brand-Orange rounded-[999px] outline outline-1 outline-offset-[-1px] outline-blue-400 flex justify-center items-center gap-0.5 overflow-hidden">
<div>
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.97203 12.2426L8.02922 13.1854C6.72748 14.4872 4.61693 14.4872 3.31518 13.1854C2.01343 11.8837 2.01343 9.77312 3.31518 8.47138L4.25799 7.52857M12.7433 8.47138L13.6861 7.52857C14.9878 6.22682 14.9878 4.11627 13.6861 2.81452C12.3843 1.51277 10.2738 1.51277 8.97203 2.81452L8.02922 3.75733M6.16729 10.3333L10.834 5.66662" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Copy</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -10,7 +10,7 @@ const isLocalhost = typeof window !== 'undefined' &&
// Use Firebase Functions emulator in development, production functions in production // Use Firebase Functions emulator in development, production functions in production
export const API_URL = isLocalhost export const API_URL = isLocalhost
? 'http://127.0.0.1:5002/auditly-c0027/us-central1' // Firebase Functions Emulator ? 'http://127.0.0.1:5002/auditly-c0027/us-central1' // Firebase Functions Emulator
: 'https://your-project.cloudfunctions.net'; // Production Firebase Functions : 'https://us-central1-auditly-c0027.cloudfunctions.net'; // Production Firebase Functions
// Log URL configuration in development // Log URL configuration in development
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
@@ -51,7 +51,7 @@ export const REPORT_DATA: Report = {
}, },
risks: ['Sample data - use AI-generated reports'], risks: ['Sample data - use AI-generated reports'],
recommendation: { recommendation: {
action: 'Sample', action: 'Keep',
details: ['Sample data - use AI-generated reports'], details: ['Sample data - use AI-generated reports'],
}, },
grading: [], grading: [],
@@ -114,7 +114,7 @@ export const SAMPLE_COMPANY_REPORT: CompanyReport = {
submissionRate: 0, submissionRate: 0,
lastUpdated: Date.now(), lastUpdated: Date.now(),
averagePerformanceScore: 0, averagePerformanceScore: 0,
riskLevel: 'Unknown' riskLevel: 'Low'
}, },
gradingBreakdown: [], gradingBreakdown: [],
operatingPlan: { nextQuarterGoals: [], keyInitiatives: [], resourceNeeds: [], riskMitigation: [] }, operatingPlan: { nextQuarterGoals: [], keyInitiatives: [], resourceNeeds: [], riskMitigation: [] },

View File

@@ -4,8 +4,8 @@
"": { "": {
"name": "auditly-functions", "name": "auditly-functions",
"dependencies": { "dependencies": {
"firebase-admin": "^13.4.0", "firebase-admin": "^12.1.1",
"firebase-functions": "^4.5.0", "firebase-functions": "^5.0.1",
"openai": "^5.12.2", "openai": "^5.12.2",
"stripe": "^18.4.0", "stripe": "^18.4.0",
}, },
@@ -95,23 +95,23 @@
"@fastify/busboy": ["@fastify/busboy@3.2.0", "", {}, "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA=="], "@fastify/busboy": ["@fastify/busboy@3.2.0", "", {}, "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA=="],
"@firebase/app-check-interop-types": ["@firebase/app-check-interop-types@0.3.3", "", {}, "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A=="], "@firebase/app-check-interop-types": ["@firebase/app-check-interop-types@0.3.2", "", {}, "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ=="],
"@firebase/app-types": ["@firebase/app-types@0.9.3", "", {}, "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="], "@firebase/app-types": ["@firebase/app-types@0.9.2", "", {}, "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ=="],
"@firebase/auth-interop-types": ["@firebase/auth-interop-types@0.2.4", "", {}, "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA=="], "@firebase/auth-interop-types": ["@firebase/auth-interop-types@0.2.3", "", {}, "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ=="],
"@firebase/component": ["@firebase/component@0.7.0", "", { "dependencies": { "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg=="], "@firebase/component": ["@firebase/component@0.6.9", "", { "dependencies": { "@firebase/util": "1.10.0", "tslib": "^2.1.0" } }, "sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q=="],
"@firebase/database": ["@firebase/database@1.1.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg=="], "@firebase/database": ["@firebase/database@1.0.8", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.2", "@firebase/auth-interop-types": "0.2.3", "@firebase/component": "0.6.9", "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg=="],
"@firebase/database-compat": ["@firebase/database-compat@2.1.0", "", { "dependencies": { "@firebase/component": "0.7.0", "@firebase/database": "1.1.0", "@firebase/database-types": "1.0.16", "@firebase/logger": "0.5.0", "@firebase/util": "1.13.0", "tslib": "^2.1.0" } }, "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg=="], "@firebase/database-compat": ["@firebase/database-compat@1.0.8", "", { "dependencies": { "@firebase/component": "0.6.9", "@firebase/database": "1.0.8", "@firebase/database-types": "1.0.5", "@firebase/logger": "0.4.2", "@firebase/util": "1.10.0", "tslib": "^2.1.0" } }, "sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg=="],
"@firebase/database-types": ["@firebase/database-types@1.0.16", "", { "dependencies": { "@firebase/app-types": "0.9.3", "@firebase/util": "1.13.0" } }, "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw=="], "@firebase/database-types": ["@firebase/database-types@1.0.5", "", { "dependencies": { "@firebase/app-types": "0.9.2", "@firebase/util": "1.10.0" } }, "sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ=="],
"@firebase/logger": ["@firebase/logger@0.5.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g=="], "@firebase/logger": ["@firebase/logger@0.4.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A=="],
"@firebase/util": ["@firebase/util@1.13.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ=="], "@firebase/util": ["@firebase/util@1.10.0", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ=="],
"@google-cloud/firestore": ["@google-cloud/firestore@7.11.3", "", { "dependencies": { "@opentelemetry/api": "^1.3.0", "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^4.3.3", "protobufjs": "^7.2.6" } }, "sha512-qsM3/WHpawF07SRVvEJJVRwhYzM7o9qtuksyuqnrMig6fxIrwWnsezECWsG/D5TyYru51Fv5c/RTqNDQ2yU+4w=="], "@google-cloud/firestore": ["@google-cloud/firestore@7.11.3", "", { "dependencies": { "@opentelemetry/api": "^1.3.0", "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^4.3.3", "protobufjs": "^7.2.6" } }, "sha512-qsM3/WHpawF07SRVvEJJVRwhYzM7o9qtuksyuqnrMig6fxIrwWnsezECWsG/D5TyYru51Fv5c/RTqNDQ2yU+4w=="],
@@ -501,9 +501,9 @@
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
"firebase-admin": ["firebase-admin@13.4.0", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "^2.0.0", "@firebase/database-types": "^1.0.6", "@types/node": "^22.8.7", "farmhash-modern": "^1.1.0", "google-auth-library": "^9.14.2", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", "node-forge": "^1.3.1", "uuid": "^11.0.2" }, "optionalDependencies": { "@google-cloud/firestore": "^7.11.0", "@google-cloud/storage": "^7.14.0" } }, "sha512-Y8DcyKK+4pl4B93ooiy1G8qvdyRMkcNFfBSh+8rbVcw4cW8dgG0VXCCTp5NUwub8sn9vSPsOwpb9tE2OuFmcfQ=="], "firebase-admin": ["firebase-admin@12.7.0", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@firebase/database-compat": "1.0.8", "@firebase/database-types": "1.0.5", "@types/node": "^22.0.1", "farmhash-modern": "^1.1.0", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", "node-forge": "^1.3.1", "uuid": "^10.0.0" }, "optionalDependencies": { "@google-cloud/firestore": "^7.7.0", "@google-cloud/storage": "^7.7.0" } }, "sha512-raFIrOyTqREbyXsNkSHyciQLfv8AUZazehPaQS1lZBSCDYW74FYXU0nQZa3qHI4K+hawohlDbywZ4+qce9YNxA=="],
"firebase-functions": ["firebase-functions@4.9.0", "", { "dependencies": { "@types/cors": "^2.8.5", "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", "protobufjs": "^7.2.2" }, "peerDependencies": { "firebase-admin": "^10.0.0 || ^11.0.0 || ^12.0.0" }, "bin": { "firebase-functions": "lib/bin/firebase-functions.js" } }, "sha512-IqxOEsVAWGcRv9KRGzWQR5mOFuNsil3vsfkRPPiaV1U/ATC27/jbahh4z8I4rW8Xqa6cQE5xqnw0ueyMH7i7Ag=="], "firebase-functions": ["firebase-functions@5.1.1", "", { "dependencies": { "@types/cors": "^2.8.5", "@types/express": "4.17.3", "cors": "^2.8.5", "express": "^4.17.1", "protobufjs": "^7.2.2" }, "peerDependencies": { "firebase-admin": "^11.10.0 || ^12.0.0" }, "bin": { "firebase-functions": "lib/bin/firebase-functions.js" } }, "sha512-KkyKZE98Leg/C73oRyuUYox04PQeeBThdygMfeX+7t1cmKWYKa/ZieYa89U8GHgED+0mF7m7wfNZOfbURYxIKg=="],
"firebase-functions-test": ["firebase-functions-test@3.4.1", "", { "dependencies": { "@types/lodash": "^4.14.104", "lodash": "^4.17.5", "ts-deepmerge": "^2.0.1" }, "peerDependencies": { "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", "firebase-functions": ">=4.9.0", "jest": ">=28.0.0" } }, "sha512-qAq0oszrBGdf4bnCF6t4FoSgMsepeIXh0Pi/FhikSE6e+TvKKGpfrfUP/5pFjJZxFcLsweoau88KydCql4xSeg=="], "firebase-functions-test": ["firebase-functions-test@3.4.1", "", { "dependencies": { "@types/lodash": "^4.14.104", "lodash": "^4.17.5", "ts-deepmerge": "^2.0.1" }, "peerDependencies": { "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0", "firebase-functions": ">=4.9.0", "jest": ">=28.0.0" } }, "sha512-qAq0oszrBGdf4bnCF6t4FoSgMsepeIXh0Pi/FhikSE6e+TvKKGpfrfUP/5pFjJZxFcLsweoau88KydCql4xSeg=="],
@@ -677,11 +677,11 @@
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="], "jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="],
"jwks-rsa": ["jwks-rsa@3.2.0", "", { "dependencies": { "@types/express": "^4.17.20", "@types/jsonwebtoken": "^9.0.4", "debug": "^4.3.4", "jose": "^4.15.4", "limiter": "^1.1.5", "lru-memoizer": "^2.2.0" } }, "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww=="], "jwks-rsa": ["jwks-rsa@3.2.0", "", { "dependencies": { "@types/express": "^4.17.20", "@types/jsonwebtoken": "^9.0.4", "debug": "^4.3.4", "jose": "^4.15.4", "limiter": "^1.1.5", "lru-memoizer": "^2.2.0" } }, "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww=="],
"jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="], "jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
@@ -943,7 +943,7 @@
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
"v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
@@ -1007,14 +1007,16 @@
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
"google-auth-library/jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="],
"google-gax/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "google-gax/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
"gtoken/jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="],
"http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], "http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"jsonwebtoken/jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
"jwks-rsa/@types/express": ["@types/express@4.17.23", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ=="], "jwks-rsa/@types/express": ["@types/express@4.17.23", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ=="],
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
@@ -1053,7 +1055,9 @@
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"jsonwebtoken/jws/jwa": ["jwa@1.4.2", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw=="], "google-auth-library/jws/jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
"gtoken/jws/jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],

View File

@@ -1,10 +1,10 @@
const { onRequest } = require("firebase-functions/v2/https"); const { onRequest } = require("firebase-functions/v2/https");
const admin = require("firebase-admin"); const admin = require("firebase-admin");
const serviceAccount = require("./auditly-c0027-firebase-adminsdk-fbsvc-1db7c58141.json");
const functions = require("firebase-functions");
const OpenAI = require("openai"); const OpenAI = require("openai");
const Stripe = require("stripe"); const Stripe = require("stripe");
const serviceAccount = require("./auditly-c0027-firebase-adminsdk-fbsvc-1db7c58141.json");
admin.initializeApp({ admin.initializeApp({
credential: admin.credential.cert(serviceAccount) credential: admin.credential.cert(serviceAccount)
}); });
@@ -177,32 +177,9 @@ const generateOTP = () => {
return Math.floor(100000 + Math.random() * 900000).toString(); return Math.floor(100000 + Math.random() * 900000).toString();
}; };
// CORS middleware
const cors = (req, res, next) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.set('Access-Control-Max-Age', '3600');
if (req.method === 'OPTIONS') {
res.status(204).send('');
return;
}
next();
};
// Helper function to set CORS headers
const setCorsHeaders = (res) => {
res.set('Access-Control-Allow-Origin', '*');
res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');
res.set('Access-Control-Max-Age', '3600');
};
// Send OTP Function // Send OTP Function
exports.sendOTP = functions.https.onRequest((req, res) => { exports.sendOTP = onRequest({ cors: true }, async (req, res) => {
cors(req, res, async () => {
if (req.method !== "POST") { if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" }); return res.status(405).json({ error: "Method not allowed" });
} }
@@ -241,11 +218,9 @@ exports.sendOTP = functions.https.onRequest((req, res) => {
res.status(500).json({ error: "Failed to send verification code" }); res.status(500).json({ error: "Failed to send verification code" });
} }
}); });
});
// Verify OTP Function // Verify OTP Function
exports.verifyOTP = functions.https.onRequest((req, res) => { exports.verifyOTP = onRequest({ cors: true }, async (req, res) => {
cors(req, res, async () => {
if (req.method !== "POST") { if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" }); return res.status(405).json({ error: "Method not allowed" });
} }
@@ -372,11 +347,9 @@ exports.verifyOTP = functions.https.onRequest((req, res) => {
res.status(500).json({ error: "Failed to verify code" }); res.status(500).json({ error: "Failed to verify code" });
} }
}); });
});
// Create Invitation Function // Create Invitation Function
exports.createInvitation = functions.https.onRequest(async (req, res) => { exports.createInvitation = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
@@ -429,7 +402,7 @@ exports.createInvitation = functions.https.onRequest(async (req, res) => {
}); });
// Generate invite links // Generate invite links
const baseUrl = process.env.CLIENT_URL || 'http://localhost:5174'; const baseUrl = process.env.CLIENT_URL || 'http://localhost:5173';
const inviteLink = `${baseUrl}/#/employee-form/${code}`; const inviteLink = `${baseUrl}/#/employee-form/${code}`;
const emailLink = `mailto:${email}?subject=You're invited to join our organization&body=Hi ${name},%0A%0AYou've been invited to complete a questionnaire for our organization. Please click the link below to get started:%0A%0A${inviteLink}%0A%0AThis link will expire in 7 days.%0A%0AThank you!`; const emailLink = `mailto:${email}?subject=You're invited to join our organization&body=Hi ${name},%0A%0AYou've been invited to complete a questionnaire for our organization. Please click the link below to get started:%0A%0A${inviteLink}%0A%0AThis link will expire in 7 days.%0A%0AThank you!`;
@@ -452,9 +425,7 @@ exports.createInvitation = functions.https.onRequest(async (req, res) => {
}); });
// Get Invitation Status Function // Get Invitation Status Function
exports.getInvitationStatus = functions.https.onRequest(async (req, res) => { exports.getInvitationStatus = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
return; return;
@@ -501,9 +472,7 @@ exports.getInvitationStatus = functions.https.onRequest(async (req, res) => {
}); });
// Consume Invitation Function // Consume Invitation Function
exports.consumeInvitation = functions.https.onRequest(async (req, res) => { exports.consumeInvitation = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
return; return;
@@ -581,8 +550,7 @@ exports.consumeInvitation = functions.https.onRequest(async (req, res) => {
}); });
// Submit Employee Answers Function // Submit Employee Answers Function
exports.submitEmployeeAnswers = functions.https.onRequest(async (req, res) => { exports.submitEmployeeAnswers = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
@@ -659,8 +627,7 @@ exports.submitEmployeeAnswers = functions.https.onRequest(async (req, res) => {
}); });
// Generate Employee Report Function // Generate Employee Report Function
exports.generateEmployeeReport = functions.https.onRequest(async (req, res) => { exports.generateEmployeeReport = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
@@ -794,9 +761,7 @@ Return ONLY valid JSON that matches this structure. Be thorough but professional
}); });
// Generate Company Wiki Function // Generate Company Wiki Function
exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => { exports.generateCompanyWiki = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
return; return;
@@ -840,6 +805,8 @@ exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
}); });
// content is guaranteed to be schema-conformant JSON // content is guaranteed to be schema-conformant JSON
console.log(completion.choices[0].message);
console.log(completion.choices[0].message.content);
const parsed = JSON.parse(completion.choices[0].message.content); const parsed = JSON.parse(completion.choices[0].message.content);
const report = { const report = {
@@ -852,6 +819,9 @@ exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
generatedAt: Date.now(), generatedAt: Date.now(),
...parsed.wiki, ...parsed.wiki,
}; };
console.log(report);
console.log(wiki);
} else { } else {
// Fallback to mock data when OpenAI is not available // Fallback to mock data when OpenAI is not available
report = { report = {
@@ -921,8 +891,8 @@ exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
res.json({ res.json({
success: true, success: true,
report, ...report,
wiki, ...wiki,
}); });
} catch (error) { } catch (error) {
console.error("Generate company wiki error:", error); console.error("Generate company wiki error:", error);
@@ -931,8 +901,7 @@ exports.generateCompanyWiki = functions.https.onRequest(async (req, res) => {
}); });
// Chat Function // Chat Function
exports.chat = functions.https.onRequest(async (req, res) => { exports.chat = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
@@ -1007,9 +976,7 @@ Provide helpful, actionable insights while maintaining professional confidential
}); });
// Create Organization Function // Create Organization Function
exports.createOrganization = functions.https.onRequest(async (req, res) => { exports.createOrganization = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
return; return;
@@ -1121,8 +1088,7 @@ exports.createOrganization = functions.https.onRequest(async (req, res) => {
}); });
// Get User Organizations Function // Get User Organizations Function
exports.getUserOrganizations = functions.https.onRequest(async (req, res) => { exports.getUserOrganizations = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
@@ -1166,9 +1132,7 @@ exports.getUserOrganizations = functions.https.onRequest(async (req, res) => {
}); });
// Join Organization Function (via invite) // Join Organization Function (via invite)
exports.joinOrganization = functions.https.onRequest(async (req, res) => { exports.joinOrganization = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
return; return;
@@ -1282,9 +1246,7 @@ exports.joinOrganization = functions.https.onRequest(async (req, res) => {
}); });
// Create Stripe Checkout Session Function // Create Stripe Checkout Session Function
exports.createCheckoutSession = functions.https.onRequest(async (req, res) => { exports.createCheckoutSession = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
return; return;
@@ -1374,7 +1336,7 @@ exports.createCheckoutSession = functions.https.onRequest(async (req, res) => {
}); });
// Handle Stripe Webhook Function // Handle Stripe Webhook Function
exports.stripeWebhook = functions.https.onRequest(async (req, res) => { exports.stripeWebhook = onRequest(async (req, res) => {
if (!stripe) { if (!stripe) {
return res.status(500).send('Stripe not configured'); return res.status(500).send('Stripe not configured');
} }
@@ -1385,7 +1347,7 @@ exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
let event; let event;
try { try {
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
} catch (err) { } catch (err) {
console.error('Webhook signature verification failed:', err.message); console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`); return res.status(400).send(`Webhook Error: ${err.message}`);
@@ -1424,9 +1386,7 @@ exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
}); });
// Get Subscription Status Function // Get Subscription Status Function
exports.getSubscriptionStatus = functions.https.onRequest(async (req, res) => { exports.getSubscriptionStatus = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
return; return;
@@ -1652,9 +1612,7 @@ async function handlePaymentFailed(invoice) {
// }); // });
// Save Company Report Function // Save Company Report Function
exports.saveCompanyReport = functions.https.onRequest(async (req, res) => { exports.saveCompanyReport = onRequest({ cors: true }, async (req, res) => {
setCorsHeaders(res);
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.status(204).send(''); res.status(204).send('');
return; return;

View File

@@ -13,8 +13,8 @@
}, },
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"firebase-admin": "^13.4.0", "firebase-admin": "^12.1.1",
"firebase-functions": "^4.5.0", "firebase-functions": "^5.0.1",
"openai": "^5.12.2", "openai": "^5.12.2",
"stripe": "^18.4.0" "stripe": "^18.4.0"
}, },

View File

@@ -11,7 +11,7 @@
--text-secondary: #717680; --text-secondary: #717680;
--text-tertiary : #A4A7AE; --text-tertiary : #A4A7AE;
--accent : ##3399FF; --accent : #5E48FC;
--accent-hover: #4C3CF0; --accent-hover: #4C3CF0;
--accent-text : #FFFFFF; --accent-text : #FFFFFF;
@@ -20,7 +20,7 @@
--sidebar-bg : #FDFDFD; --sidebar-bg : #FDFDFD;
--sidebar-text : #717680; --sidebar-text : #717680;
--sidebar-active-bg : #3399FF; --sidebar-active-bg : #5E48FC;
--sidebar-active-text: #FFFFFF; --sidebar-active-text: #FFFFFF;
--input-bg : #F5F5F5; --input-bg : #F5F5F5;
@@ -30,34 +30,40 @@
--button-secondary-bg : #F5F5F5; --button-secondary-bg : #F5F5F5;
--button-secondary-hover: #E9EAEB; --button-secondary-hover: #E9EAEB;
--color-red : #F63D68; --color-red : #FB3748;
--color-green : #3CCB7F; --color-green : #1FC16B;
--color-orange : #FF4405; --color-orange : #5E48FC;
--color-light-orange: #F38744; --color-light-orange: #F38744;
--color-yellow : #FEEE95; --color-yellow : #FEEE95;
--gray-0 : #FFFFFF; --Neutrals-NeutralState950: #0A0D12;
--gray-50 : #F7F7F8; --Neutrals-NeutralSlate900: #181D27;
--gray-100: #F1F2F4; --Neutrals-NeutralSlate800: #252B37;
--gray-200: #E2E5E9; --Neutrals-NeutralSlate700: #414651;
--gray-300: #CBD0D7; --Neutrals-NeutralSlate600: #535862;
--gray-400: #99A1AE; --Neutrals-NeutralSlate500: #7A7680;
--gray-500: #6C7889; --Neutrals-NeutralSlate400: #A4A7AE;
--gray-600: #515A67; --Neutrals-NeutralSlate300: #D5D7DA;
--gray-700: #2D3239; --Neutrals-NeutralSlate200: #E9EAEB;
--gray-800: #24282E; --Neutrals-NeutralSlate100: #F5F5F5;
--gray-900: #121417; --Neutrals-NeutralSlate50 : #FAFAFA;
--Neutrals-NeutralSlate0 : #FDFDFD;
--neutral-100: #A4A7AE; --neutral-100: #A4A7AE;
--neutral-200: #D5D7DA; --neutral-200: #D5D7DA;
--neutral-300: #E9EAEB; --neutral-300: #E9EAEB;
--neutral-400: #f5f5f5; --neutral-400: #F5F5F5;
--neutral-500: #FAFAFA; --neutral-500: #FAFAFA;
--neutral-600: #FDFDFD; --neutral-600: #FDFDFD;
--neutral-700: #FEFEFE; --neutral-700: #FEFEFE;
--button-bg-primary : #39F; --Brand-Orange: #3399FF;
--button-border-primary: #66B2FF;
--Other-Red : #FB3748;
--Other-Green: #1FC16B;
--button-bg-primary : #5E48FC;
--button-border-primary: #7B64FF;
} }
.dark { .dark {
@@ -117,17 +123,18 @@
--neutral-600 : #181D27; --neutral-600 : #181D27;
--neutral-700 : #0A0D12; --neutral-700 : #0A0D12;
--gray-900: #FFFFFF; --Neutrals-NeutralState950: #FDFDFD;
--gray-800: #F7F7F8; --Neutrals-NeutralSlate900: #FAFAFA;
--gray-700: #F1F2F4; --Neutrals-NeutralSlate800: #F5F5F5;
--gray-600: #E2E5E9; --Neutrals-NeutralSlate700: #E9EAEB;
--gray-500: #CBD0D7; --Neutrals-NeutralSlate600: #D5D7DA;
--gray-400: #99A1AE; --Neutrals-NeutralSlate500: #A4A7AE;
--gray-300: #6C7889; --Neutrals-NeutralSlate400: #7A7680;
--gray-200: #515A67; --Neutrals-NeutralSlate300: #535862;
--gray-100: #2D3239; --Neutrals-NeutralSlate200: #414651;
--gray-50 : #24282E; --Neutrals-NeutralSlate100: #252B37;
--gray-0 : #121417; --Neutrals-NeutralSlate50 : #181D27;
--Neutrals-NeutralSlate0 : #0A0D12;
} }
@@ -135,6 +142,17 @@
box-sizing: border-box; box-sizing: border-box;
} }
@font-face {
font-family: 'Neue Montreal';
src : url('/fonts/NeueMontreal-Regular.woff2') format('woff2'),
url('/fonts/NeueMontreal-Regular.woff') format('woff');
font-weight : normal;
font-style : normal;
font-display: swap;
}
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',

View File

@@ -1,165 +1,12 @@
import React, { useState, useMemo } from 'react'; import React from 'react';
import { Card, Button } from '../components/UiKit'; import ChatLayout from '../components/chat/ChatLayout';
import { useOrg } from '../contexts/OrgContext'; import ChatEmptyState from '../components/chat/ChatEmptyState';
import { CHAT_STARTERS } from '../constants';
import { apiPost } from '../services/api';
const Chat: React.FC = () => { const Chat: React.FC = () => {
const { employees, reports, generateEmployeeReport, orgId } = useOrg();
const [messages, setMessages] = useState<Array<{ id: string, role: 'user' | 'assistant', text: string }>>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [selectedEmployeeId, setSelectedEmployeeId] = useState<string>('');
const selectedReport = selectedEmployeeId ? reports[selectedEmployeeId] : undefined;
const dynamicStarters = useMemo(() => {
if (!selectedReport) return CHAT_STARTERS.slice(0, 4);
const strengths = selectedReport.insights.strengths?.slice(0, 2) || [];
const weaknesses = selectedReport.insights.weaknesses?.slice(0, 1) || [];
const risk = selectedReport.retentionRisk;
const starters: string[] = [];
if (strengths[0]) starters.push(`How can we further leverage ${strengths[0]} for cross-team impact?`);
if (weaknesses[0]) starters.push(`What is an actionable plan to address ${weaknesses[0]} this quarter?`);
if (risk) starters.push(`What factors contribute to ${selectedReport.employeeId}'s ${risk} retention risk?`);
starters.push(`Is ${selectedReport.employeeId} a candidate for expanded scope or leadership?`);
while (starters.length < 4) starters.push(CHAT_STARTERS[starters.length] || 'Provide an organizational insight.');
return starters.slice(0, 4);
}, [selectedReport]);
const handleSend = async (message?: string) => {
const textToSend = message || input;
if (!textToSend.trim()) return;
const userMessage = { id: Date.now().toString(), role: 'user' as const, text: textToSend };
setMessages(prev => [...prev, userMessage]);
setInput('');
setIsLoading(true);
try {
// Build context for the AI
const context = {
selectedEmployee: selectedEmployeeId ? employees.find(e => e.id === selectedEmployeeId) : null,
selectedReport: selectedReport,
totalEmployees: employees.length,
organizationScope: !selectedEmployeeId
};
const res = await apiPost('/chat', {
message: textToSend,
employeeId: selectedEmployeeId,
context
}, orgId);
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.error || 'Failed to get AI response');
}
const data = await res.json();
const aiResponse = {
id: (Date.now() + 1).toString(),
role: 'assistant' as const,
text: data.response || 'I apologize, but I couldn\'t generate a response at this time.'
};
setMessages(prev => [...prev, aiResponse]);
} catch (error) {
console.error('Chat error:', error);
const errorResponse = {
id: (Date.now() + 1).toString(),
role: 'assistant' as const,
text: `I apologize, but I encountered an error: ${error.message}. Please try again.`
};
setMessages(prev => [...prev, errorResponse]);
} finally {
setIsLoading(false);
}
};
return ( return (
<div className="p-6 max-w-4xl mx-auto h-full flex flex-col"> <ChatLayout>
<div className="mb-6"> <ChatEmptyState />
<h1 className="text-3xl font-bold text-[--text-primary]">Chat with AI</h1> </ChatLayout>
<p className="text-[--text-secondary] mt-1">Ask questions about your employees and organization</p>
</div>
<div className="mb-4 flex flex-col md:flex-row md:items-center gap-3">
<div className="flex items-center gap-2">
<label className="text-sm text-[--text-secondary]">Focus Employee:</label>
<select
className="px-2 py-1 text-sm bg-[--background-secondary] border border-[--border-color] rounded"
value={selectedEmployeeId}
onChange={e => setSelectedEmployeeId(e.target.value)}
>
<option value="">(Organization)</option>
{employees.map(emp => <option key={emp.id} value={emp.id}>{emp.name}</option>)}
</select>
{selectedEmployeeId && !selectedReport && (
<Button size="sm" variant="secondary" onClick={() => generateEmployeeReport(employees.find(e => e.id === selectedEmployeeId)!)}>
Generate Report
</Button>
)}
</div>
</div>
{messages.length === 0 && (
<Card className="mb-6">
<h3 className="text-lg font-semibold text-[--text-primary] mb-4">Get started with these questions:</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{dynamicStarters.map((starter, idx) => (
<Button
key={idx}
variant="secondary"
size="sm"
className="text-left justify-start"
onClick={() => handleSend(starter)}
>
{starter}
</Button>
))}
</div>
</Card>
)}
<div className="flex-1 overflow-y-auto mb-4 space-y-4">
{messages.map(message => (
<div key={message.id} className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[70%] p-4 rounded-lg ${message.role === 'user'
? 'bg-blue-500 text-white'
: 'bg-[--background-secondary] text-[--text-primary]'
}`}>
{message.text}
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-[--background-secondary] text-[--text-primary] p-4 rounded-lg">
<div className="flex space-x-1">
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
<div className="w-2 h-2 bg-gray-500 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
</div>
</div>
</div>
)}
</div>
<Card padding="sm">
<div className="flex space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
placeholder="Ask about employees, reports, or company insights..."
className="flex-1 px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<Button onClick={() => handleSend()} disabled={isLoading || !input.trim()}>
Send
</Button>
</div>
</Card>
</div>
); );
}; };

390
pages/ChatNew.tsx Normal file
View File

@@ -0,0 +1,390 @@
import React, { useState, useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { useOrg } from '../contexts/OrgContext';
import Sidebar from '../components/figma/Sidebar';
interface Message {
id: string;
role: 'user' | 'assistant';
content: string;
timestamp: Date;
}
interface ChatState {
messages: Message[];
isLoading: boolean;
showEmployeeMenu: boolean;
mentionQuery: string;
selectedEmployees: string[];
hasUploadedFiles: boolean;
uploadedFiles: Array<{
name: string;
type: string;
size: number;
}>;
}
const ChatNew: React.FC = () => {
const { user } = useAuth();
const { employees, orgId } = useOrg();
const navigate = useNavigate();
const inputRef = useRef<HTMLTextAreaElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const [state, setState] = useState<ChatState>({
messages: [],
isLoading: false,
showEmployeeMenu: false,
mentionQuery: '',
selectedEmployees: [],
hasUploadedFiles: false,
uploadedFiles: []
});
const [currentInput, setCurrentInput] = useState('');
const [selectedCategory, setSelectedCategory] = useState('Accountability');
useEffect(() => {
if (!user) {
navigate('/login');
}
}, [user, navigate]);
const questionStarters = [
"How can the company serve them better?",
"How can the company serve them better?",
"How can the company serve them better?",
"How can the company serve them better?"
];
const categories = ['Accountability', 'Employee Growth', 'Customer Focus', 'Teamwork'];
const handleSendMessage = async () => {
if (!currentInput.trim() && state.uploadedFiles.length === 0) return;
const newMessage: Message = {
id: Date.now().toString(),
role: 'user',
content: currentInput.trim(),
timestamp: new Date()
};
setState(prev => ({
...prev,
messages: [...prev.messages, newMessage],
isLoading: true
}));
setCurrentInput('');
// Simulate AI response
setTimeout(() => {
const aiResponse: Message = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: "Based on the information provided and the company data, here are my insights and recommendations...",
timestamp: new Date()
};
setState(prev => ({
...prev,
messages: [...prev.messages, aiResponse],
isLoading: false
}));
}, 2000);
};
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setCurrentInput(value);
// Check for @ mentions
const atIndex = value.lastIndexOf('@');
if (atIndex !== -1 && atIndex === value.length - 1) {
setState(prev => ({
...prev,
showEmployeeMenu: true,
mentionQuery: ''
}));
} else if (atIndex !== -1 && value.length > atIndex + 1) {
const query = value.substring(atIndex + 1);
setState(prev => ({
...prev,
showEmployeeMenu: true,
mentionQuery: query
}));
} else {
setState(prev => ({
...prev,
showEmployeeMenu: false,
mentionQuery: ''
}));
}
};
const handleEmployeeSelect = (employeeName: string) => {
const atIndex = currentInput.lastIndexOf('@');
if (atIndex !== -1) {
const newInput = currentInput.substring(0, atIndex) + '@' + employeeName + ' ';
setCurrentInput(newInput);
}
setState(prev => ({
...prev,
showEmployeeMenu: false,
mentionQuery: ''
}));
};
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
if (files.length > 0) {
const uploadedFiles = files.map(file => ({
name: file.name,
type: file.type,
size: file.size
}));
setState(prev => ({
...prev,
hasUploadedFiles: true,
uploadedFiles: [...prev.uploadedFiles, ...uploadedFiles]
}));
}
};
const removeFile = (index: number) => {
setState(prev => ({
...prev,
uploadedFiles: prev.uploadedFiles.filter((_, i) => i !== index),
hasUploadedFiles: prev.uploadedFiles.length > 1
}));
};
const handleQuestionClick = (question: string) => {
setCurrentInput(question);
};
const renderEmployeeMenu = () => {
if (!state.showEmployeeMenu) return null;
const filteredEmployees = employees.filter(emp =>
emp.name.toLowerCase().includes(state.mentionQuery.toLowerCase())
);
return (
<div className="absolute bottom-full left-[285px] mb-2 w-48 p-2 bg-Text-White-00 rounded-2xl shadow-[0px_1px_4px_4px_rgba(14,18,27,0.08)] flex flex-col justify-start items-start gap-1 z-10">
{filteredEmployees.slice(0, 3).map((employee, index) => (
<div
key={employee.id}
onClick={() => handleEmployeeSelect(employee.name)}
className={`self-stretch px-3 py-2 rounded-xl flex flex-col justify-start items-start gap-2.5 overflow-hidden cursor-pointer hover:bg-Text-Gray-100 ${index === 2 ? 'bg-Text-Gray-100' : ''
}`}
>
<div className={`self-stretch justify-start text-Text-Dark-950 text-sm leading-tight ${index === 2 ? 'font-medium' : 'font-normal'
}`}>
{employee.name}
</div>
</div>
))}
</div>
);
};
const renderUploadedFiles = () => {
if (state.uploadedFiles.length === 0) return null;
return (
<div className="inline-flex justify-start items-center gap-3 mb-4">
{state.uploadedFiles.map((file, index) => (
<div key={index} className="w-40 max-w-40 p-2 bg-Text-White-00 rounded-full outline outline-1 outline-offset-[-1px] outline-Outline-Outline-Gray-200 inline-flex flex-col justify-start items-start gap-2.5 overflow-hidden">
<div className="self-stretch pr-2 inline-flex justify-between items-center">
<div className="flex-1 flex justify-start items-center gap-1.5">
<div className="w-6 h-6 relative bg-Icon-Gray-600 rounded-full overflow-hidden">
<div className="left-[6px] top-[6px] absolute">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 1.13477V3.20004C7 3.48006 7 3.62007 7.0545 3.72703C7.10243 3.82111 7.17892 3.8976 7.273 3.94554C7.37996 4.00004 7.51997 4.00004 7.8 4.00004H9.86527M8 6.5H4M8 8.5H4M5 4.5H4M7 1H4.4C3.55992 1 3.13988 1 2.81901 1.16349C2.53677 1.3073 2.3073 1.53677 2.16349 1.81901C2 2.13988 2 2.55992 2 3.4V8.6C2 9.44008 2 9.86012 2.16349 10.181C2.3073 10.4632 2.53677 10.6927 2.81901 10.8365C3.13988 11 3.55992 11 4.4 11H7.6C8.44008 11 8.86012 11 9.18099 10.8365C9.46323 10.6927 9.6927 10.4632 9.83651 10.181C10 9.86012 10 9.44008 10 8.6V4L7 1Z" stroke="var(--Text-White-00, #FDFDFD)" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
<div className="flex-1 justify-start text-Text-Dark-950 text-sm font-medium font-['Inter'] leading-tight">{file.name}</div>
</div>
<div onClick={() => removeFile(index)} className="cursor-pointer">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4L4 12M4 4L12 12" stroke="var(--Icon-Gray-400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
</div>
))}
</div>
);
};
const renderChatInterface = () => {
if (state.messages.length === 0) {
return (
<div className="w-[736px] flex-1 max-w-[736px] pt-48 flex flex-col justify-between items-center">
<div className="self-stretch flex flex-col justify-start items-center gap-6">
<div className="justify-start text-Text-Gray-800 text-2xl font-medium font-['Neue_Montreal'] leading-normal">What would you like to understand?</div>
<div className="p-1 bg-Neutrals-NeutralSlate100 rounded-xl inline-flex justify-start items-center gap-1">
{categories.map((category) => (
<div
key={category}
onClick={() => setSelectedCategory(category)}
className={`px-3 py-1.5 rounded-lg shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden cursor-pointer ${selectedCategory === category ? 'bg-white' : ''
}`}
>
<div className="px-0.5 flex justify-center items-center">
<div className={`justify-start text-xs font-medium font-['Inter'] leading-none ${selectedCategory === category ? 'text-Neutrals-NeutralSlate900' : 'text-Neutrals-NeutralSlate600'
}`}>{category}</div>
</div>
</div>
))}
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="self-stretch inline-flex justify-start items-center gap-3">
{questionStarters.map((question, index) => (
<div
key={index}
onClick={() => handleQuestionClick(question)}
className="flex-1 h-48 px-3 py-4 bg-Main-BG-Gray-50 rounded-2xl inline-flex flex-col justify-between items-start overflow-hidden cursor-pointer hover:bg-Main-BG-Gray-100"
>
<div>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_19557)">
<path d="M7.57496 7.5013C7.77088 6.94436 8.15759 6.47472 8.66659 6.17558C9.17559 5.87643 9.77404 5.76708 10.3559 5.8669C10.9378 5.96671 11.4656 6.26924 11.8459 6.72091C12.2261 7.17258 12.4342 7.74424 12.4333 8.33464C12.4333 10.0013 9.93329 10.8346 9.93329 10.8346M9.99996 14.168H10.0083M18.3333 10.0013C18.3333 14.6037 14.6023 18.3346 9.99996 18.3346C5.39759 18.3346 1.66663 14.6037 1.66663 10.0013C1.66663 5.39893 5.39759 1.66797 9.99996 1.66797C14.6023 1.66797 18.3333 5.39893 18.3333 10.0013Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_818_19557">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
</div>
<div className="self-stretch justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">{question}</div>
</div>
))}
</div>
</div>
</div>
{renderChatInput()}
</div>
);
}
return (
<div className="w-[736px] flex-1 max-w-[736px] flex flex-col">
<div className="flex-1 overflow-y-auto py-6">
{state.messages.map((message) => (
<div key={message.id} className={`mb-4 flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] p-4 rounded-2xl ${message.role === 'user'
? 'bg-Brand-Orange text-white'
: 'bg-Main-BG-Gray-50 text-Text-Gray-800'
}`}>
{message.content}
</div>
</div>
))}
{state.isLoading && (
<div className="flex justify-start mb-4">
<div className="bg-Main-BG-Gray-50 text-Text-Gray-800 p-4 rounded-2xl">
<div className="flex space-x-1">
<div className="w-2 h-2 bg-Text-Gray-500 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-Text-Gray-500 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
<div className="w-2 h-2 bg-Text-Gray-500 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
</div>
</div>
</div>
)}
</div>
{renderChatInput()}
</div>
);
};
const renderChatInput = () => {
return (
<div className="self-stretch pl-5 pr-3 pt-5 pb-3 relative bg-Main-BG-Gray-50 rounded-3xl flex flex-col justify-start items-start gap-4">
{renderUploadedFiles()}
<div className="self-stretch justify-start text-Text-Gray-500 text-base font-normal font-['Inter'] leading-normal">
{currentInput || "Ask anything, use @ to tag staff and ask questions."}
</div>
<div className="self-stretch inline-flex justify-between items-center">
<div className="flex justify-start items-center gap-4">
<div onClick={() => fileInputRef.current?.click()} className="cursor-pointer">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.6271 9.08442L10.1141 16.5974C8.40556 18.306 5.63546 18.306 3.92692 16.5974C2.21837 14.8889 2.21837 12.1188 3.92692 10.4102L11.4399 2.89724C12.579 1.75821 14.4257 1.75821 15.5647 2.89724C16.7037 4.03627 16.7037 5.883 15.5647 7.02203L8.34633 14.2404C7.77682 14.8099 6.85345 14.8099 6.28394 14.2404C5.71442 13.6709 5.71442 12.7475 6.28394 12.178L12.6184 5.84352" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div onClick={() => fileInputRef.current?.click()} className="cursor-pointer">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5 17.5H5.77614C5.2713 17.5 5.01887 17.5 4.90199 17.4002C4.80056 17.3135 4.74674 17.1836 4.75721 17.0506C4.76927 16.8974 4.94776 16.7189 5.30474 16.3619L12.3905 9.27614C12.7205 8.94613 12.8855 8.78112 13.0758 8.7193C13.2432 8.66492 13.4235 8.66492 13.5908 8.7193C13.7811 8.78112 13.9461 8.94613 14.2761 9.27614L17.5 12.5V13.5M13.5 17.5C14.9001 17.5 15.6002 17.5 16.135 17.2275C16.6054 16.9878 16.9878 16.6054 17.2275 16.135C17.5 15.6002 17.5 14.9001 17.5 13.5M13.5 17.5H6.5C5.09987 17.5 4.3998 17.5 3.86502 17.2275C3.39462 16.9878 3.01217 16.6054 2.77248 16.135C2.5 15.6002 2.5 14.9001 2.5 13.5V6.5C2.5 5.09987 2.5 4.3998 2.77248 3.86502C3.01217 3.39462 3.39462 3.01217 3.86502 2.77248C4.3998 2.5 5.09987 2.5 6.5 2.5H13.5C14.9001 2.5 15.6002 2.5 16.135 2.77248C16.6054 3.01217 16.9878 3.39462 17.2275 3.86502C17.5 4.3998 17.5 5.09987 17.5 6.5V13.5M8.75 7.08333C8.75 8.00381 8.00381 8.75 7.08333 8.75C6.16286 8.75 5.41667 8.00381 5.41667 7.08333C5.41667 6.16286 6.16286 5.41667 7.08333 5.41667C8.00381 5.41667 8.75 6.16286 8.75 7.08333Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="cursor-pointer">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_19694)">
<path d="M13.3334 6.66745V10.8341C13.3334 11.4972 13.5968 12.133 14.0657 12.6019C14.5345 13.0707 15.1704 13.3341 15.8334 13.3341C16.4965 13.3341 17.1324 13.0707 17.6012 12.6019C18.07 12.133 18.3334 11.4972 18.3334 10.8341V10.0008C18.3333 8.11998 17.6969 6.29452 16.5278 4.82123C15.3587 3.34794 13.7256 2.31347 11.894 1.88603C10.0624 1.45859 8.14003 1.66332 6.43955 2.46692C4.73906 3.27053 3.36042 4.62575 2.5278 6.31222C1.69519 7.99869 1.45756 9.91723 1.85356 11.7559C2.24956 13.5945 3.2559 15.2451 4.70895 16.4393C6.16199 17.6335 7.97628 18.3011 9.85681 18.3334C11.7373 18.3657 13.5735 17.761 15.0668 16.6175M13.3334 10.0008C13.3334 11.8417 11.841 13.3341 10.0001 13.3341C8.15914 13.3341 6.66676 11.8417 6.66676 10.0008C6.66676 8.15984 8.15914 6.66745 10.0001 6.66745C11.841 6.66745 13.3334 8.15984 13.3334 10.0008Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_818_19694">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
</div>
</div>
<div
onClick={handleSendMessage}
className={`p-2.5 rounded-[999px] flex justify-start items-center gap-2.5 overflow-hidden cursor-pointer ${currentInput.trim() || state.uploadedFiles.length > 0
? 'bg-Main-BG-Gray-800'
: 'bg-Text-Gray-300'
}`}
>
<div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13.3346V2.66797M8 2.66797L4 6.66797M8 2.66797L12 6.66797" stroke="var(--Text-White-00, #FDFDFD)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
</div>
{renderEmployeeMenu()}
<textarea
ref={inputRef}
value={currentInput}
onChange={handleInputChange}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
}}
className="absolute inset-0 w-full h-full opacity-0 resize-none outline-none"
placeholder=""
/>
<input
ref={fileInputRef}
type="file"
multiple
onChange={handleFileUpload}
className="hidden"
accept=".pdf,.doc,.docx,.txt,.png,.jpg,.jpeg"
/>
</div>
);
};
return (
<div className="w-[1440px] h-[810px] p-4 bg-Neutrals-NeutralSlate200 inline-flex justify-start items-start overflow-hidden">
<div className="flex-1 self-stretch rounded-3xl shadow-[0px_0px_15px_0px_rgba(0,0,0,0.08)] flex justify-between items-start overflow-hidden">
<Sidebar companyName="Zitlac Media" />
<div className="flex-1 self-stretch py-6 bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-2.5">
{renderChatInterface()}
</div>
</div>
</div>
);
};
export default ChatNew;

View File

@@ -1,33 +1,69 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useOrg } from '../contexts/OrgContext'; import { useOrg } from '../contexts/OrgContext';
import { Card, Button } from '../components/UiKit';
import { FigmaAlert } from '../components/figma/FigmaAlert';
import { CompanyReport } from '../types'; import { CompanyReport } from '../types';
import RadarPerformanceChart from '../components/charts/RadarPerformanceChart'; import { CompanyWikiManager } from '../components/CompanyWiki';
const CompanyWiki: React.FC = () => { const CompanyWiki: React.FC = () => {
const { org, employees, getFullCompanyReportHistory, generateCompanyWiki } = useOrg(); const { org, employees, getFullCompanyReportHistory, generateCompanyWiki } = useOrg();
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [companyReport, setCompanyReport] = useState<CompanyReport | null>(null); const [companyReport, setCompanyReport] = useState<CompanyReport | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [onboardingProgress, setOnboardingProgress] = useState(60);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
const history = await getFullCompanyReportHistory(); const history = await getFullCompanyReportHistory();
if (history.length) setCompanyReport(history[0]); if (history.length) {
setCompanyReport(history[0]);
}
} catch (e) { } catch (e) {
console.error('Failed loading company report history', e); console.error('Failed loading company report history', e);
} }
})(); })();
}, [getFullCompanyReportHistory]); }, [getFullCompanyReportHistory]);
const generateReport = async () => { // Determine wiki state based on company data
const wikiState = companyReport ? 'completed' : 'empty';
// Create Q&A items from company report or org data
const qaItems = companyReport ? [
{
question: "What is the mission of your company?",
answer: org?.mission || "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: org?.evolution || "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: org?.vision || "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: org?.advantages || "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: org?.vulnerabilities || "Dependence on a single marketing channel, weak middle management, and rising customer acquisition costs."
}
] : undefined;
// Convert employees to suggested format for invitations
const suggestedEmployees = employees?.map(emp => ({
id: emp.id,
name: emp.name || emp.email,
email: emp.email
})) || [];
const handleCompleteOnboarding = async () => {
setIsGenerating(true); setIsGenerating(true);
setError(null); setError(null);
try { try {
const report = await generateCompanyWiki(); const report = await generateCompanyWiki();
setCompanyReport(report); setCompanyReport(report);
setOnboardingProgress(100);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
setError('Failed to generate company wiki'); setError('Failed to generate company wiki');
@@ -37,295 +73,21 @@ const CompanyWiki: React.FC = () => {
}; };
return ( return (
<div className="p-6 max-w-6xl mx-auto"> <div className="flex-1 self-stretch bg-Neutrals-NeutralSlate0 rounded-tr-3xl rounded-br-3xl inline-flex flex-col justify-start items-start">
<div className="mb-6"> {error && (
<h1 className="text-3xl font-bold text-[--text-primary]">Company Wiki</h1> <div className="self-stretch p-4 bg-red-50 border-l-4 border-red-400 text-red-700 text-sm">
<p className="text-[--text-secondary] mt-1"> {error}
Organization overview and insights
</p>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<Card>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3">Company Info</h3>
<div className="space-y-2">
<div>
<span className="text-sm text-[--text-secondary]">Name:</span>
<div className="font-medium text-[--text-primary]">{org?.name}</div>
</div>
<div>
<span className="text-sm text-[--text-secondary]">Industry:</span>
<div className="font-medium text-[--text-primary]">{org?.industry}</div>
</div>
<div>
<span className="text-sm text-[--text-secondary]">Size:</span>
<div className="font-medium text-[--text-primary]">{org?.size}</div>
</div>
</div>
</Card>
<Card>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3">Team Stats</h3>
<div className="space-y-2">
<div>
<span className="text-sm text-[--text-secondary]">Total Employees:</span>
<div className="font-medium text-[--text-primary]">{employees.length}</div>
</div>
<div>
<span className="text-sm text-[--text-secondary]">Departments:</span>
<div className="font-medium text-[--text-primary]">
{[...new Set(employees.map(e => e.department))].length}
</div>
</div>
<div>
<span className="text-sm text-[--text-secondary]">Roles:</span>
<div className="font-medium text-[--text-primary]">
{[...new Set(employees.map(e => e.role))].length}
</div>
</div>
</div>
</Card>
<Card>
<h3 className="text-lg font-semibold text-[--text-primary] mb-3">Quick Actions</h3>
<div className="space-y-3">
<Button onClick={generateReport} disabled={isGenerating} className="w-full">
{isGenerating ? 'Generating...' : companyReport ? 'Regenerate Company Wiki' : 'Generate Company Wiki'}
</Button>
{error && <FigmaAlert type="error" message={error} />}
{!companyReport && !isGenerating && (
<FigmaAlert type="info" message="No company wiki generated yet. Use the button above to create one." />
)} )}
<Button variant="secondary" className="w-full" disabled={!companyReport}>
Export Data <CompanyWikiManager
</Button> initialState={wikiState}
</div> onboardingProgress={onboardingProgress}
</Card> onCompleteOnboarding={handleCompleteOnboarding}
</div> qaItems={qaItems}
{companyReport && ( suggestedEmployees={suggestedEmployees}
<div className="space-y-6">
<Card>
<h3 className="text-xl font-semibold text-[--text-primary] mb-4">Executive Summary</h3>
<p className="text-[--text-secondary] whitespace-pre-line mb-4">{companyReport.executiveSummary}</p>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="text-center p-4 bg-[--background-secondary] rounded-lg">
<div className="text-2xl font-bold text-blue-500">{companyReport.overview.totalEmployees}</div>
<div className="text-sm text-[--text-secondary]">Employees</div>
</div>
<div className="text-center p-4 bg-[--background-secondary] rounded-lg">
<div className="text-2xl font-bold text-green-500">{companyReport.overview?.departmentBreakdown?.length || 0}</div>
<div className="text-sm text-[--text-secondary]">Departments</div>
</div>
<div className="text-center p-4 bg-[--background-secondary] rounded-lg">
<div className="text-2xl font-bold text-purple-500">{companyReport.organizationalStrengths?.length || 0}</div>
<div className="text-sm text-[--text-secondary]">Strength Areas</div>
</div>
<div className="text-center p-4 bg-[--background-secondary] rounded-lg">
<div className="text-2xl font-bold text-orange-500">{companyReport.organizationalRisks?.length || 0}</div>
<div className="text-sm text-[--text-secondary]">Risks</div>
</div>
</div>
{(Array.isArray(companyReport.gradingOverview) && companyReport.gradingOverview.length > 0) && (
<div className="mt-6 p-4 bg-[--background-tertiary] rounded-lg">
<RadarPerformanceChart
title="Organizational Grading"
data={companyReport.gradingOverview.map((g: any) => ({
label: g.category || g.department || g.subject || 'Metric',
value: g.value ?? g.averageScore ?? 0
}))}
/> />
</div> </div>
)}
</Card>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<h4 className="text-lg font-semibold text-[--text-primary] mb-3">Strengths</h4>
<ul className="space-y-2">
{(companyReport.organizationalStrengths || []).map((s: any, i) => <li key={i} className="text-[--text-secondary] text-sm"> {s.area || s}</li>)}
</ul>
</Card>
<Card>
<h4 className="text-lg font-semibold text-[--text-primary] mb-3">Risks</h4>
<ul className="space-y-2">
{(companyReport.organizationalRisks || []).map((r, i) => <li key={i} className="text-[--text-secondary] text-sm"> {r}</li>)}
</ul>
</Card>
<Card>
<h4 className="text-lg font-semibold text-[--text-primary] mb-3">Forward Plan</h4>
<div>
<h5 className="font-medium text-[--text-primary] text-sm mb-1">Goals</h5>
<ul className="mb-2 list-disc list-inside text-[--text-secondary] text-sm space-y-1">
{(companyReport.operatingPlan?.nextQuarterGoals || companyReport.forwardOperatingPlan?.quarterlyGoals || []).map((g: string, i: number) => <li key={i}>{g}</li>)}
</ul>
<h5 className="font-medium text-[--text-primary] text-sm mb-1">Resource Needs</h5>
<ul className="mb-2 list-disc list-inside text-[--text-secondary] text-sm space-y-1">
{(companyReport.operatingPlan?.resourceNeeds || companyReport.forwardOperatingPlan?.resourceNeeds || []).map((g: string, i: number) => <li key={i}>{g}</li>)}
</ul>
<h5 className="font-medium text-[--text-primary] text-sm mb-1">Risk Mitigation</h5>
<ul className="list-disc list-inside text-[--text-secondary] text-sm space-y-1">
{(companyReport.operatingPlan?.riskMitigation || companyReport.forwardOperatingPlan?.riskMitigation || []).map((g: string, i: number) => <li key={i}>{g}</li>)}
</ul>
</div>
</Card>
</div>
</div>
)}
{/* Company Profile - Q&A Format from Onboarding */}
<div className="mt-6">
<h3 className="text-2xl font-semibold text-[--text-primary] mb-6">Company Profile</h3>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{org?.mission && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">What is your company's mission?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.mission}</p>
</div>
</div>
</Card>
)}
{org?.vision && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">What is your company's vision?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.vision}</p>
</div>
</div>
</Card>
)}
{org?.evolution && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">How has your company evolved over time?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.evolution}</p>
</div>
</div>
</Card>
)}
{org?.advantages && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">What are your competitive advantages?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.advantages}</p>
</div>
</div>
</Card>
)}
{org?.vulnerabilities && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">What are your key vulnerabilities?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.vulnerabilities}</p>
</div>
</div>
</Card>
)}
{org?.shortTermGoals && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">What are your short-term goals?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.shortTermGoals}</p>
</div>
</div>
</Card>
)}
{org?.longTermGoals && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">What are your long-term goals?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.longTermGoals}</p>
</div>
</div>
</Card>
)}
{org?.cultureDescription && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">How would you describe your company culture?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.cultureDescription}</p>
</div>
</div>
</Card>
)}
{org?.workEnvironment && (
<Card className="p-4">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">What is your work environment like?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.workEnvironment}</p>
</div>
</div>
</Card>
)}
{org?.additionalContext && (
<Card className="p-4 lg:col-span-2">
<div className="space-y-3">
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Question:</h4>
<p className="text-[--text-primary] font-medium">Any additional context about your company?</p>
</div>
<div>
<h4 className="text-sm font-medium text-[--text-secondary] mb-1">Answer:</h4>
<p className="text-[--text-primary] text-sm leading-relaxed">{org.additionalContext}</p>
</div>
</div>
</Card>
)}
</div>
</div>
{org?.description && (
<Card className="mt-6">
<h3 className="text-lg font-semibold text-[--text-primary] mb-3">About</h3>
<p className="text-[--text-secondary]">{org.description}</p>
</Card>
)}
</div>
); );
}; };

101
pages/EmployeeFormNew.tsx Normal file
View File

@@ -0,0 +1,101 @@
import React, { useState } from 'react';
const EmployeeFormNew: React.FC = () => {
const [currentStep, setCurrentStep] = useState(1);
return (
<div className="w-[1440px] bg-white inline-flex justify-start items-center overflow-hidden">
<div className="flex-1 h-[810px] px-32 py-48 bg-Neutrals-NeutralSlate0 flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-12">
<div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute">
<svg width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M2.57408 17.8138C3.11835 18.3649 3.11834 19.2585 2.57406 19.8097L2.54619 19.8379C2.00191 20.389 1.11946 20.389 0.57519 19.8379C0.030919 19.2867 0.0309274 18.3931 0.575208 17.842L0.603083 17.8137C1.14736 17.2626 2.02981 17.2626 2.57408 17.8138Z" fill="url(#paint0_linear_981_10577)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M9.12583 18.2374C9.66912 18.7896 9.66752 19.6832 9.12226 20.2333L5.2617 24.1286C4.71644 24.6787 3.83399 24.6771 3.2907 24.125C2.74741 23.5728 2.74901 22.6792 3.29427 22.1291L7.15483 18.2338C7.70009 17.6837 8.58254 17.6853 9.12583 18.2374Z" fill="url(#paint1_linear_981_10577)" />
<defs>
<linearGradient id="paint0_linear_981_10577" x1="1.57463" y1="17.4004" x2="1.57463" y2="20.2513" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint1_linear_981_10577" x1="6.20827" y1="17.8223" x2="6.20827" y2="24.5401" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-2xl font-semibold font-['Inter'] leading-8">Welcome to the Auditly Employee Assessment</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal">Let's learn about your role, contribution and help us get a better understand of how you work best.</div>
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-8">
<div className="self-stretch flex flex-col justify-start items-start gap-6">
<div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight">Your Role & Output</div>
</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-sm font-normal font-['Inter'] leading-tight">Tell us about your current role and what you work on</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">Your Name</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<input
type="text"
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500 outline-none"
placeholder="Enter your full name"
/>
</div>
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">What is your role at the company?</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<input
type="text"
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500 outline-none"
placeholder="e.g. Software Engineer, Marketing Manager"
/>
</div>
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">What department do you work in?</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<input
type="text"
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500 outline-none"
placeholder="e.g. Engineering, Sales, Marketing"
/>
</div>
</div>
</div>
</div>
<div className="self-stretch inline-flex justify-start items-center gap-3">
<button className="flex-1 px-6 py-3.5 bg-Brand-Orange rounded-[999px] inline-flex justify-center items-center gap-2 overflow-hidden">
<div className="justify-center text-Neutrals-NeutralSlate0 text-base font-medium font-['Inter'] leading-normal">Continue</div>
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default EmployeeFormNew;

File diff suppressed because it is too large Load Diff

133
pages/HelpNew.tsx Normal file
View File

@@ -0,0 +1,133 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import Sidebar from '../components/figma/Sidebar';
interface FAQItem {
question: string;
answer: string;
isOpen: boolean;
}
const HelpNew: React.FC = () => {
const { user } = useAuth();
const navigate = useNavigate();
const [faqItems, setFaqItems] = useState<FAQItem[]>([
{
question: "What is the process for submitting a support ticket?",
answer: "Team members will undergo evaluations every three months, focusing on their performance, teamwork, and communication skills. Role advancements will be considered at these intervals.",
isOpen: true
},
{
question: "How can I reset my password?",
answer: "To reset your password, click on the 'Forgot Password' link on the login page and follow the instructions sent to your email address.",
isOpen: false
},
{
question: "What are the criteria for performance reviews?",
answer: "Performance reviews are based on multiple factors including goal achievement, collaboration, innovation, and adherence to company values. Reviews are conducted quarterly with detailed feedback sessions.",
isOpen: false
},
{
question: "How can I access the company's training resources?",
answer: "Training resources are available through our internal learning portal. You can access them from the main dashboard under the 'Learning & Development' section.",
isOpen: false
},
{
question: "What should I do if I encounter a technical issue?",
answer: "For technical issues, first check our troubleshooting guide. If the issue persists, contact our IT support team through the help desk or email support@company.com.",
isOpen: false
},
{
question: "How do I provide feedback on team projects?",
answer: "Feedback can be provided through our project management system or during regular team meetings. We encourage constructive feedback that helps improve project outcomes.",
isOpen: false
}
]);
const toggleFAQ = (index: number) => {
setFaqItems(prev => prev.map((item, i) => ({
...item,
isOpen: i === index ? !item.isOpen : item.isOpen
})));
};
const handleContactUs = () => {
// In a real app, this would open a contact form or support chat
alert('Contact functionality would be implemented here');
};
if (!user) {
navigate('/login');
return null;
}
return (
<div className="w-[1440px] h-[840px] p-4 bg-Neutrals-NeutralSlate200 inline-flex justify-start items-start overflow-hidden">
<div className="flex-1 self-stretch rounded-3xl shadow-[0px_0px_15px_0px_rgba(0,0,0,0.08)] flex justify-between items-start overflow-hidden">
<Sidebar companyName="Zitlac Media" />
<div className="flex-1 self-stretch pt-8 pb-6 bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-start items-center gap-6">
<div className="w-[680px] justify-start text-Text-Gray-800 text-2xl font-medium font-['Neue_Montreal'] leading-normal">Help & Support</div>
<div className="w-[680px] flex flex-col justify-start items-start gap-4">
{faqItems.map((item, index) => (
<div
key={index}
className="self-stretch p-3 bg-Text-Gray-100 rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden"
>
<div
onClick={() => toggleFAQ(index)}
className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2 cursor-pointer"
>
<div className="flex-1 justify-start text-Text-Dark-950 text-base font-medium font-['Inter'] leading-normal">
{item.question}
</div>
<div>
{item.isOpen ? (
<svg width="12" height="2" viewBox="0 0 12 2" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 1L1 1" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
) : (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.99996 4.16797V15.8346M4.16663 10.0013H15.8333" stroke="var(--Text-Gray-400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)}
</div>
<div className="w-5 h-5 opacity-0 border border-zinc-800" />
</div>
{item.isOpen && (
<div className="self-stretch p-6 bg-Neutrals-NeutralSlate0 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-base font-normal font-['Inter'] leading-normal">
{item.answer}
</div>
</div>
)}
</div>
))}
</div>
<div className="w-[680px] px-5 py-4 bg-Text-Gray-800 rounded-2xl backdrop-blur-blur inline-flex justify-center items-center gap-12 overflow-hidden">
<div className="flex-1 inline-flex flex-col justify-center items-start gap-2">
<div className="self-stretch justify-start text-Text-White-00 text-base font-medium font-['Inter'] leading-normal">Still have questions?</div>
<div className="self-stretch justify-start text-Text-Gray-400 text-sm font-normal font-['Inter'] leading-tight">We are available for 24/7</div>
</div>
<div
onClick={handleContactUs}
className="px-3 py-2.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden cursor-pointer hover:bg-Brand-Orange/90"
>
<div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.58685 5.90223C6.05085 6.86865 6.68337 7.77441 7.48443 8.57546C8.28548 9.37651 9.19124 10.009 10.1577 10.473C10.2408 10.5129 10.2823 10.5329 10.3349 10.5482C10.5218 10.6027 10.7513 10.5636 10.9096 10.4502C10.9542 10.4183 10.9923 10.3802 11.0685 10.304C11.3016 10.071 11.4181 9.95443 11.5353 9.87824C11.9772 9.59091 12.5469 9.59091 12.9889 9.87824C13.106 9.95443 13.2226 10.071 13.4556 10.304L13.5856 10.4339C13.9398 10.7882 14.117 10.9654 14.2132 11.1556C14.4046 11.534 14.4046 11.9809 14.2132 12.3592C14.117 12.5495 13.9399 12.7266 13.5856 13.0809L13.4805 13.186C13.1274 13.5391 12.9508 13.7156 12.7108 13.8505C12.4445 14.0001 12.0308 14.1077 11.7253 14.1068C11.45 14.1059 11.2619 14.0525 10.8856 13.9457C8.86333 13.3718 6.95509 12.2888 5.36311 10.6968C3.77112 9.10479 2.68814 7.19655 2.11416 5.17429C2.00735 4.79799 1.95395 4.60984 1.95313 4.33455C1.95222 4.02906 2.0598 3.6154 2.20941 3.34907C2.34424 3.10904 2.52078 2.9325 2.87386 2.57942L2.97895 2.47433C3.33325 2.12004 3.5104 1.94289 3.70065 1.84666C4.07903 1.65528 4.52587 1.65528 4.90424 1.84666C5.0945 1.94289 5.27164 2.12004 5.62594 2.47433L5.75585 2.60424C5.98892 2.83732 6.10546 2.95385 6.18165 3.07104C6.46898 3.51296 6.46898 4.08268 6.18165 4.52461C6.10546 4.6418 5.98892 4.75833 5.75585 4.9914C5.67964 5.06761 5.64154 5.10571 5.60965 5.15026C5.4963 5.30854 5.45717 5.53805 5.51165 5.72495C5.52698 5.77754 5.54694 5.81911 5.58685 5.90223Z" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Contact Us</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default HelpNew;

View File

@@ -1,266 +1,403 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
import { useOrg } from '../contexts/OrgContext'; import { Button } from '../components/UiKit';
import { Card, Button } from '../components/UiKit';
const Login: React.FC = () => { type AuthStep = 'email' | 'otp' | 'password-fallback';
const ModernLogin: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { inviteCode: routeInviteCode } = useParams<{ inviteCode: string }>(); const { inviteCode: routeInviteCode } = useParams<{ inviteCode: string }>();
const [email, setEmail] = useState('demo@auditly.com');
const [password, setPassword] = useState('demo123'); // Auth state
const { signInWithGoogle, signInWithEmail, signUpWithEmail, user, loading, sendOTP: authSendOTP, verifyOTP: authVerifyOTP } = useAuth();
// Form state
const [step, setStep] = useState<AuthStep>('email');
const [email, setEmail] = useState('');
const [otp, setOtp] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [inviteCode, setInviteCode] = useState<string | null>(null); const [inviteCode, setInviteCode] = useState<string | null>(null);
const { signInWithGoogle, signInWithEmail, signUpWithEmail, user, loading } = useAuth(); const [resendCooldown, setResendCooldown] = useState(0);
const { consumeInvite, org } = useOrg(); const [demoOTP, setDemoOTP] = useState<string | null>(null);
// Extract invite code from URL
useEffect(() => { useEffect(() => {
// Check for invite code in route params first, then fallback to query params
if (routeInviteCode) { if (routeInviteCode) {
console.log('Invite code from route params:', routeInviteCode);
setInviteCode(routeInviteCode); setInviteCode(routeInviteCode);
// Clear demo credentials for invite flow
setEmail('');
setPassword('');
} else { } else {
// Extract query params from hash-based URL
const hashSearch = location.hash.includes('?') ? location.hash.split('?')[1] : ''; const hashSearch = location.hash.includes('?') ? location.hash.split('?')[1] : '';
const searchParams = new URLSearchParams(hashSearch); const searchParams = new URLSearchParams(hashSearch);
const queryInvite = searchParams.get('invite'); const queryInvite = searchParams.get('invite');
if (queryInvite) { if (queryInvite) {
console.log('Invite code from query params:', queryInvite);
setInviteCode(queryInvite); setInviteCode(queryInvite);
// Clear demo credentials for invite flow
setEmail('');
setPassword('');
} }
} }
}, [routeInviteCode, location]); }, [routeInviteCode, location]);
const handleSuccessfulLogin = async () => { // Handle successful authentication
if (inviteCode) {
// Invite flow - redirect to org selection with invite code
navigate(`/org-selection?invite=${inviteCode}`, { replace: true });
} else {
// Regular login - redirect to org selection to choose/create org
navigate('/org-selection', { replace: true });
}
};
useEffect(() => { useEffect(() => {
if (user && !loading) { if (user && !loading) {
handleSuccessfulLogin(); if (inviteCode) {
navigate(`/org-selection?invite=${inviteCode}`, { replace: true });
} else {
navigate('/org-selection', { replace: true });
} }
}, [user, loading]); }
}, [user, loading, navigate, inviteCode]);
const handleEmailLogin = async (e: React.FormEvent) => { // Resend cooldown timer
e.preventDefault(); useEffect(() => {
if (resendCooldown > 0) {
const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1000);
return () => clearTimeout(timer);
}
}, [resendCooldown]);
const sendOTP = async (emailAddress: string) => {
try {
setIsLoading(true); setIsLoading(true);
setError(''); setError('');
try { setDemoOTP(null);
if (inviteCode) {
// For invites, try to create account first since they're new users
console.log('Invite flow: attempting to create account for', email);
await signUpWithEmail(email, password, email.split('@')[0]);
} else {
// Regular login
await signInWithEmail(email, password);
}
// Success will be handled by the useEffect hook
} catch (error) {
console.error('Auth failed:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (inviteCode) { // Call auth context method
// For invite flow, if account creation failed, try login instead const response = await authSendOTP(emailAddress, inviteCode || undefined);
if (errorMessage.includes('User already exists') || errorMessage.includes('already-exists')) {
// If OTP is returned in response (demo mode), display it
if (response.otp) {
setDemoOTP(response.otp);
}
setStep('otp');
setResendCooldown(60); // 60 second cooldown
} catch (err) {
console.error('OTP send error:', err);
setError(err instanceof Error ? err.message : 'Failed to send verification code. Please try again.');
} finally {
setIsLoading(false);
}
}; const verifyOTP = async () => {
try {
setIsLoading(true);
setError('');
// Call auth context method
await authVerifyOTP(email, otp, inviteCode || undefined);
// Success - user will be set in auth context and useEffect will handle navigation
} catch (err) {
console.error('OTP verification error:', err);
setError(err instanceof Error ? err.message : 'Invalid verification code. Please try again.');
} finally {
setIsLoading(false);
}
};
const handleEmailSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!email.trim()) return;
await sendOTP(email);
};
const handleOTPSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!otp.trim()) return;
await verifyOTP();
};
const handlePasswordFallback = async (e: React.FormEvent) => {
e.preventDefault();
if (!password.trim()) return;
try {
setIsLoading(true);
setError('');
// Try login first, then signup if user doesn't exist
try { try {
console.log('Account exists, trying login instead...');
await signInWithEmail(email, password); await signInWithEmail(email, password);
} catch (loginError) { } catch (loginError) {
console.error('Login also failed:', loginError); // If login fails, try creating account
setError(`Account exists but password is incorrect. Please check your password or contact your administrator.`);
setIsLoading(false);
}
} else {
setError(`Failed to create account: ${errorMessage}. Please try a different email or contact your administrator.`);
setIsLoading(false);
}
} else {
// Regular login flow - try signup if user not found
if (errorMessage.includes('User not found')) {
try {
console.log('User not found, attempting sign-up...');
await signUpWithEmail(email, password, email.split('@')[0]); await signUpWithEmail(email, password, email.split('@')[0]);
// Success will be handled by the useEffect hook }
} catch (signUpError) { } catch (err) {
console.error('Sign-up also failed:', signUpError); console.error('Password auth error:', err);
setError(`Failed to create account: ${signUpError instanceof Error ? signUpError.message : 'Unknown error'}`); setError(err instanceof Error ? err.message : 'Authentication failed');
} finally {
setIsLoading(false); setIsLoading(false);
} }
} else {
setError(`Login failed: ${errorMessage}`);
setIsLoading(false);
}
}
}
}; };
const handleGoogleLogin = async () => { const handleGoogleAuth = async () => {
try {
setIsLoading(true); setIsLoading(true);
setError(''); setError('');
try {
await signInWithGoogle(); await signInWithGoogle();
// Success will be handled by the useEffect hook } catch (err) {
} catch (error) { console.error('Google auth error:', err);
console.error('Google login failed:', error); setError('Google authentication failed. Please try again.');
setError(`Google login failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
return ( const renderEmailStep = () => (
<div className="min-h-screen flex items-center justify-center bg-[--background-primary] py-12 px-4 sm:px-6 lg:px-8"> <div className="w-full max-w-md mx-auto bg-white rounded-xl shadow-lg p-8">
<Card className="max-w-md w-full space-y-8" padding="lg"> <div className="text-center mb-8">
<div className="text-center"> <div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center mx-auto mb-4">
<div className="w-16 h-16 bg-blue-500 rounded-full flex items-center justify-center font-bold text-white text-2xl mx-auto mb-4"> <span className="text-white text-2xl font-bold">A</span>
A
</div> </div>
<h2 className="text-3xl font-bold text-[--text-primary]">Welcome to Auditly</h2> <h1 className="text-3xl font-bold text-gray-900 mb-2">
<p className="text-[--text-secondary] mt-2"> {inviteCode ? 'Join Organization' : 'Welcome to Auditly'}
{inviteCode ? 'Complete your profile to join the team survey' : 'Sign in to your account'} </h1>
<p className="text-gray-600">
{inviteCode
? 'Enter your email to join the organization'
: 'Enter your email to get started'
}
</p> </p>
{inviteCode && (
<div className="mt-3 p-3 bg-blue-100 dark:bg-blue-900 rounded-lg">
<p className="text-sm text-blue-800 dark:text-blue-200">
<EFBFBD> <strong>Employee Survey Invitation</strong><br />
No account needed! Just create a password to secure your responses and start the questionnaire.
</p>
</div>
)}
{error && (
<div className="mt-3 p-3 bg-red-100 dark:bg-red-900 rounded-lg">
<p className="text-sm text-red-800 dark:text-red-200">
{error}
</p>
</div>
)}
</div> </div>
<form onSubmit={handleEmailLogin} className="space-y-6"> <form onSubmit={handleEmailSubmit} className="space-y-6">
<div> <div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> <label className="block text-sm font-medium text-gray-700 mb-2">
Email {inviteCode && <span className="text-gray-500 dark:text-gray-400">(use your work email)</span>} Email Address
</label> </label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.66675 5.83325L8.47085 10.5961C9.02182 10.9818 9.29731 11.1746 9.59697 11.2493C9.86166 11.3153 10.1385 11.3153 10.4032 11.2493C10.7029 11.1746 10.9783 10.9818 11.5293 10.5961L18.3334 5.83325M5.66675 16.6666H14.3334C15.7335 16.6666 16.4336 16.6666 16.9684 16.3941C17.4388 16.1544 17.8212 15.772 18.0609 15.3016C18.3334 14.7668 18.3334 14.0667 18.3334 12.6666V7.33325C18.3334 5.93312 18.3334 5.23306 18.0609 4.69828C17.8212 4.22787 17.4388 3.84542 16.9684 3.60574C16.4336 3.33325 15.7335 3.33325 14.3334 3.33325H5.66675C4.26662 3.33325 3.56655 3.33325 3.03177 3.60574C2.56137 3.84542 2.17892 4.22787 1.93923 4.69828C1.66675 5.23306 1.66675 5.93312 1.66675 7.33325V12.6666C1.66675 14.0667 1.66675 14.7668 1.93923 15.3016C2.17892 15.772 2.56137 16.1544 3.03177 16.3941C3.56655 16.6666 4.26662 16.6666 5.66675 16.6666Z" stroke="#718096" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<input <input
id="email"
type="email" type="email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:placeholder-gray-400" placeholder="Enter your email"
className="w-full pl-12 pr-4 py-3.5 bg-gray-50 border text-gray-700 border-gray-200 rounded-full focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all"
required required
/> />
</div> </div>
</div>
<div> {error && (
<label htmlFor="password" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"> <div className="text-red-600 text-sm bg-red-50 border border-red-200 p-3 rounded-lg">
Password {inviteCode && <span className="text-gray-500 dark:text-gray-400">(create a new password)</span>} {error}
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg bg-white text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"
required
/>
{inviteCode && (
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
Choose a secure password for your new account
</p>
)}
</div> </div>
)}
<Button <Button
type="submit" type="submit"
className="w-full" className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white py-3.5 rounded-full font-medium transition-all transform hover:scale-[1.02]"
disabled={isLoading} disabled={isLoading || !email.trim()}
> >
{isLoading ? 'Processing...' : (inviteCode ? 'Create Account & Join Team' : 'Sign In')} {isLoading ? 'Sending...' : 'Continue with Email'}
</Button> </Button>
</form>
<div className="text-center"> <div className="relative my-6">
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">Or continue with</p> <div className="absolute inset-0 flex items-center">
<Button <div className="w-full border-t border-gray-200" />
variant="secondary" </div>
className="w-full" <div className="relative flex justify-center text-sm">
onClick={handleGoogleLogin} <span className="px-3 bg-white text-gray-500">or</span>
disabled={isLoading} </div>
>
Sign in with Google
</Button>
</div> </div>
{/* Manual invite code entry - only show if no invite code in URL */} <Button
{!inviteCode && ( type="button"
<div className="border-t border-[--border-color] pt-6"> variant="secondary"
<div className="text-center mb-4"> className="w-full border-gray-200 py-3.5 rounded-full transition-colors"
<h3 className="text-sm font-medium text-[--text-primary] mb-2">Employee? Use Your Invite Code</h3> onClick={handleGoogleAuth}
<p className="text-xs text-[--text-secondary]"> disabled={isLoading}
Skip account creation - employees can go directly to their questionnaire >
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
</svg>
Continue with Google
</Button>
<div className="text-center mt-6">
<button
type="button"
onClick={() => setStep('password-fallback')}
className="text-sm text-blue-600 hover:text-blue-800 transition-colors"
>
Use password instead
</button>
</div>
</form>
</div>
);
const renderOTPStep = () => (
<div className="w-full max-w-md mx-auto bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="w-16 h-16 bg-gradient-to-br from-green-500 to-emerald-600 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Check your email</h1>
<p className="text-gray-600">
We sent a verification code to <br />
<strong className="text-gray-900">{email}</strong>
</p> </p>
</div> </div>
<div className="flex space-x-3">
<form onSubmit={handleOTPSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2 text-center">
Verification Code
</label>
<input <input
type="text" type="text"
placeholder="Enter your invite code" value={otp}
className="flex-1 px-3 py-2 border border-[--input-border] rounded-lg bg-[--input-bg] text-[--text-primary] placeholder-[--input-placeholder] focus:outline-none focus:ring-2 focus:ring-[--accent] focus:border-[--accent]" onChange={(e) => setOtp(e.target.value.replace(/\D/g, '').slice(0, 6))}
onKeyDown={(e) => { placeholder="000000"
if (e.key === 'Enter') { className="w-full px-4 py-4 bg-gray-50 border border-gray-200 text-gray-700 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent text-center text-3xl tracking-[0.5em] font-mono outline-none transition-all"
const code = (e.target as HTMLInputElement).value.trim(); maxLength={6}
if (code) { required
window.location.href = `#/invite/${code}`;
} else {
alert('Please enter an invite code');
}
}
}}
/> />
<Button
variant="secondary"
onClick={() => {
const input = document.querySelector('input[placeholder="Enter your invite code"]') as HTMLInputElement;
const code = input?.value.trim();
if (code) {
window.location.href = `#/invite/${code}`;
} else {
alert('Please enter an invite code');
}
}}
>
Start Survey
</Button>
</div> </div>
<p className="text-xs text-[--text-secondary] mt-2 text-center">
No account needed - just answer questions and submit {demoOTP && (
</p> <div className="bg-gradient-to-r from-yellow-50 to-orange-50 border border-yellow-200 rounded-xl p-4">
<div className="text-yellow-800 text-sm text-center">
<div className="flex items-center justify-center mb-2">
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
<strong>Demo Mode</strong>
</div>
Your verification code is <strong className="text-2xl font-mono bg-yellow-100 px-2 py-1 rounded">{demoOTP}</strong>
</div>
</div> </div>
)} )}
<div className="text-center"> {error && (
<p className="text-xs text-[--text-secondary]"> <div className="text-red-600 text-sm bg-red-50 border border-red-200 p-3 rounded-lg">
{inviteCode ? {error}
'Demo mode: Enter any email and password to create your account.' : </div>
'Demo mode: No Firebase configuration detected.\nUse any email/password to continue.' )}
<Button
type="submit"
className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white py-4 rounded-xl font-medium transition-all transform hover:scale-[1.02]"
disabled={isLoading || otp.length !== 6}
>
{isLoading ? 'Verifying...' : 'Verify Code'}
</Button>
<div className="text-center space-y-3">
<button
type="button"
onClick={() => sendOTP(email)}
disabled={resendCooldown > 0 || isLoading}
className="text-sm text-blue-600 hover:text-blue-800 disabled:text-gray-400 transition-colors"
>
{resendCooldown > 0
? `Resend code in ${resendCooldown}s`
: 'Resend code'
} }
</button>
<div>
<button
type="button"
onClick={() => { setStep('email'); setError(''); setOtp(''); }}
className="text-sm text-gray-600 hover:text-gray-800 transition-colors"
>
Change email address
</button>
</div>
</div>
</form>
</div>
);
const renderPasswordStep = () => (
<div className="w-full max-w-md mx-auto bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="w-16 h-16 bg-gradient-to-br from-purple-500 to-pink-600 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Sign in with password</h1>
<p className="text-gray-600">
Enter your password for <br />
<strong className="text-gray-900">{email}</strong>
</p> </p>
</div> </div>
</Card>
<form onSubmit={handlePasswordFallback} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Password
</label>
<div className="relative">
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-full focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all"
required
/>
</div>
</div>
{error && (
<div className="text-red-600 text-sm bg-red-50 border border-red-200 p-3 rounded-lg">
{error}
</div>
)}
<Button
type="submit"
className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white py-3.5 rounded-full font-medium transition-all transform hover:scale-[1.02]"
disabled={isLoading || !password.trim()}
>
{isLoading ? 'Signing in...' : 'Sign In'}
</Button>
<div className="text-center">
<button
type="button"
onClick={() => { setStep('email'); setError(''); setPassword(''); }}
className="text-sm text-blue-600 hover:text-blue-800 transition-colors"
>
Back to email verification
</button>
</div>
</form>
</div>
);
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-purple-50 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-purple-50 flex items-center justify-center p-4">
{step === 'email' && renderEmailStep()}
{step === 'otp' && renderOTPStep()}
{step === 'password-fallback' && renderPasswordStep()}
</div> </div>
); );
}; };
export default Login; export default ModernLogin;

90
pages/LoginNew.tsx Normal file
View File

@@ -0,0 +1,90 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
const LoginNew: React.FC = () => {
const [email, setEmail] = useState('');
const [isLoading, setIsLoading] = useState(false);
const { login } = useAuth();
const navigate = useNavigate();
const handleLogin = async () => {
if (!email) return;
setIsLoading(true);
try {
await login(email);
navigate('/company-wiki');
} catch (error) {
console.error('Login failed:', error);
} finally {
setIsLoading(false);
}
};
return (
<div className="w-[1440px] h-[810px] bg-Neutrals-NeutralSlate0 inline-flex justify-start items-end overflow-hidden">
<div className="flex-1 self-stretch px-32 py-48 flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute">
<svg width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M2.57425 17.8128C3.11852 18.3639 3.11851 19.2575 2.57423 19.8087L2.54635 19.8369C2.00207 20.3881 1.11963 20.3881 0.575357 19.8369C0.0310869 19.2857 0.0310953 18.3921 0.575376 17.841L0.603251 17.8128C1.14753 17.2616 2.02998 17.2616 2.57425 17.8128Z" fill="url(#paint0_linear_710_14140)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M9.12599 18.2379C9.66928 18.7901 9.66769 19.6837 9.12243 20.2338L5.26187 24.1291C4.71661 24.6792 3.83416 24.6776 3.29087 24.1255C2.74758 23.5733 2.74918 22.6797 3.29444 22.1296L7.155 18.2343C7.70026 17.6842 8.5827 17.6858 9.12599 18.2379Z" fill="url(#paint1_linear_710_14140)" />
<defs>
<linearGradient id="paint0_linear_710_14140" x1="1.5748" y1="17.3994" x2="1.5748" y2="20.2503" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint1_linear_710_14140" x1="6.20843" y1="17.8228" x2="6.20843" y2="24.5406" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-2xl font-semibold font-['Inter'] leading-8">Welcome to Auditly</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal">Sign in to your account to continue</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">Email Address</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight placeholder:text-Neutrals-NeutralSlate500 outline-none"
placeholder="Enter your email address"
onKeyPress={(e) => e.key === 'Enter' && handleLogin()}
/>
</div>
</div>
</div>
<button
onClick={handleLogin}
disabled={!email || isLoading}
className="self-stretch px-6 py-3.5 bg-Brand-Orange rounded-[999px] inline-flex justify-center items-center gap-2 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="justify-center text-Neutrals-NeutralSlate0 text-base font-medium font-['Inter'] leading-normal">
{isLoading ? 'Signing in...' : 'Sign In'}
</div>
</button>
<div className="self-stretch text-center text-Neutrals-NeutralSlate500 text-sm font-normal font-['Inter'] leading-tight">
Don't have an account? Contact your administrator.
</div>
</div>
</div>
</div>
</div>
);
};
export default LoginNew;

View File

@@ -1,403 +0,0 @@
import React, { useState, useEffect } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { Button } from '../components/UiKit';
type AuthStep = 'email' | 'otp' | 'password-fallback';
const ModernLogin: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const { inviteCode: routeInviteCode } = useParams<{ inviteCode: string }>();
// Auth state
const { signInWithGoogle, signInWithEmail, signUpWithEmail, user, loading, sendOTP: authSendOTP, verifyOTP: authVerifyOTP } = useAuth();
// Form state
const [step, setStep] = useState<AuthStep>('email');
const [email, setEmail] = useState('');
const [otp, setOtp] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [inviteCode, setInviteCode] = useState<string | null>(null);
const [resendCooldown, setResendCooldown] = useState(0);
const [demoOTP, setDemoOTP] = useState<string | null>(null);
// Extract invite code from URL
useEffect(() => {
if (routeInviteCode) {
setInviteCode(routeInviteCode);
} else {
const hashSearch = location.hash.includes('?') ? location.hash.split('?')[1] : '';
const searchParams = new URLSearchParams(hashSearch);
const queryInvite = searchParams.get('invite');
if (queryInvite) {
setInviteCode(queryInvite);
}
}
}, [routeInviteCode, location]);
// Handle successful authentication
useEffect(() => {
if (user && !loading) {
if (inviteCode) {
navigate(`/org-selection?invite=${inviteCode}`, { replace: true });
} else {
navigate('/org-selection', { replace: true });
}
}
}, [user, loading, navigate, inviteCode]);
// Resend cooldown timer
useEffect(() => {
if (resendCooldown > 0) {
const timer = setTimeout(() => setResendCooldown(resendCooldown - 1), 1000);
return () => clearTimeout(timer);
}
}, [resendCooldown]);
const sendOTP = async (emailAddress: string) => {
try {
setIsLoading(true);
setError('');
setDemoOTP(null);
// Call auth context method
const response = await authSendOTP(emailAddress, inviteCode || undefined);
// If OTP is returned in response (demo mode), display it
if (response.otp) {
setDemoOTP(response.otp);
}
setStep('otp');
setResendCooldown(60); // 60 second cooldown
} catch (err) {
console.error('OTP send error:', err);
setError(err instanceof Error ? err.message : 'Failed to send verification code. Please try again.');
} finally {
setIsLoading(false);
}
}; const verifyOTP = async () => {
try {
setIsLoading(true);
setError('');
// Call auth context method
await authVerifyOTP(email, otp, inviteCode || undefined);
// Success - user will be set in auth context and useEffect will handle navigation
} catch (err) {
console.error('OTP verification error:', err);
setError(err instanceof Error ? err.message : 'Invalid verification code. Please try again.');
} finally {
setIsLoading(false);
}
};
const handleEmailSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!email.trim()) return;
await sendOTP(email);
};
const handleOTPSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!otp.trim()) return;
await verifyOTP();
};
const handlePasswordFallback = async (e: React.FormEvent) => {
e.preventDefault();
if (!password.trim()) return;
try {
setIsLoading(true);
setError('');
// Try login first, then signup if user doesn't exist
try {
await signInWithEmail(email, password);
} catch (loginError) {
// If login fails, try creating account
await signUpWithEmail(email, password, email.split('@')[0]);
}
} catch (err) {
console.error('Password auth error:', err);
setError(err instanceof Error ? err.message : 'Authentication failed');
} finally {
setIsLoading(false);
}
};
const handleGoogleAuth = async () => {
try {
setIsLoading(true);
setError('');
await signInWithGoogle();
} catch (err) {
console.error('Google auth error:', err);
setError('Google authentication failed. Please try again.');
} finally {
setIsLoading(false);
}
};
const renderEmailStep = () => (
<div className="w-full max-w-md mx-auto bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center mx-auto mb-4">
<span className="text-white text-2xl font-bold">A</span>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">
{inviteCode ? 'Join Organization' : 'Welcome to Auditly'}
</h1>
<p className="text-gray-600">
{inviteCode
? 'Enter your email to join the organization'
: 'Enter your email to get started'
}
</p>
</div>
<form onSubmit={handleEmailSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Email Address
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.66675 5.83325L8.47085 10.5961C9.02182 10.9818 9.29731 11.1746 9.59697 11.2493C9.86166 11.3153 10.1385 11.3153 10.4032 11.2493C10.7029 11.1746 10.9783 10.9818 11.5293 10.5961L18.3334 5.83325M5.66675 16.6666H14.3334C15.7335 16.6666 16.4336 16.6666 16.9684 16.3941C17.4388 16.1544 17.8212 15.772 18.0609 15.3016C18.3334 14.7668 18.3334 14.0667 18.3334 12.6666V7.33325C18.3334 5.93312 18.3334 5.23306 18.0609 4.69828C17.8212 4.22787 17.4388 3.84542 16.9684 3.60574C16.4336 3.33325 15.7335 3.33325 14.3334 3.33325H5.66675C4.26662 3.33325 3.56655 3.33325 3.03177 3.60574C2.56137 3.84542 2.17892 4.22787 1.93923 4.69828C1.66675 5.23306 1.66675 5.93312 1.66675 7.33325V12.6666C1.66675 14.0667 1.66675 14.7668 1.93923 15.3016C2.17892 15.772 2.56137 16.1544 3.03177 16.3941C3.56655 16.6666 4.26662 16.6666 5.66675 16.6666Z" stroke="#718096" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
className="w-full pl-12 pr-4 py-3.5 bg-gray-50 border text-gray-700 border-gray-200 rounded-full focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all"
required
/>
</div>
</div>
{error && (
<div className="text-red-600 text-sm bg-red-50 border border-red-200 p-3 rounded-lg">
{error}
</div>
)}
<Button
type="submit"
className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white py-3.5 rounded-full font-medium transition-all transform hover:scale-[1.02]"
disabled={isLoading || !email.trim()}
>
{isLoading ? 'Sending...' : 'Continue with Email'}
</Button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-200" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-3 bg-white text-gray-500">or</span>
</div>
</div>
<Button
type="button"
variant="secondary"
className="w-full border-gray-200 py-3.5 rounded-full transition-colors"
onClick={handleGoogleAuth}
disabled={isLoading}
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
</svg>
Continue with Google
</Button>
<div className="text-center mt-6">
<button
type="button"
onClick={() => setStep('password-fallback')}
className="text-sm text-blue-600 hover:text-blue-800 transition-colors"
>
Use password instead
</button>
</div>
</form>
</div>
);
const renderOTPStep = () => (
<div className="w-full max-w-md mx-auto bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="w-16 h-16 bg-gradient-to-br from-green-500 to-emerald-600 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Check your email</h1>
<p className="text-gray-600">
We sent a verification code to <br />
<strong className="text-gray-900">{email}</strong>
</p>
</div>
<form onSubmit={handleOTPSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2 text-center">
Verification Code
</label>
<input
type="text"
value={otp}
onChange={(e) => setOtp(e.target.value.replace(/\D/g, '').slice(0, 6))}
placeholder="000000"
className="w-full px-4 py-4 bg-gray-50 border border-gray-200 text-gray-700 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent text-center text-3xl tracking-[0.5em] font-mono outline-none transition-all"
maxLength={6}
required
/>
</div>
{demoOTP && (
<div className="bg-gradient-to-r from-yellow-50 to-orange-50 border border-yellow-200 rounded-xl p-4">
<div className="text-yellow-800 text-sm text-center">
<div className="flex items-center justify-center mb-2">
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
<strong>Demo Mode</strong>
</div>
Your verification code is <strong className="text-2xl font-mono bg-yellow-100 px-2 py-1 rounded">{demoOTP}</strong>
</div>
</div>
)}
{error && (
<div className="text-red-600 text-sm bg-red-50 border border-red-200 p-3 rounded-lg">
{error}
</div>
)}
<Button
type="submit"
className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white py-4 rounded-xl font-medium transition-all transform hover:scale-[1.02]"
disabled={isLoading || otp.length !== 6}
>
{isLoading ? 'Verifying...' : 'Verify Code'}
</Button>
<div className="text-center space-y-3">
<button
type="button"
onClick={() => sendOTP(email)}
disabled={resendCooldown > 0 || isLoading}
className="text-sm text-blue-600 hover:text-blue-800 disabled:text-gray-400 transition-colors"
>
{resendCooldown > 0
? `Resend code in ${resendCooldown}s`
: 'Resend code'
}
</button>
<div>
<button
type="button"
onClick={() => { setStep('email'); setError(''); setOtp(''); }}
className="text-sm text-gray-600 hover:text-gray-800 transition-colors"
>
Change email address
</button>
</div>
</div>
</form>
</div>
);
const renderPasswordStep = () => (
<div className="w-full max-w-md mx-auto bg-white rounded-xl shadow-lg p-8">
<div className="text-center mb-8">
<div className="w-16 h-16 bg-gradient-to-br from-purple-500 to-pink-600 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">Sign in with password</h1>
<p className="text-gray-600">
Enter your password for <br />
<strong className="text-gray-900">{email}</strong>
</p>
</div>
<form onSubmit={handlePasswordFallback} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Password
</label>
<div className="relative">
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter your password"
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-full focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all"
required
/>
</div>
</div>
{error && (
<div className="text-red-600 text-sm bg-red-50 border border-red-200 p-3 rounded-lg">
{error}
</div>
)}
<Button
type="submit"
className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white py-3.5 rounded-full font-medium transition-all transform hover:scale-[1.02]"
disabled={isLoading || !password.trim()}
>
{isLoading ? 'Signing in...' : 'Sign In'}
</Button>
<div className="text-center">
<button
type="button"
onClick={() => { setStep('email'); setError(''); setPassword(''); }}
className="text-sm text-blue-600 hover:text-blue-800 transition-colors"
>
Back to email verification
</button>
</div>
</form>
</div>
);
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-purple-50 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-purple-50 flex items-center justify-center p-4">
{step === 'email' && renderEmailStep()}
{step === 'otp' && renderOTPStep()}
{step === 'password-fallback' && renderPasswordStep()}
</div>
);
};
export default ModernLogin;

122
pages/OTPVerification.tsx Normal file
View File

@@ -0,0 +1,122 @@
import React, { useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
const OTPVerification: React.FC = () => {
const [otp, setOtp] = useState(['', '', '', '', '', '']);
const [isLoading, setIsLoading] = useState(false);
const { verifyOTP } = useAuth();
const navigate = useNavigate();
const location = useLocation();
const email = location.state?.email || '';
const handleOtpChange = (index: number, value: string) => {
if (value.length <= 1) {
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
// Auto-focus next input
if (value && index < 5) {
const nextInput = document.getElementById(`otp-${index + 1}`);
nextInput?.focus();
}
}
};
const handleKeyDown = (index: number, e: React.KeyboardEvent) => {
if (e.key === 'Backspace' && !otp[index] && index > 0) {
const prevInput = document.getElementById(`otp-${index - 1}`);
prevInput?.focus();
}
};
const handleVerify = async () => {
const otpCode = otp.join('');
if (otpCode.length !== 6) return;
setIsLoading(true);
try {
await verifyOTP(email, otpCode);
navigate('/company-wiki');
} catch (error) {
console.error('OTP verification failed:', error);
// Reset OTP on error
setOtp(['', '', '', '', '', '']);
document.getElementById('otp-0')?.focus();
} finally {
setIsLoading(false);
}
};
return (
<div className="w-[1440px] h-[810px] bg-Neutrals-NeutralSlate0 inline-flex justify-start items-end overflow-hidden">
<div className="flex-1 self-stretch px-32 py-48 flex justify-center items-center gap-2.5 overflow-hidden">
<div className="flex-1 max-w-[464px] inline-flex flex-col justify-start items-start gap-6">
<div className="w-12 h-12 relative bg-Brand-Orange rounded-xl outline outline-2 outline-offset-[-2px] outline-blue-400 overflow-hidden">
<div className="w-12 h-12 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[12px] top-[9.33px] absolute">
<svg width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M2.57425 17.8128C3.11852 18.3639 3.11851 19.2575 2.57423 19.8087L2.54635 19.8369C2.00207 20.3881 1.11963 20.3881 0.575357 19.8369C0.0310869 19.2857 0.0310953 18.3921 0.575376 17.841L0.603251 17.8128C1.14753 17.2616 2.02998 17.2616 2.57425 17.8128Z" fill="url(#paint0_linear_710_14140)" />
<path opacity="0.7" fillRule="evenodd" clipRule="evenodd" d="M9.12599 18.2379C9.66928 18.7901 9.66769 19.6837 9.12243 20.2338L5.26187 24.1291C4.71661 24.6792 3.83416 24.6776 3.29087 24.1255C2.74758 23.5733 2.74918 22.6797 3.29444 22.1296L7.155 18.2343C7.70026 17.6842 8.5827 17.6858 9.12599 18.2379Z" fill="url(#paint1_linear_710_14140)" />
<defs>
<linearGradient id="paint0_linear_710_14140" x1="1.5748" y1="17.3994" x2="1.5748" y2="20.2503" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
<linearGradient id="paint1_linear_710_14140" x1="6.20843" y1="17.8228" x2="6.20843" y2="24.5406" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-2xl font-semibold font-['Inter'] leading-8">Verify your email</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-base font-normal font-['Inter'] leading-normal">
Enter the 6-digit code sent to {email}
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-4">
<div className="self-stretch flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">Verification Code</div>
<div className="justify-start text-Brand-Orange text-sm font-medium font-['Inter'] leading-tight">*</div>
</div>
<div className="self-stretch flex justify-center items-center gap-3">
{otp.map((digit, index) => (
<input
key={index}
id={`otp-${index}`}
type="text"
value={digit}
onChange={(e) => handleOtpChange(index, e.target.value)}
onKeyDown={(e) => handleKeyDown(index, e)}
className="w-12 h-12 bg-Neutrals-NeutralSlate100 rounded-xl text-center text-Neutrals-NeutralSlate950 text-lg font-semibold font-['Inter'] outline-none focus:bg-Neutrals-NeutralSlate200 focus:outline-2 focus:outline-Brand-Orange"
maxLength={1}
/>
))}
</div>
</div>
<button
onClick={handleVerify}
disabled={otp.join('').length !== 6 || isLoading}
className="self-stretch px-6 py-3.5 bg-Brand-Orange rounded-[999px] inline-flex justify-center items-center gap-2 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="justify-center text-Neutrals-NeutralSlate0 text-base font-medium font-['Inter'] leading-normal">
{isLoading ? 'Verifying...' : 'Verify Code'}
</div>
</button>
<div className="self-stretch text-center text-Neutrals-NeutralSlate500 text-sm font-normal font-['Inter'] leading-tight">
Didn't receive the code? <button className="text-Brand-Orange font-medium">Resend</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default OTPVerification;

View File

@@ -1,47 +1,41 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useOrg } from '../contexts/OrgContext'; import { useOrg } from '../contexts/OrgContext';
import { Card, Button } from '../components/UiKit'; import { EnhancedFigmaQuestion, FigmaQuestionCard, EnhancedFigmaInput } from '../components/figma/EnhancedFigmaQuestion';
import { FigmaProgress } from '../components/figma/FigmaProgress'; import { FigmaInput, FigmaSelect } from '../components/figma/FigmaInput';
import { FigmaInput } from '../components/figma/FigmaInput'; import { FigmaMultipleChoice } from '../components/figma/FigmaMultipleChoice';
import { FigmaAlert } from '../components/figma/FigmaAlert';
interface OnboardingData { interface OnboardingData {
// Step 1: Company Basics // Step 0: Company Details
companyName: string; companyName: string;
industry: string; yourName: string;
size: string;
description: string;
// Step 2: Mission & Vision // Step 1: Company Size
companySize: string;
// Step 2: Mission
mission: string; mission: string;
// Later steps
industry: string;
description: string;
vision: string; vision: string;
values: string[]; values: string[];
// Step 3: Company Evolution & History
foundingYear: string; foundingYear: string;
evolution: string; evolution: string;
majorMilestones: string; majorMilestones: string;
// Step 4: Competitive Landscape
advantages: string; advantages: string;
vulnerabilities: string; vulnerabilities: string;
competitors: string; competitors: string;
marketPosition: string; marketPosition: string;
// Step 5: Current Challenges & Goals
currentChallenges: string[]; currentChallenges: string[];
shortTermGoals: string; shortTermGoals: string;
longTermGoals: string; longTermGoals: string;
keyMetrics: string; keyMetrics: string;
// Step 6: Team & Culture
cultureDescription: string; cultureDescription: string;
workEnvironment: string; workEnvironment: string;
leadershipStyle: string; leadershipStyle: string;
communicationStyle: string; communicationStyle: string;
// Step 7: Final Review
additionalContext: string; additionalContext: string;
} }
@@ -59,10 +53,11 @@ const Onboarding: React.FC = () => {
const [isGeneratingReport, setIsGeneratingReport] = useState(false); const [isGeneratingReport, setIsGeneratingReport] = useState(false);
const [formData, setFormData] = useState<OnboardingData>({ const [formData, setFormData] = useState<OnboardingData>({
companyName: org?.name || '', companyName: org?.name || '',
industry: '', yourName: '',
size: '', companySize: '',
description: '',
mission: '', mission: '',
industry: '',
description: '',
vision: '', vision: '',
values: [], values: [],
foundingYear: '', foundingYear: '',
@@ -85,20 +80,28 @@ const Onboarding: React.FC = () => {
const steps = [ const steps = [
{ {
title: 'Company Basics', title: 'Company Details',
description: 'Tell us about your company fundamentals' description: 'Basic information about your company'
}, },
{ {
title: 'Mission & Vision', title: 'Company Size',
description: 'Define your purpose and direction' description: 'How many people work at your company'
}, },
{ {
title: 'Evolution & History', title: 'Mission Statement',
description: 'Share your company\'s journey' description: 'What is the mission of your company'
}, },
{ {
title: 'Competitive Position', title: 'Vision & Values',
description: 'Understand your market position' description: 'Your company\'s vision and core values'
},
{
title: 'Company History',
description: 'How your company has evolved'
},
{
title: 'Market Position',
description: 'Your competitive landscape'
}, },
{ {
title: 'Goals & Challenges', title: 'Goals & Challenges',
@@ -106,16 +109,11 @@ const Onboarding: React.FC = () => {
}, },
{ {
title: 'Team & Culture', title: 'Team & Culture',
description: 'Describe your work environment' description: 'Your work environment and culture'
},
{
title: 'Final Review',
description: 'Complete your company profile'
} }
]; ];
const handleNext = async () => { const handleNext = async () => {
// Prevent re-entry during generation
if (isGeneratingReport) return; if (isGeneratingReport) return;
if (step < steps.length - 1) { if (step < steps.length - 1) {
@@ -125,12 +123,11 @@ const Onboarding: React.FC = () => {
// Final step: persist org & generate report // Final step: persist org & generate report
setIsGeneratingReport(true); setIsGeneratingReport(true);
console.log('Starting onboarding completion...', { step });
try { try {
const newOrgData = { const newOrgData = {
name: formData.companyName, name: formData.companyName,
industry: formData.industry, industry: formData.industry,
size: formData.size, size: formData.companySize,
description: formData.description, description: formData.description,
mission: formData.mission, mission: formData.mission,
vision: formData.vision, vision: formData.vision,
@@ -154,24 +151,14 @@ const Onboarding: React.FC = () => {
onboardingCompleted: true onboardingCompleted: true
}; };
console.log('Saving org data...', newOrgData);
await upsertOrg(newOrgData); await upsertOrg(newOrgData);
console.log('Org data saved successfully');
console.log('Generating company wiki...');
await generateCompanyWiki({ ...newOrgData, orgId: org!.orgId }); await generateCompanyWiki({ ...newOrgData, orgId: org!.orgId });
console.log('Company wiki generated successfully');
// Small delay to ensure states are updated, then redirect
console.log('Redirecting to reports...');
setTimeout(() => { setTimeout(() => {
console.log('Navigation executing...');
navigate('/reports', { replace: true }); navigate('/reports', { replace: true });
console.log('Navigation called successfully');
}, 100); }, 100);
} catch (error) { } catch (error) {
console.error('Error completing onboarding:', error); console.error('Error completing onboarding:', error);
// Show detailed error to user for debugging
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
alert(`There was an error completing the setup: ${errorMessage}. Please check the console for more details and try again.`); alert(`There was an error completing the setup: ${errorMessage}. Please check the console for more details and try again.`);
} finally { } finally {
@@ -183,462 +170,160 @@ const Onboarding: React.FC = () => {
if (step > 0) setStep(step - 1); if (step > 0) setStep(step - 1);
}; };
const addToArray = (field: 'values' | 'currentChallenges', value: string) => { const updateFormData = (field: keyof OnboardingData, value: string) => {
if (value.trim()) { setFormData(prev => ({ ...prev, [field]: value }));
setFormData(prev => ({
...prev,
[field]: [...prev[field], value.trim()]
}));
}
}; };
const removeFromArray = (field: 'values' | 'currentChallenges', index: number) => { const canProceed = () => {
setFormData(prev => ({
...prev,
[field]: prev[field].filter((_, i) => i !== index)
}));
};
const renderStep = () => {
switch (step) { switch (step) {
case 0: case 0: // Company Details
return formData.companyName.trim().length > 0 && formData.yourName.trim().length > 0;
case 1: // Company Size
return formData.companySize.length > 0;
case 2: // Mission
return formData.mission.trim().length > 0;
case 3: // Vision & Values
return formData.vision.trim().length > 0;
case 4: // History
return formData.evolution.trim().length > 0;
case 5: // Market Position
return formData.advantages.trim().length > 0;
case 6: // Goals
return formData.shortTermGoals.trim().length > 0;
case 7: // Culture
return formData.cultureDescription.trim().length > 0;
default:
return false;
}
};
const renderStepContent = () => {
switch (step) {
case 0: // Company Details
return ( return (
<div className="space-y-6"> <div className="self-stretch flex flex-col justify-start items-start gap-6">
<div> <FigmaInput
<label className="block text-sm font-medium text-[--text-primary] mb-2"> label="Your Name"
Company Name * placeholder="John Doe"
</label> value={formData.yourName}
<input onChange={(e) => updateFormData('yourName', e.target.value)}
type="text" required
/>
<FigmaInput
label="Company Name"
placeholder="Doe Enterprises"
value={formData.companyName} value={formData.companyName}
onChange={(e) => setFormData(prev => ({ ...prev, companyName: e.target.value }))} onChange={(e) => updateFormData('companyName', e.target.value)}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500" required
placeholder="Enter your company name"
/> />
</div> </div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Industry *
</label>
<select
value={formData.industry}
onChange={(e) => setFormData(prev => ({ ...prev, industry: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Select industry</option>
<option value="Technology">Technology</option>
<option value="Healthcare">Healthcare</option>
<option value="Finance">Finance</option>
<option value="Manufacturing">Manufacturing</option>
<option value="Retail">Retail</option>
<option value="Professional Services">Professional Services</option>
<option value="Education">Education</option>
<option value="Media & Entertainment">Media & Entertainment</option>
<option value="Other">Other</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Company Size *
</label>
<select
value={formData.size}
onChange={(e) => setFormData(prev => ({ ...prev, size: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">Select size</option>
<option value="1-10">1-10 employees</option>
<option value="11-50">11-50 employees</option>
<option value="51-200">51-200 employees</option>
<option value="201-1000">201-1000 employees</option>
<option value="1000+">1000+ employees</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Company Description *
</label>
<textarea
value={formData.description}
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Describe what your company does, its products/services, and target market"
/>
</div>
</div>
); );
case 1: case 1: // Company Size
return ( return (
<div className="space-y-6"> <FigmaMultipleChoice
<div> options={['1-10', '10-25', '25-50', '50-100', '100+']}
<label className="block text-sm font-medium text-[--text-primary] mb-2"> selectedValue={formData.companySize}
Mission Statement * onSelect={(value) => updateFormData('companySize', value)}
</label> />
<textarea );
case 2: // Mission
return (
<FigmaQuestionCard
question="What is the mission of your company?"
description="Description about the question"
>
<EnhancedFigmaInput
placeholder="Type your answer...."
value={formData.mission} value={formData.mission}
onChange={(e) => setFormData(prev => ({ ...prev, mission: e.target.value }))} onChange={(value) => updateFormData('mission', value)}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500" multiline
rows={3} rows={6}
placeholder="What is your company's purpose? Why does it exist?"
/> />
</div> </FigmaQuestionCard>
<div> );
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Vision Statement * case 3: // Vision & Values
</label> return (
<textarea <div className="space-y-6">
<FigmaQuestionCard
question="What is your company's vision?"
description="Where do you see your company in the future?"
>
<EnhancedFigmaInput
placeholder="Type your answer...."
value={formData.vision} value={formData.vision}
onChange={(e) => setFormData(prev => ({ ...prev, vision: e.target.value }))} onChange={(value) => updateFormData('vision', value)}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500" multiline
rows={3} rows={4}
placeholder="Where do you see your company in the future? What impact do you want to make?"
/> />
</div> </FigmaQuestionCard>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Core Values
</label>
<div className="space-y-2">
{formData.values.map((value, index) => (
<div key={index} className="flex items-center space-x-2">
<span className="flex-1 px-3 py-2 bg-[--background-tertiary] rounded-lg text-[--text-primary]">
{value}
</span>
<Button
size="sm"
variant="danger"
onClick={() => removeFromArray('values', index)}
>
Remove
</Button>
</div>
))}
<div className="flex space-x-2">
<input
type="text"
className="flex-1 px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Add a core value"
onKeyPress={(e) => {
if (e.key === 'Enter') {
addToArray('values', e.currentTarget.value);
e.currentTarget.value = '';
}
}}
/>
<Button
size="sm"
onClick={(e) => {
const input = (e.target as HTMLElement).parentElement?.querySelector('input');
if (input) {
addToArray('values', input.value);
input.value = '';
}
}}
>
Add
</Button>
</div>
</div>
</div>
</div> </div>
); );
case 2: case 4: // History
return ( return (
<div className="space-y-6"> <FigmaQuestionCard
<div> question="How has your company evolved?"
<label className="block text-sm font-medium text-[--text-primary] mb-2"> description="Tell us about your company's journey and evolution"
Founding Year >
</label> <EnhancedFigmaInput
<input placeholder="Type your answer...."
type="text"
value={formData.foundingYear}
onChange={(e) => setFormData(prev => ({ ...prev, foundingYear: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="When was your company founded?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Company Evolution *
</label>
<textarea
value={formData.evolution} value={formData.evolution}
onChange={(e) => setFormData(prev => ({ ...prev, evolution: e.target.value }))} onChange={(value) => updateFormData('evolution', value)}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500" multiline
rows={4} rows={6}
placeholder="How has your company evolved since its founding? What major changes or pivots have occurred?"
/> />
</div> </FigmaQuestionCard>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Major Milestones
</label>
<textarea
value={formData.majorMilestones}
onChange={(e) => setFormData(prev => ({ ...prev, majorMilestones: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="List key achievements, product launches, funding rounds, or other significant milestones"
/>
</div>
</div>
); );
case 3: case 5: // Market Position
return ( return (
<div className="space-y-6"> <FigmaQuestionCard
<div> question="What are your competitive advantages?"
<label className="block text-sm font-medium text-[--text-primary] mb-2"> description="What gives your company a competitive edge?"
Competitive Advantages * >
</label> <EnhancedFigmaInput
<textarea placeholder="Type your answer...."
value={formData.advantages} value={formData.advantages}
onChange={(e) => setFormData(prev => ({ ...prev, advantages: e.target.value }))} onChange={(value) => updateFormData('advantages', value)}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500" multiline
rows={4} rows={6}
placeholder="What gives your company a competitive edge? What are your unique strengths?"
/> />
</div> </FigmaQuestionCard>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Vulnerabilities & Weaknesses *
</label>
<textarea
value={formData.vulnerabilities}
onChange={(e) => setFormData(prev => ({ ...prev, vulnerabilities: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="What are your company's current weaknesses or areas of vulnerability?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Key Competitors
</label>
<textarea
value={formData.competitors}
onChange={(e) => setFormData(prev => ({ ...prev, competitors: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Who are your main competitors? How do you differentiate from them?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Market Position
</label>
<textarea
value={formData.marketPosition}
onChange={(e) => setFormData(prev => ({ ...prev, marketPosition: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="How do you position yourself in the market? What's your market share or standing?"
/>
</div>
</div>
); );
case 4: case 6: // Goals
return ( return (
<div className="space-y-6"> <FigmaQuestionCard
<div> question="What are your short-term goals?"
<label className="block text-sm font-medium text-[--text-primary] mb-2"> description="What are your priorities for the next 6-12 months?"
Current Challenges
</label>
<div className="space-y-2">
{formData.currentChallenges.map((challenge, index) => (
<div key={index} className="flex items-center space-x-2">
<span className="flex-1 px-3 py-2 bg-[--background-tertiary] rounded-lg text-[--text-primary]">
{challenge}
</span>
<Button
size="sm"
variant="danger"
onClick={() => removeFromArray('currentChallenges', index)}
> >
Remove <EnhancedFigmaInput
</Button> placeholder="Type your answer...."
</div>
))}
<div className="flex space-x-2">
<input
type="text"
className="flex-1 px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Add a current challenge"
onKeyPress={(e) => {
if (e.key === 'Enter') {
addToArray('currentChallenges', e.currentTarget.value);
e.currentTarget.value = '';
}
}}
/>
<Button
size="sm"
onClick={(e) => {
const input = (e.target as HTMLElement).parentElement?.querySelector('input');
if (input) {
addToArray('currentChallenges', input.value);
input.value = '';
}
}}
>
Add
</Button>
</div>
</div>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Short-term Goals (6-12 months) *
</label>
<textarea
value={formData.shortTermGoals} value={formData.shortTermGoals}
onChange={(e) => setFormData(prev => ({ ...prev, shortTermGoals: e.target.value }))} onChange={(value) => updateFormData('shortTermGoals', value)}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500" multiline
rows={4} rows={6}
placeholder="What are your immediate priorities and goals for the next 6-12 months?"
/> />
</div> </FigmaQuestionCard>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Long-term Goals (1-3 years) *
</label>
<textarea
value={formData.longTermGoals}
onChange={(e) => setFormData(prev => ({ ...prev, longTermGoals: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="What are your strategic objectives for the next 1-3 years?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Key Metrics & Success Indicators
</label>
<textarea
value={formData.keyMetrics}
onChange={(e) => setFormData(prev => ({ ...prev, keyMetrics: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="How do you measure success? What are your key performance indicators?"
/>
</div>
</div>
); );
case 5: case 7: // Culture
return ( return (
<div className="space-y-6"> <FigmaQuestionCard
<div> question="Describe your company culture"
<label className="block text-sm font-medium text-[--text-primary] mb-2"> description="What's it like to work at your company?"
Company Culture *
</label>
<textarea
value={formData.cultureDescription}
onChange={(e) => setFormData(prev => ({ ...prev, cultureDescription: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Describe your company culture. What's it like to work at your company?"
/>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Work Environment *
</label>
<select
value={formData.workEnvironment}
onChange={(e) => setFormData(prev => ({ ...prev, workEnvironment: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
> >
<option value="">Select work environment</option> <EnhancedFigmaInput
<option value="Remote">Fully Remote</option> placeholder="Type your answer...."
<option value="Hybrid">Hybrid (Remote + Office)</option> value={formData.cultureDescription}
<option value="In-office">In-office</option> onChange={(value) => updateFormData('cultureDescription', value)}
<option value="Flexible">Flexible/Varies by role</option> multiline
</select> rows={6}
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Leadership Style *
</label>
<textarea
value={formData.leadershipStyle}
onChange={(e) => setFormData(prev => ({ ...prev, leadershipStyle: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="Describe the leadership approach and management style in your organization"
/> />
</div> </FigmaQuestionCard>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Communication Style *
</label>
<textarea
value={formData.communicationStyle}
onChange={(e) => setFormData(prev => ({ ...prev, communicationStyle: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={3}
placeholder="How does your team communicate? What tools and processes do you use?"
/>
</div>
</div>
);
case 6:
return (
<div className="space-y-6">
<div className="text-center mb-6">
<div className="text-4xl mb-4">📋</div>
<h3 className="text-xl font-semibold text-[--text-primary] mb-2">
Review Your Information
</h3>
<p className="text-[--text-secondary]">
Please review the information below and add any additional context
</p>
</div>
<div className="bg-[--background-tertiary] p-4 rounded-lg space-y-3">
<div><strong>Company:</strong> {formData.companyName}</div>
<div><strong>Industry:</strong> {formData.industry}</div>
<div><strong>Size:</strong> {formData.size}</div>
<div><strong>Mission:</strong> {formData.mission.substring(0, 100)}{formData.mission.length > 100 ? '...' : ''}</div>
</div>
<div>
<label className="block text-sm font-medium text-[--text-primary] mb-2">
Additional Context
</label>
<textarea
value={formData.additionalContext}
onChange={(e) => setFormData(prev => ({ ...prev, additionalContext: e.target.value }))}
className="w-full px-3 py-2 border border-[--border-color] rounded-lg bg-[--background-secondary] text-[--text-primary] focus:outline-none focus:ring-2 focus:ring-blue-500"
rows={4}
placeholder="Is there anything else important about your company that Auditly should know to provide better insights?"
/>
</div>
<div className="text-center pt-4">
<div className="text-4xl mb-4">🎉</div>
<h3 className="text-xl font-semibold text-[--text-primary]">
Ready to Complete Setup!
</h3>
<p className="text-[--text-secondary]">
{isGeneratingReport
? 'Generating your personalized company insights...'
: 'Once you complete this step, you\'ll have access to all Auditly features and your personalized company wiki will be generated.'
}
</p>
{isGeneratingReport && (
<div className="mt-4 flex items-center justify-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
<span className="ml-3 text-[--text-secondary]">Creating your company profile...</span>
</div>
)}
</div>
</div>
); );
default: default:
@@ -646,79 +331,43 @@ const Onboarding: React.FC = () => {
} }
}; };
const canProceed = () => { // Question text for each step
const getQuestionText = () => {
switch (step) { switch (step) {
case 0: case 0: return 'Company Details';
return formData.companyName.trim().length > 0 && formData.industry && formData.size && formData.description.trim().length > 0; case 1: return `How many people work at ${formData.companyName || '[Company Name]'}?`;
case 1: case 2: return 'What is the mission of your company?';
return formData.mission.trim().length > 0 && formData.vision.trim().length > 0; case 3: return 'What is your company\'s vision?';
case 2: case 4: return 'How has your company evolved?';
return formData.evolution.trim().length > 0; case 5: return 'What are your competitive advantages?';
case 3: case 6: return 'What are your short-term goals?';
return formData.advantages.trim().length > 0 && formData.vulnerabilities.trim().length > 0; case 7: return 'Describe your company culture';
case 4: default: return 'Onboarding';
return formData.shortTermGoals.trim().length > 0 && formData.longTermGoals.trim().length > 0;
case 5:
return formData.cultureDescription.trim().length > 0 && formData.workEnvironment && formData.leadershipStyle.trim().length > 0 && formData.communicationStyle.trim().length > 0;
case 6:
return true;
default:
return false;
} }
}; };
return ( return (
<div className="min-h-screen bg-[--background-primary] flex items-center justify-center p-4"> <div className="min-h-screen bg-[--Neutrals-NeutralSlate0] flex items-center justify-center">
<div className="max-w-4xl w-full"> <EnhancedFigmaQuestion
<div className="text-center mb-8"> question={getQuestionText()}
<h1 className="text-3xl font-bold text-[--text-primary] mb-2">
Welcome to Auditly
</h1>
<p className="text-[--text-secondary]">
Let's build a comprehensive profile of your organization to provide the best insights
</p>
</div>
{/* Progress indicator */}
<div className="mb-8">
<FigmaProgress
currentStep={step + 1} currentStep={step + 1}
steps={steps.map((s, i) => ({ number: i + 1, title: s.title }))} totalSteps={steps.length}
/> stepTitle={steps[step].title}
</div> onBack={handleBack}
onNext={handleNext}
<Card className="max-w-none"> nextDisabled={!canProceed() || isGeneratingReport}
<div className="mb-6"> backDisabled={step === 0}
<h2 className="text-xl font-semibold text-[--text-primary] mb-2"> showBackButton={step > 0}
{steps[step].title} nextText={
</h2> isGeneratingReport
<p className="text-[--text-secondary]"> ? 'Generating...'
{steps[step].description} : step === steps.length - 1
</p> ? 'Complete Setup'
</div> : 'Next'
{renderStep()}
<div className="flex justify-between mt-8">
<Button
variant="secondary"
onClick={handleBack}
disabled={step === 0}
>
Back
</Button>
<Button
onClick={handleNext}
disabled={!canProceed() || isGeneratingReport}
>
{isGeneratingReport
? 'Generating Wiki...'
: step === steps.length - 1 ? 'Complete Setup & Generate Wiki' : 'Next'
} }
</Button> >
</div> {renderStepContent()}
</Card> </EnhancedFigmaQuestion>
</div>
</div> </div>
); );
}; };

304
pages/SettingsNew.tsx Normal file
View File

@@ -0,0 +1,304 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import Sidebar from '../components/figma/Sidebar';
interface UserProfile {
fullName: string;
email: string;
profilePicture?: string;
}
type ThemeMode = 'system' | 'light' | 'dark';
const SettingsNew: React.FC = () => {
const { user } = useAuth();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState<'general' | 'billing'>('general');
const [userProfile, setUserProfile] = useState<UserProfile>({
fullName: 'John Doe',
email: 'Johndoe1234@gmail.com'
});
const [selectedTheme, setSelectedTheme] = useState<ThemeMode>('light');
const handleProfileUpdate = (field: keyof UserProfile, value: string) => {
setUserProfile(prev => ({
...prev,
[field]: value
}));
};
const handlePhotoUpload = () => {
// In a real app, this would open a file picker
alert('Photo upload functionality would be implemented here');
};
const handleSaveChanges = () => {
// In a real app, this would save to backend
alert('Settings saved successfully!');
};
const handleReset = () => {
setUserProfile({
fullName: 'John Doe',
email: 'Johndoe1234@gmail.com'
});
setSelectedTheme('light');
};
if (!user) {
navigate('/login');
return null;
}
return (
<div className="w-[1440px] h-[840px] p-4 bg-Neutrals-NeutralSlate200 inline-flex justify-start items-start overflow-hidden">
<div className="flex-1 self-stretch rounded-3xl shadow-[0px_0px_15px_0px_rgba(0,0,0,0.08)] flex justify-between items-start overflow-hidden">
<Sidebar companyName="Zitlac Media" />
<div className="flex-1 self-stretch bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-start items-start">
{/* Tab Navigation */}
<div className="self-stretch px-6 pt-6 border-b border-Outline-Outline-Gray-200 flex flex-col justify-start items-end">
<div className="self-stretch inline-flex justify-start items-start gap-6">
<div
onClick={() => setActiveTab('general')}
className={`w-32 inline-flex flex-col justify-start items-start gap-3 cursor-pointer ${
activeTab === 'general' ? '' : 'opacity-60'
}`}
>
<div className={`self-stretch text-center justify-center text-base font-['Inter'] leading-normal ${
activeTab === 'general'
? 'text-Text-Gray-800 font-semibold'
: 'text-Text-Gray-500 font-normal'
}`}>
General Settings
</div>
{activeTab === 'general' && (
<div className="self-stretch h-0.5 bg-Text-Gray-800 rounded-tl-lg rounded-tr-lg" />
)}
</div>
<div
onClick={() => setActiveTab('billing')}
className={`inline-flex flex-col justify-start items-start gap-3 cursor-pointer ${
activeTab === 'billing' ? '' : 'opacity-60'
}`}
>
<div className={`text-center justify-center text-base font-['Inter'] leading-normal ${
activeTab === 'billing'
? 'text-Text-Gray-800 font-semibold'
: 'text-Text-Gray-500 font-normal'
}`}>
Plan & Billings
</div>
{activeTab === 'billing' && (
<div className="w-24 h-0.5 bg-Text-Gray-800 rounded-tl-lg rounded-tr-lg" />
)}
</div>
</div>
<div className="w-24 h-0.5 opacity-0 bg-Text-Gray-800 rounded-tl-lg rounded-tr-lg" />
</div>
{/* General Settings Content */}
{activeTab === 'general' && (
<>
{/* Profile Information Section */}
<div className="w-[1136px] h-72 p-6 flex flex-col justify-start items-start gap-6">
<div className="w-[584px] flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-start text-Text-Gray-800 text-lg font-semibold font-['Inter'] leading-7">Profile Information</div>
<div className="self-stretch justify-start text-Text-Gray-500 text-sm font-normal font-['Inter'] leading-tight">Update your personal details, and keep your profile up to date.</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-6">
{/* Profile Picture Section */}
<div className="w-[664px] px-3 py-2.5 bg-Text-White-00 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 inline-flex justify-between items-center">
<div className="flex-1 flex justify-start items-center gap-3">
<div className="w-14 h-14 relative bg-red-200 rounded-[999px]">
<div>
<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_3786)">
<ellipse cx="28" cy="54.6008" rx="22.4" ry="16.8" fill="white" fillOpacity="0.72" />
<circle opacity="0.9" cx="28" cy="22.3992" r="11.2" fill="white" />
</g>
<defs>
<clipPath id="clip0_1042_3786">
<rect width="56" height="56" rx="28" fill="white" />
</clipPath>
</defs>
</svg>
</div>
</div>
<div className="flex-1 inline-flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-center text-Text-Gray-800 text-base font-semibold font-['Inter'] leading-normal">Profile Picture</div>
<div className="self-stretch justify-center text-Text-Gray-500 text-xs font-normal font-['Inter'] leading-none">PNG, JPEG, GIF Under 10MB</div>
</div>
</div>
<div
onClick={handlePhotoUpload}
className="px-3 py-2.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden cursor-pointer hover:bg-Neutrals-NeutralSlate200"
>
<div>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.66665 10.8282C1.86266 10.29 1.33331 9.37347 1.33331 8.33333C1.33331 6.77095 2.52765 5.48753 4.05314 5.34625C4.36519 3.44809 6.01348 2 7.99998 2C9.98648 2 11.6348 3.44809 11.9468 5.34625C13.4723 5.48753 14.6666 6.77095 14.6666 8.33333C14.6666 9.37347 14.1373 10.29 13.3333 10.8282M5.33331 10.6667L7.99998 8M7.99998 8L10.6666 10.6667M7.99998 8V14" stroke="var(--Text-Dark-950, #0A0D12)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Upload Photo</div>
</div>
</div>
</div>
{/* Name and Email Fields */}
<div className="w-[664px] inline-flex justify-start items-center gap-4">
<div className="flex-1 inline-flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">Full Name</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.5C7.35828 12.5 5.00901 13.7755 3.51334 15.755C3.19143 16.181 3.03047 16.394 3.03574 16.6819C3.03981 16.9043 3.17948 17.1849 3.35448 17.3222C3.581 17.5 3.8949 17.5 4.5227 17.5H15.4773C16.1051 17.5 16.419 17.5 16.6455 17.3222C16.8205 17.1849 16.9602 16.9043 16.9643 16.6819C16.9695 16.394 16.8086 16.181 16.4867 15.755C14.991 13.7755 12.6417 12.5 10 12.5Z" stroke="var(--Text-Gray-600, #535862)" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
<path d="M10 10C12.0711 10 13.75 8.32107 13.75 6.25C13.75 4.17893 12.0711 2.5 10 2.5C7.92894 2.5 6.25001 4.17893 6.25001 6.25C6.25001 8.32107 7.92894 10 10 10Z" stroke="var(--Text-Gray-600, #535862)" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<input
type="text"
value={userProfile.fullName}
onChange={(e) => handleProfileUpdate('fullName', e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight outline-none"
/>
</div>
</div>
</div>
<div className="flex-1 inline-flex flex-col justify-start items-start gap-2">
<div className="self-stretch inline-flex justify-start items-center gap-0.5">
<div className="justify-start text-Neutrals-NeutralSlate900 text-sm font-normal font-['Inter'] leading-tight">Email Address</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-1">
<div className="self-stretch px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
<div>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.66669 5.83203L8.47079 10.5949C9.02176 10.9806 9.29725 11.1734 9.59691 11.2481C9.8616 11.3141 10.1384 11.3141 10.4031 11.2481C10.7028 11.1734 10.9783 10.9806 11.5293 10.5949L18.3334 5.83203M5.66669 16.6654H14.3334C15.7335 16.6654 16.4336 16.6654 16.9683 16.3929C17.4387 16.1532 17.8212 15.7707 18.0609 15.3003C18.3334 14.7656 18.3334 14.0655 18.3334 12.6654V7.33203C18.3334 5.9319 18.3334 5.23183 18.0609 4.69705C17.8212 4.22665 17.4387 3.8442 16.9683 3.60451C16.4336 3.33203 15.7335 3.33203 14.3334 3.33203H5.66669C4.26656 3.33203 3.56649 3.33203 3.03171 3.60451C2.56131 3.8442 2.17885 4.22665 1.93917 4.69705C1.66669 5.23183 1.66669 5.9319 1.66669 7.33203V12.6654C1.66669 14.0655 1.66669 14.7656 1.93917 15.3003C2.17885 15.7707 2.56131 16.1532 3.03171 16.3929C3.56649 16.6654 4.26656 16.6654 5.66669 16.6654Z" stroke="var(--Text-Gray-600, #535862)" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<input
type="email"
value={userProfile.email}
onChange={(e) => handleProfileUpdate('email', e.target.value)}
className="flex-1 bg-transparent text-Neutrals-NeutralSlate950 text-sm font-normal font-['Inter'] leading-tight outline-none"
/>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Divider */}
<div>
<svg width="1136" height="2" viewBox="0 0 1136 2" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1H1136" stroke="var(--Text-Gray-200, #E9EAEB)" />
</svg>
</div>
{/* Theme Customization Section */}
<div className="w-[1170px] p-6 flex flex-col justify-start items-start gap-6">
<div className="w-[584px] flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-start text-Text-Gray-800 text-lg font-semibold font-['Inter'] leading-7">Theme Customization</div>
<div className="self-stretch justify-start text-Text-Gray-500 text-sm font-normal font-['Inter'] leading-tight">Personalize your interface with light or dark mode and enhance your visual experience.</div>
</div>
<div className="inline-flex justify-start items-start gap-3 flex-wrap content-start">
{/* System Preference */}
<div
onClick={() => setSelectedTheme('system')}
className={`max-w-60 inline-flex flex-col justify-start items-start gap-3 cursor-pointer ${
selectedTheme === 'system' ? 'opacity-100' : 'opacity-70'
}`}
>
<div className="inline-flex justify-start items-center">
<img className="w-24 h-28 rounded-tl-lg rounded-bl-lg" src="https://via.placeholder.com/94x107/f8f9fa/6c757d?text=Light" />
<img className="w-24 h-28 rounded-tr-lg rounded-br-lg" src="https://via.placeholder.com/96x107/212529/ffffff?text=Dark" />
</div>
<div className="self-stretch h-5 justify-start text-Text-Gray-800 text-sm font-normal font-['Inter'] leading-tight">System preference</div>
</div>
{/* Light Mode */}
<div
onClick={() => setSelectedTheme('light')}
className={`w-48 max-w-60 inline-flex flex-col justify-start items-start gap-3 cursor-pointer ${
selectedTheme === 'light' ? 'opacity-100' : 'opacity-70'
}`}
>
<div className="self-stretch h-28 relative bg-Text-White-00 rounded-lg overflow-hidden">
<div className={`w-48 h-28 left-0 top-0 absolute bg-Text-White-00 rounded-[10px] outline outline-1 outline-offset-[-1px] overflow-hidden ${
selectedTheme === 'light' ? 'outline-Brand-Orange' : 'outline-Text-Gray-200'
}`}>
<img className="w-48 h-28 left-0 top-0 absolute rounded-lg" src="https://via.placeholder.com/190x107/f8f9fa/6c757d?text=Light+Mode" />
</div>
</div>
<div className="self-stretch h-5 justify-start text-Text-Gray-800 text-sm font-normal font-['Inter'] leading-tight">Light Mode</div>
</div>
{/* Dark Mode */}
<div
onClick={() => setSelectedTheme('dark')}
className={`max-w-60 inline-flex flex-col justify-start items-start gap-3 cursor-pointer ${
selectedTheme === 'dark' ? 'opacity-100' : 'opacity-70'
}`}
>
<div className="w-48 h-28 relative bg-Text-White-00 rounded-lg overflow-hidden">
<div className={`w-48 h-28 left-0 top-0 absolute bg-Text-White-00 rounded-[10px] outline outline-1 outline-offset-[-1px] overflow-hidden ${
selectedTheme === 'dark' ? 'outline-Brand-Orange' : 'outline-Text-Gray-200'
}`}>
<img className="w-48 h-28 left-0 top-0 absolute rounded-lg" src="https://via.placeholder.com/190x107/212529/ffffff?text=Dark+Mode" />
</div>
</div>
<div className="self-stretch h-5 justify-start text-Text-Gray-800 text-sm font-normal font-['Inter'] leading-tight">Dark Mode</div>
</div>
</div>
</div>
{/* Another Divider */}
<div>
<svg width="1136" height="2" viewBox="0 0 1136 2" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1H1136" stroke="var(--Text-Gray-200, #E9EAEB)" />
</svg>
</div>
{/* Action Buttons */}
<div className="w-[1175px] p-6 inline-flex justify-start items-center gap-2">
<div
onClick={handleReset}
className="px-3 py-2.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden cursor-pointer hover:bg-Neutrals-NeutralSlate200"
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Reset</div>
</div>
</div>
<div
onClick={handleSaveChanges}
className="px-3 py-2.5 bg-Brand-Orange rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden cursor-pointer hover:bg-Brand-Orange/90"
>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Save Changes</div>
</div>
</div>
</div>
</>
)}
{/* Billing Content */}
{activeTab === 'billing' && (
<div className="flex-1 flex items-center justify-center">
<div className="text-center">
<h2 className="text-2xl font-semibold text-Text-Gray-800 mb-4">Plan & Billing</h2>
<p className="text-Text-Gray-500">Billing management features would be implemented here.</p>
</div>
</div>
)}
</div>
</div>
</div>
);
};
export default SettingsNew;

View File

@@ -0,0 +1,223 @@
import React from 'react';
const ChatAIResponse: React.FC = () => {
return (
<div className="w-full h-[810px] p-4 bg-Neutrals-NeutralSlate200 inline-flex justify-start items-start overflow-hidden">
<div className="flex-1 self-stretch rounded-3xl shadow-[0px_0px_15px_0px_rgba(0,0,0,0.08)] flex justify-between items-start overflow-hidden">
{/* Sidebar */}
<div className="w-64 self-stretch max-w-64 min-w-64 px-3 pt-4 pb-3 bg-Neutrals-NeutralSlate0 border-r border-Neutrals-NeutralSlate200 inline-flex flex-col justify-between items-center overflow-hidden">
<div className="self-stretch flex flex-col justify-start items-start gap-5">
{/* Company Selector */}
<div className="w-60 pl-2 pr-4 py-2 bg-Neutrals-NeutralSlate0 rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-between items-center overflow-hidden">
<div className="flex-1 flex justify-start items-center gap-2">
<div className="w-8 h-8 rounded-full flex justify-start items-center gap-2.5">
<div className="w-8 h-8 relative bg-Brand-Orange rounded-full outline outline-[1.60px] outline-offset-[-1.60px] outline-white/10 overflow-hidden">
<div className="w-8 h-8 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[8.80px] top-[7.20px] absolute">
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_1042_694)">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M4.34342 10.6855C4.66998 11.0162 4.66998 11.5524 4.34341 11.8831L4.32669 11.9C4.00012 12.2307 3.47065 12.2307 3.14409 11.9C2.81753 11.5693 2.81753 11.0331 3.1441 10.7024L3.16082 10.6855C3.48739 10.3548 4.01686 10.3548 4.34342 10.6855Z" fill="url(#paint0_linear_1042_694)" />
</g>
<defs>
<linearGradient id="paint0_linear_1042_694" x1="3.74376" y1="10.4375" x2="3.74376" y2="12.148" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
</div>
</div>
</div>
<div className="flex-1 inline-flex flex-col justify-start items-start gap-0.5">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-base font-medium font-['Inter'] leading-normal">Zitlac Media</div>
</div>
</div>
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.83325 12.4987L9.99992 16.6654L14.1666 12.4987M5.83325 7.4987L9.99992 3.33203L14.1666 7.4987" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
{/* Navigation Menu */}
<div className="self-stretch flex flex-col justify-start items-start gap-5">
<div className="self-stretch flex flex-col justify-start items-start gap-1.5">
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 17.5016V11.3349C7.5 10.8682 7.5 10.6348 7.59083 10.4566C7.67072 10.2998 7.79821 10.1723 7.95501 10.0924C8.13327 10.0016 8.36662 10.0016 8.83333 10.0016H11.1667C11.6334 10.0016 11.8667 10.0016 12.045 10.0924C12.2018 10.1723 12.3293 10.2998 12.4092 10.4566C12.5 10.6348 12.5 10.8682 12.5 11.3349V17.5016M9.18141 2.30492L3.52949 6.70086C3.15168 6.99471 2.96278 7.14163 2.82669 7.32563C2.70614 7.48862 2.61633 7.67224 2.56169 7.86746C2.5 8.08785 2.5 8.32717 2.5 8.8058V14.8349C2.5 15.7683 2.5 16.235 2.68166 16.5916C2.84144 16.9052 3.09641 17.1601 3.41002 17.3199C3.76654 17.5016 4.23325 17.5016 5.16667 17.5016H14.8333C15.7668 17.5016 16.2335 17.5016 16.59 17.3199C16.9036 17.1601 17.1586 16.9052 17.3183 16.5916C17.5 16.235 17.5 15.7683 17.5 14.8349V8.8058C17.5 8.32717 17.5 8.08785 17.4383 7.86746C17.3837 7.67224 17.2939 7.48862 17.1733 7.32563C17.0372 7.14163 16.8483 6.99471 16.4705 6.70086L10.8186 2.30492C10.5258 2.07721 10.3794 1.96335 10.2178 1.91959C10.0752 1.88097 9.92484 1.88097 9.78221 1.91959C9.62057 1.96335 9.47418 2.07721 9.18141 2.30492Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Company Wiki</div>
</div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6666 9.16797H6.66659M8.33325 12.5013H6.66659M13.3333 5.83464H6.66659M16.6666 5.66797V14.3346C16.6666 15.7348 16.6666 16.4348 16.3941 16.9696C16.1544 17.44 15.772 17.8225 15.3016 18.0622C14.7668 18.3346 14.0667 18.3346 12.6666 18.3346H7.33325C5.93312 18.3346 5.23306 18.3346 4.69828 18.0622C4.22787 17.8225 3.84542 17.44 3.60574 16.9696C3.33325 16.4348 3.33325 15.7348 3.33325 14.3346V5.66797C3.33325 4.26784 3.33325 3.56777 3.60574 3.03299C3.84542 2.56259 4.22787 2.18014 4.69828 1.94045C5.23306 1.66797 5.93312 1.66797 7.33325 1.66797H12.6666C14.0667 1.66797 14.7668 1.66797 15.3016 1.94045C15.772 2.18014 16.1544 2.56259 16.3941 3.03299C16.6666 3.56777 16.6666 4.26784 16.6666 5.66797Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Submissions</div>
</div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_720)">
<path d="M10.0001 1.66797C11.0944 1.66797 12.1781 1.88352 13.1891 2.30231C14.2002 2.7211 15.1188 3.33493 15.8926 4.10875C16.6665 4.88257 17.2803 5.80123 17.6991 6.81228C18.1179 7.82332 18.3334 8.90696 18.3334 10.0013M10.0001 1.66797V10.0013M10.0001 1.66797C5.39771 1.66797 1.66675 5.39893 1.66675 10.0013C1.66675 14.6037 5.39771 18.3346 10.0001 18.3346C14.6025 18.3346 18.3334 14.6037 18.3334 10.0013M10.0001 1.66797C14.6025 1.66797 18.3334 5.39893 18.3334 10.0013M18.3334 10.0013L10.0001 10.0013M18.3334 10.0013C18.3334 11.3164 18.0222 12.6128 17.4251 13.7846C16.8281 14.9563 15.9622 15.9701 14.8983 16.7431L10.0001 10.0013" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
<defs>
<clipPath id="clip0_1042_720">
<rect width="20" height="20" fill="white" />
</clipPath>
</defs>
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Reports</div>
</div>
{/* Active Chat Item */}
<div className="w-60 px-4 py-2.5 bg-Neutrals-NeutralSlate100 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.4996 9.58333C17.4996 13.4953 14.3283 16.6667 10.4163 16.6667C9.51896 16.6667 8.66061 16.4998 7.87057 16.1954C7.72612 16.1398 7.6539 16.112 7.59647 16.0987C7.53998 16.0857 7.49908 16.0803 7.44116 16.0781C7.38226 16.0758 7.31764 16.0825 7.18841 16.0958L2.92089 16.537C2.51402 16.579 2.31059 16.6001 2.19058 16.5269C2.08606 16.4631 2.01487 16.3566 1.99592 16.2356C1.97416 16.0968 2.07138 15.9168 2.2658 15.557L3.62885 13.034C3.7411 12.8262 3.79723 12.7223 3.82265 12.6225C3.84776 12.5238 3.85383 12.4527 3.8458 12.3512C3.83766 12.2484 3.79258 12.1147 3.70241 11.8472C3.46281 11.1363 3.33294 10.375 3.33294 9.58333C3.33294 5.67132 6.50426 2.5 10.4163 2.5C14.3283 2.5 17.4996 5.67132 17.4996 9.58333Z" stroke="var(--Brand-Orange, #3399FF)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Chat</div>
</div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_728)">
<path d="M7.57508 7.5013C7.771 6.94436 8.15771 6.47472 8.66671 6.17558C9.17571 5.87643 9.77416 5.76708 10.3561 5.8669C10.938 5.96671 11.4658 6.26924 11.846 6.72091C12.2262 7.17258 12.4343 7.74424 12.4334 8.33464C12.4334 10.0013 9.93342 10.8346 9.93342 10.8346M10.0001 14.168H10.0084M18.3334 10.0013C18.3334 14.6037 14.6025 18.3346 10.0001 18.3346C5.39771 18.3346 1.66675 14.6037 1.66675 10.0013C1.66675 5.39893 5.39771 1.66797 10.0001 1.66797C14.6025 1.66797 18.3334 5.39893 18.3334 10.0013Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Help</div>
</div>
</div>
</div>
</div>
{/* Bottom Section */}
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_733)">
<path d="M10.0001 12.5013C11.3808 12.5013 12.5001 11.382 12.5001 10.0013C12.5001 8.62059 11.3808 7.5013 10.0001 7.5013C8.61937 7.5013 7.50008 8.62059 7.50008 10.0013C7.50008 11.382 8.61937 12.5013 10.0001 12.5013Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Settings</div>
</div>
{/* Company Report Card */}
<div className="self-stretch bg-Neutrals-NeutralSlate0 rounded-[20px] shadow-[0px_1px_4px_0px_rgba(14,18,27,0.04)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 flex flex-col justify-start items-start overflow-hidden">
<div className="self-stretch h-24 relative">
<div className="w-60 h-32 left-0 top-[-0.50px] absolute bg-gradient-to-b from-black to-black/0" />
<div className="w-60 p-3 left-[18.12px] top-[42.52px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
<div className="w-60 p-3 left-[31.44px] top-[22px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
</div>
<div className="self-stretch p-3 flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-sm font-semibold font-['Inter'] leading-tight">Build [Company]'s Report</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-xs font-normal font-['Inter'] leading-none">Share this form with your team members to capture valuable info about your company to train Auditly.</div>
</div>
<div className="self-stretch px-3 pb-3 flex flex-col justify-start items-start gap-8">
<div className="self-stretch inline-flex justify-start items-start gap-2">
<div className="flex-1 px-3 py-1.5 bg-Button-Secondary rounded-[999px] flex justify-center items-center gap-0.5 overflow-hidden">
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Invite</div>
</div>
<div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99992 3.33203V12.6654M3.33325 7.9987H12.6666" stroke="var(--Neutrals-NeutralSlate950, #0A0D12)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
<div className="flex-1 px-3 py-1.5 bg-Brand-Orange rounded-[999px] outline outline-1 outline-offset-[-1px] outline-blue-400 flex justify-center items-center gap-0.5 overflow-hidden">
<div className="relative">
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.97179 12.2442L8.02898 13.1871C6.72723 14.4888 4.61668 14.4888 3.31493 13.1871C2.01319 11.8853 2.01319 9.77476 3.31493 8.47301L4.25774 7.5302M12.743 8.47301L13.6858 7.5302C14.9876 6.22845 14.9876 4.1179 13.6858 2.81615C12.3841 1.51441 10.2735 1.51441 8.97179 2.81615L8.02898 3.75896M6.16705 10.3349L10.8337 5.66826" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Copy</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Main Chat Area */}
<div className="flex-1 self-stretch py-6 bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-2.5">
<div className="w-[736px] flex-1 max-w-[736px] pt-48 flex flex-col justify-start items-center gap-6">
<div className="self-stretch flex flex-col justify-start items-end gap-4">
{/* User Question */}
<div className="px-4 py-3 bg-Main-BG-Gray-100 rounded-2xl inline-flex justify-start items-center gap-3 overflow-hidden">
<div className="justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">What are the main characteristics?</div>
<div className="pl-1.5 pr-2 py-0.5 bg-Text-Gray-300 rounded-2xl flex justify-start items-center gap-1.5">
<div className="relative">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_20157)">
<path d="M9.33318 4.66761V7.58427C9.33318 8.0484 9.51755 8.49352 9.84574 8.82171C10.1739 9.1499 10.619 9.33427 11.0832 9.33427C11.5473 9.33427 11.9924 9.1499 12.3206 8.82171C12.6488 8.49352 12.8332 8.0484 12.8332 7.58427V7.00094C12.8331 5.68437 12.3876 4.40655 11.5693 3.37525C10.7509 2.34395 9.60767 1.61982 8.32555 1.32061C7.04343 1.02141 5.6978 1.16471 4.50746 1.72724C3.31712 2.28976 2.35207 3.23841 1.76924 4.41895C1.18641 5.59948 1.02007 6.94245 1.29727 8.22951C1.57447 9.51656 2.27891 10.672 3.29604 11.5079C4.31317 12.3439 5.58318 12.8111 6.89955 12.8338C8.21592 12.8564 9.50124 12.4331 10.5465 11.6326M9.33318 7.00094C9.33318 8.2896 8.28851 9.33427 6.99984 9.33427C5.71118 9.33427 4.66651 8.2896 4.66651 7.00094C4.66651 5.71228 5.71118 4.66761 6.99984 4.66761C8.28851 4.66761 9.33318 5.71228 9.33318 7.00094Z" stroke="var(--Text-Gray-600, #535862)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="text-center justify-start text-Text-Gray-600 text-sm font-medium font-['Inter'] leading-tight">Alex Green</div>
</div>
</div>
{/* AI Response */}
<div className="self-stretch justify-start">
<span className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">Main characteristics refer to the most important features or qualities that define or distinguish something. These can be physical, emotional, mental, or moral attributes that help identify and differentiate a person, object, or concept. <br />Elaboration:<br /></span>
<span className="text-Text-Gray-800 text-base font-medium font-['Inter'] leading-normal">Physical Characteristics:</span>
<span className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal"> These relate to the outward appearance of something, such as its shape, size, color, or material. For example, the physical characteristics of a chair might include its height, the material it's made of (wood, metal, etc.), and its color. <br /></span>
<span className="text-Text-Gray-800 text-base font-medium font-['Inter'] leading-normal">Emotional Characteristics:</span>
<span className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal"> These describe how someone or something responds to situations or experiences. Examples include being happy, sad, angry, or calm. <br /></span>
<span className="text-Text-Gray-800 text-base font-medium font-['Inter'] leading-normal">Mental Characteristics:</span>
<span className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal"> These relate to a person's cognitive abilities and thinking patterns. Examples include intelligence, creativity, and curiosity. <br /></span>
<span className="text-Text-Gray-800 text-base font-medium font-['Inter'] leading-normal">Moral Characteristics:</span>
<span className="text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal"> These describe a person's ethical or value-based behavior. Examples include kindness, honesty, and generosity. </span>
</div>
</div>
{/* Chat Input */}
<div className="self-stretch pl-5 pr-3 pt-5 pb-3 bg-Main-BG-Gray-50 rounded-3xl flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Text-Gray-500 text-base font-normal font-['Inter'] leading-normal">Ask anything, use @ to tag staff and ask questions.</div>
<div className="self-stretch inline-flex justify-between items-center">
<div className="flex justify-start items-center gap-4">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.6271 9.08442L10.1141 16.5974C8.40556 18.306 5.63546 18.306 3.92692 16.5974C2.21837 14.8889 2.21837 12.1188 3.92692 10.4102L11.4399 2.89724C12.579 1.75821 14.4257 1.75821 15.5647 2.89724C16.7037 4.03627 16.7037 5.883 15.5647 7.02203L8.34633 14.2404C7.77682 14.8099 6.85345 14.8099 6.28394 14.2404C5.71442 13.6709 5.71442 12.7475 6.28394 12.178L12.6184 5.84352" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5 17.5H5.77614C5.2713 17.5 5.01887 17.5 4.90199 17.4002C4.80056 17.3135 4.74674 17.1836 4.75721 17.0506C4.76927 16.8974 4.94776 16.7189 5.30474 16.3619L12.3905 9.27614C12.7205 8.94613 12.8855 8.78112 13.0758 8.7193C13.2432 8.66492 13.4235 8.66492 13.5908 8.7193C13.7811 8.78112 13.9461 8.94613 14.2761 9.27614L17.5 12.5V13.5M13.5 17.5C14.9001 17.5 15.6002 17.5 16.135 17.2275C16.6054 16.9878 16.9878 16.6054 17.2275 16.135C17.5 15.6002 17.5 14.9001 17.5 13.5M13.5 17.5H6.5C5.09987 17.5 4.3998 17.5 3.86502 17.2275C3.39462 16.9878 3.01217 16.6054 2.77248 16.135C2.5 15.6002 2.5 14.9001 2.5 13.5V6.5C2.5 5.09987 2.5 4.3998 2.77248 3.86502C3.01217 3.39462 3.39462 3.01217 3.86502 2.77248C4.3998 2.5 5.09987 2.5 6.5 2.5H13.5C14.9001 2.5 15.6002 2.5 16.135 2.77248C16.6054 3.01217 16.9878 3.39462 17.2275 3.86502C17.5 4.3998 17.5 5.09987 17.5 6.5V13.5M8.75 7.08333C8.75 8.00381 8.00381 8.75 7.08333 8.75C6.16286 8.75 5.41667 8.00381 5.41667 7.08333C5.41667 6.16286 6.16286 5.41667 7.08333 5.41667C8.00381 5.41667 8.75 6.16286 8.75 7.08333Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_20047)">
<path d="M13.3334 6.66745V10.8341C13.3334 11.4972 13.5968 12.133 14.0657 12.6019C14.5345 13.0707 15.1704 13.3341 15.8334 13.3341C16.4965 13.3341 17.1324 13.0707 17.6012 12.6019C18.07 12.133 18.3334 11.4972 18.3334 10.8341V10.0008C18.3333 8.11998 17.6969 6.29452 16.5278 4.82123C15.3587 3.34794 13.7256 2.31347 11.894 1.88603C10.0624 1.45859 8.14003 1.66332 6.43955 2.46692C4.73906 3.27053 3.36042 4.62575 2.5278 6.31222C1.69519 7.99869 1.45756 9.91723 1.85356 11.7559C2.24956 13.5945 3.2559 15.2451 4.70895 16.4393C6.16199 17.6335 7.97628 18.3011 9.85681 18.3334C11.7373 18.3657 13.5735 17.761 15.0668 16.6175M13.3334 10.0008C13.3334 11.8417 11.841 13.3341 10.0001 13.3341C8.15914 13.3341 6.66676 11.8417 6.66676 10.0008C6.66676 8.15984 8.15914 6.66745 10.0001 6.66745C11.841 6.66745 13.3334 8.15984 13.3334 10.0008Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
</div>
<div className="p-2.5 bg-Text-Gray-300 rounded-[999px] flex justify-start items-center gap-2.5 overflow-hidden">
<div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13.3346V2.66797M8 2.66797L4 6.66797M8 2.66797L12 6.66797" stroke="var(--Text-White-00, #FDFDFD)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ChatAIResponse;

256
pages/figma/ChatLight.tsx Normal file
View File

@@ -0,0 +1,256 @@
import React from 'react';
const ChatLight: React.FC = () => {
return (
<div className="w-full h-[810px] p-4 bg-Neutrals-NeutralSlate200 inline-flex justify-start items-start overflow-hidden">
<div className="flex-1 self-stretch rounded-3xl shadow-[0px_0px_15px_0px_rgba(0,0,0,0.08)] flex justify-between items-start overflow-hidden">
{/* Sidebar */}
<div className="w-64 self-stretch max-w-64 min-w-64 px-3 pt-4 pb-3 bg-Neutrals-NeutralSlate0 border-r border-Neutrals-NeutralSlate200 inline-flex flex-col justify-between items-center overflow-hidden">
<div className="self-stretch flex flex-col justify-start items-start gap-5">
{/* Company Selector */}
<div className="w-60 pl-2 pr-4 py-2 bg-Neutrals-NeutralSlate0 rounded-3xl outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex justify-between items-center overflow-hidden">
<div className="flex-1 flex justify-start items-center gap-2">
<div className="w-8 h-8 rounded-full flex justify-start items-center gap-2.5">
<div className="w-8 h-8 relative bg-Brand-Orange rounded-full outline outline-[1.60px] outline-offset-[-1.60px] outline-white/10 overflow-hidden">
<div className="w-8 h-8 left-0 top-0 absolute bg-gradient-to-b from-white/0 to-white/10" />
<div className="left-[8.80px] top-[7.20px] absolute">
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_1042_1443)">
<path opacity="0.5" fillRule="evenodd" clipRule="evenodd" d="M4.34347 10.6855C4.67003 11.0162 4.67003 11.5524 4.34346 11.8831L4.32674 11.9C4.00017 12.2307 3.4707 12.2307 3.14414 11.9C2.81758 11.5693 2.81758 11.0331 3.14415 10.7024L3.16087 10.6855C3.48744 10.3548 4.01691 10.3548 4.34347 10.6855Z" fill="url(#paint0_linear_1042_1443)" />
</g>
<defs>
<linearGradient id="paint0_linear_1042_1443" x1="3.7438" y1="10.4375" x2="3.7438" y2="12.148" gradientUnits="userSpaceOnUse">
<stop stopColor="white" stopOpacity="0.8" />
<stop offset="1" stopColor="white" stopOpacity="0.5" />
</linearGradient>
</defs>
</svg>
</div>
</div>
</div>
<div className="flex-1 inline-flex flex-col justify-start items-start gap-0.5">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate950 text-base font-medium font-['Inter'] leading-normal">Zitlac Media</div>
</div>
</div>
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.83333 12.4987L9.99999 16.6654L14.1667 12.4987M5.83333 7.4987L9.99999 3.33203L14.1667 7.4987" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
{/* Navigation Menu */}
<div className="self-stretch flex flex-col justify-start items-start gap-5">
<div className="self-stretch flex flex-col justify-start items-start gap-1.5">
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 17.5016V11.3349C7.5 10.8682 7.5 10.6348 7.59083 10.4566C7.67072 10.2998 7.79821 10.1723 7.95501 10.0924C8.13327 10.0016 8.36662 10.0016 8.83333 10.0016H11.1667C11.6334 10.0016 11.8667 10.0016 12.045 10.0924C12.2018 10.1723 12.3293 10.2998 12.4092 10.4566C12.5 10.6348 12.5 10.8682 12.5 11.3349V17.5016M9.18141 2.30492L3.52949 6.70086C3.15168 6.99471 2.96278 7.14163 2.82669 7.32563C2.70614 7.48862 2.61633 7.67224 2.56169 7.86746C2.5 8.08785 2.5 8.32717 2.5 8.8058V14.8349C2.5 15.7683 2.5 16.235 2.68166 16.5916C2.84144 16.9052 3.09641 17.1601 3.41002 17.3199C3.76654 17.5016 4.23325 17.5016 5.16667 17.5016H14.8333C15.7668 17.5016 16.2335 17.5016 16.59 17.3199C16.9036 17.1601 17.1586 16.9052 17.3183 16.5916C17.5 16.235 17.5 15.7683 17.5 14.8349V8.8058C17.5 8.32717 17.5 8.08785 17.4383 7.86746C17.3837 7.67224 17.2939 7.48862 17.1733 7.32563C17.0372 7.14163 16.8483 6.99471 16.4705 6.70086L10.8186 2.30492C10.5258 2.07721 10.3794 1.96335 10.2178 1.91959C10.0752 1.88097 9.92484 1.88097 9.78221 1.91959C9.62057 1.96335 9.47418 2.07721 9.18141 2.30492Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Company Wiki</div>
</div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.6667 9.16797H6.66667M8.33333 12.5013H6.66667M13.3333 5.83464H6.66667M16.6667 5.66797V14.3346C16.6667 15.7348 16.6667 16.4348 16.3942 16.9696C16.1545 17.44 15.772 17.8225 15.3016 18.0622C14.7669 18.3346 14.0668 18.3346 12.6667 18.3346H7.33333C5.9332 18.3346 5.23314 18.3346 4.69836 18.0622C4.22795 17.8225 3.8455 17.44 3.60582 16.9696C3.33333 16.4348 3.33333 15.7348 3.33333 14.3346V5.66797C3.33333 4.26784 3.33333 3.56777 3.60582 3.03299C3.8455 2.56259 4.22795 2.18014 4.69836 1.94045C5.23314 1.66797 5.9332 1.66797 7.33333 1.66797H12.6667C14.0668 1.66797 14.7669 1.66797 15.3016 1.94045C15.772 2.18014 16.1545 2.56259 16.3942 3.03299C16.6667 3.56777 16.6667 4.26784 16.6667 5.66797Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Submissions</div>
</div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_1469)">
<path d="M10 1.66797C11.0943 1.66797 12.178 1.88352 13.189 2.30231C14.2001 2.7211 15.1187 3.33493 15.8926 4.10875C16.6664 4.88257 17.2802 5.80123 17.699 6.81228C18.1178 7.82332 18.3333 8.90696 18.3333 10.0013M10 1.66797V10.0013M10 1.66797C5.39763 1.66797 1.66667 5.39893 1.66667 10.0013C1.66667 14.6037 5.39763 18.3346 10 18.3346C14.6024 18.3346 18.3333 14.6037 18.3333 10.0013M10 1.66797C14.6024 1.66797 18.3333 5.39893 18.3333 10.0013M18.3333 10.0013L10 10.0013M18.3333 10.0013C18.3333 11.3164 18.0221 12.6128 17.4251 13.7846C16.828 14.9563 15.9621 15.9701 14.8982 16.7431L10 10.0013" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Reports</div>
</div>
{/* Active Chat Item */}
<div className="w-60 px-4 py-2.5 bg-Neutrals-NeutralSlate100 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.4997 9.58333C17.4997 13.4953 14.3284 16.6667 10.4164 16.6667C9.51904 16.6667 8.66069 16.4998 7.87065 16.1954C7.7262 16.1398 7.65398 16.112 7.59655 16.0987C7.54006 16.0857 7.49917 16.0803 7.44124 16.0781C7.38234 16.0758 7.31772 16.0825 7.18849 16.0958L2.92097 16.537C2.5141 16.579 2.31067 16.6001 2.19067 16.5269C2.08614 16.4631 2.01495 16.3566 1.996 16.2356C1.97425 16.0968 2.07146 15.9168 2.26588 15.557L3.62893 13.034C3.74118 12.8262 3.79731 12.7223 3.82273 12.6225C3.84784 12.5238 3.85391 12.4527 3.84588 12.3512C3.83775 12.2484 3.79266 12.1147 3.7025 11.8472C3.46289 11.1363 3.33302 10.375 3.33302 9.58333C3.33302 5.67132 6.50434 2.5 10.4164 2.5C14.3284 2.5 17.4997 5.67132 17.4997 9.58333Z" stroke="var(--Brand-Orange, #3399FF)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Chat</div>
</div>
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_1477)">
<path d="M7.575 7.5013C7.77092 6.94436 8.15763 6.47472 8.66663 6.17558C9.17563 5.87643 9.77408 5.76708 10.356 5.8669C10.9379 5.96671 11.4657 6.26924 11.8459 6.72091C12.2261 7.17258 12.4342 7.74424 12.4333 8.33464C12.4333 10.0013 9.93333 10.8346 9.93333 10.8346M10 14.168H10.0083M18.3333 10.0013C18.3333 14.6037 14.6024 18.3346 10 18.3346C5.39763 18.3346 1.66667 14.6037 1.66667 10.0013C1.66667 5.39893 5.39763 1.66797 10 1.66797C14.6024 1.66797 18.3333 5.39893 18.3333 10.0013Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Help</div>
</div>
</div>
</div>
</div>
{/* Bottom Section */}
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="w-60 px-4 py-2.5 rounded-[34px] inline-flex justify-start items-center gap-2">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_1042_1482)">
<path d="M10 12.5013C11.3807 12.5013 12.5 11.382 12.5 10.0013C12.5 8.62059 11.3807 7.5013 10 7.5013C8.61929 7.5013 7.5 8.62059 7.5 10.0013C7.5 11.382 8.61929 12.5013 10 12.5013Z" stroke="var(--Neutrals-NeutralSlate400, #A4A7AE)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="flex-1 justify-start text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">Settings</div>
</div>
{/* Company Report Card */}
<div className="self-stretch bg-Neutrals-NeutralSlate0 rounded-[20px] shadow-[0px_1px_4px_0px_rgba(14,18,27,0.04)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 flex flex-col justify-start items-start overflow-hidden">
<div className="self-stretch h-24 relative">
<div className="w-60 h-32 left-0 top-[-0.50px] absolute bg-gradient-to-b from-black to-black/0" />
<div className="w-60 p-3 left-[18.12px] top-[42.52px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
<div className="w-60 p-3 left-[31.44px] top-[22px] absolute origin-top-left rotate-[-28.34deg] bg-Neutrals-NeutralSlate0 rounded-xl shadow-[0px_10px_20px_4px_rgba(14,18,27,0.08)] outline outline-1 outline-offset-[-1px] outline-Neutrals-NeutralSlate200 inline-flex flex-col justify-start items-start gap-3 overflow-hidden" />
</div>
<div className="self-stretch p-3 flex flex-col justify-start items-start gap-1">
<div className="self-stretch justify-start text-Neutrals-NeutralSlate800 text-sm font-semibold font-['Inter'] leading-tight">Build [Company]'s Report</div>
<div className="self-stretch justify-start text-Neutrals-NeutralSlate500 text-xs font-normal font-['Inter'] leading-none">Share this form with your team members to capture valuable info about your company to train Auditly.</div>
</div>
<div className="self-stretch px-3 pb-3 flex flex-col justify-start items-start gap-8">
<div className="self-stretch inline-flex justify-start items-start gap-2">
<div className="flex-1 px-3 py-1.5 bg-Button-Secondary rounded-[999px] flex justify-center items-center gap-0.5 overflow-hidden">
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Neutrals-NeutralSlate950 text-sm font-medium font-['Inter'] leading-tight">Invite</div>
</div>
<div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00004 3.33398V12.6673M3.33337 8.00065H12.6667" stroke="var(--Neutrals-NeutralSlate950, #0A0D12)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
<div className="flex-1 px-3 py-1.5 bg-Brand-Orange rounded-[999px] outline outline-1 outline-offset-[-1px] outline-blue-400 flex justify-center items-center gap-0.5 overflow-hidden">
<div className="relative">
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.97167 12.2423L8.02886 13.1851C6.72711 14.4868 4.61656 14.4868 3.31481 13.1851C2.01306 11.8834 2.01306 9.7728 3.31481 8.47106L4.25762 7.52825M12.7429 8.47106L13.6857 7.52825C14.9875 6.2265 14.9875 4.11595 13.6857 2.8142C12.384 1.51245 10.2734 1.51245 8.97167 2.8142L8.02886 3.75701M6.16693 10.333L10.8336 5.6663" stroke="var(--Other-White, white)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-Other-White text-sm font-medium font-['Inter'] leading-tight">Copy</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Main Chat Area */}
<div className="flex-1 self-stretch py-6 bg-Neutrals-NeutralSlate0 inline-flex flex-col justify-center items-center gap-2.5">
<div className="w-[736px] flex-1 max-w-[736px] pt-48 flex flex-col justify-between items-center">
<div className="self-stretch flex flex-col justify-start items-center gap-6">
<div className="justify-start text-Text-Gray-800 text-2xl font-medium font-['Neue_Montreal'] leading-normal">What would you like to understand?</div>
<div className="p-1 bg-Neutrals-NeutralSlate100 rounded-xl inline-flex justify-start items-center gap-1">
<div className="px-3 py-1.5 bg-white rounded-lg shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden">
<div className="px-0.5 flex justify-center items-center">
<div className="justify-start text-Neutrals-NeutralSlate900 text-xs font-medium font-['Inter'] leading-none">Accountability</div>
</div>
</div>
<div className="px-3 py-1.5 rounded-lg shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden">
<div className="px-0.5 flex justify-center items-center">
<div className="justify-start text-Neutrals-NeutralSlate600 text-xs font-medium font-['Inter'] leading-none">Employee Growth</div>
</div>
</div>
<div className="px-3 py-1.5 rounded-lg shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden">
<div className="px-0.5 flex justify-center items-center">
<div className="justify-start text-Neutrals-NeutralSlate600 text-xs font-medium font-['Inter'] leading-none">Customer Focus</div>
</div>
</div>
<div className="px-3 py-1.5 rounded-lg shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)] flex justify-center items-center gap-1 overflow-hidden">
<div className="px-0.5 flex justify-center items-center">
<div className="justify-start text-Neutrals-NeutralSlate600 text-xs font-medium font-['Inter'] leading-none">Teamwork</div>
</div>
</div>
</div>
<div className="self-stretch flex flex-col justify-start items-start gap-3">
<div className="self-stretch inline-flex justify-start items-center gap-3">
<div className="flex-1 h-48 px-3 py-4 bg-Main-BG-Gray-50 rounded-2xl inline-flex flex-col justify-between items-start overflow-hidden">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_19218)">
<path d="M7.57499 7.5013C7.77091 6.94436 8.15762 6.47472 8.66662 6.17558C9.17562 5.87643 9.77407 5.76708 10.356 5.8669C10.9379 5.96671 11.4657 6.26924 11.8459 6.72091C12.2261 7.17258 12.4342 7.74424 12.4333 8.33464C12.4333 10.0013 9.93332 10.8346 9.93332 10.8346M9.99999 14.168H10.0083M18.3333 10.0013C18.3333 14.6037 14.6024 18.3346 9.99999 18.3346C5.39762 18.3346 1.66666 14.6037 1.66666 10.0013C1.66666 5.39893 5.39762 1.66797 9.99999 1.66797C14.6024 1.66797 18.3333 5.39893 18.3333 10.0013Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="self-stretch justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">How can the company serve them better?</div>
</div>
<div className="flex-1 h-48 px-3 py-4 bg-Main-BG-Gray-50 rounded-2xl inline-flex flex-col justify-between items-start overflow-hidden">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_19221)">
<path d="M7.57502 7.5013C7.77094 6.94436 8.15765 6.47472 8.66665 6.17558C9.17565 5.87643 9.7741 5.76708 10.356 5.8669C10.9379 5.96671 11.4657 6.26924 11.8459 6.72091C12.2261 7.17258 12.4342 7.74424 12.4334 8.33464C12.4334 10.0013 9.93335 10.8346 9.93335 10.8346M10 14.168H10.0084M18.3334 10.0013C18.3334 14.6037 14.6024 18.3346 10 18.3346C5.39765 18.3346 1.66669 14.6037 1.66669 10.0013C1.66669 5.39893 5.39765 1.66797 10 1.66797C14.6024 1.66797 18.3334 5.39893 18.3334 10.0013Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="self-stretch justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">How can the company serve them better?</div>
</div>
<div className="flex-1 h-48 px-3 py-4 bg-Main-BG-Gray-50 rounded-2xl inline-flex flex-col justify-between items-start overflow-hidden">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_19224)">
<path d="M7.57502 7.5013C7.77094 6.94436 8.15765 6.47472 8.66665 6.17558C9.17565 5.87643 9.7741 5.76708 10.356 5.8669C10.9379 5.96671 11.4657 6.26924 11.8459 6.72091C12.2261 7.17258 12.4342 7.74424 12.4334 8.33464C12.4334 10.0013 9.93335 10.8346 9.93335 10.8346M10 14.168H10.0084M18.3334 10.0013C18.3334 14.6037 14.6024 18.3346 10 18.3346C5.39765 18.3346 1.66669 14.6037 1.66669 10.0013C1.66669 5.39893 5.39765 1.66797 10 1.66797C14.6024 1.66797 18.3334 5.39893 18.3334 10.0013Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="self-stretch justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">How can the company serve them better?</div>
</div>
<div className="flex-1 h-48 px-3 py-4 bg-Main-BG-Gray-50 rounded-2xl inline-flex flex-col justify-between items-start overflow-hidden">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_19227)">
<path d="M7.57502 7.5013C7.77094 6.94436 8.15765 6.47472 8.66665 6.17558C9.17565 5.87643 9.7741 5.76708 10.356 5.8669C10.9379 5.96671 11.4657 6.26924 11.8459 6.72091C12.2261 7.17258 12.4342 7.74424 12.4334 8.33464C12.4334 10.0013 9.93335 10.8346 9.93335 10.8346M10 14.168H10.0084M18.3334 10.0013C18.3334 14.6037 14.6024 18.3346 10 18.3346C5.39765 18.3346 1.66669 14.6037 1.66669 10.0013C1.66669 5.39893 5.39765 1.66797 10 1.66797C14.6024 1.66797 18.3334 5.39893 18.3334 10.0013Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
<div className="self-stretch justify-start text-Text-Gray-800 text-base font-normal font-['Inter'] leading-normal">How can the company serve them better?</div>
</div>
</div>
</div>
</div>
<div className="self-stretch pl-5 pr-3 pt-5 pb-3 bg-Main-BG-Gray-50 rounded-3xl flex flex-col justify-start items-start gap-4">
<div className="self-stretch justify-start text-Text-Gray-500 text-base font-normal font-['Inter'] leading-normal">Ask anything, use @ to tag staff and ask questions.</div>
<div className="self-stretch inline-flex justify-between items-center">
<div className="flex justify-start items-center gap-4">
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.6271 9.08442L10.1141 16.5974C8.40553 18.306 5.63543 18.306 3.92688 16.5974C2.21834 14.8889 2.21834 12.1188 3.92688 10.4102L11.4399 2.89724C12.5789 1.75821 14.4257 1.75821 15.5647 2.89724C16.7037 4.03627 16.7037 5.883 15.5647 7.02203L8.3463 14.2404C7.77679 14.8099 6.85342 14.8099 6.28391 14.2404C5.71439 13.6709 5.71439 12.7475 6.28391 12.178L12.6184 5.84352" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5 17.5H5.77614C5.2713 17.5 5.01887 17.5 4.90199 17.4002C4.80056 17.3135 4.74674 17.1836 4.75721 17.0506C4.76927 16.8974 4.94776 16.7189 5.30474 16.3619L12.3905 9.27614C12.7205 8.94613 12.8855 8.78112 13.0758 8.7193C13.2432 8.66492 13.4235 8.66492 13.5908 8.7193C13.7811 8.78112 13.9461 8.94613 14.2761 9.27614L17.5 12.5V13.5M13.5 17.5C14.9001 17.5 15.6002 17.5 16.135 17.2275C16.6054 16.9878 16.9878 16.6054 17.2275 16.135C17.5 15.6002 17.5 14.9001 17.5 13.5M13.5 17.5H6.5C5.09987 17.5 4.3998 17.5 3.86502 17.2275C3.39462 16.9878 3.01217 16.6054 2.77248 16.135C2.5 15.6002 2.5 14.9001 2.5 13.5V6.5C2.5 5.09987 2.5 4.3998 2.77248 3.86502C3.01217 3.39462 3.39462 3.01217 3.86502 2.77248C4.3998 2.5 5.09987 2.5 6.5 2.5H13.5C14.9001 2.5 15.6002 2.5 16.135 2.77248C16.6054 3.01217 16.9878 3.39462 17.2275 3.86502C17.5 4.3998 17.5 5.09987 17.5 6.5V13.5M8.75 7.08333C8.75 8.00381 8.00381 8.75 7.08333 8.75C6.16286 8.75 5.41667 8.00381 5.41667 7.08333C5.41667 6.16286 6.16286 5.41667 7.08333 5.41667C8.00381 5.41667 8.75 6.16286 8.75 7.08333Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="relative">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_818_19240)">
<path d="M13.3334 6.66745V10.8341C13.3334 11.4972 13.5968 12.133 14.0656 12.6019C14.5344 13.0707 15.1703 13.3341 15.8334 13.3341C16.4964 13.3341 17.1323 13.0707 17.6011 12.6019C18.07 12.133 18.3334 11.4972 18.3334 10.8341V10.0008C18.3332 8.11998 17.6969 6.29452 16.5278 4.82123C15.3586 3.34794 13.7255 2.31347 11.8939 1.88603C10.0623 1.45859 8.13997 1.66332 6.43949 2.46692C4.739 3.27053 3.36036 4.62575 2.52774 6.31222C1.69513 7.99869 1.4575 9.91723 1.8535 11.7559C2.2495 13.5945 3.25584 15.2451 4.70889 16.4393C6.16193 17.6335 7.97622 18.3011 9.85675 18.3334C11.7373 18.3657 13.5735 17.761 15.0667 16.6175M13.3334 10.0008C13.3334 11.8417 11.841 13.3341 10 13.3341C8.15908 13.3341 6.6667 11.8417 6.6667 10.0008C6.6667 8.15984 8.15908 6.66745 10 6.66745C11.841 6.66745 13.3334 8.15984 13.3334 10.0008Z" stroke="var(--Text-Gray-500, #717680)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</g>
</svg>
</div>
</div>
<div className="p-2.5 bg-Text-Gray-300 rounded-[999px] flex justify-start items-center gap-2.5 overflow-hidden">
<div className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13.3346V2.66797M8 2.66797L4 6.66797M8 2.66797L12 6.66797" stroke="var(--Text-White-00, #FDFDFD)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default ChatLight;

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

View File

@@ -9,11 +9,47 @@ export default {
theme: { theme: {
extend: { extend: {
colors: { colors: {
// Brand colors // Figma Brand colors
'Brand-Orange': '#5E48FC',
// Figma Neutral colors
'Neutrals-NeutralSlate0': '#FDFDFD',
'Neutrals-NeutralSlate50': '#FAFAFA',
'Neutrals-NeutralSlate100': '#F5F5F5',
'Neutrals-NeutralSlate200': '#E9EAEB',
'Neutrals-NeutralSlate300': '#D5D7DA',
'Neutrals-NeutralSlate400': '#A4A7AE',
'Neutrals-NeutralSlate500': '#7A7680',
'Neutrals-NeutralSlate600': '#535862',
'Neutrals-NeutralSlate700': '#414651',
'Neutrals-NeutralSlate800': '#252B37',
'Neutrals-NeutralSlate900': '#181D27',
'Neutrals-NeutralSlate950': '#0A0D12',
// Figma Other colors
'Other-White': '#FFFFFF',
'Main-BG-Gray-50': '#FAFAFA',
'bg-white-0': '#FFFFFF',
// Figma Button colors
'Button-Secondary': '#F5F5F5',
// Additional Figma colors from the designs
'Main-BG-Gray-100': '#F5F5F5',
'Text-Gray-100': '#F5F5F5',
'Text-Gray-200': '#E9EAEB',
'Text-Gray-500': '#A4A7AE',
'Text-Gray-600': '#717680',
'Text-Gray-800': '#252B37',
'Text-Dark-950': '#0A0D12',
'Light-Grays-l-gray08': '#FAFAFA',
'Outline-Outline-Gray-200': '#E9EAEB',
// Legacy brand colors (keep for compatibility)
brand: { brand: {
main: '#5E48FC', main: '#5E48FC',
}, },
// Neutral Light colors // Legacy Neutral Light colors (keep for compatibility)
gray: { gray: {
1: '#A4A7AE', 1: '#A4A7AE',
2: '#D5D7DA', 2: '#D5D7DA',
@@ -22,7 +58,7 @@ export default {
5: '#FAFAFA', 5: '#FAFAFA',
6: '#FDFDFD', 6: '#FDFDFD',
}, },
// Neutral Dark colors // Legacy Neutral Dark colors (keep for compatibility)
dark: { dark: {
1: '#A4A7AE', 1: '#A4A7AE',
2: '#717680', 2: '#717680',
@@ -32,7 +68,7 @@ export default {
6: '#181D27', 6: '#181D27',
7: '#0A0D12', 7: '#0A0D12',
}, },
// Status colors // Legacy Status colors (keep for compatibility)
status: { status: {
red: '#F63D68', red: '#F63D68',
green: '#3CCB7F', green: '#3CCB7F',
@@ -40,13 +76,14 @@ export default {
'orange-light': '#F38744', 'orange-light': '#F38744',
yellow: '#FEEE95', yellow: '#FEEE95',
}, },
// Base colors // Legacy Base colors (keep for compatibility)
base: { base: {
white: '#FFFFFF', white: '#FFFFFF',
}, },
}, },
fontFamily: { fontFamily: {
'inter': ['Inter Display', 'Inter', 'sans-serif'], 'inter': ['Inter', 'sans-serif'],
'neue-montreal': ['Neue Montreal', 'Inter', 'sans-serif'],
}, },
}, },
}, },

View File

@@ -2,12 +2,11 @@ import path from 'path';
import { defineConfig, loadEnv } from 'vite'; import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite' import tailwindcss from '@tailwindcss/vite'
import autoprefixer from 'autoprefixer';
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), ''); const env = loadEnv(mode, process.cwd(), '');
const API_URL = env.VITE_API_URL || 'http://localhost:5050'; const API_URL = env.VITE_API_URL || 'http://localhost:5173';
return { return {
plugins: [tailwindcss(), react()], plugins: [tailwindcss(), react()],