Ver código fonte

Merge branch 'main' of https://github.com/fosrl/pangolin

Milo Schwartz 8 meses atrás
pai
commit
0ff183796c

+ 4 - 4
server/routers/external.ts

@@ -46,10 +46,10 @@ authenticated.put("/org/:orgId/site", verifyOrgAccess, site.createSite);
 authenticated.get("/org/:orgId/sites", verifyOrgAccess, site.listSites);
 authenticated.get("/org/:orgId/sites", verifyOrgAccess, site.listSites);
 authenticated.get("/org/:orgId/site/:niceId", verifyOrgAccess, site.getSite);
 authenticated.get("/org/:orgId/site/:niceId", verifyOrgAccess, site.getSite);
 
 
-authenticated.get("/site/siteId/:siteId", verifySiteAccess, site.getSite);
-authenticated.get("/site/siteId/:siteId/roles", verifySiteAccess, site.listSiteRoles);
-authenticated.post("/site/siteId/:siteId", verifySiteAccess, site.updateSite);
-authenticated.delete("/site/siteId/:siteId", verifySiteAccess, site.deleteSite);
+authenticated.get("/site/:siteId", verifySiteAccess, site.getSite);
+authenticated.get("/site/:siteId/roles", verifySiteAccess, site.listSiteRoles);
+authenticated.post("/site/:siteId", verifySiteAccess, site.updateSite);
+authenticated.delete("/site/:siteId", verifySiteAccess, site.deleteSite);
 
 
 authenticated.put(
 authenticated.put(
     "/org/:orgId/site/:siteId/resource",
     "/org/:orgId/site/:siteId/resource",

+ 54 - 0
src/app/[orgId]/sites/[niceId]/components/ClientLayout.tsx

@@ -0,0 +1,54 @@
+"use client";
+
+import { SidebarNav } from "@app/components/sidebar-nav";
+import { useSiteContext } from "@app/hooks/useSiteContext";
+
+const sidebarNavItems = [
+    {
+        title: "Profile",
+        href: "/{orgId}/sites/{niceId}",
+    },
+    {
+        title: "Appearance",
+        href: "/{orgId}/sites/{niceId}/appearance",
+    },
+    {
+        title: "Notifications",
+        href: "/{orgId}/sites/{niceId}/notifications",
+    },
+    {
+        title: "Display",
+        href: "/{orgId}/sites/{niceId}/display",
+    },
+];
+
+export function ClientLayout({ isCreate, children }: { isCreate: boolean; children: React.ReactNode }) {
+    const { site } = useSiteContext();
+    return (<div className="hidden space-y-6 0 pb-16 md:block">
+        <div className="space-y-0.5">
+            <h2 className="text-2xl font-bold tracking-tight">
+                {isCreate
+                    ? "New Site"
+                    : site?.name + " Settings"}
+            </h2>
+            <p className="text-muted-foreground">
+                {isCreate
+                    ? "Create a new site"
+                    : "Configure the settings on your site: " +
+                    site?.name || ""}
+                .
+            </p>
+        </div>
+        <div className="flex flex-col space-y-6 lg:flex-row lg:space-x-12 lg:space-y-0">
+            <aside className="-mx-4 lg:w-1/5">
+                <SidebarNav
+                    items={sidebarNavItems}
+                    disabled={isCreate}
+                />
+            </aside>
+            <div className="flex-1 lg:max-w-2xl">
+                {children}
+            </div>
+        </div>
+    </div>);
+}

+ 1 - 1
src/app/[orgId]/sites/[niceId]/components/create-site.tsx → src/app/[orgId]/sites/[niceId]/components/CreateSite.tsx

@@ -18,7 +18,7 @@ import {
     FormMessage,
     FormMessage,
 } from "@/components/ui/form"
 } from "@/components/ui/form"
 import { Input } from "@/components/ui/input"
 import { Input } from "@/components/ui/input"
-import { generateKeypair } from "./wireguard-config";
+import { generateKeypair } from "./wireguardConfig";
 import React, { useState, useEffect } from "react";
 import React, { useState, useEffect } from "react";
 import { api } from "@/api";
 import { api } from "@/api";
 import { useParams } from "next/navigation";
 import { useParams } from "next/navigation";

+ 174 - 0
src/app/[orgId]/sites/[niceId]/components/GeneralForm.tsx

@@ -0,0 +1,174 @@
+"use client"
+
+import Link from "next/link"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useFieldArray, useForm } from "react-hook-form"
+import { z } from "zod"
+
+import { cn } from "@/lib/utils"
+import { toast } from "@/hooks/use-toast"
+
+import { Button } from "@/components/ui/button"
+import {
+    Form,
+    FormControl,
+    FormDescription,
+    FormField,
+    FormItem,
+    FormLabel,
+    FormMessage,
+} from "@/components/ui/form"
+import { Input } from "@/components/ui/input"
+import {
+    Select,
+    SelectContent,
+    SelectItem,
+    SelectTrigger,
+    SelectValue,
+} from "@/components/ui/select"
+import { Textarea } from "@/components/ui/textarea"
+import { useSiteContext } from "@app/hooks/useSiteContext"
+import api from "@app/api"
+
+const GeneralFormSchema = z.object({
+    name: z.string()
+    //   email: z
+    //     .string({
+    //       required_error: "Please select an email to display.",
+    //     })
+    //     .email(),
+    //   bio: z.string().max(160).min(4),
+    //   urls: z
+    //     .array(
+    //       z.object({
+    //         value: z.string().url({ message: "Please enter a valid URL." }),
+    //       })
+    //     )
+    //     .optional(),
+})
+
+type GeneralFormValues = z.infer<typeof GeneralFormSchema>
+
+export function GeneralForm() {
+    const { site, updateSite } = useSiteContext();
+
+    const form = useForm<GeneralFormValues>({
+        resolver: zodResolver(GeneralFormSchema),
+        defaultValues: {
+            name: site?.name
+        },
+        mode: "onChange",
+    })
+
+    //   const { fields, append } = useFieldArray({
+    //     name: "urls",
+    //     control: form.control,
+    //   })
+
+    async function onSubmit(data: GeneralFormValues) {
+        await updateSite({ name: data.name });
+    }
+
+    return (
+        <Form {...form}>
+            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
+                <FormField
+                    control={form.control}
+                    name="name"
+                    render={({ field }) => (
+                        <FormItem>
+                            <FormLabel>Name</FormLabel>
+                            <FormControl>
+                                <Input {...field} />
+                            </FormControl>
+                            <FormDescription>
+                                This is the display name of the site.
+                            </FormDescription>
+                            <FormMessage />
+                        </FormItem>
+                    )}
+                />
+                {/* <FormField
+          control={form.control}
+          name="email"
+          render={({ field }) => (
+            <FormItem>
+              <FormLabel>Email</FormLabel>
+              <Select onValueChange={field.onChange} defaultValue={field.value}>
+                <FormControl>
+                  <SelectTrigger>
+                    <SelectValue placeholder="Select a verified email to display" />
+                  </SelectTrigger>
+                </FormControl>
+                <SelectContent>
+                  <SelectItem value="m@example.com">m@example.com</SelectItem>
+                  <SelectItem value="m@google.com">m@google.com</SelectItem>
+                  <SelectItem value="m@support.com">m@support.com</SelectItem>
+                </SelectContent>
+              </Select>
+              <FormDescription>
+                You can manage verified email addresses in your{" "}
+                <Link href="/examples/forms">email settings</Link>.
+              </FormDescription>
+              <FormMessage />
+            </FormItem>
+          )}
+        />
+        <FormField
+          control={form.control}
+          name="bio"
+          render={({ field }) => (
+            <FormItem>
+              <FormLabel>Bio</FormLabel>
+              <FormControl>
+                <Textarea
+                  placeholder="Tell us a little bit about yourself"
+                  className="resize-none"
+                  {...field}
+                />
+              </FormControl>
+              <FormDescription>
+                You can <span>@mention</span> other users and organizations to
+                link to them.
+              </FormDescription>
+              <FormMessage />
+            </FormItem>
+          )}
+        />
+        <div>
+          {fields.map((field, index) => (
+            <FormField
+              control={form.control}
+              key={field.id}
+              name={`urls.${index}.value`}
+              render={({ field }) => (
+                <FormItem>
+                  <FormLabel className={cn(index !== 0 && "sr-only")}>
+                    URLs
+                  </FormLabel>
+                  <FormDescription className={cn(index !== 0 && "sr-only")}>
+                    Add links to your website, blog, or social media profiles.
+                  </FormDescription>
+                  <FormControl>
+                    <Input {...field} />
+                  </FormControl>
+                  <FormMessage />
+                </FormItem>
+              )}
+            />
+          ))}
+          <Button
+            type="button"
+            variant="outline"
+            size="sm"
+            className="mt-2"
+            onClick={() => append({ value: "" })}
+          >
+            Add URL
+          </Button>
+        </div> */}
+                <Button type="submit">Update Site</Button>
+            </form>
+        </Form>
+    )
+}

+ 0 - 0
src/app/[orgId]/sites/[niceId]/components/newt-config.tsx → src/app/[orgId]/sites/[niceId]/components/NewtConfig.tsx


+ 0 - 0
src/app/[orgId]/sites/[niceId]/components/wireguard-config.ts → src/app/[orgId]/sites/[niceId]/components/wireguardConfig.ts


+ 10 - 46
src/app/[orgId]/sites/[niceId]/layout.tsx

@@ -11,31 +11,15 @@ import { redirect } from "next/navigation";
 import { authCookieHeader } from "@app/api/cookies";
 import { authCookieHeader } from "@app/api/cookies";
 import Link from "next/link";
 import Link from "next/link";
 import { ArrowLeft, ChevronLeft } from "lucide-react";
 import { ArrowLeft, ChevronLeft } from "lucide-react";
+import { useEffect, useState } from "react";
+import { toast } from "@app/hooks/use-toast";
+import { ClientLayout } from "./components/ClientLayout";
 
 
 export const metadata: Metadata = {
 export const metadata: Metadata = {
     title: "Forms",
     title: "Forms",
     description: "Advanced form example using react-hook-form and Zod.",
     description: "Advanced form example using react-hook-form and Zod.",
 };
 };
 
 
-const sidebarNavItems = [
-    {
-        title: "Profile",
-        href: "/{orgId}/sites/{niceId}",
-    },
-    {
-        title: "Appearance",
-        href: "/{orgId}/sites/{niceId}/appearance",
-    },
-    {
-        title: "Notifications",
-        href: "/{orgId}/sites/{niceId}/notifications",
-    },
-    {
-        title: "Display",
-        href: "/{orgId}/sites/{niceId}/display",
-    },
-];
-
 interface SettingsLayoutProps {
 interface SettingsLayoutProps {
     children: React.ReactNode;
     children: React.ReactNode;
     params: { niceId: string; orgId: string };
     params: { niceId: string; orgId: string };
@@ -46,6 +30,7 @@ export default async function SettingsLayout({
     params,
     params,
 }: SettingsLayoutProps) {
 }: SettingsLayoutProps) {
     let site = null;
     let site = null;
+
     if (params.niceId !== "create") {
     if (params.niceId !== "create") {
         try {
         try {
             const res = await internal.get<AxiosResponse<GetSiteResponse>>(
             const res = await internal.get<AxiosResponse<GetSiteResponse>>(
@@ -85,33 +70,12 @@ export default async function SettingsLayout({
                 </Link>
                 </Link>
             </div>
             </div>
 
 
-            <div className="hidden space-y-6 0 pb-16 md:block">
-                <div className="space-y-0.5">
-                    <h2 className="text-2xl font-bold tracking-tight">
-                        {params.niceId == "create"
-                            ? "New Site"
-                            : site?.name + " Settings" || "Site Settings"}
-                    </h2>
-                    <p className="text-muted-foreground">
-                        {params.niceId == "create"
-                            ? "Create a new site"
-                            : "Configure the settings on your site: " +
-                                  site?.name || ""}
-                        .
-                    </p>
-                </div>
-                <div className="flex flex-col space-y-6 lg:flex-row lg:space-x-12 lg:space-y-0">
-                    <aside className="-mx-4 lg:w-1/5">
-                        <SidebarNav
-                            items={sidebarNavItems}
-                            disabled={params.niceId == "create"}
-                        />
-                    </aside>
-                    <div className="flex-1 lg:max-w-2xl">
-                        <SiteProvider site={site}>{children}</SiteProvider>
-                    </div>
-                </div>
-            </div>
+            <SiteProvider site={site}>                
+                <ClientLayout
+                isCreate={params.niceId === "create"}
+            >
+                {children}
+            </ClientLayout></SiteProvider>
         </>
         </>
     );
     );
 }
 }

+ 6 - 6
src/app/[orgId]/sites/[niceId]/page.tsx

@@ -1,30 +1,30 @@
 import React from "react";
 import React from "react";
 import { Separator } from "@/components/ui/separator";
 import { Separator } from "@/components/ui/separator";
-import { ProfileForm } from "@app/components/profile-form";
-import { CreateSiteForm } from "./components/create-site";
+import { CreateSiteForm } from "./components/CreateSite";
+import { GeneralForm } from "./components/GeneralForm";
 
 
 export default function SettingsProfilePage({
 export default function SettingsProfilePage({
     params,
     params,
 }: {
 }: {
     params: { niceId: string };
     params: { niceId: string };
 }) {
 }) {
-    const isCreateForm = params.niceId === "create";
+    const isCreate = params.niceId === "create";
 
 
     return (
     return (
         <div className="space-y-6">
         <div className="space-y-6">
             <div>
             <div>
                 <h3 className="text-lg font-medium">
                 <h3 className="text-lg font-medium">
-                    {isCreateForm ? "Create Site" : "Profile"}
+                    {isCreate ? "Create Site" : "Profile"}
                 </h3>
                 </h3>
                 <p className="text-sm text-muted-foreground">
                 <p className="text-sm text-muted-foreground">
-                    {isCreateForm
+                    {isCreate
                         ? "Create a new site for your profile."
                         ? "Create a new site for your profile."
                         : "This is how others will see you on the site."}
                         : "This is how others will see you on the site."}
                 </p>
                 </p>
             </div>
             </div>
             <Separator />
             <Separator />
 
 
-            {isCreateForm ? <CreateSiteForm /> : <ProfileForm />}
+            {isCreate ? <CreateSiteForm /> : <GeneralForm />}
         </div>
         </div>
     );
     );
 }
 }

+ 8 - 1
src/contexts/siteContext.ts

@@ -1,4 +1,11 @@
 import { GetSiteResponse } from "@server/routers/site/getSite";
 import { GetSiteResponse } from "@server/routers/site/getSite";
 import { createContext } from "react";
 import { createContext } from "react";
 
 
-export const SiteContext = createContext<GetSiteResponse | null>(null);
+interface SiteContextType {
+    site: GetSiteResponse | null;
+    updateSite: (updatedSite: Partial<GetSiteResponse>) => Promise<void>;
+}
+
+const SiteContext = createContext<SiteContextType | undefined>(undefined);
+
+export default SiteContext;

+ 7 - 4
src/hooks/useSiteContext.ts

@@ -1,7 +1,10 @@
-import { SiteContext } from "@app/contexts/siteContext";
+import SiteContext from "@app/contexts/siteContext";
 import { useContext } from "react";
 import { useContext } from "react";
 
 
 export function useSiteContext() {
 export function useSiteContext() {
-    const site = useContext(SiteContext);
-    return site;
-}
+    const context = useContext(SiteContext);
+    if (context === undefined) {
+        throw new Error('useSiteContext must be used within a SiteProvider');
+    }
+    return context;
+}

+ 36 - 8
src/providers/SiteProvider.tsx

@@ -1,16 +1,44 @@
 "use client";
 "use client";
 
 
-import { SiteContext } from "@app/contexts/siteContext";
+import api from "@app/api";
+import SiteContext from "@app/contexts/siteContext";
+import { toast } from "@app/hooks/use-toast";
 import { GetSiteResponse } from "@server/routers/site/getSite";
 import { GetSiteResponse } from "@server/routers/site/getSite";
-import { ReactNode } from "react";
+import { AxiosResponse } from "axios";
+import { useState } from "react";
 
 
-type LandingProviderProps = {
+interface SiteProviderProps {
+    children: React.ReactNode;
     site: GetSiteResponse | null;
     site: GetSiteResponse | null;
-    children: ReactNode;
-};
+}
+
+export function SiteProvider({ children, site: serverSite }: SiteProviderProps) {
+    const [site, setSite] = useState<GetSiteResponse | null>(serverSite);
+
+    const updateSite = async (updatedSite: Partial<GetSiteResponse>) => {
+        try {
+            if (!site) {
+                throw new Error("No site to update");
+            }
+
+            const res = await api.post<AxiosResponse<GetSiteResponse>>(
+                `site/${site.siteId}`,
+                updatedSite,
+            );
+            setSite(res.data.data);
+            toast({
+                title: "Site updated!",
+            });
+        } catch (error) {
+            console.error(error);
+            toast({
+                title: "Error updating site...",
+            })
+        }
+    };
+
 
 
-export function SiteProvider({ site, children }: LandingProviderProps) {
-    return <SiteContext.Provider value={site}>{children}</SiteContext.Provider>;
+    return <SiteContext.Provider value={{ site, updateSite }}>{children}</SiteContext.Provider>;
 }
 }
 
 
-export default SiteProvider;
+export default SiteProvider;