feat(GRO-2513): gate Settings nav+route to manager/super-user, eliminate groomer 403 (#82)
CI / Test (push) Successful in 38s
CI / Lint & Typecheck (push) Successful in 47s
CI / Test (pull_request) Successful in 23s
CI / Lint & Typecheck (pull_request) Successful in 30s
CI / Build & Push Docker Image (push) Successful in 17s
CI / Build & Push Docker Image (pull_request) Successful in 15s
CI / Test (push) Successful in 38s
CI / Lint & Typecheck (push) Successful in 47s
CI / Test (pull_request) Successful in 23s
CI / Lint & Typecheck (pull_request) Successful in 30s
CI / Build & Push Docker Image (push) Successful in 17s
CI / Build & Push Docker Image (pull_request) Successful in 15s
feat(GRO-2513): gate Settings nav+route to manager/super-user, eliminate groomer 403 Co-Authored-By: Paperclip <noreply@paperclip.ing> Co-authored-by: Lint Roller <23+gb_lint@noreply.git.farh.net> Co-committed-by: Lint Roller <23+gb_lint@noreply.git.farh.net>
This commit was merged in pull request #82.
This commit is contained in:
+57
-42
@@ -86,51 +86,66 @@ export function SettingsPage() {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Load user role first, then gate settings/auth-provider fetches on role
|
||||
useEffect(() => {
|
||||
fetch("/api/admin/settings")
|
||||
fetch("/api/staff/me")
|
||||
.then((r) => r.json())
|
||||
.then(async (data) => {
|
||||
// The logo is now proxied through the API server so the browser
|
||||
// never receives an S3 URL — use the proxy path directly as the src.
|
||||
setForm({
|
||||
businessName: data.businessName ?? "GroomBook",
|
||||
primaryColor: data.primaryColor ?? "#4f8a6f",
|
||||
accentColor: data.accentColor ?? "#8b7355",
|
||||
logoKey: data.logoKey ?? null,
|
||||
logoUrl: data.logoKey ? "/api/admin/settings/logo" : null,
|
||||
logoBase64: data.logoBase64 ?? null,
|
||||
logoMimeType: data.logoMimeType ?? null,
|
||||
});
|
||||
setLoaded(true);
|
||||
})
|
||||
.catch(() => setLoaded(true));
|
||||
}, []);
|
||||
.then((u) => {
|
||||
const user = u as CurrentUser;
|
||||
setCurrentUser(user);
|
||||
const isManager = user.role === "manager" || user.isSuperUser;
|
||||
|
||||
// Load current user (for isSuperUser check) and auth provider config
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
fetch("/api/staff/me").then((r) => r.json()).catch(() => null),
|
||||
fetch("/api/admin/auth-provider").then(async (r) => {
|
||||
if (r.ok) return r.json();
|
||||
if (r.status === 404) return null;
|
||||
throw new Error(`HTTP ${r.status}`);
|
||||
}).catch(() => null),
|
||||
]).then(([user, auth]) => {
|
||||
setCurrentUser(user as CurrentUser | null);
|
||||
if (auth) {
|
||||
setAuthConfig(auth as AuthProviderConfig);
|
||||
setAuthForm({
|
||||
providerId: (auth as AuthProviderConfig).providerId,
|
||||
displayName: (auth as AuthProviderConfig).displayName,
|
||||
issuerUrl: (auth as AuthProviderConfig).issuerUrl,
|
||||
internalBaseUrl: (auth as AuthProviderConfig).internalBaseUrl ?? "",
|
||||
clientId: (auth as AuthProviderConfig).clientId,
|
||||
clientSecret: (auth as AuthProviderConfig).clientSecret,
|
||||
scopes: (auth as AuthProviderConfig).scopes,
|
||||
});
|
||||
}
|
||||
setAuthLoaded(true);
|
||||
});
|
||||
if (isManager) {
|
||||
fetch("/api/admin/settings")
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
setForm({
|
||||
businessName: data.businessName ?? "GroomBook",
|
||||
primaryColor: data.primaryColor ?? "#4f8a6f",
|
||||
accentColor: data.accentColor ?? "#8b7355",
|
||||
logoKey: data.logoKey ?? null,
|
||||
logoUrl: data.logoKey ? "/api/admin/settings/logo" : null,
|
||||
logoBase64: data.logoBase64 ?? null,
|
||||
logoMimeType: data.logoMimeType ?? null,
|
||||
});
|
||||
setLoaded(true);
|
||||
})
|
||||
.catch(() => setLoaded(true));
|
||||
} else {
|
||||
setLoaded(true);
|
||||
}
|
||||
|
||||
if (user.isSuperUser) {
|
||||
fetch("/api/admin/auth-provider")
|
||||
.then(async (r) => {
|
||||
if (r.ok) return r.json();
|
||||
if (r.status === 404) return null;
|
||||
throw new Error(`HTTP ${r.status}`);
|
||||
})
|
||||
.then((auth) => {
|
||||
if (auth) {
|
||||
setAuthConfig(auth as AuthProviderConfig);
|
||||
setAuthForm({
|
||||
providerId: (auth as AuthProviderConfig).providerId,
|
||||
displayName: (auth as AuthProviderConfig).displayName,
|
||||
issuerUrl: (auth as AuthProviderConfig).issuerUrl,
|
||||
internalBaseUrl: (auth as AuthProviderConfig).internalBaseUrl ?? "",
|
||||
clientId: (auth as AuthProviderConfig).clientId,
|
||||
clientSecret: (auth as AuthProviderConfig).clientSecret,
|
||||
scopes: (auth as AuthProviderConfig).scopes,
|
||||
});
|
||||
}
|
||||
setAuthLoaded(true);
|
||||
})
|
||||
.catch(() => setAuthLoaded(true));
|
||||
} else {
|
||||
setAuthLoaded(true);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setLoaded(true);
|
||||
setAuthLoaded(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleLogoChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
Reference in New Issue
Block a user