1
0
forked from baron/baron-sso

앱 로고 URL 검증 및 미리보기 상태 표시

This commit is contained in:
2026-04-09 09:46:40 +09:00
parent 1e53b66abb
commit df09694ed6
4 changed files with 137 additions and 5 deletions

View File

@@ -2,6 +2,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import {
ArrowLeft,
ExternalLink,
Info,
Plus,
Save,
@@ -133,6 +134,9 @@ function ClientGeneralPage() {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [logoUrl, setLogoUrl] = useState("");
const [logoPreviewStatus, setLogoPreviewStatus] = useState<
"idle" | "loading" | "loaded" | "error"
>("idle");
const [clientType, setClientType] = useState<ClientType>("private");
const [status, setStatus] = useState<ClientStatus>("active");
const [initialStatus, setInitialStatus] = useState<ClientStatus>("active");
@@ -240,6 +244,21 @@ function ClientGeneralPage() {
const securityProfile: SecurityProfile =
clientType === "pkce" ? "pkce" : "private";
const trimmedLogoUrl = logoUrl.trim();
const hasLogoUrl = trimmedLogoUrl.length > 0;
const hasValidLogoUrl = !hasLogoUrl || isValidUrl(trimmedLogoUrl);
useEffect(() => {
if (!hasLogoUrl) {
setLogoPreviewStatus("idle");
return;
}
if (!hasValidLogoUrl) {
setLogoPreviewStatus("error");
return;
}
setLogoPreviewStatus("loading");
}, [hasLogoUrl, hasValidLogoUrl, trimmedLogoUrl]);
const handleSecurityProfileChange = (profile: SecurityProfile) => {
setClientType(profile);
@@ -438,6 +457,15 @@ function ClientGeneralPage() {
const mutation = useMutation({
mutationFn: async () => {
if (hasLogoUrl && !hasValidLogoUrl) {
throw new Error(
t(
"msg.dev.clients.general.identity.logo_invalid",
"앱 로고 URL 형식이 올바르지 않습니다. http 또는 https 주소를 입력하세요.",
),
);
}
const scopeNames = scopes.map((scope) => scope.name).filter(Boolean);
const effectiveTokenEndpointAuthMethod =
@@ -457,7 +485,7 @@ function ClientGeneralPage() {
: undefined,
metadata: {
description,
logo_url: logoUrl,
logo_url: trimmedLogoUrl,
structured_scopes: scopes,
token_endpoint_auth_method: effectiveTokenEndpointAuthMethod,
headless_login_enabled: headlessLoginEnabled,
@@ -722,6 +750,8 @@ function ClientGeneralPage() {
<Input
value={logoUrl}
onChange={(e) => setLogoUrl(e.target.value)}
aria-invalid={!hasValidLogoUrl}
className={!hasValidLogoUrl ? "border-destructive" : ""}
placeholder={t(
"ui.dev.clients.general.identity.logo_placeholder",
"https://example.com/logo.png",
@@ -733,19 +763,100 @@ function ClientGeneralPage() {
"인증 화면에 표시될 PNG/SVG URL입니다.",
)}
</p>
{!hasValidLogoUrl ? (
<p className="text-xs text-destructive">
{t(
"msg.dev.clients.general.identity.logo_invalid",
"앱 로고 URL 형식이 올바르지 않습니다. http 또는 https 주소를 입력하세요.",
)}
</p>
) : null}
{hasLogoUrl && hasValidLogoUrl ? (
<div className="flex items-center gap-2 text-xs">
<span
className={cn("text-muted-foreground", {
"text-foreground": logoPreviewStatus === "loaded",
"text-destructive": logoPreviewStatus === "error",
})}
>
{logoPreviewStatus === "loading"
? t(
"msg.dev.clients.general.identity.logo_preview_loading",
"로고 미리보기를 불러오는 중입니다.",
)
: logoPreviewStatus === "loaded"
? t(
"msg.dev.clients.general.identity.logo_preview_ready",
"로고 미리보기를 확인했습니다.",
)
: logoPreviewStatus === "error"
? t(
"msg.dev.clients.general.identity.logo_preview_failed",
"로고 미리보기를 불러오지 못했습니다. URL 또는 이미지 접근 권한을 확인하세요.",
)
: null}
</span>
<a
href={trimmedLogoUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-1 text-muted-foreground underline-offset-4 hover:text-foreground hover:underline"
>
<ExternalLink className="h-3 w-3" />
{t(
"ui.dev.clients.general.identity.logo_open",
"새 탭에서 열기",
)}
</a>
</div>
) : null}
</div>
<div className="flex h-20 w-20 items-center justify-center rounded-lg border-2 border-dashed border-border bg-muted/40 shrink-0">
{logoUrl ? (
<div
className={cn(
"flex h-20 w-20 shrink-0 items-center justify-center rounded-lg border-2 border-dashed",
hasLogoUrl && hasValidLogoUrl && logoPreviewStatus !== "error"
? "bg-white"
: "bg-muted/40",
logoPreviewStatus === "error"
? "border-destructive/60"
: "border-border",
)}
>
{hasLogoUrl && hasValidLogoUrl ? (
<img
src={logoUrl}
key={trimmedLogoUrl}
src={trimmedLogoUrl}
alt={t(
"ui.dev.clients.general.identity.logo_preview",
"Logo Preview",
)}
className="h-full w-full object-contain"
onLoad={() => setLogoPreviewStatus("loaded")}
onError={() => setLogoPreviewStatus("error")}
/>
) : (
<Upload className="h-5 w-5 text-muted-foreground" />
<div className="flex flex-col items-center justify-center gap-1 px-2 text-center">
<Upload
className={cn("h-5 w-5 text-muted-foreground", {
"text-destructive": logoPreviewStatus === "error",
})}
/>
{logoPreviewStatus === "error" ? (
<span className="text-[10px] leading-tight text-destructive">
{t(
"ui.dev.clients.general.identity.logo_preview_error_badge",
"미리보기 실패",
)}
</span>
) : (
<span className="text-[10px] leading-tight text-muted-foreground">
{t(
"ui.dev.clients.general.identity.logo_preview_empty",
"미리보기",
)}
</span>
)}
</div>
)}
</div>
</div>

View File

@@ -377,6 +377,10 @@ empty = "No IdP configurations found."
[msg.dev.clients.general.identity]
logo_help = "PNG or SVG URL shown on the consent and authentication screens."
logo_invalid = "The app logo URL format is invalid. Enter an http or https address."
logo_preview_loading = "Loading the logo preview."
logo_preview_ready = "Logo preview loaded."
logo_preview_failed = "Failed to load the logo preview. Check the URL or image access policy."
subtitle = "Set the application name, description, and logo."
[msg.dev.clients.general.redirect]
@@ -1378,6 +1382,9 @@ description_placeholder = "Description Placeholder"
logo = "App Logo URL"
logo_placeholder = "https://example.com/logo.png"
logo_preview = "Logo Preview"
logo_open = "Open in new tab"
logo_preview_error_badge = "Preview failed"
logo_preview_empty = "Preview"
name = "Name"
name_placeholder = "My Awesome Application"
title = "Application Identity"

View File

@@ -377,6 +377,10 @@ subtitle = "이 애플리케이션의 외부 IdP 설정을 관리합니다."
[msg.dev.clients.general.identity]
logo_help = "인증 화면에 표시될 PNG/SVG URL입니다."
logo_invalid = "앱 로고 URL 형식이 올바르지 않습니다. http 또는 https 주소를 입력하세요."
logo_preview_loading = "로고 미리보기를 불러오는 중입니다."
logo_preview_ready = "로고 미리보기를 확인했습니다."
logo_preview_failed = "로고 미리보기를 불러오지 못했습니다. URL 또는 이미지 접근 권한을 확인하세요."
subtitle = "앱 이름과 설명, 로고를 설정합니다."
[msg.dev.clients.general.redirect]
@@ -1377,6 +1381,9 @@ description_placeholder = "앱에 대한 간단한 설명을 입력하세요."
logo = "앱 로고 URL"
logo_placeholder = "https://example.com/logo.png"
logo_preview = "로고 미리보기"
logo_open = "새 탭에서 열기"
logo_preview_error_badge = "미리보기 실패"
logo_preview_empty = "미리보기"
name = "앱 이름"
name_placeholder = "예: 멋진 애플리케이션"
title = "애플리케이션 정보"

View File

@@ -377,6 +377,10 @@ empty = ""
[msg.dev.clients.general.identity]
logo_help = ""
logo_invalid = ""
logo_preview_loading = ""
logo_preview_ready = ""
logo_preview_failed = ""
subtitle = ""
[msg.dev.clients.general.redirect]
@@ -1378,6 +1382,9 @@ description_placeholder = ""
logo = ""
logo_placeholder = ""
logo_preview = ""
logo_open = ""
logo_preview_error_badge = ""
logo_preview_empty = ""
name = ""
name_placeholder = ""
title = ""