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:
Najjar\NajjarV02
2026-05-19 17:40:04 +04:00
co-authored by Claude Opus 4.7
parent 03dbc4ac98
commit 52b910ed93
8 changed files with 103 additions and 48 deletions
+7 -14
View File
@@ -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); }}>