Compare commits

..

4 Commits

Author SHA1 Message Date
Grae Jones
a6e96bd61a home urls
All checks were successful
Client Home / build-deploy (push) Successful in 7s
2026-03-25 10:28:12 -07:00
Grae Jones
7925b45d76 Rewg Fix 2
All checks were successful
Client Tech / build-deploy (push) Successful in 11s
2026-03-23 14:10:25 -07:00
Grae Jones
d8dda59f4c reg fix 1
All checks were successful
Client Registration / build-deploy (push) Successful in 10s
2026-03-23 14:01:15 -07:00
Grae Jones
541251f065 agin... 2026-03-23 13:59:56 -07:00
9 changed files with 78 additions and 55 deletions

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
// - APP_URL: the URL that should open the *app* (direct entry / dashboard). // - APP_URL: the URL that should open the *app* (direct entry / dashboard).
// - REGISTRATION_URL: the URL to your external registration experience. // - REGISTRATION_URL: the URL to your external registration experience.
// //
// Tip: keep these as full absolute URLs. // Tip: keep these as full absolute
export const APP_URL = 'https://adpclient.usimdev.com/'; export const APP_URL = 'https://client.positivespend.com/';
export const REGISTRATION_URL = 'https://adpregist.usimdev.com/'; export const REGISTRATION_URL = 'https://register.positivespend.com/';

File diff suppressed because one or more lines are too long

View File

@@ -41,12 +41,17 @@ export function useAuth() {
// ── Map ID token claims → user object ───────────────────────────────────── // ── Map ID token claims → user object ─────────────────────────────────────
function claimsToUser(claims, provider) { function claimsToUser(claims, provider) {
const firstName = claims.given_name ?? null;
const surname = claims.family_name ?? null;
const displayName = claims.name
?? [firstName, surname].filter(Boolean).join(' ')
?? null;
return { return {
entraSubjectId: claims.oid ?? claims.sub ?? null, entraSubjectId: claims.oid ?? claims.sub ?? null,
email: claims.email ?? claims.preferred_username ?? null, email: claims.email ?? claims.preferred_username ?? null,
displayName: claims.name ?? null, displayName,
firstName: claims.given_name ?? null, firstName,
surname: claims.family_name ?? null, surname,
provider: provider ?? 'unknown', provider: provider ?? 'unknown',
}; };
} }

View File

@@ -6,11 +6,12 @@ export default function ContactStep() {
const { user } = useAuth(); const { user } = useAuth();
const { contactData, setContactData, saveContact, goBack, loading, error } = useRegistration(); const { contactData, setContactData, saveContact, goBack, loading, error } = useRegistration();
// Pre-fill from auth claims on mount // Pre-fill from CIAM claims on mount
useEffect(() => { useEffect(() => {
if (user) { if (user) {
setContactData(prev => ({ setContactData(prev => ({
contactName: prev.contactName || user.displayName || '', firstName: prev.firstName || user.firstName || '',
lastName: prev.lastName || user.surname || '',
contactEmail: prev.contactEmail || user.email || '', contactEmail: prev.contactEmail || user.email || '',
contactPhone: prev.contactPhone || '', contactPhone: prev.contactPhone || '',
})); }));
@@ -39,7 +40,9 @@ export default function ContactStep() {
{user && ( {user && (
<div className="info-card"> <div className="info-card">
<div className="info-card-primary">{user.displayName}</div> <div className="info-card-primary">
{[user.firstName, user.surname].filter(Boolean).join(' ') || user.displayName || user.email}
</div>
<div className="info-card-secondary">{user.email}</div> <div className="info-card-secondary">{user.email}</div>
</div> </div>
)} )}
@@ -47,26 +50,41 @@ export default function ContactStep() {
{error && <div className="error-message">{error}</div>} {error && <div className="error-message">{error}</div>}
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="form-row">
<div className="form-group"> <div className="form-group">
<label htmlFor="contactName">Full Name <span className="required">*</span></label> <label htmlFor="firstName">First Name <span className="required">*</span></label>
<input <input
type="text" type="text"
id="contactName" id="firstName"
name="contactName" name="firstName"
value={contactData.contactName} value={contactData.firstName || ''}
onChange={handleChange} onChange={handleChange}
placeholder="Jane Smith" placeholder="Jane"
required required
/> />
</div> </div>
<div className="form-group">
<label htmlFor="lastName">Last Name <span className="required">*</span></label>
<input
type="text"
id="lastName"
name="lastName"
value={contactData.lastName || ''}
onChange={handleChange}
placeholder="Smith"
required
/>
</div>
</div>
<div className="form-group"> <div className="form-group">
<label htmlFor="contactEmail">Email <span className="required">*</span></label> <label htmlFor="contactEmail">Email <span className="required">*</span></label>
<input <input
type="email" type="email"
id="contactEmail" id="contactEmail"
name="contactEmail" name="contactEmail"
value={contactData.contactEmail} value={contactData.contactEmail || ''}
onChange={handleChange} onChange={handleChange}
placeholder="jane@example.com" placeholder="jane@example.com"
required required
@@ -79,7 +97,7 @@ export default function ContactStep() {
type="tel" type="tel"
id="contactPhone" id="contactPhone"
name="contactPhone" name="contactPhone"
value={contactData.contactPhone} value={contactData.contactPhone || ''}
onChange={handleChange} onChange={handleChange}
placeholder="(555) 123-4567" placeholder="(555) 123-4567"
/> />

View File

@@ -53,7 +53,8 @@ export function RegistrationProvider({ children }) {
// Form data — maps to RegisterRequest model fields (minus entraSubjectId) // Form data — maps to RegisterRequest model fields (minus entraSubjectId)
const [contactData, setContactData] = useState({ const [contactData, setContactData] = useState({
contactName: '', firstName: '',
lastName: '',
contactEmail: '', contactEmail: '',
contactPhone: '', contactPhone: '',
}); });
@@ -83,8 +84,9 @@ export function RegistrationProvider({ children }) {
// ─── Validation ───────────────────────────────────────────────────── // ─── Validation ─────────────────────────────────────────────────────
const validateContact = useCallback(() => { const validateContact = useCallback(() => {
if (!contactData.contactName.trim()) return 'Contact name is required'; if (!contactData.firstName?.trim()) return 'First name is required';
if (!contactData.contactEmail.trim()) return 'Email is required'; if (!contactData.lastName?.trim()) return 'Last name is required';
if (!contactData.contactEmail?.trim()) return 'Email is required';
return null; return null;
}, [contactData]); }, [contactData]);
@@ -132,6 +134,8 @@ export function RegistrationProvider({ children }) {
const request = { const request = {
...contactData, ...contactData,
...businessData, ...businessData,
// Combine for server-side contactName field
contactName: [contactData.firstName, contactData.lastName].filter(Boolean).join(' '),
}; };
// Pre-fill email from auth if the user left it blank // Pre-fill email from auth if the user left it blank

View File

@@ -326,6 +326,16 @@ body {
Forms Forms
============================================================ */ ============================================================ */
.form-row {
display: flex;
gap: 12px;
}
.form-row .form-group {
flex: 1;
margin-bottom: 0;
}
.form-group { .form-group {
margin-bottom: 16px; margin-bottom: 16px;
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,20 +1,5 @@
/** /**
* authConfig.js — Tech Client (Staff Plane) * authConfig.js — Tech Client (Staff Plane)
*
* ┌─────────────────────────────────────────────────────────────────────────┐
* │ PRODUCTION MIGRATION — only these values change at handoff: │
* │ │
* │ STAFF_AUTHORITY → 'https://login.microsoftonline.com/{ORG_TENANT}' │
* │ STAFF_TENANT_ID → new company org tenant ID │
* │ STAFF_CLIENT_ID → staff app registration in org tenant │
* │ │
* │ No other code changes required anywhere. │
* └─────────────────────────────────────────────────────────────────────────┘
*
* DEV NOTE: Staff currently authenticate against the CIAM tenant (same as
* clients) because no org tenant exists yet. The login screen looks identical
* to the client login — this is cosmetic only. API isolation is enforced by
* audience: staff tokens are rejected by Gateway, client tokens by Management.
*/ */
// ── Staff Identity Config ───────────────────────────────────────────────────── // ── Staff Identity Config ─────────────────────────────────────────────────────
@@ -22,9 +7,11 @@
const STAFF_TENANT_ID = 'f56a3c51-9b5c-4356-920f-b4dcf932a96b'; const STAFF_TENANT_ID = 'f56a3c51-9b5c-4356-920f-b4dcf932a96b';
const STAFF_CLIENT_ID = '217928a9-4591-4dff-9f09-5b233824cf4f'; const STAFF_CLIENT_ID = '217928a9-4591-4dff-9f09-5b233824cf4f';
// PROD: swap to → 'https://login.microsoftonline.com/' + STAFF_TENANT_ID
const STAFF_AUTHORITY = 'https://login.microsoftonline.com/' + STAFF_TENANT_ID; const STAFF_AUTHORITY = 'https://login.microsoftonline.com/' + STAFF_TENANT_ID;
// Management Staff API — resource the Tech SPA requests a token for
const MGMT_APP_ID = 'af95fa13-2ef4-4911-b137-7acc6a784cfa';
// ── MSAL Config ─────────────────────────────────────────────────────────────── // ── MSAL Config ───────────────────────────────────────────────────────────────
export const msalConfig = { export const msalConfig = {
@@ -33,7 +20,7 @@ export const msalConfig = {
authority: STAFF_AUTHORITY, authority: STAFF_AUTHORITY,
redirectUri: window.location.origin, redirectUri: window.location.origin,
postLogoutRedirectUri: window.location.origin, postLogoutRedirectUri: window.location.origin,
navigateToLoginRequestUrl: true, navigateToLoginRequestUrl: false, // ← was true, caused the loop
}, },
cache: { cache: {
cacheLocation: 'sessionStorage', cacheLocation: 'sessionStorage',
@@ -50,19 +37,18 @@ export const msalConfig = {
case 3: console.debug(message); break; case 3: console.debug(message); break;
} }
}, },
logLevel: 3, logLevel: 1, // warn + error only in prod
}, },
}, },
}; };
export const loginRequest = { export const loginRequest = {
scopes: ["api://4e4d69c3-558a-4a27-a689-17bd397175e5/access_as_user"] scopes: [`api://${MGMT_APP_ID}/access_as_user`] // ← fixed
}; };
// ── API Endpoints ───────────────────────────────────────────────────────────── // ── API Endpoints ─────────────────────────────────────────────────────────────
export const API_BASE = 'https://adpapi.usimdev.com'; // Gateway API export const API_BASE = 'https://portal.positivespend.com'; // ← fixed
export const MGMT_BASE = 'https://adpmgmt.usimdev.com'; // Management API export const MGMT_BASE = 'https://mgmt.positivespend.com'; // ← fixed
// Legacy — kept for backward compatibility with apiClient.js
export const SESSION_ENDPOINT = `${API_BASE}/api/auth/session`; export const SESSION_ENDPOINT = `${API_BASE}/api/auth/session`;