From ab023f404117e4f9969dbc622e28b7cc3d3e8eb5 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 5 Mar 2026 14:19:43 +0800 Subject: [PATCH 1/6] refactor: simplify GitHub integration UI and reorganize components - Simplify GitHubStatusCard to single card with two states (connected/not connected) - Remove redundant GitHub App Installations section - Move GitHubStatusCard to route-specific _components/ directory - Add component organization guidelines to CLAUDE.md --- CLAUDE.md | 8 + .../_components/github-status-card.tsx | 154 ++++++++++++ app/(dashboard)/settings/page.tsx | 3 +- components/github/github-status-card.tsx | 237 ------------------ 4 files changed, 164 insertions(+), 238 deletions(-) create mode 100644 app/(dashboard)/settings/_components/github-status-card.tsx delete mode 100644 components/github/github-status-card.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 39227a8..5401728 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,6 +52,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - API routes: kebab-case - Files: kebab-case +### Component Organization +- **Route-specific components**: Place in `_components/` directory under the route folder + - Use `_` prefix to prevent Next.js from treating it as a route + - Example: `app/(dashboard)/settings/_components/github-status-card.tsx` +- **Shared components**: Place in top-level `components/` directory + - Only for components used across multiple routes + - Example: `components/ui/button.tsx`, `components/sidebar.tsx` + ### Important Patterns 1. **Always use user-specific K8s service**: diff --git a/app/(dashboard)/settings/_components/github-status-card.tsx b/app/(dashboard)/settings/_components/github-status-card.tsx new file mode 100644 index 0000000..5c5832b --- /dev/null +++ b/app/(dashboard)/settings/_components/github-status-card.tsx @@ -0,0 +1,154 @@ +'use client' + +import { useEffect, useState } from 'react' +import { FaGithub } from 'react-icons/fa' +import { MdCheck, MdRefresh } from 'react-icons/md' +import Image from 'next/image' +import { toast } from 'sonner' + +import { Button } from '@/components/ui/button' +import { getInstallations, type GitHubInstallation } from '@/lib/actions/github' +import { env } from '@/lib/env' + +export function GitHubStatusCard() { + const [isLoading, setIsLoading] = useState(true) + const [installation, setInstallation] = useState(null) + + useEffect(() => { + loadData() + }, []) + + const loadData = async () => { + setIsLoading(true) + try { + const installationsResult = await getInstallations() + + if (installationsResult.success && installationsResult.data.length > 0) { + setInstallation(installationsResult.data[0]) + } + } catch (error) { + console.error('Failed to load GitHub data:', error) + } finally { + setIsLoading(false) + } + } + + const handleInstallApp = () => { + const appName = env.NEXT_PUBLIC_GITHUB_APP_NAME + if (!appName) { + toast.error('GitHub App is not configured') + return + } + + const installUrl = `https://github.com/apps/${appName}/installations/new` + + const width = 800 + const height = 800 + const left = window.screen.width / 2 - width / 2 + const top = window.screen.height / 2 - height / 2 + + const popup = window.open( + installUrl, + 'github-app-install', + `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes` + ) + + if (!popup) { + toast.error('Failed to open popup window. Please allow popups for this site.') + return + } + + const checkClosed = setInterval(() => { + if (popup.closed) { + clearInterval(checkClosed) + window.removeEventListener('message', handleMessage) + } + }, 500) + + const handleMessage = (event: MessageEvent) => { + if (event.origin !== window.location.origin) return + if (event.data.type !== 'github-app-installed') return + + window.removeEventListener('message', handleMessage) + clearInterval(checkClosed) + toast.success('GitHub App installed successfully!') + loadData() + } + + window.addEventListener('message', handleMessage) + } + + if (isLoading) { + return ( +
+
+ + Loading... +
+
+ ) + } + + return ( +
+
+
+ +
+
+

GitHub Account

+

+ Connect your GitHub account to enable repository access and code management features. +

+
+
+ +
+ {installation ? ( +
+ {installation.accountAvatarUrl ? ( + {installation.accountLogin} + ) : ( +
+ +
+ )} +
+
+ + {installation.accountLogin} + + ● Connected +
+

+ Your GitHub account is connected and ready to use. +

+
+
+ ) : ( +
+
+

+ Install the GitHub App to connect your account and access repositories. +

+
+ + +
+ )} +
+
+ ) +} diff --git a/app/(dashboard)/settings/page.tsx b/app/(dashboard)/settings/page.tsx index 364fb0a..621eeb9 100644 --- a/app/(dashboard)/settings/page.tsx +++ b/app/(dashboard)/settings/page.tsx @@ -1,9 +1,10 @@ import { redirect } from 'next/navigation' -import { GitHubStatusCard } from '@/components/github/github-status-card' import { Sidebar } from '@/components/sidebar' import { auth } from '@/lib/auth' +import { GitHubStatusCard } from './_components/github-status-card' + export const metadata = { title: 'Settings | Fulling', description: 'Manage your account settings and integrations.', diff --git a/components/github/github-status-card.tsx b/components/github/github-status-card.tsx deleted file mode 100644 index a3ef401..0000000 --- a/components/github/github-status-card.tsx +++ /dev/null @@ -1,237 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' -import { FaGithub } from 'react-icons/fa' -import { MdCheck, MdOpenInNew, MdRefresh } from 'react-icons/md' -import Image from 'next/image' -import { toast } from 'sonner' - -import { Button } from '@/components/ui/button' -import { getInstallations, type GitHubInstallation } from '@/lib/actions/github' -import { env } from '@/lib/env' - -interface GitHubStatusCardProps { - onInstallApp?: () => void -} - -export function GitHubStatusCard({ onInstallApp }: GitHubStatusCardProps) { - const [isLoading, setIsLoading] = useState(true) - const [installations, setInstallations] = useState([]) - - useEffect(() => { - loadData() - }, []) - - const loadData = async () => { - setIsLoading(true) - try { - const installationsResult = await getInstallations() - - if (installationsResult.success) { - setInstallations(installationsResult.data) - } - } catch (error) { - console.error('Failed to load GitHub data:', error) - } finally { - setIsLoading(false) - } - } - - const handleInstallApp = () => { - const appName = env.NEXT_PUBLIC_GITHUB_APP_NAME - if (!appName) { - toast.error('GitHub App is not configured') - return - } - - const installUrl = `https://github.com/apps/${appName}/installations/new` - - const width = 800 - const height = 800 - const left = window.screen.width / 2 - width / 2 - const top = window.screen.height / 2 - height / 2 - - const popup = window.open( - installUrl, - 'github-app-install', - `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes` - ) - - if (!popup) { - toast.error('Failed to open popup window. Please allow popups for this site.') - return - } - - const checkClosed = setInterval(() => { - if (popup.closed) { - clearInterval(checkClosed) - window.removeEventListener('message', handleMessage) - } - }, 500) - - const handleMessage = (event: MessageEvent) => { - if (event.origin !== window.location.origin) return - if (event.data.type !== 'github-app-installed') return - - window.removeEventListener('message', handleMessage) - clearInterval(checkClosed) - toast.success('GitHub App installed successfully!') - loadData() - onInstallApp?.() - } - - window.addEventListener('message', handleMessage) - } - - if (isLoading) { - return ( -
-
- - Loading... -
-
- ) - } - - return ( -
- {/* GitHub Identity Section */} -
-
-
- -
-
-

GitHub Account

-

- Connect your GitHub account to enable repository access and code management features. -

-
-
- -
- {installations.length > 0 ? ( -
-
- -
-
-
- - {installations[0].accountLogin || 'Connected'} - - ● Connected -
-

- Your GitHub account is connected and ready to use. -

-
-
- ) : ( -
-
-

- Install the GitHub App to connect your account and access repositories. -

-
- - -
- )} -
-
- - {/* GitHub App Installations Section */} - {installations.length > 0 && ( -
-
-
- -
-
-

GitHub App Installations

-

- Install the GitHub App to grant access to your repositories. -

-
-
- -
- {installations.length > 0 ? ( - <> -
- {installations.map((installation) => ( -
- {installation.accountAvatarUrl ? ( - {installation.accountLogin} - ) : ( -
- -
- )} -
-
- - {installation.accountLogin} - - - ({installation.accountType}) - - {installation.status === 'ACTIVE' && ( - ● Active - )} - {installation.status === 'SUSPENDED' && ( - ● Suspended - )} -
-

- {installation.repositorySelection === 'all' - ? 'All repositories' - : 'Selected repositories'} -

-
-
- ))} -
- - - - ) : ( -
-
-

- No GitHub App installations found. Install the app to access your repositories. -

-
- - -
- )} -
-
- )} -
- ) -} From 50d4312f0c0420567ffaf84020fc0ee3cc7fb483 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:16:22 +0800 Subject: [PATCH 2/6] feat: add settings sidebar with nested routing - Add SettingsSidebar component with Integrations and Account menu items - Create /settings/integrations route with GitHub integration content - Add shared layout.tsx for settings pages - Redirect /settings to /settings/integrations - Move page header to individual pages for flexibility --- .../settings/_components/settings-sidebar.tsx | 45 +++++++++++++++++++ .../settings/integrations/page.tsx | 26 +++++++++++ app/(dashboard)/settings/layout.tsx | 21 +++++++++ app/(dashboard)/settings/page.tsx | 26 +---------- 4 files changed, 93 insertions(+), 25 deletions(-) create mode 100644 app/(dashboard)/settings/_components/settings-sidebar.tsx create mode 100644 app/(dashboard)/settings/integrations/page.tsx create mode 100644 app/(dashboard)/settings/layout.tsx diff --git a/app/(dashboard)/settings/_components/settings-sidebar.tsx b/app/(dashboard)/settings/_components/settings-sidebar.tsx new file mode 100644 index 0000000..bb979b2 --- /dev/null +++ b/app/(dashboard)/settings/_components/settings-sidebar.tsx @@ -0,0 +1,45 @@ +'use client' + +import Link from 'next/link' +import { usePathname } from 'next/navigation' + +import { Separator } from '@/components/ui/separator' +import { cn } from '@/lib/utils' + +const menuItems = [ + { label: 'Integrations', href: '/settings/integrations' }, + { label: 'Account', href: '/settings/account' }, +] + +export function SettingsSidebar() { + const pathname = usePathname() + + return ( + + ) +} diff --git a/app/(dashboard)/settings/integrations/page.tsx b/app/(dashboard)/settings/integrations/page.tsx new file mode 100644 index 0000000..8959f22 --- /dev/null +++ b/app/(dashboard)/settings/integrations/page.tsx @@ -0,0 +1,26 @@ +import { GitHubStatusCard } from '../_components/github-status-card' + +export const metadata = { + title: 'Integrations | Settings | Fulling', + description: 'Manage your integrations and connected services.', +} + +export default function IntegrationsPage() { + return ( + <> +
+

Integrations

+

+ Manage your integrations and connected services. +

+
+ +
+
+

GitHub Integration

+ +
+
+ + ) +} diff --git a/app/(dashboard)/settings/layout.tsx b/app/(dashboard)/settings/layout.tsx new file mode 100644 index 0000000..3a4020b --- /dev/null +++ b/app/(dashboard)/settings/layout.tsx @@ -0,0 +1,21 @@ +import { Sidebar } from '@/components/sidebar' + +import { SettingsSidebar } from './_components/settings-sidebar' + +export default function SettingsLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( +
+ + +
+
+ {children} +
+
+
+ ) +} diff --git a/app/(dashboard)/settings/page.tsx b/app/(dashboard)/settings/page.tsx index 621eeb9..c1ebece 100644 --- a/app/(dashboard)/settings/page.tsx +++ b/app/(dashboard)/settings/page.tsx @@ -1,10 +1,7 @@ import { redirect } from 'next/navigation' -import { Sidebar } from '@/components/sidebar' import { auth } from '@/lib/auth' -import { GitHubStatusCard } from './_components/github-status-card' - export const metadata = { title: 'Settings | Fulling', description: 'Manage your account settings and integrations.', @@ -17,26 +14,5 @@ export default async function SettingsPage() { redirect('/login') } - return ( -
- -
-
-
-

Settings

-

- Manage your account settings and integrations. -

-
- -
-
-

GitHub Integration

- -
-
-
-
-
- ) + redirect('/settings/integrations') } From a99267ba5c2180ab47b990861b89dcd45f36507a Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:34:20 +0800 Subject: [PATCH 3/6] refactor: redesign GitHub integration card with left-right layout - Add left section with title, description, and GitHub logo card - Add right section with integration explanation and action area - Show green checkmark on logo when connected - Display user avatar and connected status in connected state - Show Connect to GitHub button in disconnected state --- .../_components/github-status-card.tsx | 102 +++++++++--------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/app/(dashboard)/settings/_components/github-status-card.tsx b/app/(dashboard)/settings/_components/github-status-card.tsx index 5c5832b..0b802e5 100644 --- a/app/(dashboard)/settings/_components/github-status-card.tsx +++ b/app/(dashboard)/settings/_components/github-status-card.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { FaGithub } from 'react-icons/fa' -import { MdCheck, MdRefresh } from 'react-icons/md' +import { MdCheck } from 'react-icons/md' import Image from 'next/image' import { toast } from 'sonner' @@ -80,74 +80,72 @@ export function GitHubStatusCard() { if (isLoading) { return ( -
-
- - Loading... -
+
+
) } return (
-
-
- -
-
-

GitHub Account

-

- Connect your GitHub account to enable repository access and code management features. +

+
+

GitHub Integration

+

+ Connect your GitHub account to Fulling

-
-
- -
- {installation ? ( -
- {installation.accountAvatarUrl ? ( - {installation.accountLogin} - ) : ( -
- +
+ {installation ? ( +
+ +
+ +
+ ) : ( + )} -
-
- - {installation.accountLogin} - - ● Connected -
-

- Your GitHub account is connected and ready to use. -

-
- ) : ( -
-
-

- Install the GitHub App to connect your account and access repositories. -

-
+
+ +
+

+ What does the GitHub Integration do? +

+

+ Connecting to GitHub allows you to sync your project to fulling, and automatically deploys your production-ready project to Sealos. +

+ {installation ? ( +
+ {installation.accountAvatarUrl ? ( + {installation.accountLogin} + ) : ( +
+ +
+ )} + + {installation.accountLogin} + + ● Connected +
+ ) : ( -
- )} + )} +
) From 1cd435977df64c08a365b1c523e18008911bd926 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:48:19 +0800 Subject: [PATCH 4/6] refactor: flatten GitHub integration card structure - Move card wrapper to parent page component - Add GitHub Integration title inside the item - Fix capitalization: fulling -> Fulling - Remove redundant section heading from integrations page --- .../_components/github-status-card.tsx | 24 ++++++++++++------- .../settings/integrations/page.tsx | 7 ++---- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/app/(dashboard)/settings/_components/github-status-card.tsx b/app/(dashboard)/settings/_components/github-status-card.tsx index 0b802e5..7c1b5f5 100644 --- a/app/(dashboard)/settings/_components/github-status-card.tsx +++ b/app/(dashboard)/settings/_components/github-status-card.tsx @@ -80,20 +80,28 @@ export function GitHubStatusCard() { if (isLoading) { return ( -
-
+
+
+
+
+
+
+
+
+
+
+
+
+
) } return ( -
+
-

GitHub Integration

-

- Connect your GitHub account to Fulling -

+

GitHub Integration

{installation ? (
@@ -113,7 +121,7 @@ export function GitHubStatusCard() { What does the GitHub Integration do?

- Connecting to GitHub allows you to sync your project to fulling, and automatically deploys your production-ready project to Sealos. + Connecting to GitHub allows you to sync your project to Fulling, and automatically deploys your production-ready project to Sealos.

{installation ? ( diff --git a/app/(dashboard)/settings/integrations/page.tsx b/app/(dashboard)/settings/integrations/page.tsx index 8959f22..a664135 100644 --- a/app/(dashboard)/settings/integrations/page.tsx +++ b/app/(dashboard)/settings/integrations/page.tsx @@ -15,11 +15,8 @@ export default function IntegrationsPage() {

-
-
-

GitHub Integration

- -
+
+
) From 9795c87ac61648f352727e070c40d546e6f9aa82 Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:01:42 +0800 Subject: [PATCH 5/6] fix: DialogContent overflow in import-github-dialog - Add min-w-0 to prevent flex container overflow - Simplify repo button layout structure - Increase DialogContent max-width from max-w-lg to max-w-2xl - Add whitespace-nowrap to Private badge to prevent wrapping --- components/dialog/import-github-dialog.tsx | 68 +++++++++------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/components/dialog/import-github-dialog.tsx b/components/dialog/import-github-dialog.tsx index 3484f8e..3a281d2 100644 --- a/components/dialog/import-github-dialog.tsx +++ b/components/dialog/import-github-dialog.tsx @@ -39,7 +39,7 @@ export function ImportGitHubDialog({ open, onOpenChange }: ImportGitHubDialogPro const resetState = useCallback(() => { setStep('check-github-app') - setIsLoading(false) + setIsLoading(true) setSearchQuery('') setHasInstallation(false) setRepos([]) @@ -173,7 +173,7 @@ export function ImportGitHubDialog({ open, onOpenChange }: ImportGitHubDialogPro } return ( -
+

Install the GitHub App to grant access to your repositories. @@ -193,7 +193,7 @@ export function ImportGitHubDialog({ open, onOpenChange }: ImportGitHubDialogPro case 'select-repo': return ( -

+
- - {isLoading ? ( -
-
- - Loading repositories... + +
+ {isLoading ? ( +
+
+ + Loading repositories... +
-
- ) : filteredRepos.length === 0 ? ( -
- No repositories found -
- ) : ( -
- {filteredRepos.map((repo) => ( + ) : filteredRepos.length === 0 ? ( +
+ No repositories found +
+ ) : ( +
+ {filteredRepos.map((repo) => ( ))}
)} +
{selectedRepo && ( @@ -291,12 +281,12 @@ export function ImportGitHubDialog({ open, onOpenChange }: ImportGitHubDialogPro return ( - + {getStepTitle()} -
{renderStepContent()}
+
{renderStepContent()}
{/* Step indicators */}
From 5516cb0106b343f1d9ab514be1fb5f940c486a6b Mon Sep 17 00:00:00 2001 From: Che <30403707+Che-Zhu@users.noreply.github.com> Date: Thu, 5 Mar 2026 22:25:54 +0800 Subject: [PATCH 6/6] refactor: move project-related dialogs to route-specific _components - Move create-project-dialog.tsx from components/dialog/ to app/(dashboard)/projects/_components/ - Update import paths in search-bar.tsx and create-project-card.tsx - Remove now-empty import-github-dialog.tsx from components/dialog/ - Keep settings-dialog.tsx in components/dialog/ as it's used across multiple routes This follows the component organization convention: - Route-specific components (used only within projects/*) -> _components/ - Shared components (used across multiple routes) -> components/ --- .../projects/(list)/_components/create-project-card.tsx | 3 ++- app/(dashboard)/projects/(list)/layout.tsx | 3 ++- .../projects/_components}/create-project-dialog.tsx | 0 .../projects/_components}/import-github-dialog.tsx | 0 .../(dashboard)/projects/_components}/search-bar.tsx | 5 +++-- 5 files changed, 7 insertions(+), 4 deletions(-) rename {components/dialog => app/(dashboard)/projects/_components}/create-project-dialog.tsx (100%) rename {components/dialog => app/(dashboard)/projects/_components}/import-github-dialog.tsx (100%) rename {components => app/(dashboard)/projects/_components}/search-bar.tsx (94%) diff --git a/app/(dashboard)/projects/(list)/_components/create-project-card.tsx b/app/(dashboard)/projects/(list)/_components/create-project-card.tsx index 8e9a62a..6c6a3f5 100644 --- a/app/(dashboard)/projects/(list)/_components/create-project-card.tsx +++ b/app/(dashboard)/projects/(list)/_components/create-project-card.tsx @@ -3,9 +3,10 @@ import { useState } from 'react' import { MdAdd } from 'react-icons/md' -import CreateProjectDialog from '@/components/dialog/create-project-dialog' import { cn } from '@/lib/utils' +import CreateProjectDialog from '../../_components/create-project-dialog' + export function CreateProjectCard() { const [isDialogOpen, setIsDialogOpen] = useState(false) diff --git a/app/(dashboard)/projects/(list)/layout.tsx b/app/(dashboard)/projects/(list)/layout.tsx index 2aeb200..826509b 100644 --- a/app/(dashboard)/projects/(list)/layout.tsx +++ b/app/(dashboard)/projects/(list)/layout.tsx @@ -1,6 +1,7 @@ -import { SearchBar } from '@/components/search-bar' import { Sidebar } from '@/components/sidebar' +import { SearchBar } from '../_components/search-bar' + export default function HomeLayout({ children, }: { diff --git a/components/dialog/create-project-dialog.tsx b/app/(dashboard)/projects/_components/create-project-dialog.tsx similarity index 100% rename from components/dialog/create-project-dialog.tsx rename to app/(dashboard)/projects/_components/create-project-dialog.tsx diff --git a/components/dialog/import-github-dialog.tsx b/app/(dashboard)/projects/_components/import-github-dialog.tsx similarity index 100% rename from components/dialog/import-github-dialog.tsx rename to app/(dashboard)/projects/_components/import-github-dialog.tsx diff --git a/components/search-bar.tsx b/app/(dashboard)/projects/_components/search-bar.tsx similarity index 94% rename from components/search-bar.tsx rename to app/(dashboard)/projects/_components/search-bar.tsx index a009464..cb542ba 100644 --- a/components/search-bar.tsx +++ b/app/(dashboard)/projects/_components/search-bar.tsx @@ -3,12 +3,13 @@ import { useState } from 'react' import { MdAdd, MdSearch } from 'react-icons/md' -import CreateProjectDialog from '@/components/dialog/create-project-dialog' -import { ImportGitHubDialog } from '@/components/dialog/import-github-dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Kbd } from '@/components/ui/kbd' +import CreateProjectDialog from './create-project-dialog' +import { ImportGitHubDialog } from './import-github-dialog' + export function SearchBar() { const [isDialogOpen, setIsDialogOpen] = useState(false) const [isImportDialogOpen, setIsImportDialogOpen] = useState(false)