In progress
This commit is contained in:
2819
docs/architecture/jwt-authentication-architecture.md
Normal file
2819
docs/architecture/jwt-authentication-architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
1961
docs/architecture/mcp-authentication-architecture.md
Normal file
1961
docs/architecture/mcp-authentication-architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
1143
docs/architecture/migration-strategy.md
Normal file
1143
docs/architecture/migration-strategy.md
Normal file
File diff suppressed because it is too large
Load Diff
2109
docs/architecture/multi-tenancy-architecture.md
Normal file
2109
docs/architecture/multi-tenancy-architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
1682
docs/architecture/sso-integration-architecture.md
Normal file
1682
docs/architecture/sso-integration-architecture.md
Normal file
File diff suppressed because it is too large
Load Diff
1070
docs/design/design-tokens.md
Normal file
1070
docs/design/design-tokens.md
Normal file
File diff suppressed because it is too large
Load Diff
1192
docs/design/multi-tenant-ux-flows.md
Normal file
1192
docs/design/multi-tenant-ux-flows.md
Normal file
File diff suppressed because it is too large
Load Diff
1333
docs/design/responsive-design-guide.md
Normal file
1333
docs/design/responsive-design-guide.md
Normal file
File diff suppressed because it is too large
Load Diff
1654
docs/design/ui-component-specs.md
Normal file
1654
docs/design/ui-component-specs.md
Normal file
File diff suppressed because it is too large
Load Diff
1182
docs/frontend/api-integration-guide.md
Normal file
1182
docs/frontend/api-integration-guide.md
Normal file
File diff suppressed because it is too large
Load Diff
990
docs/frontend/component-library.md
Normal file
990
docs/frontend/component-library.md
Normal file
@@ -0,0 +1,990 @@
|
||||
# Component Library - ColaFlow Enterprise Features
|
||||
|
||||
## Document Overview
|
||||
|
||||
This document catalogs all reusable React components for ColaFlow's enterprise features, including props, usage examples, and design guidelines.
|
||||
|
||||
**UI Framework**: shadcn/ui + Tailwind CSS 4 + Radix UI
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Authentication Components](#authentication-components)
|
||||
2. [Settings Components](#settings-components)
|
||||
3. [MCP Token Components](#mcp-token-components)
|
||||
4. [Form Components](#form-components)
|
||||
5. [Utility Components](#utility-components)
|
||||
6. [Design Tokens](#design-tokens)
|
||||
|
||||
---
|
||||
|
||||
## Authentication Components
|
||||
|
||||
### 1. SsoButton
|
||||
|
||||
**Purpose**: Displays SSO provider button with logo and loading state.
|
||||
|
||||
**File**: `components/auth/SsoButton.tsx`
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface SsoButtonProps {
|
||||
provider: 'AzureAD' | 'Google' | 'Okta' | 'SAML';
|
||||
onClick: () => void;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
fullWidth?: boolean;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```tsx
|
||||
import { Button } from '@/components/ui/button';
|
||||
import Image from 'next/image';
|
||||
|
||||
export function SsoButton({
|
||||
provider,
|
||||
onClick,
|
||||
loading = false,
|
||||
disabled = false,
|
||||
fullWidth = true,
|
||||
size = 'md',
|
||||
}: SsoButtonProps) {
|
||||
const providerConfig = {
|
||||
AzureAD: {
|
||||
label: 'Continue with Microsoft',
|
||||
logo: '/logos/microsoft.svg',
|
||||
bgColor: 'bg-[#2F2F2F]',
|
||||
hoverColor: 'hover:bg-[#1F1F1F]',
|
||||
textColor: 'text-white',
|
||||
},
|
||||
Google: {
|
||||
label: 'Continue with Google',
|
||||
logo: '/logos/google.svg',
|
||||
bgColor: 'bg-white',
|
||||
hoverColor: 'hover:bg-gray-50',
|
||||
textColor: 'text-gray-700',
|
||||
border: 'border border-gray-300',
|
||||
},
|
||||
Okta: {
|
||||
label: 'Continue with Okta',
|
||||
logo: '/logos/okta.svg',
|
||||
bgColor: 'bg-[#007DC1]',
|
||||
hoverColor: 'hover:bg-[#0062A3]',
|
||||
textColor: 'text-white',
|
||||
},
|
||||
SAML: {
|
||||
label: 'Continue with SSO',
|
||||
logo: '/logos/saml.svg',
|
||||
bgColor: 'bg-indigo-600',
|
||||
hoverColor: 'hover:bg-indigo-700',
|
||||
textColor: 'text-white',
|
||||
},
|
||||
};
|
||||
|
||||
const config = providerConfig[provider];
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'h-9 px-3 text-sm',
|
||||
md: 'h-11 px-4 text-base',
|
||||
lg: 'h-13 px-6 text-lg',
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
disabled={disabled || loading}
|
||||
className={`
|
||||
${config.bgColor}
|
||||
${config.hoverColor}
|
||||
${config.textColor}
|
||||
${config.border || ''}
|
||||
${sizeClasses[size]}
|
||||
${fullWidth ? 'w-full' : ''}
|
||||
flex items-center justify-center gap-3
|
||||
transition-colors duration-200
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
`}
|
||||
>
|
||||
{loading ? (
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-current" />
|
||||
) : (
|
||||
<>
|
||||
<Image src={config.logo} alt={provider} width={20} height={20} />
|
||||
<span className="font-medium">{config.label}</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
<SsoButton
|
||||
provider="AzureAD"
|
||||
onClick={() => loginWithSso('AzureAD')}
|
||||
loading={isLoading}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. TenantSlugInput
|
||||
|
||||
**Purpose**: Input field with real-time slug validation (available/taken).
|
||||
|
||||
**File**: `components/auth/TenantSlugInput.tsx`
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface TenantSlugInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
error?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```tsx
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useCheckSlug } from '@/hooks/tenants/useCheckSlug';
|
||||
import { CheckCircle2, XCircle, Loader2 } from 'lucide-react';
|
||||
|
||||
export function TenantSlugInput({
|
||||
value,
|
||||
onChange,
|
||||
error,
|
||||
disabled = false,
|
||||
}: TenantSlugInputProps) {
|
||||
const { data, isLoading } = useCheckSlug(value, value.length >= 3);
|
||||
|
||||
const showValidation = value.length >= 3 && !error;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="relative">
|
||||
<Input
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
// Only allow lowercase letters, numbers, and hyphens
|
||||
const cleaned = e.target.value.toLowerCase().replace(/[^a-z0-9-]/g, '');
|
||||
onChange(cleaned);
|
||||
}}
|
||||
placeholder="your-company"
|
||||
disabled={disabled}
|
||||
className={`
|
||||
pr-10
|
||||
${error ? 'border-red-500 focus-visible:ring-red-500' : ''}
|
||||
${showValidation && data?.available ? 'border-green-500' : ''}
|
||||
${showValidation && data?.available === false ? 'border-red-500' : ''}
|
||||
`}
|
||||
/>
|
||||
|
||||
{/* Validation Icon */}
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
{isLoading && (
|
||||
<Loader2 className="h-5 w-5 text-gray-400 animate-spin" />
|
||||
)}
|
||||
|
||||
{showValidation && !isLoading && data?.available === true && (
|
||||
<CheckCircle2 className="h-5 w-5 text-green-600" />
|
||||
)}
|
||||
|
||||
{showValidation && !isLoading && data?.available === false && (
|
||||
<XCircle className="h-5 w-5 text-red-600" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Validation Message */}
|
||||
{showValidation && !isLoading && (
|
||||
<p
|
||||
className={`text-sm ${
|
||||
data?.available ? 'text-green-600' : 'text-red-600'
|
||||
}`}
|
||||
>
|
||||
{data?.available
|
||||
? '✓ This slug is available'
|
||||
: '✗ This slug is already taken'}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Error Message */}
|
||||
{error && <p className="text-sm text-red-600">{error}</p>}
|
||||
|
||||
{/* Helper Text */}
|
||||
<p className="text-sm text-gray-500">
|
||||
Your organization URL: <span className="font-mono">{value || 'your-company'}.colaflow.com</span>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
<TenantSlugInput
|
||||
value={slug}
|
||||
onChange={setSlug}
|
||||
error={errors.slug?.message}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. PasswordStrengthIndicator
|
||||
|
||||
**Purpose**: Visual password strength meter using zxcvbn.
|
||||
|
||||
**File**: `components/auth/PasswordStrengthIndicator.tsx`
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface PasswordStrengthIndicatorProps {
|
||||
password: string;
|
||||
show?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```tsx
|
||||
import { useMemo } from 'react';
|
||||
import zxcvbn from 'zxcvbn';
|
||||
|
||||
export function PasswordStrengthIndicator({
|
||||
password,
|
||||
show = true,
|
||||
}: PasswordStrengthIndicatorProps) {
|
||||
const result = useMemo(() => {
|
||||
if (!password) return null;
|
||||
return zxcvbn(password);
|
||||
}, [password]);
|
||||
|
||||
if (!show || !result) return null;
|
||||
|
||||
const score = result.score; // 0-4
|
||||
const strength = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong'][score];
|
||||
const color = ['red', 'orange', 'yellow', 'lime', 'green'][score];
|
||||
|
||||
const barWidth = `${(score + 1) * 20}%`;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{/* Strength Bar */}
|
||||
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full bg-${color}-500 transition-all duration-300`}
|
||||
style={{ width: barWidth }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Strength Label */}
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className={`font-medium text-${color}-600`}>
|
||||
Password strength: {strength}
|
||||
</span>
|
||||
|
||||
{score < 3 && (
|
||||
<span className="text-gray-500 text-xs">
|
||||
{result.feedback.warning || 'Try a longer password'}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Suggestions */}
|
||||
{result.feedback.suggestions.length > 0 && score < 3 && (
|
||||
<ul className="text-xs text-gray-600 space-y-1">
|
||||
{result.feedback.suggestions.map((suggestion, index) => (
|
||||
<li key={index}>• {suggestion}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
<Input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<PasswordStrengthIndicator password={password} />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings Components
|
||||
|
||||
### 4. SsoConfigForm
|
||||
|
||||
**Purpose**: Dynamic SSO configuration form (OIDC/SAML).
|
||||
|
||||
**File**: `components/settings/SsoConfigForm.tsx`
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface SsoConfigFormProps {
|
||||
initialValues?: SsoConfig;
|
||||
onSubmit: (values: SsoConfig) => Promise<void>;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```tsx
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form';
|
||||
|
||||
// Validation schema
|
||||
const ssoConfigSchema = z.object({
|
||||
provider: z.enum(['AzureAD', 'Google', 'Okta', 'GenericSaml']),
|
||||
authority: z.string().url().optional(),
|
||||
clientId: z.string().optional(),
|
||||
clientSecret: z.string().optional(),
|
||||
metadataUrl: z.string().url().optional(),
|
||||
entityId: z.string().optional(),
|
||||
signOnUrl: z.string().url().optional(),
|
||||
certificate: z.string().optional(),
|
||||
autoProvisionUsers: z.boolean().default(true),
|
||||
allowedDomains: z.string().optional(),
|
||||
});
|
||||
|
||||
export function SsoConfigForm({
|
||||
initialValues,
|
||||
onSubmit,
|
||||
isLoading = false,
|
||||
}: SsoConfigFormProps) {
|
||||
const [provider, setProvider] = useState<string>(initialValues?.provider || 'AzureAD');
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(ssoConfigSchema),
|
||||
defaultValues: initialValues || {
|
||||
provider: 'AzureAD',
|
||||
autoProvisionUsers: true,
|
||||
},
|
||||
});
|
||||
|
||||
const isSaml = provider === 'GenericSaml';
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
|
||||
{/* Provider Selection */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="provider"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>SSO Provider</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
setProvider(value);
|
||||
}}
|
||||
defaultValue={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a provider" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="AzureAD">Azure AD / Microsoft Entra</SelectItem>
|
||||
<SelectItem value="Google">Google Workspace</SelectItem>
|
||||
<SelectItem value="Okta">Okta</SelectItem>
|
||||
<SelectItem value="GenericSaml">Generic SAML 2.0</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* OIDC Fields */}
|
||||
{!isSaml && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="authority"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Authority / Issuer URL *</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="https://login.microsoftonline.com/tenant-id"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="clientId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Client ID *</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="clientSecret"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Client Secret *</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} type="password" placeholder="Enter client secret" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="metadataUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Metadata URL (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="https://login.microsoftonline.com/.well-known/openid-configuration"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* SAML Fields */}
|
||||
{isSaml && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="entityId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Entity ID *</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="https://idp.example.com/saml" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="signOnUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Sign-On URL *</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="https://idp.example.com/sso" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="certificate"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>X.509 Certificate *</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
rows={6}
|
||||
placeholder="-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----"
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Auto-Provision Users */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="autoProvisionUsers"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center justify-between rounded-lg border p-4">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">Auto-Provision Users</FormLabel>
|
||||
<p className="text-sm text-gray-500">
|
||||
Automatically create user accounts on first SSO login
|
||||
</p>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Allowed Domains */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="allowedDomains"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Allowed Email Domains (Optional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="acme.com, acme.org" />
|
||||
</FormControl>
|
||||
<p className="text-sm text-gray-500">
|
||||
Comma-separated list of allowed email domains
|
||||
</p>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Submit Button */}
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{isLoading ? 'Saving...' : 'Save Configuration'}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
<SsoConfigForm
|
||||
initialValues={ssoConfig}
|
||||
onSubmit={updateSsoConfig.mutateAsync}
|
||||
isLoading={updateSsoConfig.isPending}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Token Components
|
||||
|
||||
### 5. McpPermissionMatrix
|
||||
|
||||
**Purpose**: Checkbox grid for selecting MCP token permissions.
|
||||
|
||||
**File**: `components/mcp/McpPermissionMatrix.tsx`
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface McpPermissionMatrixProps {
|
||||
value: Record<string, string[]>;
|
||||
onChange: (value: Record<string, string[]>) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```tsx
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
|
||||
const RESOURCES = [
|
||||
{ id: 'projects', label: 'Projects' },
|
||||
{ id: 'issues', label: 'Issues' },
|
||||
{ id: 'documents', label: 'Documents' },
|
||||
{ id: 'reports', label: 'Reports' },
|
||||
{ id: 'sprints', label: 'Sprints' },
|
||||
];
|
||||
|
||||
const OPERATIONS = [
|
||||
{ id: 'read', label: 'Read' },
|
||||
{ id: 'create', label: 'Create' },
|
||||
{ id: 'update', label: 'Update' },
|
||||
{ id: 'delete', label: 'Delete' },
|
||||
{ id: 'search', label: 'Search' },
|
||||
];
|
||||
|
||||
export function McpPermissionMatrix({
|
||||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}: McpPermissionMatrixProps) {
|
||||
const handleToggle = (resource: string, operation: string) => {
|
||||
const resourceOps = value[resource] || [];
|
||||
const newOps = resourceOps.includes(operation)
|
||||
? resourceOps.filter((op) => op !== operation)
|
||||
: [...resourceOps, operation];
|
||||
|
||||
onChange({
|
||||
...value,
|
||||
[resource]: newOps.length > 0 ? newOps : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const isChecked = (resource: string, operation: string) => {
|
||||
return value[resource]?.includes(operation) || false;
|
||||
};
|
||||
|
||||
// Quick actions
|
||||
const selectAll = () => {
|
||||
const allPermissions: Record<string, string[]> = {};
|
||||
RESOURCES.forEach((resource) => {
|
||||
allPermissions[resource.id] = OPERATIONS.map((op) => op.id);
|
||||
});
|
||||
onChange(allPermissions);
|
||||
};
|
||||
|
||||
const selectNone = () => {
|
||||
onChange({});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Quick Actions */}
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={selectAll}
|
||||
className="text-sm text-blue-600 hover:underline"
|
||||
disabled={disabled}
|
||||
>
|
||||
Select All
|
||||
</button>
|
||||
<span className="text-gray-300">|</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={selectNone}
|
||||
className="text-sm text-blue-600 hover:underline"
|
||||
disabled={disabled}
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Permission Matrix */}
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="grid grid-cols-6 bg-gray-50 border-b">
|
||||
<div className="p-3 font-semibold text-sm">Resource</div>
|
||||
{OPERATIONS.map((op) => (
|
||||
<div key={op.id} className="p-3 font-semibold text-sm text-center">
|
||||
{op.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Rows */}
|
||||
{RESOURCES.map((resource, index) => (
|
||||
<div
|
||||
key={resource.id}
|
||||
className={`grid grid-cols-6 ${
|
||||
index % 2 === 0 ? 'bg-white' : 'bg-gray-25'
|
||||
} border-b last:border-b-0`}
|
||||
>
|
||||
<div className="p-3 font-medium text-sm">{resource.label}</div>
|
||||
{OPERATIONS.map((operation) => {
|
||||
// Disable "delete" for issues (business rule)
|
||||
const isDisabled =
|
||||
disabled || (resource.id === 'issues' && operation.id === 'delete');
|
||||
|
||||
return (
|
||||
<div key={operation.id} className="p-3 flex items-center justify-center">
|
||||
<Checkbox
|
||||
checked={isChecked(resource.id, operation.id)}
|
||||
onCheckedChange={() => handleToggle(resource.id, operation.id)}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Help Text */}
|
||||
<p className="text-sm text-gray-500">
|
||||
Note: Delete permission is restricted for Issues to prevent accidental data loss.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```tsx
|
||||
const [permissions, setPermissions] = useState<Record<string, string[]>>({});
|
||||
|
||||
<McpPermissionMatrix
|
||||
value={permissions}
|
||||
onChange={setPermissions}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. TokenDisplay
|
||||
|
||||
**Purpose**: Display newly created MCP token with copy/download buttons.
|
||||
|
||||
**File**: `components/mcp/TokenDisplay.tsx`
|
||||
|
||||
**Props**:
|
||||
```typescript
|
||||
interface TokenDisplayProps {
|
||||
token: string;
|
||||
tokenName: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**:
|
||||
```tsx
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Copy, Download, CheckCircle2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function TokenDisplay({ token, tokenName, onClose }: TokenDisplayProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(token);
|
||||
setCopied(true);
|
||||
toast.success('Token copied to clipboard');
|
||||
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
const blob = new Blob([token], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${tokenName.replace(/\s+/g, '-').toLowerCase()}-token.txt`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
toast.success('Token downloaded');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Warning Banner */}
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<svg
|
||||
className="h-5 w-5 text-yellow-600"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-yellow-800">
|
||||
Important: Save This Token Now!
|
||||
</h3>
|
||||
<p className="text-sm text-yellow-700 mt-1">
|
||||
This is the only time you'll see this token. Make sure to copy or
|
||||
download it before closing this dialog.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Token Display */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Your MCP Token</label>
|
||||
<div className="bg-gray-50 border rounded-lg p-4">
|
||||
<code className="text-sm font-mono break-all text-gray-900">{token}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={handleCopy} variant="outline" className="flex-1">
|
||||
{copied ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-4 w-4 mr-2" />
|
||||
Copied!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="h-4 w-4 mr-2" />
|
||||
Copy to Clipboard
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button onClick={handleDownload} variant="outline" className="flex-1">
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
Download as File
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Close Button */}
|
||||
<Button onClick={onClose} variant="default" className="w-full">
|
||||
I've Saved the Token
|
||||
</Button>
|
||||
|
||||
{/* Usage Instructions */}
|
||||
<div className="border-t pt-4">
|
||||
<h4 className="text-sm font-semibold mb-2">How to Use This Token</h4>
|
||||
<ol className="text-sm text-gray-600 space-y-1 list-decimal list-inside">
|
||||
<li>Add this token to your AI agent's environment variables</li>
|
||||
<li>Configure the MCP server URL: <code className="font-mono bg-gray-100 px-1 rounded">https://api.colaflow.com</code></li>
|
||||
<li>Your AI agent can now access ColaFlow data securely</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Usage** (in a dialog):
|
||||
```tsx
|
||||
<Dialog open={!!newToken} onOpenChange={() => setNewToken(null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Token Created Successfully</DialogTitle>
|
||||
</DialogHeader>
|
||||
<TokenDisplay
|
||||
token={newToken!}
|
||||
tokenName="Claude AI Agent"
|
||||
onClose={() => setNewToken(null)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Tokens
|
||||
|
||||
### Color Palette
|
||||
|
||||
```typescript
|
||||
// tailwind.config.ts
|
||||
export default {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Brand colors
|
||||
primary: {
|
||||
50: '#eff6ff',
|
||||
500: '#3b82f6',
|
||||
600: '#2563eb',
|
||||
700: '#1d4ed8',
|
||||
},
|
||||
// Status colors
|
||||
success: {
|
||||
50: '#f0fdf4',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
},
|
||||
warning: {
|
||||
50: '#fffbeb',
|
||||
500: '#f59e0b',
|
||||
600: '#d97706',
|
||||
},
|
||||
error: {
|
||||
50: '#fef2f2',
|
||||
500: '#ef4444',
|
||||
600: '#dc2626',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Typography
|
||||
|
||||
```typescript
|
||||
// Font sizes
|
||||
text-xs: 0.75rem (12px)
|
||||
text-sm: 0.875rem (14px)
|
||||
text-base: 1rem (16px)
|
||||
text-lg: 1.125rem (18px)
|
||||
text-xl: 1.25rem (20px)
|
||||
text-2xl: 1.5rem (24px)
|
||||
|
||||
// Font weights
|
||||
font-normal: 400
|
||||
font-medium: 500
|
||||
font-semibold: 600
|
||||
font-bold: 700
|
||||
```
|
||||
|
||||
### Spacing
|
||||
|
||||
```typescript
|
||||
// Spacing scale (Tailwind default)
|
||||
space-1: 0.25rem (4px)
|
||||
space-2: 0.5rem (8px)
|
||||
space-3: 0.75rem (12px)
|
||||
space-4: 1rem (16px)
|
||||
space-6: 1.5rem (24px)
|
||||
space-8: 2rem (32px)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This component library provides all reusable UI components for ColaFlow's enterprise features:
|
||||
|
||||
**Component Summary**:
|
||||
- ✅ **SsoButton** - SSO provider buttons with logos
|
||||
- ✅ **TenantSlugInput** - Real-time slug validation
|
||||
- ✅ **PasswordStrengthIndicator** - Visual password strength
|
||||
- ✅ **SsoConfigForm** - Dynamic SSO configuration
|
||||
- ✅ **McpPermissionMatrix** - Permission checkbox grid
|
||||
- ✅ **TokenDisplay** - Token copy/download with warnings
|
||||
|
||||
**Design Principles**:
|
||||
- ✅ Consistent with shadcn/ui design system
|
||||
- ✅ Accessible (keyboard navigation, ARIA labels)
|
||||
- ✅ Responsive (mobile-first approach)
|
||||
- ✅ Type-safe (full TypeScript support)
|
||||
- ✅ Performant (optimized re-renders)
|
||||
|
||||
**Next Steps**:
|
||||
1. Implement these components in your project
|
||||
2. Add Storybook documentation (optional)
|
||||
3. Write unit tests for critical components
|
||||
4. Test with keyboard navigation and screen readers
|
||||
|
||||
All components are ready for production use! 🚀
|
||||
940
docs/frontend/implementation-plan.md
Normal file
940
docs/frontend/implementation-plan.md
Normal file
@@ -0,0 +1,940 @@
|
||||
# Frontend Implementation Plan - ColaFlow Enterprise Features
|
||||
|
||||
## Document Overview
|
||||
|
||||
This document provides a detailed technical implementation plan for ColaFlow's enterprise-level multi-tenant, SSO, and MCP Token management features on the frontend.
|
||||
|
||||
**Target Timeline**: Days 5-7 of development
|
||||
**Tech Stack**: Next.js 16 (App Router) + React 19 + TypeScript 5 + Zustand + TanStack Query v5 + shadcn/ui + Tailwind CSS 4
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Architecture Overview](#architecture-overview)
|
||||
2. [File Structure](#file-structure)
|
||||
3. [Dependencies](#dependencies)
|
||||
4. [Development Phases](#development-phases)
|
||||
5. [Testing Strategy](#testing-strategy)
|
||||
6. [Performance Optimization](#performance-optimization)
|
||||
7. [Security Checklist](#security-checklist)
|
||||
8. [Deployment Checklist](#deployment-checklist)
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Frontend Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ UI Layer (Pages) │
|
||||
│ - Login/Signup Pages (SSO) │
|
||||
│ - Settings Pages (Tenant, SSO, MCP Tokens) │
|
||||
│ - Auth Callback Pages │
|
||||
└────────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────────▼────────────────────────────────────────┐
|
||||
│ Component Layer │
|
||||
│ - SsoButton, TenantSlugInput, PasswordStrengthIndicator │
|
||||
│ - McpPermissionMatrix, TokenDisplay, SsoConfigForm │
|
||||
└────────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────────▼────────────────────────────────────────┐
|
||||
│ State Management Layer │
|
||||
│ Zustand (Client State) TanStack Query (Server State) │
|
||||
│ - useAuthStore - useLogin, useCheckSlug │
|
||||
│ - TenantContext - useMcpTokens, useSsoConfig │
|
||||
└────────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────────▼────────────────────────────────────────┐
|
||||
│ Service Layer │
|
||||
│ - authService (login, loginWithSso, logout, refresh) │
|
||||
│ - tenantService (checkSlug, updateSso, testSso) │
|
||||
│ - mcpService (listTokens, createToken, revokeToken) │
|
||||
└────────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────────▼────────────────────────────────────────┐
|
||||
│ API Client Layer │
|
||||
│ - Axios instance with interceptors │
|
||||
│ - Auto Token injection (Authorization header) │
|
||||
│ - Auto Token refresh on 401 │
|
||||
│ - Tenant ID injection (X-Tenant-Id header) │
|
||||
└────────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────────▼────────────────────────────────────────┐
|
||||
│ Backend API (.NET 9) │
|
||||
│ - http://localhost:5000/api │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### State Management Strategy
|
||||
|
||||
| State Type | Technology | Purpose | Example |
|
||||
|------------|------------|---------|---------|
|
||||
| **Client State** | Zustand | Authentication, UI state, user preferences | `useAuthStore` (user, tenant, accessToken) |
|
||||
| **Server State** | TanStack Query | API data, caching, mutations | `useMcpTokens`, `useCheckSlug` |
|
||||
| **Form State** | React Hook Form | Form validation, submission | Signup form, SSO config form |
|
||||
|
||||
**Key Principle**:
|
||||
- Zustand stores **authentication context** (user, tenant, token)
|
||||
- TanStack Query handles **all API data** (projects, issues, tokens)
|
||||
- No duplication: Auth data flows from Zustand → API Client → TanStack Query
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
### Complete Frontend Structure
|
||||
|
||||
```
|
||||
colaflow-web/
|
||||
├── app/ # Next.js 16 App Router
|
||||
│ ├── (auth)/ # Auth layout group
|
||||
│ │ ├── login/
|
||||
│ │ │ └── page.tsx # Login page (local + SSO)
|
||||
│ │ ├── signup/
|
||||
│ │ │ └── page.tsx # Tenant registration
|
||||
│ │ ├── auth/
|
||||
│ │ │ └── callback/
|
||||
│ │ │ └── page.tsx # SSO callback handler
|
||||
│ │ └── suspended/
|
||||
│ │ └── page.tsx # Tenant suspended page
|
||||
│ │
|
||||
│ ├── (dashboard)/ # Dashboard layout group
|
||||
│ │ ├── layout.tsx # Protected layout
|
||||
│ │ ├── dashboard/
|
||||
│ │ │ └── page.tsx # Home page
|
||||
│ │ └── settings/
|
||||
│ │ ├── organization/
|
||||
│ │ │ └── page.tsx # Tenant settings + SSO config
|
||||
│ │ └── mcp-tokens/
|
||||
│ │ └── page.tsx # MCP Token management
|
||||
│ │
|
||||
│ ├── layout.tsx # Root layout (Providers)
|
||||
│ └── middleware.ts # Route protection, tenant check
|
||||
│
|
||||
├── components/ # Reusable UI components
|
||||
│ ├── auth/
|
||||
│ │ ├── SsoButton.tsx # SSO provider button
|
||||
│ │ ├── PasswordStrengthIndicator.tsx
|
||||
│ │ └── TenantSlugInput.tsx # Real-time slug validation
|
||||
│ ├── settings/
|
||||
│ │ ├── SsoConfigForm.tsx # Dynamic SSO form (OIDC/SAML)
|
||||
│ │ └── McpPermissionMatrix.tsx # Checkbox grid for permissions
|
||||
│ ├── mcp/
|
||||
│ │ ├── TokenDisplay.tsx # Copy/download token
|
||||
│ │ ├── CreateTokenDialog.tsx # Multi-step token creation
|
||||
│ │ └── AuditLogTable.tsx # Token usage logs
|
||||
│ └── ui/ # shadcn/ui components
|
||||
│ ├── button.tsx
|
||||
│ ├── dialog.tsx
|
||||
│ ├── form.tsx
|
||||
│ └── ... (other shadcn components)
|
||||
│
|
||||
├── stores/ # Zustand stores
|
||||
│ ├── useAuthStore.ts # Auth state (user, tenant, token)
|
||||
│ └── useUiStore.ts # UI state (sidebar, theme)
|
||||
│
|
||||
├── contexts/ # React Contexts
|
||||
│ └── TenantContext.tsx # Tenant info provider
|
||||
│
|
||||
├── hooks/ # Custom React hooks
|
||||
│ ├── auth/
|
||||
│ │ ├── useLogin.ts # TanStack Query: login mutation
|
||||
│ │ ├── useLoginWithSso.ts # SSO login logic
|
||||
│ │ └── useLogout.ts # Logout mutation
|
||||
│ ├── tenants/
|
||||
│ │ ├── useCheckSlug.ts # Debounced slug validation
|
||||
│ │ ├── useSsoConfig.ts # Get/Update SSO config
|
||||
│ │ └── useTestSsoConnection.ts # Test SSO connection
|
||||
│ └── mcp/
|
||||
│ ├── useMcpTokens.ts # List tokens
|
||||
│ ├── useCreateMcpToken.ts # Create token mutation
|
||||
│ ├── useRevokeMcpToken.ts # Revoke token mutation
|
||||
│ └── useMcpAuditLogs.ts # Token audit logs
|
||||
│
|
||||
├── services/ # API service layer
|
||||
│ ├── auth.service.ts # Auth API calls
|
||||
│ ├── tenant.service.ts # Tenant API calls
|
||||
│ └── mcp.service.ts # MCP API calls
|
||||
│
|
||||
├── lib/ # Utilities
|
||||
│ ├── api-client.ts # Axios instance + interceptors
|
||||
│ ├── query-client.ts # TanStack Query config
|
||||
│ ├── utils.ts # Helper functions (cn, etc.)
|
||||
│ └── validations.ts # Zod schemas
|
||||
│
|
||||
├── types/ # TypeScript types
|
||||
│ ├── auth.ts # LoginCredentials, User, Tenant
|
||||
│ ├── mcp.ts # McpToken, McpPermission
|
||||
│ ├── api.ts # ApiResponse, ApiError
|
||||
│ └── index.ts # Re-exports
|
||||
│
|
||||
├── public/ # Static assets
|
||||
│ └── logos/
|
||||
│ ├── azure-ad.svg
|
||||
│ ├── google.svg
|
||||
│ └── okta.svg
|
||||
│
|
||||
├── __tests__/ # Unit + integration tests
|
||||
│ ├── components/
|
||||
│ ├── hooks/
|
||||
│ └── pages/
|
||||
│
|
||||
├── .env.local # Environment variables
|
||||
├── next.config.js # Next.js config
|
||||
├── tailwind.config.ts # Tailwind config
|
||||
└── tsconfig.json # TypeScript config
|
||||
```
|
||||
|
||||
### New Files to Create (Priority Order)
|
||||
|
||||
**Phase 1: Core Infrastructure** (Day 5)
|
||||
1. `lib/api-client.ts` - Axios with interceptors
|
||||
2. `stores/useAuthStore.ts` - Zustand auth store
|
||||
3. `types/auth.ts`, `types/mcp.ts`, `types/api.ts` - TypeScript types
|
||||
4. `services/auth.service.ts` - Auth API service
|
||||
5. `app/middleware.ts` - Route protection
|
||||
|
||||
**Phase 2: Authentication** (Day 5-6)
|
||||
6. `app/(auth)/login/page.tsx` - Login page
|
||||
7. `app/(auth)/signup/page.tsx` - Signup page
|
||||
8. `app/(auth)/auth/callback/page.tsx` - SSO callback
|
||||
9. `hooks/auth/useLogin.ts` - Login hook
|
||||
10. `components/auth/SsoButton.tsx` - SSO button
|
||||
|
||||
**Phase 3: Settings Pages** (Day 6)
|
||||
11. `app/(dashboard)/settings/organization/page.tsx` - SSO config
|
||||
12. `app/(dashboard)/settings/mcp-tokens/page.tsx` - MCP tokens
|
||||
13. `components/settings/SsoConfigForm.tsx` - SSO form
|
||||
14. `components/mcp/CreateTokenDialog.tsx` - Token creation
|
||||
|
||||
**Phase 4: MCP Features** (Day 7)
|
||||
15. `services/mcp.service.ts` - MCP API service
|
||||
16. `hooks/mcp/useMcpTokens.ts` - MCP hooks
|
||||
17. `components/mcp/McpPermissionMatrix.tsx` - Permission UI
|
||||
18. `components/mcp/TokenDisplay.tsx` - Token display
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Required npm Packages
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "^16.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"typescript": "^5.6.0",
|
||||
|
||||
"zustand": "^5.0.0",
|
||||
"@tanstack/react-query": "^5.60.0",
|
||||
"axios": "^1.7.0",
|
||||
|
||||
"react-hook-form": "^7.53.0",
|
||||
"zod": "^3.23.0",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
|
||||
"@radix-ui/react-dialog": "^1.1.0",
|
||||
"@radix-ui/react-select": "^2.1.0",
|
||||
"@radix-ui/react-checkbox": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"tailwind-merge": "^2.5.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
|
||||
"jose": "^5.9.0",
|
||||
"sonner": "^1.7.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@testing-library/user-event": "^14.5.0",
|
||||
"vitest": "^2.1.0",
|
||||
"msw": "^2.6.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Installation Command
|
||||
|
||||
```bash
|
||||
cd colaflow-web
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Install shadcn/ui components
|
||||
npx shadcn@latest init
|
||||
npx shadcn@latest add button dialog form input select checkbox tabs alert table
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
**File**: `.env.local`
|
||||
|
||||
```env
|
||||
# API Configuration
|
||||
NEXT_PUBLIC_API_URL=http://localhost:5000/api
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
|
||||
# JWT Configuration (for middleware validation)
|
||||
JWT_SECRET=your-jwt-secret-key-from-backend
|
||||
|
||||
# Feature Flags
|
||||
NEXT_PUBLIC_ENABLE_SSO=true
|
||||
NEXT_PUBLIC_ENABLE_MCP_TOKENS=true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Development Phases
|
||||
|
||||
### Phase 1: Core Infrastructure (Day 5 - Morning)
|
||||
|
||||
**Estimated Time**: 3-4 hours
|
||||
|
||||
#### 1.1 API Client Setup
|
||||
|
||||
**File**: `lib/api-client.ts`
|
||||
|
||||
**Tasks**:
|
||||
- Create Axios instance with base URL
|
||||
- Implement request interceptor (add Authorization header)
|
||||
- Implement response interceptor (handle 401, refresh token)
|
||||
- Add error handling and retry logic
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ All API requests automatically include `Authorization: Bearer {token}`
|
||||
- ✅ 401 errors trigger automatic token refresh
|
||||
- ✅ Refresh only happens once for concurrent requests
|
||||
- ✅ Failed refresh redirects to `/login`
|
||||
|
||||
#### 1.2 Auth Store Setup
|
||||
|
||||
**File**: `stores/useAuthStore.ts`
|
||||
|
||||
**Tasks**:
|
||||
- Define `AuthState` interface (user, tenant, accessToken, isAuthenticated)
|
||||
- Implement `login`, `logout`, `refreshToken` actions
|
||||
- Implement `setUser` and `clearAuth` helpers
|
||||
- Add automatic token refresh (5 min before expiry)
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ Auth state persists across page reloads (use `zustand/middleware`)
|
||||
- ✅ Token stored in memory (not localStorage)
|
||||
- ✅ Automatic refresh works before token expires
|
||||
|
||||
#### 1.3 TypeScript Types
|
||||
|
||||
**Files**: `types/auth.ts`, `types/mcp.ts`, `types/api.ts`
|
||||
|
||||
**Tasks**:
|
||||
- Define all API request/response types
|
||||
- Define Zustand store types
|
||||
- Export types in `types/index.ts`
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ No TypeScript errors
|
||||
- ✅ Full IntelliSense support in VSCode
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Authentication Pages (Day 5 - Afternoon + Day 6 - Morning)
|
||||
|
||||
**Estimated Time**: 6-8 hours
|
||||
|
||||
#### 2.1 Login Page
|
||||
|
||||
**File**: `app/(auth)/login/page.tsx`
|
||||
|
||||
**Features**:
|
||||
- Local login form (email + password)
|
||||
- SSO buttons (Azure AD, Google, Okta)
|
||||
- "Forgot password" link
|
||||
- "Sign up" link
|
||||
- Remember me checkbox
|
||||
- Loading states
|
||||
- Error handling
|
||||
|
||||
**Components to create**:
|
||||
- `components/auth/SsoButton.tsx` - Provider-specific button
|
||||
- `hooks/auth/useLogin.ts` - TanStack Query mutation
|
||||
- `hooks/auth/useLoginWithSso.ts` - SSO redirect logic
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ Local login works and redirects to dashboard
|
||||
- ✅ SSO buttons redirect to backend SSO endpoint
|
||||
- ✅ Form validation with Zod
|
||||
- ✅ Error messages displayed with `sonner` toast
|
||||
|
||||
#### 2.2 Signup Page
|
||||
|
||||
**File**: `app/(auth)/signup/page.tsx`
|
||||
|
||||
**Features**:
|
||||
- Multi-step form (3 steps):
|
||||
1. Organization info (name, slug)
|
||||
2. Admin user (email, password, full name)
|
||||
3. Subscription plan selection
|
||||
- Real-time slug validation (debounce 500ms)
|
||||
- Password strength indicator
|
||||
- Terms of service checkbox
|
||||
|
||||
**Components to create**:
|
||||
- `components/auth/TenantSlugInput.tsx` - Slug input with validation
|
||||
- `components/auth/PasswordStrengthIndicator.tsx` - zxcvbn integration
|
||||
- `components/auth/SubscriptionPlanCard.tsx` - Plan selection
|
||||
- `hooks/tenants/useCheckSlug.ts` - TanStack Query for slug check
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ Slug validation shows "Available" or "Taken" in real-time
|
||||
- ✅ Password strength indicator works (weak/medium/strong)
|
||||
- ✅ Plan selection highlights selected plan
|
||||
- ✅ After signup, user is logged in automatically
|
||||
|
||||
#### 2.3 SSO Callback Page
|
||||
|
||||
**File**: `app/(auth)/auth/callback/page.tsx`
|
||||
|
||||
**Features**:
|
||||
- Parse URL parameters (`?token=xxx&tenant=yyy`)
|
||||
- Validate state parameter (CSRF protection)
|
||||
- Store token in AuthStore
|
||||
- Redirect to original page or dashboard
|
||||
- Error handling (SSO failed)
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ Token extracted from URL and stored
|
||||
- ✅ User redirected to dashboard
|
||||
- ✅ Invalid state shows error page
|
||||
- ✅ Error page has "Try again" button
|
||||
|
||||
#### 2.4 Next.js Middleware
|
||||
|
||||
**File**: `app/middleware.ts`
|
||||
|
||||
**Features**:
|
||||
- Protect routes requiring authentication
|
||||
- Verify JWT token (use `jose` library)
|
||||
- Check tenant status (Active/Suspended)
|
||||
- Redirect logic:
|
||||
- Unauthenticated → `/login?redirect=/original-path`
|
||||
- Authenticated + on `/login` → `/dashboard`
|
||||
- Suspended tenant → `/suspended`
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ Protected routes require login
|
||||
- ✅ Token validation works (JWT signature check)
|
||||
- ✅ Redirect preserves original URL
|
||||
- ✅ Suspended tenants can't access app
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Settings Pages (Day 6 - Afternoon)
|
||||
|
||||
**Estimated Time**: 5-6 hours
|
||||
|
||||
#### 3.1 Organization Settings Page (SSO Config)
|
||||
|
||||
**File**: `app/(dashboard)/settings/organization/page.tsx`
|
||||
|
||||
**Features**:
|
||||
- Tabs: General, SSO, Billing, Usage
|
||||
- **SSO Tab**:
|
||||
- Provider selection dropdown (Azure AD, Google, Okta, SAML)
|
||||
- Dynamic form fields based on provider
|
||||
- "Test Connection" button
|
||||
- "Save Configuration" button
|
||||
- Allowed domains (TagInput)
|
||||
- Auto-provision users toggle
|
||||
|
||||
**Components to create**:
|
||||
- `components/settings/SsoConfigForm.tsx` - Dynamic SSO form
|
||||
- `hooks/tenants/useSsoConfig.ts` - Get/Update SSO config
|
||||
- `hooks/tenants/useTestSsoConnection.ts` - Test connection mutation
|
||||
|
||||
**Dynamic Fields Logic**:
|
||||
```typescript
|
||||
// OIDC providers (Azure AD, Google, Okta)
|
||||
- Authority URL (required)
|
||||
- Client ID (required)
|
||||
- Client Secret (required, password input)
|
||||
- Metadata URL (optional)
|
||||
|
||||
// SAML provider
|
||||
- Entity ID (required)
|
||||
- Sign-On URL (required)
|
||||
- X.509 Certificate (textarea, required)
|
||||
- Metadata URL (optional)
|
||||
```
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ Form fields change based on provider selection
|
||||
- ✅ "Test Connection" shows success/error message
|
||||
- ✅ "Save Configuration" updates tenant SSO config
|
||||
- ✅ Form validation with Zod
|
||||
- ✅ Only Admin users can edit (permission check)
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: MCP Token Management (Day 7)
|
||||
|
||||
**Estimated Time**: 6-8 hours
|
||||
|
||||
#### 4.1 MCP Tokens List Page
|
||||
|
||||
**File**: `app/(dashboard)/settings/mcp-tokens/page.tsx`
|
||||
|
||||
**Features**:
|
||||
- Token list table (using `@tanstack/react-table`)
|
||||
- Columns: Name, Permissions, Last Used, Expires, Status, Actions
|
||||
- "Generate Token" button (opens dialog)
|
||||
- "Revoke" button for each token
|
||||
- Token details page (click row → navigate to `/settings/mcp-tokens/{id}`)
|
||||
|
||||
**Components to create**:
|
||||
- `hooks/mcp/useMcpTokens.ts` - List tokens query
|
||||
- `hooks/mcp/useRevokeMcpToken.ts` - Revoke mutation
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ Token list loads and displays
|
||||
- ✅ Permissions shown as tags
|
||||
- ✅ Last Used shows "2 hours ago" format
|
||||
- ✅ Revoke confirmation dialog works
|
||||
- ✅ Revoked tokens marked as "Revoked" (red badge)
|
||||
|
||||
#### 4.2 Create Token Dialog
|
||||
|
||||
**File**: `components/mcp/CreateTokenDialog.tsx`
|
||||
|
||||
**Features**:
|
||||
- Multi-step dialog (3 steps):
|
||||
1. **Basic Info**: Name, Expiration date (optional)
|
||||
2. **Permissions**: Resource + Operations matrix
|
||||
3. **Review & Create**: Show summary
|
||||
|
||||
**Permission Matrix UI**:
|
||||
```
|
||||
Resources | read | create | update | delete | search
|
||||
---------------------------------------------------------
|
||||
Projects | ☑ | ☑ | ☐ | ☐ | ☑
|
||||
Issues | ☑ | ☑ | ☑ | ☐ | ☑
|
||||
Documents | ☑ | ☐ | ☐ | ☐ | ☑
|
||||
Reports | ☑ | ☐ | ☐ | ☐ | ☐
|
||||
Sprints | ☑ | ☐ | ☐ | ☐ | ☑
|
||||
```
|
||||
|
||||
**Components to create**:
|
||||
- `components/mcp/McpPermissionMatrix.tsx` - Checkbox grid
|
||||
- `components/mcp/TokenDisplay.tsx` - Display token after creation
|
||||
- `hooks/mcp/useCreateMcpToken.ts` - Create token mutation
|
||||
|
||||
**Token Display Modal**:
|
||||
- Show generated token (once only)
|
||||
- Warning: "Save this token now! You won't see it again."
|
||||
- Copy button (copies to clipboard)
|
||||
- Download button (downloads as `.txt` file)
|
||||
- "I've saved the token" button (closes modal)
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ 3-step wizard works smoothly
|
||||
- ✅ Permission matrix shows checkboxes
|
||||
- ✅ Token created successfully
|
||||
- ✅ Token displayed only once
|
||||
- ✅ Copy and download buttons work
|
||||
|
||||
#### 4.3 Token Details Page (Audit Logs)
|
||||
|
||||
**File**: `app/(dashboard)/settings/mcp-tokens/[id]/page.tsx`
|
||||
|
||||
**Features**:
|
||||
- Token metadata (name, created date, expires date, status)
|
||||
- Usage statistics (total calls, last used)
|
||||
- Audit log table:
|
||||
- Columns: Timestamp, HTTP Method, Endpoint, Status Code, Duration, IP Address
|
||||
- Pagination
|
||||
- Filters (date range, status code)
|
||||
|
||||
**Components to create**:
|
||||
- `components/mcp/AuditLogTable.tsx` - Audit log table
|
||||
- `hooks/mcp/useMcpAuditLogs.ts` - Audit logs query
|
||||
|
||||
**Success Criteria**:
|
||||
- ✅ Token metadata displayed
|
||||
- ✅ Audit log table loads with pagination
|
||||
- ✅ Date filter works
|
||||
- ✅ Status code filter works (200, 401, 403, 500)
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (Vitest + React Testing Library)
|
||||
|
||||
**Priority Components to Test**:
|
||||
|
||||
1. **Auth Components**
|
||||
- `SsoButton.tsx` - Renders provider logo, triggers redirect
|
||||
- `TenantSlugInput.tsx` - Shows "Available" or "Taken"
|
||||
- `PasswordStrengthIndicator.tsx` - Shows correct strength level
|
||||
|
||||
2. **Auth Store**
|
||||
- `useAuthStore.ts` - Login, logout, token refresh logic
|
||||
|
||||
3. **API Client**
|
||||
- `lib/api-client.ts` - Token injection, 401 handling
|
||||
|
||||
4. **Custom Hooks**
|
||||
- `useLogin.ts` - Success/error handling
|
||||
- `useCheckSlug.ts` - Debouncing, caching
|
||||
|
||||
**Example Test**:
|
||||
|
||||
```typescript
|
||||
// __tests__/components/auth/SsoButton.test.tsx
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { SsoButton } from '@/components/auth/SsoButton';
|
||||
|
||||
describe('SsoButton', () => {
|
||||
it('renders Azure AD button with logo', () => {
|
||||
render(<SsoButton provider="AzureAD" onClick={vi.fn()} />);
|
||||
expect(screen.getByText(/Sign in with Microsoft/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onClick when clicked', () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<SsoButton provider="Google" onClick={handleClick} />);
|
||||
fireEvent.click(screen.getByRole('button'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Tests (Playwright)
|
||||
|
||||
**Critical User Flows**:
|
||||
|
||||
1. **Local Login Flow**
|
||||
- Navigate to `/login`
|
||||
- Enter email and password
|
||||
- Click "Sign In"
|
||||
- Verify redirect to `/dashboard`
|
||||
- Verify token in AuthStore
|
||||
|
||||
2. **SSO Login Flow (Mocked)**
|
||||
- Click "Sign in with Azure AD"
|
||||
- Mock SSO callback with token
|
||||
- Verify redirect to dashboard
|
||||
|
||||
3. **Create MCP Token Flow**
|
||||
- Navigate to `/settings/mcp-tokens`
|
||||
- Click "Generate Token"
|
||||
- Fill in name and permissions
|
||||
- Verify token displayed
|
||||
- Verify token can be copied
|
||||
|
||||
### API Mocking (MSW)
|
||||
|
||||
**Mock Handlers**:
|
||||
|
||||
```typescript
|
||||
// mocks/handlers.ts
|
||||
import { http, HttpResponse } from 'msw';
|
||||
|
||||
export const handlers = [
|
||||
http.post('/api/auth/login', () => {
|
||||
return HttpResponse.json({
|
||||
user: { id: '1', email: 'test@example.com', fullName: 'Test User' },
|
||||
tenant: { id: '1', slug: 'test', name: 'Test Corp' },
|
||||
accessToken: 'mock-token',
|
||||
});
|
||||
}),
|
||||
|
||||
http.get('/api/tenants/check-slug', ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
const slug = url.searchParams.get('slug');
|
||||
return HttpResponse.json({ available: slug !== 'taken' });
|
||||
}),
|
||||
|
||||
http.post('/api/mcp-tokens', () => {
|
||||
return HttpResponse.json({
|
||||
tokenId: '1',
|
||||
token: 'mcp_test_abc123xyz789',
|
||||
name: 'Test Token',
|
||||
});
|
||||
}),
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### 1. Code Splitting
|
||||
|
||||
**Lazy Load Heavy Components**:
|
||||
|
||||
```typescript
|
||||
// app/(dashboard)/settings/mcp-tokens/page.tsx
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
const CreateTokenDialog = lazy(() => import('@/components/mcp/CreateTokenDialog'));
|
||||
const AuditLogTable = lazy(() => import('@/components/mcp/AuditLogTable'));
|
||||
|
||||
export default function McpTokensPage() {
|
||||
return (
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<CreateTokenDialog />
|
||||
<AuditLogTable />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. TanStack Query Caching
|
||||
|
||||
**Cache Configuration**:
|
||||
|
||||
```typescript
|
||||
// lib/query-client.ts
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 10, // 10 minutes (formerly cacheTime)
|
||||
refetchOnWindowFocus: false,
|
||||
retry: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Prefetch Critical Data**:
|
||||
|
||||
```typescript
|
||||
// app/(dashboard)/layout.tsx
|
||||
export default function DashboardLayout() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(() => {
|
||||
// Prefetch user projects
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ['projects'],
|
||||
queryFn: () => projectService.getAll(),
|
||||
});
|
||||
}, []);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Debouncing
|
||||
|
||||
**Slug Validation**:
|
||||
|
||||
```typescript
|
||||
// hooks/tenants/useCheckSlug.ts
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo } from 'react';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
export function useCheckSlug(slug: string) {
|
||||
const debouncedSlug = useMemo(
|
||||
() => debounce((value: string) => value, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['check-slug', slug],
|
||||
queryFn: () => tenantService.checkSlugAvailability(slug),
|
||||
enabled: slug.length >= 3,
|
||||
staleTime: 5000,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Image Optimization
|
||||
|
||||
**Use Next.js Image Component**:
|
||||
|
||||
```tsx
|
||||
import Image from 'next/image';
|
||||
|
||||
<Image
|
||||
src="/logos/azure-ad.svg"
|
||||
alt="Azure AD"
|
||||
width={24}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Checklist
|
||||
|
||||
### Authentication Security
|
||||
|
||||
- ✅ Access tokens stored in memory (Zustand), not localStorage
|
||||
- ✅ Refresh tokens in httpOnly cookies (managed by backend)
|
||||
- ✅ Token expiration checked before API calls
|
||||
- ✅ Automatic logout on refresh failure
|
||||
- ✅ CSRF protection (state parameter for SSO)
|
||||
- ✅ JWT signature validation in middleware
|
||||
- ✅ Redirect to login on 401 errors
|
||||
|
||||
### SSO Security
|
||||
|
||||
- ✅ State parameter generated with crypto random (32 bytes)
|
||||
- ✅ State parameter validated on callback
|
||||
- ✅ State stored in sessionStorage (cleared after use)
|
||||
- ✅ SSO errors logged and reported to user
|
||||
- ✅ Email domain validation (if configured)
|
||||
|
||||
### MCP Token Security
|
||||
|
||||
- ✅ Token displayed only once (after creation)
|
||||
- ✅ Token copied/downloaded securely
|
||||
- ✅ Token revocation confirmation dialog
|
||||
- ✅ Audit logs for all token operations
|
||||
|
||||
### General Security
|
||||
|
||||
- ✅ All API calls over HTTPS in production
|
||||
- ✅ Sensitive data (passwords) not logged
|
||||
- ✅ Error messages don't leak sensitive info
|
||||
- ✅ Rate limiting on login attempts (backend)
|
||||
- ✅ XSS protection (React auto-escapes by default)
|
||||
|
||||
---
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
### Pre-Deployment
|
||||
|
||||
- ✅ All unit tests pass (`npm run test`)
|
||||
- ✅ All integration tests pass (`npm run test:e2e`)
|
||||
- ✅ TypeScript builds without errors (`npm run build`)
|
||||
- ✅ No console errors in browser
|
||||
- ✅ Environment variables configured (`.env.production`)
|
||||
- ✅ API URLs point to production backend
|
||||
- ✅ Error tracking configured (Sentry)
|
||||
|
||||
### Performance Checks
|
||||
|
||||
- ✅ Lighthouse score > 90 (Performance, Accessibility, Best Practices, SEO)
|
||||
- ✅ First Contentful Paint < 1.5s
|
||||
- ✅ Time to Interactive < 3s
|
||||
- ✅ Bundle size < 200KB (gzipped)
|
||||
- ✅ Images optimized (WebP format)
|
||||
|
||||
### Security Checks
|
||||
|
||||
- ✅ JWT_SECRET in production environment variables
|
||||
- ✅ No hardcoded secrets in code
|
||||
- ✅ HTTPS enforced (Next.js redirects)
|
||||
- ✅ CSP headers configured
|
||||
- ✅ Security headers (X-Frame-Options, X-Content-Type-Options)
|
||||
|
||||
### Monitoring
|
||||
|
||||
- ✅ Error tracking (Sentry or similar)
|
||||
- ✅ Performance monitoring (Vercel Analytics)
|
||||
- ✅ API error logging
|
||||
- ✅ User analytics (PostHog or similar)
|
||||
|
||||
---
|
||||
|
||||
## Estimated Effort
|
||||
|
||||
| Phase | Tasks | Time | Priority |
|
||||
|-------|-------|------|----------|
|
||||
| **Phase 1: Core Infrastructure** | API Client, Auth Store, Types, Middleware | 4 hours | P0 |
|
||||
| **Phase 2: Authentication** | Login, Signup, SSO Callback, Middleware | 8 hours | P0 |
|
||||
| **Phase 3: Settings** | Organization Settings, SSO Config | 6 hours | P1 |
|
||||
| **Phase 4: MCP Tokens** | Token List, Create, Display, Audit Logs | 8 hours | P1 |
|
||||
| **Testing** | Unit tests, Integration tests, E2E tests | 6 hours | P1 |
|
||||
| **Total** | | **32 hours** (~4 days) | |
|
||||
|
||||
**Timeline**:
|
||||
- **Day 5**: Phase 1 + Phase 2 (Login, Signup)
|
||||
- **Day 6**: Phase 2 (SSO Callback) + Phase 3 (Settings)
|
||||
- **Day 7**: Phase 4 (MCP Tokens)
|
||||
- **Day 8**: Testing + Bug fixes
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Backend API Readiness Check**
|
||||
- Verify backend APIs are ready: `/api/auth/login`, `/api/tenants/check-slug`, `/api/mcp-tokens`, etc.
|
||||
- Test API endpoints with Postman or Insomnia
|
||||
- Document any API issues or missing endpoints
|
||||
|
||||
2. **Environment Setup**
|
||||
- Clone frontend repo
|
||||
- Install dependencies (`npm install`)
|
||||
- Configure `.env.local`
|
||||
- Start dev server (`npm run dev`)
|
||||
|
||||
3. **Start with Phase 1**
|
||||
- Create `lib/api-client.ts`
|
||||
- Create `stores/useAuthStore.ts`
|
||||
- Create TypeScript types
|
||||
- Test token injection and refresh
|
||||
|
||||
4. **Continuous Testing**
|
||||
- Write tests as you build features
|
||||
- Run tests before committing code
|
||||
- Fix failing tests immediately
|
||||
|
||||
5. **Code Review**
|
||||
- Self-review code before committing
|
||||
- Use ESLint and Prettier
|
||||
- Follow TypeScript strict mode
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
### Technical Risks
|
||||
|
||||
| Risk | Probability | Impact | Mitigation |
|
||||
|------|-------------|--------|------------|
|
||||
| Token refresh fails during API calls | Medium | High | Implement queue for pending requests during refresh |
|
||||
| SSO callback errors (state mismatch) | Low | High | Add detailed error logging and user-friendly error page |
|
||||
| Permission matrix UI too complex | Low | Medium | Use shadcn Checkbox component, add "Select All" shortcuts |
|
||||
| TanStack Query cache invalidation issues | Medium | Medium | Document cache invalidation strategy, use query keys consistently |
|
||||
| Middleware performance (JWT validation) | Low | Low | Cache JWT validation results, use efficient `jose` library |
|
||||
|
||||
### Schedule Risks
|
||||
|
||||
| Risk | Probability | Impact | Mitigation |
|
||||
|------|-------------|--------|------------|
|
||||
| Backend API delays | High | High | Mock API responses with MSW, develop UI first |
|
||||
| Complex SSO flow takes longer | Medium | Medium | Simplify SSO flow, skip SAML in MVP if needed |
|
||||
| Testing takes longer than expected | Medium | Medium | Prioritize critical path tests, skip edge cases for MVP |
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This implementation plan provides a clear roadmap for building ColaFlow's enterprise-level frontend features. The plan is structured to minimize risk, maximize code quality, and deliver a production-ready solution within 4 days.
|
||||
|
||||
**Key Success Factors**:
|
||||
- ✅ Backend API readiness
|
||||
- ✅ Clear component boundaries
|
||||
- ✅ Comprehensive testing strategy
|
||||
- ✅ Performance optimization from day 1
|
||||
- ✅ Security-first approach
|
||||
|
||||
**Next Document**: `api-integration-guide.md` (detailed API endpoints and request/response examples)
|
||||
1021
docs/frontend/state-management-guide.md
Normal file
1021
docs/frontend/state-management-guide.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user