Merge branch 'main' of https://github.com/fosrl/pangolin
This commit is contained in:
commit
0ff183796c
11 changed files with 301 additions and 71 deletions
|
@ -46,10 +46,10 @@ authenticated.put("/org/:orgId/site", verifyOrgAccess, site.createSite);
|
|||
authenticated.get("/org/:orgId/sites", verifyOrgAccess, site.listSites);
|
||||
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(
|
||||
"/org/:orgId/site/:siteId/resource",
|
||||
|
|
54
src/app/[orgId]/sites/[niceId]/components/ClientLayout.tsx
Normal file
54
src/app/[orgId]/sites/[niceId]/components/ClientLayout.tsx
Normal file
|
@ -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>);
|
||||
}
|
|
@ -18,7 +18,7 @@ import {
|
|||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { generateKeypair } from "./wireguard-config";
|
||||
import { generateKeypair } from "./wireguardConfig";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { api } from "@/api";
|
||||
import { useParams } from "next/navigation";
|
174
src/app/[orgId]/sites/[niceId]/components/GeneralForm.tsx
Normal file
174
src/app/[orgId]/sites/[niceId]/components/GeneralForm.tsx
Normal file
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -11,31 +11,15 @@ import { redirect } from "next/navigation";
|
|||
import { authCookieHeader } from "@app/api/cookies";
|
||||
import Link from "next/link";
|
||||
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 = {
|
||||
title: "Forms",
|
||||
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 {
|
||||
children: React.ReactNode;
|
||||
params: { niceId: string; orgId: string };
|
||||
|
@ -46,6 +30,7 @@ export default async function SettingsLayout({
|
|||
params,
|
||||
}: SettingsLayoutProps) {
|
||||
let site = null;
|
||||
|
||||
if (params.niceId !== "create") {
|
||||
try {
|
||||
const res = await internal.get<AxiosResponse<GetSiteResponse>>(
|
||||
|
@ -85,33 +70,12 @@ export default async function SettingsLayout({
|
|||
</Link>
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import React from "react";
|
||||
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({
|
||||
params,
|
||||
}: {
|
||||
params: { niceId: string };
|
||||
}) {
|
||||
const isCreateForm = params.niceId === "create";
|
||||
const isCreate = params.niceId === "create";
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">
|
||||
{isCreateForm ? "Create Site" : "Profile"}
|
||||
{isCreate ? "Create Site" : "Profile"}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{isCreateForm
|
||||
{isCreate
|
||||
? "Create a new site for your profile."
|
||||
: "This is how others will see you on the site."}
|
||||
</p>
|
||||
</div>
|
||||
<Separator />
|
||||
|
||||
{isCreateForm ? <CreateSiteForm /> : <ProfileForm />}
|
||||
{isCreate ? <CreateSiteForm /> : <GeneralForm />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import { GetSiteResponse } from "@server/routers/site/getSite";
|
||||
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;
|
|
@ -1,7 +1,10 @@
|
|||
import { SiteContext } from "@app/contexts/siteContext";
|
||||
import SiteContext from "@app/contexts/siteContext";
|
||||
import { useContext } from "react";
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,16 +1,44 @@
|
|||
"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 { ReactNode } from "react";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useState } from "react";
|
||||
|
||||
type LandingProviderProps = {
|
||||
interface SiteProviderProps {
|
||||
children: React.ReactNode;
|
||||
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;
|
Loading…
Add table
Reference in a new issue