Merge branch 'dev' into feature/newt-podman-install

This commit is contained in:
Owen Schwartz
2025-04-10 21:40:23 -04:00
committed by GitHub
62 changed files with 2206 additions and 1898 deletions

View File

@@ -53,13 +53,11 @@ const createSiteFormSchema = z
.object({
name: z
.string()
.min(2, {
message: "Name must be at least 2 characters."
})
.min(2, { message: "Name must be at least 2 characters." })
.max(30, {
message: "Name must not be longer than 30 characters."
}),
method: z.string(),
method: z.enum(["newt", "wireguard", "local"]),
copied: z.boolean()
})
.refine(
@@ -77,6 +75,15 @@ const createSiteFormSchema = z
type CreateSiteFormValues = z.infer<typeof createSiteFormSchema>;
type SiteType = "newt" | "wireguard" | "local";
interface TunnelTypeOption {
id: SiteType;
title: string;
description: string;
disabled?: boolean;
}
type Commands = {
mac: Record<string, string[]>;
linux: Record<string, string[]>;
@@ -102,7 +109,9 @@ export default function Page() {
const { orgId } = useParams();
const router = useRouter();
const [tunnelTypes, setTunnelTypes] = useState<any>([
const [tunnelTypes, setTunnelTypes] = useState<
ReadonlyArray<TunnelTypeOption>
>([
{
id: "newt",
title: "Newt Tunnel (Recommended)",
@@ -342,22 +351,15 @@ WantedBy=default.target`
}
};
const form = useForm({
const form = useForm<CreateSiteFormValues>({
resolver: zodResolver(createSiteFormSchema),
defaultValues: {
name: "",
copied: false,
method: "newt"
}
defaultValues: { name: "", copied: false, method: "newt" }
});
async function onSubmit(data: CreateSiteFormValues) {
setCreateLoading(true);
let payload: CreateSiteBody = {
name: data.name,
type: data.method
};
let payload: CreateSiteBody = { name: data.name, type: data.method };
if (data.method == "wireguard") {
if (!siteDefaults || !wgConfig) {
@@ -486,10 +488,7 @@ WantedBy=default.target`
setTunnelTypes((prev: any) => {
return prev.map((item: any) => {
return {
...item,
disabled: false
};
return { ...item, disabled: false };
});
});
}
@@ -564,9 +563,8 @@ WantedBy=default.target`
</FormControl>
<FormMessage />
<FormDescription>
This is the
display name for the
site.
This is the display
name for the site.
</FormDescription>
</FormItem>
)}
@@ -590,12 +588,10 @@ WantedBy=default.target`
<SettingsSectionBody>
<StrategySelect
options={tunnelTypes}
defaultValue={
form.getValues("method") as string
}
onChange={(value) =>
form.setValue("method", value)
}
defaultValue={form.getValues("method")}
onChange={(value) => {
form.setValue("method", value);
}}
cols={3}
/>
</SettingsSectionBody>

View File

@@ -0,0 +1,46 @@
"use client";
import React from "react";
import confetti from "canvas-confetti";
export default function SupporterMessage({ tier }: { tier: string }) {
return (
<div className="relative flex items-center space-x-2 whitespace-nowrap group">
<span
className="cursor-pointer"
onClick={(e) => {
// Get the bounding box of the element
const rect = (
e.target as HTMLElement
).getBoundingClientRect();
// Trigger confetti centered on the word "Pangolin"
confetti({
particleCount: 100,
spread: 70,
origin: {
x: (rect.left + rect.width / 2) / window.innerWidth,
y: rect.top / window.innerHeight
},
colors: ["#FFA500", "#FF4500", "#FFD700"]
});
}}
>
Pangolin
</span>
{/* SVG Star */}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
className="w-4 h-4 text-primary"
>
<path d="M12 .587l3.668 7.431 8.2 1.192-5.934 5.782 1.4 8.168L12 18.896l-7.334 3.864 1.4-8.168L.132 9.21l8.2-1.192z" />
</svg>
{/* Popover */}
<div className="absolute left-1/2 transform -translate-x-1/2 -top-10 hidden group-hover:block bg-white/10 text-primary text-sm rounded-md shadow-lg px-4 py-2 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity">
Thank you for supporting Pangolin as a {tier}!
</div>
</div>
);
}

View File

@@ -2,65 +2,63 @@
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 0.0% 10.0%;
--card: 0 0% 100%;
--card-foreground: 20 0.0% 10.0%;
--popover: 0 0% 100%;
--popover-foreground: 20 0.0% 10.0%;
--primary: 24.6 95% 53.1%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 85.0%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 90%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 80%;
--input: 20 5.9% 75%;
--ring: 24.6 95% 53.1%;
--radius: 0.75rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
:root {
--background: 0 0% 100%;
--foreground: 20 0% 10%;
--card: 0 0% 100%;
--card-foreground: 20 0% 10%;
--popover: 0 0% 100%;
--popover-foreground: 20 0% 10%;
--primary: 24.6 95% 53.1%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 85%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 90%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 80%;
--input: 20 5.9% 75%;
--ring: 24.6 95% 53.1%;
--radius: 0.75rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 20 0.0% 10.0%;
--foreground: 60 9.1% 97.8%;
--card: 20 0.0% 10.0%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 0.0% 10.0%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 20.5 90.2% 48.2%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 12 6.5% 15.0%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 25.0%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 2.5% 15.0%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 30.0%;
--input: 12 6.5% 35.0%;
--ring: 20.5 90.2% 48.2%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
.dark {
--background: 20 0% 10%;
--foreground: 60 9.1% 97.8%;
--card: 20 0% 10%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 0% 10%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 20.5 90.2% 48.2%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 12 6.5% 15%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 25%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 2.5% 15%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 30%;
--input: 12 6.5% 35%;
--ring: 20.5 90.2% 48.2%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer base {
* {
@apply border-border;
@@ -70,4 +68,3 @@
@apply bg-background text-foreground;
}
}

View File

@@ -12,13 +12,14 @@ import SupportStatusProvider from "@app/providers/SupporterStatusProvider";
import { createApiClient, internal, priv } from "@app/lib/api";
import { AxiosResponse } from "axios";
import { IsSupporterKeyVisibleResponse } from "@server/routers/supporterKey";
import SupporterMessage from "./components/SupporterMessage";
export const metadata: Metadata = {
title: `Dashboard - Pangolin`,
description: ""
};
export const dynamic = 'force-dynamic';
export const dynamic = "force-dynamic";
// const font = Figtree({ subsets: ["latin"] });
const font = Inter({ subsets: ["latin"] });
@@ -34,9 +35,9 @@ export default async function RootLayout({
visible: true
} as any;
const res = await priv.get<
AxiosResponse<IsSupporterKeyVisibleResponse>
>("supporter-key/visible");
const res = await priv.get<AxiosResponse<IsSupporterKeyVisibleResponse>>(
"supporter-key/visible"
);
supporterData.visible = res.data.data.visible;
supporterData.tier = res.data.data.tier;
@@ -61,9 +62,15 @@ export default async function RootLayout({
{/* Footer */}
<footer className="hidden md:block w-full mt-12 py-3 mb-6 px-4">
<div className="container mx-auto flex flex-wrap justify-center items-center h-3 space-x-4 text-sm text-neutral-400 dark:text-neutral-600">
<div className="flex items-center space-x-2 whitespace-nowrap">
<span>Pangolin</span>
</div>
{supporterData?.tier ? (
<SupporterMessage
tier={supporterData.tier}
/>
) : (
<div className="flex items-center space-x-2 whitespace-nowrap">
<span>Pangolin</span>
</div>
)}
<Separator orientation="vertical" />
<a
href="https://fossorial.io/"