Skip to content

Commit 9c2b6f1

Browse files
committed
UI revamp: Workspace and session management improvements
Major UI improvements including: - Refactor workspace creation and management into modals - Convert workspace page tabs into sections - Add spec repository integration and UI - Add Context widget with functionality - Add Share Feedback feature - Update model configuration to use Claude Sonnet 4.5 - Improve workspace and session details layouts - Cleanup older workspace-related files - Various UI styling and spacing fixes
1 parent 1f69370 commit 9c2b6f1

File tree

38 files changed

+2384
-1632
lines changed

38 files changed

+2384
-1632
lines changed

components/frontend/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,7 @@ jspm_packages/
6060

6161
# TypeScript
6262
*.tsbuildinfo
63-
next-env.d.ts
63+
next-env.d.ts
64+
65+
# Previous frontend
66+
previous-frontend/

components/frontend/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,15 @@ In production, put an OAuth/ingress proxy in front of the app to set these heade
6767
### Environment variables
6868
- `BACKEND_URL` (default: `http://localhost:8080/api`)
6969
- Used by server-side API routes to reach the backend.
70+
- `FEEDBACK_URL` (optional)
71+
- URL for the feedback link in the masthead. If not set, the link will not appear.
7072
- Optional dev helpers: `OC_USER`, `OC_EMAIL`, `OC_TOKEN`, `ENABLE_OC_WHOAMI=1`
7173

7274
You can also put these in a `.env.local` file in this folder:
7375
```
7476
BACKEND_URL=http://localhost:8080/api
77+
# Optional: URL for feedback link in masthead
78+
# FEEDBACK_URL=https://forms.example.com/feedback
7579
# Optional dev helpers
7680
# OC_USER=your.name
7781
# OC_EMAIL=your.name@example.com
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { BACKEND_URL } from '@/lib/config'
2+
import { buildForwardHeadersAsync } from '@/lib/auth'
3+
4+
export async function POST(
5+
request: Request,
6+
{ params }: { params: Promise<{ name: string; id: string }> },
7+
) {
8+
const { name, id } = await params
9+
const headers = await buildForwardHeadersAsync(request)
10+
const body = await request.text()
11+
const resp = await fetch(`${BACKEND_URL}/projects/${encodeURIComponent(name)}/rfe-workflows/${encodeURIComponent(id)}/sessions/link`, {
12+
method: 'POST',
13+
headers,
14+
body,
15+
})
16+
const data = await resp.text()
17+
return new Response(data, { status: resp.status, headers: { 'Content-Type': 'application/json' } })
18+
}
19+

components/frontend/src/app/integrations/IntegrationsClient.tsx

Lines changed: 20 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,32 @@
11
'use client'
22

33
import React from 'react'
4-
import { Button } from '@/components/ui/button'
5-
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
6-
import { useGitHubStatus, useDisconnectGitHub } from '@/services/queries'
7-
import { successToast, errorToast } from '@/hooks/use-toast'
4+
import { GitHubConnectionCard } from '@/components/github-connection-card'
5+
import { PageHeader } from '@/components/page-header'
86

97
type Props = { appSlug?: string }
108

119
export default function IntegrationsClient({ appSlug }: Props) {
12-
const { data: status, isLoading, refetch } = useGitHubStatus()
13-
const disconnectMutation = useDisconnectGitHub()
14-
15-
const handleConnect = () => {
16-
if (!appSlug) return
17-
const setupUrl = new URL('/integrations/github/setup', window.location.origin)
18-
const redirectUri = encodeURIComponent(setupUrl.toString())
19-
const url = `https://github.com/apps/${appSlug}/installations/new?redirect_uri=${redirectUri}`
20-
window.location.href = url
21-
}
22-
23-
const handleDisconnect = async () => {
24-
disconnectMutation.mutate(undefined, {
25-
onSuccess: () => {
26-
successToast('GitHub disconnected successfully')
27-
refetch()
28-
},
29-
onError: (error) => {
30-
errorToast(error instanceof Error ? error.message : 'Failed to disconnect GitHub')
31-
},
32-
})
33-
}
34-
35-
const handleManage = () => {
36-
window.open('https://github.com/settings/installations', '_blank')
37-
}
38-
3910
return (
40-
<div className="max-w-3xl mx-auto p-6 space-y-6">
41-
<h1 className="text-2xl font-semibold">Integrations</h1>
42-
43-
<Card>
44-
<CardHeader>
45-
<CardTitle>GitHub</CardTitle>
46-
<CardDescription>Connect GitHub to enable forks, PRs, and repo browsing</CardDescription>
47-
</CardHeader>
48-
<CardContent className="flex items-center justify-between gap-4">
49-
<div className="text-sm">
50-
{status?.installed ? (
51-
<div>
52-
Connected{status.githubUserId ? ` as ${status.githubUserId}` : ''}
53-
{status.updatedAt ? (
54-
<span className="text-gray-500"> · updated {new Date(status.updatedAt).toLocaleString()}</span>
55-
) : null}
56-
</div>
57-
) : (
58-
<div>Not connected</div>
59-
)}
60-
</div>
61-
<div className="flex gap-2">
62-
<Button variant="ghost" onClick={handleManage} disabled={isLoading || disconnectMutation.isPending}>Manage in GitHub</Button>
63-
{status?.installed ? (
64-
<Button variant="destructive" onClick={handleDisconnect} disabled={isLoading || disconnectMutation.isPending}>Disconnect</Button>
65-
) : (
66-
<Button onClick={handleConnect} disabled={isLoading || !appSlug}>Connect</Button>
67-
)}
11+
<div className="min-h-screen bg-[#f8fafc]">
12+
{/* Sticky header */}
13+
<div className="sticky top-0 z-20 bg-white border-b">
14+
<div className="container mx-auto px-6 py-6">
15+
<PageHeader
16+
title="Integrations"
17+
description="Connect Ambient Code Platform with your favorite tools and services"
18+
/>
19+
</div>
20+
</div>
21+
22+
<div className="container mx-auto p-0">
23+
{/* Content */}
24+
<div className="px-6 pt-6">
25+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
26+
<GitHubConnectionCard appSlug={appSlug} showManageButton={true} />
6827
</div>
69-
</CardContent>
70-
</Card>
28+
</div>
29+
</div>
7130
</div>
7231
)
7332
}

components/frontend/src/app/layout.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ export default function RootLayout({
2121
children: React.ReactNode;
2222
}) {
2323
const wsBase = env.BACKEND_URL.replace(/^http:/, 'ws:').replace(/^https:/, 'wss:')
24+
const feedbackUrl = env.FEEDBACK_URL
2425
return (
25-
<html lang="en">
26+
<html lang="en" suppressHydrationWarning>
2627
<head>
2728
<meta name="backend-ws-base" content={wsBase} />
2829
</head>
29-
<body className={`${inter.className} min-h-screen flex flex-col`}>
30+
<body className={`${inter.className} min-h-screen flex flex-col`} suppressHydrationWarning>
3031
<QueryProvider>
31-
<Navigation />
32+
<Navigation feedbackUrl={feedbackUrl} />
3233
<main className="flex-1 bg-background overflow-auto">{children}</main>
3334
<VersionFooter />
3435
<Toaster />

components/frontend/src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default function HomeRedirect() {
1616
<div className="flex items-center justify-center h-64">
1717
<div className="text-center">
1818
<Loader2 className="mx-auto h-8 w-8 animate-spin mb-4" />
19-
<p className="text-muted-foreground">Redirecting to RFE Wokspaces...</p>
19+
<p className="text-muted-foreground">Redirecting to Workspaces...</p>
2020
</div>
2121
</div>
2222
</div>

components/frontend/src/app/projects/[name]/page.tsx

Lines changed: 84 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,114 @@
11
'use client';
22

3-
import { useCallback } from 'react';
4-
import { useParams, useRouter } from 'next/navigation';
5-
import { formatDistanceToNow } from 'date-fns';
6-
import { RefreshCw } from 'lucide-react';
3+
import { useState, useEffect } from 'react';
4+
import { useParams, useSearchParams } from 'next/navigation';
5+
import { Star, Settings, Users, RefreshCw } from 'lucide-react';
6+
import { cn } from '@/lib/utils';
77

88
import { Button } from '@/components/ui/button';
99
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
10-
import { Label } from '@/components/ui/label';
11-
import { ProjectSubpageHeader } from '@/components/project-subpage-header';
12-
import { ErrorMessage } from '@/components/error-message';
10+
import { PageHeader } from '@/components/page-header';
1311
import { Breadcrumbs } from '@/components/breadcrumbs';
1412

15-
import { useProject } from '@/services/queries';
13+
import { SessionsSection } from '@/components/workspace-sections/sessions-section';
14+
import { SharingSection } from '@/components/workspace-sections/sharing-section';
15+
import { SettingsSection } from '@/components/workspace-sections/settings-section';
16+
import { useProject } from '@/services/queries/use-projects';
17+
18+
type Section = 'sessions' | 'sharing' | 'settings';
1619

1720
export default function ProjectDetailsPage() {
1821
const params = useParams();
19-
const router = useRouter();
22+
const searchParams = useSearchParams();
2023
const projectName = params?.name as string;
24+
25+
// Fetch project data for display name and description
26+
const { data: project, isLoading: projectLoading } = useProject(projectName);
27+
28+
// Initialize active section from query parameter or default to 'sessions'
29+
const initialSection = (searchParams.get('section') as Section) || 'sessions';
30+
const [activeSection, setActiveSection] = useState<Section>(initialSection);
2131

22-
// React Query hook replaces all manual state management
23-
const { data: project, isLoading, error, refetch } = useProject(projectName);
32+
// Update active section when query parameter changes
33+
useEffect(() => {
34+
const sectionParam = searchParams.get('section') as Section;
35+
if (sectionParam && ['sessions', 'sharing', 'settings'].includes(sectionParam)) {
36+
setActiveSection(sectionParam);
37+
}
38+
}, [searchParams]);
2439

25-
const handleRefresh = useCallback(() => {
26-
refetch();
27-
}, [refetch]);
40+
const navItems = [
41+
{ id: 'sessions' as Section, label: 'Sessions', icon: Star },
42+
{ id: 'sharing' as Section, label: 'Sharing', icon: Users },
43+
{ id: 'settings' as Section, label: 'Workspace Settings', icon: Settings },
44+
];
2845

2946
// Loading state
30-
if (!projectName || (isLoading && !project)) {
47+
if (!projectName || projectLoading) {
3148
return (
3249
<div className="container mx-auto p-6">
3350
<div className="flex items-center justify-center h-64">
3451
<RefreshCw className="animate-spin h-8 w-8" />
35-
<span className="ml-2">Loading project...</span>
52+
<span className="ml-2">Loading workspace...</span>
3653
</div>
3754
</div>
3855
);
3956
}
4057

41-
// Error state (no project loaded)
42-
if (error && !project) {
43-
return (
44-
<div className="container mx-auto p-6">
45-
<Card className="border-red-200 bg-red-50">
46-
<CardContent className="pt-6">
47-
<p className="text-red-700">{error instanceof Error ? error.message : 'Failed to load project'}</p>
48-
<div className="mt-4 flex gap-4">
49-
<Button onClick={() => router.push('/projects')} variant="outline">
50-
Back to Projects
51-
</Button>
52-
<Button onClick={handleRefresh}>Try Again</Button>
53-
</div>
54-
</CardContent>
55-
</Card>
56-
</div>
57-
);
58-
}
59-
60-
if (!project) return null;
61-
6258
return (
63-
<div className="container mx-auto p-6">
64-
<Breadcrumbs
65-
items={[
66-
{ label: 'Projects', href: '/projects' },
67-
{ label: project.displayName || project.name },
68-
]}
69-
className="mb-4"
70-
/>
71-
<ProjectSubpageHeader
72-
title={<>{project.displayName || project.name}</>}
73-
description={<>{projectName}</>}
74-
actions={
75-
<Button variant="outline" onClick={handleRefresh} disabled={isLoading}>
76-
<RefreshCw className={`w-4 h-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
77-
Refresh
78-
</Button>
79-
}
80-
/>
81-
82-
{/* Error state (with project loaded) */}
83-
{error && project && (
84-
<div className="px-6">
85-
<ErrorMessage error={error} onRetry={handleRefresh} />
59+
<div className="min-h-screen bg-[#f8fafc]">
60+
{/* Sticky header */}
61+
<div className="sticky top-0 z-20 bg-white border-b">
62+
<div className="container mx-auto px-6 py-4">
63+
<Breadcrumbs
64+
items={[
65+
{ label: 'Workspaces', href: '/projects' },
66+
{ label: projectName },
67+
]}
68+
className="mb-4"
69+
/>
70+
<PageHeader
71+
title={project?.displayName || projectName}
72+
description={project?.description || 'Workspace details and configuration'}
73+
/>
8674
</div>
87-
)}
75+
</div>
76+
77+
<div className="container mx-auto p-0">
78+
{/* Content */}
79+
<div className="px-6 pt-4 flex gap-6">
80+
{/* Sidebar Navigation */}
81+
<aside className="w-56 shrink-0">
82+
<Card>
83+
<CardHeader>
84+
<CardTitle>Workspace</CardTitle>
85+
</CardHeader>
86+
<CardContent className="px-4 pb-4 pt-2">
87+
<div className="space-y-1">
88+
{navItems.map((item) => {
89+
const isActive = activeSection === item.id;
90+
const Icon = item.icon;
91+
return (
92+
<Button
93+
key={item.id}
94+
variant={isActive ? "secondary" : "ghost"}
95+
className={cn("w-full justify-start", isActive && "font-semibold")}
96+
onClick={() => setActiveSection(item.id)}
97+
>
98+
<Icon className="w-4 h-4 mr-2" />
99+
{item.label}
100+
</Button>
101+
);
102+
})}
103+
</div>
104+
</CardContent>
105+
</Card>
106+
</aside>
88107

89-
<div className="pt-2">
90-
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
91-
{/* Project Info */}
92-
<Card>
93-
<CardHeader>
94-
<CardTitle>Project Information</CardTitle>
95-
</CardHeader>
96-
<CardContent className="space-y-4">
97-
<div>
98-
<Label className="text-sm font-medium">Description</Label>
99-
<p className="text-sm text-muted-foreground">
100-
{project.description || 'No description provided'}
101-
</p>
102-
</div>
103-
<div>
104-
<Label className="text-sm font-medium">Created</Label>
105-
<p className="text-sm text-muted-foreground">
106-
{project.creationTimestamp &&
107-
formatDistanceToNow(new Date(project.creationTimestamp), {
108-
addSuffix: true,
109-
})}
110-
</p>
111-
</div>
112-
</CardContent>
113-
</Card>
108+
{/* Main Content */}
109+
{activeSection === 'sessions' && <SessionsSection projectName={projectName} />}
110+
{activeSection === 'sharing' && <SharingSection projectName={projectName} />}
111+
{activeSection === 'settings' && <SettingsSection projectName={projectName} />}
114112
</div>
115113
</div>
116114
</div>

0 commit comments

Comments
 (0)