chore(admin-web): clear repo-wide lint errors
This commit is contained in:
parent
5e40cd1b6e
commit
631784e551
@ -1,9 +1,9 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
|
||||
const ADMIN_EMAIL = 'admin@example.com';
|
||||
const ADMIN_PASSWORD = 'Admin123!';
|
||||
|
||||
async function loginAsAdmin(page: any) {
|
||||
async function loginAsAdmin(page: Page) {
|
||||
await page.goto('/login');
|
||||
await page.getByLabel('Email').fill(ADMIN_EMAIL);
|
||||
await page.getByLabel('Password').fill(ADMIN_PASSWORD);
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
|
||||
const ADMIN_EMAIL = 'admin@example.com';
|
||||
const ADMIN_PASSWORD = 'Admin123!';
|
||||
|
||||
async function loginAsAdmin(page: any) {
|
||||
async function loginAsAdmin(page: Page) {
|
||||
await page.goto('/login');
|
||||
await page.getByLabel('Email').fill(ADMIN_EMAIL);
|
||||
await page.getByLabel('Password').fill(ADMIN_PASSWORD);
|
||||
@ -94,7 +94,11 @@ test.describe('Diagnostics - Debug Sessions', () => {
|
||||
await expect(page.getByText('Session paused')).toBeVisible();
|
||||
|
||||
// Resume
|
||||
await page.locator('tr:has-text("Paused")').first().getByRole('button', { name: 'Resume' }).click();
|
||||
await page
|
||||
.locator('tr:has-text("Paused")')
|
||||
.first()
|
||||
.getByRole('button', { name: 'Resume' })
|
||||
.click();
|
||||
await expect(page.getByText('Session resumed')).toBeVisible();
|
||||
}
|
||||
});
|
||||
@ -165,7 +169,9 @@ test.describe('Diagnostics - Logs & Traces', () => {
|
||||
await expect(page.locator('[data-testid="log-entry"]').first()).toBeVisible();
|
||||
|
||||
// Check log level badges
|
||||
await expect(page.getByText('INFO').or(page.getByText('ERROR')).or(page.getByText('DEBUG'))).toBeVisible();
|
||||
await expect(
|
||||
page.getByText('INFO').or(page.getByText('ERROR')).or(page.getByText('DEBUG'))
|
||||
).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
|
||||
const ADMIN_EMAIL = 'admin@example.com';
|
||||
const ADMIN_PASSWORD = 'Admin123!';
|
||||
|
||||
async function loginAsAdmin(page: any) {
|
||||
async function loginAsAdmin(page: Page) {
|
||||
await page.goto('/login');
|
||||
await page.getByLabel('Email').fill(ADMIN_EMAIL);
|
||||
await page.getByLabel('Password').fill(ADMIN_PASSWORD);
|
||||
@ -75,7 +75,10 @@ test.describe('Rich Media Broadcasts', () => {
|
||||
|
||||
// Go back to list and open preview
|
||||
await page.click('text=Broadcasts');
|
||||
await page.locator('tr:has-text("Preview Test")').getByRole('button', { name: 'Preview' }).click();
|
||||
await page
|
||||
.locator('tr:has-text("Preview Test")')
|
||||
.getByRole('button', { name: 'Preview' })
|
||||
.click();
|
||||
|
||||
// Verify media in preview modal
|
||||
await expect(page.locator('img[src*="preview.jpg"]')).toBeVisible();
|
||||
|
||||
@ -23,11 +23,23 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import type { CreateExperimentInput, ExperimentSuggestion } from '@/lib/experiments-types';
|
||||
import type {
|
||||
AllocationStrategy,
|
||||
CreateExperimentInput,
|
||||
ExperimentSuggestion,
|
||||
MetricType,
|
||||
PrimaryMetric,
|
||||
} from '@/lib/experiments-types';
|
||||
|
||||
const steps = [
|
||||
{ id: 'hypothesis', title: 'Hypothesis', icon: Lightbulb },
|
||||
@ -49,7 +61,13 @@ export default function NewExperimentPage() {
|
||||
description: '',
|
||||
hypothesis: '',
|
||||
variants: [
|
||||
{ key: 'control', name: 'Control', description: 'Current implementation', isControl: true, flagConfig: {} },
|
||||
{
|
||||
key: 'control',
|
||||
name: 'Control',
|
||||
description: 'Current implementation',
|
||||
isControl: true,
|
||||
flagConfig: {},
|
||||
},
|
||||
{ key: 'variant_a', name: 'Variant A', description: '', isControl: false, flagConfig: {} },
|
||||
],
|
||||
allocationStrategy: 'random',
|
||||
@ -191,18 +209,10 @@ export default function NewExperimentPage() {
|
||||
onApplySuggestion={applyAiSuggestion}
|
||||
/>
|
||||
)}
|
||||
{currentStep === 1 && (
|
||||
<VariantsStep formData={formData} setFormData={setFormData} />
|
||||
)}
|
||||
{currentStep === 2 && (
|
||||
<MetricsStep formData={formData} setFormData={setFormData} />
|
||||
)}
|
||||
{currentStep === 3 && (
|
||||
<TargetingStep formData={formData} setFormData={setFormData} />
|
||||
)}
|
||||
{currentStep === 4 && (
|
||||
<ReviewStep formData={formData} />
|
||||
)}
|
||||
{currentStep === 1 && <VariantsStep formData={formData} setFormData={setFormData} />}
|
||||
{currentStep === 2 && <MetricsStep formData={formData} setFormData={setFormData} />}
|
||||
{currentStep === 3 && <TargetingStep formData={formData} setFormData={setFormData} />}
|
||||
{currentStep === 4 && <ReviewStep formData={formData} />}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@ -312,9 +322,15 @@ function HypothesisStep({
|
||||
{aiSuggestions.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{aiSuggestions.map((suggestion, index) => (
|
||||
<Card key={index} className="cursor-pointer hover:border-primary" onClick={() => onApplySuggestion(suggestion)}>
|
||||
<Card
|
||||
key={index}
|
||||
className="cursor-pointer hover:border-primary"
|
||||
onClick={() => onApplySuggestion(suggestion)}
|
||||
>
|
||||
<CardContent className="p-3">
|
||||
<p className="text-sm font-medium line-clamp-2">{suggestion.hypothesis.primary}</p>
|
||||
<p className="text-sm font-medium line-clamp-2">
|
||||
{suggestion.hypothesis.primary}
|
||||
</p>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Impact: {suggestion.hypothesis.impactScore}/100
|
||||
@ -329,7 +345,8 @@ function HypothesisStep({
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Click "Load Suggestions" to see AI-generated experiment ideas based on your product usage patterns.
|
||||
Click "Load Suggestions" to see AI-generated experiment ideas based on your
|
||||
product usage patterns.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@ -383,7 +400,9 @@ function VariantsStep({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{variant.isControl ? (
|
||||
<Badge variant="outline" className="border-blue-300 text-blue-700">Control</Badge>
|
||||
<Badge variant="outline" className="border-blue-300 text-blue-700">
|
||||
Control
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="outline">Variant</Badge>
|
||||
)}
|
||||
@ -429,7 +448,8 @@ function VariantsStep({
|
||||
))}
|
||||
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You need at least 2 variants: a Control (current implementation) and at least one Treatment variant.
|
||||
You need at least 2 variants: a Control (current implementation) and at least one Treatment
|
||||
variant.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@ -488,7 +508,7 @@ function MetricsStep({
|
||||
onValueChange={v =>
|
||||
setFormData({
|
||||
...formData,
|
||||
primaryMetric: { ...formData.primaryMetric!, type: v as any },
|
||||
primaryMetric: { ...formData.primaryMetric!, type: v as MetricType },
|
||||
})
|
||||
}
|
||||
>
|
||||
@ -511,7 +531,10 @@ function MetricsStep({
|
||||
onValueChange={v =>
|
||||
setFormData({
|
||||
...formData,
|
||||
primaryMetric: { ...formData.primaryMetric!, direction: v as any },
|
||||
primaryMetric: {
|
||||
...formData.primaryMetric!,
|
||||
direction: v as PrimaryMetric['direction'],
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
@ -536,7 +559,9 @@ function MetricsStep({
|
||||
<CardContent className="space-y-4">
|
||||
<Select
|
||||
value={formData.allocationStrategy}
|
||||
onValueChange={v => setFormData({ ...formData, allocationStrategy: v as any })}
|
||||
onValueChange={v =>
|
||||
setFormData({ ...formData, allocationStrategy: v as AllocationStrategy })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
@ -590,7 +615,9 @@ function TargetingStep({
|
||||
{['ios', 'android', 'web'].map(platform => (
|
||||
<Badge
|
||||
key={platform}
|
||||
variant={formData.targeting?.platforms?.includes(platform) ? 'default' : 'outline'}
|
||||
variant={
|
||||
formData.targeting?.platforms?.includes(platform) ? 'default' : 'outline'
|
||||
}
|
||||
className="cursor-pointer capitalize"
|
||||
onClick={() => {
|
||||
const current = formData.targeting?.platforms || [];
|
||||
@ -615,7 +642,9 @@ function TargetingStep({
|
||||
{['free', 'pro', 'enterprise'].map(segment => (
|
||||
<Badge
|
||||
key={segment}
|
||||
variant={formData.targeting?.userSegments?.includes(segment) ? 'default' : 'outline'}
|
||||
variant={
|
||||
formData.targeting?.userSegments?.includes(segment) ? 'default' : 'outline'
|
||||
}
|
||||
className="cursor-pointer capitalize"
|
||||
onClick={() => {
|
||||
const current = formData.targeting?.userSegments || [];
|
||||
@ -668,7 +697,10 @@ function TargetingStep({
|
||||
onChange={e =>
|
||||
setFormData({
|
||||
...formData,
|
||||
guardrails: { ...formData.guardrails!, maxDurationDays: parseInt(e.target.value) },
|
||||
guardrails: {
|
||||
...formData.guardrails!,
|
||||
maxDurationDays: parseInt(e.target.value),
|
||||
},
|
||||
})
|
||||
}
|
||||
className="mt-1"
|
||||
@ -686,7 +718,10 @@ function TargetingStep({
|
||||
onChange={e =>
|
||||
setFormData({
|
||||
...formData,
|
||||
guardrails: { ...formData.guardrails!, winnerThreshold: parseInt(e.target.value) },
|
||||
guardrails: {
|
||||
...formData.guardrails!,
|
||||
winnerThreshold: parseInt(e.target.value),
|
||||
},
|
||||
})
|
||||
}
|
||||
className="mt-1"
|
||||
@ -713,7 +748,10 @@ function ReviewStep({ formData }: { formData: Partial<CreateExperimentInput> })
|
||||
<ReviewItem label="Primary Metric" value={formData.primaryMetric?.name} />
|
||||
<ReviewItem label="Allocation Strategy" value={formData.allocationStrategy} />
|
||||
<ReviewItem label="Target Traffic" value={`${formData.targetPercent}%`} />
|
||||
<ReviewItem label="Auto Stop" value={formData.guardrails?.autoStopEnabled ? 'Enabled' : 'Disabled'} />
|
||||
<ReviewItem
|
||||
label="Auto Stop"
|
||||
value={formData.guardrails?.autoStopEnabled ? 'Enabled' : 'Disabled'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Alert>
|
||||
|
||||
@ -40,6 +40,7 @@ export default function ExperimentsPage() {
|
||||
const [experiments, setExperiments] = useState<ExperimentDoc[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [now] = useState(() => Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
fetchExperiments();
|
||||
@ -113,9 +114,7 @@ export default function ExperimentsPage() {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
Running
|
||||
</CardTitle>
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Running</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold text-green-600">{runningCount}</div>
|
||||
@ -123,9 +122,7 @@ export default function ExperimentsPage() {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
Completed
|
||||
</CardTitle>
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Completed</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold text-blue-600">{completedCount}</div>
|
||||
@ -149,12 +146,14 @@ export default function ExperimentsPage() {
|
||||
<TabsTrigger value="all">All ({experiments.length})</TabsTrigger>
|
||||
<TabsTrigger value="running">Running ({runningCount})</TabsTrigger>
|
||||
<TabsTrigger value="completed">Completed ({completedCount})</TabsTrigger>
|
||||
<TabsTrigger value="draft">Drafts ({experiments.filter(e => e.status === 'draft').length})</TabsTrigger>
|
||||
<TabsTrigger value="draft">
|
||||
Drafts ({experiments.filter(e => e.status === 'draft').length})
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="all" className="space-y-4">
|
||||
{experiments.map(experiment => (
|
||||
<ExperimentCard key={experiment.id} experiment={experiment} />
|
||||
<ExperimentCard key={experiment.id} experiment={experiment} now={now} />
|
||||
))}
|
||||
{experiments.length === 0 && (
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
@ -168,7 +167,7 @@ export default function ExperimentsPage() {
|
||||
{experiments
|
||||
.filter(e => e.status === 'running')
|
||||
.map(experiment => (
|
||||
<ExperimentCard key={experiment.id} experiment={experiment} />
|
||||
<ExperimentCard key={experiment.id} experiment={experiment} now={now} />
|
||||
))}
|
||||
</TabsContent>
|
||||
|
||||
@ -176,7 +175,7 @@ export default function ExperimentsPage() {
|
||||
{experiments
|
||||
.filter(e => e.status === 'completed')
|
||||
.map(experiment => (
|
||||
<ExperimentCard key={experiment.id} experiment={experiment} />
|
||||
<ExperimentCard key={experiment.id} experiment={experiment} now={now} />
|
||||
))}
|
||||
</TabsContent>
|
||||
|
||||
@ -184,7 +183,7 @@ export default function ExperimentsPage() {
|
||||
{experiments
|
||||
.filter(e => e.status === 'draft')
|
||||
.map(experiment => (
|
||||
<ExperimentCard key={experiment.id} experiment={experiment} />
|
||||
<ExperimentCard key={experiment.id} experiment={experiment} now={now} />
|
||||
))}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
@ -192,12 +191,12 @@ export default function ExperimentsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function ExperimentCard({ experiment }: { experiment: ExperimentDoc }) {
|
||||
function ExperimentCard({ experiment, now }: { experiment: ExperimentDoc; now: number }) {
|
||||
const status = statusConfig[experiment.status] || statusConfig.draft;
|
||||
const StatusIcon = status.icon;
|
||||
|
||||
const daysRunning = experiment.startedAt
|
||||
? Math.floor((Date.now() - new Date(experiment.startedAt).getTime()) / (1000 * 60 * 60 * 24))
|
||||
? Math.floor((now - new Date(experiment.startedAt).getTime()) / (1000 * 60 * 60 * 24))
|
||||
: 0;
|
||||
|
||||
return (
|
||||
|
||||
@ -1,23 +1,22 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export interface TextareaProps
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
)
|
||||
Textarea.displayName = "Textarea"
|
||||
);
|
||||
Textarea.displayName = 'Textarea';
|
||||
|
||||
export { Textarea }
|
||||
export { Textarea };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user