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
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:
32
Client-Registration/src/components/ProgressStepper.jsx
Normal file
32
Client-Registration/src/components/ProgressStepper.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
31
Client-Registration/src/components/Shell.jsx
Normal file
31
Client-Registration/src/components/Shell.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
172
Client-Registration/src/components/steps/BusinessStep.jsx
Normal file
172
Client-Registration/src/components/steps/BusinessStep.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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 1–2 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>
|
||||
);
|
||||
}
|
||||
99
Client-Registration/src/components/steps/ContactStep.jsx
Normal file
99
Client-Registration/src/components/steps/ContactStep.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
76
Client-Registration/src/components/steps/ReviewStep.jsx
Normal file
76
Client-Registration/src/components/steps/ReviewStep.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
54
Client-Registration/src/components/steps/SignInStep.jsx
Normal file
54
Client-Registration/src/components/steps/SignInStep.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user