Enhance Authentication Flow in Catalog Views

- Added redirect functionality to the LoginForm component, allowing for dynamic redirection after login.
- Updated PublicInternetConfigure and PublicSimConfigure views to utilize modals for user authentication, improving user experience by avoiding full-page redirects.
- Enhanced PublicInternetPlans and PublicSimPlans views with updated button labels for clarity and consistency.
- Refactored plan configuration views to display detailed plan information and prompt users for account creation or login, streamlining the onboarding process.
This commit is contained in:
barsa 2025-12-19 16:33:53 +09:00
parent ab429f91dc
commit 90fa9443d4
8 changed files with 591 additions and 51 deletions

View File

@ -0,0 +1,140 @@
"use client";
import { useState, useEffect } from "react";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { SignupForm } from "../SignupForm/SignupForm";
import { LoginForm } from "../LoginForm/LoginForm";
import { useAuthStore } from "../../services/auth.store";
import { useRouter } from "next/navigation";
interface AuthModalProps {
isOpen: boolean;
onClose: () => void;
initialMode?: "signup" | "login";
redirectTo?: string;
title?: string;
description?: string;
showCloseButton?: boolean;
}
export function AuthModal({
isOpen,
onClose,
initialMode = "signup",
redirectTo,
title,
description,
showCloseButton = true,
}: AuthModalProps) {
const [mode, setMode] = useState<"signup" | "login">(initialMode);
const router = useRouter();
const isAuthenticated = useAuthStore(state => state.isAuthenticated);
const hasCheckedAuth = useAuthStore(state => state.hasCheckedAuth);
// Update mode when initialMode changes
useEffect(() => {
setMode(initialMode);
}, [initialMode]);
// Close modal and redirect when authenticated
useEffect(() => {
if (isOpen && hasCheckedAuth && isAuthenticated && redirectTo) {
onClose();
router.push(redirectTo);
}
}, [isOpen, hasCheckedAuth, isAuthenticated, redirectTo, onClose, router]);
if (!isOpen) return null;
const defaultTitle = mode === "signup" ? "Create your account" : "Sign in to continue";
const defaultDescription =
mode === "signup"
? "Create an account to continue with your order and access personalized plans."
: "Sign in to your account to continue with your order.";
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4"
onClick={e => {
if (e.target === e.currentTarget) {
onClose();
}
}}
>
{/* Backdrop */}
<div
className="absolute inset-0 bg-background/80 backdrop-blur-sm transition-opacity"
aria-hidden="true"
/>
{/* Modal */}
<div
className="relative z-10 w-full max-w-lg rounded-2xl border border-border bg-card text-card-foreground shadow-[var(--cp-shadow-3)] max-h-[90vh] overflow-y-auto"
onClick={e => e.stopPropagation()}
>
<div className="sticky top-0 z-10 flex items-center justify-between border-b border-border bg-card px-6 py-4">
<div className="flex-1">
<h2 className="text-xl font-semibold text-foreground">{title || defaultTitle}</h2>
<p className="mt-1 text-sm text-muted-foreground">
{description || defaultDescription}
</p>
</div>
{showCloseButton && (
<button
onClick={onClose}
className="ml-4 text-muted-foreground transition-colors hover:text-foreground"
aria-label="Close modal"
type="button"
>
<XMarkIcon className="h-5 w-5" />
</button>
)}
</div>
<div className="p-6">
{mode === "signup" ? (
<SignupForm
redirectTo={redirectTo}
onSuccess={() => {
// Will be handled by useEffect above
}}
/>
) : (
<LoginForm
redirectTo={redirectTo}
onSuccess={() => {
// Will be handled by useEffect above
}}
showSignupLink={false}
/>
)}
{/* Toggle between signup and login */}
<div className="mt-6 border-t border-border pt-6 text-center">
{mode === "signup" ? (
<p className="text-sm text-muted-foreground">
Already have an account?{" "}
<button
onClick={() => setMode("login")}
className="font-medium text-primary hover:underline transition-colors"
>
Sign in
</button>
</p>
) : (
<p className="text-sm text-muted-foreground">
Don&apos;t have an account?{" "}
<button
onClick={() => setMode("signup")}
className="font-medium text-primary hover:underline transition-colors"
>
Create one
</button>
</p>
)}
</div>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1 @@
export { AuthModal } from "./AuthModal";

View File

@ -21,6 +21,7 @@ interface LoginFormProps {
showSignupLink?: boolean;
showForgotPasswordLink?: boolean;
className?: string;
redirectTo?: string;
}
/**
@ -41,10 +42,11 @@ export function LoginForm({
showSignupLink = true,
showForgotPasswordLink = true,
className = "",
redirectTo,
}: LoginFormProps) {
const searchParams = useSearchParams();
const { login, loading, error, clearError } = useLogin();
const redirect = searchParams?.get("next") || searchParams?.get("redirect");
const redirect = redirectTo || searchParams?.get("next") || searchParams?.get("redirect");
const redirectQuery = redirect ? `?redirect=${encodeURIComponent(redirect)}` : "";
const handleLogin = useCallback(

View File

@ -1,48 +1,193 @@
"use client";
import { useState } from "react";
import { useSearchParams } from "next/navigation";
import { ServerIcon, CheckIcon } from "@heroicons/react/24/outline";
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
import { Button } from "@/components/atoms/button";
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
import { CatalogHero } from "@/features/catalog/components/base/CatalogHero";
import { useShopBasePath } from "@/features/catalog/hooks/useShopBasePath";
import { SignupForm } from "@/features/auth/components/SignupForm/SignupForm";
import { useInternetPlan } from "@/features/catalog/hooks";
import { AuthModal } from "@/features/auth/components/AuthModal/AuthModal";
import { CardPricing } from "@/features/catalog/components/base/CardPricing";
import { Skeleton } from "@/components/atoms/loading-skeleton";
/**
* Public Internet Configure View
*
* Public shop is browse-only. Users must create an account so we can verify internet availability
* for their service address before showing personalized plans and allowing configuration.
* Shows selected plan information and prompts for authentication via modal.
* Much better UX than redirecting to a full signup page.
*/
export function PublicInternetConfigureView() {
const shopBasePath = useShopBasePath();
const searchParams = useSearchParams();
const planSku = searchParams?.get("planSku");
const { plan, isLoading } = useInternetPlan(planSku || undefined);
const [authModalOpen, setAuthModalOpen] = useState(false);
const [authMode, setAuthMode] = useState<"signup" | "login">("signup");
const redirectTo = planSku
? `/account/shop/internet/configure?planSku=${encodeURIComponent(planSku)}`
: "/account/shop/internet";
if (isLoading) {
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<CatalogBackLink href={`${shopBasePath}/internet`} label="Back to Internet plans" />
<div className="mt-8 space-y-6">
<Skeleton className="h-10 w-96" />
<Skeleton className="h-32 w-full" />
</div>
</div>
);
}
if (!plan) {
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<CatalogBackLink href={`${shopBasePath}/internet`} label="Back to Internet plans" />
<AlertBanner variant="error" title="Plan not found">
The selected plan could not be found. Please go back and select a plan.
</AlertBanner>
</div>
);
}
return (
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<CatalogBackLink href={`${shopBasePath}/internet`} label="Back to Internet plans" />
<CatalogHero
title="Step 1: Create your account"
description="Well verify service availability for your address, then show personalized internet plans and configuration options."
title="Get started with your internet plan"
description="Create an account to verify service availability for your address and configure your plan."
/>
<AlertBanner variant="info" title="Already have an account?" className="max-w-2xl mx-auto">
<div className="space-y-3 text-sm text-foreground/80">
<div className="flex flex-col sm:flex-row gap-3">
{/* Plan Summary Card */}
<div className="mt-8 bg-card border border-border rounded-2xl p-6 md:p-8 shadow-[var(--cp-shadow-1)]">
<div className="flex items-start gap-4 mb-6">
<div className="flex-shrink-0">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 border border-primary/20">
<ServerIcon className="h-6 w-6 text-primary" />
</div>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-xl font-semibold text-foreground mb-2">{plan.name}</h3>
{plan.description && (
<p className="text-sm text-muted-foreground mb-4">{plan.description}</p>
)}
<div className="flex flex-wrap gap-2 mb-4">
{plan.internetPlanTier && (
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20">
{plan.internetPlanTier} Tier
</span>
)}
{plan.internetOfferingType && (
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-muted text-muted-foreground border border-border">
{plan.internetOfferingType}
</span>
)}
</div>
<div className="mt-4">
<CardPricing
monthlyPrice={plan.monthlyPrice}
oneTimePrice={plan.oneTimePrice}
size="lg"
alignment="left"
/>
</div>
</div>
</div>
{/* Plan Features */}
{plan.catalogMetadata?.features && plan.catalogMetadata.features.length > 0 && (
<div className="border-t border-border pt-6 mt-6">
<h4 className="text-sm font-semibold text-foreground mb-4 uppercase tracking-wide">
Plan Includes:
</h4>
<ul className="grid grid-cols-1 md:grid-cols-2 gap-3">
{plan.catalogMetadata.features.slice(0, 6).map((feature, index) => {
const [label, detail] = feature.split(":");
return (
<li key={index} className="flex items-start gap-2">
<CheckIcon className="h-4 w-4 text-success mt-0.5 flex-shrink-0" />
<span className="text-sm text-muted-foreground">
{label.trim()}
{detail && (
<>
: <span className="text-foreground font-medium">{detail.trim()}</span>
</>
)}
</span>
</li>
);
})}
</ul>
</div>
)}
</div>
{/* Auth Prompt */}
<div className="mt-8 bg-muted/50 border border-border rounded-2xl p-6 md:p-8">
<div className="text-center mb-6">
<h3 className="text-lg font-semibold text-foreground mb-2">Ready to get started?</h3>
<p className="text-sm text-muted-foreground max-w-2xl mx-auto">
Create an account to verify service availability for your address and complete your
order. We&apos;ll guide you through the setup process.
</p>
</div>
<div className="flex flex-col sm:flex-row gap-3 justify-center max-w-md mx-auto">
<Button
as="a"
href={`/auth/login?redirect=${encodeURIComponent("/account/shop/internet")}`}
className="whitespace-nowrap"
onClick={() => {
setAuthMode("signup");
setAuthModalOpen(true);
}}
className="flex-1"
>
Create account
</Button>
<Button
onClick={() => {
setAuthMode("login");
setAuthModalOpen(true);
}}
variant="outline"
className="flex-1"
>
Sign in
</Button>
</div>
</div>
</AlertBanner>
<div className="mt-8 bg-card border border-border rounded-2xl p-6 md:p-7 shadow-[var(--cp-shadow-1)]">
<SignupForm redirectTo="/account/shop/internet" />
<div className="mt-6 pt-6 border-t border-border">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-center">
<div>
<div className="text-sm font-medium text-foreground mb-1">Verify Availability</div>
<div className="text-xs text-muted-foreground">Check service at your address</div>
</div>
<div>
<div className="text-sm font-medium text-foreground mb-1">Personalized Plans</div>
<div className="text-xs text-muted-foreground">See plans tailored to you</div>
</div>
<div>
<div className="text-sm font-medium text-foreground mb-1">Secure Ordering</div>
<div className="text-xs text-muted-foreground">Complete your order safely</div>
</div>
</div>
</div>
</div>
</div>
<AuthModal
isOpen={authModalOpen}
onClose={() => setAuthModalOpen(false)}
initialMode={authMode}
redirectTo={redirectTo}
title="Create your account"
description="We'll verify service availability for your address, then show personalized internet plans and configuration options."
/>
</>
);
}

View File

@ -129,7 +129,7 @@ export function PublicInternetPlansView() {
</div>
<div className="flex flex-col sm:flex-row gap-3 pt-2">
<Button as="a" href="/auth/signup" className="whitespace-nowrap">
Create account to check availability
Get started
</Button>
<Button as="a" href="/auth/login" variant="outline" className="whitespace-nowrap">
Sign in
@ -149,8 +149,8 @@ export function PublicInternetPlansView() {
disabled={false}
pricingPrefix="Starting from"
action={{
label: "Create account",
href: `/auth/signup?redirect=${encodeURIComponent("/account/shop/internet")}`,
label: "Get started",
href: `/shop/internet/configure?planSku=${encodeURIComponent(plan.sku)}`,
}}
/>
</div>

View File

@ -1,52 +1,207 @@
"use client";
import { useState } from "react";
import { useSearchParams } from "next/navigation";
import { DevicePhoneMobileIcon, CheckIcon } from "@heroicons/react/24/outline";
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
import { Button } from "@/components/atoms/button";
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
import { CatalogHero } from "@/features/catalog/components/base/CatalogHero";
import { useShopBasePath } from "@/features/catalog/hooks/useShopBasePath";
import { useSearchParams } from "next/navigation";
import { SignupForm } from "@/features/auth/components/SignupForm/SignupForm";
import { useSimPlan } from "@/features/catalog/hooks";
import { AuthModal } from "@/features/auth/components/AuthModal/AuthModal";
import { CardPricing } from "@/features/catalog/components/base/CardPricing";
import { Skeleton } from "@/components/atoms/loading-skeleton";
/**
* Public SIM Configure View
*
* Public shop is browse-only. Users must create an account to add a payment method and
* complete identity verification before ordering SIM service.
* Shows selected plan information and prompts for authentication via modal.
* Much better UX than redirecting to a full signup page.
*/
export function PublicSimConfigureView() {
const shopBasePath = useShopBasePath();
const searchParams = useSearchParams();
const plan = searchParams?.get("planSku") || undefined;
const redirectTarget = plan ? `/account/shop/sim/configure?planSku=${plan}` : "/account/shop/sim";
const planSku = searchParams?.get("planSku");
const { plan, isLoading } = useSimPlan(planSku || undefined);
const [authModalOpen, setAuthModalOpen] = useState(false);
const [authMode, setAuthMode] = useState<"signup" | "login">("signup");
const redirectTarget = planSku
? `/account/shop/sim/configure?planSku=${encodeURIComponent(planSku)}`
: "/account/shop/sim";
if (isLoading) {
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<CatalogBackLink href={`${shopBasePath}/sim`} label="Back to SIM plans" />
<div className="mt-8 space-y-6">
<Skeleton className="h-10 w-96" />
<Skeleton className="h-32 w-full" />
</div>
</div>
);
}
if (!plan) {
return (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<CatalogBackLink href={`${shopBasePath}/sim`} label="Back to SIM plans" />
<AlertBanner variant="error" title="Plan not found">
The selected plan could not be found. Please go back and select a plan.
</AlertBanner>
</div>
);
}
return (
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
<CatalogBackLink href={`${shopBasePath}/sim`} label="Back to SIM plans" />
<CatalogHero
title="Step 1: Create your account"
description="Ordering requires a payment method and identity verification."
title="Get started with your SIM plan"
description="Create an account to complete your order, add a payment method, and complete identity verification."
/>
<AlertBanner variant="info" title="Already have an account?" className="max-w-2xl mx-auto">
<div className="space-y-3 text-sm text-foreground/80">
<div className="flex flex-col sm:flex-row gap-3">
{/* Plan Summary Card */}
<div className="mt-8 bg-card border border-border rounded-2xl p-6 md:p-8 shadow-[var(--cp-shadow-1)]">
<div className="flex items-start gap-4 mb-6">
<div className="flex-shrink-0">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10 border border-primary/20">
<DevicePhoneMobileIcon className="h-6 w-6 text-primary" />
</div>
</div>
<div className="flex-1 min-w-0">
<h3 className="text-xl font-semibold text-foreground mb-2">{plan.name}</h3>
{plan.description && (
<p className="text-sm text-muted-foreground mb-4">{plan.description}</p>
)}
<div className="flex flex-wrap gap-2 mb-4">
{plan.simPlanType && (
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-primary/10 text-primary border border-primary/20">
{plan.simPlanType}
</span>
)}
</div>
<div className="mt-4">
<CardPricing
monthlyPrice={plan.monthlyPrice}
oneTimePrice={plan.oneTimePrice}
size="lg"
alignment="left"
/>
</div>
</div>
</div>
{/* Plan Details */}
{(plan.simDataSize || plan.description) && (
<div className="border-t border-border pt-6 mt-6">
<h4 className="text-sm font-semibold text-foreground mb-4 uppercase tracking-wide">
Plan Details:
</h4>
<ul className="grid grid-cols-1 md:grid-cols-2 gap-3">
{plan.simDataSize && (
<li className="flex items-start gap-2">
<CheckIcon className="h-4 w-4 text-success mt-0.5 flex-shrink-0" />
<span className="text-sm text-muted-foreground">
Data: <span className="text-foreground font-medium">{plan.simDataSize}</span>
</span>
</li>
)}
{plan.simPlanType && (
<li className="flex items-start gap-2">
<CheckIcon className="h-4 w-4 text-success mt-0.5 flex-shrink-0" />
<span className="text-sm text-muted-foreground">
Type: <span className="text-foreground font-medium">{plan.simPlanType}</span>
</span>
</li>
)}
{plan.simHasFamilyDiscount && (
<li className="flex items-start gap-2">
<CheckIcon className="h-4 w-4 text-success mt-0.5 flex-shrink-0" />
<span className="text-sm text-muted-foreground">
<span className="text-foreground font-medium">Family Discount Available</span>
</span>
</li>
)}
{plan.billingCycle && (
<li className="flex items-start gap-2">
<CheckIcon className="h-4 w-4 text-success mt-0.5 flex-shrink-0" />
<span className="text-sm text-muted-foreground">
Billing:{" "}
<span className="text-foreground font-medium">{plan.billingCycle}</span>
</span>
</li>
)}
</ul>
</div>
)}
</div>
{/* Auth Prompt */}
<div className="mt-8 bg-muted/50 border border-border rounded-2xl p-6 md:p-8">
<div className="text-center mb-6">
<h3 className="text-lg font-semibold text-foreground mb-2">Ready to order?</h3>
<p className="text-sm text-muted-foreground max-w-2xl mx-auto">
Create an account to complete your SIM order. You&apos;ll need to add a payment method
and complete identity verification.
</p>
</div>
<div className="flex flex-col sm:flex-row gap-3 justify-center max-w-md mx-auto">
<Button
as="a"
href={`/auth/login?redirect=${encodeURIComponent(redirectTarget)}`}
className="whitespace-nowrap"
onClick={() => {
setAuthMode("signup");
setAuthModalOpen(true);
}}
className="flex-1"
>
Create account
</Button>
<Button
onClick={() => {
setAuthMode("login");
setAuthModalOpen(true);
}}
variant="outline"
className="flex-1"
>
Sign in
</Button>
</div>
</div>
</AlertBanner>
<div className="mt-8 bg-card border border-border rounded-2xl p-6 md:p-7 shadow-[var(--cp-shadow-1)]">
<SignupForm redirectTo={redirectTarget} />
<div className="mt-6 pt-6 border-t border-border">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-center">
<div>
<div className="text-sm font-medium text-foreground mb-1">Secure Payment</div>
<div className="text-xs text-muted-foreground">Add payment method safely</div>
</div>
<div>
<div className="text-sm font-medium text-foreground mb-1">
Identity Verification
</div>
<div className="text-xs text-muted-foreground">Complete verification process</div>
</div>
<div>
<div className="text-sm font-medium text-foreground mb-1">Order Management</div>
<div className="text-xs text-muted-foreground">Track your order status</div>
</div>
</div>
</div>
</div>
</div>
<AuthModal
isOpen={authModalOpen}
onClose={() => setAuthModalOpen(false)}
initialMode={authMode}
redirectTo={redirectTarget}
title="Create your account"
description="Ordering requires a payment method and identity verification."
/>
</>
);
}

View File

@ -38,8 +38,7 @@ export function PublicSimPlansView() {
"data-voice"
);
const buildRedirect = (planSku?: string) => {
const target = planSku ? `/account/shop/sim/configure?planSku=${planSku}` : "/account/shop/sim";
return `/auth/signup?redirect=${encodeURIComponent(target)}`;
return planSku ? `/shop/sim/configure?planSku=${encodeURIComponent(planSku)}` : "/shop/sim";
};
if (isLoading) {
@ -120,8 +119,8 @@ export function PublicSimPlansView() {
verification.
</p>
<div className="flex gap-3 sm:ml-auto">
<Button as="a" href={buildRedirect()} size="sm" className="whitespace-nowrap">
Create account
<Button as="a" href="/auth/signup" size="sm" className="whitespace-nowrap">
Get started
</Button>
<Button
as="a"

View File

@ -0,0 +1,98 @@
# Shop Experience UX Improvements
## Overview
The shop experience has been significantly improved to be more user-friendly and less disruptive. Instead of redirecting users to a full signup page when they want to configure a plan, we now show plan context and use a modal for authentication.
## Key Improvements
### 1. **AuthModal Component**
- **Location**: `apps/portal/src/features/auth/components/AuthModal/`
- **Purpose**: Reusable modal component for signup/login that doesn't break the shopping flow
- **Features**:
- Overlay design that keeps users in context
- Toggle between signup and login modes
- Automatic redirect after successful authentication
- Customizable title and description
- Responsive design
### 2. **Improved Configure Pages**
Both `PublicInternetConfigureView` and `PublicSimConfigureView` now:
- **Show plan information** before requiring authentication
- Plan name, description, pricing
- Plan features and badges
- Visual plan summary card
- **Use modal authentication** instead of full-page redirects
- Users stay on the configure page
- Can see what they're signing up for
- Better context preservation
- **Better loading and error states**
- Skeleton loaders while fetching plan data
- Clear error messages if plan not found
### 3. **Improved Plan Card CTAs**
- Changed button labels from "Create account" to "Get started" (more inviting)
- Plan cards now link to configure pages instead of directly to signup
- Configure pages show plan context before requiring auth
### 4. **Better User Flow**
**Before:**
1. User browses plans
2. Clicks "Create account" → Redirected to full signup page
3. Loses context of what plan they selected
4. Must navigate back after signup
**After:**
1. User browses plans
2. Clicks "Get started" → Goes to configure page
3. Sees plan details and context
4. Clicks "Create account" → Modal opens
5. Completes signup/login in modal
6. Automatically redirected to authenticated configure page
7. Never loses context
## Technical Details
### Components Created
- `AuthModal` - Reusable authentication modal
- Updated `PublicInternetConfigureView` - Shows plan context with modal auth
- Updated `PublicSimConfigureView` - Shows plan context with modal auth
### Components Updated
- `LoginForm` - Added `redirectTo` prop for modal usage
- `PublicInternetPlans` - Improved CTAs and redirects
- `PublicSimPlans` - Improved CTAs and redirects
### Key Features
- **Context Preservation**: Users always see what plan they're configuring
- **Progressive Disclosure**: Plan details shown before requiring authentication
- **Non-Blocking**: Modal can be closed to continue browsing
- **Seamless Flow**: Automatic redirect after authentication maintains user intent
## Benefits
1. **Better UX**: Users don't lose context when authenticating
2. **Higher Conversion**: Less friction in the signup process
3. **Clearer Intent**: Users see what they're signing up for
4. **Professional Feel**: Modal-based auth feels more modern
5. **Flexible**: Easy to reuse AuthModal in other parts of the app
## Future Enhancements
Potential improvements for the future:
- Add "Continue as guest" option (if business logic allows)
- Show more plan details before auth (pricing breakdown, installation options)
- Add plan comparison before auth
- Remember selected plan in localStorage for returning visitors
- Add social login options in the modal