Skip to content
This repository has been archived by the owner on Sep 22, 2024. It is now read-only.

Commit

Permalink
feat(plugins/netzolabs): add netzo.netzolabs() plugins with issues …
Browse files Browse the repository at this point in the history
…functionality
  • Loading branch information
miguelrk committed Jun 4, 2024
1 parent e5dbc7b commit ce81ed9
Show file tree
Hide file tree
Showing 8 changed files with 655 additions and 5 deletions.
30 changes: 28 additions & 2 deletions lib/components/blocks/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { signal } from "@preact/signals";
import type { ComponentChildren, JSX } from "preact";
import type { NetzoState } from "../../mod.ts";
import { DialogIssuesCreate } from "../../plugins/netzolabs/islands/dialog-issues-create.tsx";
import { Avatar, AvatarFallback, AvatarImage } from "../avatar.tsx";
import { buttonVariants } from "../button.tsx";
import { Button, buttonVariants } from "../button.tsx";
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -25,7 +26,7 @@ type NavRootProps = JSX.IntrinsicElements["nav"] & {
};

export function NavRoot({ className, ...props }: NavRootProps) {
const { denoJson } = props.state ?? {};
const { denoJson } = props.state?.netzolabs ?? {};
const darkMode = useDarkMode();

const logoSrc = darkMode.value
Expand Down Expand Up @@ -301,3 +302,28 @@ export function NavItemUser(
</DropdownMenu>
);
}

export function NavItemFeedback(props: {
icon?: string;
text: string;
locale: "en" | "es";
state: NetzoState;
}) {
return (
<DialogIssuesCreate locale={props.locale} state={props.state}>
<Button
variant="ghost"
className={cn(
"rounded-none",
"flex w-full justify-start h-[40px]",
"hover:cursor-pointer hover:bg-muted", // hover
)}
>
{props.icon && (props.icon.startsWith("http")
? <img {...props} src={props.icon} className="w-4 h-4 mr-3" />
: <div {...props} className={cn("w-4 h-4 mr-3", props.icon)} />)}
{props.text}
</Button>
</DialogIssuesCreate>
);
}
2 changes: 2 additions & 0 deletions lib/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { CronState } from "./plugins/cron/plugin.ts";
import type { DatabaseState } from "./plugins/database/plugin.ts";
import type { DatastoreState } from "./plugins/datastore/plugin.ts";
import type { EnvironmentsState } from "./plugins/environments/plugin.ts";
import type { NetzolabsState } from "./plugins/netzolabs/plugin.ts";
import type { StorageState } from "./plugins/storage/plugin.ts";
import { proxyConsole } from "./plugins/utils.ts";

Expand All @@ -26,6 +27,7 @@ export type NetzoState = {
environments?: EnvironmentsState;
loader?: LoaderState;
mdx?: MdxState;
netzolabs?: NetzolabsState;
storage?: StorageState;
// unocss?: UnocssState;
[k: string]: unknown;
Expand Down
1 change: 1 addition & 0 deletions lib/plugins/auth/middlewares/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type NetzoStateWithAuth = NetzoState & {

const skip = (_req: Request, ctx: FreshContext<NetzoStateWithAuth>) => {
if (!["route"].includes(ctx.destination)) return true;
if (ctx.url.pathname.startsWith("/api")) return true; // skip api routes by (custom endpoints)
if (ctx.url.pathname.startsWith("/auth/")) return true; // skip auth routes (signin, callback, signout)
if (ctx.url.pathname.startsWith("/database")) return true; // skip database routes
if (ctx.url.pathname.startsWith("/datastore")) return true; // skip datastore routes
Expand Down
4 changes: 1 addition & 3 deletions lib/plugins/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export function cors(): MiddlewareHandler {
};
}

export function apiKeyAuthentication(
{ apiKey }: { apiKey: string },
): MiddlewareHandler {
export function apiKeyAuthentication({ apiKey }: { apiKey: string }): MiddlewareHandler {
return async (req, ctx) => {
try {
if (!["route"].includes(ctx.destination)) return await ctx.next();
Expand Down
1 change: 1 addition & 0 deletions lib/plugins/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export * from "./datastore/plugin.ts";
export * from "./environments/plugin.ts";
export * from "./loader/plugin.ts";
export * from "./mdx/plugin.ts";
export * from "./netzolabs/plugin.ts";
export * from "./storage/plugin.ts";
export * from "./unocss/plugin.ts";
174 changes: 174 additions & 0 deletions lib/plugins/netzolabs/islands/dialog-issues-create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import type { ComponentChildren } from "preact";
import { useState } from "preact/compat";
import { Button } from "../../../components/button.tsx";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../../components/dialog.tsx";
import {
FormFieldCombobox,
FormFieldInput,
FormFieldTextarea,
} from "../../../components/form-fields.tsx";
import { Form, useForm } from "../../../components/form.tsx";
import type { NetzoState } from "../../../mod.ts";

export const NETZOLABS_API_URL = "https://netzolabs-ops.deno.dev";

export type Issue = {
// hidden:
id: string;
projectId: string;
userId: string;
userEmail: null | string;
// public:
type: "bug" | "enhancement" | "feature" | "feedback" | "question";
title: string;
description: string;
// private:
status: null | "backlog" | "stuck" | "todo" | "in-progress" | "done" | "waiting" | "deprecated";
priority: null | "low" | "medium" | "high" | "critical";
complexity: null | "low" | "medium" | "high" | "very-high";
// metadata:
createdAt: string;
updatedAt: string;
deletedAt: null | string;
};

export function DialogIssuesCreate(props: {
locale: "en" | "es";
state: NetzoState;
children: ComponentChildren;
}) {
const id = "nav-item-feedback-form";
const [open, setOpen] = useState(false);

const form = useForm<Issue>({
defaultValues: {
projectId: props.state?.netzolabs?.projectId,
userId: props.state?.auth?.sessionUser?.id,
userEmail: props.state?.auth?.sessionUser?.email,
type: "feedback",
title: "",
description: "",
status: null,
priority: null,
complexity: null,
},
});

const onSubmit = async ({ ...data }: Issue) => {
await fetch(`${NETZOLABS_API_URL}/api/issues`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(data),
});
form.reset();
setOpen(false);
};

const t = ({
en: {
title: "Report an Issue",
description: "Please provide details about the issue encountered.",
form: {
type: {
label: "Type",
options: [
{ value: "bug", label: "Bug Report" },
{ value: "enhancement", label: "Enhancement Request" },
{ value: "feature", label: "Feature Request" },
{ value: "feedback", label: "General Feedback" },
{ value: "question", label: "Question" },
],
},
title: { label: "Title" },
description: { label: "Description" },
submit: "Submit",
cancel: "Cancel",
},
},
es: {
title: "Reportar incidencia",
description: "Proporcione detalles sobre la incidencia encontrada.",
form: {
type: {
label: "Tipo",
options: [
{ value: "bug", label: "Reporte de error" },
{ value: "enhancement", label: "Solicitud de mejora" },
{ value: "feature", label: "Solicitud de nueva función" },
{ value: "feedback", label: "Comentarios generales" },
{ value: "question", label: "Pregunta" },
],
},
title: { label: "Título" },
description: { label: "Descripción" },
submit: "Enviar",
cancel: "Cancelar",
},
},
})?.[props.locale ?? "es"];

// NOTE: must manually invoke submit because submit button isteleported
// by dialog out of form (see https://github.com/shadcn-ui/ui/issues/709)
// IMPORTANT: When utilizing <Input type="file" /> alongside React Hook Form,
// it is important to have an uncontrolled input (i.e. without value prop).
// see https://github.com/shadcn-ui/ui/discussions/2137#discussioncomment-7907793
return (
<Dialog open={open} onOpenChange={() => !open && setOpen(true)}>
<DialogTrigger asChild>
{props.children}
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{t.title}</DialogTitle>
<DialogDescription>
{t.description}
</DialogDescription>
</DialogHeader>

<Form {...form}>
<form
id={id}
onSubmit={form.handleSubmit(onSubmit)}
onReset={() => form.reset(props.defaultValues)}
>
<FormFieldCombobox
name="type"
label={t.form.type.label}
options={t.form.type.options}
required={true}
form={form}
/>
<FormFieldInput
name="title"
label={t.form.title.label}
type="text"
required={true}
form={form}
/>
<FormFieldTextarea
name="description"
label={t.form.description.label}
required={true}
rows={6}
form={form}
/>
</form>
</Form>

<DialogFooter>
<Button form={id} type="submit">
{t.submit}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
14 changes: 14 additions & 0 deletions lib/plugins/netzolabs/middlewares/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { FreshContext } from "fresh/server.ts";
import { NetzoState } from "../../../mod.ts";
import type { NetzolabsConfig, NetzolabsState } from "../plugin.ts";

export function createNetzolabsState(config: NetzolabsConfig) {
return async (_req: Request, ctx: FreshContext<NetzoState>) => {
ctx.state.netzolabs ??= {
projectId: Deno.env.get("NETZO_PROJECT_ID")!,
denoJson: config.denoJson,
} satisfies NetzolabsState;

return await ctx.next();
};
}
Loading

0 comments on commit ce81ed9

Please sign in to comment.