diff --git a/resources/js/components/server-switch.tsx b/resources/js/components/server-switch.tsx
index a4886406..770381eb 100644
--- a/resources/js/components/server-switch.tsx
+++ b/resources/js/components/server-switch.tsx
@@ -31,8 +31,8 @@ export function ServerSwitch() {
{selectedServer && (
@@ -41,8 +41,8 @@ export function ServerSwitch() {
{!selectedServer && (
diff --git a/resources/js/components/ui/button.tsx b/resources/js/components/ui/button.tsx
index 3c605828..29bc82f1 100644
--- a/resources/js/components/ui/button.tsx
+++ b/resources/js/components/ui/button.tsx
@@ -5,21 +5,22 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
- "cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
destructive:
- 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
- outline: 'border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
+ 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
- ghost: 'hover:bg-accent hover:text-accent-foreground',
+ ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
- sm: 'h-8 rounded-md px-3 has-[>svg]:px-2.5',
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
},
diff --git a/resources/js/components/input-error.tsx b/resources/js/components/ui/input-error.tsx
similarity index 100%
rename from resources/js/components/input-error.tsx
rename to resources/js/components/ui/input-error.tsx
diff --git a/resources/js/components/ui/input.tsx b/resources/js/components/ui/input.tsx
index 724de857..3c1cfcaf 100644
--- a/resources/js/components/ui/input.tsx
+++ b/resources/js/components/ui/input.tsx
@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
type={type}
data-slot="input"
className={cn(
- 'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground bg-background border-input flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
+ 'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className,
diff --git a/resources/js/components/ui/separator.tsx b/resources/js/components/ui/separator.tsx
index fb8eb4fc..2d953010 100644
--- a/resources/js/components/ui/separator.tsx
+++ b/resources/js/components/ui/separator.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';
diff --git a/resources/js/components/ui/sheet.tsx b/resources/js/components/ui/sheet.tsx
index a02f879c..46fbc062 100644
--- a/resources/js/components/ui/sheet.tsx
+++ b/resources/js/components/ui/sheet.tsx
@@ -25,7 +25,7 @@ function SheetOverlay({ className, ...props }: React.ComponentProps
) {
- return ;
+ return ;
}
export { Skeleton };
diff --git a/resources/js/components/ui/tooltip.tsx b/resources/js/components/ui/tooltip.tsx
index e72aca08..942fa16c 100644
--- a/resources/js/components/ui/tooltip.tsx
+++ b/resources/js/components/ui/tooltip.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
diff --git a/resources/js/components/user-info.tsx b/resources/js/components/user-info.tsx
index 7b1947bc..f3645461 100644
--- a/resources/js/components/user-info.tsx
+++ b/resources/js/components/user-info.tsx
@@ -7,7 +7,7 @@ export function UserInfo({ user, showEmail = false }: { user: User; showEmail?:
return (
<>
-
+
{getInitials(user.name)}
diff --git a/resources/js/components/user-menu-content.tsx b/resources/js/components/user-menu-content.tsx
index 78dce392..d7db0476 100644
--- a/resources/js/components/user-menu-content.tsx
+++ b/resources/js/components/user-menu-content.tsx
@@ -30,7 +30,7 @@ export function UserMenuContent({ user }: UserMenuContentProps) {
-
+
Settings
diff --git a/resources/js/hooks/use-mobile.ts b/resources/js/hooks/use-mobile.ts
new file mode 100644
index 00000000..ba553c6f
--- /dev/null
+++ b/resources/js/hooks/use-mobile.ts
@@ -0,0 +1,19 @@
+import * as React from 'react';
+
+const MOBILE_BREAKPOINT = 768;
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined);
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener('change', onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener('change', onChange);
+ }, []);
+
+ return !!isMobile;
+}
diff --git a/resources/js/layouts/app-layout.tsx b/resources/js/layouts/app-layout.tsx
index ef24f5df..d1940576 100644
--- a/resources/js/layouts/app-layout.tsx
+++ b/resources/js/layouts/app-layout.tsx
@@ -1,4 +1,4 @@
-import AppLayoutTemplate from '@/layouts/app/app-header-layout';
+import AppLayout from '@/layouts/app/layout';
import { type BreadcrumbItem } from '@/types';
import { type ReactNode } from 'react';
@@ -7,4 +7,4 @@ interface AppLayoutProps {
breadcrumbs?: BreadcrumbItem[];
}
-export default ({ children, ...props }: AppLayoutProps) => {children};
+export default ({ children, ...props }: AppLayoutProps) => {children};
diff --git a/resources/js/layouts/app/app-header-layout.tsx b/resources/js/layouts/app/app-header-layout.tsx
deleted file mode 100644
index 05644883..00000000
--- a/resources/js/layouts/app/app-header-layout.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { AppContent } from '@/components/app-content';
-import { AppHeader } from '@/components/app-header';
-import { AppShell } from '@/components/app-shell';
-import { type BreadcrumbItem } from '@/types';
-import type { PropsWithChildren } from 'react';
-import { usePoll } from '@inertiajs/react';
-import { Toaster } from '@/components/ui/sonner';
-
-export default function AppHeaderLayout({ children }: PropsWithChildren<{ breadcrumbs?: BreadcrumbItem[] }>) {
- usePoll(10000);
-
- return (
-
-
- {children}
-
-
- );
-}
diff --git a/resources/js/layouts/app/app-sidebar-layout.tsx b/resources/js/layouts/app/app-sidebar-layout.tsx
deleted file mode 100644
index f2df3540..00000000
--- a/resources/js/layouts/app/app-sidebar-layout.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { AppContent } from '@/components/app-content';
-import { AppShell } from '@/components/app-shell';
-import { AppSidebar } from '@/components/app-sidebar';
-import { AppSidebarHeader } from '@/components/app-sidebar-header';
-import { type BreadcrumbItem } from '@/types';
-import { type PropsWithChildren } from 'react';
-
-export default function AppSidebarLayout({ children, breadcrumbs = [] }: PropsWithChildren<{ breadcrumbs?: BreadcrumbItem[] }>) {
- return (
-
-
-
-
- {children}
-
-
- );
-}
diff --git a/resources/js/layouts/app/layout.tsx b/resources/js/layouts/app/layout.tsx
new file mode 100644
index 00000000..e5d11a1b
--- /dev/null
+++ b/resources/js/layouts/app/layout.tsx
@@ -0,0 +1,35 @@
+import { AppSidebar } from '@/components/app-sidebar';
+import { AppHeader } from '@/components/app-header';
+import { type BreadcrumbItem, NavItem } from '@/types';
+import { CSSProperties, type PropsWithChildren } from 'react';
+import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
+import { usePoll } from '@inertiajs/react';
+
+export default function Layout({
+ children,
+ secondNavItems,
+ secondNavTitle,
+}: PropsWithChildren<{
+ breadcrumbs?: BreadcrumbItem[];
+ secondNavItems?: NavItem[];
+ secondNavTitle?: string;
+}>) {
+ usePoll(10000);
+
+ return (
+ 0)}
+ >
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/resources/js/layouts/server/layout.tsx b/resources/js/layouts/server/layout.tsx
new file mode 100644
index 00000000..3a9d6842
--- /dev/null
+++ b/resources/js/layouts/server/layout.tsx
@@ -0,0 +1,102 @@
+import { type NavItem } from '@/types';
+import {
+ ChartPieIcon,
+ ClockIcon,
+ CogIcon,
+ DatabaseIcon,
+ FlameIcon,
+ FolderOpenIcon,
+ HomeIcon,
+ KeyIcon,
+ ListEndIcon,
+ LogsIcon,
+ MousePointerClickIcon,
+ Settings2Icon,
+ TerminalSquareIcon,
+} from 'lucide-react';
+import { ReactNode } from 'react';
+import { Server } from '@/types/server';
+import ServerHeader from '@/pages/servers/partials/header';
+import Layout from '@/layouts/app/layout';
+
+export default function ServerLayout({ server, children }: { server: Server; children: ReactNode }) {
+ // When server-side rendering, we only render the layout on the client...
+ if (typeof window === 'undefined') {
+ return null;
+ }
+ const sidebarNavItems: NavItem[] = [
+ {
+ title: 'Overview',
+ href: route('servers.show', { server: server.id }),
+ icon: HomeIcon,
+ },
+ {
+ title: 'Databases',
+ href: '#',
+ icon: DatabaseIcon,
+ },
+ {
+ title: 'Sites',
+ href: '#',
+ icon: MousePointerClickIcon,
+ },
+ {
+ title: 'File Manager',
+ href: '#',
+ icon: FolderOpenIcon,
+ },
+ {
+ title: 'Firewall',
+ href: '#',
+ icon: FlameIcon,
+ },
+ {
+ title: 'CronJobs',
+ href: '#',
+ icon: ClockIcon,
+ },
+ {
+ title: 'Workers',
+ href: '#',
+ icon: ListEndIcon,
+ },
+ {
+ title: 'SSH Keys',
+ href: '#',
+ icon: KeyIcon,
+ },
+ {
+ title: 'Services',
+ href: '#',
+ icon: CogIcon,
+ },
+ {
+ title: 'Metrics',
+ href: '#',
+ icon: ChartPieIcon,
+ },
+ {
+ title: 'Console',
+ href: '#',
+ icon: TerminalSquareIcon,
+ },
+ {
+ title: 'Logs',
+ href: '#',
+ icon: LogsIcon,
+ },
+ {
+ title: 'Settings',
+ href: '#',
+ icon: Settings2Icon,
+ },
+ ];
+
+ return (
+
+
+
+ {children}
+
+ );
+}
diff --git a/resources/js/layouts/settings/layout.tsx b/resources/js/layouts/settings/layout.tsx
index f568cdb1..f61928dd 100644
--- a/resources/js/layouts/settings/layout.tsx
+++ b/resources/js/layouts/settings/layout.tsx
@@ -1,16 +1,12 @@
-import Container from '@/components/container';
-import { Button } from '@/components/ui/button';
-import { Separator } from '@/components/ui/separator';
-import { cn } from '@/lib/utils';
-import { type NavItem } from '@/types';
-import { Link } from '@inertiajs/react';
-import { ListIcon, LockIcon, UserIcon } from 'lucide-react';
-import { type PropsWithChildren } from 'react';
+import { type BreadcrumbItem, type NavItem } from '@/types';
+import { ListIcon, UserIcon } from 'lucide-react';
+import { ReactNode } from 'react';
+import Layout from '@/layouts/app/layout';
const sidebarNavItems: NavItem[] = [
{
title: 'Profile',
- href: '/settings/profile',
+ href: route('profile'),
icon: UserIcon,
},
{
@@ -18,51 +14,17 @@ const sidebarNavItems: NavItem[] = [
href: '/',
icon: ListIcon,
},
- {
- title: 'Password',
- href: '/settings/password',
- icon: LockIcon,
- },
];
-export default function SettingsLayout({ children }: PropsWithChildren) {
+export default function SettingsLayout({ children, breadcrumbs }: { children: ReactNode; breadcrumbs?: BreadcrumbItem[] }) {
// When server-side rendering, we only render the layout on the client...
if (typeof window === 'undefined') {
return null;
}
- const currentPath = window.location.pathname;
-
return (
-
-
-
-
-
-
-
-
-
-
-
+
+ {children}
+
);
}
diff --git a/resources/js/pages/auth/confirm-password.tsx b/resources/js/pages/auth/confirm-password.tsx
index f55e1f28..62fbe0f1 100644
--- a/resources/js/pages/auth/confirm-password.tsx
+++ b/resources/js/pages/auth/confirm-password.tsx
@@ -3,7 +3,7 @@ import { Head, useForm } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { FormEventHandler } from 'react';
-import InputError from '@/components/input-error';
+import InputError from '@/components/ui/input-error';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
diff --git a/resources/js/pages/auth/forgot-password.tsx b/resources/js/pages/auth/forgot-password.tsx
index 2adae3fe..f7d4d8d0 100644
--- a/resources/js/pages/auth/forgot-password.tsx
+++ b/resources/js/pages/auth/forgot-password.tsx
@@ -3,7 +3,7 @@ import { Head, useForm } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { FormEventHandler } from 'react';
-import InputError from '@/components/input-error';
+import InputError from '@/components/ui/input-error';
import TextLink from '@/components/text-link';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
diff --git a/resources/js/pages/auth/login.tsx b/resources/js/pages/auth/login.tsx
index f1da1e46..e7a22e70 100644
--- a/resources/js/pages/auth/login.tsx
+++ b/resources/js/pages/auth/login.tsx
@@ -2,7 +2,7 @@ import { Head, useForm } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { FormEventHandler } from 'react';
-import InputError from '@/components/input-error';
+import InputError from '@/components/ui/input-error';
import TextLink from '@/components/text-link';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
diff --git a/resources/js/pages/auth/reset-password.tsx b/resources/js/pages/auth/reset-password.tsx
index a4d4d4b4..54e0ae80 100644
--- a/resources/js/pages/auth/reset-password.tsx
+++ b/resources/js/pages/auth/reset-password.tsx
@@ -2,7 +2,7 @@ import { Head, useForm } from '@inertiajs/react';
import { LoaderCircle } from 'lucide-react';
import { FormEventHandler } from 'react';
-import InputError from '@/components/input-error';
+import InputError from '@/components/ui/input-error';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
diff --git a/resources/js/pages/server-logs/columns.tsx b/resources/js/pages/server-logs/partials/columns.tsx
similarity index 93%
rename from resources/js/pages/server-logs/columns.tsx
rename to resources/js/pages/server-logs/partials/columns.tsx
index 2ffc1e3f..3be139fe 100644
--- a/resources/js/pages/server-logs/columns.tsx
+++ b/resources/js/pages/server-logs/partials/columns.tsx
@@ -45,7 +45,7 @@ const LogActionCell = ({ row }: { row: Row }) => {
View Log
This is all content of the log
-
+
{content}
diff --git a/resources/js/pages/server-providers/create-server-provider.tsx b/resources/js/pages/server-providers/partials/create-server-provider.tsx
similarity index 98%
rename from resources/js/pages/server-providers/create-server-provider.tsx
rename to resources/js/pages/server-providers/partials/create-server-provider.tsx
index 609e2604..cc9ecdd1 100644
--- a/resources/js/pages/server-providers/create-server-provider.tsx
+++ b/resources/js/pages/server-providers/partials/create-server-provider.tsx
@@ -14,7 +14,7 @@ import { useForm, usePage } from '@inertiajs/react';
import { FormEventHandler, useEffect, useState } from 'react';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
-import InputError from '@/components/input-error';
+import InputError from '@/components/ui/input-error';
import { Form, FormField, FormFields } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { SharedData } from '@/types';
diff --git a/resources/js/pages/servers/index.tsx b/resources/js/pages/servers/index.tsx
index c5fbba6e..53ceccf8 100644
--- a/resources/js/pages/servers/index.tsx
+++ b/resources/js/pages/servers/index.tsx
@@ -2,16 +2,16 @@ import { Head, usePage } from '@inertiajs/react';
import { type Configs } from '@/types';
-import AppLayout from '@/layouts/app-layout';
import { DataTable } from '@/components/data-table';
-import { columns } from '@/pages/servers/columns';
+import { columns } from '@/pages/servers/partials/columns';
import { Server } from '@/types/server';
import Heading from '@/components/heading';
-import CreateServer from '@/pages/servers/create-server';
+import CreateServer from '@/pages/servers/partials/create-server';
import Container from '@/components/container';
import { PlusIcon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import React from 'react';
+import Layout from '@/layouts/app/layout';
type Response = {
servers: {
@@ -24,12 +24,12 @@ type Response = {
export default function Servers() {
const page = usePage();
return (
-
+
-
+
-
+
-
+
);
}
diff --git a/resources/js/pages/servers/installing.tsx b/resources/js/pages/servers/installing.tsx
index a2211eab..3407123d 100644
--- a/resources/js/pages/servers/installing.tsx
+++ b/resources/js/pages/servers/installing.tsx
@@ -1,12 +1,9 @@
import type { Server } from '@/types/server';
import type { ServerLog } from '@/types/server-log';
import Container from '@/components/container';
-import Heading from '@/components/heading';
-import { Progress } from '@/components/ui/progress';
import { DataTable } from '@/components/data-table';
-import { columns } from '@/pages/server-logs/columns';
+import { columns } from '@/pages/server-logs/partials/columns';
import { usePage } from '@inertiajs/react';
-import { Button } from '@/components/ui/button';
export default function InstallingServer() {
const page = usePage<{
@@ -17,14 +14,8 @@ export default function InstallingServer() {
}>();
return (
-
-
-
- {page.props.server.status === 'installation_failed' && }
-
-
- {page.props.server.progress}%
-
+
+ {' '}
);
}
diff --git a/resources/js/pages/servers/overview.tsx b/resources/js/pages/servers/overview.tsx
new file mode 100644
index 00000000..47e238f9
--- /dev/null
+++ b/resources/js/pages/servers/overview.tsx
@@ -0,0 +1,21 @@
+import type { Server } from '@/types/server';
+import type { ServerLog } from '@/types/server-log';
+import { DataTable } from '@/components/data-table';
+import { columns } from '@/pages/server-logs/partials/columns';
+import { usePage } from '@inertiajs/react';
+import Container from '@/components/container';
+
+export default function ServerOverview() {
+ const page = usePage<{
+ server: Server;
+ logs: {
+ data: ServerLog[];
+ };
+ }>();
+
+ return (
+
+
+
+ );
+}
diff --git a/resources/js/pages/servers/partials/actions.tsx b/resources/js/pages/servers/partials/actions.tsx
new file mode 100644
index 00000000..8d4e4bef
--- /dev/null
+++ b/resources/js/pages/servers/partials/actions.tsx
@@ -0,0 +1,27 @@
+import { Server } from '@/types/server';
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
+import { Button } from '@/components/ui/button';
+import { MoreVerticalIcon } from 'lucide-react';
+import DeleteServer from '@/pages/servers/partials/delete-server';
+
+export default function ServerActions({ server }: { server: Server }) {
+ return (
+
+
+
+
+
+ Copy payment ID
+
+
+ e.preventDefault()} variant="destructive">
+ Delete Server
+
+
+
+
+ );
+}
diff --git a/resources/js/pages/servers/columns.tsx b/resources/js/pages/servers/partials/columns.tsx
similarity index 89%
rename from resources/js/pages/servers/columns.tsx
rename to resources/js/pages/servers/partials/columns.tsx
index 24b19ea3..d11643cc 100644
--- a/resources/js/pages/servers/columns.tsx
+++ b/resources/js/pages/servers/partials/columns.tsx
@@ -2,10 +2,10 @@
import { ColumnDef } from '@tanstack/react-table';
import { Server } from '@/types/server';
-import { Badge } from '@/components/ui/badge';
import { Link } from '@inertiajs/react';
import { Button } from '@/components/ui/button';
import { EyeIcon } from 'lucide-react';
+import ServerStatus from '@/pages/servers/partials/status';
export const columns: ColumnDef[] = [
{
@@ -33,7 +33,7 @@ export const columns: ColumnDef[] = [
enableColumnFilter: true,
enableSorting: true,
cell: ({ row }) => {
- return {row.original.status};
+ return ;
},
},
{
diff --git a/resources/js/pages/servers/create-server.tsx b/resources/js/pages/servers/partials/create-server.tsx
similarity index 98%
rename from resources/js/pages/servers/create-server.tsx
rename to resources/js/pages/servers/partials/create-server.tsx
index 4d056206..910d99e3 100644
--- a/resources/js/pages/servers/create-server.tsx
+++ b/resources/js/pages/servers/partials/create-server.tsx
@@ -5,11 +5,11 @@ import { useForm, usePage } from '@inertiajs/react';
import React, { FormEventHandler, useState } from 'react';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
-import InputError from '@/components/input-error';
+import InputError from '@/components/ui/input-error';
import { Input } from '@/components/ui/input';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { ServerProvider } from '@/types/server-provider';
-import CreateServerProvider from '@/pages/server-providers/create-server-provider';
+import CreateServerProvider from '@/pages/server-providers/partials/create-server-provider';
import axios from 'axios';
import { Form, FormField, FormFields } from '@/components/ui/form';
import type { SharedData } from '@/types';
diff --git a/resources/js/pages/servers/partials/delete-server.tsx b/resources/js/pages/servers/partials/delete-server.tsx
new file mode 100644
index 00000000..cee23362
--- /dev/null
+++ b/resources/js/pages/servers/partials/delete-server.tsx
@@ -0,0 +1,63 @@
+import { Server } from '@/types/server';
+import { FormEvent, ReactNode } from 'react';
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from '@/components/ui/dialog';
+import { Button } from '@/components/ui/button';
+import { useForm } from '@inertiajs/react';
+import { Form, FormField, FormFields } from '@/components/ui/form';
+import { Label } from '@/components/ui/label';
+import { Input } from '@/components/ui/input';
+import InputError from '@/components/ui/input-error';
+import { LoaderCircleIcon } from 'lucide-react';
+
+export default function DeleteServer({ server, children }: { server: Server; children: ReactNode }) {
+ const form = useForm({
+ name: '',
+ });
+
+ const submit = (e: FormEvent) => {
+ e.preventDefault();
+ form.delete(route('servers.destroy', server.id));
+ };
+
+ return (
+
+ );
+}
diff --git a/resources/js/pages/servers/partials/header.tsx b/resources/js/pages/servers/partials/header.tsx
new file mode 100644
index 00000000..714327d3
--- /dev/null
+++ b/resources/js/pages/servers/partials/header.tsx
@@ -0,0 +1,73 @@
+import { Server } from '@/types/server';
+import { CloudIcon, IdCardIcon, LoaderCircleIcon, MapPinIcon, SlashIcon } from 'lucide-react';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
+import ServerStatus from '@/pages/servers/partials/status';
+import ServerActions from '@/pages/servers/partials/actions';
+import { cn } from '@/lib/utils';
+
+export default function ServerHeader({ server }: { server: Server }) {
+ return (
+
+
+
+
+
+
+
+
+ {server.name}
+ Server Name
+
+
+
+
+
+
+
+
+ {server.provider}
+ Server Provider
+
+
+
+
+
+
+
+
+ {server.ip}
+ Server IP
+
+
+ {['installing', 'installation_failed'].includes(server.status) && (
+ <>
+
+
+
+
+
+ Installation Progress
+
+ >
+ )}
+
+
+
+
+
+
+
+ );
+}
diff --git a/resources/js/pages/servers/partials/status.tsx b/resources/js/pages/servers/partials/status.tsx
new file mode 100644
index 00000000..2824ad29
--- /dev/null
+++ b/resources/js/pages/servers/partials/status.tsx
@@ -0,0 +1,6 @@
+import { Server } from '@/types/server';
+import { Badge } from '@/components/ui/badge';
+
+export default function ServerStatus({ server }: { server: Server }) {
+ return {server.status};
+}
diff --git a/resources/js/pages/servers/show.tsx b/resources/js/pages/servers/show.tsx
index 585c6c52..c97bf967 100644
--- a/resources/js/pages/servers/show.tsx
+++ b/resources/js/pages/servers/show.tsx
@@ -2,10 +2,11 @@ import { Head, usePage } from '@inertiajs/react';
import { type Configs } from '@/types';
-import AppLayout from '@/layouts/app-layout';
import { type Server } from '@/types/server';
import InstallingServer from '@/pages/servers/installing';
import type { ServerLog } from '@/types/server-log';
+import ServerOverview from '@/pages/servers/overview';
+import ServerLayout from '@/layouts/server/layout';
type Response = {
servers: {
@@ -22,10 +23,10 @@ type Response = {
export default function ShowServer() {
const page = usePage();
return (
-
+
- {['installing', 'installation_failed'].includes(page.props.server.status) && }
-
+ {['installing', 'installation_failed'].includes(page.props.server.status) ? : }
+
);
}
diff --git a/resources/js/pages/settings/password.tsx b/resources/js/pages/settings/password.tsx
deleted file mode 100644
index ae2e2287..00000000
--- a/resources/js/pages/settings/password.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import InputError from '@/components/input-error';
-import AppLayout from '@/layouts/app-layout';
-import SettingsLayout from '@/layouts/settings/layout';
-import { type BreadcrumbItem } from '@/types';
-import { Transition } from '@headlessui/react';
-import { Head, useForm } from '@inertiajs/react';
-import { FormEventHandler, useRef } from 'react';
-
-import HeadingSmall from '@/components/heading-small';
-import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-
-const breadcrumbs: BreadcrumbItem[] = [
- {
- title: 'Password settings',
- href: '/settings/password',
- },
-];
-
-export default function Password() {
- const passwordInput = useRef(null);
- const currentPasswordInput = useRef(null);
-
- const { data, setData, errors, put, reset, processing, recentlySuccessful } = useForm({
- current_password: '',
- password: '',
- password_confirmation: '',
- });
-
- const updatePassword: FormEventHandler = (e) => {
- e.preventDefault();
-
- put(route('password.update'), {
- preserveScroll: true,
- onSuccess: () => reset(),
- onError: (errors) => {
- if (errors.password) {
- reset('password', 'password_confirmation');
- passwordInput.current?.focus();
- }
-
- if (errors.current_password) {
- reset('current_password');
- currentPasswordInput.current?.focus();
- }
- },
- });
- };
-
- return (
-
-
-
-
-
-
-
- );
-}
diff --git a/resources/js/pages/settings/profile.tsx b/resources/js/pages/settings/profile.tsx
deleted file mode 100644
index 81ef3bb7..00000000
--- a/resources/js/pages/settings/profile.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-import { type BreadcrumbItem, type SharedData } from '@/types';
-import { Transition } from '@headlessui/react';
-import { Head, Link, useForm, usePage } from '@inertiajs/react';
-import { FormEventHandler } from 'react';
-
-import DeleteUser from '@/components/delete-user';
-import HeadingSmall from '@/components/heading-small';
-import InputError from '@/components/input-error';
-import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import AppLayout from '@/layouts/app-layout';
-import SettingsLayout from '@/layouts/settings/layout';
-
-const breadcrumbs: BreadcrumbItem[] = [
- {
- title: 'Home',
- href: '/',
- },
- {
- title: 'Profile settings',
- href: '/settings/profile',
- },
-];
-
-type ProfileForm = {
- name: string;
- email: string;
-};
-
-export default function Profile({ mustVerifyEmail, status }: { mustVerifyEmail: boolean; status?: string }) {
- const { auth } = usePage().props;
-
- const { data, setData, patch, errors, processing, recentlySuccessful } = useForm>({
- name: auth.user.name,
- email: auth.user.email,
- });
-
- const submit: FormEventHandler = (e) => {
- e.preventDefault();
-
- patch(route('profile.update'), {
- preserveScroll: true,
- });
- };
-
- return (
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/resources/js/pages/settings/profile/index.tsx b/resources/js/pages/settings/profile/index.tsx
new file mode 100644
index 00000000..2a8d470a
--- /dev/null
+++ b/resources/js/pages/settings/profile/index.tsx
@@ -0,0 +1,19 @@
+import { Head } from '@inertiajs/react';
+import DeleteUser from '@/pages/settings/profile/partials/delete-user';
+import SettingsLayout from '@/layouts/settings/layout';
+import Container from '@/components/container';
+import UpdatePassword from '@/pages/settings/profile/partials/update-password';
+import UpdateUser from '@/pages/settings/profile/partials/update-user';
+
+export default function Profile({ mustVerifyEmail, status }: { mustVerifyEmail: boolean; status?: string }) {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/resources/js/components/delete-user.tsx b/resources/js/pages/settings/profile/partials/delete-user.tsx
similarity index 72%
rename from resources/js/components/delete-user.tsx
rename to resources/js/pages/settings/profile/partials/delete-user.tsx
index c8205304..58e08472 100644
--- a/resources/js/components/delete-user.tsx
+++ b/resources/js/pages/settings/profile/partials/delete-user.tsx
@@ -1,18 +1,29 @@
import { useForm } from '@inertiajs/react';
import { FormEventHandler, useRef } from 'react';
-import InputError from '@/components/input-error';
+import InputError from '@/components/ui/input-error';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
-import HeadingSmall from '@/components/heading-small';
-
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
export default function DeleteUser() {
const passwordInput = useRef(null);
- const { data, setData, delete: destroy, processing, reset, errors, clearErrors } = useForm>({ password: '' });
+ const {
+ data,
+ setData,
+ delete: destroy,
+ processing,
+ reset,
+ errors,
+ clearErrors,
+ } = useForm<
+ Required<{
+ password: string;
+ }>
+ >({ password: '' });
const deleteUser: FormEventHandler = (e) => {
e.preventDefault();
@@ -31,14 +42,19 @@ export default function DeleteUser() {
};
return (
-
-
-
-
-
Warning
-
Please proceed with caution, this cannot be undone.
-
+
+
+ Delete account
+ Delete your account and all of its resources
+
+
+
+
+
Warning
+
Please proceed with caution, this cannot be undone.
+
+
-
-
+
+
);
}
diff --git a/resources/js/pages/settings/profile/partials/update-password.tsx b/resources/js/pages/settings/profile/partials/update-password.tsx
new file mode 100644
index 00000000..f6dc7b16
--- /dev/null
+++ b/resources/js/pages/settings/profile/partials/update-password.tsx
@@ -0,0 +1,116 @@
+import InputError from '@/components/ui/input-error';
+import { Transition } from '@headlessui/react';
+import { useForm } from '@inertiajs/react';
+import { FormEventHandler, useRef } from 'react';
+
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+
+export default function UpdatePassword() {
+ const passwordInput = useRef(null);
+ const currentPasswordInput = useRef(null);
+
+ const { data, setData, errors, put, reset, processing, recentlySuccessful } = useForm({
+ current_password: '',
+ password: '',
+ password_confirmation: '',
+ });
+
+ const updatePassword: FormEventHandler = (e) => {
+ e.preventDefault();
+
+ put(route('profile.password'), {
+ preserveScroll: true,
+ onSuccess: () => reset(),
+ onError: (errors) => {
+ if (errors.password) {
+ reset('password', 'password_confirmation');
+ passwordInput.current?.focus();
+ }
+
+ if (errors.current_password) {
+ reset('current_password');
+ currentPasswordInput.current?.focus();
+ }
+ },
+ });
+ };
+
+ return (
+
+
+ Update password
+ Ensure your account is using a long, random password to stay secure.
+
+
+
+
+
+ );
+}
diff --git a/resources/js/pages/settings/profile/partials/update-user.tsx b/resources/js/pages/settings/profile/partials/update-user.tsx
new file mode 100644
index 00000000..f20eb1bf
--- /dev/null
+++ b/resources/js/pages/settings/profile/partials/update-user.tsx
@@ -0,0 +1,110 @@
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Label } from '@/components/ui/label';
+import { Input } from '@/components/ui/input';
+import InputError from '@/components/ui/input-error';
+import { Link, useForm, usePage } from '@inertiajs/react';
+import { Button } from '@/components/ui/button';
+import { Transition } from '@headlessui/react';
+import type { SharedData } from '@/types';
+import { FormEventHandler } from 'react';
+
+type ProfileForm = {
+ name: string;
+ email: string;
+};
+
+export default function UpdateUser({ mustVerifyEmail, status }: { mustVerifyEmail: boolean; status?: string }) {
+ const { auth } = usePage().props;
+
+ const { data, setData, patch, errors, processing, recentlySuccessful } = useForm>({
+ name: auth.user.name,
+ email: auth.user.email,
+ });
+
+ const submit: FormEventHandler = (e) => {
+ e.preventDefault();
+
+ patch(route('profile.update'), {
+ preserveScroll: true,
+ });
+ };
+
+ return (
+
+
+ Profile information
+ Update your profile information and email address.
+
+
+
+
+
+ );
+}