Skip to content

Commit 53aa559

Browse files
committed
Removed bypass onboarding process
1 parent 25e9960 commit 53aa559

5 files changed

Lines changed: 100 additions & 23 deletions

File tree

components/character/character-creator.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
type ColorPreset,
2525
} from "@/lib/character/presets";
2626
import type { LayerColorState, LayerVariantState } from "@/lib/character/types";
27+
import { isVariantUnlocked } from "@/lib/character/variant-access";
2728

2829
function SectionLabel({ children }: { children: React.ReactNode }) {
2930
return (
@@ -84,12 +85,16 @@ export function CharacterCreator({
8485
const activeLayer = getLayerById(activeLayerId);
8586
const activeColor = colors[activeLayerId];
8687
const ownedSet = new Set(ownedVariantIds);
87-
const pieceVariants = activeLayer.allowVariants
88-
? activeLayer.variants.filter(
88+
const pieceVariants = activeLayer.allowVariants ? activeLayer.variants : [];
89+
const lockedVariantIds = new Set(
90+
pieceVariants
91+
.filter(
8992
(variant) =>
90-
variant.id === NONE_VARIANT_ID || ownedSet.has(variant.id),
93+
variant.id !== NONE_VARIANT_ID &&
94+
!isVariantUnlocked(variant.id, ownedSet),
9195
)
92-
: [];
96+
.map((variant) => variant.id),
97+
);
9398

9499
const applyPreset = (preset: ColorPreset) => {
95100
setActivePresetId(preset.id);
@@ -120,6 +125,10 @@ export function CharacterCreator({
120125
};
121126

122127
const handleVariantChange = (variantId: string) => {
128+
if (!isVariantUnlocked(variantId, ownedSet)) {
129+
return;
130+
}
131+
123132
setVariants((current) => ({
124133
...current,
125134
[activeLayerId]: variantId,
@@ -189,6 +198,7 @@ export function CharacterCreator({
189198
<div className="min-h-14">
190199
<CharacterPieceSelector
191200
variants={pieceVariants}
201+
lockedVariantIds={lockedVariantIds}
192202
activeId={variants[activeLayerId]}
193203
color={activeColor}
194204
skinColor={colors.skin}

components/character/character-piece-selector.tsx

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { CircleSlash } from "lucide-react";
3+
import { CircleSlash, Lock } from "lucide-react";
44

55
import type { HSL } from "@/lib/character/color-utils";
66
import { NONE_VARIANT_ID, PIECE_SLOT_COUNT } from "@/lib/character/presets";
@@ -11,6 +11,7 @@ import { TintedSpriteIcon } from "./tinted-sprite-icon";
1111

1212
type CharacterPieceSelectorProps = {
1313
variants: LayerVariant[];
14+
lockedVariantIds?: ReadonlySet<string>;
1415
activeId: string;
1516
color: HSL;
1617
skinColor?: HSL;
@@ -19,6 +20,7 @@ type CharacterPieceSelectorProps = {
1920

2021
export function CharacterPieceSelector({
2122
variants,
23+
lockedVariantIds,
2224
activeId,
2325
color,
2426
skinColor,
@@ -66,19 +68,29 @@ export function CharacterPieceSelector({
6668
);
6769
}
6870

71+
const locked = lockedVariantIds?.has(variant.id) ?? false;
72+
6973
return (
7074
<button
7175
key={variant.id}
7276
type="button"
73-
title={variant.label}
74-
aria-label={variant.label}
77+
disabled={locked}
78+
aria-disabled={locked}
79+
title={
80+
locked ? `${variant.label} — unlock in the shop` : variant.label
81+
}
82+
aria-label={
83+
locked ? `${variant.label}, locked — unlock in the shop` : variant.label
84+
}
7585
aria-pressed={activeId === variant.id}
7686
onClick={() => onSelect(variant.id)}
7787
className={cn(
78-
"flex size-14 items-center justify-center rounded-lg border bg-zinc-950/80 p-1.5 transition-colors",
79-
activeId === variant.id
80-
? "border-foreground bg-zinc-900 shadow-[inset_0_0_0_1px_hsl(var(--foreground)/0.15)]"
81-
: "border-border/60 hover:border-border hover:bg-zinc-900/60",
88+
"relative flex size-14 items-center justify-center rounded-lg border bg-zinc-950/80 p-1.5 transition-colors",
89+
locked
90+
? "cursor-not-allowed border-border/40 opacity-80"
91+
: activeId === variant.id
92+
? "border-foreground bg-zinc-900 shadow-[inset_0_0_0_1px_hsl(var(--foreground)/0.15)]"
93+
: "border-border/60 hover:border-border hover:bg-zinc-900/60",
8294
)}
8395
>
8496
<TintedSpriteIcon
@@ -87,6 +99,14 @@ export function CharacterPieceSelector({
8799
skinColor={skinColor}
88100
size={36}
89101
/>
102+
{locked ? (
103+
<span
104+
aria-hidden
105+
className="absolute inset-0 flex items-center justify-center rounded-lg bg-background/75"
106+
>
107+
<Lock className="size-4 text-muted-foreground" strokeWidth={2.25} />
108+
</span>
109+
) : null}
90110
</button>
91111
);
92112
})}

lib/character/variant-access.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { NONE_VARIANT_ID } from "@/lib/character/presets";
2+
3+
/** First N numbered styles per layer are free; higher numbers are shop-only. */
4+
export const FREE_STYLE_COUNT = 2;
5+
6+
export function getVariantStyleNumber(variantId: string): number | null {
7+
if (variantId === NONE_VARIANT_ID) {
8+
return null;
9+
}
10+
11+
const match = variantId.match(/-(\d+)$/);
12+
return match ? Number.parseInt(match[1], 10) : null;
13+
}
14+
15+
export function isFreeStyleVariant(variantId: string): boolean {
16+
const styleNumber = getVariantStyleNumber(variantId);
17+
return styleNumber !== null && styleNumber <= FREE_STYLE_COUNT;
18+
}
19+
20+
export function isShopOnlyStyleVariant(variantId: string): boolean {
21+
const styleNumber = getVariantStyleNumber(variantId);
22+
return styleNumber !== null && styleNumber > FREE_STYLE_COUNT;
23+
}
24+
25+
export function isVariantUnlocked(
26+
variantId: string,
27+
ownedVariantIds: ReadonlySet<string> | readonly string[],
28+
): boolean {
29+
if (variantId === NONE_VARIANT_ID || isFreeStyleVariant(variantId)) {
30+
return true;
31+
}
32+
33+
const owned =
34+
ownedVariantIds instanceof Set
35+
? ownedVariantIds
36+
: new Set(ownedVariantIds);
37+
38+
return owned.has(variantId);
39+
}

lib/shop-catalog.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isShopOnlyStyleVariant } from "@/lib/character/variant-access";
12
import type { CharacterLayerId } from "@/lib/character/presets";
23
import { CHARACTER_LAYERS, NONE_VARIANT_ID } from "@/lib/character/presets";
34

@@ -57,7 +58,10 @@ export function getShopCatalogFromPresets(): ShopItemRecord[] {
5758
}
5859

5960
return layer.variants
60-
.filter((variant) => variant.id !== NONE_VARIANT_ID)
61+
.filter(
62+
(variant) =>
63+
variant.id !== NONE_VARIANT_ID && isShopOnlyStyleVariant(variant.id),
64+
)
6165
.map((variant) => ({
6266
id: variant.id,
6367
name: variant.label,

supabase/seed.sql

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,28 @@ delete from public.user_items
44
where item_id in ('hat_blue', 'glasses_round', 'room_sunset');
55

66
delete from public.shop_items
7-
where id in ('hat_blue', 'glasses_round', 'room_sunset');
7+
where id in (
8+
'hat_blue',
9+
'glasses_round',
10+
'room_sunset',
11+
'pants-1',
12+
'pants-2',
13+
'shoes-1',
14+
'shoes-2',
15+
'torso-1',
16+
'torso-2',
17+
'eyes-1',
18+
'eyes-2',
19+
'head-1',
20+
'head-2'
21+
);
822

923
insert into public.shop_items (id, name, type, price, image_path)
1024
values
11-
('pants-1', 'Pants 1', 'pants', 20, '/character/pants/pants-1.png'),
12-
('pants-2', 'Pants 2', 'pants', 25, '/character/pants/pants-2.png'),
1325
('pants-3', 'Pants 3', 'pants', 30, '/character/pants/pants-3.png'),
14-
('shoes-1', 'Shoes 1', 'shoes', 20, '/character/shoes/shoes-1.png'),
15-
('shoes-2', 'Shoes 2', 'shoes', 25, '/character/shoes/shoes-2.png'),
1626
('shoes-3', 'Shoes 3', 'shoes', 30, '/character/shoes/shoes-3.png'),
17-
('torso-1', 'Torso 1', 'torso', 25, '/character/torso/torso-1.png'),
18-
('torso-2', 'Torso 2', 'torso', 30, '/character/torso/torso-2.png'),
1927
('torso-3', 'Torso 3', 'torso', 35, '/character/torso/torso-3.png'),
2028
('torso-4', 'Torso 4', 'torso', 40, '/character/torso/torso-4.png'),
21-
('eyes-1', 'Eyes 1', 'eyes', 15, '/character/eyes/eyes-1.png'),
22-
('eyes-2', 'Eyes 2', 'eyes', 20, '/character/eyes/eyes-2.png'),
23-
('head-1', 'Head 1', 'head', 30, '/character/head/head-1.png'),
24-
('head-2', 'Head 2', 'head', 35, '/character/head/head-2.png'),
2529
('head-3', 'Head 3', 'head', 40, '/character/head/head-3.png'),
2630
('head-4', 'Head 4', 'head', 45, '/character/head/head-4.png'),
2731
('head-5', 'Head 5', 'head', 50, '/character/head/head-5.png'),

0 commit comments

Comments
 (0)