Add avatar unlock system with XP tiers
This commit is contained in:
parent
a7ce4d17a1
commit
36d6fbfad9
1 changed files with 60 additions and 33 deletions
|
|
@ -21,24 +21,29 @@ import {
|
||||||
Shield,
|
Shield,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Check,
|
Check,
|
||||||
ArrowLeft
|
ArrowLeft,
|
||||||
|
Lock
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
// Avatar color palette - Jewel Tones (Primary Set)
|
// Avatar color palette - Jewel Tones with XP unlock tiers
|
||||||
const PRESET_COLORS = [
|
const PRESET_COLORS = [
|
||||||
{ value: "#0F4C81", name: "Sapphire" },
|
// Starter Tier (0 XP) - Always unlocked
|
||||||
{ value: "#9B111E", name: "Ruby" },
|
{ value: "#0F4C81", name: "Sapphire", tier: "Starter", xpRequired: 0 },
|
||||||
{ value: "#50C878", name: "Emerald" },
|
{ value: "#9B111E", name: "Ruby", tier: "Starter", xpRequired: 0 },
|
||||||
{ value: "#9966CC", name: "Amethyst" },
|
{ value: "#50C878", name: "Emerald", tier: "Starter", xpRequired: 0 },
|
||||||
{ value: "#0D98BA", name: "Topaz" },
|
{ value: "#9966CC", name: "Amethyst", tier: "Starter", xpRequired: 0 },
|
||||||
{ value: "#E0115F", name: "Rose Quartz" },
|
// Bronze Tier (100 XP)
|
||||||
{ value: "#082567", name: "Lapis" },
|
{ value: "#0D98BA", name: "Topaz", tier: "Bronze", xpRequired: 100 },
|
||||||
{ value: "#FF7518", name: "Carnelian" },
|
{ value: "#E0115F", name: "Rose Quartz", tier: "Bronze", xpRequired: 100 },
|
||||||
{ value: "#006B3C", name: "Jade" },
|
{ value: "#082567", name: "Lapis", tier: "Bronze", xpRequired: 100 },
|
||||||
{ value: "#1C1C1C", name: "Onyx" },
|
{ value: "#FF7518", name: "Carnelian", tier: "Bronze", xpRequired: 100 },
|
||||||
{ value: "#E6E200", name: "Citrine" },
|
// Silver Tier (500 XP)
|
||||||
{ value: "#702963", name: "Garnet" },
|
{ value: "#006B3C", name: "Jade", tier: "Silver", xpRequired: 500 },
|
||||||
|
{ value: "#1C1C1C", name: "Onyx", tier: "Silver", xpRequired: 500 },
|
||||||
|
// Gold Tier (1000 XP)
|
||||||
|
{ value: "#E6E200", name: "Citrine", tier: "Gold", xpRequired: 1000 },
|
||||||
|
{ value: "#702963", name: "Garnet", tier: "Gold", xpRequired: 1000 },
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
|
|
@ -233,6 +238,7 @@ export default function SettingsPage() {
|
||||||
saved={avatarSaved}
|
saved={avatarSaved}
|
||||||
error={avatarError}
|
error={avatarError}
|
||||||
onSave={handleSaveAvatar}
|
onSave={handleSaveAvatar}
|
||||||
|
userXp={(user as any)?.xp || 0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
@ -296,6 +302,7 @@ export default function SettingsPage() {
|
||||||
saved={avatarSaved}
|
saved={avatarSaved}
|
||||||
error={avatarError}
|
error={avatarError}
|
||||||
onSave={handleSaveAvatar}
|
onSave={handleSaveAvatar}
|
||||||
|
userXp={(user as any)?.xp || 0}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|
@ -412,7 +419,8 @@ function AppearanceSection({
|
||||||
saving,
|
saving,
|
||||||
saved,
|
saved,
|
||||||
error,
|
error,
|
||||||
onSave
|
onSave,
|
||||||
|
userXp = 0
|
||||||
}: any) {
|
}: any) {
|
||||||
return (
|
return (
|
||||||
<Card id="appearance">
|
<Card id="appearance">
|
||||||
|
|
@ -422,7 +430,7 @@ function AppearanceSection({
|
||||||
Appearance
|
Appearance
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
Customize how you appear across the site
|
Customize how you appear across the site. Earn XP to unlock more colors!
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-6">
|
<CardContent className="space-y-6">
|
||||||
|
|
@ -455,24 +463,43 @@ function AppearanceSection({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Color Grid */}
|
{/* Color Grid with Tiers */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<Label>Background Color</Label>
|
<Label>Background Color</Label>
|
||||||
|
<span className="text-xs text-muted-foreground">Your XP: {userXp}</span>
|
||||||
|
</div>
|
||||||
<div className="grid grid-cols-6 gap-3">
|
<div className="grid grid-cols-6 gap-3">
|
||||||
{PRESET_COLORS.map((color) => (
|
{PRESET_COLORS.map((color) => {
|
||||||
|
const isLocked = userXp < color.xpRequired
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={color.value}
|
key={color.value}
|
||||||
onClick={() => setAvatarBgColor(color.value)}
|
onClick={() => !isLocked && setAvatarBgColor(color.value)}
|
||||||
className="relative aspect-square rounded-xl transition-all hover:scale-110 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-ring"
|
disabled={isLocked}
|
||||||
|
className={`relative aspect-square rounded-xl transition-all focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-ring ${isLocked
|
||||||
|
? 'opacity-40 cursor-not-allowed'
|
||||||
|
: 'hover:scale-110'
|
||||||
|
}`}
|
||||||
style={{ backgroundColor: color.value }}
|
style={{ backgroundColor: color.value }}
|
||||||
title={color.name}
|
title={isLocked
|
||||||
|
? `${color.name} - Unlock at ${color.xpRequired} XP`
|
||||||
|
: `${color.name} (${color.tier})`
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{avatarBgColor === color.value && (
|
{avatarBgColor === color.value && !isLocked && (
|
||||||
<Check className="absolute inset-0 m-auto h-5 w-5 text-white drop-shadow-lg" />
|
<Check className="absolute inset-0 m-auto h-5 w-5 text-white drop-shadow-lg" />
|
||||||
)}
|
)}
|
||||||
|
{isLocked && (
|
||||||
|
<Lock className="absolute inset-0 m-auto h-4 w-4 text-white/70 drop-shadow-lg" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
)
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Earn XP by attending shows, writing reviews, and rating performances
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
|
|
@ -481,7 +508,7 @@ function AppearanceSection({
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button onClick={onSave} disabled={saving}>
|
<Button onClick={onSave} disabled={saving}>
|
||||||
{saving ? "Saving..." : saved ? "Saved ✓" : "Save Avatar"}
|
{saving ? "Saving..." : saved ? "Saved" : "Save Avatar"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue