Files
calendar/apps/web/modules/settings/admin/oauth-clients-admin-view.tsx
T
Eunjae LeeGitHubClaude Opus 4.5Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
81e4241f85 refactor: apply biome formatting to apps/web (#27692)
* refactor: apply biome formatting to apps/web (batch 1)

Formats apps/web non-modules directories:
- apps/web/app
- apps/web/lib
- apps/web/pages
- apps/web/styles
- apps/web/server
- apps/web/test
- Root-level .ts and .mjs files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web/modules (batch 2)

Formats smaller apps/web/modules directories:
- onboarding, data-table, users, apps, auth
- integration-attribute-sync, shell, availability
- feature-flags, team, webhooks, getting-started
- feature-opt-in, troubleshooter, schedules, notifications
- signup-view.tsx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web/modules (batch 3)

Formats medium apps/web/modules directories:
- bookings
- event-types
- insights
- embed

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web/modules/ee (batch 4)

Formats apps/web/modules/ee directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web (batch 5)

Formats remaining apps/web directories:
- apps/web/modules/settings
- apps/web/modules/booking-audit
- apps/web/playwright

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to apps/web/components (batch 6)

Formats remaining apps/web/components files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to new apps/web files from main

Formats newly added/modified files from main merge:
- WorkflowStepContainer.tsx (resolved merge conflicts)
- Newly moved files (blocklist, form-builder, schedules, etc.)
- Other files modified in main

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: apply biome formatting to new apps/web files from main

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-10 23:25:24 +05:30

176 lines
5.9 KiB
TypeScript

"use client";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { OAuthClientsAdminSkeleton } from "./oauth-clients-admin-skeleton";
import { showToast } from "@calcom/ui/components/toast";
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
import type { OAuthClientCreateFormValues } from "../oauth/create/OAuthClientCreateModal";
import { OAuthClientCreateDialog } from "../oauth/create/OAuthClientCreateModal";
import { OAuthClientPreviewDialog } from "../oauth/create/OAuthClientPreviewDialog";
import { OAuthClientDetailsDialog, type OAuthClientDetails } from "../oauth/view/OAuthClientDetailsDialog";
import { OAuthClientsList } from "../oauth/OAuthClientsList";
export default function OAuthClientsAdminView() {
const { t } = useLocale();
const utils = trpc.useUtils();
const [isCreatingClient, setIsCreatingClient] = useState(false);
const [createdClient, setCreatedClient] = useState<OAuthClientDetails | null>(null);
const [selectedClient, setSelectedClient] = useState<OAuthClientDetails | null>(null);
const { data: pendingClients, isLoading: isPendingClientsLoading } = trpc.viewer.oAuth.listClients.useQuery(
{
status: "PENDING",
}
);
const { data: rejectedClients, isLoading: isRejectedClientsLoading } =
trpc.viewer.oAuth.listClients.useQuery({
status: "REJECTED",
});
const { data: approvedClients, isLoading: isApprovedClientsLoading } =
trpc.viewer.oAuth.listClients.useQuery({
status: "APPROVED",
});
const createMutation = trpc.viewer.oAuth.createClient.useMutation({
onSuccess: async (data) => {
setCreatedClient({
clientId: data.clientId,
clientSecret: data.clientSecret,
name: data.name,
purpose: data.purpose ?? "",
status: data.status,
redirectUri: data.redirectUri,
logo: data.logo || null,
});
showToast(t("oauth_client_created"), "success");
utils.viewer.oAuth.listClients.invalidate();
},
onError: (error) => {
showToast(`${t("oauth_client_create_error")}: ${error.message}`, "error");
},
});
const updateStatusMutation = trpc.viewer.oAuth.updateClient.useMutation({
onSuccess: async (data) => {
showToast(t("oauth_client_status_updated", { name: data.name, status: data.status }), "success");
setSelectedClient((prev) => {
if (!prev) return prev;
if (prev.clientId !== data.clientId) return prev;
return {
...prev,
status: data.status,
rejectionReason: data.rejectionReason,
};
});
utils.viewer.oAuth.listClients.invalidate();
},
onError: (error) => {
showToast(`${t("oauth_client_status_update_error")}: ${error.message}`, "error");
},
});
const handleCreateClient = (values: OAuthClientCreateFormValues) => {
createMutation.mutate({
name: values.name,
purpose: values.purpose,
redirectUri: values.redirectUri,
websiteUrl: values.websiteUrl,
logo: values.logo,
enablePkce: values.enablePkce,
});
};
const handleCloseDialog = () => {
setIsCreatingClient(false);
setCreatedClient(null);
};
const handleCloseClientDialog = () => {
setSelectedClient(null);
};
const handleApprove = (clientId: string) => {
updateStatusMutation.mutate({ clientId, status: "APPROVED" });
};
const handleReject = (input: { clientId: string; rejectionReason: string }) => {
updateStatusMutation.mutate({
clientId: input.clientId,
status: "REJECTED",
rejectionReason: input.rejectionReason,
});
};
if (isPendingClientsLoading || isRejectedClientsLoading || isApprovedClientsLoading) {
return <OAuthClientsAdminSkeleton />;
}
return (
<SettingsHeader title={t("oauth_clients_admin")} description={t("oauth_clients_admin_description")}>
<div className="space-y-10">
<div className="space-y-3" data-testid="oauth-client-admin-pending-section">
<h2 className="text-emphasis text-base font-semibold">{t("pending")}</h2>
<OAuthClientsList
clients={pendingClients ?? []}
onSelectClient={(client) => setSelectedClient(client)}
showStatus
/>
</div>
<div className="space-y-3" data-testid="oauth-client-admin-rejected-section">
<h2 className="text-emphasis text-base font-semibold">{t("rejected")}</h2>
<OAuthClientsList
clients={rejectedClients ?? []}
onSelectClient={(client) => setSelectedClient(client)}
showStatus
/>
</div>
<div className="space-y-3" data-testid="oauth-client-admin-approved-section">
<h2 className="text-emphasis text-base font-semibold">{t("approved")}</h2>
<OAuthClientsList
clients={approvedClients ?? []}
onSelectClient={(client) => setSelectedClient(client)}
showStatus
/>
</div>
</div>
{createdClient ? (
<OAuthClientPreviewDialog
open={isCreatingClient}
onOpenChange={setIsCreatingClient}
title={t("oauth_client_created")}
description={t("oauth_client_created_description")}
client={createdClient}
onClose={handleCloseDialog}
/>
) : (
<OAuthClientCreateDialog
open={isCreatingClient}
onOpenChange={setIsCreatingClient}
isSubmitting={createMutation.isPending}
onSubmit={handleCreateClient}
onClose={handleCloseDialog}
/>
)}
<OAuthClientDetailsDialog
open={Boolean(selectedClient)}
onOpenChange={(open) => !open && handleCloseClientDialog()}
client={selectedClient}
onApprove={handleApprove}
onReject={handleReject}
isStatusChangePending={updateStatusMutation.isPending}
/>
</SettingsHeader>
);
}