Pull up downstream changes

This commit is contained in:
Owen
2025-07-13 21:57:24 -07:00
parent c679875273
commit 98a261e38c
108 changed files with 9799 additions and 2038 deletions

View File

@@ -1,4 +1,5 @@
import ProfileIcon from "@app/components/ProfileIcon";
import ThemeSwitcher from "@app/components/ThemeSwitcher";
import { Separator } from "@app/components/ui/separator";
import { priv } from "@app/lib/api";
import { verifySession } from "@app/lib/auth/verifySession";
@@ -11,7 +12,7 @@ import { cache } from "react";
import { getTranslations } from "next-intl/server";
export const metadata: Metadata = {
title: `Auth - Pangolin`,
title: `Auth - "Pangolin`,
description: ""
};
@@ -23,6 +24,7 @@ export default async function AuthLayout({ children }: AuthLayoutProps) {
const getUser = cache(verifySession);
const user = await getUser();
const t = await getTranslations();
const hideFooter = true;
const licenseStatusRes = await cache(
async () =>
@@ -34,20 +36,18 @@ export default async function AuthLayout({ children }: AuthLayoutProps) {
return (
<div className="h-full flex flex-col">
{user && (
<UserProvider user={user}>
<div className="p-3 ml-auto">
<ProfileIcon />
</div>
</UserProvider>
)}
<div className="flex justify-end items-center p-3 space-x-2">
<ThemeSwitcher />
</div>
<div className="flex-1 flex items-center justify-center">
<div className="w-full max-w-md p-3">{children}</div>
</div>
{!(
licenseStatus.isHostLicensed && licenseStatus.isLicenseValid
hideFooter || (
licenseStatus.isHostLicensed &&
licenseStatus.isLicenseValid)
) && (
<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">
@@ -73,7 +73,7 @@ export default async function AuthLayout({ children }: AuthLayoutProps) {
aria-label="GitHub"
className="flex items-center space-x-2 whitespace-nowrap"
>
<span>{t('communityEdition')}</span>
<span>{t("communityEdition")}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"

View File

@@ -54,10 +54,10 @@ export default async function Page(props: {
<div className="flex flex-col items-center">
<Mail className="w-12 h-12 mb-4 text-primary" />
<h2 className="text-2xl font-bold mb-2 text-center">
{t('inviteAlready')}
{t("inviteAlready")}
</h2>
<p className="text-center">
{t('inviteAlreadyDescription')}
{t("inviteAlreadyDescription")}
</p>
</div>
</div>
@@ -67,7 +67,7 @@ export default async function Page(props: {
{(!signUpDisabled || isInvite) && (
<p className="text-center text-muted-foreground mt-4">
{t('authNoAccount')}{" "}
{t("authNoAccount")}{" "}
<Link
href={
!redirectUrl
@@ -76,7 +76,7 @@ export default async function Page(props: {
}
className="underline"
>
{t('signup')}
{t("signup")}
</Link>
</p>
)}

View File

@@ -54,12 +54,14 @@ export type ResetPasswordFormProps = {
emailParam?: string;
tokenParam?: string;
redirect?: string;
quickstart?: boolean;
};
export default function ResetPasswordForm({
emailParam,
tokenParam,
redirect
redirect,
quickstart
}: ResetPasswordFormProps) {
const router = useRouter();
@@ -184,17 +186,63 @@ export default function ResetPasswordForm({
return;
}
setSuccessMessage(t('passwordResetSuccess'));
setSuccessMessage(quickstart ? t('accountSetupSuccess') : t('passwordResetSuccess'));
setTimeout(() => {
if (redirect) {
const safe = cleanRedirect(redirect);
router.push(safe);
} else {
router.push("/login");
// Auto-login after successful password reset
try {
const loginRes = await api.post("/auth/login", {
email: form.getValues("email"),
password: form.getValues("password")
});
if (loginRes.data.data?.codeRequested) {
if (redirect) {
router.push(`/auth/login?redirect=${redirect}`);
} else {
router.push("/auth/login");
}
return;
}
setIsSubmitting(false);
}, 1500);
if (loginRes.data.data?.emailVerificationRequired) {
try {
await api.post("/auth/verify-email/request");
} catch (verificationError) {
console.error("Failed to send verification code:", verificationError);
}
if (redirect) {
router.push(`/auth/verify-email?redirect=${redirect}`);
} else {
router.push("/auth/verify-email");
}
return;
}
// Login successful, redirect
setTimeout(() => {
if (redirect) {
const safe = cleanRedirect(redirect);
router.push(safe);
} else {
router.push("/");
}
setIsSubmitting(false);
}, 1500);
} catch (loginError) {
// Auto-login failed, but password reset was successful
console.error("Auto-login failed:", loginError);
setTimeout(() => {
if (redirect) {
const safe = cleanRedirect(redirect);
router.push(safe);
} else {
router.push("/login");
}
setIsSubmitting(false);
}, 1500);
}
}
}
@@ -202,9 +250,14 @@ export default function ResetPasswordForm({
<div>
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>{t('passwordReset')}</CardTitle>
<CardTitle>
{quickstart ? t('completeAccountSetup') : t('passwordReset')}
</CardTitle>
<CardDescription>
{t('passwordResetDescription')}
{quickstart
? t('completeAccountSetupDescription')
: t('passwordResetDescription')
}
</CardDescription>
</CardHeader>
<CardContent>
@@ -229,7 +282,10 @@ export default function ResetPasswordForm({
</FormControl>
<FormMessage />
<FormDescription>
{t('passwordResetSent')}
{quickstart
? t('accountSetupSent')
: t('passwordResetSent')
}
</FormDescription>
</FormItem>
)}
@@ -269,7 +325,10 @@ export default function ResetPasswordForm({
render={({ field }) => (
<FormItem>
<FormLabel>
{t('passwordResetCode')}
{quickstart
? t('accountSetupCode')
: t('passwordResetCode')
}
</FormLabel>
<FormControl>
<Input
@@ -279,7 +338,10 @@ export default function ResetPasswordForm({
</FormControl>
<FormMessage />
<FormDescription>
{t('passwordResetCodeDescription')}
{quickstart
? t('accountSetupCodeDescription')
: t('passwordResetCodeDescription')
}
</FormDescription>
</FormItem>
)}
@@ -292,7 +354,10 @@ export default function ResetPasswordForm({
render={({ field }) => (
<FormItem>
<FormLabel>
{t('passwordNew')}
{quickstart
? t('passwordCreate')
: t('passwordNew')
}
</FormLabel>
<FormControl>
<Input
@@ -310,7 +375,10 @@ export default function ResetPasswordForm({
render={({ field }) => (
<FormItem>
<FormLabel>
{t('passwordNewConfirm')}
{quickstart
? t('passwordCreateConfirm')
: t('passwordNewConfirm')
}
</FormLabel>
<FormControl>
<Input
@@ -407,7 +475,7 @@ export default function ResetPasswordForm({
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
{state === "reset"
? t('passwordReset')
? (quickstart ? t('completeSetup') : t('passwordReset'))
: t('pincodeSubmit2')}
</Button>
)}
@@ -422,7 +490,10 @@ export default function ResetPasswordForm({
{isSubmitting && (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
{t('passwordResetSubmit')}
{quickstart
? t('accountSetupSubmit')
: t('passwordResetSubmit')
}
</Button>
)}

View File

@@ -13,6 +13,7 @@ export default async function Page(props: {
redirect: string | undefined;
email: string | undefined;
token: string | undefined;
quickstart?: string | undefined;
}>;
}) {
const searchParams = await props.searchParams;
@@ -35,6 +36,9 @@ export default async function Page(props: {
redirect={searchParams.redirect}
tokenParam={searchParams.token}
emailParam={searchParams.email}
quickstart={
searchParams.quickstart === "true" ? true : undefined
}
/>
<p className="text-center text-muted-foreground mt-4">
@@ -46,7 +50,7 @@ export default async function Page(props: {
}
className="underline"
>
{t('loginBack')}
{t("loginBack")}
</Link>
</p>
</>

View File

@@ -43,6 +43,7 @@ import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import Link from "next/link";
import Image from "next/image";
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
import { useTranslations } from "next-intl";
@@ -185,8 +186,8 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
setOtpState("otp_sent");
submitOtpForm.setValue("email", values.email);
toast({
title: t('otpEmailSent'),
description: t('otpEmailSentDescription')
title: t("otpEmailSent"),
description: t("otpEmailSentDescription")
});
return;
}
@@ -202,7 +203,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
.catch((e) => {
console.error(e);
setWhitelistError(
formatAxiosError(e, t('otpEmailErrorAuthenticate'))
formatAxiosError(e, t("otpEmailErrorAuthenticate"))
);
})
.then(() => setLoadingLogin(false));
@@ -227,7 +228,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
.catch((e) => {
console.error(e);
setPincodeError(
formatAxiosError(e, t('pincodeErrorAuthenticate'))
formatAxiosError(e, t("pincodeErrorAuthenticate"))
);
})
.then(() => setLoadingLogin(false));
@@ -255,7 +256,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
.catch((e) => {
console.error(e);
setPasswordError(
formatAxiosError(e, t('passwordErrorAuthenticate'))
formatAxiosError(e, t("passwordErrorAuthenticate"))
);
})
.finally(() => setLoadingLogin(false));
@@ -276,30 +277,25 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
}
}
function getTitle() {
return t("authenticationRequired");
}
function getSubtitle(resourceName: string) {
return numMethods > 1
? t("authenticationMethodChoose", { name: props.resource.name })
: t("authenticationRequest", { name: props.resource.name });
}
return (
<div>
{!accessDenied ? (
<div>
<div className="text-center mb-2">
<span className="text-sm text-muted-foreground">
{t('poweredBy')}{" "}
<Link
href="https://github.com/fosrl/pangolin"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
Pangolin
</Link>
</span>
</div>
<Card>
<CardHeader>
<CardTitle>{t('authenticationRequired')}</CardTitle>
<CardTitle>{getTitle()}</CardTitle>
<CardDescription>
{numMethods > 1
? t('authenticationMethodChoose', {name: props.resource.name})
: t('authenticationRequest', {name: props.resource.name})}
{getSubtitle(props.resource.name)}
</CardDescription>
</CardHeader>
<CardContent>
@@ -329,19 +325,19 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
{props.methods.password && (
<TabsTrigger value="password">
<Key className="w-4 h-4 mr-1" />{" "}
{t('password')}
{t("password")}
</TabsTrigger>
)}
{props.methods.sso && (
<TabsTrigger value="sso">
<User className="w-4 h-4 mr-1" />{" "}
{t('user')}
{t("user")}
</TabsTrigger>
)}
{props.methods.whitelist && (
<TabsTrigger value="whitelist">
<AtSign className="w-4 h-4 mr-1" />{" "}
{t('email')}
{t("email")}
</TabsTrigger>
)}
</TabsList>
@@ -364,7 +360,9 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('pincodeInput')}
{t(
"pincodeInput"
)}
</FormLabel>
<FormControl>
<div className="flex justify-center">
@@ -433,7 +431,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
disabled={loadingLogin}
>
<LockIcon className="w-4 h-4 mr-2" />
{t('pincodeSubmit')}
{t("pincodeSubmit")}
</Button>
</form>
</Form>
@@ -459,7 +457,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('password')}
{t("password")}
</FormLabel>
<FormControl>
<Input
@@ -487,7 +485,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
disabled={loadingLogin}
>
<LockIcon className="w-4 h-4 mr-2" />
{t('passwordSubmit')}
{t("passwordSubmit")}
</Button>
</form>
</Form>
@@ -528,7 +526,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('email')}
{t("email")}
</FormLabel>
<FormControl>
<Input
@@ -537,7 +535,9 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
/>
</FormControl>
<FormDescription>
{t('otpEmailDescription')}
{t(
"otpEmailDescription"
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -559,7 +559,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
disabled={loadingLogin}
>
<Send className="w-4 h-4 mr-2" />
{t('otpEmailSend')}
{t("otpEmailSend")}
</Button>
</form>
</Form>
@@ -581,7 +581,9 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
render={({ field }) => (
<FormItem>
<FormLabel>
{t('otpEmail')}
{t(
"otpEmail"
)}
</FormLabel>
<FormControl>
<Input
@@ -609,7 +611,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
disabled={loadingLogin}
>
<LockIcon className="w-4 h-4 mr-2" />
{t('otpEmailSubmit')}
{t("otpEmailSubmit")}
</Button>
<Button
@@ -621,7 +623,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
submitOtpForm.reset();
}}
>
{t('backToEmail')}
{t("backToEmail")}
</Button>
</form>
</Form>
@@ -634,7 +636,7 @@ export default function ResourceAuthPortal(props: ResourceAuthPortalProps) {
{supporterStatus?.visible && (
<div className="text-center mt-2">
<span className="text-sm text-muted-foreground opacity-50">
{t('noSupportKey')}
{t("noSupportKey")}
</span>
</div>
)}

View File

@@ -57,7 +57,9 @@ export default function SignupForm({
}: SignupFormProps) {
const router = useRouter();
const api = createApiClient(useEnvContext());
const { env } = useEnvContext();
const api = createApiClient({ env });
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@@ -116,8 +118,8 @@ export default function SignupForm({
}
return (
<Card className="w-full max-w-md">
<CardHeader>
<Card className="w-full max-w-md shadow-md">
<CardHeader className="border-b">
<div className="flex flex-row items-center justify-center">
<Image
src={`/logo/pangolin_orange.svg`}
@@ -135,7 +137,7 @@ export default function SignupForm({
</p>
</div>
</CardHeader>
<CardContent>
<CardContent className="pt-6">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
@@ -161,10 +163,7 @@ export default function SignupForm({
<FormItem>
<FormLabel>{t('password')}</FormLabel>
<FormControl>
<Input
type="password"
{...field}
/>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -177,10 +176,7 @@ export default function SignupForm({
<FormItem>
<FormLabel>{t('confirmPassword')}</FormLabel>
<FormControl>
<Input
type="password"
{...field}
/>
<Input type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>