File size: 4,090 Bytes
391c43e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 'use client';
import React, { useEffect, useState } from 'react';
import { Deployment } from '@/lib/vfs/types';
import { Server, Loader2 } from 'lucide-react';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
interface DeploymentSelectorProps {
projectId: string;
selectedDeploymentId: string | null;
onDeploymentChange: (deploymentId: string | null, deploymentName: string | null) => void;
className?: string;
workspaceId?: string;
}
export function DeploymentSelector({
projectId,
selectedDeploymentId,
onDeploymentChange,
className,
workspaceId,
}: DeploymentSelectorProps) {
const apiBase = workspaceId ? `/api/w/${workspaceId}` : '/api';
const [deployments, setDeployments] = useState<Deployment[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Fetch deployments for this project
useEffect(() => {
const fetchDeployments = async () => {
// Only fetch in server mode
if (process.env.NEXT_PUBLIC_SERVER_MODE !== 'true') {
setLoading(false);
return;
}
try {
setLoading(true);
setError(null);
const response = await fetch(`${apiBase}/projects/${projectId}/deployments`);
if (!response.ok) {
throw new Error('Failed to fetch deployments');
}
const data = await response.json();
setDeployments(data.deployments || []);
// Auto-select first deployment if only one exists and nothing is selected
if (data.deployments?.length === 1 && !selectedDeploymentId) {
const deployment = data.deployments[0];
if (deployment.databaseEnabled) {
onDeploymentChange(deployment.id, deployment.name);
}
}
} catch {
// Expected in browser mode or when project hasn't been synced to server yet
// Don't show error — deployments are optional
} finally {
setLoading(false);
}
};
fetchDeployments();
}, [projectId]); // Only refetch when project changes
// Don't render in browser mode
if (process.env.NEXT_PUBLIC_SERVER_MODE !== 'true') {
return null;
}
// Don't render if no deployments with database enabled
const databaseEnabledDeployments = deployments.filter(s => s.databaseEnabled);
if (!loading && databaseEnabledDeployments.length === 0) {
return null;
}
if (loading) {
return (
<div className={cn('flex items-center gap-2 text-sm text-muted-foreground', className)}>
<Loader2 className="h-4 w-4 animate-spin" />
<span>Loading deployments...</span>
</div>
);
}
if (error) {
return (
<div className={cn('flex items-center gap-2 text-sm text-destructive', className)}>
<Server className="h-4 w-4" />
<span>{error}</span>
</div>
);
}
return (
<div className={cn('flex items-center gap-2', className)}>
<Select
value={selectedDeploymentId || 'none'}
onValueChange={(value) => {
if (value === 'none') {
onDeploymentChange(null, null);
} else {
const deployment = deployments.find(s => s.id === value);
onDeploymentChange(value, deployment?.name || null);
}
}}
>
<SelectTrigger size="sm" className="w-[180px] h-8">
<SelectValue placeholder="No deployment" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">
<span className="text-muted-foreground">No deployment</span>
</SelectItem>
{databaseEnabledDeployments.map((deployment) => (
<SelectItem key={deployment.id} value={deployment.id}>
<div className="flex items-center gap-2">
<Server className="h-3.5 w-3.5" />
<span>{deployment.name}</span>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
);
}
|