mirror of
https://github.com/vitodeploy/vito.git
synced 2025-07-04 15:32:35 +00:00
Plugins base (#613)
* wip * wip * cleanup * notification channels * phpstan * services * remove server types * refactoring * refactoring
This commit is contained in:
@ -0,0 +1,94 @@
|
||||
import { Site, SiteFeatureAction } from '@/types/site';
|
||||
import React, { FormEvent, ReactNode, useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Form, FormField, FormFields } from '@/components/ui/form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useForm } from '@inertiajs/react';
|
||||
import { DynamicFieldConfig } from '@/types/dynamic-field-config';
|
||||
import DynamicField from '@/components/ui/dynamic-field';
|
||||
import { LoaderCircleIcon } from 'lucide-react';
|
||||
|
||||
export default function FeatureAction({
|
||||
site,
|
||||
featureId,
|
||||
actionId,
|
||||
action,
|
||||
children,
|
||||
}: {
|
||||
site: Site;
|
||||
featureId: string;
|
||||
actionId: string;
|
||||
action: SiteFeatureAction;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const form = useForm();
|
||||
|
||||
const submit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
form.post(
|
||||
route('site-features.action', {
|
||||
server: site.server_id,
|
||||
site: site.id,
|
||||
feature: featureId,
|
||||
action: actionId,
|
||||
}),
|
||||
{
|
||||
onSuccess: () => {
|
||||
setOpen(false);
|
||||
form.reset();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{action.label}</DialogTitle>
|
||||
<DialogDescription className="sr-only">action {action.label}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Form id="action-form" onSubmit={submit} className="p-4">
|
||||
<FormFields>
|
||||
<FormField>
|
||||
<p className="text-muted-foreground">
|
||||
You're performing action <b className="text-foreground">[{action.label}]</b> on site{' '}
|
||||
<b className="text-foreground">[{site.domain}]</b>
|
||||
</p>
|
||||
</FormField>
|
||||
{action.form?.map((field: DynamicFieldConfig) => (
|
||||
<DynamicField
|
||||
key={`field-${field.name}`}
|
||||
/*@ts-expect-error dynamic types*/
|
||||
value={form.data[field.name]}
|
||||
onChange={(value) => form.setData(field.name, value)}
|
||||
config={field}
|
||||
error={form.errors[field.name]}
|
||||
/>
|
||||
))}
|
||||
</FormFields>
|
||||
</Form>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">Cancel</Button>
|
||||
</DialogClose>
|
||||
<Button form="action-form" disabled={form.processing} onClick={submit}>
|
||||
{form.processing && <LoaderCircleIcon className="animate-spin" />}
|
||||
{action.label}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
83
resources/js/pages/site-features/index.tsx
Normal file
83
resources/js/pages/site-features/index.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import { Head, usePage } from '@inertiajs/react';
|
||||
import { Server } from '@/types/server';
|
||||
import Container from '@/components/container';
|
||||
import HeaderContainer from '@/components/header-container';
|
||||
import Heading from '@/components/heading';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import ServerLayout from '@/layouts/server/layout';
|
||||
import { BookOpenIcon, MoreVerticalIcon } from 'lucide-react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import React from 'react';
|
||||
import { Site, SiteFeature } from '@/types/site';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import FeatureAction from '@/pages/site-features/components/feature-action';
|
||||
|
||||
export default function SiteFeatures() {
|
||||
const page = usePage<{
|
||||
server: Server;
|
||||
site: Site;
|
||||
features: {
|
||||
[key: string]: SiteFeature;
|
||||
};
|
||||
}>();
|
||||
|
||||
return (
|
||||
<ServerLayout>
|
||||
<Head title={`Features - ${page.props.site.domain}`} />
|
||||
|
||||
<Container className="max-w-5xl">
|
||||
<HeaderContainer>
|
||||
<Heading title="Features" description="Your site has some features enabled by Vito or other plugins" />
|
||||
<div className="flex items-center gap-2">
|
||||
<a href="https://vitodeploy.com/docs/sites/features" target="_blank">
|
||||
<Button variant="outline">
|
||||
<BookOpenIcon />
|
||||
<span className="hidden lg:block">Docs</span>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</HeaderContainer>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex-row items-center justify-between gap-2">
|
||||
<div className="space-y-2">
|
||||
<CardTitle>Site features</CardTitle>
|
||||
<CardDescription>Here you can see the list of features and their actions</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{Object.entries(page.props.features).map(([key, feature], index) => (
|
||||
<div key={`feature-${key}`}>
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<div className="space-y-1">
|
||||
<p>{feature.label}</p>
|
||||
<p className="text-muted-foreground text-sm">{feature.description}</p>
|
||||
</div>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline">
|
||||
Actions
|
||||
<MoreVerticalIcon />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{Object.entries(feature.actions || {}).map(([actionKey, action]) => (
|
||||
<FeatureAction key={`action-${actionKey}`} site={page.props.site} featureId={key} actionId={actionKey} action={action}>
|
||||
<DropdownMenuItem onSelect={(e) => e.preventDefault()} disabled={!action.active}>
|
||||
{action.label}
|
||||
</DropdownMenuItem>
|
||||
</FeatureAction>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{index < Object.keys(page.props.features).length - 1 && <Separator />}
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Container>
|
||||
</ServerLayout>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user