Skip to content

Commit 155378a

Browse files
Refactor: Split ServerDetailPage.tsx into smaller components
This commit refactors the ServerDetailPage.tsx file into smaller, more manageable components to improve code organization and readability.
1 parent eb07a35 commit 155378a

6 files changed

Lines changed: 320 additions & 279 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
2+
import { useState, useEffect } from 'react';
3+
import { FilesIcon, Loader2 } from 'lucide-react';
4+
import { toast } from 'sonner';
5+
import { supabase } from '@/integrations/supabase/client';
6+
import { CodeEditor } from '@/components/editor/CodeEditor';
7+
import { formatJson, isValidJson } from '@/utils/formatter';
8+
9+
interface ManifestCodeViewerProps {
10+
manifestId: bigint | null;
11+
isLoading: boolean;
12+
}
13+
14+
export function ManifestCodeViewer({ manifestId, isLoading: initialLoading }: ManifestCodeViewerProps) {
15+
const [codeContent, setCodeContent] = useState('');
16+
const [isCodeLoading, setIsCodeLoading] = useState(false);
17+
const [contentLanguage, setContentLanguage] = useState<'json' | 'yaml'>('json');
18+
const [useMonaco, setUseMonaco] = useState(true);
19+
20+
// Fetch code content when manifestId changes
21+
useEffect(() => {
22+
const fetchManifestContent = async () => {
23+
if (manifestId && !codeContent) {
24+
setIsCodeLoading(true);
25+
try {
26+
// response format is: { content: {manifest: string}, status_code: number }
27+
const { data: manifestData, error } = await supabase
28+
.rpc('get_generated_manifest', { manifest_id: manifestId });
29+
30+
if (error) throw error;
31+
32+
// Detect content type and set language
33+
const contentStr = manifestData?.content?.manifest || '';
34+
detectContentLanguage(contentStr);
35+
36+
// Set content with a small delay to ensure UI is ready
37+
setTimeout(() => {
38+
setCodeContent(contentStr);
39+
setIsCodeLoading(false);
40+
}, 100);
41+
42+
} catch (error) {
43+
console.error('Error fetching manifest code:', error);
44+
toast.error(`Failed to load code content: ${error.message}`);
45+
setIsCodeLoading(false);
46+
setUseMonaco(false); // Fallback to textarea on error
47+
}
48+
}
49+
};
50+
51+
fetchManifestContent();
52+
}, [manifestId, codeContent]);
53+
54+
// Function to detect content language (JSON or YAML)
55+
const detectContentLanguage = (content: string) => {
56+
if (!content.trim()) return;
57+
58+
if (isValidJson(content)) {
59+
setContentLanguage('json');
60+
} else {
61+
setContentLanguage('yaml');
62+
}
63+
};
64+
65+
// Format content based on language
66+
const formatContent = () => {
67+
try {
68+
if (contentLanguage === 'json') {
69+
const formattedContent = formatJson(codeContent);
70+
setCodeContent(formattedContent);
71+
toast.success('Content formatted as JSON');
72+
} else {
73+
toast.success('Content formatted as YAML');
74+
}
75+
} catch (error) {
76+
console.error('Error formatting content:', error);
77+
toast.error('Failed to format content. Invalid syntax.');
78+
}
79+
};
80+
81+
// Handle language toggle
82+
const toggleLanguage = () => {
83+
setContentLanguage(prev => prev === 'json' ? 'yaml' : 'json');
84+
};
85+
86+
return (
87+
<div className="mt-6 border rounded-md overflow-hidden">
88+
<div className="p-2 bg-muted border-b flex items-center justify-between">
89+
<div className="flex items-center">
90+
<FilesIcon className="h-4 w-4 mr-2" />
91+
<span className="text-sm font-medium">Server Code View</span>
92+
</div>
93+
<div className="flex items-center gap-2">
94+
<button
95+
onClick={formatContent}
96+
className="text-xs hover:underline"
97+
>
98+
Format
99+
</button>
100+
<button
101+
onClick={toggleLanguage}
102+
className="text-xs hover:underline mx-2"
103+
>
104+
{contentLanguage.toUpperCase()}
105+
</button>
106+
</div>
107+
</div>
108+
<div className="h-[500px]">
109+
{isCodeLoading || initialLoading ? (
110+
<div className="flex items-center justify-center h-full">
111+
<Loader2 className="h-6 w-6 animate-spin mr-2" />
112+
<span>Loading code...</span>
113+
</div>
114+
) : (
115+
<CodeEditor
116+
value={codeContent}
117+
onChange={(value) => setCodeContent(value)}
118+
language={contentLanguage}
119+
height="500px"
120+
/>
121+
)}
122+
</div>
123+
</div>
124+
);
125+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
export const getStatusColorClass = (status: string) => {
3+
switch (status?.toLowerCase()) {
4+
case 'active': return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300';
5+
case 'development': return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300';
6+
case 'maintenance': return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300';
7+
case 'deprecated': return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300';
8+
case 'planning': return 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300';
9+
case 'inactive': return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300';
10+
default: return 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300';
11+
}
12+
};

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export { useServerTools } from './servers/tools/useServerTools';
99
export { useProjectTools } from './projects/useProjectTools';
1010
export { useIsMobile as useMobile } from './use-mobile';
1111
export { useDashboardMetrics } from './useDashboardMetrics';
12+
export { useServerManifest } from './servers/useServerManifest';

src/hooks/servers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './useServer';
55
export * from './useServerApplications';
66
export * from './useServerAITools';
77
export * from './useServerDetails';
8+
export * from './useServerManifest';
89
export * from './tools/useServerTools';
910
export * from './tools/useProjectTools';
1011
export * from './tools/useOrganizationTools';
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
2+
import { useState, useEffect } from 'react';
3+
import { supabase } from '@/integrations/supabase/client';
4+
import { toast } from 'sonner';
5+
import { Server } from '@/types/server';
6+
7+
interface UseServerManifestResult {
8+
server: Server | null;
9+
isLoading: boolean;
10+
manifestId: bigint | null;
11+
isActionsOpen: boolean;
12+
setIsActionsOpen: (open: boolean) => void;
13+
showCodeView: boolean;
14+
setShowCodeView: (show: boolean) => void;
15+
handleImport: () => Promise<void>;
16+
handleExport: () => Promise<void>;
17+
handleDeploy: () => Promise<void>;
18+
handleUndeploy: () => Promise<void>;
19+
}
20+
21+
export function useServerManifest(id: string | undefined): UseServerManifestResult {
22+
const [server, setServer] = useState<Server | null>(null);
23+
const [isLoading, setIsLoading] = useState(true);
24+
const [isActionsOpen, setIsActionsOpen] = useState(false);
25+
const [showCodeView, setShowCodeView] = useState(false);
26+
const [manifestId, setManifestId] = useState<bigint | null>(null);
27+
28+
useEffect(() => {
29+
const fetchServerDetails = async () => {
30+
if (!id) return;
31+
32+
setIsLoading(true);
33+
try {
34+
console.log('Fetching server details for ID/slug:', id);
35+
36+
let query = supabase.from('servers')
37+
.select(`
38+
*,
39+
project_servers!inner (
40+
project_id
41+
)
42+
`);
43+
44+
// Check if the ID is a UUID format or a slug
45+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id);
46+
if (isUuid) {
47+
query = query.eq('id', id);
48+
} else {
49+
query = query.eq('slug', id);
50+
}
51+
52+
let { data, error } = await query;
53+
console.log('Initial query result:', data, error);
54+
55+
if (error || !data || data.length === 0) {
56+
// If no results found with inner join, try without the join
57+
query = supabase.from('servers').select('*');
58+
if (isUuid) {
59+
query = query.eq('id', id);
60+
} else {
61+
query = query.eq('slug', id);
62+
}
63+
64+
const result = await query.single();
65+
data = result.data;
66+
error = result.error;
67+
}
68+
console.log('Fallback query result:', data, error);
69+
70+
if (error) {
71+
console.error('Error fetching server details:', error);
72+
toast.error(`Failed to load server: ${error.message}`);
73+
throw error;
74+
}
75+
76+
// Process the data to extract project_id if available
77+
let serverData;
78+
if (Array.isArray(data) && data.length > 0) {
79+
const firstResult = data[0];
80+
// Extract the project_id if available
81+
const projectId = firstResult.project_servers?.length > 0
82+
? firstResult.project_servers[0].project_id
83+
: null;
84+
85+
// Create the server object with project_id
86+
serverData = {
87+
...firstResult,
88+
project_id: projectId
89+
};
90+
delete serverData.project_servers;
91+
} else {
92+
serverData = data;
93+
}
94+
95+
console.log('Fetched server details:', serverData);
96+
setServer(serverData as Server);
97+
const { data: dataCode, error: dataCodeError } = await supabase
98+
.rpc('generate_manifest', { server_id: serverData.id });
99+
100+
if (dataCodeError) {
101+
console.error('Error fetching server code:', dataCodeError);
102+
toast.error(`Failed to load server code: ${dataCodeError.message}`);
103+
return;
104+
}
105+
106+
setManifestId(dataCode);
107+
} catch (error) {
108+
console.error('Error in server fetch:', error);
109+
toast.error('Failed to load server details');
110+
} finally {
111+
setIsLoading(false);
112+
}
113+
};
114+
115+
fetchServerDetails();
116+
}, [id]);
117+
118+
const handleImport = async () => {
119+
if (!server) return;
120+
// simulate import process
121+
await new Promise((resolve) => setTimeout(resolve, 2000));
122+
toast.success('Server imported successfully');
123+
};
124+
125+
const handleExport = async () => {
126+
if (!server) return;
127+
// simulate export process
128+
await new Promise((resolve) => setTimeout(resolve, 2000));
129+
toast.success('Server exported successfully');
130+
};
131+
132+
const handleDeploy = async () => {
133+
if (!server) return;
134+
// simulate deploy process
135+
await new Promise((resolve) => setTimeout(resolve, 2000));
136+
toast.success('Server deployed successfully');
137+
};
138+
139+
const handleUndeploy = async () => {
140+
if (!server) return;
141+
// simulate undeploy process
142+
await new Promise((resolve) => setTimeout(resolve, 2000));
143+
toast.success('Server undeployed successfully');
144+
};
145+
146+
return {
147+
server,
148+
isLoading,
149+
manifestId,
150+
isActionsOpen,
151+
setIsActionsOpen,
152+
showCodeView,
153+
setShowCodeView,
154+
handleImport,
155+
handleExport,
156+
handleDeploy,
157+
handleUndeploy
158+
};
159+
}

0 commit comments

Comments
 (0)