Merge master into ishan/fix-activity-clerkprovider

This commit is contained in:
abhi1693
2026-02-07 15:12:00 +00:00
175 changed files with 12588 additions and 3703 deletions

View File

@@ -77,16 +77,12 @@ const humanizeAction = (value: string) =>
value
.split(".")
.map((part) =>
part
.replace(/_/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase())
part.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase()),
)
.join(" · ");
const formatStatusLabel = (status: string) =>
status
.replace(/_/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase());
status.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
const statusDotClass = (status: string) => {
if (status === "approved") return "bg-emerald-500";
@@ -108,17 +104,15 @@ type TooltipValue = number | string | Array<number | string>;
const formatRubricTooltipValue = (
value?: TooltipValue,
name?: TooltipValue,
item?:
| {
color?: string | null;
payload?: {
name?: string;
fill?: string;
percent?: number;
percentLabel?: string;
};
}
| null,
item?: {
color?: string | null;
payload?: {
name?: string;
fill?: string;
percent?: number;
percentLabel?: string;
};
} | null,
) => {
const payload = item?.payload;
const label =
@@ -223,19 +217,19 @@ export function BoardApprovalsPanel({
const approvals = useMemo(() => {
const raw = usingExternal
? externalApprovals ?? []
? (externalApprovals ?? [])
: approvalsQuery.data?.status === 200
? approvalsQuery.data.data.items ?? []
? (approvalsQuery.data.data.items ?? [])
: [];
return raw.map(normalizeApproval);
}, [approvalsQuery.data, externalApprovals, usingExternal]);
const loadingState = usingExternal
? externalLoading ?? false
? (externalLoading ?? false)
: approvalsQuery.isLoading;
const errorState = usingExternal
? externalError ?? null
: error ?? approvalsQuery.error?.message ?? null;
? (externalError ?? null)
: (error ?? approvalsQuery.error?.message ?? null);
const handleDecision = useCallback(
(approvalId: string, status: "approved" | "rejected") => {
@@ -244,9 +238,9 @@ export function BoardApprovalsPanel({
.filter((item) => item.status === "pending")
.sort(
(a, b) =>
(apiDatetimeToMs(b.created_at) ?? 0) - (apiDatetimeToMs(a.created_at) ?? 0),
)[0]
?.id;
(apiDatetimeToMs(b.created_at) ?? 0) -
(apiDatetimeToMs(a.created_at) ?? 0),
)[0]?.id;
if (pendingNext) {
setSelectedId(pendingNext);
}
@@ -311,17 +305,17 @@ export function BoardApprovalsPanel({
return bTime - aTime;
});
const pending = sortByTime(
approvals.filter((item) => item.status === "pending")
approvals.filter((item) => item.status === "pending"),
);
const resolved = sortByTime(
approvals.filter((item) => item.status !== "pending")
approvals.filter((item) => item.status !== "pending"),
);
return { pending, resolved };
}, [approvals]);
const orderedApprovals = useMemo(
() => [...sortedApprovals.pending, ...sortedApprovals.resolved],
[sortedApprovals.pending, sortedApprovals.resolved]
[sortedApprovals.pending, sortedApprovals.resolved],
);
const effectiveSelectedId = useMemo(() => {
@@ -344,7 +338,6 @@ export function BoardApprovalsPanel({
return (
<div className={cn("space-y-6", scrollable && "h-full")}>
{errorState ? (
<div className="rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
{errorState}
@@ -358,13 +351,13 @@ export function BoardApprovalsPanel({
<div
className={cn(
"grid gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]",
scrollable && "h-full"
scrollable && "h-full",
)}
>
<div
className={cn(
"overflow-hidden rounded-xl border border-slate-200 bg-white",
scrollable && "flex min-h-0 flex-col"
scrollable && "flex min-h-0 flex-col",
)}
>
<div className="border-b border-slate-200 bg-slate-50 px-4 py-3">
@@ -378,7 +371,7 @@ export function BoardApprovalsPanel({
<div
className={cn(
"divide-y divide-slate-100",
scrollable && "min-h-0 overflow-y-auto"
scrollable && "min-h-0 overflow-y-auto",
)}
>
{orderedApprovals.map((approval) => {
@@ -386,10 +379,10 @@ export function BoardApprovalsPanel({
const isSelected = effectiveSelectedId === approval.id;
const isPending = approval.status === "pending";
const titleRow = summary.rows.find(
(row) => row.label.toLowerCase() === "title"
(row) => row.label.toLowerCase() === "title",
);
const fallbackRow = summary.rows.find(
(row) => row.label.toLowerCase() !== "title"
(row) => row.label.toLowerCase() !== "title",
);
const primaryLabel =
titleRow?.value ?? fallbackRow?.value ?? "Untitled";
@@ -400,9 +393,8 @@ export function BoardApprovalsPanel({
onClick={() => setSelectedId(approval.id)}
className={cn(
"w-full px-4 py-4 text-left transition hover:bg-slate-50",
isSelected &&
"bg-amber-50 border-l-2 border-amber-500",
!isPending && "opacity-60"
isSelected && "bg-amber-50 border-l-2 border-amber-500",
!isPending && "opacity-60",
)}
>
<div className="flex items-start justify-between gap-3">
@@ -412,7 +404,7 @@ export function BoardApprovalsPanel({
<span
className={cn(
"rounded-[3px] px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.2em]",
statusBadgeClass(approval.status)
statusBadgeClass(approval.status),
)}
>
{formatStatusLabel(approval.status)}
@@ -434,7 +426,7 @@ export function BoardApprovalsPanel({
<div
className={cn(
"overflow-hidden rounded-xl border border-slate-200 bg-white",
scrollable && "flex min-h-0 flex-col"
scrollable && "flex min-h-0 flex-col",
)}
>
<div className="border-b border-slate-200 bg-slate-50 px-4 py-3">
@@ -452,7 +444,7 @@ export function BoardApprovalsPanel({
(() => {
const summary = approvalSummary(selectedApproval);
const titleRow = summary.rows.find(
(row) => row.label.toLowerCase() === "title"
(row) => row.label.toLowerCase() === "title",
);
const titleText = titleRow?.value?.trim() ?? "";
const descriptionText = summary.description?.trim() ?? "";
@@ -465,7 +457,7 @@ export function BoardApprovalsPanel({
return true;
});
const rubricEntries = Object.entries(
selectedApproval.rubric_scores ?? {}
selectedApproval.rubric_scores ?? {},
).map(([key, value]) => ({
label: key
.replace(/_/g, " ")
@@ -478,7 +470,8 @@ export function BoardApprovalsPanel({
);
const hasRubric = rubricEntries.length > 0 && rubricTotal > 0;
const rubricChartData = rubricEntries.map((entry, index) => {
const percent = rubricTotal > 0 ? (entry.value / rubricTotal) * 100 : 0;
const percent =
rubricTotal > 0 ? (entry.value / rubricTotal) * 100 : 0;
return {
key: entry.label.toLowerCase().replace(/[^a-z0-9]+/g, "_"),
name: entry.label,
@@ -507,14 +500,15 @@ export function BoardApprovalsPanel({
{humanizeAction(selectedApproval.action_type)}
</p>
<p className="mt-1 text-xs text-slate-500">
Requested {formatTimestamp(selectedApproval.created_at)}
Requested{" "}
{formatTimestamp(selectedApproval.created_at)}
</p>
</div>
<div className="flex flex-wrap items-center gap-3">
<span
className={cn(
"rounded-md px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.2em]",
confidenceBadgeClass(selectedApproval.confidence)
confidenceBadgeClass(selectedApproval.confidence),
)}
>
{selectedApproval.confidence}% confidence
@@ -552,7 +546,7 @@ export function BoardApprovalsPanel({
<span
className={cn(
"h-2 w-2 rounded-full",
statusDotClass(selectedApproval.status)
statusDotClass(selectedApproval.status),
)}
/>
<div>
@@ -684,7 +678,6 @@ export function BoardApprovalsPanel({
</div>
</div>
) : null}
</div>
);
})()

View File

@@ -56,7 +56,10 @@ function BoardChatComposerImpl({
disabled={isSending}
/>
<div className="flex justify-end">
<Button onClick={() => void send()} disabled={isSending || !value.trim()}>
<Button
onClick={() => void send()}
disabled={isSending || !value.trim()}
>
{isSending ? "Sending…" : "Send"}
</Button>
</div>

View File

@@ -95,8 +95,14 @@ export function BoardGoalPanel({
<p className="text-xs font-semibold uppercase tracking-wider text-muted">
Objective
</p>
<p className={cn("text-sm", board?.objective ? "text-strong" : "text-muted")}>
{board?.objective || (isGoalBoard ? "No objective yet." : "Not required.")}
<p
className={cn(
"text-sm",
board?.objective ? "text-strong" : "text-muted",
)}
>
{board?.objective ||
(isGoalBoard ? "No objective yet." : "Not required.")}
</p>
</div>
<div className="space-y-2">
@@ -122,7 +128,9 @@ export function BoardGoalPanel({
<p className="text-xs font-semibold uppercase tracking-wider text-muted">
Target date
</p>
<p className="text-sm text-strong">{formatTargetDate(board?.target_date)}</p>
<p className="text-sm text-strong">
{formatTargetDate(board?.target_date)}
</p>
</div>
{onStartOnboarding || onEdit ? (
<div className="flex flex-wrap gap-2">

View File

@@ -255,9 +255,7 @@ export function BoardOnboardingChat({
setSelectedOptions([]);
setAwaitingAssistantFingerprint(fingerprintBefore);
setAwaitingKind("answer");
setLastSubmittedAnswer(
freeText ? `${value}: ${freeText}` : value,
);
setLastSubmittedAnswer(freeText ? `${value}: ${freeText}` : value);
} catch (err) {
setError(
err instanceof Error ? err.message : "Failed to submit answer.",
@@ -497,14 +495,14 @@ export function BoardOnboardingChat({
{extraContextOpen ? "Hide" : "Add"}
</Button>
</div>
{extraContextOpen ? (
<div className="mt-2 space-y-2">
<Textarea
ref={extraContextRef}
className="min-h-[84px]"
placeholder="Anything else the agent should know before you confirm? (constraints, context, preferences, links, etc.)"
value={extraContext}
onChange={(event) => setExtraContext(event.target.value)}
{extraContextOpen ? (
<div className="mt-2 space-y-2">
<Textarea
ref={extraContextRef}
className="min-h-[84px]"
placeholder="Anything else the agent should know before you confirm? (constraints, context, preferences, links, etc.)"
value={extraContext}
onChange={(event) => setExtraContext(event.target.value)}
onKeyDown={(event) => {
if (event.key !== "Enter") return;
if (event.nativeEvent.isComposing) return;
@@ -521,9 +519,15 @@ export function BoardOnboardingChat({
size="sm"
type="button"
onClick={() => void submitExtraContext()}
disabled={loading || isAwaitingAgent || !extraContext.trim()}
disabled={
loading || isAwaitingAgent || !extraContext.trim()
}
>
{loading ? "Sending..." : isAwaitingAgent ? "Waiting..." : "Send context"}
{loading
? "Sending..."
: isAwaitingAgent
? "Waiting..."
: "Send context"}
</Button>
</div>
<p className="text-xs text-slate-500">
@@ -538,7 +542,11 @@ export function BoardOnboardingChat({
)}
</div>
<DialogFooter>
<Button onClick={confirmGoal} disabled={loading || isAwaitingAgent} type="button">
<Button
onClick={confirmGoal}
disabled={loading || isAwaitingAgent}
type="button"
>
Confirm goal
</Button>
</DialogFooter>

View File

@@ -1,6 +1,6 @@
"use client";
import { memo } from "react";
import { memo, type HTMLAttributes } from "react";
import ReactMarkdown, { type Components } from "react-markdown";
import remarkBreaks from "remark-breaks";
@@ -8,6 +8,60 @@ import remarkGfm from "remark-gfm";
import { cn } from "@/lib/utils";
type MarkdownCodeProps = HTMLAttributes<HTMLElement> & {
node?: unknown;
inline?: boolean;
};
const MARKDOWN_CODE_COMPONENTS: Components = {
pre: ({ node: _node, className, ...props }) => (
<pre
className={cn(
"my-3 overflow-x-auto rounded-lg bg-slate-950 p-3 text-xs leading-relaxed text-slate-100",
className,
)}
{...props}
/>
),
code: (rawProps) => {
// react-markdown passes `inline`, but the public `Components` typing doesn't
// currently include it, so we pluck it safely here without leaking it to DOM.
const {
node: _node,
inline,
className,
children,
...props
} = rawProps as MarkdownCodeProps;
const codeText = Array.isArray(children)
? children.join("")
: String(children ?? "");
const isInline =
typeof inline === "boolean" ? inline : !codeText.includes("\n");
if (isInline) {
return (
<code
className={cn(
"rounded bg-slate-100 px-1 py-0.5 font-mono text-[0.85em] text-slate-900",
className,
)}
{...props}
>
{children}
</code>
);
}
// For fenced blocks, the parent <pre> handles the box styling.
return (
<code className={cn("font-mono", className)} {...props}>
{children}
</code>
);
},
};
const MARKDOWN_TABLE_COMPONENTS: Components = {
table: ({ node: _node, className, ...props }) => (
<div className="my-3 overflow-x-auto">
@@ -42,6 +96,7 @@ const MARKDOWN_TABLE_COMPONENTS: Components = {
const MARKDOWN_COMPONENTS_BASIC: Components = {
...MARKDOWN_TABLE_COMPONENTS,
...MARKDOWN_CODE_COMPONENTS,
p: ({ node: _node, className, ...props }) => (
<p className={cn("mb-2 last:mb-0", className)} {...props} />
),
@@ -73,21 +128,6 @@ const MARKDOWN_COMPONENTS_DESCRIPTION: Components = {
h3: ({ node: _node, className, ...props }) => (
<h3 className={cn("mb-2 text-sm font-semibold", className)} {...props} />
),
code: ({ node: _node, className, ...props }) => (
<code
className={cn("rounded bg-slate-100 px-1 py-0.5 text-xs", className)}
{...props}
/>
),
pre: ({ node: _node, className, ...props }) => (
<pre
className={cn(
"overflow-auto rounded-lg bg-slate-900 p-3 text-xs text-slate-100",
className,
)}
{...props}
/>
),
};
const MARKDOWN_REMARK_PLUGINS_BASIC = [remarkGfm];
@@ -119,4 +159,3 @@ export const Markdown = memo(function Markdown({
});
Markdown.displayName = "Markdown";

View File

@@ -36,14 +36,15 @@ export function TaskCard({
onDragEnd,
}: TaskCardProps) {
const hasPendingApproval = approvalsPendingCount > 0;
const needsLeadReview = status === "review" && !isBlocked && !hasPendingApproval;
const needsLeadReview =
status === "review" && !isBlocked && !hasPendingApproval;
const leftBarClassName = isBlocked
? "bg-rose-400"
: hasPendingApproval
? "bg-amber-400"
: needsLeadReview
? "bg-indigo-400"
: null;
: null;
const priorityBadge = (value?: string) => {
if (!value) return null;
const normalized = value.toLowerCase();

View File

@@ -2,7 +2,14 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Activity, BarChart3, Bot, LayoutGrid, Network } from "lucide-react";
import {
Activity,
BarChart3,
Bot,
Folder,
LayoutGrid,
Network,
} from "lucide-react";
import { ApiError } from "@/api/mutator";
import {
@@ -13,14 +20,16 @@ import { cn } from "@/lib/utils";
export function DashboardSidebar() {
const pathname = usePathname();
const healthQuery = useHealthzHealthzGet<healthzHealthzGetResponse, ApiError>({
query: {
refetchInterval: 30_000,
refetchOnMount: "always",
retry: false,
const healthQuery = useHealthzHealthzGet<healthzHealthzGetResponse, ApiError>(
{
query: {
refetchInterval: 30_000,
refetchOnMount: "always",
retry: false,
},
request: { cache: "no-store" },
},
request: { cache: "no-store" },
});
);
const okValue = healthQuery.data?.data?.ok;
const systemStatus: "unknown" | "operational" | "degraded" =
@@ -51,7 +60,7 @@ export function DashboardSidebar() {
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname === "/dashboard"
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<BarChart3 className="h-4 w-4" />
@@ -63,19 +72,31 @@ export function DashboardSidebar() {
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/gateways")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<Network className="h-4 w-4" />
Gateways
</Link>
<Link
href="/board-groups"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/board-groups")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100",
)}
>
<Folder className="h-4 w-4" />
Board groups
</Link>
<Link
href="/boards"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/boards")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<LayoutGrid className="h-4 w-4" />
@@ -87,7 +108,7 @@ export function DashboardSidebar() {
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/activity")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<Activity className="h-4 w-4" />
@@ -99,7 +120,7 @@ export function DashboardSidebar() {
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/agents")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100"
: "hover:bg-slate-100",
)}
>
<Bot className="h-4 w-4" />
@@ -114,7 +135,7 @@ export function DashboardSidebar() {
"h-2 w-2 rounded-full",
systemStatus === "operational" && "bg-emerald-500",
systemStatus === "degraded" && "bg-rose-500",
systemStatus === "unknown" && "bg-slate-300"
systemStatus === "unknown" && "bg-slate-300",
)}
/>
{statusLabel}

View File

@@ -2,10 +2,21 @@
import Link from "next/link";
import { SignInButton, SignedIn, SignedOut, isClerkEnabled } from "@/auth/clerk";
import {
SignInButton,
SignedIn,
SignedOut,
isClerkEnabled,
} from "@/auth/clerk";
const ArrowIcon = () => (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
aria-hidden="true"
>
<path
d="M6 12L10 8L6 4"
stroke="currentColor"
@@ -81,16 +92,14 @@ export function LandingHero() {
</div>
<div className="hero-features">
{[
"Agent-First Operations",
"Approval Queues",
"Live Signals",
].map((label) => (
<div key={label} className="hero-feature">
<div className="feature-icon"></div>
<span>{label}</span>
</div>
))}
{["Agent-First Operations", "Approval Queues", "Live Signals"].map(
(label) => (
<div key={label} className="hero-feature">
<div className="feature-icon"></div>
<span>{label}</span>
</div>
),
)}
</div>
</div>
@@ -104,7 +113,9 @@ export function LandingHero() {
</div>
<div className="surface-subtitle">
<h3>Ship work without losing the thread.</h3>
<p>Tasks, approvals, and agent status stay synced across the board.</p>
<p>
Tasks, approvals, and agent status stay synced across the board.
</p>
</div>
<div className="metrics-row">
{[
@@ -266,4 +277,3 @@ export function LandingHero() {
</>
);
}

View File

@@ -1,6 +1,13 @@
"use client";
import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
import {
memo,
useCallback,
useLayoutEffect,
useMemo,
useRef,
useState,
} from "react";
import { TaskCard } from "@/components/molecules/TaskCard";
import { parseApiDatetime } from "@/lib/datetime";
@@ -140,6 +147,7 @@ export const TaskBoard = memo(function TaskBoard({
}, []);
useLayoutEffect(() => {
const cardRefsSnapshot = cardRefs.current;
if (animationRafRef.current !== null) {
window.cancelAnimationFrame(animationRafRef.current);
animationRafRef.current = null;
@@ -149,7 +157,7 @@ export const TaskBoard = memo(function TaskBoard({
cleanupTimeoutRef.current = null;
}
for (const taskId of animatedTaskIdsRef.current) {
const element = cardRefs.current.get(taskId);
const element = cardRefsSnapshot.get(taskId);
if (!element) continue;
element.style.transform = "";
element.style.transition = "";
@@ -182,7 +190,7 @@ export const TaskBoard = memo(function TaskBoard({
const dx = prev.left - next.left;
const dy = prev.top - next.top;
if (Math.abs(dx) < 1 && Math.abs(dy) < 1) continue;
const element = cardRefs.current.get(taskId);
const element = cardRefsSnapshot.get(taskId);
if (!element) continue;
moved.push({ taskId, element, dx, dy });
}
@@ -229,7 +237,7 @@ export const TaskBoard = memo(function TaskBoard({
cleanupTimeoutRef.current = null;
}
for (const taskId of animatedTaskIdsRef.current) {
const element = cardRefs.current.get(taskId);
const element = cardRefsSnapshot.get(taskId);
if (!element) continue;
element.style.transform = "";
element.style.transition = "";
@@ -302,10 +310,10 @@ export const TaskBoard = memo(function TaskBoard({
};
const handleDragLeave = (status: TaskStatus) => () => {
if (activeColumn === status) {
setActiveColumn(null);
}
};
if (activeColumn === status) {
setActiveColumn(null);
}
};
return (
<div
@@ -343,9 +351,14 @@ export const TaskBoard = memo(function TaskBoard({
? columnTasks.filter((task) => {
if (reviewBucket === "blocked") return Boolean(task.is_blocked);
if (reviewBucket === "approval_needed")
return (task.approvals_pending_count ?? 0) > 0 && !task.is_blocked;
return (
(task.approvals_pending_count ?? 0) > 0 && !task.is_blocked
);
if (reviewBucket === "waiting_lead")
return !task.is_blocked && (task.approvals_pending_count ?? 0) === 0;
return (
!task.is_blocked &&
(task.approvals_pending_count ?? 0) === 0
);
return true;
})
: columnTasks;
@@ -393,7 +406,11 @@ export const TaskBoard = memo(function TaskBoard({
label: "Lead review",
count: reviewCounts.waiting_lead,
},
{ key: "blocked", label: "Blocked", count: reviewCounts.blocked },
{
key: "blocked",
label: "Blocked",
count: reviewCounts.blocked,
},
] as const
).map((option) => (
<button

View File

@@ -15,7 +15,11 @@ import {
Trello,
} from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
export function UserMenu({ className }: { className?: string }) {

View File

@@ -12,5 +12,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return <>{children}</>;
}
return <ClerkProvider publishableKey={publishableKey}>{children}</ClerkProvider>;
return (
<ClerkProvider publishableKey={publishableKey}>{children}</ClerkProvider>
);
}

View File

@@ -20,7 +20,7 @@ export function QueryProvider({ children }: { children: ReactNode }) {
retry: 0,
},
},
})
}),
);
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;

View File

@@ -3,7 +3,12 @@
import Link from "next/link";
import type { ReactNode } from "react";
import { SignInButton, SignedIn, SignedOut, isClerkEnabled } from "@/auth/clerk";
import {
SignInButton,
SignedIn,
SignedOut,
isClerkEnabled,
} from "@/auth/clerk";
import { UserMenu } from "@/components/organisms/UserMenu";

View File

@@ -11,23 +11,22 @@ const badgeVariants = cva(
default: "bg-[color:var(--surface-muted)] text-strong",
outline:
"border border-[color:var(--border-strong)] text-[color:var(--text-muted)]",
accent: "bg-[color:var(--accent-soft)] text-[color:var(--accent-strong)]",
success:
"bg-[color:rgba(15,118,110,0.14)] text-[color:var(--success)]",
warning:
"bg-[color:rgba(180,83,9,0.15)] text-[color:var(--warning)]",
danger:
"bg-[color:rgba(180,35,24,0.15)] text-[color:var(--danger)]",
accent:
"bg-[color:var(--accent-soft)] text-[color:var(--accent-strong)]",
success: "bg-[color:rgba(15,118,110,0.14)] text-[color:var(--success)]",
warning: "bg-[color:rgba(180,83,9,0.15)] text-[color:var(--warning)]",
danger: "bg-[color:rgba(180,35,24,0.15)] text-[color:var(--danger)]",
},
},
defaultVariants: {
variant: "default",
},
}
},
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
extends
React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {

View File

@@ -16,7 +16,8 @@ const buttonVariants = cva(
"border border-[color:var(--border)] bg-[color:var(--surface)] text-strong hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]",
outline:
"border border-[color:var(--border-strong)] bg-transparent text-strong hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]",
ghost: "bg-transparent text-strong hover:bg-[color:var(--surface-strong)]",
ghost:
"bg-transparent text-strong hover:bg-[color:var(--surface-strong)]",
},
size: {
sm: "h-9 px-4",
@@ -28,11 +29,12 @@ const buttonVariants = cva(
variant: "primary",
size: "md",
},
}
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
extends
React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
@@ -42,7 +44,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
),
);
Button.displayName = "Button";

View File

@@ -2,15 +2,16 @@ import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-2xl surface-card", className)}
{...props}
/>
)
);
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-2xl surface-card", className)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<

View File

@@ -18,7 +18,7 @@ const DialogOverlay = React.forwardRef<
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-slate-950/40 backdrop-blur-[2px] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
className,
)}
{...props}
/>
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
ref={ref}
className={cn(
"w-full max-w-2xl max-h-[calc(100vh-2rem)] overscroll-contain overflow-y-auto rounded-3xl border border-[color:var(--border)] bg-[color:var(--surface)] p-6 shadow-lush focus:outline-none supports-[height:100dvh]:max-h-[calc(100dvh-2rem)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
className
className,
)}
{...props}
>

View File

@@ -4,7 +4,11 @@ import * as React from "react";
import { Check, ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Command,
CommandEmpty,

View File

@@ -2,19 +2,20 @@ import * as React from "react";
import { cn } from "@/lib/utils";
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
({ className, type, ...props }, ref) => (
<input
ref={ref}
type={type}
className={cn(
"flex h-11 w-full rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-4 text-sm text-strong shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)]",
className
)}
{...props}
/>
)
);
const Input = React.forwardRef<
HTMLInputElement,
React.InputHTMLAttributes<HTMLInputElement>
>(({ className, type, ...props }, ref) => (
<input
ref={ref}
type={type}
className={cn(
"flex h-11 w-full rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-4 text-sm text-strong shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)]",
className,
)}
{...props}
/>
));
Input.displayName = "Input";
export { Input };

View File

@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
type="button"
className={cn(
"flex h-11 w-full cursor-pointer items-center justify-between rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-4 text-sm text-strong shadow-sm focus:outline-none focus:ring-2 focus:ring-[color:var(--accent)] focus:ring-offset-2",
className
className,
)}
{...props}
>
@@ -37,7 +37,10 @@ const SelectScrollUpButton = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn("flex cursor-pointer items-center justify-center py-1", className)}
className={cn(
"flex cursor-pointer items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
@@ -51,7 +54,10 @@ const SelectScrollDownButton = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn("flex cursor-pointer items-center justify-center py-1", className)}
className={cn(
"flex cursor-pointer items-center justify-center py-1",
className,
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
@@ -97,7 +103,10 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold text-strong", className)}
className={cn(
"py-1.5 pl-8 pr-2 text-sm font-semibold text-strong",
className,
)}
{...props}
/>
));

View File

@@ -15,7 +15,7 @@ const TabsList = React.forwardRef<
ref={ref}
className={cn(
"inline-flex items-center gap-2 rounded-full border border-[color:var(--border)] bg-[color:var(--surface)] p-1",
className
className,
)}
{...props}
/>
@@ -30,7 +30,7 @@ const TabsTrigger = React.forwardRef<
ref={ref}
className={cn(
"rounded-full px-4 py-2 text-xs font-semibold text-muted transition data-[state=active]:bg-[color:var(--accent)] data-[state=active]:text-white",
className
className,
)}
{...props}
/>

View File

@@ -10,7 +10,7 @@ const Textarea = React.forwardRef<
ref={ref}
className={cn(
"min-h-[120px] w-full rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-4 py-3 text-sm text-strong shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--accent)]",
className
className,
)}
{...props}
/>

View File

@@ -18,7 +18,7 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"rounded-lg bg-slate-900 px-3 py-2 text-xs font-semibold text-white shadow-lg",
className
className,
)}
{...props}
/>