1
0
forked from baron/baron-sso

Merge pull request 'feature/1183-signup-personal-default' (#1187) from feature/1183-signup-personal-default into dev

Reviewed-on: baron/baron-sso#1187
This commit is contained in:
2026-06-16 18:39:35 +09:00
15 changed files with 201 additions and 175 deletions

View File

@@ -270,8 +270,7 @@ function AppLayout() {
if (item.to === "/permissions-direct") return false;
if (item.to === "/tenants") return permissions.tenants;
if (item.to === orgfrontUrl) return permissions.org_chart;
if (item.to === "/worksmobile")
return permissions.worksmobile && showWorksmobile;
if (item.to === "/worksmobile") return permissions.worksmobile;
if (item.to === "/system/ory-ssot") return permissions.ory_ssot;
if (item.to === "/system/data-integrity")
return permissions.data_integrity;

View File

@@ -61,7 +61,7 @@ const users = [
id: "user-owner",
name: "Owner User",
email: "owner@example.com",
role: "tenant_admin",
role: "super_admin",
status: "active",
},
{

View File

@@ -13,7 +13,9 @@ export type TenantPermissionKey =
| "view_organization"
| "manage_organization"
| "view_schema"
| "manage_schema";
| "manage_schema"
| "view_worksmobile"
| "manage_worksmobile";
export function useTenantPermission(tenantId: string) {
const { data: profile } = useQuery({

View File

@@ -645,7 +645,9 @@ export function TenantFineGrainedPermissionsPage() {
{menu.label}
</span>
{(menu.relation === "ory_ssot" ||
menu.relation === "data_integrity") && (
menu.relation === "data_integrity" ||
menu.relation ===
"permissions_direct") && (
<Badge
variant="secondary"
className="text-[10px] py-0.5 px-1.5 font-semibold text-destructive bg-destructive/10 border-destructive/20"
@@ -667,7 +669,8 @@ export function TenantFineGrainedPermissionsPage() {
value={permissionValue}
disabled={
menu.relation === "ory_ssot" ||
menu.relation === "data_integrity"
menu.relation === "data_integrity" ||
menu.relation === "permissions_direct"
}
onChange={(e) => {
const nextVal = e.target.value as

View File

@@ -31,13 +31,13 @@ import {
import { toast } from "../../../components/ui/use-toast";
import {
addTenantRelation,
fetchMe,
fetchTenantRelations,
fetchUsers,
removeTenantRelation,
type TenantRelation,
} from "../../../lib/adminApi";
import { t } from "../../../lib/i18n";
import { useTenantPermission } from "../hooks/useTenantPermission";
interface TenantFineGrainedPermissionsTabProps {
tenantIdProp?: string;
@@ -48,8 +48,11 @@ export function TenantFineGrainedPermissionsTab({
}: TenantFineGrainedPermissionsTabProps = {}) {
const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>();
const tenantId = tenantIdProp || tenantIdParam || "";
const { hasPermission } = useTenantPermission(tenantId);
const isWritable = hasPermission("manage_admins");
const { data: profile } = useQuery({
queryKey: ["me"],
queryFn: fetchMe,
});
const isWritable = profile?.role === "super_admin";
const queryClient = useQueryClient();
const [searchTerm, setSearchTerm] = useState("");
const [isDialogOpen, setIsDialogOpen] = useState(false);
@@ -75,7 +78,13 @@ export function TenantFineGrainedPermissionsTab({
> = {};
for (const user of relationsQuery.data) {
initialMap[user.userId] = {};
const tabs = ["profile", "permissions", "organization", "schema"];
const tabs = [
"profile",
"permissions",
"organization",
"schema",
"worksmobile",
];
for (const tab of tabs) {
const isWrite = user.relations.includes(`${tab}_managers`);
const isRead = user.relations.includes(`${tab}_viewers`);
@@ -204,7 +213,7 @@ export function TenantFineGrainedPermissionsTab({
const handleRelationChange = async (
userId: string,
tab: "profile" | "permissions" | "organization" | "schema",
tab: "profile" | "permissions" | "organization" | "schema" | "worksmobile",
currentVal: "none" | "read" | "write",
newVal: "none" | "read" | "write",
) => {
@@ -318,6 +327,14 @@ export function TenantFineGrainedPermissionsTab({
</Button>
</CardHeader>
<CardContent className="pt-0">
{!isWritable && (
<div className="mb-4 p-3 bg-amber-50 dark:bg-amber-950/20 text-amber-800 dark:text-amber-200 border border-amber-200 dark:border-amber-800/30 rounded-lg text-sm font-medium">
{t(
"msg.admin.tenants.relations.super_admin_only_desc",
"이 화면의 권한 설정은 시스템 최고 관리자(super_admin)만 수정할 수 있습니다.",
)}
</div>
)}
<div className="rounded-md border border-border overflow-hidden">
<Table>
<TableHeader className="bg-secondary/40">
@@ -337,6 +354,12 @@ export function TenantFineGrainedPermissionsTab({
<TableHead className="font-bold">
{t("ui.admin.tenants.detail.tab_schema", "사용자 스키마")}
</TableHead>
<TableHead className="font-bold">
{t(
"ui.admin.tenants.detail.tab_worksmobile",
"네이버웍스 연동",
)}
</TableHead>
<TableHead className="font-bold text-center w-20">
{t("ui.common.action", "작업")}
</TableHead>
@@ -346,7 +369,7 @@ export function TenantFineGrainedPermissionsTab({
{relations.length === 0 ? (
<TableRow>
<TableCell
colSpan={6}
colSpan={7}
className="text-center py-12 text-muted-foreground"
>
{t(
@@ -387,6 +410,14 @@ export function TenantFineGrainedPermissionsTab({
? "read"
: "none";
const worksmobileVal = user.relations.includes(
"worksmobile_managers",
)
? "write"
: user.relations.includes("worksmobile_viewers")
? "read"
: "none";
const curProfileVal =
localTenantPermissions[user.userId]?.profile ??
profileVal;
@@ -398,6 +429,9 @@ export function TenantFineGrainedPermissionsTab({
organizationVal;
const curSchemaVal =
localTenantPermissions[user.userId]?.schema ?? schemaVal;
const curWorksmobileVal =
localTenantPermissions[user.userId]?.worksmobile ??
worksmobileVal;
return (
<TableRow
@@ -562,6 +596,43 @@ export function TenantFineGrainedPermissionsTab({
</option>
</select>
</TableCell>
<TableCell>
<select
className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
value={curWorksmobileVal}
disabled={!isWritable}
name={`tenant-fine-grained-worksmobile-${user.userId}`}
onChange={(e) => {
const nextVal = e.target.value as
| "none"
| "read"
| "write";
setLocalTenantPermissions((prev) => ({
...prev,
[user.userId]: {
...(prev[user.userId] ?? {}),
worksmobile: nextVal,
},
}));
handleRelationChange(
user.userId,
"worksmobile",
worksmobileVal,
nextVal,
);
}}
>
<option value="none">
{t("ui.common.none", "권한 없음")}
</option>
<option value="read">
{t("ui.common.read", "조회 가능 (Read)")}
</option>
<option value="write">
{t("ui.common.write", "수정 가능 (Write)")}
</option>
</select>
</TableCell>
<TableCell className="text-center">
<Button
variant="ghost"

View File

@@ -45,6 +45,8 @@ export type TenantSummary = {
manage_organization?: boolean;
view_schema?: boolean;
manage_schema?: boolean;
view_worksmobile?: boolean;
manage_worksmobile?: boolean;
};
createdAt: string;
updatedAt: string;