feat: refactor authentication translations and improve loading indicators
- Moved common authentication messages to a new `common` namespace in English and Chinese translation files for better organization. - Updated `LoginPage`, `RegisterPage`, and various form components to utilize the new translation keys. - Enhanced loading indicators in `ForgotPasswordForm`, `LoginForm`, `RegisterForm`, and `ResetPasswordForm` components for improved user feedback. - Refactored `SocialLoginButton` and `NavbarMobile` components to utilize the current user context for better user experience.
This commit is contained in:
parent
043016e08d
commit
db7d86851a
@ -149,10 +149,6 @@
|
||||
"signInWithGitHub": "Sign In with GitHub",
|
||||
"showPassword": "Show password",
|
||||
"hidePassword": "Hide password",
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"byClickingContinue": "By clicking continue, you agree to our ",
|
||||
"and": " and ",
|
||||
"or": "Or continue with"
|
||||
},
|
||||
"register": {
|
||||
@ -187,6 +183,12 @@
|
||||
"tryAgain": "Please try again.",
|
||||
"backToLogin": "Back to login",
|
||||
"checkEmail": "Please check your email inbox"
|
||||
},
|
||||
"common": {
|
||||
"termsOfService": "Terms of Service",
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"byClickingContinue": "By clicking continue, you agree to our ",
|
||||
"and": " and "
|
||||
}
|
||||
},
|
||||
"BlogPage": {
|
||||
|
@ -145,11 +145,7 @@
|
||||
"signInWithGitHub": "使用 GitHub 登录",
|
||||
"showPassword": "显示密码",
|
||||
"hidePassword": "隐藏密码",
|
||||
"termsOfService": "服务条款",
|
||||
"privacyPolicy": "隐私政策",
|
||||
"byClickingContinue": "继续即表示您同意我们的 ",
|
||||
"and": " 和 ",
|
||||
"or": "或"
|
||||
"or": "或"
|
||||
},
|
||||
"register": {
|
||||
"title": "注册",
|
||||
@ -183,6 +179,12 @@
|
||||
"tryAgain": "请重试",
|
||||
"backToLogin": "返回登录",
|
||||
"checkEmail": "请检查您的邮箱"
|
||||
},
|
||||
"common": {
|
||||
"termsOfService": "服务条款",
|
||||
"privacyPolicy": "隐私政策",
|
||||
"byClickingContinue": "继续即表示您同意我们的 ",
|
||||
"and": " 和 "
|
||||
}
|
||||
},
|
||||
"BlogPage": {
|
||||
|
@ -24,7 +24,7 @@ export async function generateMetadata({
|
||||
}
|
||||
|
||||
export default async function LoginPage() {
|
||||
const t = await getTranslations('AuthPage.login');
|
||||
const t = await getTranslations('AuthPage.common');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { RegisterForm } from '@/components/auth/register-form';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
|
||||
import { Routes } from '@/routes';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
@ -10,10 +12,10 @@ export async function generateMetadata({
|
||||
}: {
|
||||
params: Promise<{ locale: Locale }>;
|
||||
}): Promise<Metadata | undefined> {
|
||||
const {locale} = await params;
|
||||
const t = await getTranslations({locale, namespace: 'Metadata'});
|
||||
const pt = await getTranslations({locale, namespace: 'AuthPage.register'});
|
||||
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'Metadata' });
|
||||
const pt = await getTranslations({ locale, namespace: 'AuthPage.register' });
|
||||
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: t('description'),
|
||||
@ -22,5 +24,27 @@ export async function generateMetadata({
|
||||
}
|
||||
|
||||
export default async function RegisterPage() {
|
||||
return <RegisterForm />;
|
||||
const t = await getTranslations('AuthPage.common');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<RegisterForm />
|
||||
<div className="text-balance text-center text-xs text-muted-foreground">
|
||||
{t('byClickingContinue')}
|
||||
<LocaleLink
|
||||
href={Routes.TermsOfService}
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
{t('termsOfService')}
|
||||
</LocaleLink>{' '}
|
||||
{t('and')}{' '}
|
||||
<LocaleLink
|
||||
href={Routes.PrivacyPolicy}
|
||||
className="underline underline-offset-4 hover:text-primary"
|
||||
>
|
||||
{t('privacyPolicy')}
|
||||
</LocaleLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -113,10 +113,8 @@ export const ForgotPasswordForm = ({ className }: { className?: string }) => {
|
||||
type="submit"
|
||||
className="w-full flex items-center justify-center gap-2"
|
||||
>
|
||||
{isPending ? (
|
||||
<Icons.spinner className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
''
|
||||
{isPending && (
|
||||
<Icons.spinner className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
<span>{t('send')}</span>
|
||||
</Button>
|
||||
|
@ -175,10 +175,8 @@ export const LoginForm = ({ className }: { className?: string }) => {
|
||||
type="submit"
|
||||
className="w-full flex items-center justify-center gap-2 cursor-pointer"
|
||||
>
|
||||
{isPending ? (
|
||||
<Icons.spinner className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
''
|
||||
{isPending && (
|
||||
<Icons.spinner className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
<span>{t('signIn')}</span>
|
||||
</Button>
|
||||
|
@ -174,10 +174,8 @@ export const RegisterForm = () => {
|
||||
type="submit"
|
||||
className="cursor-pointer w-full flex items-center justify-center gap-2"
|
||||
>
|
||||
{isPending ? (
|
||||
<Icons.spinner className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
''
|
||||
{isPending && (
|
||||
<Icons.spinner className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
<span>{t('signUp')}</span>
|
||||
</Button>
|
||||
|
@ -148,10 +148,8 @@ export const ResetPasswordForm = () => {
|
||||
type="submit"
|
||||
className="w-full flex items-center justify-center gap-2"
|
||||
>
|
||||
{isPending ? (
|
||||
<Icons.spinner className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
''
|
||||
{isPending && (
|
||||
<Icons.spinner className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
<span>{t('reset')}</span>
|
||||
</Button>
|
||||
|
@ -15,10 +15,10 @@ import { DividerWithText } from '@/components/auth/divider-with-text';
|
||||
* social login buttons
|
||||
*/
|
||||
export const SocialLoginButton = () => {
|
||||
const t = useTranslations('AuthPage.login');
|
||||
const searchParams = useSearchParams();
|
||||
const callbackUrl = searchParams.get('callbackUrl');
|
||||
const [isLoading, setIsLoading] = useState<'google' | 'github' | null>(null);
|
||||
const t = useTranslations('AuthPage.login');
|
||||
|
||||
const onClick = async (provider: 'google' | 'github') => {
|
||||
await authClient.signIn.social(
|
||||
|
@ -10,8 +10,8 @@ import {
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import { getMenuLinks } from '@/config';
|
||||
import { useCurrentUser } from '@/hooks/use-current-user';
|
||||
import { LocaleLink, useLocalePathname } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { Portal } from '@radix-ui/react-portal';
|
||||
@ -24,19 +24,18 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { RemoveScroll } from 'react-remove-scroll';
|
||||
import { UserButton } from './user-button';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function NavbarMobile({
|
||||
className,
|
||||
...other
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
const t = useTranslations();
|
||||
const [open, setOpen] = React.useState<boolean>(false);
|
||||
const localePathname = useLocalePathname();
|
||||
const { data: session, error } = authClient.useSession();
|
||||
const user = session?.user;
|
||||
const t = useTranslations();
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChangeStart = () => {
|
||||
@ -80,10 +79,10 @@ export function NavbarMobile({
|
||||
</span>
|
||||
</LocaleLink>
|
||||
|
||||
{/* navbar right shows menu icon */}
|
||||
{/* navbar right shows menu icon and user button */}
|
||||
<div className="flex items-center justify-end gap-4">
|
||||
{/* show user button if user is logged in */}
|
||||
{user ? <UserButton user={user} /> : null}
|
||||
{currentUser ? <UserButton user={currentUser} /> : null}
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
@ -110,7 +109,7 @@ export function NavbarMobile({
|
||||
page will scroll when we scroll the mobile menu */}
|
||||
<RemoveScroll allowPinchZoom enabled>
|
||||
<MainMobileMenu
|
||||
userLoggedIn={!!user}
|
||||
userLoggedIn={!!currentUser}
|
||||
onLinkClicked={handleToggleMobileMenu}
|
||||
/>
|
||||
</RemoveScroll>
|
||||
@ -267,6 +266,7 @@ function MainMobileMenu({ userLoggedIn, onLinkClicked }: MainMobileMenuProps) {
|
||||
>
|
||||
{subItem.title}
|
||||
</span>
|
||||
{/* hide description for now */}
|
||||
{/* {subItem.description && (
|
||||
<p
|
||||
className={cn(
|
||||
|
@ -21,7 +21,9 @@ interface CheckoutButtonProps {
|
||||
* Checkout Button
|
||||
*
|
||||
* This client component creates a Stripe checkout session and redirects to it
|
||||
* It's used to initiate the checkout process for a specific plan and price
|
||||
* It's used to initiate the checkout process for a specific plan and price.
|
||||
*
|
||||
* NOTICE: Login is required when using this button.
|
||||
*/
|
||||
export function CheckoutButton({
|
||||
planId,
|
||||
@ -71,7 +73,7 @@ export function CheckoutButton({
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin" />
|
||||
{t('loading')}
|
||||
</>
|
||||
) : (
|
||||
|
@ -69,7 +69,7 @@ export function CustomerPortalButton({
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin" />
|
||||
{t('loading')}
|
||||
</>
|
||||
) : (
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { useCurrentUser } from '@/hooks/use-current-user';
|
||||
import { formatPrice } from '@/lib/formatter';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { PaymentType, PaymentTypes, PlanInterval, PlanIntervals, Price, PricePlan } from '@/payment/types';
|
||||
@ -59,6 +60,7 @@ export function PricingCard({
|
||||
}: PricingCardProps) {
|
||||
const t = useTranslations('PricingPage.PricingCard');
|
||||
const price = getPriceForPlan(plan, interval, paymentType);
|
||||
const currentUser = useCurrentUser();
|
||||
|
||||
// generate formatted price and price label
|
||||
let formattedPrice = '';
|
||||
@ -115,18 +117,24 @@ export function PricingCard({
|
||||
<CardDescription className="text-sm">{plan.description}</CardDescription>
|
||||
|
||||
{plan.isFree ? (
|
||||
<LoginWrapper mode="modal" asChild>
|
||||
<Button variant="outline" className="mt-4 w-full cursor-pointer">
|
||||
currentUser ? (
|
||||
<Button variant="outline" className="mt-4 w-full disabled">
|
||||
{t('getStartedForFree')}
|
||||
</Button>
|
||||
</LoginWrapper>
|
||||
) : (
|
||||
<LoginWrapper mode="modal" asChild>
|
||||
<Button variant="outline" className="mt-4 w-full cursor-pointer">
|
||||
{t('getStartedForFree')}
|
||||
</Button>
|
||||
</LoginWrapper>
|
||||
)
|
||||
) : isCurrentPlan ? (
|
||||
<Button disabled className="mt-4 w-full bg-blue-100 dark:bg-blue-800
|
||||
text-blue-700 dark:text-blue-100 hover:bg-blue-100 dark:hover:bg-blue-800 border border-blue-200 dark:border-blue-700">
|
||||
{t('yourCurrentPlan')}
|
||||
</Button>
|
||||
) : isPaidPlan ? (
|
||||
<LoginWrapper mode="modal" asChild>
|
||||
currentUser ? (
|
||||
<CheckoutButton
|
||||
planId={plan.id}
|
||||
priceId={price.productId}
|
||||
@ -135,7 +143,13 @@ export function PricingCard({
|
||||
>
|
||||
{plan.isLifetime ? t('getLifetimeAccess') : t('getStarted')}
|
||||
</CheckoutButton>
|
||||
</LoginWrapper>
|
||||
) : (
|
||||
<LoginWrapper mode="modal" asChild>
|
||||
<Button variant="default" className="mt-4 w-full cursor-pointer">
|
||||
{t('getStarted')}
|
||||
</Button>
|
||||
</LoginWrapper>
|
||||
)
|
||||
) : (
|
||||
<Button disabled className="mt-4 w-full">
|
||||
{t('notAvailable')}
|
||||
|
@ -182,9 +182,7 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
|
||||
<FormControl>
|
||||
<div className="relative flex items-center">
|
||||
{isLoading && (
|
||||
<div className="mr-2">
|
||||
<Loader2Icon className="h-4 w-4 animate-spin text-primary" />
|
||||
</div>
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin text-primary" />
|
||||
)}
|
||||
<Switch
|
||||
checked={field.value}
|
||||
|
Loading…
Reference in New Issue
Block a user