* 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>
96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
import { useState } from "react";
|
|
|
|
import { trpc } from "@calcom/trpc/react";
|
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
|
import { Badge } from "@calcom/ui/components/badge";
|
|
import { Button } from "@calcom/ui/components/button";
|
|
import { PanelCard } from "@calcom/ui/components/card";
|
|
import { Switch } from "@calcom/ui/components/form";
|
|
import { ListItem, ListItemText, ListItemTitle } from "@calcom/ui/components/list";
|
|
import { List } from "@calcom/ui/components/list";
|
|
import { showToast } from "@calcom/ui/components/toast";
|
|
|
|
import { AssignFeatureSheet } from "./AssignFeatureSheet";
|
|
|
|
export const FlagAdminList = () => {
|
|
const [data] = trpc.viewer.features.list.useSuspenseQuery();
|
|
const [selectedFlag, setSelectedFlag] = useState<Flag | null>(null);
|
|
const [sheetOpen, setSheetOpen] = useState(false);
|
|
|
|
const groupedFlags = data.reduce(
|
|
(acc, flag) => {
|
|
const type = flag.type || "OTHER";
|
|
if (!acc[type]) {
|
|
acc[type] = [];
|
|
}
|
|
acc[type].push(flag);
|
|
return acc;
|
|
},
|
|
{} as Record<string, typeof data>
|
|
);
|
|
|
|
const sortedTypes = Object.keys(groupedFlags).sort();
|
|
|
|
const handleAssignClick = (flag: Flag) => {
|
|
setSelectedFlag(flag);
|
|
setSheetOpen(true);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="stack-y-4">
|
|
{sortedTypes.map((type) => (
|
|
<PanelCard key={type} title={type.replace(/_/g, " ")} collapsible defaultCollapsed={false}>
|
|
<List roundContainer noBorderTreatment>
|
|
{groupedFlags[type].map((flag: Flag, index: number) => (
|
|
<ListItem key={flag.slug} rounded={index === 0 || index === groupedFlags[type].length - 1}>
|
|
<div className="flex flex-1 flex-col">
|
|
<ListItemTitle component="h3">{flag.slug}</ListItemTitle>
|
|
<ListItemText component="p">{flag.description}</ListItemText>
|
|
</div>
|
|
<div className="flex items-center gap-2 py-2">
|
|
<FlagToggle flag={flag} />
|
|
<Button
|
|
color="secondary"
|
|
size="sm"
|
|
variant="icon"
|
|
onClick={() => handleAssignClick(flag)}
|
|
StartIcon="users"></Button>
|
|
</div>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</PanelCard>
|
|
))}
|
|
</div>
|
|
{selectedFlag && (
|
|
<AssignFeatureSheet flag={selectedFlag} open={sheetOpen} onOpenChange={setSheetOpen} />
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
type Flag = RouterOutputs["viewer"]["features"]["list"][number];
|
|
|
|
const FlagToggle = (props: { flag: Flag }) => {
|
|
const {
|
|
flag: { slug, enabled },
|
|
} = props;
|
|
const utils = trpc.useUtils();
|
|
const mutation = trpc.viewer.admin.toggleFeatureFlag.useMutation({
|
|
onSuccess: () => {
|
|
showToast("Flags successfully updated", "success");
|
|
utils.viewer.features.list.invalidate();
|
|
utils.viewer.features.map.invalidate();
|
|
},
|
|
});
|
|
return (
|
|
<Switch
|
|
defaultChecked={enabled}
|
|
onCheckedChange={(checked) => {
|
|
mutation.mutate({ slug, enabled: checked });
|
|
}}
|
|
/>
|
|
);
|
|
};
|