Refactor user information handling and improve the post-login experience.
* **Login Flow & User Profile:**
* Introduced `UserInfoService` to centralize user data storage and retrieval from local storage, replacing direct `localStorage` access for user `person` data.
* Modified the `LoginPage` to use `userInfoService.updateUserInfo()` after successful OTP verification.
* Implemented immediate fetching of the full user profile (`fetchUserProfile`) after login to ensure up-to-date user details.
* Adjusted post-login navigation logic: users with a complete profile (indicated by `userInfo?.username`) are now directed to the campaigns page, while those with incomplete profiles are guided to the profile page for completion.
* **UI/UX Improvements:**
* The `CustomRadio` component now displays its `value` as a fallback if no `label` is explicitly provided, improving usability for radio buttons.
* Adjusted styling for form field labels in `DynamicForm` to include a bottom margin, enhancing visual separation and readability.
108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import { cn } from "@core/lib/utils";
|
|
import * as React from "react";
|
|
|
|
export interface CustomRadioProps {
|
|
id: string;
|
|
name: string;
|
|
value: string;
|
|
label: string;
|
|
checked?: boolean;
|
|
onChange?: (value: string) => void;
|
|
variant?: "primary" | "info" | "error";
|
|
disabled?: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
const CustomRadio = React.forwardRef<HTMLInputElement, CustomRadioProps>(
|
|
(
|
|
{
|
|
id,
|
|
name,
|
|
value,
|
|
label,
|
|
checked,
|
|
onChange,
|
|
variant = "primary",
|
|
disabled,
|
|
error,
|
|
},
|
|
ref
|
|
) => {
|
|
const finalVariant = error ? "error" : variant;
|
|
|
|
return (
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center gap-3">
|
|
<div className="relative flex items-center">
|
|
<input
|
|
type="radio"
|
|
id={id}
|
|
name={name}
|
|
value={value}
|
|
checked={checked}
|
|
onChange={(e) => onChange?.(e.target.value)}
|
|
disabled={disabled}
|
|
ref={ref}
|
|
className="peer h-5 w-5 cursor-pointer appearance-none rounded-full border-2 transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-50"
|
|
style={{
|
|
borderColor: disabled
|
|
? "#d1d5db"
|
|
: finalVariant === "primary"
|
|
? "#2563eb"
|
|
: finalVariant === "info"
|
|
? "#0891b2"
|
|
: "#ef4444",
|
|
}}
|
|
/>
|
|
<div
|
|
className={cn(
|
|
"pointer-events-none absolute left-1/2 top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full opacity-0 transition-all duration-200 peer-checked:opacity-100",
|
|
{
|
|
"bg-blue-600": finalVariant === "primary" && !disabled,
|
|
"bg-cyan-600": finalVariant === "info" && !disabled,
|
|
"bg-red-600": finalVariant === "error" && !disabled,
|
|
"bg-gray-400": disabled,
|
|
}
|
|
)}
|
|
/>
|
|
</div>
|
|
<label
|
|
htmlFor={id}
|
|
className={cn(
|
|
"cursor-pointer text-sm font-medium transition-colors",
|
|
disabled
|
|
? "cursor-not-allowed text-gray-400"
|
|
: "text-foreground hover:text-foreground/80"
|
|
)}
|
|
>
|
|
{label ?? value}
|
|
</label>
|
|
</div>
|
|
{error && (
|
|
<p className="ml-8 text-sm text-red-600 flex items-center gap-1">
|
|
<svg
|
|
className="h-4 w-4"
|
|
fill="none"
|
|
strokeWidth="2"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
|
|
/>
|
|
</svg>
|
|
{error}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
CustomRadio.displayName = "CustomRadio";
|
|
|
|
export { CustomRadio };
|