Compare commits
18 Commits
dcb510e28c
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6e96bd61a | ||
|
|
7925b45d76 | ||
|
|
d8dda59f4c | ||
|
|
541251f065 | ||
|
|
981117629a | ||
|
|
ba3af87c70 | ||
|
|
d2c2b328b2 | ||
|
|
7e270c2a9e | ||
|
|
23fc92bfb6 | ||
|
|
8929eda2fa | ||
|
|
b9950e3316 | ||
|
|
732f81333b | ||
|
|
3a310c5d3f | ||
|
|
8b0e5ea2f5 | ||
|
|
8c37f3d624 | ||
|
|
245c85a08b | ||
|
|
6006834265 | ||
|
|
ef378b7cbf |
2
Client-Admin/dist/bundle.js
vendored
2
Client-Admin/dist/bundle.js
vendored
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@ const TENANT_ID = 'f56a3c51-9b5c-4356-920f-b4dcf932a96b'; // positivespend tenan
|
|||||||
|
|
||||||
// AdPlatform Admin SPA app registration — registered in positivespend tenant
|
// AdPlatform Admin SPA app registration — registered in positivespend tenant
|
||||||
// Portal → App Registrations → AdPlatform Admin SPA → Application (client) ID
|
// Portal → App Registrations → AdPlatform Admin SPA → Application (client) ID
|
||||||
const CLIENT_ID = '6873dee3-aff8-405d-9bc1-120c20794f98'; // TODO: replace after creating Admin SPA registration
|
const CLIENT_ID = '6873dee3-aff8-405d-9bc1-120c20794f98';
|
||||||
|
|
||||||
const AUTHORITY = `https://login.microsoftonline.com/${TENANT_ID}`;
|
const AUTHORITY = `https://login.microsoftonline.com/${TENANT_ID}`;
|
||||||
|
|
||||||
|
|||||||
2
Client-Home/dist/bundle.js
vendored
2
Client-Home/dist/bundle.js
vendored
File diff suppressed because one or more lines are too long
@@ -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/';
|
||||||
|
|||||||
2
Client-Registration/dist/bundle.js
vendored
2
Client-Registration/dist/bundle.js
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -13,7 +13,7 @@ import { msalConfig, loginRequest } from './authConfig';
|
|||||||
* isSignedIn — true when a user is authenticated
|
* isSignedIn — true when a user is authenticated
|
||||||
* user — { entraSubjectId, email, displayName, firstName, surname, provider }
|
* user — { entraSubjectId, email, displayName, firstName, surname, provider }
|
||||||
* error — string | null
|
* error — string | null
|
||||||
* signIn(hint) — initiates login popup for 'google' | 'apple' | 'microsoft'
|
* signIn(hint, isNew) — initiates login popup; hint = 'google'|'apple'|'microsoft', isNew=true forces sign-up screen
|
||||||
* signOut() — clears session
|
* signOut() — clears session
|
||||||
* getAccessToken()— returns a fresh ID token for authenticating API calls
|
* getAccessToken()— returns a fresh ID token for authenticating API calls
|
||||||
* clearError() — clears the error state
|
* clearError() — clears the error state
|
||||||
@@ -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',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -109,13 +114,16 @@ export function AuthProvider({ children }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ── Sign in ────────────────────────────────────────────────────────
|
// ── Sign in ────────────────────────────────────────────────────────
|
||||||
const signIn = useCallback(async (providerHint) => {
|
// isNewUser=true → prompt:'create' forces CIAM sign-up screen (Apply path)
|
||||||
|
// isNewUser=false → standard sign-in screen (Returning path)
|
||||||
|
const signIn = useCallback(async (providerHint, isNewUser = false) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const hint = PROVIDER_HINTS[providerHint];
|
const hint = PROVIDER_HINTS[providerHint];
|
||||||
const request = {
|
const request = {
|
||||||
...loginRequest,
|
...loginRequest,
|
||||||
|
...(isNewUser && { prompt: 'create' }),
|
||||||
...(hint && { extraQueryParameters: { identity_provider_hint: hint } }),
|
...(hint && { extraQueryParameters: { identity_provider_hint: hint } }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
/**
|
/**
|
||||||
* MSAL Configuration for Entra External ID (Customer Identity)
|
* MSAL Configuration for Entra External ID (Customer Identity / CIAM)
|
||||||
*
|
*
|
||||||
* This uses the External ID tenant (CIAM) — different from the
|
* Tenant: Positive Spend Clients
|
||||||
* internal Entra tenant used by the Admin/Management console.
|
* Domain: positiveclients.onmicrosoft.com
|
||||||
*
|
* Tenant ID: cbf8b7d7-1e13-486d-b5b0-287ba79fdf0b
|
||||||
* TODO: Replace placeholder values with actual External ID tenant details.
|
* SPA App: AdPlatform Client SPA
|
||||||
|
* Client ID: 43c493e4-e1ed-4cd7-ab0a-e507e20af724
|
||||||
*/
|
*/
|
||||||
export const msalConfig = {
|
export const msalConfig = {
|
||||||
auth: {
|
auth: {
|
||||||
clientId: '154c9111-14a0-4c0f-8132-7bc68254a74e',
|
clientId: '43c493e4-e1ed-4cd7-ab0a-e507e20af724',
|
||||||
authority: 'https://usimclients.ciamlogin.com/891f98f1-ed34-42a1-9b6c-28b0554d92c2',
|
authority: 'https://positiveclients.ciamlogin.com/positiveclients.onmicrosoft.com',
|
||||||
redirectUri: window.location.origin,
|
redirectUri: 'https://register.positivespend.com',
|
||||||
postLogoutRedirectUri: window.location.origin,
|
postLogoutRedirectUri: 'https://register.positivespend.com',
|
||||||
knownAuthorities: ['usimclients.ciamlogin.com'],
|
knownAuthorities: ['positiveclients.ciamlogin.com'],
|
||||||
},
|
},
|
||||||
cache: {
|
cache: {
|
||||||
cacheLocation: 'sessionStorage',
|
cacheLocation: 'sessionStorage',
|
||||||
storeAuthStateInCookie: false,
|
storeAuthStateInCookie: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -24,9 +25,5 @@ export const loginRequest = {
|
|||||||
scopes: ['openid', 'profile', 'email'],
|
scopes: ['openid', 'profile', 'email'],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Registration Function API
|
export const API_BASE_URL = 'https://portal.positivespend.com';
|
||||||
export const API_BASE_URL = 'https://adpregapi.usimdev.com';
|
export const API_FUNCTION_KEY = '';
|
||||||
|
|
||||||
// Function key for Registration API (AuthorizationLevel.Function)
|
|
||||||
// TODO: Set this from your Azure Function → App Keys → default host key
|
|
||||||
export const API_FUNCTION_KEY = '';
|
|
||||||
@@ -6,12 +6,13 @@ 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 || '',
|
||||||
contactEmail: prev.contactEmail || user.email || '',
|
lastName: prev.lastName || user.surname || '',
|
||||||
|
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,17 +50,32 @@ 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-group">
|
<div className="form-row">
|
||||||
<label htmlFor="contactName">Full Name <span className="required">*</span></label>
|
<div className="form-group">
|
||||||
<input
|
<label htmlFor="firstName">First Name <span className="required">*</span></label>
|
||||||
type="text"
|
<input
|
||||||
id="contactName"
|
type="text"
|
||||||
name="contactName"
|
id="firstName"
|
||||||
value={contactData.contactName}
|
name="firstName"
|
||||||
onChange={handleChange}
|
value={contactData.firstName || ''}
|
||||||
placeholder="Jane Smith"
|
onChange={handleChange}
|
||||||
required
|
placeholder="Jane"
|
||||||
/>
|
required
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
@@ -66,7 +84,7 @@ export default function ContactStep() {
|
|||||||
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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,53 +1,130 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useAuth } from '../../auth/AuthProvider';
|
import { useAuth } from '../../auth/AuthProvider';
|
||||||
|
|
||||||
export default function SignInStep() {
|
export default function SignInStep() {
|
||||||
const { signIn, isLoading, error } = useAuth();
|
const { signIn, isLoading, error } = useAuth();
|
||||||
|
const [mode, setMode] = useState(null); // null | 'new' | 'returning'
|
||||||
|
|
||||||
|
const providers = (isNew) => (
|
||||||
|
<div className="provider-list">
|
||||||
|
<button
|
||||||
|
className="btn-provider btn-provider-google"
|
||||||
|
onClick={() => signIn('google', isNew)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<span className="provider-icon">G</span>
|
||||||
|
{isLoading ? 'Connecting...' : 'Continue with Google'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn-provider btn-provider-apple"
|
||||||
|
onClick={() => signIn('apple', isNew)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<span className="provider-icon">🍎</span>
|
||||||
|
{isLoading ? 'Connecting...' : 'Continue with Apple'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn-provider btn-provider-microsoft"
|
||||||
|
onClick={() => signIn('microsoft', isNew)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
<span className="provider-icon">⊞</span>
|
||||||
|
{isLoading ? 'Connecting...' : 'Continue with Microsoft'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Landing — user hasn't chosen yet
|
||||||
|
if (!mode) {
|
||||||
|
return (
|
||||||
|
<div className="step-card">
|
||||||
|
<div className="step-header">
|
||||||
|
<span className="step-icon">🔷</span>
|
||||||
|
<h2>Welcome to AdPlatform</h2>
|
||||||
|
<p className="step-description">
|
||||||
|
What would you like to do?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <div className="error-message">{error}</div>}
|
||||||
|
|
||||||
|
<div className="path-choice">
|
||||||
|
<button
|
||||||
|
className="btn-path btn-path-primary"
|
||||||
|
onClick={() => setMode('new')}
|
||||||
|
>
|
||||||
|
<span className="path-icon">✨</span>
|
||||||
|
<div className="path-text">
|
||||||
|
<strong>Apply for Access</strong>
|
||||||
|
<span>New to AdPlatform? Start your registration here.</span>
|
||||||
|
</div>
|
||||||
|
<span className="path-arrow">→</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="btn-path btn-path-secondary"
|
||||||
|
onClick={() => setMode('returning')}
|
||||||
|
>
|
||||||
|
<span className="path-icon">🔄</span>
|
||||||
|
<div className="path-text">
|
||||||
|
<strong>Check My Application</strong>
|
||||||
|
<span>Already applied? Sign in to view your status.</span>
|
||||||
|
</div>
|
||||||
|
<span className="path-arrow">→</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New applicant — sign up path
|
||||||
|
if (mode === 'new') {
|
||||||
|
return (
|
||||||
|
<div className="step-card">
|
||||||
|
<button className="btn-back" onClick={() => setMode(null)}>← Back</button>
|
||||||
|
<div className="step-header">
|
||||||
|
<span className="step-icon">✨</span>
|
||||||
|
<h2>Create Your Account</h2>
|
||||||
|
<p className="step-description">
|
||||||
|
Choose how you'd like to sign up. We'll create your AdPlatform identity
|
||||||
|
and walk you through the application.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && <div className="error-message">{error}</div>}
|
||||||
|
|
||||||
|
{providers(true)}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returning applicant — sign in path
|
||||||
return (
|
return (
|
||||||
<div className="step-card">
|
<div className="step-card">
|
||||||
|
<button className="btn-back" onClick={() => setMode(null)}>← Back</button>
|
||||||
<div className="step-header">
|
<div className="step-header">
|
||||||
<span className="step-icon">🔐</span>
|
<span className="step-icon">🔄</span>
|
||||||
<h2>Create Your Account</h2>
|
<h2>Welcome Back</h2>
|
||||||
<p className="step-description">
|
<p className="step-description">
|
||||||
Sign in with your preferred provider to get started with AdPlatform.
|
Sign in with the same account you used when you applied.
|
||||||
|
We'll pick up right where you left off.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && <div className="error-message">{error}</div>}
|
{error && <div className="error-message">{error}</div>}
|
||||||
|
|
||||||
<div className="provider-list">
|
{providers(false)}
|
||||||
<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">
|
<p className="step-fine-print">
|
||||||
By continuing, you agree to AdPlatform's Terms of Service and Privacy Policy.
|
Use the same provider you signed up with. If you need help, contact
|
||||||
Your account will be managed through Microsoft Entra External ID.
|
support@positivespend.com.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -288,6 +288,33 @@ body {
|
|||||||
.btn-provider-microsoft { background: #0078d4; color: #fff; border-color: #0078d4; }
|
.btn-provider-microsoft { background: #0078d4; color: #fff; border-color: #0078d4; }
|
||||||
.btn-provider-microsoft:hover { background: #006abc; border-color: #006abc; }
|
.btn-provider-microsoft:hover { background: #006abc; border-color: #006abc; }
|
||||||
|
|
||||||
|
/* Two-path landing */
|
||||||
|
.path-choice { display: flex; flex-direction: column; gap: 12px; margin-top: 8px; }
|
||||||
|
|
||||||
|
.btn-path {
|
||||||
|
display: flex; align-items: center; gap: 16px;
|
||||||
|
padding: 18px 20px; border-radius: 12px; border: 2px solid var(--border);
|
||||||
|
background: #fff; cursor: pointer; text-align: left;
|
||||||
|
transition: border-color 0.15s, box-shadow 0.15s;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.btn-path:hover { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-light); }
|
||||||
|
.btn-path-primary:hover { border-color: #0078d4; box-shadow: 0 0 0 3px rgba(0,120,212,0.1); }
|
||||||
|
|
||||||
|
.path-icon { font-size: 24px; flex-shrink: 0; }
|
||||||
|
.path-text { flex: 1; display: flex; flex-direction: column; gap: 2px; }
|
||||||
|
.path-text strong { font-size: 15px; color: var(--text); font-weight: 600; }
|
||||||
|
.path-text span { font-size: 13px; color: var(--text-muted); }
|
||||||
|
.path-arrow { font-size: 18px; color: var(--text-muted); flex-shrink: 0; }
|
||||||
|
|
||||||
|
.btn-back {
|
||||||
|
display: inline-flex; align-items: center; gap: 4px;
|
||||||
|
background: none; border: none; cursor: pointer;
|
||||||
|
color: var(--text-muted); font-size: 14px; padding: 0 0 16px 0;
|
||||||
|
transition: color 0.15s;
|
||||||
|
}
|
||||||
|
.btn-back:hover { color: var(--text); }
|
||||||
|
|
||||||
.provider-icon {
|
.provider-icon {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
@@ -299,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;
|
||||||
}
|
}
|
||||||
|
|||||||
2
Client-Tech/dist/bundle.js
vendored
2
Client-Tech/dist/bundle.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,33 +1,17 @@
|
|||||||
/**
|
/**
|
||||||
* authConfig.js - Tech Client (Staff Plane)
|
* authConfig.js — Tech Client (Staff Plane)
|
||||||
*
|
|
||||||
* APP REGISTRATION MAP (positivespend tenant: f56a3c51-9b5c-4356-920f-b4dcf932a96b)
|
|
||||||
* -------------------------------------------------------------------------
|
|
||||||
* Tech SPA (this app) 217928a9-4591-4dff-9f09-5b233824cf4f
|
|
||||||
* - Platform: SPA
|
|
||||||
* - Redirect URI: <Tech deployment origin> - must be registered in portal,
|
|
||||||
* matches window.location.origin at runtime.
|
|
||||||
* - API permissions: api://af95fa13-.../access_as_user (delegated)
|
|
||||||
*
|
|
||||||
* Management Staff API af95fa13-2ef4-4911-b137-7acc6a784cfa
|
|
||||||
* - Exposes scope: access_as_user
|
|
||||||
* - App roles: Staff.Admin, Staff.Tech
|
|
||||||
* - Management validates JWTs issued for this audience
|
|
||||||
*
|
|
||||||
* FLOW: MSAL authenticates as 217928a9, acquires a token scoped to
|
|
||||||
* api://af95fa13-.../access_as_user, sends as Bearer to Management API.
|
|
||||||
* Management validates: issuer = login.microsoftonline.com/f56a3c51/v2.0,
|
|
||||||
* audience = af95fa13 or api://af95fa13, roles = Staff.Admin | Staff.Tech.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ── Staff Identity Config ─────────────────────────────────────────────────────
|
// ── Staff Identity Config ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
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 = {
|
||||||
@@ -36,10 +20,10 @@ 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',
|
||||||
storeAuthStateInCookie: false,
|
storeAuthStateInCookie: false,
|
||||||
},
|
},
|
||||||
system: {
|
system: {
|
||||||
@@ -53,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://af95fa13-2ef4-4911-b137-7acc6a784cfa/access_as_user"]
|
scopes: [`api://${MGMT_APP_ID}/access_as_user`] // ← fixed
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── API Endpoints ─────────────────────────────────────────────────────────────
|
// ── API Endpoints ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export const API_BASE = 'https://portal.positivespend.com'; // Gateway API
|
export const API_BASE = 'https://portal.positivespend.com'; // ← fixed
|
||||||
export const MGMT_BASE = 'https://mgmt.positivespend.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`;
|
|
||||||
Reference in New Issue
Block a user