Compare commits
86
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06e2c3ee6a | ||
|
|
3c0fd8844c | ||
|
|
b7c135f0fc | ||
|
|
db053e3e5f | ||
|
|
d7177e1a72 | ||
|
|
c67041faea | ||
|
|
0e8a5e1169 | ||
|
|
41fdb067fe | ||
|
|
b4b687dfcc | ||
|
|
f200ccbf60 | ||
|
|
27f57150e1 | ||
|
|
79c63107e9 | ||
|
|
d945731252 | ||
|
|
c979a4632e | ||
|
|
9eed3cff6b | ||
|
|
6db1a2f367 | ||
|
|
06d9952cd7 | ||
|
|
92d481f75d | ||
|
|
1c36602995 | ||
|
|
5ae0c500ac | ||
|
|
db04f800e9 | ||
|
|
e7a19611f0 | ||
|
|
5ad24da7cf | ||
|
|
156dae40b7 | ||
|
|
1b67d9cf8b | ||
|
|
1bd05e64e5 | ||
|
|
303dba3c6f | ||
|
|
1dd4f54a1d | ||
|
|
fae1e080c4 | ||
|
|
4de23d27b3 | ||
|
|
d4bce12dbb | ||
|
|
299b876e1c | ||
|
|
dde1dbe056 | ||
|
|
b81e6feb45 | ||
|
|
4138a1941f | ||
|
|
17af31d810 | ||
|
|
b85cf6a1cb | ||
|
|
81ddc11746 | ||
|
|
5b94672ff5 | ||
|
|
c535392ef1 | ||
|
|
0607de1802 | ||
|
|
4c1e52e452 | ||
|
|
2414d74d1c | ||
|
|
93d9661a7f | ||
|
|
dba401fe94 | ||
|
|
42d73fe6cb | ||
|
|
1475887a7f | ||
|
|
730664d9c4 | ||
|
|
f38e569a34 | ||
|
|
7e79171f97 | ||
|
|
d7b868f2c1 | ||
|
|
d43a09b371 | ||
|
|
0dc0de1502 | ||
|
|
de47dd4a4f | ||
|
|
2810cc669b | ||
|
|
734af8c86f | ||
|
|
a754c42d14 | ||
|
|
bd7ca9372f | ||
|
|
0d01d91ab4 | ||
|
|
2c817a8818 | ||
|
|
4c3a96917b | ||
|
|
e5c3a75795 | ||
|
|
1bc7e65960 | ||
|
|
094126d84b | ||
|
|
2506241a25 | ||
|
|
cfe3530639 | ||
|
|
985557bee4 | ||
|
|
f0b8b3051a | ||
|
|
6c88b9e533 | ||
|
|
7d67408d2c | ||
|
|
d1d58a6183 | ||
|
|
60ad154841 | ||
|
|
2c13170859 | ||
|
|
d87c8aa3c9 | ||
|
|
6af7815ad1 | ||
|
|
a169f6c3f6 | ||
|
|
65ea753c19 | ||
|
|
e6d2663265 | ||
|
|
91b5814b60 | ||
|
|
0c4c848568 | ||
|
|
fb05150ebf | ||
|
|
2f9c20020b | ||
|
|
a771957db3 | ||
|
|
46267a2a34 | ||
|
|
a7f360cc2a | ||
|
|
67c08b40ca |
@@ -14,7 +14,6 @@ import {
|
|||||||
} from '../services/userSyncService';
|
} from '../services/userSyncService';
|
||||||
import { AccountActivationModal } from '../components/auth/AccountActivationModal';
|
import { AccountActivationModal } from '../components/auth/AccountActivationModal';
|
||||||
import { SessionExpiredModal } from '../components/auth/SessionExpiredModal';
|
import { SessionExpiredModal } from '../components/auth/SessionExpiredModal';
|
||||||
import { encodeHashIdSync } from '../services/toolsService';
|
|
||||||
import {
|
import {
|
||||||
onSessionExpired,
|
onSessionExpired,
|
||||||
triggerSessionExpired,
|
triggerSessionExpired,
|
||||||
@@ -31,7 +30,6 @@ export interface AuthUserProfile {
|
|||||||
role: string;
|
role: string;
|
||||||
organisationName?: string;
|
organisationName?: string;
|
||||||
tenant?: string;
|
tenant?: string;
|
||||||
tenant_id?: number;
|
|
||||||
avatarUrl?: string;
|
avatarUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,8 +39,6 @@ interface AuthContextType {
|
|||||||
user: AuthUserProfile | null;
|
user: AuthUserProfile | null;
|
||||||
sub: string;
|
sub: string;
|
||||||
claims: UserClaims;
|
claims: UserClaims;
|
||||||
tenantId: number | null;
|
|
||||||
hashedTenantId: string | null;
|
|
||||||
accessToken: string | null;
|
accessToken: string | null;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@@ -64,75 +60,6 @@ export const LOCAL_STORAGE_USER_KEY = 'pfm_auth_user';
|
|||||||
export const LOCAL_STORAGE_SUB_KEY = 'pfm_user_sub';
|
export const LOCAL_STORAGE_SUB_KEY = 'pfm_user_sub';
|
||||||
export const LOCAL_STORAGE_CLAIMS_KEY = 'pfm_user_claims';
|
export const LOCAL_STORAGE_CLAIMS_KEY = 'pfm_user_claims';
|
||||||
|
|
||||||
export function extractTenantId(claims?: UserClaims | null, token?: string | null): number | null {
|
|
||||||
if (claims) {
|
|
||||||
const raw =
|
|
||||||
claims.tenant_id ??
|
|
||||||
(claims as any).tenantId ??
|
|
||||||
claims['tenant_id'] ??
|
|
||||||
claims['http://schemas.microsoft.com/ws/2008/06/identity/claims/tenantid'];
|
|
||||||
if (raw !== undefined && raw !== null && raw !== '') {
|
|
||||||
const parsed = Number(raw);
|
|
||||||
if (!isNaN(parsed) && isFinite(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (token) {
|
|
||||||
try {
|
|
||||||
const tokenClaims = parseJwtClaims(token);
|
|
||||||
const raw = tokenClaims.tenant_id ?? tokenClaims.tenantId ?? tokenClaims['tenant_id'];
|
|
||||||
if (raw !== undefined && raw !== null && raw !== '') {
|
|
||||||
const parsed = Number(raw);
|
|
||||||
if (!isNaN(parsed) && isFinite(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
try {
|
|
||||||
const storedClaimsRaw = localStorage.getItem(LOCAL_STORAGE_CLAIMS_KEY);
|
|
||||||
if (storedClaimsRaw) {
|
|
||||||
const parsedClaims = JSON.parse(storedClaimsRaw);
|
|
||||||
const raw = parsedClaims.tenant_id ?? parsedClaims.tenantId ?? parsedClaims['tenant_id'];
|
|
||||||
if (raw !== undefined && raw !== null && raw !== '') {
|
|
||||||
const parsed = Number(raw);
|
|
||||||
if (!isNaN(parsed) && isFinite(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const storedToken = localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY);
|
|
||||||
if (storedToken) {
|
|
||||||
const tokenClaims = parseJwtClaims(storedToken);
|
|
||||||
const raw = tokenClaims.tenant_id ?? tokenClaims.tenantId ?? tokenClaims['tenant_id'];
|
|
||||||
if (raw !== undefined && raw !== null && raw !== '') {
|
|
||||||
const parsed = Number(raw);
|
|
||||||
if (!isNaN(parsed) && isFinite(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const storedUser = localStorage.getItem(LOCAL_STORAGE_USER_KEY);
|
|
||||||
if (storedUser) {
|
|
||||||
const parsedUser = JSON.parse(storedUser);
|
|
||||||
const raw = parsedUser.tenant_id ?? parsedUser.tenantId;
|
|
||||||
if (raw !== undefined && raw !== null && raw !== '') {
|
|
||||||
const parsed = Number(raw);
|
|
||||||
if (!isNaN(parsed) && isFinite(parsed)) return parsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentUserTenantId(): number | null {
|
|
||||||
return extractTenantId(
|
|
||||||
getCurrentUserClaims(),
|
|
||||||
typeof window !== 'undefined' ? localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY) : null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentUserHashedTenantId(): string | null {
|
|
||||||
const tid = getCurrentUserTenantId();
|
|
||||||
if (tid === null || tid === undefined) return null;
|
|
||||||
return encodeHashIdSync(tid);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCurrentUserSub(): string {
|
export function getCurrentUserSub(): string {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
return localStorage.getItem(LOCAL_STORAGE_SUB_KEY) || '';
|
return localStorage.getItem(LOCAL_STORAGE_SUB_KEY) || '';
|
||||||
@@ -192,12 +119,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
const [user, setUser] = useState<AuthUserProfile | null>(null);
|
const [user, setUser] = useState<AuthUserProfile | null>(null);
|
||||||
const [sub, setSub] = useState<string>('');
|
const [sub, setSub] = useState<string>('');
|
||||||
const [claims, setClaims] = useState<UserClaims>({});
|
const [claims, setClaims] = useState<UserClaims>({});
|
||||||
const [tenantId, setTenantId] = useState<number | null>(() => {
|
|
||||||
return extractTenantId(
|
|
||||||
getCurrentUserClaims(),
|
|
||||||
typeof window !== 'undefined' ? localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY) : null
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
const [isSynced, setIsSynced] = useState<boolean>(() => isUserSyncedInBrowser());
|
const [isSynced, setIsSynced] = useState<boolean>(() => isUserSyncedInBrowser());
|
||||||
@@ -207,8 +128,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
const [isSessionExpired, setIsSessionExpired] = useState<boolean>(false);
|
const [isSessionExpired, setIsSessionExpired] = useState<boolean>(false);
|
||||||
const [sessionExpiredReason, setSessionExpiredReason] = useState<string | null>(null);
|
const [sessionExpiredReason, setSessionExpiredReason] = useState<string | null>(null);
|
||||||
|
|
||||||
const hashedTenantId = tenantId !== null && tenantId !== undefined ? encodeHashIdSync(tenantId) : null;
|
|
||||||
|
|
||||||
// Subscribe to global HTTP 401 session expiration events
|
// Subscribe to global HTTP 401 session expiration events
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = onSessionExpired((reason) => {
|
const unsubscribe = onSessionExpired((reason) => {
|
||||||
@@ -284,8 +203,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
localStorage.setItem(LOCAL_STORAGE_CLAIMS_KEY, JSON.stringify(mergedClaims));
|
localStorage.setItem(LOCAL_STORAGE_CLAIMS_KEY, JSON.stringify(mergedClaims));
|
||||||
|
|
||||||
const tenantClaim = (mergedClaims.tenant as string) || (oidcUser.profile?.tenant as string) || undefined;
|
const tenantClaim = (mergedClaims.tenant as string) || (oidcUser.profile?.tenant as string) || undefined;
|
||||||
const parsedTenantId = extractTenantId(mergedClaims, token);
|
|
||||||
setTenantId(parsedTenantId);
|
|
||||||
|
|
||||||
const profile: AuthUserProfile = {
|
const profile: AuthUserProfile = {
|
||||||
id: userSub || 'user-1',
|
id: userSub || 'user-1',
|
||||||
@@ -295,7 +212,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
role: (oidcUser.profile.role as string) || (mergedClaims.role as string) || (Array.isArray(mergedClaims.roles) ? mergedClaims.roles[0] : 'Programme Sponsor'),
|
role: (oidcUser.profile.role as string) || (mergedClaims.role as string) || (Array.isArray(mergedClaims.roles) ? mergedClaims.roles[0] : 'Programme Sponsor'),
|
||||||
organisationName: (mergedClaims.organisation as string) || (mergedClaims.org as string) || undefined,
|
organisationName: (mergedClaims.organisation as string) || (mergedClaims.org as string) || undefined,
|
||||||
tenant: tenantClaim,
|
tenant: tenantClaim,
|
||||||
tenant_id: parsedTenantId ?? undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setUser(profile);
|
setUser(profile);
|
||||||
@@ -365,9 +281,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
setSub(storedSub);
|
setSub(storedSub);
|
||||||
}
|
}
|
||||||
|
|
||||||
const storedTenantId = extractTenantId(parsedClaims, storedToken);
|
|
||||||
setTenantId(storedTenantId);
|
|
||||||
|
|
||||||
let resolvedUser: AuthUserProfile | null = null;
|
let resolvedUser: AuthUserProfile | null = null;
|
||||||
if (storedUser) {
|
if (storedUser) {
|
||||||
try {
|
try {
|
||||||
@@ -375,9 +288,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
if (!parsedUser.tenant && parsedClaims.tenant) {
|
if (!parsedUser.tenant && parsedClaims.tenant) {
|
||||||
parsedUser.tenant = parsedClaims.tenant;
|
parsedUser.tenant = parsedClaims.tenant;
|
||||||
}
|
}
|
||||||
if (parsedUser.tenant_id === undefined && storedTenantId !== null) {
|
|
||||||
parsedUser.tenant_id = storedTenantId;
|
|
||||||
}
|
|
||||||
setUser(parsedUser);
|
setUser(parsedUser);
|
||||||
resolvedUser = parsedUser;
|
resolvedUser = parsedUser;
|
||||||
} catch {}
|
} catch {}
|
||||||
@@ -492,7 +402,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
setUser(null);
|
setUser(null);
|
||||||
setSub('');
|
setSub('');
|
||||||
setClaims({});
|
setClaims({});
|
||||||
setTenantId(null);
|
|
||||||
setAccessToken(null);
|
setAccessToken(null);
|
||||||
setIsSynced(false);
|
setIsSynced(false);
|
||||||
setIsPendingActivation(false);
|
setIsPendingActivation(false);
|
||||||
@@ -515,8 +424,6 @@ export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
|
|||||||
user,
|
user,
|
||||||
sub,
|
sub,
|
||||||
claims,
|
claims,
|
||||||
tenantId,
|
|
||||||
hashedTenantId,
|
|
||||||
accessToken,
|
accessToken,
|
||||||
isAuthenticated: !!accessToken,
|
isAuthenticated: !!accessToken,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const navItems = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const AppLayout: React.FC = () => {
|
export const AppLayout: React.FC = () => {
|
||||||
const { user, claims, sub, tenantId, hashedTenantId, logout } = useAuth();
|
const { user, claims, sub, logout } = useAuth();
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(true);
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||||
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
const [userMenuOpen, setUserMenuOpen] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -219,25 +219,15 @@ export const AppLayout: React.FC = () => {
|
|||||||
<div className="hidden sm:flex items-center gap-2 text-xs font-medium text-slate-300">
|
<div className="hidden sm:flex items-center gap-2 text-xs font-medium text-slate-300">
|
||||||
<span className="text-slate-400">Tenant:</span>
|
<span className="text-slate-400">Tenant:</span>
|
||||||
<span
|
<span
|
||||||
className="bg-slate-900/80 text-slate-200 px-2.5 py-1 rounded-lg border border-slate-800 flex items-center gap-1.5 font-medium shadow-xs max-w-[320px] truncate"
|
className="bg-slate-900/80 text-slate-200 px-2.5 py-1 rounded-lg border border-slate-800 flex items-center gap-1.5 font-medium shadow-xs max-w-[280px] truncate"
|
||||||
title={`${tenantName}${hashedTenantId ? ` (Partition: ${hashedTenantId})` : ''}`}
|
title={tenantName}
|
||||||
>
|
>
|
||||||
<Database className="w-3.5 h-3.5 text-blue-400 shrink-0" />
|
<Database className="w-3.5 h-3.5 text-blue-400 shrink-0" />
|
||||||
<span className="truncate">{tenantName}</span>
|
<span className="truncate">{tenantName}</span>
|
||||||
{hashedTenantId && (
|
|
||||||
<span
|
|
||||||
className="text-[10px] font-mono text-blue-400 bg-blue-950/60 px-1.5 py-0.5 rounded border border-blue-800/60 shrink-0"
|
|
||||||
title={`Tenant Partition HashID: ${hashedTenantId}`}
|
|
||||||
>
|
|
||||||
{hashedTenantId}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
<HelpTooltip
|
<HelpTooltip
|
||||||
title="Tenant Context"
|
title="Tenant Context"
|
||||||
content={`Identifies the multi-tenant organization partition and database boundary within the national PFM environment.${
|
content="Identifies the multi-tenant organization partition and database boundary within the national PFM environment."
|
||||||
hashedTenantId ? ` Tenant Partition (HashID): ${hashedTenantId}` : ''
|
|
||||||
}`}
|
|
||||||
size="sm"
|
size="sm"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
import axios, { AxiosError } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import { apiClient } from './httpClient';
|
import { apiClient } from './httpClient';
|
||||||
import { toolsService } from './toolsService';
|
import { toolsService } from './toolsService';
|
||||||
import {
|
import { getCurrentUserSub } from '../auth/AuthProvider';
|
||||||
getCurrentUserSub,
|
|
||||||
getCurrentUserTenantId,
|
|
||||||
getCurrentUserHashedTenantId,
|
|
||||||
} from '../auth/AuthProvider';
|
|
||||||
|
|
||||||
export { getCurrentUserTenantId, getCurrentUserHashedTenantId };
|
|
||||||
import {
|
import {
|
||||||
Dashboard,
|
Dashboard,
|
||||||
Organisation,
|
Organisation,
|
||||||
@@ -150,16 +144,8 @@ export const getOrganisations = async (page = 1, pageSize = 100): Promise<Organi
|
|||||||
return list.map(normalizeOrganisation);
|
return list.map(normalizeOrganisation);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getOrganisation = async (organisationId?: string | number): Promise<Organisation> => {
|
export const getOrganisation = async (organisationId: string | number): Promise<Organisation> => {
|
||||||
const targetOrgId =
|
const hashedOrgId = await toolsService.resolveOrganisationId(organisationId);
|
||||||
organisationId !== undefined && organisationId !== '' && organisationId !== null
|
|
||||||
? organisationId
|
|
||||||
: getCurrentUserTenantId();
|
|
||||||
|
|
||||||
if (targetOrgId === undefined || targetOrgId === null || targetOrgId === '') {
|
|
||||||
throw new Error('Organisation ID is required');
|
|
||||||
}
|
|
||||||
const hashedOrgId = await toolsService.resolveOrganisationId(targetOrgId);
|
|
||||||
const res = await apiClient.get<any>(`/v1/api/governance/organisations/${hashedOrgId}`);
|
const res = await apiClient.get<any>(`/v1/api/governance/organisations/${hashedOrgId}`);
|
||||||
return normalizeOrganisation(res.data);
|
return normalizeOrganisation(res.data);
|
||||||
};
|
};
|
||||||
@@ -185,16 +171,8 @@ export const updateOrganisation = async (payload: UpdateOrganisationRequest): Pr
|
|||||||
return res.data;
|
return res.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPortfolios = async (organisationId?: string | number): Promise<Portfolio[]> => {
|
export const getPortfolios = async (organisationId: string | number): Promise<Portfolio[]> => {
|
||||||
const targetOrgId =
|
const hashedOrgId = await toolsService.resolveOrganisationId(organisationId);
|
||||||
organisationId !== undefined && organisationId !== '' && organisationId !== null
|
|
||||||
? organisationId
|
|
||||||
: getCurrentUserTenantId();
|
|
||||||
|
|
||||||
if (targetOrgId === undefined || targetOrgId === null || targetOrgId === '') {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const hashedOrgId = await toolsService.resolveOrganisationId(targetOrgId);
|
|
||||||
const res = await apiClient.get<Portfolio[]>(`/v1/api/governance/organisations/${hashedOrgId}/portfolio`);
|
const res = await apiClient.get<Portfolio[]>(`/v1/api/governance/organisations/${hashedOrgId}/portfolio`);
|
||||||
return res.data || [];
|
return res.data || [];
|
||||||
};
|
};
|
||||||
@@ -236,16 +214,10 @@ export const createPortfolio = async (payload: CreatePortfolioRequest): Promise<
|
|||||||
if (ownerId && typeof ownerId === 'string' && ownerId.includes('@')) {
|
if (ownerId && typeof ownerId === 'string' && ownerId.includes('@')) {
|
||||||
ownerId = getUserIdByEmail(ownerId) || userSub || ownerId;
|
ownerId = getUserIdByEmail(ownerId) || userSub || ownerId;
|
||||||
}
|
}
|
||||||
const tenantId = getCurrentUserTenantId();
|
|
||||||
const orgId =
|
|
||||||
payload.organisationId !== undefined && payload.organisationId !== '' && payload.organisationId !== null
|
|
||||||
? toIntegerId(payload.organisationId)
|
|
||||||
: (tenantId !== null ? tenantId : 0);
|
|
||||||
|
|
||||||
const res = await apiClient.post('/v1/api/governance/organisations/portfolio', {
|
const res = await apiClient.post('/v1/api/governance/organisations/portfolio', {
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
description: payload.description,
|
description: payload.description,
|
||||||
organisationId: orgId,
|
organisationId: toIntegerId(payload.organisationId),
|
||||||
ownerId: ownerId || userSub || '00000000-0000-0000-0000-000000000000',
|
ownerId: ownerId || userSub || '00000000-0000-0000-0000-000000000000',
|
||||||
});
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
@@ -509,14 +481,8 @@ export const getAward = async (awardId: string | number): Promise<Award> => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const createAward = async (payload: CreateAwardRequest): Promise<Award> => {
|
export const createAward = async (payload: CreateAwardRequest): Promise<Award> => {
|
||||||
const tenantId = getCurrentUserTenantId();
|
|
||||||
const orgId =
|
|
||||||
payload.organisationId !== undefined && payload.organisationId !== '' && payload.organisationId !== null
|
|
||||||
? toIntegerId(payload.organisationId)
|
|
||||||
: (tenantId !== null ? tenantId : 0);
|
|
||||||
|
|
||||||
const res = await apiClient.post('/v1/api/grants/awards', {
|
const res = await apiClient.post('/v1/api/grants/awards', {
|
||||||
organisationId: orgId,
|
organisationId: toIntegerId(payload.organisationId),
|
||||||
programmeId: toIntegerId(payload.programmeId),
|
programmeId: toIntegerId(payload.programmeId),
|
||||||
amount: Number(payload.amount),
|
amount: Number(payload.amount),
|
||||||
});
|
});
|
||||||
@@ -922,36 +888,7 @@ export const getUsers = async (
|
|||||||
pageSize = 100,
|
pageSize = 100,
|
||||||
filter: UserFilterRequest = {}
|
filter: UserFilterRequest = {}
|
||||||
): Promise<User[]> => {
|
): Promise<User[]> => {
|
||||||
const tenantId = getCurrentUserTenantId();
|
const res = await apiClient.post<User[]>(`/v1/api/users/${page}/${pageSize}`, filter);
|
||||||
// Unless otherwise specified by the user, ensure tenant_id is passed into endpoints where organisationId is required for system activities
|
|
||||||
const orgId =
|
|
||||||
filter.organisationId !== undefined
|
|
||||||
? (filter.organisationId !== null && filter.organisationId !== ''
|
|
||||||
? toIntegerId(filter.organisationId)
|
|
||||||
: null)
|
|
||||||
: tenantId;
|
|
||||||
|
|
||||||
const requestBody: Record<string, any> = {
|
|
||||||
...filter,
|
|
||||||
};
|
|
||||||
if (orgId !== null && orgId !== undefined) {
|
|
||||||
requestBody.organisationId = orgId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashID query parameter for query string protection
|
|
||||||
const queryParams: Record<string, any> = {};
|
|
||||||
if (orgId !== null && orgId !== undefined) {
|
|
||||||
const hashedOrgId = await toolsService.encodeId(orgId);
|
|
||||||
if (hashedOrgId) {
|
|
||||||
queryParams.organisationId = hashedOrgId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await apiClient.post<User[]>(
|
|
||||||
`/v1/api/users/${page}/${pageSize}`,
|
|
||||||
requestBody,
|
|
||||||
{ params: Object.keys(queryParams).length > 0 ? queryParams : undefined }
|
|
||||||
);
|
|
||||||
const list = res.data || [];
|
const list = res.data || [];
|
||||||
registerUserEmails(list);
|
registerUserEmails(list);
|
||||||
return list;
|
return list;
|
||||||
@@ -1044,14 +981,8 @@ export const syncIntegration = async (force = true): Promise<void> => {
|
|||||||
// 8. Communications & Invites APIs
|
// 8. Communications & Invites APIs
|
||||||
// ==========================================
|
// ==========================================
|
||||||
export const sendInvite = async (payload: InviteRequest): Promise<Invite> => {
|
export const sendInvite = async (payload: InviteRequest): Promise<Invite> => {
|
||||||
const tenantId = getCurrentUserTenantId();
|
|
||||||
const hostOrgId =
|
|
||||||
payload.organisationId !== undefined && payload.organisationId !== '' && payload.organisationId !== null
|
|
||||||
? toIntegerId(payload.organisationId)
|
|
||||||
: (tenantId !== null ? tenantId : 0);
|
|
||||||
|
|
||||||
const res = await apiClient.post<Invite>('/v1/api/communications/invite', {
|
const res = await apiClient.post<Invite>('/v1/api/communications/invite', {
|
||||||
organisationId: hostOrgId,
|
organisationId: toIntegerId(payload.organisationId),
|
||||||
invitedOrganisationId: toIntegerId(payload.invitedOrganisationId),
|
invitedOrganisationId: toIntegerId(payload.invitedOrganisationId),
|
||||||
awardId: toIntegerId(payload.awardId),
|
awardId: toIntegerId(payload.awardId),
|
||||||
contractId: toIntegerId(payload.contractId),
|
contractId: toIntegerId(payload.contractId),
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ export interface UserSyncProfile {
|
|||||||
name?: string | null;
|
name?: string | null;
|
||||||
role?: string | null;
|
role?: string | null;
|
||||||
tenant?: string | null;
|
tenant?: string | null;
|
||||||
tenant_id?: number | string | null;
|
|
||||||
organisationId?: number | string | null;
|
|
||||||
organisationName?: string | null;
|
organisationName?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,19 +199,6 @@ async function executeBackendSync(
|
|||||||
userSub?: string,
|
userSub?: string,
|
||||||
profile?: UserSyncProfile
|
profile?: UserSyncProfile
|
||||||
): Promise<{ endpoint: string; data?: any }> {
|
): Promise<{ endpoint: string; data?: any }> {
|
||||||
const rawTenantId = profile?.tenant_id ?? (profile as any)?.tenantId;
|
|
||||||
const parsedTenantId =
|
|
||||||
rawTenantId !== undefined && rawTenantId !== null && rawTenantId !== ''
|
|
||||||
? Number(rawTenantId)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const resolvedOrgId =
|
|
||||||
parsedTenantId !== undefined
|
|
||||||
? parsedTenantId
|
|
||||||
: profile?.organisationId !== undefined && profile?.organisationId !== null && profile?.organisationId !== ''
|
|
||||||
? Number(profile.organisationId)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
id: userSub || profile?.id || profile?.sub,
|
id: userSub || profile?.id || profile?.sub,
|
||||||
sub: userSub || profile?.sub,
|
sub: userSub || profile?.sub,
|
||||||
@@ -221,8 +206,6 @@ async function executeBackendSync(
|
|||||||
name: profile?.name || undefined,
|
name: profile?.name || undefined,
|
||||||
role: profile?.role || undefined,
|
role: profile?.role || undefined,
|
||||||
tenant: profile?.tenant || undefined,
|
tenant: profile?.tenant || undefined,
|
||||||
tenant_id: parsedTenantId,
|
|
||||||
organisationId: resolvedOrgId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const candidateEndpoints = USER_SYNC_CANDIDATE_ENDPOINTS;
|
const candidateEndpoints = USER_SYNC_CANDIDATE_ENDPOINTS;
|
||||||
|
|||||||
@@ -261,8 +261,6 @@ export interface User {
|
|||||||
name?: string;
|
name?: string;
|
||||||
organisationId?: number | string;
|
organisationId?: number | string;
|
||||||
organisationName?: string;
|
organisationName?: string;
|
||||||
tenant?: string;
|
|
||||||
tenant_id?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SystemUser = User;
|
export type SystemUser = User;
|
||||||
@@ -288,7 +286,6 @@ export interface UserFilterRequest {
|
|||||||
email?: string | null;
|
email?: string | null;
|
||||||
sortBy?: string | null;
|
sortBy?: string | null;
|
||||||
sortDescending?: boolean;
|
sortDescending?: boolean;
|
||||||
organisationId?: number | string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Organisation {
|
export interface Organisation {
|
||||||
|
|||||||
Reference in New Issue
Block a user