yari-garan/src/core/components/base/radio.tsx
MehrdadAdabi 518650ccd5 feat(auth): Enhance login flow and user profile management
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.
2025-11-27 16:25:27 +03:30

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 };