tr]:last:border-b-0',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
+ return (
+
+ );
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
+ return (
+ [role=checkbox]]:translate-y-[2px]',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
+ return (
+ | [role=checkbox]]:translate-y-[2px]',
+ className,
+ )}
+ {...props}
+ />
+ );
+}
+
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<'caption'>) {
+ return (
+
+ );
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+};
diff --git a/src/components/ui/toast-context.tsx b/src/components/ui/toast-context.tsx
new file mode 100644
index 0000000000..265fce8668
--- /dev/null
+++ b/src/components/ui/toast-context.tsx
@@ -0,0 +1,65 @@
+'use client';
+
+import * as React from 'react';
+
+import type { ToastActionElement, ToastProps } from '@/components/ui/toast';
+
+type ToastType = ToastProps & {
+ id: string;
+ title?: React.ReactNode;
+ description?: React.ReactNode;
+ action?: ToastActionElement;
+};
+
+const ToastContext = React.createContext<{
+ toasts: ToastType[];
+ addToast: (props: Omit) => void;
+ removeToast: (id: string) => void;
+}>({
+ toasts: [],
+ addToast: () => {},
+ removeToast: () => {},
+});
+
+export function ToastProvider({ children }: { children: React.ReactNode }) {
+ const [toasts, setToasts] = React.useState([]);
+
+ const addToast = React.useCallback(
+ (props: Omit) => {
+ const id = Math.random().toString(36).substring(2, 9);
+ setToasts((prev) => [...prev, { id, ...props }]);
+
+ // Auto-dismiss after 5 seconds
+ setTimeout(() => {
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
+ }, 5000);
+ },
+ [setToasts],
+ );
+
+ const removeToast = React.useCallback(
+ (id: string) => {
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
+ },
+ [setToasts],
+ );
+
+ const contextValue = React.useMemo(
+ () => ({ toasts, addToast, removeToast }),
+ [toasts, addToast, removeToast],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useToast = () => {
+ const context = React.useContext(ToastContext);
+ if (context === undefined) {
+ throw new Error('useToast must be used within a ToastProvider');
+ }
+ return context;
+};
diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx
new file mode 100644
index 0000000000..86110ed833
--- /dev/null
+++ b/src/components/ui/toast.tsx
@@ -0,0 +1,129 @@
+'use client';
+
+import { Cross2Icon } from '@radix-ui/react-icons';
+import * as ToastPrimitives from '@radix-ui/react-toast';
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+const ToastProvider = ToastPrimitives.Provider;
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
+
+const toastVariants = cva(
+ 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
+ {
+ variants: {
+ variant: {
+ default: 'border bg-background text-foreground',
+ destructive:
+ 'destructive group border-destructive bg-destructive text-destructive-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+);
+
+const Toast = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, ...props }, ref) => {
+ return (
+
+ );
+});
+Toast.displayName = ToastPrimitives.Root.displayName;
+
+const ToastAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+ToastAction.displayName = ToastPrimitives.Action.displayName;
+
+const ToastClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+ToastClose.displayName = ToastPrimitives.Close.displayName;
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+ToastTitle.displayName = ToastPrimitives.Title.displayName;
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+ToastDescription.displayName = ToastPrimitives.Description.displayName;
+
+type ToastProps = React.ComponentPropsWithoutRef;
+
+type ToastActionElement = React.ReactElement;
+
+export {
+ Toast,
+ ToastAction,
+ type ToastActionElement,
+ ToastClose,
+ ToastDescription,
+ type ToastProps,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+};
diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx
new file mode 100644
index 0000000000..0f6483f7dc
--- /dev/null
+++ b/src/components/ui/toaster.tsx
@@ -0,0 +1,28 @@
+'use client';
+
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastTitle,
+} from '@/components/ui/toast';
+import { useToast } from '@/components/ui/toast-context';
+
+export function Toaster() {
+ const { toasts } = useToast();
+
+ return (
+ <>
+ {toasts.map(({ id, title, description, action, ...props }) => (
+
+
+ {title && {title}}
+ {description && {description}}
+
+ {action}
+
+
+ ))}
+ >
+ );
+}
diff --git a/src/features/analytics/AnalyticsPage.tsx b/src/features/analytics/AnalyticsPage.tsx
new file mode 100644
index 0000000000..49784f28b0
--- /dev/null
+++ b/src/features/analytics/AnalyticsPage.tsx
@@ -0,0 +1,943 @@
+'use client';
+
+import type { ColumnDef } from '@tanstack/react-table';
+import { X } from 'lucide-react';
+import { useTranslations } from 'next-intl';
+import React, { useMemo, useState } from 'react';
+
+import { Button } from '@/components/ui/button';
+import { DataTable } from '@/components/ui/data-table';
+
+// Types
+type TimePeriod = '7' | '30' | '90';
+type ViewMode = 'developers' | 'models' | 'tasks';
+
+// Mock data types
+type Developer = {
+ id: string;
+ name: string;
+ email: string;
+ tasksStarted: number;
+ tasksCompleted: number;
+ tokensConsumed: number;
+ cost: number;
+};
+
+type Model = {
+ id: string;
+ name: string;
+ provider: string;
+ tasks: number;
+ tokensConsumed: number;
+ cost: number;
+};
+
+type ConversationMessage = {
+ id: string;
+ role: 'user' | 'assistant' | 'system';
+ content: string;
+ timestamp: Date;
+};
+
+type Task = {
+ id: string;
+ date: Date;
+ developerId: string;
+ developerName: string;
+ modelId: string;
+ modelName: string;
+ tokensConsumed: number;
+ cost: number;
+ status: 'completed' | 'started' | 'failed';
+ conversation: ConversationMessage[];
+};
+
+type SummaryData = {
+ activeDevelopers: number;
+ tasksStarted: number;
+ tasksCompleted: number;
+ tokensConsumed: string;
+ costs: number;
+};
+
+type Filter = {
+ type: 'developer' | 'model';
+ id: string;
+ name: string;
+} | null;
+
+// Generate mock conversation data
+const generateMockConversation = (taskId: string): ConversationMessage[] => {
+ const baseConversations: Record = {
+ task1: [
+ {
+ id: 'msg1',
+ role: 'user',
+ content:
+ 'Create a React component that displays a list of items with pagination.',
+ timestamp: new Date('2025-05-01T10:00:00'),
+ },
+ {
+ id: 'msg2',
+ role: 'assistant',
+ content:
+ "I'll create a React component for displaying a paginated list. Here's how we can implement it...",
+ timestamp: new Date('2025-05-01T10:00:30'),
+ },
+ {
+ id: 'msg3',
+ role: 'user',
+ content: 'Can you add sorting functionality as well?',
+ timestamp: new Date('2025-05-01T10:02:00'),
+ },
+ {
+ id: 'msg4',
+ role: 'assistant',
+ content:
+ "Certainly! I'll add sorting functionality to the component. Here's the updated implementation...",
+ timestamp: new Date('2025-05-01T10:02:30'),
+ },
+ ],
+ task2: [
+ {
+ id: 'msg1',
+ role: 'user',
+ content:
+ 'Write a function to calculate the Fibonacci sequence recursively.',
+ timestamp: new Date('2025-05-02T11:30:00'),
+ },
+ {
+ id: 'msg2',
+ role: 'assistant',
+ content:
+ "Here's a recursive function to calculate the Fibonacci sequence in JavaScript...",
+ timestamp: new Date('2025-05-02T11:30:30'),
+ },
+ {
+ id: 'msg3',
+ role: 'user',
+ content: 'Can you optimize it to avoid redundant calculations?',
+ timestamp: new Date('2025-05-02T11:32:00'),
+ },
+ {
+ id: 'msg4',
+ role: 'assistant',
+ content:
+ "Yes, we can optimize it using memoization to avoid redundant calculations. Here's the optimized version...",
+ timestamp: new Date('2025-05-02T11:32:30'),
+ },
+ ],
+ };
+
+ // Return specific conversation if available, otherwise generate a generic one
+ if (baseConversations[taskId]) {
+ return baseConversations[taskId];
+ }
+
+ // Generic conversation
+ return [
+ {
+ id: `${taskId}-msg1`,
+ role: 'user',
+ content: 'I need help with a coding task.',
+ timestamp: new Date(),
+ },
+ {
+ id: `${taskId}-msg2`,
+ role: 'assistant',
+ content:
+ "I'd be happy to help! What kind of coding task are you working on?",
+ timestamp: new Date(Date.now() + 30000),
+ },
+ {
+ id: `${taskId}-msg3`,
+ role: 'user',
+ content: "I'm trying to implement a feature in my application.",
+ timestamp: new Date(Date.now() + 60000),
+ },
+ {
+ id: `${taskId}-msg4`,
+ role: 'assistant',
+ content:
+ 'I can help with that. Let me provide some guidance on implementing that feature...',
+ timestamp: new Date(Date.now() + 90000),
+ },
+ ];
+};
+
+// Mock data
+const mockDevelopers: Developer[] = [
+ {
+ id: 'dev1',
+ name: 'John Doe',
+ email: 'john@example.com',
+ tasksStarted: 42,
+ tasksCompleted: 38,
+ tokensConsumed: 1200000,
+ cost: 24.5,
+ },
+ {
+ id: 'dev2',
+ name: 'Jane Smith',
+ email: 'jane@example.com',
+ tasksStarted: 35,
+ tasksCompleted: 32,
+ tokensConsumed: 980000,
+ cost: 19.6,
+ },
+ {
+ id: 'dev3',
+ name: 'Bob Johnson',
+ email: 'bob@example.com',
+ tasksStarted: 28,
+ tasksCompleted: 25,
+ tokensConsumed: 750000,
+ cost: 15.0,
+ },
+ {
+ id: 'dev4',
+ name: 'Alice Williams',
+ email: 'alice@example.com',
+ tasksStarted: 31,
+ tasksCompleted: 29,
+ tokensConsumed: 820000,
+ cost: 16.4,
+ },
+ {
+ id: 'dev5',
+ name: 'Charlie Brown',
+ email: 'charlie@example.com',
+ tasksStarted: 22,
+ tasksCompleted: 19,
+ tokensConsumed: 650000,
+ cost: 13.0,
+ },
+];
+
+const mockModels: Model[] = [
+ {
+ id: 'model1',
+ name: 'GPT-4',
+ provider: 'OpenAI',
+ tasks: 45,
+ tokensConsumed: 1500000,
+ cost: 30.0,
+ },
+ {
+ id: 'model2',
+ name: 'Claude 3 Opus',
+ provider: 'Anthropic',
+ tasks: 35,
+ tokensConsumed: 1000000,
+ cost: 20.0,
+ },
+ {
+ id: 'model3',
+ name: 'Mistral Large',
+ provider: 'Mistral AI',
+ tasks: 25,
+ tokensConsumed: 430000,
+ cost: 9.1,
+ },
+ {
+ id: 'model4',
+ name: 'GPT-3.5 Turbo',
+ provider: 'OpenAI',
+ tasks: 38,
+ tokensConsumed: 850000,
+ cost: 8.5,
+ },
+ {
+ id: 'model5',
+ name: 'Claude 3 Sonnet',
+ provider: 'Anthropic',
+ tasks: 30,
+ tokensConsumed: 720000,
+ cost: 14.4,
+ },
+];
+
+const mockTasks: Task[] = [
+ {
+ id: 'task1',
+ date: new Date('2025-05-01T10:00:00'),
+ developerId: 'dev1',
+ developerName: 'John Doe',
+ modelId: 'model1',
+ modelName: 'GPT-4',
+ tokensConsumed: 15000,
+ cost: 0.3,
+ status: 'completed',
+ conversation: generateMockConversation('task1'),
+ },
+ {
+ id: 'task2',
+ date: new Date('2025-05-02T11:30:00'),
+ developerId: 'dev2',
+ developerName: 'Jane Smith',
+ modelId: 'model2',
+ modelName: 'Claude 3 Opus',
+ tokensConsumed: 12000,
+ cost: 0.24,
+ status: 'completed',
+ conversation: generateMockConversation('task2'),
+ },
+ {
+ id: 'task3',
+ date: new Date('2025-05-03T14:15:00'),
+ developerId: 'dev3',
+ developerName: 'Bob Johnson',
+ modelId: 'model3',
+ modelName: 'Mistral Large',
+ tokensConsumed: 8000,
+ cost: 0.16,
+ status: 'completed',
+ conversation: generateMockConversation('task3'),
+ },
+ {
+ id: 'task4',
+ date: new Date('2025-05-04T09:45:00'),
+ developerId: 'dev1',
+ developerName: 'John Doe',
+ modelId: 'model2',
+ modelName: 'Claude 3 Opus',
+ tokensConsumed: 10000,
+ cost: 0.2,
+ status: 'completed',
+ conversation: generateMockConversation('task4'),
+ },
+ {
+ id: 'task5',
+ date: new Date('2025-05-05T16:30:00'),
+ developerId: 'dev2',
+ developerName: 'Jane Smith',
+ modelId: 'model1',
+ modelName: 'GPT-4',
+ tokensConsumed: 18000,
+ cost: 0.36,
+ status: 'started',
+ conversation: generateMockConversation('task5'),
+ },
+ {
+ id: 'task6',
+ date: new Date('2025-05-06T13:20:00'),
+ developerId: 'dev4',
+ developerName: 'Alice Williams',
+ modelId: 'model4',
+ modelName: 'GPT-3.5 Turbo',
+ tokensConsumed: 7500,
+ cost: 0.08,
+ status: 'completed',
+ conversation: generateMockConversation('task6'),
+ },
+ {
+ id: 'task7',
+ date: new Date('2025-05-07T10:10:00'),
+ developerId: 'dev5',
+ developerName: 'Charlie Brown',
+ modelId: 'model5',
+ modelName: 'Claude 3 Sonnet',
+ tokensConsumed: 9200,
+ cost: 0.18,
+ status: 'failed',
+ conversation: generateMockConversation('task7'),
+ },
+ {
+ id: 'task8',
+ date: new Date('2025-05-07T15:45:00'),
+ developerId: 'dev3',
+ developerName: 'Bob Johnson',
+ modelId: 'model1',
+ modelName: 'GPT-4',
+ tokensConsumed: 14000,
+ cost: 0.28,
+ status: 'completed',
+ conversation: generateMockConversation('task8'),
+ },
+];
+
+// Component for summary metrics
+const SummaryMetrics = ({
+ data,
+ timePeriod,
+ setTimePeriod,
+}: {
+ data: SummaryData;
+ timePeriod: TimePeriod;
+ setTimePeriod: (period: TimePeriod) => void;
+}) => {
+ const t = useTranslations('Analytics');
+
+ return (
+
+
+ {t('summary_title')}
+
+
+ {/* Time period toggle */}
+
+
+
+
+
+
+ {/* Metrics grid */}
+
+ {/* Active Developers */}
+
+
+ {t('active_developers')}
+
+
+ {data.activeDevelopers}
+
+
+
+ {/* Tasks Started */}
+
+
+ {t('tasks_started')}
+
+ {data.tasksStarted}
+
+
+ {/* Tasks Completed */}
+
+
+ {t('tasks_completed')}
+
+
+ {data.tasksCompleted}
+
+
+
+ {/* Tokens Consumed */}
+
+
+ {t('tokens_consumed')}
+
+
+ {data.tokensConsumed}
+
+
+
+ {/* LLM Model Costs */}
+
+ {t('llm_costs')}
+
+ ${data.costs.toFixed(2)}
+
+
+
+
+ );
+};
+
+// Component for view mode toggle
+const ViewModeToggle = ({
+ viewMode,
+ setViewMode,
+}: {
+ viewMode: ViewMode;
+ setViewMode: (mode: ViewMode) => void;
+}) => {
+ const t = useTranslations('Analytics');
+
+ return (
+
+ {t('view_mode_title')}
+
+
+
+
+
+
+ );
+};
+
+// Component for active filter display
+const ActiveFilter = ({
+ filter,
+ onClear,
+}: {
+ filter: Filter;
+ onClear: () => void;
+}) => {
+ if (!filter) {
+ return null;
+ }
+
+ return (
+
+
+ Filtered by {filter.type}: {filter.name}
+
+
+
+ );
+};
+
+// Component for task details drawer
+const TaskDetailsDrawer = ({
+ task,
+ isOpen,
+ onClose,
+}: {
+ task: Task | null;
+ isOpen: boolean;
+ onClose: () => void;
+}) => {
+ if (!isOpen || !task) {
+ return null;
+ }
+
+ return (
+
+
+
+ Task Details
+
+
+
+
+
+ Task ID:
+ {task.id}
+
+
+ Date:
+ {task.date.toLocaleString()}
+
+
+ Developer:
+ {task.developerName}
+
+
+ Model:
+ {task.modelName}
+
+
+ Tokens:
+
+ {task.tokensConsumed.toLocaleString()}
+
+
+
+ Cost:
+ ${task.cost.toFixed(2)}
+
+
+ Status:
+
+ {task.status.charAt(0).toUpperCase() + task.status.slice(1)}
+
+
+
+
+ Conversation
+
+ {task.conversation.map((message) => (
+
+
+ {message.role.charAt(0).toUpperCase() + message.role.slice(1)} •
+ {message.timestamp.toLocaleTimeString()}
+
+ {message.content}
+
+ ))}
+
+
+
+ );
+};
+
+// Main component
+export const AnalyticsPage = () => {
+ const [timePeriod, setTimePeriod] = useState('7');
+ const [viewMode, setViewMode] = useState('tasks');
+ const [filter, setFilter] = useState(null);
+ const [selectedTask, setSelectedTask] = useState(null);
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
+
+ // Generate summary data based on time period
+ const summaryData = useMemo(() => {
+ // In a real implementation, this would fetch data from an API
+ // For now, we'll use mock data with different values for each time period
+ switch (timePeriod) {
+ case '7':
+ return {
+ activeDevelopers: 8,
+ tasksStarted: 42,
+ tasksCompleted: 38,
+ tokensConsumed: '1.2M',
+ costs: 24.5,
+ };
+ case '30':
+ return {
+ activeDevelopers: 12,
+ tasksStarted: 187,
+ tasksCompleted: 165,
+ tokensConsumed: '5.8M',
+ costs: 112.75,
+ };
+ case '90':
+ return {
+ activeDevelopers: 15,
+ tasksStarted: 563,
+ tasksCompleted: 498,
+ tokensConsumed: '18.3M',
+ costs: 347.2,
+ };
+ }
+ }, [timePeriod]);
+
+ // Handle developer click
+ const handleDeveloperClick = (developer: Developer) => {
+ setFilter({
+ type: 'developer',
+ id: developer.id,
+ name: developer.name,
+ });
+ setViewMode('tasks');
+ };
+
+ // Handle model click
+ const handleModelClick = (model: Model) => {
+ setFilter({
+ type: 'model',
+ id: model.id,
+ name: model.name,
+ });
+ setViewMode('tasks');
+ };
+
+ // Handle task click
+ const handleTaskClick = (task: Task) => {
+ setSelectedTask(task);
+ setIsDrawerOpen(true);
+ };
+
+ // Clear filter
+ const clearFilter = () => {
+ setFilter(null);
+ };
+
+ // Close drawer
+ const closeDrawer = () => {
+ setIsDrawerOpen(false);
+ setSelectedTask(null);
+ };
+
+ // Data tables for each view mode
+ const DevelopersTable = () => {
+ const developerColumns: ColumnDef[] = [
+ {
+ accessorKey: 'name',
+ header: 'Developer',
+ cell: ({ row }) => {
+ const developer = row.original;
+ return (
+
+ );
+ },
+ },
+ {
+ accessorKey: 'email',
+ header: 'Email',
+ },
+ {
+ accessorKey: 'tasksStarted',
+ header: 'Tasks Started',
+ },
+ {
+ accessorKey: 'tasksCompleted',
+ header: 'Tasks Completed',
+ },
+ {
+ accessorKey: 'tokensConsumed',
+ header: 'Tokens',
+ cell: ({ row }) => {
+ const tokens = row.getValue('tokensConsumed') as number;
+ return tokens >= 1000000
+ ? `${(tokens / 1000000).toFixed(1)}M`
+ : tokens >= 1000
+ ? `${(tokens / 1000).toFixed(1)}K`
+ : tokens;
+ },
+ },
+ {
+ accessorKey: 'cost',
+ header: 'Cost (USD)',
+ cell: ({ row }) => {
+ const cost = row.getValue('cost') as number;
+ return `$${cost.toFixed(2)}`;
+ },
+ },
+ ];
+
+ return ;
+ };
+
+ const ModelsTable = () => {
+ const modelColumns: ColumnDef[] = [
+ {
+ accessorKey: 'name',
+ header: 'Model',
+ cell: ({ row }) => {
+ const model = row.original;
+ return (
+
+ );
+ },
+ },
+ {
+ accessorKey: 'provider',
+ header: 'Provider',
+ },
+ {
+ accessorKey: 'tasks',
+ header: 'Tasks',
+ },
+ {
+ accessorKey: 'tokensConsumed',
+ header: 'Tokens',
+ cell: ({ row }) => {
+ const tokens = row.getValue('tokensConsumed') as number;
+ return tokens >= 1000000
+ ? `${(tokens / 1000000).toFixed(1)}M`
+ : tokens >= 1000
+ ? `${(tokens / 1000).toFixed(1)}K`
+ : tokens;
+ },
+ },
+ {
+ accessorKey: 'cost',
+ header: 'Cost (USD)',
+ cell: ({ row }) => {
+ const cost = row.getValue('cost') as number;
+ return `$${cost.toFixed(2)}`;
+ },
+ },
+ ];
+
+ return ;
+ };
+
+ const TasksTable = () => {
+ // Filter tasks based on the active filter
+ const filteredTasks = useMemo(() => {
+ if (!filter) {
+ return mockTasks;
+ }
+
+ return mockTasks.filter((task) =>
+ filter.type === 'developer'
+ ? task.developerId === filter.id
+ : task.modelId === filter.id,
+ );
+ }, []);
+
+ const taskColumns: ColumnDef[] = [
+ {
+ accessorKey: 'id',
+ header: 'Task ID',
+ cell: ({ row }) => {
+ const task = row.original;
+ return (
+
+ );
+ },
+ },
+ {
+ accessorKey: 'date',
+ header: 'Date',
+ cell: ({ row }) => {
+ const date = row.getValue('date') as Date;
+ return date.toLocaleString();
+ },
+ },
+ {
+ accessorKey: 'developerName',
+ header: 'Developer',
+ },
+ {
+ accessorKey: 'modelName',
+ header: 'Model',
+ },
+ {
+ accessorKey: 'tokensConsumed',
+ header: 'Tokens',
+ cell: ({ row }) => {
+ const tokens = row.getValue('tokensConsumed') as number;
+ return tokens >= 1000000
+ ? `${(tokens / 1000000).toFixed(1)}M`
+ : tokens >= 1000
+ ? `${(tokens / 1000).toFixed(1)}K`
+ : tokens;
+ },
+ },
+ {
+ accessorKey: 'cost',
+ header: 'Cost (USD)',
+ cell: ({ row }) => {
+ const cost = row.getValue('cost') as number;
+ return `$${cost.toFixed(2)}`;
+ },
+ },
+ {
+ accessorKey: 'status',
+ header: 'Status',
+ cell: ({ row }) => {
+ const status = row.getValue('status') as string;
+ return (
+
+ {status.charAt(0).toUpperCase() + status.slice(1)}
+
+ );
+ },
+ },
+ ];
+
+ return ;
+ };
+
+ // Render the appropriate table based on view mode
+ const renderTable = () => {
+ switch (viewMode) {
+ case 'developers':
+ return ;
+ case 'models':
+ return ;
+ case 'tasks':
+ return ;
+ }
+ };
+
+ return (
+
+ {/* Summary metrics */}
+
+
+ {/* View mode toggle */}
+
+
+ {/* Active filter */}
+ {filter && }
+
+ {/* Data table */}
+
+
+
+ {viewMode === 'developers'
+ ? 'Developers'
+ : viewMode === 'models'
+ ? 'Models'
+ : 'Tasks'}
+
+
+ {renderTable()}
+
+
+ {/* Task details drawer */}
+
+
+ );
+};
diff --git a/src/features/dashboard/AuditLogCard.tsx b/src/features/dashboard/AuditLogCard.tsx
new file mode 100644
index 0000000000..07cb7a4c75
--- /dev/null
+++ b/src/features/dashboard/AuditLogCard.tsx
@@ -0,0 +1,72 @@
+'use client';
+
+import Link from 'next/link';
+import React, { useState } from 'react';
+
+import { Drawer } from '@/components/ui/drawer';
+
+import { AuditLogDetails } from './AuditLogDetails';
+import { AuditLogEntry } from './AuditLogEntry';
+import type { AuditLog } from './mockAuditLogs';
+import { mockAuditLogs } from './mockAuditLogs';
+
+export function AuditLogCard() {
+ // Always show the most recent 5 entries
+ const [selectedLog, setSelectedLog] = useState(null);
+ const [isDrawerOpen, setIsDrawerOpen] = useState(false);
+
+ // Get the 5 most recent logs
+ const logs = mockAuditLogs.slice(0, 5);
+
+ const handleLogClick = (log: AuditLog) => {
+ setSelectedLog(log);
+ setIsDrawerOpen(true);
+ };
+
+ const handleCloseDrawer = () => {
+ setIsDrawerOpen(false);
+ };
+
+ return (
+
+
+ Recent Activity
+
+ Organization audit logs and changes
+
+
+
+ {/* Log entries */}
+
+ {logs.length > 0 ? (
+ logs.map((log: AuditLog) => (
+
+ ))
+ ) : (
+
+ )}
+
+
+ {/* "See all logs" link */}
+
+
+ See all logs
+
+
+
+ {/* Drawer for log details */}
+
+ {selectedLog && }
+
+
+ );
+}
diff --git a/src/features/dashboard/AuditLogDetails.tsx b/src/features/dashboard/AuditLogDetails.tsx
new file mode 100644
index 0000000000..38ad9d1ee4
--- /dev/null
+++ b/src/features/dashboard/AuditLogDetails.tsx
@@ -0,0 +1,98 @@
+'use client';
+
+import { ArrowRight, Calendar, Clock, User } from 'lucide-react';
+import Link from 'next/link';
+import React from 'react';
+
+import type { AuditLog } from './mockAuditLogs';
+
+type AuditLogDetailsProps = {
+ log: AuditLog;
+};
+
+const formatValue = (value: unknown): React.ReactNode => {
+ if (value === null || value === undefined) {
+ return None;
+ }
+
+ if (Array.isArray(value)) {
+ return (
+
+ {value.map((item, index) => (
+ - {String(item)}
+ ))}
+
+ );
+ }
+
+ if (typeof value === 'object') {
+ return (
+
+ {Object.entries(value).map(([key, val]) => (
+
+ {key}:
+ {String(val)}
+
+ ))}
+
+ );
+ }
+
+ return String(value);
+};
+
+export function AuditLogDetails({ log }: AuditLogDetailsProps) {
+ return (
+
+ {/* Header information */}
+
+
+
+ {log.timestamp.toLocaleDateString()}
+
+ {log.timestamp.toLocaleTimeString()}
+
+
+
+
+ {log.user}
+
+
+ {log.path && (
+
+ View in settings
+
+
+ )}
+
+
+ {/* Change details */}
+
+ Changes
+
+
+
+
+ Before
+
+
+ {formatValue(log.details.before)}
+
+
+
+
+
+ After
+
+
+ {formatValue(log.details.after)}
+
+
+
+
+
+ );
+}
diff --git a/src/features/dashboard/AuditLogEntry.tsx b/src/features/dashboard/AuditLogEntry.tsx
new file mode 100644
index 0000000000..86e26c1aa9
--- /dev/null
+++ b/src/features/dashboard/AuditLogEntry.tsx
@@ -0,0 +1,110 @@
+'use client';
+
+import { Settings, Sliders, Users } from 'lucide-react';
+import React from 'react';
+
+import { cn } from '@/lib/utils';
+
+import type { AuditLog, AuditLogType } from './mockAuditLogs';
+import { getFormattedTime } from './mockAuditLogs';
+
+type AuditLogEntryProps = {
+ log: AuditLog;
+ onClick: (log: AuditLog) => void;
+};
+
+const getIconByType = (type: AuditLogType) => {
+ switch (type) {
+ case 'provider_whitelist':
+ return ;
+ case 'default_parameters':
+ return ;
+ case 'member_change':
+ return ;
+ default:
+ return ;
+ }
+};
+
+export function AuditLogEntry({ log, onClick }: AuditLogEntryProps) {
+ return (
+
+ );
+}
diff --git a/src/features/dashboard/DefaultParametersPage.tsx b/src/features/dashboard/DefaultParametersPage.tsx
new file mode 100644
index 0000000000..d962c250d9
--- /dev/null
+++ b/src/features/dashboard/DefaultParametersPage.tsx
@@ -0,0 +1,793 @@
+/* eslint-disable react/no-unescaped-entities */
+
+'use client';
+
+import { useTranslations } from 'next-intl';
+import { useState } from 'react';
+import { useForm } from 'react-hook-form';
+
+import { Button } from '@/components/ui/button';
+import { Checkbox } from '@/components/ui/checkbox';
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+} from '@/components/ui/form';
+import { Input } from '@/components/ui/input';
+import { Slider } from '@/components/ui/slider';
+import { useToast } from '@/components/ui/toast-context';
+
+// Default parameters form type
+type DefaultParamsFormValues = {
+ experimentalPowerSteering: boolean;
+ terminalOutputLimit: number;
+ compressProgressBar: boolean;
+ inheritEnvVars: boolean;
+ disableShellIntegration: boolean;
+ shellIntegrationTimeout: number;
+ commandDelay: number;
+ enablePowerShellCounter: boolean;
+ clearZshEol: boolean;
+ enableOhMyZsh: boolean;
+ enablePowerlevel10k: boolean;
+ openTabsLimit: number;
+ workspaceFilesLimit: number;
+ showRooignoreFiles: boolean;
+ fileReadThreshold: number;
+ alwaysReadEntireFile: boolean;
+ enableAutoCheckpoints: boolean;
+ useCustomTemperature: boolean;
+ temperature: number;
+ rateLimit: number;
+ enableEditingThroughDiffs: boolean;
+ matchPrecision: number;
+};
+
+const DefaultParametersPage = () => {
+ const t = useTranslations('ProviderWhitelist');
+ const { addToast } = useToast();
+
+ const [isSaving, setIsSaving] = useState(false);
+
+ // Form for default parameters
+ const form = useForm({
+ defaultValues: {
+ experimentalPowerSteering: true,
+ terminalOutputLimit: 500,
+ compressProgressBar: true,
+ inheritEnvVars: true,
+ disableShellIntegration: false,
+ shellIntegrationTimeout: 5,
+ commandDelay: 0,
+ enablePowerShellCounter: false,
+ clearZshEol: true,
+ enableOhMyZsh: false,
+ enablePowerlevel10k: false,
+ openTabsLimit: 20,
+ workspaceFilesLimit: 200,
+ showRooignoreFiles: true,
+ fileReadThreshold: 500,
+ alwaysReadEntireFile: false,
+ enableAutoCheckpoints: true,
+ useCustomTemperature: true,
+ temperature: 0,
+ rateLimit: 0,
+ enableEditingThroughDiffs: true,
+ matchPrecision: 100,
+ },
+ });
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const onSubmit = (_data: DefaultParamsFormValues) => {
+ setIsSaving(true);
+
+ // Simulate API call
+ setTimeout(() => {
+ // Show success toast
+ addToast({
+ title: 'Settings saved',
+ description: 'Default parameters have been updated successfully.',
+ variant: 'default',
+ });
+
+ setIsSaving(false);
+ }, 1000);
+ };
+
+ return (
+ <>
+
+
+
+ {t('parameters_section_title')}
+
+
+
+
+
+ {t('parameters_section_description')}
+
+
+
+ >
+ );
+};
+
+export default DefaultParametersPage;
diff --git a/src/features/dashboard/ProviderWhitelistPage.tsx b/src/features/dashboard/ProviderWhitelistPage.tsx
new file mode 100644
index 0000000000..103be5a8d0
--- /dev/null
+++ b/src/features/dashboard/ProviderWhitelistPage.tsx
@@ -0,0 +1,216 @@
+'use client';
+
+import { useTranslations } from 'next-intl';
+import { useState } from 'react';
+
+import { Badge } from '@/components/ui/badge';
+import { Checkbox } from '@/components/ui/checkbox';
+import { Label } from '@/components/ui/label';
+
+// Mock data for providers and models
+const initialProviders = [
+ {
+ id: 'openai',
+ name: 'OpenAI',
+ enabled: true,
+ models: [
+ { id: 'gpt-4', name: 'GPT-4', enabled: true },
+ { id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', enabled: true },
+ { id: 'gpt-4o', name: 'GPT-4o', enabled: false },
+ ],
+ },
+ {
+ id: 'anthropic',
+ name: 'Anthropic',
+ enabled: true,
+ models: [
+ { id: 'claude-3-opus', name: 'Claude 3 Opus', enabled: true },
+ { id: 'claude-3-sonnet', name: 'Claude 3 Sonnet', enabled: true },
+ { id: 'claude-3-haiku', name: 'Claude 3 Haiku', enabled: false },
+ ],
+ },
+ {
+ id: 'mistral',
+ name: 'Mistral AI',
+ enabled: false,
+ models: [
+ { id: 'mistral-large', name: 'Mistral Large', enabled: false },
+ { id: 'mistral-medium', name: 'Mistral Medium', enabled: false },
+ { id: 'mistral-small', name: 'Mistral Small', enabled: false },
+ ],
+ },
+ {
+ id: 'cohere',
+ name: 'Cohere',
+ enabled: false,
+ models: [
+ { id: 'command-r', name: 'Command R', enabled: false },
+ { id: 'command-r-plus', name: 'Command R+', enabled: false },
+ ],
+ },
+];
+
+const ProviderWhitelistPage = () => {
+ const t = useTranslations('ProviderWhitelist');
+
+ // State for providers and models
+ const [providers, setProviders] = useState(initialProviders);
+ const [policyVersion] = useState(1);
+ const [allowAllProviders, setAllowAllProviders] = useState(true);
+
+ // Toggle allow all providers
+ const toggleAllowAllProviders = () => {
+ const newAllowAllProviders = !allowAllProviders;
+ setAllowAllProviders(newAllowAllProviders);
+
+ if (newAllowAllProviders) {
+ // Enable all providers and their models
+ setProviders(
+ providers.map((provider) => ({
+ ...provider,
+ enabled: true,
+ models: provider.models.map((model) => ({
+ ...model,
+ enabled: true,
+ })),
+ })),
+ );
+ }
+ };
+
+ // Toggle provider enabled state
+ const toggleProvider = (providerId: string) => {
+ if (allowAllProviders) {
+ return;
+ }
+
+ setProviders(
+ providers.map((provider) => {
+ if (provider.id === providerId) {
+ const newEnabled = !provider.enabled;
+ return {
+ ...provider,
+ enabled: newEnabled,
+ // If provider is disabled, disable all its models
+ models: provider.models.map((model) => ({
+ ...model,
+ enabled: newEnabled ? model.enabled : false,
+ })),
+ };
+ }
+ return provider;
+ }),
+ );
+ };
+
+ // Toggle model enabled state
+ const toggleModel = (providerId: string, modelId: string) => {
+ if (allowAllProviders) {
+ return;
+ }
+
+ setProviders(
+ providers.map((provider) => {
+ if (provider.id === providerId) {
+ return {
+ ...provider,
+ models: provider.models.map((model) => {
+ if (model.id === modelId) {
+ return { ...model, enabled: !model.enabled };
+ }
+ return model;
+ }),
+ };
+ }
+ return provider;
+ }),
+ );
+ };
+
+ return (
+ <>
+
+
+
+ {t('providers_section_title')}
+
+
+
+
+
+ {t('providers_section_description')}
+
+
+
+
+
+
+
+
+
+
+ {providers.map((provider) => (
+
+
+ toggleProvider(provider.id)}
+ />
+
+
+
+
+ {provider.models.map((model) => (
+
+
+ toggleModel(provider.id, model.id)
+ }
+ />
+
+
+ ))}
+
+
+ ))}
+
+
+
+
+ {`Policy v${policyVersion}`}
+
+
+ Changes will be pushed to SSE stream within 30 seconds
+
+
+
+
+ >
+ );
+};
+
+export default ProviderWhitelistPage;
diff --git a/src/features/dashboard/UsageAnalyticsCard.tsx b/src/features/dashboard/UsageAnalyticsCard.tsx
new file mode 100644
index 0000000000..c12383437e
--- /dev/null
+++ b/src/features/dashboard/UsageAnalyticsCard.tsx
@@ -0,0 +1,152 @@
+'use client';
+
+import Link from 'next/link';
+import { useTranslations } from 'next-intl';
+import React, { useMemo, useState } from 'react';
+
+import { Button } from '@/components/ui/button';
+
+type TimePeriod = '7' | '30' | '90';
+
+type AnalyticsData = {
+ tasksStarted: number;
+ tasksCompleted: number;
+ tokensConsumed: string;
+ costs: number;
+ activeDevelopers: number;
+};
+
+export const UsageAnalyticsCard = () => {
+ const t = useTranslations('DashboardIndex');
+ const [timePeriod, setTimePeriod] = useState('7');
+
+ // Mock data based on selected time period
+ const analyticsData = useMemo(() => {
+ // Return different data based on timePeriod
+ switch (timePeriod) {
+ case '7':
+ return {
+ tasksStarted: 42,
+ tasksCompleted: 38,
+ tokensConsumed: '1.2M',
+ costs: 24.5,
+ activeDevelopers: 8,
+ };
+ case '30':
+ return {
+ tasksStarted: 187,
+ tasksCompleted: 165,
+ tokensConsumed: '5.8M',
+ costs: 112.75,
+ activeDevelopers: 12,
+ };
+ case '90':
+ return {
+ tasksStarted: 563,
+ tasksCompleted: 498,
+ tokensConsumed: '18.3M',
+ costs: 347.2,
+ activeDevelopers: 15,
+ };
+ }
+ }, [timePeriod]);
+
+ return (
+
+
+ {t('analytics_title')}
+
+ {t('analytics_description')}
+
+
+
+ {/* Time period toggle */}
+
+
+
+
+
+
+ {/* Metrics grid */}
+
+ {/* Active Developers */}
+
+
+ {t('analytics_active_developers')}
+
+
+ {analyticsData.activeDevelopers}
+
+
+
+ {/* Tasks Started */}
+
+
+ {t('analytics_tasks_started')}
+
+
+ {analyticsData.tasksStarted}
+
+
+
+ {/* Tasks Completed */}
+
+
+ {t('analytics_tasks_completed')}
+
+
+ {analyticsData.tasksCompleted}
+
+
+
+ {/* Tokens Consumed */}
+
+
+ {t('analytics_tokens_consumed')}
+
+
+ {analyticsData.tokensConsumed}
+
+
+
+ {/* LLM Model Costs */}
+
+
+ {t('analytics_llm_costs')}
+
+
+ ${analyticsData.costs.toFixed(2)}
+
+
+
+
+ {/* Link to detailed analytics */}
+
+
+ {t('analytics_view_details')}
+
+
+
+ );
+};
diff --git a/src/features/dashboard/mockAuditLogs.ts b/src/features/dashboard/mockAuditLogs.ts
new file mode 100644
index 0000000000..df57bcd39c
--- /dev/null
+++ b/src/features/dashboard/mockAuditLogs.ts
@@ -0,0 +1,144 @@
+export type AuditLogType =
+ | 'provider_whitelist'
+ | 'default_parameters'
+ | 'member_change';
+
+export type AuditLogDetails = {
+ before: unknown;
+ after: unknown;
+};
+
+export type AuditLog = {
+ id: string;
+ type: AuditLogType;
+ description: string;
+ timestamp: Date;
+ user: string;
+ details: AuditLogDetails;
+ path?: string;
+};
+
+export const mockAuditLogs: AuditLog[] = [
+ {
+ id: '1',
+ type: 'provider_whitelist',
+ description: 'Added OpenAI to provider whitelist',
+ timestamp: new Date(2025, 4, 10, 14, 30),
+ user: 'John Doe',
+ details: {
+ before: ['Anthropic', 'Cohere'],
+ after: ['Anthropic', 'Cohere', 'OpenAI'],
+ },
+ path: '/dashboard/organization-profile/provider-whitelist',
+ },
+ {
+ id: '2',
+ type: 'default_parameters',
+ description: 'Updated default temperature parameter',
+ timestamp: new Date(2025, 4, 9, 11, 15),
+ user: 'Jane Smith',
+ details: {
+ before: { temperature: 0.7 },
+ after: { temperature: 0.9 },
+ },
+ path: '/dashboard/organization-profile/default-parameters',
+ },
+ {
+ id: '3',
+ type: 'member_change',
+ description: 'Changed role for Alex Johnson from Member to Admin',
+ timestamp: new Date(2025, 4, 8, 9, 45),
+ user: 'Sarah Williams',
+ details: {
+ before: { role: 'Member' },
+ after: { role: 'Admin' },
+ },
+ path: '/dashboard/organization-profile/organization-members',
+ },
+ {
+ id: '4',
+ type: 'provider_whitelist',
+ description: 'Removed Claude from provider whitelist',
+ timestamp: new Date(2025, 4, 7, 16, 20),
+ user: 'Michael Brown',
+ details: {
+ before: ['OpenAI', 'Claude', 'Cohere'],
+ after: ['OpenAI', 'Cohere'],
+ },
+ path: '/dashboard/organization-profile/provider-whitelist',
+ },
+ {
+ id: '5',
+ type: 'default_parameters',
+ description: 'Updated max tokens parameter',
+ timestamp: new Date(2025, 4, 6, 13, 10),
+ user: 'Emily Davis',
+ details: {
+ before: { max_tokens: 1000 },
+ after: { max_tokens: 2000 },
+ },
+ path: '/dashboard/organization-profile/default-parameters',
+ },
+ {
+ id: '6',
+ type: 'member_change',
+ description: 'Added new member David Wilson',
+ timestamp: new Date(2025, 4, 5, 10, 30),
+ user: 'John Doe',
+ details: {
+ before: null,
+ after: {
+ name: 'David Wilson',
+ email: 'david@example.com',
+ role: 'Member',
+ },
+ },
+ path: '/dashboard/organization-profile/organization-members',
+ },
+ {
+ id: '7',
+ type: 'member_change',
+ description: 'Removed member Lisa Taylor',
+ timestamp: new Date(2025, 4, 4, 15, 45),
+ user: 'Sarah Williams',
+ details: {
+ before: {
+ name: 'Lisa Taylor',
+ email: 'lisa@example.com',
+ role: 'Member',
+ },
+ after: null,
+ },
+ path: '/dashboard/organization-profile/organization-members',
+ },
+];
+
+export const getFilteredLogs = (days: number): AuditLog[] => {
+ const now = new Date();
+ const cutoff = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
+
+ return mockAuditLogs.filter((log) => log.timestamp >= cutoff);
+};
+
+export const getFormattedTime = (date: Date): string => {
+ const now = new Date();
+ const diffInHours = Math.floor(
+ (now.getTime() - date.getTime()) / (1000 * 60 * 60),
+ );
+
+ if (diffInHours < 24) {
+ return diffInHours === 0
+ ? 'Just now'
+ : diffInHours === 1
+ ? '1 hour ago'
+ : `${diffInHours} hours ago`;
+ }
+
+ const diffInDays = Math.floor(diffInHours / 24);
+
+ if (diffInDays < 7) {
+ return diffInDays === 1 ? 'Yesterday' : `${diffInDays} days ago`;
+ }
+
+ return date.toLocaleDateString();
+};
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 42ccfc6980..5a6dbfa256 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -13,8 +13,14 @@
"company": "Company"
},
"Hero": {
+ "follow_twitter": "Follow @Ixartz on Twitter",
"title": "The perfect SaaS template to build and scale your business with ease.",
- "description": "A free and open-source landing page template for your SaaS business, built with React, TypeScript, Shadcn UI, and Tailwind CSS."
+ "description": "A free and open-source landing page template for your SaaS business, built with React, TypeScript, Shadcn UI, and Tailwind CSS.",
+ "primary_button": "Get Started",
+ "secondary_button": "Star on GitHub"
+ },
+ "Sponsors": {
+ "title": "Sponsored by"
},
"Features": {
"section_subtitle": "Features",
@@ -28,6 +34,37 @@
"feature6_title": "ESLint",
"feature_description": "A free and open-source landing page template for your SaaS business, built with React, TypeScript, Shadcn UI, and Tailwind CSS."
},
+ "Pricing": {
+ "section_subtitle": "Features",
+ "section_title": "Unlock the Full Potential of the SaaS Template",
+ "section_description": "A free and open-source landing page template for your SaaS business, built with React, TypeScript, Shadcn UI, and Tailwind CSS.",
+ "button_text": "Get Started"
+ },
+ "PricingPlan": {
+ "free_plan_name": "Free",
+ "premium_plan_name": "Premium",
+ "enterprise_plan_name": "Enterprise",
+ "free_plan_description": "For individuals",
+ "premium_plan_description": "For small teams",
+ "enterprise_plan_description": "For industry leaders",
+ "feature_team_member": "{number} Team Members",
+ "feature_website": "{number} Websites",
+ "feature_storage": "{number} GB Storage",
+ "feature_transfer": "{number} TB Transfer",
+ "feature_email_support": "Email Support",
+ "plan_interval_month": "month",
+ "plan_interval_year": "year",
+ "next_renew_date": "Your subscription renews on {date}"
+ },
+ "FAQ": {
+ "question": "Lorem ipsum dolor sit amet, consectetur adipiscing elit?",
+ "answer": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam aliquam enim dui, id consequat turpis ullamcorper ac. Mauris id quam dolor. Nullam eu egestas turpis. Proin risus elit, sollicitudin in mi a, accumsan euismod turpis. In euismod mi sed diam tristique hendrerit."
+ },
+ "CTA": {
+ "title": "You are ready?",
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+ "button_text": "Star on GitHub"
+ },
"Footer": {
"product": "Product",
"docs": "Docs",
@@ -35,7 +72,11 @@
"community": "Community",
"company": "Company",
"terms_of_service": "Terms Of Service",
- "privacy_policy": "Privacy Policy"
+ "privacy_policy": "Privacy Policy",
+ "designed_by": "Designed by ."
+ },
+ "ProtectFallback": {
+ "not_enough_permission": "You do not have the permissions to perform this action"
},
"SignIn": {
"meta_title": "Sign in",
@@ -47,10 +88,13 @@
},
"DashboardLayout": {
"home": "Home",
+ "analytics": "Analytics",
"todos": "Todos",
"members": "Members",
+ "audit_logs": "Audit Logs",
"billing": "Billing",
- "settings": "Settings"
+ "settings": "Settings",
+ "provider_whitelist": "Provider Whitelist"
},
"Dashboard": {
"meta_title": "SaaS Template Dashboard",
@@ -59,7 +103,22 @@
"DashboardIndex": {
"title_bar": "Dashboard",
"title_bar_description": "Welcome to your dashboard",
- "message_state_title": "Let's get started"
+ "message_state_title": "Let's get started",
+ "message_state_description": "You can customize this page by editing the file at dashboard/page.tsx",
+ "message_state_button": "Star on GitHub",
+ "message_state_alternative": "Want more features using the same stack? Try .",
+ "analytics_title": "Usage Analytics",
+ "analytics_description": "Organization usage metrics and statistics",
+ "analytics_period_7_days": "Last 7 days",
+ "analytics_period_30_days": "Last 30 days",
+ "analytics_period_90_days": "Last 90 days",
+ "analytics_tasks_started": "Tasks Started",
+ "analytics_tasks_completed": "Tasks Completed",
+ "analytics_tokens_consumed": "Tokens Consumed",
+ "analytics_costs": "Costs (USD)",
+ "analytics_llm_costs": "LLM Model Costs (USD)",
+ "analytics_active_developers": "Active Developers",
+ "analytics_view_details": "View detailed analytics"
},
"UserProfile": {
"title_bar": "User Profile",
@@ -67,6 +126,95 @@
},
"OrganizationProfile": {
"title_bar": "Organization Management",
- "title_bar_description": "Manage your organization"
+ "title_bar_description": "Manage your organization",
+ "provider_whitelist": "Provider Whitelist",
+ "default_parameters": "Default Parameters"
+ },
+ "ProviderWhitelist": {
+ "title": "Provider Whitelist & Default Parameters",
+ "description": "Control which AI providers are allowed and set default parameters for your organization",
+ "providers_section_title": "Allowed Providers",
+ "providers_section_description": "Select which AI providers and models are allowed to be used",
+ "parameters_section_title": "Default Parameters",
+ "parameters_section_description": "Set global default parameters for AI model calls"
+ },
+ "Billing": {
+ "title_bar": "Billing",
+ "title_bar_description": "Manage your billing and subscription",
+ "current_section_title": "Current Plan",
+ "current_section_description": "Adjust your payment plan to best suit your requirements",
+ "manage_subscription_button": "Manage Subscription"
+ },
+ "BillingOptions": {
+ "current_plan": "Current Plan",
+ "upgrade_plan": "Get Started"
+ },
+ "CheckoutConfirmation": {
+ "title_bar": "Payment Confirmation",
+ "message_state_title": "Payment successful",
+ "message_state_description": "Your payment has been successfully processed. Thank you for your purchase!",
+ "message_state_button": "Go back to Billing"
+ },
+ "DataTable": {
+ "no_results": "No results."
+ },
+ "Todos": {
+ "title_bar": "Todo List",
+ "title_bar_description": "View and manage your todo list",
+ "add_todo_button": "New todo"
+ },
+ "TodoTableColumns": {
+ "open_menu": "Open menu",
+ "edit": "Edit",
+ "delete": "Delete",
+ "title_header": "Title",
+ "message_header": "Message",
+ "created_at_header": "Created at"
+ },
+ "AddTodo": {
+ "title_bar": "Add Todo",
+ "add_todo_section_title": "Create a new todo",
+ "add_todo_section_description": "Fill in the form below to create a new todo"
+ },
+ "EditTodo": {
+ "title_bar": "Edit todo",
+ "edit_todo_section_title": "Modify todo",
+ "edit_todo_section_description": "Fill in the form below to edit the todo"
+ },
+ "TodoForm": {
+ "title_label": "Title",
+ "title_description": "Enter a descriptive title for your todo.",
+ "message_title": "Message",
+ "message_description": "Enter a detailed message for your todo.",
+ "submit_button": "Submit"
+ },
+ "Analytics": {
+ "title_bar": "Usage Analytics",
+ "title_bar_description": "Detailed analytics for your organization",
+ "summary_title": "Usage Summary",
+ "period_7_days": "Last 7 days",
+ "period_30_days": "Last 30 days",
+ "period_90_days": "Last 90 days",
+ "active_developers": "Active Developers",
+ "tasks_started": "Tasks Started",
+ "tasks_completed": "Tasks Completed",
+ "tokens_consumed": "Tokens Consumed",
+ "llm_costs": "LLM Model Costs (USD)",
+ "view_mode_title": "View By",
+ "view_mode_developers": "Developers",
+ "view_mode_models": "Models",
+ "view_mode_tasks": "Tasks",
+ "filter_title": "Filters",
+ "filter_developer": "Developer",
+ "filter_model": "Model",
+ "filter_clear_all": "Clear All",
+ "task_details_title": "Task Details",
+ "task_details_close": "Close",
+ "pagination_prev": "Previous",
+ "pagination_next": "Next",
+ "pagination_of": "of",
+ "pagination_page_size": "Page size",
+ "no_data": "No data available",
+ "loading": "Loading data..."
}
}
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json
index eb003fe4bd..101df9caf5 100644
--- a/src/i18n/locales/fr.json
+++ b/src/i18n/locales/fr.json
@@ -13,8 +13,14 @@
"company": "Entreprise"
},
"Hero": {
+ "follow_twitter": "Suivez @Ixartz sur Twitter",
"title": "Le parfait SaaS template pour construire et mettre à l'échelle votre entreprise en toute simplicité.",
- "description": "Un template gratuit et open-source de landing page pour votre entreprise SaaS, construit avec React, TypeScript, Shadcn UI et Tailwind CSS."
+ "description": "Un template gratuit et open-source de landing page pour votre entreprise SaaS, construit avec React, TypeScript, Shadcn UI et Tailwind CSS.",
+ "primary_button": "Démarrer",
+ "secondary_button": "Mettez une étoile sur GitHub"
+ },
+ "Sponsors": {
+ "title": "Sponsorisé par"
},
"Features": {
"section_subtitle": "Fonctionnalités",
@@ -28,6 +34,37 @@
"feature6_title": "ESLint",
"feature_description": "Un template gratuit et open-source de landing page pour votre entreprise SaaS, construit avec React, TypeScript, Shadcn UI et Tailwind CSS."
},
+ "Pricing": {
+ "section_subtitle": "Fonctionnalités",
+ "section_title": "Débloquer le plein potentiel du SaaS Template",
+ "section_description": "Un template gratuit et open-source de landing page pour votre entreprise SaaS, construit avec React, TypeScript, Shadcn UI et Tailwind CSS.",
+ "button_text": "Démarrer"
+ },
+ "PricingPlan": {
+ "free_plan_name": "Gratuit",
+ "premium_plan_name": "Premium",
+ "enterprise_plan_name": "Enterprise",
+ "free_plan_description": "Pour les particuliers",
+ "premium_plan_description": "Pour les petites équipes",
+ "enterprise_plan_description": "Pour les leaders de l'industrie",
+ "feature_team_member": "{number} membres",
+ "feature_website": "{number} sites internet",
+ "feature_storage": "{number} Go de stockage",
+ "feature_transfer": "{number} TB de transfert",
+ "feature_email_support": "Support par e-mail",
+ "plan_interval_month": "mois",
+ "plan_interval_year": "année",
+ "next_renew_date": "Votre abonnement sera renouvelé le {date}"
+ },
+ "FAQ": {
+ "question": "Lorem ipsum dolor sit amet, consectetur adipiscing elit?",
+ "answer": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam aliquam enim dui, id consequat turpis ullamcorper ac. Mauris id quam dolor. Nullam eu egestas turpis. Proin risus elit, sollicitudin in mi a, accumsan euismod turpis. In euismod mi sed diam tristique hendrerit."
+ },
+ "CTA": {
+ "title": "Vous ĂŞtes prĂŞt?",
+ "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
+ "button_text": "Mettez une étoile sur GitHub"
+ },
"Footer": {
"product": "Produit",
"docs": "Docs",
@@ -35,7 +72,11 @@
"community": "Communauté",
"company": "Entreprise",
"terms_of_service": "Conditions d'utilisation",
- "privacy_policy": "Politique de confidentialité"
+ "privacy_policy": "Politique de confidentialité",
+ "designed_by": "Designé by ."
+ },
+ "ProtectFallback": {
+ "not_enough_permission": "Vous n'avez pas les permissions pour effectuer cette action"
},
"SignIn": {
"meta_title": "Se connecter",
@@ -47,6 +88,7 @@
},
"DashboardLayout": {
"home": "Accueil",
+ "analytics": "Analytiques",
"todos": "Todos",
"members": "Membres",
"billing": "Facturation",
@@ -59,7 +101,22 @@
"DashboardIndex": {
"title_bar": "Tableau de bord",
"title_bar_description": "Bienvenue sur votre tableau de bord",
- "message_state_title": "C'est parti"
+ "message_state_title": "C'est parti",
+ "message_state_description": "Vous pouvez personnaliser cette page en modifiant le fichier dans dashboard/page.tsx",
+ "message_state_button": "Mettez une étoile sur GitHub",
+ "message_state_alternative": "Vous voulez plus de fonctionnalités en utilisant la même stack ? Essayez .",
+ "analytics_title": "Analytique d'Utilisation",
+ "analytics_description": "Métriques et statistiques d'utilisation de l'organisation",
+ "analytics_period_7_days": "7 derniers jours",
+ "analytics_period_30_days": "30 derniers jours",
+ "analytics_period_90_days": "90 derniers jours",
+ "analytics_tasks_started": "Tâches Commencées",
+ "analytics_tasks_completed": "Tâches Terminées",
+ "analytics_tokens_consumed": "Tokens Consommés",
+ "analytics_costs": "Coûts (USD)",
+ "analytics_llm_costs": "Coûts des Modèles LLM (USD)",
+ "analytics_active_developers": "Développeurs Actifs",
+ "analytics_view_details": "Voir les analytiques détaillées"
},
"UserProfile": {
"title_bar": "Profil utilisateur",
@@ -68,5 +125,84 @@
"OrganizationProfile": {
"title_bar": "Gestion de l’organisation",
"title_bar_description": "Gérer votre organisation"
+ },
+ "Billing": {
+ "title_bar": "Facturation",
+ "title_bar_description": "Gérer votre facturation et votre abonnement",
+ "current_section_title": "Plan actuel",
+ "current_section_description": "Ajuster votre plan de paiement pour le mieux répondre à vos besoins",
+ "manage_subscription_button": "Gérer l'abonnement"
+ },
+ "BillingOptions": {
+ "current_plan": "Plan actuel",
+ "upgrade_plan": "Démarrer"
+ },
+ "CheckoutConfirmation": {
+ "title_bar": "Confirmation du paiement",
+ "message_state_title": "Paiement accepté",
+ "message_state_description": "Votre paiement a été traité avec succès. Merci pour votre achat !",
+ "message_state_button": "Revenir Ă la facturation"
+ },
+ "DataTable": {
+ "no_results": "Aucun résultat."
+ },
+ "Todos": {
+ "title_bar": "Liste de Todos",
+ "title_bar_description": "Afficher et gérer votre liste de todos",
+ "add_todo_button": "Nouveau todo"
+ },
+ "TodoTableColumns": {
+ "open_menu": "Ouvrir le menu",
+ "edit": "Modifier",
+ "delete": "Supprimer",
+ "title_header": "Titre",
+ "message_header": "Message",
+ "created_at_header": "Créé le"
+ },
+ "AddTodo": {
+ "title_bar": "Ajouter une todo",
+ "add_todo_section_title": "Créer une nouvelle todo",
+ "add_todo_section_description": "Remplissez le formulaire ci-dessous pour créer une nouvelle todo"
+ },
+ "EditTodo": {
+ "title_bar": "Editer le todo",
+ "edit_todo_section_title": "Modifier le todo",
+ "edit_todo_section_description": "Remplissez le formulaire ci-dessous pour modifier la todo"
+ },
+ "TodoForm": {
+ "title_label": "Titre",
+ "title_description": "Entrez un titre descriptif pour votre todo.",
+ "message_title": "Message",
+ "message_description": "Entrez un message détaillé pour votre todo.",
+ "submit_button": "Envoyer"
+ },
+ "Analytics": {
+ "title_bar": "Analytique d'Utilisation",
+ "title_bar_description": "Analytiques détaillées pour votre organisation",
+ "summary_title": "Résumé d'Utilisation",
+ "period_7_days": "7 derniers jours",
+ "period_30_days": "30 derniers jours",
+ "period_90_days": "90 derniers jours",
+ "active_developers": "Développeurs Actifs",
+ "tasks_started": "Tâches Commencées",
+ "tasks_completed": "Tâches Terminées",
+ "tokens_consumed": "Tokens Consommés",
+ "llm_costs": "Coûts des Modèles LLM (USD)",
+ "view_mode_title": "Afficher Par",
+ "view_mode_developers": "Développeurs",
+ "view_mode_models": "Modèles",
+ "view_mode_tasks": "Tâches",
+ "filter_title": "Filtres",
+ "filter_developer": "Développeur",
+ "filter_model": "Modèle",
+ "filter_clear_all": "Effacer Tout",
+ "task_details_title": "Détails de la Tâche",
+ "task_details_close": "Fermer",
+ "pagination_prev": "Précédent",
+ "pagination_next": "Suivant",
+ "pagination_of": "de",
+ "pagination_page_size": "Taille de page",
+ "no_data": "Aucune donnée disponible",
+ "loading": "Chargement des données..."
}
}
|