diff --git a/apps/portal/src/components/common/RouteLoading.tsx b/apps/portal/src/components/common/RouteLoading.tsx index 2d9c0519..abcab5a2 100644 --- a/apps/portal/src/components/common/RouteLoading.tsx +++ b/apps/portal/src/components/common/RouteLoading.tsx @@ -1,23 +1,19 @@ import type { ReactNode } from "react"; import { PageLayout } from "@/components/layout/PageLayout"; +import { PageLoadingState } from "@/components/ui"; interface RouteLoadingProps { icon?: ReactNode; title: string; description?: string; - mode?: "spinner" | "content"; + mode?: "skeleton" | "content"; children?: ReactNode; } // Shared route-level loading wrapper used by segment loading.tsx files -export function RouteLoading({ icon, title, description, mode = "spinner", children }: RouteLoadingProps) { - if (mode === "spinner") { - return ( - - {/* When loading=true, PageLayout renders its own loading UI */} - <> - - ); +export function RouteLoading({ icon, title, description, mode = "skeleton", children }: RouteLoadingProps) { + if (mode === "skeleton") { + return ; } return ( diff --git a/apps/portal/src/components/ui/button.tsx b/apps/portal/src/components/ui/button.tsx index d9b3a966..ed38080f 100644 --- a/apps/portal/src/components/ui/button.tsx +++ b/apps/portal/src/components/ui/button.tsx @@ -2,7 +2,7 @@ import type { AnchorHTMLAttributes, ButtonHTMLAttributes, ReactNode } from "reac import { forwardRef } from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/shared/utils"; -import { LoadingSpinner } from "@/components/ui/loading-spinner"; +// Loading spinner removed - using inline spinner for buttons const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", @@ -76,7 +76,9 @@ const Button = forwardRef((p {...anchorProps} > - {loading ? : leftIcon} + {loading ? ( +
+ ) : leftIcon} {loading ? loadingText ?? children : children} {!loading && rightIcon ? {rightIcon} : null} @@ -94,7 +96,9 @@ const Button = forwardRef((p {...buttonProps} > - {loading ? : leftIcon} + {loading ? ( +
+ ) : leftIcon} {loading ? loadingText ?? children : children} {!loading && rightIcon ? {rightIcon} : null} diff --git a/apps/portal/src/components/ui/index.ts b/apps/portal/src/components/ui/index.ts index 399c2c9f..38bcef96 100644 --- a/apps/portal/src/components/ui/index.ts +++ b/apps/portal/src/components/ui/index.ts @@ -23,8 +23,7 @@ export type { StatusPillProps } from "./status-pill"; export { Badge, badgeVariants } from "./badge"; export type { BadgeProps } from "./badge"; -export { LoadingSpinner, CenteredLoadingSpinner, spinnerVariants } from "./loading-spinner"; -export type { LoadingSpinnerProps } from "./loading-spinner"; +// Loading components consolidated into skeleton loading export { ErrorState, diff --git a/apps/portal/src/components/ui/loading-skeleton.tsx b/apps/portal/src/components/ui/loading-skeleton.tsx index b461250e..2ab84941 100644 --- a/apps/portal/src/components/ui/loading-skeleton.tsx +++ b/apps/portal/src/components/ui/loading-skeleton.tsx @@ -92,31 +92,6 @@ export function LoadingStats({ count = 4 }: { count?: number }) { ); } -export function LoadingSpinner({ - size = "md", - className, -}: { - size?: "sm" | "md" | "lg"; - className?: string; -}) { - const sizeClasses = { - sm: "h-4 w-4", - md: "h-8 w-8", - lg: "h-12 w-12", - }; - - return ( -
-
-
- ); -} - export function PageLoadingState({ title }: { title: string }) { return (
diff --git a/apps/portal/src/components/ui/loading-spinner.tsx b/apps/portal/src/components/ui/loading-spinner.tsx deleted file mode 100644 index 957fd852..00000000 --- a/apps/portal/src/components/ui/loading-spinner.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { forwardRef } from "react"; -import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/shared/utils"; - -const spinnerVariants = cva( - "animate-spin rounded-full border-solid border-current border-r-transparent", - { - variants: { - size: { - xs: "h-3 w-3 border", - sm: "h-4 w-4 border", - default: "h-6 w-6 border-2", - lg: "h-8 w-8 border-2", - xl: "h-12 w-12 border-4", - }, - variant: { - default: "text-blue-600", - white: "text-white", - gray: "text-gray-400", - current: "text-current", - }, - }, - defaultVariants: { - size: "default", - variant: "default", - }, - } -); - -interface LoadingSpinnerProps - extends React.HTMLAttributes, - VariantProps { - label?: string; -} - -const LoadingSpinner = forwardRef( - ({ className, size, variant, label = "Loading...", ...props }, ref) => { - return ( -
-
- {label} -
- ); - } -); -LoadingSpinner.displayName = "LoadingSpinner"; - -// Centered loading spinner for full-page or container loading -const CenteredLoadingSpinner = forwardRef< - HTMLDivElement, - LoadingSpinnerProps & { containerClassName?: string } ->(({ containerClassName, ...props }, ref) => { - return ( -
- -
- ); -}); -CenteredLoadingSpinner.displayName = "CenteredLoadingSpinner"; - -export { LoadingSpinner, CenteredLoadingSpinner, spinnerVariants }; -export type { LoadingSpinnerProps }; diff --git a/apps/portal/src/features/auth/components/PasswordResetForm/PasswordResetForm.tsx b/apps/portal/src/features/auth/components/PasswordResetForm/PasswordResetForm.tsx index aa2c3651..94430359 100644 --- a/apps/portal/src/features/auth/components/PasswordResetForm/PasswordResetForm.tsx +++ b/apps/portal/src/features/auth/components/PasswordResetForm/PasswordResetForm.tsx @@ -12,10 +12,10 @@ import { FormField } from "@/components/common/FormField"; import { usePasswordReset } from "../../hooks/use-auth"; import { useZodForm } from "@/core/forms"; import { - passwordResetRequestSchema, - passwordResetSchema, - type PasswordResetRequestData, - type PasswordResetData + passwordResetRequestFormSchema, + passwordResetFormSchema, + type PasswordResetRequestFormData, + type PasswordResetFormData } from "@customer-portal/domain"; interface PasswordResetFormProps { @@ -39,11 +39,11 @@ export function PasswordResetForm({ // Zod form for password reset request const requestForm = useZodForm({ - schema: passwordResetRequestSchema, + schema: passwordResetRequestFormSchema, initialValues: { email: "" }, onSubmit: async (data) => { try { - await requestPasswordReset(data.email); + await requestPasswordReset(data); onSuccess?.(); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Request failed"; @@ -54,16 +54,11 @@ export function PasswordResetForm({ // Zod form for password reset (with confirm password) const resetForm = useZodForm({ - schema: passwordResetSchema.extend({ - confirmPassword: passwordResetSchema.shape.password, - }).refine((data) => data.password === data.confirmPassword, { - message: "Passwords do not match", - path: ["confirmPassword"], - }), + schema: passwordResetFormSchema, initialValues: { token: token || "", password: "", confirmPassword: "" }, onSubmit: async (data) => { try { - await resetPassword(data.token, data.password); + await resetPassword(data); onSuccess?.(); } catch (err) { const errorMessage = err instanceof Error ? err.message : "Reset failed"; @@ -110,22 +105,22 @@ export function PasswordResetForm({ placeholder="Enter your email" value={requestForm.values.email} onChange={(e) => requestForm.setValue("email", e.target.value)} - onBlur={() => requestForm.setTouched("email", true)} + onBlur={() => requestForm.validate()} disabled={loading || requestForm.isSubmitting} className={requestForm.errors.email ? "border-red-300" : ""} /> - {(error || requestForm.errors._form) && ( + {error && ( - {requestForm.errors._form || error} + {error} )}