feat(frontend): add React UI — procurement office, wizard, suppliers, inventory panel (dash port), role-based login

This commit is contained in:
ali_mohaghegh
2026-08-25 19:08:15 +03:30
parent 170aed66ea
commit 4e105c5cd5
103 changed files with 16352 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
// ============================================================
// Casdoor OIDC + PKCE login — v2.6
// Config lives in /casdoor-config.js (window.CASDOOR_CONFIG), served
// statically next to index.html (same values as the shipped dash).
// ============================================================
const CASDOOR_CONFIG = (window as unknown as { CASDOOR_CONFIG?: CasdoorConfig }).CASDOOR_CONFIG;
export interface CasdoorConfig {
serverUrl: string;
clientId: string;
clientSecret: string;
organization: string;
appName: string;
redirectUri: string;
}
function b64url(bytes: Uint8Array): string {
let s = "";
for (const b of bytes) s += String.fromCharCode(b);
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function randomBytes(n: number): Uint8Array {
const arr = new Uint8Array(n);
crypto.getRandomValues(arr);
return arr;
}
async function sha256b64url(text: string): Promise<string> {
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
return b64url(new Uint8Array(buf));
}
export function startCasdoorLogin(): void {
if (!CASDOOR_CONFIG) {
alert("تنظیمات Casdoor یافت نشد — فایل casdoor-config.js را بررسی کنید");
return;
}
const verifier = b64url(randomBytes(64));
const state = b64url(randomBytes(16));
sessionStorage.setItem("casdoor-pkce", JSON.stringify({ verifier, state }));
void sha256b64url(verifier).then((challenge) => {
const params = new URLSearchParams({
client_id: CASDOOR_CONFIG.clientId,
redirect_uri: CASDOOR_CONFIG.redirectUri,
response_type: "code",
scope: "read",
state,
organization: CASDOOR_CONFIG.organization,
application: CASDOOR_CONFIG.appName,
code_challenge: challenge,
code_challenge_method: "S256",
});
window.location.href = `${CASDOOR_CONFIG.serverUrl}/login/oauth/authorize?${params.toString()}`;
});
}
/** Exchange the OIDC authorization code for a token (called on /callback). */
export async function exchangeCodeForToken(code: string): Promise<{ access_token: string; id_token?: string }> {
if (!CASDOOR_CONFIG) throw new Error("تنظیمات Casdoor یافت نشد");
const pkce = JSON.parse(sessionStorage.getItem("casdoor-pkce") || "{}") as { verifier?: string };
const resp = await fetch(`${CASDOOR_CONFIG.serverUrl}/api/login/oauth/access_token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: CASDOOR_CONFIG.clientId,
client_secret: CASDOOR_CONFIG.clientSecret,
code,
redirect_uri: CASDOOR_CONFIG.redirectUri,
code_verifier: pkce.verifier || "",
}),
});
if (!resp.ok) throw new Error("خطا در دریافت توکن از Casdoor");
return resp.json();
}
export { CASDOOR_CONFIG };