update onboarding colors and add image upload

This commit is contained in:
Ra
2025-08-20 11:20:28 -07:00
parent 875280cdac
commit 9332a48542
12 changed files with 2078 additions and 426 deletions

View File

@@ -1,4 +1,6 @@
import React, { ReactNode } from 'react';
import React, { ReactNode, useState } from 'react';
import ImageUpload from '../ui/ImageUpload';
import { StoredImage, uploadCompanyLogo } from '../../services/imageStorageService';
interface FigmaQuestionProps {
question: string;
@@ -15,6 +17,10 @@ interface FigmaQuestionProps {
totalSteps?: number;
stepTitle?: string;
className?: string;
// Image upload props
orgId?: string;
onImageUploaded?: (image: StoredImage) => void;
currentImage?: StoredImage | null;
}
export const EnhancedFigmaQuestion: React.FC<FigmaQuestionProps> = ({
@@ -31,8 +37,44 @@ export const EnhancedFigmaQuestion: React.FC<FigmaQuestionProps> = ({
currentStep = 1,
totalSteps = 8,
stepTitle = "Company Overview & Mission.",
className = ""
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 = [];
@@ -62,12 +104,52 @@ export const EnhancedFigmaQuestion: React.FC<FigmaQuestionProps> = ({
};
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-[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>
{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">
@@ -78,11 +160,11 @@ export const EnhancedFigmaQuestion: React.FC<FigmaQuestionProps> = ({
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'}`}
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">
<div className="justify-center text-[--Neutrals-NeutralSlate950] text-sm font-medium font-['Inter'] leading-tight">
{backText}
</div>
</div>
@@ -94,11 +176,11 @@ export const EnhancedFigmaQuestion: React.FC<FigmaQuestionProps> = ({
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'}`}
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">
<div className="justify-center text-white text-sm font-medium font-['Inter'] leading-tight">
{nextText}
</div>
</div>
@@ -107,25 +189,25 @@ export const EnhancedFigmaQuestion: React.FC<FigmaQuestionProps> = ({
</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">
<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">
<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">
<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">
<div className="self-stretch text-center justify-start text-[--Neutrals-NeutralSlate500] text-base font-medium font-['Neue_Montreal'] leading-normal">
{stepTitle}
</div>
</div>
@@ -148,21 +230,21 @@ export const FigmaQuestionCard: React.FC<FigmaQuestionCardProps> = ({
className = ""
}) => {
return (
<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}`}>
<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">
<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">
<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 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">
@@ -191,8 +273,8 @@ export const EnhancedFigmaInput: React.FC<EnhancedFigmaInputProps> = ({
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";
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 (
@@ -205,8 +287,8 @@ export const EnhancedFigmaInput: React.FC<EnhancedFigmaInputProps> = ({
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 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>
);

View File

@@ -31,17 +31,17 @@ export const FigmaInput: React.FC<FigmaInputProps> = ({
<div className={`self-stretch flex flex-col justify-start items-start gap-2 ${className}`}>
{showLabel && 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">
<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 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">
<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 && (
<div data-svg-wrapper className="relative">
{icon}
@@ -52,7 +52,7 @@ export const FigmaInput: React.FC<FigmaInputProps> = ({
value={value}
onChange={onChange}
placeholder={placeholder}
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"
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>
@@ -60,9 +60,9 @@ export const FigmaInput: React.FC<FigmaInputProps> = ({
{buttonText && (
<button
onClick={onButtonClick}
className="w-32 max-w-32 px-4 py-3.5 bg-Neutrals-NeutralSlate100 rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-Neutrals-NeutralSlate200 transition-colors"
className="w-32 max-w-32 px-4 py-3.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-[--Neutrals-NeutralSlate200] transition-colors"
>
<div className="justify-center text-Neutrals-NeutralSlate500 text-sm font-medium font-['Inter'] leading-tight">
<div className="justify-center text-[--Neutrals-NeutralSlate500] text-sm font-medium font-['Inter'] leading-tight">
{buttonText}
</div>
</button>
@@ -95,23 +95,23 @@ export const FigmaSelect: React.FC<FigmaSelectProps> = ({
<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">
<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 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">
<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"
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>
<option value="" className="text-[--Neutrals-NeutralSlate500]">{placeholder}</option>
{options.map((option, index) => (
<option key={index} value={option.value}>
{option.label}

View File

@@ -0,0 +1,181 @@
import React, { useState, useRef } from 'react';
import { StoredImage } from '../../services/imageStorageService';
interface ImageUploadProps {
onImageSelected: (file: File) => void;
onImageRemove?: () => void;
currentImage?: StoredImage | null;
loading?: boolean;
error?: string;
className?: string;
maxSizeMB?: number;
acceptedFormats?: string[];
size?: 'small' | 'medium' | 'large';
}
const ImageUpload: React.FC<ImageUploadProps> = ({
onImageSelected,
onImageRemove,
currentImage,
loading = false,
error,
className = '',
maxSizeMB = 10,
acceptedFormats = ['JPEG', 'PNG', 'GIF', 'WebP'],
size = 'medium'
}) => {
const [dragOver, setDragOver] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const sizeClasses = {
small: 'w-12 h-12',
medium: 'w-16 h-16',
large: 'w-24 h-24'
};
const handleFileSelect = (file: File) => {
// Basic validation
if (!file.type.startsWith('image/')) {
return;
}
if (file.size > maxSizeMB * 1024 * 1024) {
return;
}
onImageSelected(file);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
handleFileSelect(file);
}
// Reset input
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
setDragOver(true);
};
const handleDragLeave = (e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setDragOver(false);
const file = e.dataTransfer.files[0];
if (file) {
handleFileSelect(file);
}
};
const handleClick = () => {
if (!loading) {
fileInputRef.current?.click();
}
};
const handleRemove = (e: React.MouseEvent) => {
e.stopPropagation();
if (onImageRemove) {
onImageRemove();
}
};
return (
<div className={`relative ${className}`}>
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleInputChange}
className="hidden"
disabled={loading}
/>
<div
className={`
${sizeClasses[size]} relative rounded-[250px] overflow-hidden
cursor-pointer transition-all duration-200
${dragOver ? 'ring-2 ring-Brand-Orange ring-opacity-50' : ''}
${loading ? 'opacity-50 cursor-not-allowed' : 'hover:opacity-80'}
`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={handleClick}
>
{currentImage ? (
<>
<img
src={currentImage.dataUrl}
alt="Uploaded"
className="w-full h-full object-cover"
/>
{!loading && onImageRemove && (
<button
onClick={handleRemove}
className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white rounded-full flex items-center justify-center text-xs hover:bg-red-600 transition-colors"
title="Remove image"
>
×
</button>
)}
</>
) : (
<div className="w-full h-full bg-gray-200 flex items-center justify-center">
{loading ? (
<div className="animate-spin w-6 h-6 border-2 border-Brand-Orange border-t-transparent rounded-full" />
) : (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="text-gray-400"
>
<path
d="M12 16L12 8M12 8L8 12M12 8L16 12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4 16V20C4 20.5523 4.44772 21 5 21H19C19.5523 21 20 20.5523 20 20V16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</div>
)}
{dragOver && (
<div className="absolute inset-0 bg-Brand-Orange bg-opacity-20 flex items-center justify-center">
<span className="text-Brand-Orange text-xs font-medium">Drop image</span>
</div>
)}
</div>
{error && (
<div className="absolute top-full left-0 mt-1 text-xs text-red-500 whitespace-nowrap">
{error}
</div>
)}
</div>
);
};
export default ImageUpload;