feat(landing): multi-type user model with primary selection
- Users can now have multiple account types simultaneously - Added canBePrimary() to prevent User type being primary when other types exist - DevUserSwitcher: multi-select checkboxes with star buttons for primary - ProfilePage: type management with toggle and primary selection - UserMenu: shows primary type with +N badge for additional types - Auto-switches primary away from User when adding declared types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e527115c6a
commit
b40b3f6fe9
6 changed files with 409 additions and 103 deletions
|
|
@ -1,4 +1,5 @@
|
|||
/* Dev User Switcher - Only visible in development mode */
|
||||
/* Supports multi-type selection with primary type */
|
||||
|
||||
.dev-user-switcher {
|
||||
position: fixed;
|
||||
|
|
@ -60,7 +61,7 @@
|
|||
position: absolute;
|
||||
bottom: calc(100% + 8px);
|
||||
left: 0;
|
||||
width: 280px;
|
||||
width: 320px;
|
||||
background: rgba(20, 20, 25, 0.98);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
|
|
@ -119,34 +120,118 @@
|
|||
box-shadow: 0 0 4px rgba(34, 197, 94, 0.5);
|
||||
}
|
||||
|
||||
/* Quick actions */
|
||||
.dev-user-switcher-actions {
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.dev-user-action {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: rgba(168, 85, 247, 0.2);
|
||||
border: 1px solid rgba(168, 85, 247, 0.3);
|
||||
border-radius: 6px;
|
||||
color: #a855f7;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.dev-user-action:hover {
|
||||
background: rgba(168, 85, 247, 0.3);
|
||||
border-color: rgba(168, 85, 247, 0.5);
|
||||
}
|
||||
|
||||
.dev-user-action--danger {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
border-color: rgba(239, 68, 68, 0.3);
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.dev-user-action--danger:hover {
|
||||
background: rgba(239, 68, 68, 0.3);
|
||||
border-color: rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
/* Section label */
|
||||
.dev-user-switcher-section-label {
|
||||
padding: 10px 16px 6px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.dev-user-switcher-hint {
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* Options list */
|
||||
.dev-user-switcher-options {
|
||||
padding: 8px;
|
||||
padding: 4px 8px 8px;
|
||||
}
|
||||
|
||||
.dev-user-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
gap: 4px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.dev-user-option:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
.dev-user-option:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dev-user-option.active {
|
||||
background: rgba(168, 85, 247, 0.15);
|
||||
border-color: rgba(168, 85, 247, 0.3);
|
||||
background: rgba(168, 85, 247, 0.1);
|
||||
}
|
||||
|
||||
/* Toggle button (checkbox + icon + content) */
|
||||
.dev-user-option-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
border-radius: 8px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.dev-user-option-toggle:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
.dev-user-option-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.dev-user-option-checkbox.checked {
|
||||
background: #a855f7;
|
||||
border-color: #a855f7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dev-user-option-icon {
|
||||
|
|
@ -171,10 +256,34 @@
|
|||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.dev-user-option-check {
|
||||
color: #a855f7;
|
||||
font-weight: bold;
|
||||
/* Primary star button */
|
||||
.dev-user-option-primary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.dev-user-option-primary:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.dev-user-option-primary.is-primary {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.dev-user-option-primary:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
|
|
@ -205,5 +314,6 @@
|
|||
.dev-user-switcher-panel {
|
||||
left: auto;
|
||||
right: 0;
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,12 @@
|
|||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { Star } from 'lucide-react'
|
||||
|
||||
import { useDevUser, DEV_USER_TYPES, type DevUserType } from '../contexts/DevUserContext'
|
||||
import { useDevUser, getUserTypeEmoji, DEV_USER_TYPES, type DevUserType } from '../contexts'
|
||||
import './DevUserSwitcher.css'
|
||||
|
||||
/** Get icon for each user type */
|
||||
function getUserTypeIcon(type: DevUserType): string {
|
||||
switch (type) {
|
||||
case 'guest':
|
||||
return '👻'
|
||||
case 'registered-user':
|
||||
return '👤'
|
||||
case 'registered-provider':
|
||||
return '🎭'
|
||||
case 'registered-client':
|
||||
return '💜'
|
||||
case 'registered-investor':
|
||||
return '💎'
|
||||
}
|
||||
}
|
||||
|
||||
/** Get short label for each user type */
|
||||
function getShortLabel(type: DevUserType): string {
|
||||
switch (type) {
|
||||
case 'guest':
|
||||
return 'Guest'
|
||||
case 'registered-user':
|
||||
return 'User'
|
||||
case 'registered-provider':
|
||||
|
|
@ -38,8 +21,6 @@ function getShortLabel(type: DevUserType): string {
|
|||
/** Get description for each user type */
|
||||
function getDescription(type: DevUserType): string {
|
||||
switch (type) {
|
||||
case 'guest':
|
||||
return 'Not authenticated'
|
||||
case 'registered-user':
|
||||
return 'Registered via shop, no declared intent'
|
||||
case 'registered-provider':
|
||||
|
|
@ -53,10 +34,23 @@ function getDescription(type: DevUserType): string {
|
|||
|
||||
/**
|
||||
* Dev-only floating switcher for simulating different user types
|
||||
* Supports multi-type selection with primary type
|
||||
* Only renders in development mode
|
||||
*/
|
||||
export default function DevUserSwitcher() {
|
||||
const { userType, setUserType, isDevMode, isAuthenticated, hasDeclaredIntent } = useDevUser()
|
||||
const {
|
||||
userTypes,
|
||||
primaryType,
|
||||
isDevMode,
|
||||
isAuthenticated,
|
||||
hasDeclaredIntent,
|
||||
toggleType,
|
||||
setPrimaryType,
|
||||
signOut,
|
||||
signInAsUser,
|
||||
hasType,
|
||||
canBePrimary,
|
||||
} = useDevUser()
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
|
||||
const toggleExpanded = useCallback(() => {
|
||||
|
|
@ -81,6 +75,9 @@ export default function DevUserSwitcher() {
|
|||
// Don't render in production
|
||||
if (!isDevMode) return null
|
||||
|
||||
const displayLabel = primaryType ? getShortLabel(primaryType) : 'Guest'
|
||||
const displayEmoji = getUserTypeEmoji(primaryType)
|
||||
|
||||
return (
|
||||
<div className="dev-user-switcher" data-expanded={isExpanded}>
|
||||
{/* Toggle button */}
|
||||
|
|
@ -88,11 +85,11 @@ export default function DevUserSwitcher() {
|
|||
className="dev-user-switcher-toggle"
|
||||
onClick={toggleExpanded}
|
||||
title="Dev User Switcher"
|
||||
aria-label={`Dev user switcher - current: ${getShortLabel(userType)}`}
|
||||
aria-label={`Dev user switcher - current: ${displayLabel}`}
|
||||
aria-expanded={isExpanded}
|
||||
>
|
||||
<span className="dev-user-switcher-icon">{getUserTypeIcon(userType)}</span>
|
||||
<span className="dev-user-switcher-label">{getShortLabel(userType)}</span>
|
||||
<span className="dev-user-switcher-icon">{displayEmoji}</span>
|
||||
<span className="dev-user-switcher-label">{displayLabel}</span>
|
||||
<span className="dev-user-switcher-badge">DEV</span>
|
||||
</button>
|
||||
|
||||
|
|
@ -103,33 +100,66 @@ export default function DevUserSwitcher() {
|
|||
<h3>Dev User Switcher</h3>
|
||||
<div className="dev-user-switcher-status">
|
||||
<span className={`dev-user-status-dot ${isAuthenticated ? 'authenticated' : 'guest'}`} />
|
||||
{isAuthenticated ? 'Authenticated' : 'Guest'}
|
||||
{isAuthenticated ? `${userTypes.length} type${userTypes.length !== 1 ? 's' : ''}` : 'Guest'}
|
||||
{hasDeclaredIntent && ' • Intent declared'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="dev-user-switcher-options">
|
||||
{DEV_USER_TYPES.map((type) => (
|
||||
<button
|
||||
key={type}
|
||||
className={`dev-user-option ${type === userType ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
setUserType(type)
|
||||
setIsExpanded(false)
|
||||
}}
|
||||
role="menuitem"
|
||||
aria-current={type === userType ? 'true' : undefined}
|
||||
>
|
||||
<span className="dev-user-option-icon">{getUserTypeIcon(type)}</span>
|
||||
<div className="dev-user-option-content">
|
||||
<span className="dev-user-option-label">{getShortLabel(type)}</span>
|
||||
<span className="dev-user-option-description">{getDescription(type)}</span>
|
||||
</div>
|
||||
{type === userType && (
|
||||
<span className="dev-user-option-check">✓</span>
|
||||
)}
|
||||
{/* Quick actions */}
|
||||
<div className="dev-user-switcher-actions">
|
||||
{isAuthenticated ? (
|
||||
<button className="dev-user-action dev-user-action--danger" onClick={signOut}>
|
||||
Sign Out
|
||||
</button>
|
||||
))}
|
||||
) : (
|
||||
<button className="dev-user-action" onClick={signInAsUser}>
|
||||
Sign In as User
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="dev-user-switcher-section-label">
|
||||
Account Types
|
||||
<span className="dev-user-switcher-hint">(click to toggle, star to set primary)</span>
|
||||
</div>
|
||||
|
||||
<div className="dev-user-switcher-options">
|
||||
{DEV_USER_TYPES.map((type) => {
|
||||
const isActive = hasType(type)
|
||||
const isPrimary = primaryType === type
|
||||
|
||||
return (
|
||||
<div key={type} className={`dev-user-option ${isActive ? 'active' : ''}`}>
|
||||
{/* Checkbox area - click to toggle type */}
|
||||
<button
|
||||
className="dev-user-option-toggle"
|
||||
onClick={() => toggleType(type)}
|
||||
role="menuitemcheckbox"
|
||||
aria-checked={isActive}
|
||||
>
|
||||
<span className={`dev-user-option-checkbox ${isActive ? 'checked' : ''}`}>
|
||||
{isActive && '✓'}
|
||||
</span>
|
||||
<span className="dev-user-option-icon">{getUserTypeEmoji(type)}</span>
|
||||
<div className="dev-user-option-content">
|
||||
<span className="dev-user-option-label">{getShortLabel(type)}</span>
|
||||
<span className="dev-user-option-description">{getDescription(type)}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Star button - click to set as primary */}
|
||||
<button
|
||||
className={`dev-user-option-primary ${isPrimary ? 'is-primary' : ''}`}
|
||||
onClick={() => setPrimaryType(type)}
|
||||
disabled={!canBePrimary(type)}
|
||||
title={isPrimary ? 'Primary type' : !canBePrimary(type) ? 'Cannot be primary' : 'Set as primary'}
|
||||
aria-label={isPrimary ? 'Primary type' : `Set ${getShortLabel(type)} as primary`}
|
||||
>
|
||||
<Star size={16} fill={isPrimary ? 'currentColor' : 'none'} />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="dev-user-switcher-footer">
|
||||
|
|
|
|||
|
|
@ -55,6 +55,41 @@
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.user-menu__badge {
|
||||
padding: 2px 6px;
|
||||
background: rgba(168, 85, 247, 0.3);
|
||||
border-radius: 10px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
/* Multiple types display */
|
||||
.user-menu__types {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 8px 16px 12px;
|
||||
}
|
||||
|
||||
.user-menu__type-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.user-menu__type-badge--primary {
|
||||
background: rgba(168, 85, 247, 0.15);
|
||||
border-color: rgba(168, 85, 247, 0.3);
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
.user-menu__chevron {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ interface DevUserContextValue extends DevUserState {
|
|||
toggleType: (type: DevUserType) => void
|
||||
/** Check if user has a specific type */
|
||||
hasType: (type: DevUserType) => boolean
|
||||
/** Check if a type can be set as primary (User can only be primary if it's the only type) */
|
||||
canBePrimary: (type: DevUserType) => boolean
|
||||
/** Sign out - clear all types */
|
||||
signOut: () => void
|
||||
/** Sign in as guest (registered-user only) */
|
||||
|
|
@ -163,7 +165,12 @@ export function DevUserProvider({ children }: { children: ReactNode }) {
|
|||
if (prev.types.includes(type)) return prev
|
||||
const newTypes = [...prev.types, type]
|
||||
// If this is the first type, make it primary
|
||||
const newPrimary = prev.primary || type
|
||||
// Also, if current primary is 'registered-user' and we're adding a declared type,
|
||||
// auto-switch primary to the new type (User shouldn't be primary when other types exist)
|
||||
let newPrimary = prev.primary || type
|
||||
if (prev.primary === 'registered-user' && type !== 'registered-user') {
|
||||
newPrimary = type
|
||||
}
|
||||
return { types: newTypes, primary: newPrimary }
|
||||
})
|
||||
}, [isDevMode])
|
||||
|
|
@ -188,13 +195,19 @@ export function DevUserProvider({ children }: { children: ReactNode }) {
|
|||
const newTypes = prev.types.filter(t => t !== type)
|
||||
let newPrimary = prev.primary
|
||||
if (prev.primary === type) {
|
||||
newPrimary = newTypes[0] || null
|
||||
// Pick a new primary, preferring non-User types
|
||||
const declaredTypes = newTypes.filter(t => t !== 'registered-user')
|
||||
newPrimary = declaredTypes[0] || newTypes[0] || null
|
||||
}
|
||||
return { types: newTypes, primary: newPrimary }
|
||||
} else {
|
||||
// Add it
|
||||
const newTypes = [...prev.types, type]
|
||||
const newPrimary = prev.primary || type
|
||||
// Auto-switch from User to the new type if adding a declared type
|
||||
let newPrimary = prev.primary || type
|
||||
if (prev.primary === 'registered-user' && type !== 'registered-user') {
|
||||
newPrimary = type
|
||||
}
|
||||
return { types: newTypes, primary: newPrimary }
|
||||
}
|
||||
})
|
||||
|
|
@ -208,6 +221,10 @@ export function DevUserProvider({ children }: { children: ReactNode }) {
|
|||
// Auto-add the type if setting as primary
|
||||
return { types: [...prev.types, type], primary: type }
|
||||
}
|
||||
// User type can only be primary if it's the only type
|
||||
if (type === 'registered-user' && prev.types.length > 1) {
|
||||
return prev // Don't allow setting User as primary when other types exist
|
||||
}
|
||||
return { ...prev, primary: type }
|
||||
})
|
||||
}, [isDevMode])
|
||||
|
|
@ -216,6 +233,16 @@ export function DevUserProvider({ children }: { children: ReactNode }) {
|
|||
return types.includes(type)
|
||||
}, [types])
|
||||
|
||||
// User type can only be primary if it's the ONLY type selected
|
||||
const canBePrimary = useCallback((type: DevUserType): boolean => {
|
||||
if (!types.includes(type)) return false
|
||||
// registered-user can only be primary if there are no other types
|
||||
if (type === 'registered-user') {
|
||||
return types.length === 1
|
||||
}
|
||||
return true
|
||||
}, [types])
|
||||
|
||||
const signOut = useCallback(() => {
|
||||
if (!isDevMode) return
|
||||
setUserData({ types: [], primary: null })
|
||||
|
|
@ -235,6 +262,7 @@ export function DevUserProvider({ children }: { children: ReactNode }) {
|
|||
toggleType,
|
||||
setPrimaryType,
|
||||
hasType,
|
||||
canBePrimary,
|
||||
signOut,
|
||||
signInAsUser,
|
||||
isDevMode,
|
||||
|
|
|
|||
|
|
@ -52,45 +52,130 @@
|
|||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
/* No Types State */
|
||||
.profile-no-types {
|
||||
padding: 1.5rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px dashed rgba(255, 255, 255, 0.15);
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profile-no-types p {
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
/* Active Types Summary */
|
||||
.profile-active-types {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.profile-active-type {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: color-mix(in srgb, var(--type-color) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--type-color) 30%, transparent);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.profile-active-type--primary {
|
||||
background: color-mix(in srgb, var(--type-color) 15%, transparent);
|
||||
border-color: var(--type-color);
|
||||
}
|
||||
|
||||
.profile-active-type-emoji {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.profile-active-type-label {
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.profile-active-type-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: var(--type-color);
|
||||
border-radius: 999px;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Type Card */
|
||||
.profile-type-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.25rem;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.profile-type-card:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: var(--type-color, rgba(255, 255, 255, 0.15));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.profile-type-card:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.profile-type-card--current,
|
||||
.profile-type-card--active {
|
||||
background: color-mix(in srgb, var(--type-color) 10%, transparent);
|
||||
border-color: color-mix(in srgb, var(--type-color) 40%, transparent);
|
||||
background: color-mix(in srgb, var(--type-color) 8%, transparent);
|
||||
border-color: color-mix(in srgb, var(--type-color) 30%, transparent);
|
||||
}
|
||||
|
||||
/* Toggle Button (checkbox + icon + info) */
|
||||
.profile-type-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.profile-type-toggle:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
.profile-type-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.profile-type-checkbox.checked {
|
||||
background: var(--type-color);
|
||||
border-color: var(--type-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.profile-type-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: color-mix(in srgb, var(--type-color) 15%, transparent);
|
||||
border-radius: 12px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
background: color-mix(in srgb, var(--type-color) 12%, transparent);
|
||||
border-radius: 10px;
|
||||
color: var(--type-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
@ -102,29 +187,46 @@
|
|||
|
||||
.profile-type-label {
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin-bottom: 0.25rem;
|
||||
margin-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.profile-type-description {
|
||||
display: block;
|
||||
font-size: 0.875rem;
|
||||
font-size: 0.8125rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.profile-type-badge {
|
||||
/* Primary Star Button */
|
||||
.profile-type-primary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: color-mix(in srgb, var(--type-color) 20%, transparent);
|
||||
border-radius: 999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--type-color);
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.profile-type-primary:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.profile-type-primary.is-primary {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.profile-type-primary:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Types Grid */
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export default function ProfilePage() {
|
|||
primaryType,
|
||||
isAuthenticated,
|
||||
hasType,
|
||||
canBePrimary,
|
||||
addType: addUserType,
|
||||
removeType,
|
||||
setPrimaryType,
|
||||
|
|
@ -214,8 +215,8 @@ export default function ProfilePage() {
|
|||
className={`profile-type-primary ${isPrimary ? 'is-primary' : ''}`}
|
||||
onClick={() => handleSetPrimary(type)}
|
||||
onMouseEnter={() => playSound('button-hover')}
|
||||
disabled={!isActive}
|
||||
title={isPrimary ? 'Primary type' : 'Set as primary'}
|
||||
disabled={!canBePrimary(type)}
|
||||
title={isPrimary ? 'Primary type' : !canBePrimary(type) ? 'Cannot be primary when other types exist' : 'Set as primary'}
|
||||
aria-label={isPrimary ? 'Primary type' : `Set ${info.label} as primary`}
|
||||
>
|
||||
<Star size={18} fill={isPrimary ? 'currentColor' : 'none'} />
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue