first commit
This commit is contained in:
360
src/app/onboarding/page.tsx
Normal file
360
src/app/onboarding/page.tsx
Normal file
@@ -0,0 +1,360 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import styles from "./page.module.css";
|
||||
import { indianLocations } from "@/data/indianLocations";
|
||||
import { businessTypes } from "@/data/businessTypes";
|
||||
|
||||
type NotificationType = 'success' | 'error' | 'info';
|
||||
interface Notification {
|
||||
id: number;
|
||||
message: string;
|
||||
type: NotificationType;
|
||||
}
|
||||
|
||||
export default function Onboarding() {
|
||||
const router = useRouter();
|
||||
const [view, setView] = useState<'selection' | 'create' | 'join'>('selection');
|
||||
|
||||
// Create Company State
|
||||
const [companyName, setCompanyName] = useState("");
|
||||
const [businessType, setBusinessType] = useState("");
|
||||
const [industry, setIndustry] = useState("");
|
||||
const [size, setSize] = useState("");
|
||||
const [city, setCity] = useState("");
|
||||
const [state, setState] = useState("");
|
||||
|
||||
// Compliance State
|
||||
const [complianceType, setComplianceType] = useState("");
|
||||
const [complianceId, setComplianceId] = useState("");
|
||||
|
||||
// Derived Data
|
||||
// Filter cities based on selected state if available, else show all or empty
|
||||
// const availableCities = indianLocations.find(s => s.name === state)?.cities || [];
|
||||
|
||||
// Join Company State
|
||||
const [inviteCode, setInviteCode] = useState("");
|
||||
|
||||
// Notifications
|
||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||
|
||||
const showNotification = (message: string, type: NotificationType = 'info') => {
|
||||
const id = Date.now();
|
||||
setNotifications(prev => [...prev, { id, message, type }]);
|
||||
setTimeout(() => {
|
||||
setNotifications(prev => prev.filter(n => n.id !== id));
|
||||
}, 4000);
|
||||
};
|
||||
|
||||
const handleCreateSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// Simulate API call
|
||||
if (!companyName || !businessType || !industry || !size || !state || !city) {
|
||||
showNotification("Please fill in all required fields.", 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showNotification("Creating your workspace...", 'success');
|
||||
|
||||
// Mock success redirect
|
||||
setTimeout(() => {
|
||||
// Ideally redirect to dashboard, for now just show success
|
||||
console.log("Company Created:", {
|
||||
companyName,
|
||||
businessType,
|
||||
industry,
|
||||
size,
|
||||
city,
|
||||
state,
|
||||
compliance: complianceType ? { type: complianceType, id: complianceId } : null,
|
||||
country: "India",
|
||||
currency: "INR"
|
||||
});
|
||||
// Redirect to dashboard
|
||||
router.push('/dashboard');
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const handleJoinSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!inviteCode) {
|
||||
showNotification("Please enter an invite code.", 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
showNotification("Verifying invitation...", 'info');
|
||||
|
||||
setTimeout(() => {
|
||||
showNotification("Successfully joined company!", 'success');
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
|
||||
{/* Notification Toast (Reused logic) */}
|
||||
<div style={{ position: 'fixed', top: '20px', right: '20px', zIndex: 1000, display: 'flex', flexDirection: 'column', gap: '10px' }}>
|
||||
{notifications.map(n => (
|
||||
<div key={n.id} style={{
|
||||
background: n.type === 'success' ? 'rgba(0, 200, 100, 0.9)' : n.type === 'error' ? 'rgba(255, 80, 80, 0.9)' : 'rgba(0, 150, 255, 0.9)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
padding: '12px 20px',
|
||||
borderRadius: '8px',
|
||||
color: 'white',
|
||||
boxShadow: '0 5px 15px rgba(0,0,0,0.2)',
|
||||
minWidth: '250px',
|
||||
fontSize: '0.9rem',
|
||||
animation: 'fadeIn 0.3s ease'
|
||||
}}>
|
||||
{n.message}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className={styles.onboardingCard}>
|
||||
|
||||
{/* Header with Logo */}
|
||||
<div className={styles.header}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1rem' }}>
|
||||
<Image
|
||||
src="/logo_symbol.png"
|
||||
alt="Pennyzo"
|
||||
width={80}
|
||||
height={80}
|
||||
style={{ width: 'auto', height: '64px', objectFit: 'contain' }}
|
||||
/>
|
||||
</div>
|
||||
<h1 className={styles.title}>
|
||||
{view === 'selection' && "Welcome, Founder"}
|
||||
{view === 'create' && "Establish Entity"}
|
||||
{view === 'join' && "Join Structure"}
|
||||
</h1>
|
||||
<p className={styles.subtitle}>
|
||||
{view === 'selection' && "Choose your path to begin using the Business 360 Platform."}
|
||||
{view === 'create' && "Register a new business entity to manage operations, finance, and teams."}
|
||||
{view === 'join' && "Enter your invitation code provided by your organization administrator."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* VIEW 1: SELECTION */}
|
||||
{view === 'selection' && (
|
||||
<div className={styles.selectionContainer}>
|
||||
|
||||
{/* Option: Create Company */}
|
||||
<div className={styles.optionCard} onClick={() => setView('create')}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<span>+</span>
|
||||
</div>
|
||||
<h3 className={styles.optionTitle}>Create Company</h3>
|
||||
<p className={styles.optionDesc}>
|
||||
Register a new business organization. You will be assigned the <strong>Founder</strong> role with full administrative controls.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Option: Join Company */}
|
||||
<div className={` ${styles.optionCard} ${styles.join} `} onClick={() => setView('join')}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<span>→</span>
|
||||
</div>
|
||||
<h3 className={styles.optionTitle}>Join Existing</h3>
|
||||
<p className={styles.optionDesc}>
|
||||
Join a team using an invitation code or request access. You will be assigned a role based on the invite.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* VIEW 2: CREATE COMPANY FORM */}
|
||||
{view === 'create' && (
|
||||
<div className={styles.formContainer}>
|
||||
<form onSubmit={handleCreateSubmit}>
|
||||
<div className={styles.formGrid}>
|
||||
{/* Company Name */}
|
||||
<div className={styles.formGroup} style={{ gridColumn: 'span 2' }}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
placeholder=" "
|
||||
value={companyName}
|
||||
onChange={e => setCompanyName(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<label className={styles.label}>Company/Trade Name</label>
|
||||
</div>
|
||||
|
||||
{/* Business Type */}
|
||||
<div className={styles.formGroup}>
|
||||
<select
|
||||
className={styles.select}
|
||||
value={businessType}
|
||||
onChange={e => setBusinessType(e.target.value)}
|
||||
required
|
||||
>
|
||||
<option value="" disabled>Select Business Type</option>
|
||||
{businessTypes.map(bt => (
|
||||
<option key={bt.id} value={bt.id}>{bt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className={styles.label} style={{ top: '4px', fontSize: '0.7rem', color: 'var(--color-gold)' }}>Legal Structure</label>
|
||||
</div>
|
||||
|
||||
{/* Industry */}
|
||||
<div className={styles.formGroup}>
|
||||
<select
|
||||
className={styles.select}
|
||||
value={industry}
|
||||
onChange={e => setIndustry(e.target.value)}
|
||||
required
|
||||
>
|
||||
<option value="" disabled>Select Industry</option>
|
||||
<option value="tech">Technology & SaaS</option>
|
||||
<option value="finance">Finance & Banking</option>
|
||||
<option value="retail">Retail & E-commerce</option>
|
||||
<option value="healthcare">Healthcare</option>
|
||||
<option value="manufacturing">Manufacturing</option>
|
||||
<option value="consulting">Consulting & Services</option>
|
||||
<option value="education">Education & EdTech</option>
|
||||
<option value="construction">Real Estate & Construction</option>
|
||||
</select>
|
||||
<label className={styles.label} style={{ top: '4px', fontSize: '0.7rem', color: 'var(--color-gold)' }}>Industry Sector</label>
|
||||
</div>
|
||||
|
||||
{/* Company Size */}
|
||||
<div className={styles.formGroup}>
|
||||
<select
|
||||
className={styles.select}
|
||||
value={size}
|
||||
onChange={e => setSize(e.target.value)}
|
||||
required
|
||||
>
|
||||
<option value="" disabled>Employee Count</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-500">201 - 500 Employees</option>
|
||||
<option value="500+">500+ Employees</option>
|
||||
</select>
|
||||
<label className={styles.label} style={{ top: '4px', fontSize: '0.7rem', color: 'var(--color-gold)' }}>Business Size</label>
|
||||
</div>
|
||||
|
||||
{/* State Selection */}
|
||||
<div className={styles.formGroup}>
|
||||
<select
|
||||
className={styles.select}
|
||||
value={state}
|
||||
onChange={e => {
|
||||
setState(e.target.value);
|
||||
setCity(""); // Reset city on state change
|
||||
}}
|
||||
required
|
||||
>
|
||||
<option value="" disabled>Select State</option>
|
||||
{indianLocations.map((s, i) => (
|
||||
<option key={i} value={s.name}>{s.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className={styles.label} style={{ top: '4px', fontSize: '0.7rem', color: 'var(--color-gold)' }}>State</label>
|
||||
</div>
|
||||
|
||||
{/* City Selection (Dependent) */}
|
||||
<div className={styles.formGroup}>
|
||||
<select
|
||||
className={styles.select}
|
||||
value={city}
|
||||
onChange={e => setCity(e.target.value)}
|
||||
required
|
||||
disabled={!state}
|
||||
>
|
||||
<option value="" disabled>Select City</option>
|
||||
{indianLocations.find(s => s.name === state)?.cities.map((c: string, i: number) => (
|
||||
<option key={i} value={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className={styles.label} style={{ top: '4px', fontSize: '0.7rem', color: 'var(--color-gold)' }}>City</label>
|
||||
</div>
|
||||
|
||||
{/* Country (Locked) */}
|
||||
<div className={styles.formGroup}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value="India"
|
||||
readOnly
|
||||
style={{ opacity: 0.7, cursor: 'not-allowed', textAlign: 'center' }}
|
||||
/>
|
||||
<label className={styles.label} style={{ top: '4px', fontSize: '0.7rem', color: 'var(--color-gold)' }}>Country (Locked)</label>
|
||||
</div>
|
||||
|
||||
{/* Compliance Section (Optional) */}
|
||||
<div className={styles.formGroup}>
|
||||
<select
|
||||
className={styles.select}
|
||||
value={complianceType}
|
||||
onChange={e => {
|
||||
setComplianceType(e.target.value);
|
||||
setComplianceId(""); // Reset ID on type change
|
||||
}}
|
||||
>
|
||||
<option value="">No Compliance (Optional)</option>
|
||||
<option value="msme">MSME Udyam</option>
|
||||
<option value="gstin">GSTIN</option>
|
||||
<option value="cin">CIN</option>
|
||||
</select>
|
||||
<label className={styles.label} style={{ top: '4px', fontSize: '0.7rem', color: 'var(--color-gold)' }}>Compliance Type</label>
|
||||
</div>
|
||||
|
||||
<div className={styles.formGroup}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
placeholder=" "
|
||||
value={complianceId}
|
||||
onChange={e => setComplianceId(e.target.value)}
|
||||
disabled={!complianceType}
|
||||
style={{ opacity: !complianceType ? 0.5 : 1, cursor: !complianceType ? 'not-allowed' : 'text' }}
|
||||
/>
|
||||
<label className={styles.label}>
|
||||
{complianceType ? 'Registration Number' : 'ID Number'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<button type="button" className={styles.backBtn} onClick={() => setView('selection')}>Back</button>
|
||||
<button type="submit" className={styles.submitBtn}>Register Entity</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* VIEW 3: JOIN COMPANY */}
|
||||
{view === 'join' && (
|
||||
<div className={styles.formContainer}>
|
||||
<form onSubmit={handleJoinSubmit}>
|
||||
<div className={styles.formGroup} style={{ marginBottom: '2rem' }}>
|
||||
<input
|
||||
type="text"
|
||||
className={styles.input}
|
||||
placeholder=" "
|
||||
value={inviteCode}
|
||||
onChange={e => setInviteCode(e.target.value)}
|
||||
style={{ letterSpacing: '2px', textAlign: 'center', fontSize: '1.2rem' }}
|
||||
required
|
||||
/>
|
||||
<label className={styles.label} style={{ left: '50%', transform: 'translateX(-50%)' }}>Enter Invitation Code</label>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions} style={{ justifyContent: 'center' }}>
|
||||
<button type="button" className={styles.backBtn} onClick={() => setView('selection')}>Back</button>
|
||||
<button type="submit" className={styles.submitBtn}>Verify & Join</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user