mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-20 20:06:39 +00:00
Pull up downstream changes
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user