Files
auditly/components/figma/EnhancedFigmaQuestion.tsx
2025-08-20 12:24:56 -07:00

320 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { ReactNode, useState } from 'react';
import ImageUpload from '../ui/ImageUpload';
import { StoredImage, uploadCompanyLogo } from '../../services/imageStorageService';
interface FigmaQuestionProps {
question: string;
description?: string;
children: ReactNode;
onBack?: () => void;
onNext?: () => void;
nextDisabled?: boolean;
backDisabled?: boolean;
nextText?: string;
backText?: string;
showBackButton?: boolean;
currentStep?: number;
totalSteps?: number;
stepTitle?: string;
className?: string;
// Image upload props
orgId?: string;
onImageUploaded?: (image: StoredImage) => void;
currentImage?: StoredImage | null;
}
export const EnhancedFigmaQuestion: React.FC<FigmaQuestionProps> = ({
question,
description,
children,
onBack,
onNext,
nextDisabled = false,
backDisabled = false,
nextText = "Next",
backText = "Back",
showBackButton = true,
currentStep = 1,
totalSteps = 8,
stepTitle = "Company Overview & Mission.",
className = "",
// Image upload props
orgId,
onImageUploaded,
currentImage
}) => {
const [uploadingImage, setUploadingImage] = useState(false);
const [uploadError, setUploadError] = useState<string>('');
const handleImageSelected = async (file: File) => {
if (!orgId) {
setUploadError('Organization ID is required');
return;
}
setUploadingImage(true);
setUploadError('');
try {
const uploadedImage = await uploadCompanyLogo(file, orgId);
if (onImageUploaded) {
onImageUploaded(uploadedImage);
}
} catch (error) {
console.error('Failed to upload image:', error);
setUploadError(error instanceof Error ? error.message : 'Failed to upload image');
} finally {
setUploadingImage(false);
}
};
const handleImageRemove = () => {
// For now, just call the callback with null
// You could also implement deleteCompanyLogo here if needed
if (onImageUploaded) {
onImageUploaded(null as any);
}
};
// 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">
{currentStep === 1 ?
<div className="self-stretch 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-NeutralSlate950] text-sm font-normal font-['Inter'] leading-tight">Company Logo</div>
</div>
<div className="self-stretch p-4 rounded-3xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] inline-flex justify-start items-center gap-4">
<ImageUpload
onImageSelected={handleImageSelected}
onImageRemove={currentImage ? handleImageRemove : undefined}
currentImage={currentImage}
loading={uploadingImage}
error={uploadError}
size="medium"
/>
<div className="inline-flex flex-col justify-start items-start gap-4">
<div className="self-stretch inline-flex justify-start items-center gap-3">
<div className="flex justify-start items-center gap-2">
<div data-svg-wrapper className="relative">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H2M12 8.66667L8 4.66667M8 4.66667L4 8.66667M8 4.66667V14" stroke="var(--Neutrals-NeutralSlate950, #FDFDFD)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</div>
<div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">
{currentImage ? 'Change image' : 'Upload image'}
</div>
</div>
</div>
{uploadError && (
<div className="text-red-500 text-xs">{uploadError}</div>
)}
{currentImage && (
<div className="text-[--Neutrals-NeutralSlate500] text-xs">
{Math.round(currentImage.compressedSize / 1024)}KB {currentImage.width}×{currentImage.height}px
</div>
)}
</div>
</div>
</div>
:
<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-NeutralSlate50] 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-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 (
// <div className="w-full px-5 pt-5 pb-6 bg-white rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-grow flex-col gap-4">
// <div className="self-stretch inline-flex justify-start items-center gap-3">
// <div className="flex-1">
// {children}
// </div>
// </div>
// </div>
<div className="self-stretch justify-center flex items-center gap-3">
{children}
</div>
// <div className="w-full px-5 pt-5 pb-6 bg-white rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] inline-flex flex-col justify-end items-end gap-4">
// {/* <div className="self-stretch inline-flex justify-start items-start gap-3">
// <div className="justify-start text-zinc-300 text-xl font-medium font-['Inter'] leading-loose">Q</div>
// <div className="flex-1 inline-flex flex-col justify-start items-start gap-2">
// <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate950] text-xl font-semibold font-['Inter'] leading-loose">
// {question}
// </div>
// {description && (
// <div className="self-stretch justify-start text-[--Neutrals-NeutralSlate500] text-sm font-normal font-['Inter'] leading-tight">
// {description}
// </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">
// <div className="justify-start text-zinc-300 text-xl font-medium font-['Inter'] leading-loose">A</div>
// <div className="flex-1">
// {children}
// </div>
// </div>
// </div>
);
};
// 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-NeutralSlate50] rounded-xl inline-flex justify-start items-start gap-2.5 overflow-hidden";
const inputClasses = "flex self-stretch w-100 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>
);
}
return (
<div className={`${baseClasses} ${className}`}>
<input
value={value}
onChange={(e) => onChange?.(e.target.value)}
placeholder={placeholder}
className={inputClasses}
/>
</div>
);
};
export default EnhancedFigmaQuestion;