320 lines
16 KiB
TypeScript
320 lines
16 KiB
TypeScript
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;
|