mirror of
https://github.com/Afilmory/afilmory
synced 2026-04-25 07:15:36 +00:00
refactor: improve header and user menu components with responsive plan badge
- Updated the Header component to conditionally render the PlanBadge for larger screens. - Enhanced the UserMenu component to display a loading state for the plan badge on mobile and link to the plan page when available. - Introduced new props in UserMenu for better plan management and localization support. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -52,13 +52,20 @@ export function Header() {
|
|||||||
{/* Right side - User Menu */}
|
{/* Right side - User Menu */}
|
||||||
{user && (
|
{user && (
|
||||||
<div className="border-fill-tertiary/50 ml-2 sm:ml-auto flex items-center gap-3 border-l pl-2 sm:pl-4">
|
<div className="border-fill-tertiary/50 ml-2 sm:ml-auto flex items-center gap-3 border-l pl-2 sm:pl-4">
|
||||||
<PlanBadge
|
<div className="hidden md:block">
|
||||||
label={planLabel}
|
<PlanBadge
|
||||||
isLoading={planQuery.isLoading}
|
label={planLabel}
|
||||||
onClick={() => navigate('/plan')}
|
isLoading={planQuery.isLoading}
|
||||||
labelKey="header.plan.badge"
|
onClick={() => navigate('/plan')}
|
||||||
|
labelKey="header.plan.badge"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<UserMenu
|
||||||
|
user={user}
|
||||||
|
planLabel={planLabel}
|
||||||
|
planLabelKey="header.plan.badge"
|
||||||
|
planLoading={planQuery.isLoading}
|
||||||
/>
|
/>
|
||||||
<UserMenu user={user} />
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { clsxm } from '@afilmory/utils'
|
import { clsxm } from '@afilmory/utils'
|
||||||
import { LogOut, Settings, User as UserIcon } from 'lucide-react'
|
import { LogOut, Settings, User as UserIcon } from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link } from 'react-router'
|
import { Link } from 'react-router'
|
||||||
|
|
||||||
import { usePageRedirect } from '~/hooks/usePageRedirect'
|
import { usePageRedirect } from '~/hooks/usePageRedirect'
|
||||||
@@ -16,12 +17,16 @@ import type { BetterAuthUser } from '~/modules/auth/types'
|
|||||||
|
|
||||||
interface UserMenuProps {
|
interface UserMenuProps {
|
||||||
user: BetterAuthUser
|
user: BetterAuthUser
|
||||||
|
planLabel?: string | null
|
||||||
|
planLabelKey?: I18nKeys
|
||||||
|
planLoading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserMenu({ user }: UserMenuProps) {
|
export function UserMenu({ user, planLabel, planLabelKey = 'header.plan.badge', planLoading }: UserMenuProps) {
|
||||||
const { logout } = usePageRedirect()
|
const { logout } = usePageRedirect()
|
||||||
const [isLoggingOut, setIsLoggingOut] = useState(false)
|
const [isLoggingOut, setIsLoggingOut] = useState(false)
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
if (isLoggingOut) return
|
if (isLoggingOut) return
|
||||||
@@ -84,6 +89,27 @@ export function UserMenu({ user }: UserMenuProps) {
|
|||||||
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
|
{/* Plan badge for mobile */}
|
||||||
|
{planLoading && (
|
||||||
|
<DropdownMenuItem className="md:hidden" disabled>
|
||||||
|
<div className="flex w-full items-center justify-between text-xs text-text-tertiary">
|
||||||
|
<span className="font-medium uppercase tracking-wide">{t(planLabelKey)}</span>
|
||||||
|
<span className="bg-fill/30 h-4 w-12 animate-pulse rounded" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!planLoading && planLabel && (
|
||||||
|
<DropdownMenuItem className="md:hidden">
|
||||||
|
<Link to="/plan" className="flex w-full items-center justify-between text-xs">
|
||||||
|
<span className="text-text-tertiary font-medium uppercase tracking-wide">{t(planLabelKey)}</span>
|
||||||
|
<span className="text-text font-semibold capitalize">{planLabel}</span>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(planLoading || planLabel) && <DropdownMenuSeparator className="md:hidden" />}
|
||||||
|
|
||||||
<DropdownMenuItem icon={<UserIcon className="size-4" />}>
|
<DropdownMenuItem icon={<UserIcon className="size-4" />}>
|
||||||
<Link to="/settings/account">Account Settings</Link>
|
<Link to="/settings/account">Account Settings</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FormError, Input, Label } from '@afilmory/ui'
|
import { FormError, Input, Label } from '@afilmory/ui'
|
||||||
import { useStore } from '@tanstack/react-form'
|
import { useStore } from '@tanstack/react-form'
|
||||||
import type { FC, MutableRefObject } from 'react'
|
import type { FC, MutableRefObject } from 'react'
|
||||||
import { useEffect } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import type { useRegistrationForm } from '~/modules/auth/hooks/useRegistrationForm'
|
import type { useRegistrationForm } from '~/modules/auth/hooks/useRegistrationForm'
|
||||||
@@ -34,14 +34,20 @@ export const WorkspaceStep: FC<WorkspaceStepProps> = ({
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const slugValue = useStore(form.store, (state) => state.values.tenantSlug)
|
const slugValue = useStore(form.store, (state) => state.values.tenantSlug)
|
||||||
const tenantNameValue = useStore(form.store, (state) => state.values.tenantName)
|
const tenantNameValue = useStore(form.store, (state) => state.values.tenantName)
|
||||||
|
const tenantNameAutofilledRef = useRef(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tenantNameValue || !slugValue) {
|
if (tenantNameAutofilledRef.current || !slugValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (tenantNameValue) {
|
||||||
|
tenantNameAutofilledRef.current = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const derivedName = titleCaseFromSlug(slugValue)
|
const derivedName = titleCaseFromSlug(slugValue)
|
||||||
if (derivedName) {
|
if (derivedName) {
|
||||||
form.setFieldValue('tenantName', () => derivedName)
|
form.setFieldValue('tenantName', () => derivedName)
|
||||||
|
tenantNameAutofilledRef.current = true
|
||||||
}
|
}
|
||||||
}, [form, slugValue, tenantNameValue])
|
}, [form, slugValue, tenantNameValue])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user