feat(pricing): switch base+EDU prices to AED and unify attire at 5000 AED
- `base` (Basic body) → 77,125 AED (Unitree G1 retail $16k + $5k markup) - new `edu` row → 146,900 AED ($40k flat) - all persona attire (kandura, vest, suit, robot-doctor, security-guard) → 5,000 AED - `custom-color` unchanged at 3,500 AED - PricingEngine now swaps the base line when EDU body is selected - localStorage key bumped to `lootah-pricing-v2` to invalidate stale client caches - Robot Body buttons now describe the variants as `Standard consumer` vs `Open-source education / research` (same chassis GLB, differs in licensing) - New `prisma/update-prices.ts` idempotent script applies the new prices to an existing DB; seed.ts updated for fresh installs - Pricing tests updated to match new defaults Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
03dbc4ac98
commit
52b910ed93
@@ -4,7 +4,7 @@ import { useRef, useMemo, useEffect, Suspense, useState, useCallback, Component,
|
||||
import { useGLTF } from '@react-three/drei';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { configStore, useConfigStore } from '@/store/useConfigStore';
|
||||
import { useConfigStore } from '@/store/useConfigStore';
|
||||
import { personaStore, usePersonaStore } from '@/store/usePersonaStore';
|
||||
|
||||
// Configure Draco decoder so compressed .glb files load correctly
|
||||
@@ -16,9 +16,11 @@ const STATIC_ATTIRE_GLB: Record<string, string> = {
|
||||
'business-suit': '/Suit.glb',
|
||||
};
|
||||
|
||||
// Same chassis GLB for both — variants differ in software/licensing (EDU = open-source
|
||||
// education / research build), not in geometry.
|
||||
const BODY_GLB: Record<'basic' | 'edu', string> = {
|
||||
basic: '/Unitree_G1.glb',
|
||||
edu: '/Unitree_G1_EDU.glb',
|
||||
edu: '/Unitree_G1.glb',
|
||||
};
|
||||
|
||||
// Attire models are loaded on-demand to avoid blocking the initial 50 MB robot load
|
||||
@@ -243,20 +245,11 @@ export function RobotModel({ onError: _onError }: RobotModelProps) {
|
||||
setAttireReady(true);
|
||||
}, []);
|
||||
|
||||
const handleBodyError = useCallback(() => {
|
||||
// Fallback to basic body when EDU (or any non-basic) GLB fails to load
|
||||
if (configStore.getState().activeBody !== 'basic') {
|
||||
configStore.getState().setBody('basic');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<group ref={groupRef}>
|
||||
<ModelErrorBoundary key={bodyGlbPath} tag="BodyModel" onError={handleBodyError}>
|
||||
<Suspense fallback={null}>
|
||||
<BaseBodyMesh glbPath={bodyGlbPath} primaryColor={activeColors.primary} visible={showBase} />
|
||||
</Suspense>
|
||||
</ModelErrorBoundary>
|
||||
<Suspense fallback={null}>
|
||||
<BaseBodyMesh glbPath={bodyGlbPath} primaryColor={activeColors.primary} visible={showBase} />
|
||||
</Suspense>
|
||||
|
||||
{attireGlbPath && (
|
||||
<ModelErrorBoundary key={attireGlbPath} tag="AttireModel" onError={() => { setDisplayedAttire('none'); setAttireReady(false); }}>
|
||||
|
||||
Reference in New Issue
Block a user