First build
Some checks failed
Client Admin / build-deploy (push) Failing after 8s
Client Client / build-deploy (push) Failing after 3s
Client Registration / build-deploy (push) Failing after 20s
Client Tech / build-deploy (push) Failing after 1s
Client Home / build-deploy (push) Successful in 14s

This commit is contained in:
Grae Jones
2026-03-21 17:54:42 -07:00
parent 3647b304a3
commit fdb3e117a9
203 changed files with 35733 additions and 18189 deletions

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { STEPS } from '../context/RegistrationContext';
export default function ProgressStepper({ currentStep }) {
return (
<div className="stepper">
{STEPS.map((step, idx) => {
const isComplete = idx < currentStep;
const isCurrent = idx === currentStep;
const stepClass = [
'stepper-item',
isComplete ? 'stepper-complete' : '',
isCurrent ? 'stepper-active' : '',
].filter(Boolean).join(' ');
return (
<React.Fragment key={step.key}>
<div className={stepClass}>
<div className="stepper-circle">
{isComplete ? '✓' : idx + 1}
</div>
<span className="stepper-label">{step.label}</span>
</div>
{idx < STEPS.length - 1 && (
<div className={`stepper-line ${isComplete ? 'stepper-line-complete' : ''}`} />
)}
</React.Fragment>
);
})}
</div>
);
}

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { useAuth } from '../auth/AuthProvider';
export default function Shell({ children }) {
const { isSignedIn, user, signOut } = useAuth();
return (
<div className="shell">
<header className="shell-header">
<div className="shell-logo">
<span className="logo-icon"></span>
<span className="logo-text">AdPlatform</span>
<span className="logo-badge">Registration</span>
</div>
{isSignedIn && user && (
<div className="shell-user">
<span className="user-name">{user.displayName || user.email}</span>
<button onClick={signOut} className="btn-signout">Sign Out</button>
</div>
)}
</header>
<main className="shell-content">{children}</main>
<footer className="shell-footer">
<span>AdPlatform Registration Portal v1.0</span>
</footer>
</div>
);
}

View File

@@ -0,0 +1,172 @@
import React, { useCallback } from 'react';
import { useRegistration } from '../../context/RegistrationContext';
// Business industry categories (from tbBusinessCategory)
const INDUSTRY_CATEGORIES = [
{ value: 'retail', label: 'Retail & E-Commerce' },
{ value: 'technology', label: 'Technology' },
{ value: 'healthcare', label: 'Healthcare' },
{ value: 'finance', label: 'Finance & Insurance' },
{ value: 'real_estate', label: 'Real Estate' },
{ value: 'food_beverage', label: 'Restaurant & Food Service' },
{ value: 'professional', label: 'Professional Services' },
{ value: 'home_services', label: 'Home Services' },
{ value: 'education', label: 'Education' },
{ value: 'entertainment', label: 'Entertainment & Media' },
{ value: 'automotive', label: 'Automotive' },
{ value: 'travel', label: 'Travel & Hospitality' },
{ value: 'other', label: 'Other' },
];
// Account type options — map to tbClientCategory (General/Franchisee/Franchisor)
const ACCOUNT_TYPES = [
{
value: 'General',
icon: '🏢',
label: 'Independent Business',
description: 'A standalone business running its own advertising campaigns.',
},
{
value: 'Franchisee',
icon: '🏪',
label: 'Franchisee',
description: 'You operate one or more franchise locations under a brand.',
},
{
value: 'Franchisor',
icon: '🏗️',
label: 'Franchisor / Brand',
description: 'You manage advertising across a network of franchise locations.',
},
];
export default function BusinessStep() {
const { businessData, setBusinessData, saveBusiness, goBack, loading, error } = useRegistration();
const handleChange = useCallback((e) => {
const { name, value } = e.target;
setBusinessData(prev => ({ ...prev, [name]: value }));
}, [setBusinessData]);
const handleAccountType = useCallback((value) => {
setBusinessData(prev => ({ ...prev, clientCategory: value }));
}, [setBusinessData]);
const handleSubmit = useCallback((e) => {
e.preventDefault();
saveBusiness();
}, [saveBusiness]);
return (
<div className="step-card">
<div className="step-header">
<span className="step-icon">🏢</span>
<h2>Business Information</h2>
<p className="step-description">
Tell us about your business so we can set up your advertising accounts.
</p>
</div>
{error && <div className="error-message">{error}</div>}
<form onSubmit={handleSubmit}>
{/* ── Account Type ─────────────────────────────────── */}
<div className="form-group">
<label>Account Type <span className="required">*</span></label>
<span className="form-help" style={{ display: 'block', marginBottom: '10px' }}>
How best describes your business structure?
</span>
<div className="account-type-list">
{ACCOUNT_TYPES.map(type => (
<button
key={type.value}
type="button"
className={`account-type-option${businessData.clientCategory === type.value ? ' selected' : ''}`}
onClick={() => handleAccountType(type.value)}
>
<span className="account-type-icon">{type.icon}</span>
<div className="account-type-text">
<span className="account-type-label">{type.label}</span>
<span className="account-type-desc">{type.description}</span>
</div>
<span className="account-type-check">
{businessData.clientCategory === type.value ? '✓' : ''}
</span>
</button>
))}
</div>
</div>
{/* ── Business Name ────────────────────────────────── */}
<div className="form-group">
<label htmlFor="businessName">Business Name <span className="required">*</span></label>
<input
type="text"
id="businessName"
name="businessName"
value={businessData.businessName}
onChange={handleChange}
placeholder="Acme Corp"
required
/>
<span className="form-help">This name will appear on your advertising accounts</span>
</div>
{/* ── Website ──────────────────────────────────────── */}
<div className="form-group">
<label htmlFor="websiteUrl">Website</label>
<input
type="url"
id="websiteUrl"
name="websiteUrl"
value={businessData.websiteUrl}
onChange={handleChange}
placeholder="https://example.com"
/>
</div>
{/* ── Industry ─────────────────────────────────────── */}
<div className="form-group">
<label htmlFor="businessCategory">Industry / Category</label>
<select
id="businessCategory"
name="businessCategory"
value={businessData.businessCategory}
onChange={handleChange}
>
<option value="">Select a category...</option>
{INDUSTRY_CATEGORIES.map(cat => (
<option key={cat.value} value={cat.value}>{cat.label}</option>
))}
</select>
</div>
{/* ── Description ──────────────────────────────────── */}
<div className="form-group">
<label htmlFor="businessDescription">About Your Business</label>
<textarea
id="businessDescription"
name="businessDescription"
value={businessData.businessDescription}
onChange={handleChange}
rows={4}
placeholder="Tell us about your business, your advertising goals, and your target audience..."
/>
<span className="form-help">
This helps our team understand your needs and set up the right campaigns
</span>
</div>
<div className="step-actions">
<button type="button" className="btn-secondary" onClick={goBack}>
Back
</button>
<button type="submit" className="btn-primary" disabled={loading}>
{loading ? 'Saving...' : 'Continue →'}
</button>
</div>
</form>
</div>
);
}

View File

@@ -0,0 +1,72 @@
import React from 'react';
import { useAuth } from '../../auth/AuthProvider';
import { useRegistration } from '../../context/RegistrationContext';
export default function ConfirmationStep() {
const { user } = useAuth();
const { businessData, registrationId } = useRegistration();
return (
<div className="step-card">
<div className="confirmation-hero">
<div className="confirmation-check"></div>
<h2>Application Submitted!</h2>
<p className="step-description">
Thank you{businessData.businessName ? `, ${businessData.businessName}` : ''}.
Your application is now under review.
We'll notify you at <strong>{user?.email}</strong> once approved.
</p>
</div>
{registrationId && (
<div className="info-card">
<div className="info-card-secondary">Registration ID</div>
<div className="info-card-primary"><code>{registrationId}</code></div>
</div>
)}
<div className="next-steps">
<h3 className="next-steps-title">What happens next?</h3>
<div className="next-steps-list">
<div className="next-step-item">
<div className="next-step-icon">🔍</div>
<div>
<div className="next-step-label">Application Review</div>
<div className="next-step-desc">
Our team reviews your information (typically 12 business days)
</div>
</div>
</div>
<div className="next-step-item">
<div className="next-step-icon">🏗️</div>
<div>
<div className="next-step-label">Account Creation</div>
<div className="next-step-desc">
Upon approval, we create your accounts on Google Ads, Meta, and TikTok
</div>
</div>
</div>
<div className="next-step-item">
<div className="next-step-icon">🚀</div>
<div>
<div className="next-step-label">Get Started</div>
<div className="next-step-desc">
You'll receive login credentials and can begin creating campaigns
</div>
</div>
</div>
</div>
</div>
<div className="info-banner info-banner-highlight">
<strong>Account naming convention:</strong> Your advertising accounts will be
created as{' '}
<code>ADP-{businessData.businessName.replace(/\s+/g, '') || 'YourBusiness'}-XXXX</code>{' '}
across all platforms for easy identification.
</div>
</div>
);
}

View File

@@ -0,0 +1,99 @@
import React, { useCallback, useEffect } from 'react';
import { useAuth } from '../../auth/AuthProvider';
import { useRegistration } from '../../context/RegistrationContext';
export default function ContactStep() {
const { user } = useAuth();
const { contactData, setContactData, saveContact, goBack, loading, error } = useRegistration();
// Pre-fill from auth claims on mount
useEffect(() => {
if (user) {
setContactData(prev => ({
contactName: prev.contactName || user.displayName || '',
contactEmail: prev.contactEmail || user.email || '',
contactPhone: prev.contactPhone || '',
}));
}
}, [user, setContactData]);
const handleChange = useCallback((e) => {
const { name, value } = e.target;
setContactData(prev => ({ ...prev, [name]: value }));
}, [setContactData]);
const handleSubmit = useCallback((e) => {
e.preventDefault();
saveContact();
}, [saveContact]);
return (
<div className="step-card">
<div className="step-header">
<span className="step-icon">👤</span>
<h2>Contact Information</h2>
<p className="step-description">
How should we reach you about your advertising account?
</p>
</div>
{user && (
<div className="info-card">
<div className="info-card-primary">{user.displayName}</div>
<div className="info-card-secondary">{user.email}</div>
</div>
)}
{error && <div className="error-message">{error}</div>}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="contactName">Full Name <span className="required">*</span></label>
<input
type="text"
id="contactName"
name="contactName"
value={contactData.contactName}
onChange={handleChange}
placeholder="Jane Smith"
required
/>
</div>
<div className="form-group">
<label htmlFor="contactEmail">Email <span className="required">*</span></label>
<input
type="email"
id="contactEmail"
name="contactEmail"
value={contactData.contactEmail}
onChange={handleChange}
placeholder="jane@example.com"
required
/>
</div>
<div className="form-group">
<label htmlFor="contactPhone">Phone</label>
<input
type="tel"
id="contactPhone"
name="contactPhone"
value={contactData.contactPhone}
onChange={handleChange}
placeholder="(555) 123-4567"
/>
</div>
<div className="step-actions">
<button type="button" className="btn-secondary" onClick={goBack}>
Back
</button>
<button type="submit" className="btn-primary" disabled={loading}>
{loading ? 'Saving...' : 'Continue →'}
</button>
</div>
</form>
</div>
);
}

View File

@@ -0,0 +1,76 @@
import React from 'react';
import { useAuth } from '../../auth/AuthProvider';
import { useRegistration } from '../../context/RegistrationContext';
function ReviewSection({ title, items }) {
const filtered = items.filter(([, value]) => value);
if (filtered.length === 0) return null;
return (
<div className="review-section">
<h3 className="review-section-title">{title}</h3>
<div className="review-section-body">
{filtered.map(([label, value]) => (
<div key={label} className="review-row">
<span className="review-label">{label}</span>
<span className="review-value">{value}</span>
</div>
))}
</div>
</div>
);
}
export default function ReviewStep() {
const { user } = useAuth();
const { contactData, businessData, submitApplication, goBack, loading, error } = useRegistration();
return (
<div className="step-card">
<div className="step-header">
<span className="step-icon">📋</span>
<h2>Review Your Application</h2>
<p className="step-description">
Please verify all information before submitting.
</p>
</div>
{error && <div className="error-message">{error}</div>}
<ReviewSection title="Account" items={[
['Signed in as', user?.displayName],
['Email', user?.email],
['Provider', user?.provider],
]} />
<ReviewSection title="Contact" items={[
['Name', contactData.contactName],
['Email', contactData.contactEmail],
['Phone', contactData.contactPhone],
]} />
<ReviewSection title="Business" items={[
['Business Name', businessData.businessName],
['Website', businessData.websiteUrl],
['Category', businessData.businessCategory],
['Description', businessData.businessDescription],
]} />
<div className="info-banner">
<strong>What happens next?</strong> After submission, our team will review your
application. Upon approval, your advertising accounts will be created across
Google Ads, Meta, and TikTok with the naming convention{' '}
<code>ADP-{businessData.businessName.replace(/\s+/g, '') || 'YourBusiness'}-XXXX</code>.
</div>
<div className="step-actions">
<button className="btn-secondary" onClick={goBack}>
Back
</button>
<button className="btn-primary" onClick={submitApplication} disabled={loading}>
{loading ? 'Submitting...' : 'Submit Application ✓'}
</button>
</div>
</div>
);
}

View File

@@ -0,0 +1,54 @@
import React from 'react';
import { useAuth } from '../../auth/AuthProvider';
export default function SignInStep() {
const { signIn, isLoading, error } = useAuth();
return (
<div className="step-card">
<div className="step-header">
<span className="step-icon">🔐</span>
<h2>Create Your Account</h2>
<p className="step-description">
Sign in with your preferred provider to get started with AdPlatform.
</p>
</div>
{error && <div className="error-message">{error}</div>}
<div className="provider-list">
<button
className="btn-provider btn-provider-google"
onClick={() => signIn('google')}
disabled={isLoading}
>
<span className="provider-icon">G</span>
{isLoading ? 'Connecting...' : 'Continue with Google'}
</button>
<button
className="btn-provider btn-provider-apple"
onClick={() => signIn('apple')}
disabled={isLoading}
>
<span className="provider-icon">🍎</span>
{isLoading ? 'Connecting...' : 'Continue with Apple'}
</button>
<button
className="btn-provider btn-provider-microsoft"
onClick={() => signIn('microsoft')}
disabled={isLoading}
>
<span className="provider-icon"></span>
{isLoading ? 'Connecting...' : 'Continue with Microsoft'}
</button>
</div>
<p className="step-fine-print">
By continuing, you agree to AdPlatform's Terms of Service and Privacy Policy.
Your account will be managed through Microsoft Entra External ID.
</p>
</div>
);
}