diff --git a/src/apps/officer-web/App.tsx b/src/apps/officer-web/App.tsx index 5c873a72..8f6709af 100644 --- a/src/apps/officer-web/App.tsx +++ b/src/apps/officer-web/App.tsx @@ -41,6 +41,7 @@ export function App() { : } /> : } /> } /> + } /> } /> } /> } /> diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx index dba42ca4..944599b3 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx @@ -1,7 +1,7 @@ import { Link } from 'react-router'; import * as Dropdown from '@/components/ui/dropdown-menu'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { User, Users, LogOut, Settings, Package, Puzzle, Sun, Moon } from 'lucide-react'; +import { User, Users, LogOut, Settings, Package, Puzzle, Rocket, Sun, Moon } from 'lucide-react'; import { useAuth } from 'hooks/useAuth'; import { useTranslation } from '@/lib/i18n'; import { useColorMode } from '@/components/ui/ThemeProvider'; @@ -65,6 +65,12 @@ export function UserMenu() { Integrations + + + + Apps + + {user?.role === 'Super Admin' && ( diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/AppsSettings/AppsList.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/AppsSettings/AppsList.tsx new file mode 100644 index 00000000..6d613fbe --- /dev/null +++ b/src/apps/officer-web/Screens/Dashboard/Settings/AppsSettings/AppsList.tsx @@ -0,0 +1,115 @@ +import { useState } from 'react'; +import { Trash2, Loader2, ExternalLink } from 'lucide-react'; +import { toast } from 'sonner'; +import { useClient } from 'hooks/useClient'; +import { useUserApps, type AppManifest } from 'state/useUserApps'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; + +export const AppsList = () => { + const client = useClient(); + const { apps, isLoading, refetch } = useUserApps(); + const [deleting, setDeleting] = useState(null); + const [deletingInProgress, setDeletingInProgress] = useState(false); + + const confirmDelete = async () => { + if (!deleting) return; + setDeletingInProgress(true); + try { + await client.delete(`/apps/${deleting.slug}`); + toast.success(`Deleted ${deleting.name}`); + refetch(); + } catch { + toast.error('Failed to delete app'); + } finally { + setDeletingInProgress(false); + setDeleting(null); + } + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (apps.length === 0) { + return ( +
+ No published apps yet. Publish a project from the Projects page. +
+ ); + } + + return ( + <> +
+ {apps.map((app) => ( +
+
+
+ {app.name} + v{app.version} +
+ {app.description && ( +

{app.description}

+ )} +
+ Source: {app.sourceProject} + Commit: {app.commitHash} + Published: {new Date(app.publishedAt).toLocaleDateString()} +
+
+ + + + +
+ ))} +
+ + { if (!open) setDeleting(null); }}> + + + Delete published app + + Are you sure you want to delete {deleting?.name}? This removes the published build. The source project is not affected. + + + + Cancel + + {deletingInProgress ? : 'Delete'} + + + + + + ); +}; diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/AppsSettings/index.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/AppsSettings/index.tsx new file mode 100644 index 00000000..6c51c6f5 --- /dev/null +++ b/src/apps/officer-web/Screens/Dashboard/Settings/AppsSettings/index.tsx @@ -0,0 +1,46 @@ +import { useMemo } from 'react'; +import { Rocket, List } from 'lucide-react'; +import type { LayoutNode, PanelComponents } from 'officerdev'; +import { WorkspaceLayout } from 'officerdev'; + +import { createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel'; +import { AppsList } from './AppsList'; + +const GLOBAL_KEY = 'APPS_SETTINGS_SELECTED'; + +const sections: SettingsSection[] = [ + { key: 'published-apps', icon: List, title: 'Published Apps', description: 'View and manage your published apps', content: }, +]; + +const { Sidebar, Content } = createSettingsPanelComponents({ + globalKey: GLOBAL_KEY, + sidebarIcon: Rocket, + sidebarLabel: 'Apps', + sections, +}); + +const layout: LayoutNode = { + type: 'group', + id: 'apps-settings-root', + direction: 'horizontal', + children: [ + { node: { type: 'panel', id: 'apps-settings-left', appType: null }, size: 20 }, + { node: { type: 'panel', id: 'apps-settings-right', appType: null }, size: 80 }, + ], +}; + +export const AppsSettings = () => { + const panelComponents: PanelComponents = useMemo( + () => ({ + 'apps-settings-left': Sidebar, + 'apps-settings-right': Content, + }), + [], + ); + + return ( +
+ {}} components={panelComponents} /> +
+ ); +}; diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/index.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/index.tsx index 08a0b741..166aa8e4 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/index.tsx @@ -3,3 +3,4 @@ export * from './SystemSettings'; export * from './ResourceSettings'; export * from './UserSettings'; export * from './IntegrationsSettings'; +export * from './AppsSettings';