mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:21:10 +02:00
✨ feat(figgos): scaffold backend + mobile app
Add new Figgos project under apps/figgos/ with: - NestJS backend (port 3025) with Drizzle ORM, health check, metrics - Expo React Native mobile app with Mana Core Auth, tab navigation - Shared types package (@figgos/shared) - Root integration: env generation, dev:figgos:full script, MinIO bucket Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5b6f231e1a
commit
1cffc6de81
40 changed files with 1572 additions and 194 deletions
|
|
@ -324,6 +324,13 @@ PLANTA_GEMINI_API_KEY=AIzaSyC_-hPWpVttTlqJdU4jbXR5H0OAnRi2LgI
|
|||
SKILLTREE_BACKEND_PORT=3024
|
||||
SKILLTREE_DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/skilltree
|
||||
|
||||
# ============================================
|
||||
# FIGGOS PROJECT
|
||||
# ============================================
|
||||
|
||||
FIGGOS_BACKEND_PORT=3025
|
||||
FIGGOS_DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/figgos
|
||||
|
||||
# ============================================
|
||||
# WORLDREAM GAME
|
||||
# ============================================
|
||||
|
|
|
|||
68
apps/figgos/CLAUDE.md
Normal file
68
apps/figgos/CLAUDE.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Figgos
|
||||
|
||||
A collectible figure game where users create and collect AI-generated fantasy figures.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
apps/figgos/
|
||||
├── apps/
|
||||
│ ├── backend/ # @figgos/backend - NestJS API (port 3025)
|
||||
│ └── mobile/ # @figgos/mobile - Expo React Native app
|
||||
├── packages/
|
||||
│ └── shared/ # @figgos/shared - Shared types & constants
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### From monorepo root
|
||||
|
||||
```bash
|
||||
pnpm dev:figgos:mobile # Start mobile app
|
||||
pnpm dev:figgos:backend # Start backend
|
||||
pnpm dev:figgos:app # Start web + backend together
|
||||
pnpm dev:figgos:full # Start with auth + auto DB setup
|
||||
|
||||
pnpm figgos:db:push # Push schema to database
|
||||
pnpm figgos:db:studio # Open Drizzle Studio
|
||||
```
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Mobile**: React Native 0.76 + Expo SDK 52, NativeWind, Expo Router
|
||||
- **Backend**: NestJS 10, Drizzle ORM, PostgreSQL
|
||||
- **Auth**: Mana Core Auth (JWT via @manacore/shared-nestjs-auth)
|
||||
- **AI**: Google Gemini API (planned)
|
||||
- **Storage**: MinIO (local) / Hetzner S3 (production)
|
||||
|
||||
## Ports
|
||||
|
||||
| App | Port |
|
||||
|-----|------|
|
||||
| Backend | 3025 |
|
||||
| Web (planned) | 5181 |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Backend (.env)
|
||||
|
||||
```env
|
||||
PORT=3025
|
||||
DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/figgos
|
||||
MANA_CORE_AUTH_URL=http://localhost:3001
|
||||
```
|
||||
|
||||
### Mobile (.env)
|
||||
|
||||
```env
|
||||
EXPO_PUBLIC_BACKEND_URL=http://localhost:3025
|
||||
EXPO_PUBLIC_MANA_CORE_AUTH_URL=http://localhost:3001
|
||||
```
|
||||
|
||||
## Game Concept
|
||||
|
||||
- Users create fantasy figures by providing a name/subject
|
||||
- AI generates character info (description, lore, items) + image
|
||||
- Figures have rarities: common, rare, epic, legendary
|
||||
- Users can browse public figures, like them, and collect their own
|
||||
6
apps/figgos/apps/backend/drizzle.config.ts
Normal file
6
apps/figgos/apps/backend/drizzle.config.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { createDrizzleConfig } from '@manacore/shared-drizzle-config';
|
||||
|
||||
export default createDrizzleConfig({
|
||||
dbName: 'figgos',
|
||||
additionalEnvVars: ['FIGGOS_DATABASE_URL'],
|
||||
});
|
||||
10
apps/figgos/apps/backend/nest-cli.json
Normal file
10
apps/figgos/apps/backend/nest-cli.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": false,
|
||||
"assets": [],
|
||||
"watchAssets": false
|
||||
}
|
||||
}
|
||||
52
apps/figgos/apps/backend/package.json
Normal file
52
apps/figgos/apps/backend/package.json
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "@figgos/backend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"start": "nest start",
|
||||
"dev": "nest start --watch",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"type-check": "tsc --noEmit",
|
||||
"migration:generate": "drizzle-kit generate",
|
||||
"migration:run": "tsx src/db/migrate.ts",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"dependencies": {
|
||||
"@figgos/shared": "workspace:*",
|
||||
"@manacore/shared-drizzle-config": "workspace:*",
|
||||
"@manacore/shared-nestjs-auth": "workspace:*",
|
||||
"@manacore/shared-nestjs-health": "workspace:*",
|
||||
"@manacore/shared-nestjs-metrics": "workspace:*",
|
||||
"@manacore/shared-nestjs-setup": "workspace:*",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/config": "^3.3.0",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
"@nestjs/platform-express": "^10.4.15",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"drizzle-kit": "^0.30.2",
|
||||
"drizzle-orm": "^0.38.3",
|
||||
"postgres": "^3.4.5",
|
||||
"prom-client": "^15.1.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.9",
|
||||
"@nestjs/schematics": "^10.2.3",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
21
apps/figgos/apps/backend/src/app.module.ts
Normal file
21
apps/figgos/apps/backend/src/app.module.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { MetricsModule } from '@manacore/shared-nestjs-metrics';
|
||||
import { HealthModule } from '@manacore/shared-nestjs-health';
|
||||
import { DatabaseModule } from './db/database.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
isGlobal: true,
|
||||
envFilePath: '.env',
|
||||
}),
|
||||
MetricsModule.register({
|
||||
prefix: 'figgos_',
|
||||
excludePaths: ['/health'],
|
||||
}),
|
||||
DatabaseModule,
|
||||
HealthModule.forRoot({ serviceName: 'figgos-backend' }),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
38
apps/figgos/apps/backend/src/db/connection.ts
Normal file
38
apps/figgos/apps/backend/src/db/connection.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import * as schema from './schema';
|
||||
|
||||
// Use require for postgres to avoid ESM/CommonJS interop issues
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const postgres = require('postgres');
|
||||
|
||||
let connection: ReturnType<typeof postgres> | null = null;
|
||||
let db: ReturnType<typeof drizzle> | null = null;
|
||||
|
||||
export function getConnection(databaseUrl: string) {
|
||||
if (!connection) {
|
||||
connection = postgres(databaseUrl, {
|
||||
max: 10,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
});
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
export function getDb(databaseUrl: string) {
|
||||
if (!db) {
|
||||
const conn = getConnection(databaseUrl);
|
||||
db = drizzle(conn, { schema });
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function closeConnection() {
|
||||
if (connection) {
|
||||
await connection.end();
|
||||
connection = null;
|
||||
db = null;
|
||||
}
|
||||
}
|
||||
|
||||
export type Database = ReturnType<typeof getDb>;
|
||||
29
apps/figgos/apps/backend/src/db/database.module.ts
Normal file
29
apps/figgos/apps/backend/src/db/database.module.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Module, Global, OnModuleDestroy } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { getDb, closeConnection } from './connection';
|
||||
import type { Database } from './connection';
|
||||
|
||||
export const DATABASE_CONNECTION = 'DATABASE_CONNECTION';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [
|
||||
{
|
||||
provide: DATABASE_CONNECTION,
|
||||
useFactory: (configService: ConfigService): Database => {
|
||||
const databaseUrl = configService.get<string>('DATABASE_URL');
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL environment variable is not set');
|
||||
}
|
||||
return getDb(databaseUrl);
|
||||
},
|
||||
inject: [ConfigService],
|
||||
},
|
||||
],
|
||||
exports: [DATABASE_CONNECTION],
|
||||
})
|
||||
export class DatabaseModule implements OnModuleDestroy {
|
||||
async onModuleDestroy() {
|
||||
await closeConnection();
|
||||
}
|
||||
}
|
||||
3
apps/figgos/apps/backend/src/db/schema/index.ts
Normal file
3
apps/figgos/apps/backend/src/db/schema/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// Database schema exports
|
||||
// Will be populated as features are added
|
||||
export {};
|
||||
8
apps/figgos/apps/backend/src/main.ts
Normal file
8
apps/figgos/apps/backend/src/main.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { bootstrapApp } from '@manacore/shared-nestjs-setup';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
bootstrapApp(AppModule, {
|
||||
defaultPort: 3025,
|
||||
serviceName: 'Figgos',
|
||||
additionalCorsOrigins: ['http://localhost:5181'],
|
||||
});
|
||||
27
apps/figgos/apps/backend/tsconfig.json
Normal file
27
apps/figgos/apps/backend/tsconfig.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"rootDir": "./src",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"strictBindCallApply": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
14
apps/figgos/apps/mobile/.gitignore
vendored
Normal file
14
apps/figgos/apps/mobile/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
node_modules/
|
||||
.expo/
|
||||
dist/
|
||||
npm-debug.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
*.orig.*
|
||||
web-build/
|
||||
ios/
|
||||
android/
|
||||
.env
|
||||
1
apps/figgos/apps/mobile/app-env.d.ts
vendored
Normal file
1
apps/figgos/apps/mobile/app-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="expo/types" />
|
||||
51
apps/figgos/apps/mobile/app.json
Normal file
51
apps/figgos/apps/mobile/app.json
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"expo": {
|
||||
"name": "Figgos",
|
||||
"slug": "figgos",
|
||||
"version": "1.0.0",
|
||||
"scheme": "figgos",
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"output": "server",
|
||||
"favicon": "./assets/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
[
|
||||
"expo-dev-launcher",
|
||||
{
|
||||
"launchMode": "most-recent"
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true,
|
||||
"tsconfigPaths": true
|
||||
},
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"userInterfaceStyle": "light",
|
||||
"splash": {
|
||||
"image": "./assets/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"bundleIdentifier": "com.tilljs.figgos"
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"package": "com.tilljs.figgos"
|
||||
},
|
||||
"extra": {
|
||||
"router": {
|
||||
"origin": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
apps/figgos/apps/mobile/app/(auth)/_layout.tsx
Normal file
9
apps/figgos/apps/mobile/app/(auth)/_layout.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { Stack } from 'expo-router';
|
||||
|
||||
export default function AuthLayout() {
|
||||
return (
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="login" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
79
apps/figgos/apps/mobile/app/(auth)/login.tsx
Normal file
79
apps/figgos/apps/mobile/app/(auth)/login.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { useState } from 'react';
|
||||
import { View, Text, TextInput, Pressable, KeyboardAvoidingView, Platform } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useAuth } from '~/contexts/AuthContext';
|
||||
|
||||
export default function LoginScreen() {
|
||||
const { signIn } = useAuth();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleLogin = async () => {
|
||||
if (!email || !password) {
|
||||
setError('Please enter email and password');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const result = await signIn(email, password);
|
||||
|
||||
if (result.error) {
|
||||
setError(result.error.message || 'Login failed');
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-background dark:bg-dark-background">
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
className="flex-1 justify-center px-6"
|
||||
>
|
||||
<View className="items-center mb-12">
|
||||
<Text className="text-4xl font-bold text-primary">Figgos</Text>
|
||||
<Text className="text-base text-muted mt-2">Collect your fantasy figures</Text>
|
||||
</View>
|
||||
|
||||
<View className="space-y-4">
|
||||
<TextInput
|
||||
className="bg-card dark:bg-dark-card border border-border dark:border-dark-border rounded-lg px-4 py-3 text-textColor dark:text-dark-textColor"
|
||||
placeholder="Email"
|
||||
placeholderTextColor="#B2BEC3"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
autoCapitalize="none"
|
||||
keyboardType="email-address"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
className="bg-card dark:bg-dark-card border border-border dark:border-dark-border rounded-lg px-4 py-3 text-textColor dark:text-dark-textColor mt-3"
|
||||
placeholder="Password"
|
||||
placeholderTextColor="#B2BEC3"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
/>
|
||||
|
||||
{error && <Text className="text-red-500 text-center mt-2">{error}</Text>}
|
||||
|
||||
<Pressable
|
||||
onPress={handleLogin}
|
||||
disabled={loading}
|
||||
className={({ pressed }) =>
|
||||
`bg-primary rounded-lg py-3 mt-4 ${pressed ? 'opacity-80' : ''} ${loading ? 'opacity-50' : ''}`
|
||||
}
|
||||
>
|
||||
<Text className="text-white text-center font-semibold text-base">
|
||||
{loading ? 'Signing in...' : 'Sign In'}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
49
apps/figgos/apps/mobile/app/(tabs)/_layout.tsx
Normal file
49
apps/figgos/apps/mobile/app/(tabs)/_layout.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { Tabs } from 'expo-router';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
|
||||
export default function TabLayout() {
|
||||
return (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
tabBarActiveTintColor: '#6C5CE7',
|
||||
tabBarInactiveTintColor: '#B2BEC3',
|
||||
tabBarStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopColor: '#DFE6E9',
|
||||
},
|
||||
headerStyle: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
headerTintColor: '#2D3436',
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: 'Community',
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="globe-outline" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="create"
|
||||
options={{
|
||||
title: 'Create',
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="add-circle-outline" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="shelf"
|
||||
options={{
|
||||
title: 'Collection',
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="grid-outline" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
15
apps/figgos/apps/mobile/app/(tabs)/create.tsx
Normal file
15
apps/figgos/apps/mobile/app/(tabs)/create.tsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { View, Text } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
export default function CreateScreen() {
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-background dark:bg-dark-background" edges={['bottom']}>
|
||||
<View className="flex-1 items-center justify-center px-6">
|
||||
<Text className="text-2xl font-bold text-textColor dark:text-dark-textColor">Create</Text>
|
||||
<Text className="text-muted mt-2 text-center">
|
||||
Generate your own AI-powered fantasy figures.
|
||||
</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
17
apps/figgos/apps/mobile/app/(tabs)/index.tsx
Normal file
17
apps/figgos/apps/mobile/app/(tabs)/index.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { View, Text } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
export default function CommunityScreen() {
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-background dark:bg-dark-background" edges={['bottom']}>
|
||||
<View className="flex-1 items-center justify-center px-6">
|
||||
<Text className="text-2xl font-bold text-textColor dark:text-dark-textColor">
|
||||
Community
|
||||
</Text>
|
||||
<Text className="text-muted mt-2 text-center">
|
||||
Public figures from the community will appear here.
|
||||
</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
17
apps/figgos/apps/mobile/app/(tabs)/shelf.tsx
Normal file
17
apps/figgos/apps/mobile/app/(tabs)/shelf.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { View, Text } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
|
||||
export default function ShelfScreen() {
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-background dark:bg-dark-background" edges={['bottom']}>
|
||||
<View className="flex-1 items-center justify-center px-6">
|
||||
<Text className="text-2xl font-bold text-textColor dark:text-dark-textColor">
|
||||
My Collection
|
||||
</Text>
|
||||
<Text className="text-muted mt-2 text-center">
|
||||
Your collected figures will appear here.
|
||||
</Text>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
18
apps/figgos/apps/mobile/app/+not-found.tsx
Normal file
18
apps/figgos/apps/mobile/app/+not-found.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { View, Text } from 'react-native';
|
||||
import { Link, Stack } from 'expo-router';
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: 'Not Found' }} />
|
||||
<View className="flex-1 items-center justify-center bg-background dark:bg-dark-background">
|
||||
<Text className="text-xl font-bold text-textColor dark:text-dark-textColor">
|
||||
Page not found
|
||||
</Text>
|
||||
<Link href="/(tabs)" className="mt-4">
|
||||
<Text className="text-primary text-base">Go to home</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
||||
47
apps/figgos/apps/mobile/app/_layout.tsx
Normal file
47
apps/figgos/apps/mobile/app/_layout.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import '../global.css';
|
||||
|
||||
import { Stack, useRouter, useSegments } from 'expo-router';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { AuthProvider, useAuth } from '~/contexts/AuthContext';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
function AuthGuard({ children }: { children: React.ReactNode }) {
|
||||
const { user, loading } = useAuth();
|
||||
const segments = useSegments();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
|
||||
const inAuthGroup = segments[0] === '(auth)';
|
||||
|
||||
if (!user && !inAuthGroup) {
|
||||
router.replace('/(auth)/login');
|
||||
} else if (user && inAuthGroup) {
|
||||
router.replace('/(tabs)');
|
||||
}
|
||||
}, [user, loading, segments]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
function Layout() {
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<AuthProvider>
|
||||
<AuthGuard>
|
||||
<Layout />
|
||||
</AuthGuard>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
12
apps/figgos/apps/mobile/babel.config.js
Normal file
12
apps/figgos/apps/mobile/babel.config.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
const plugins = [];
|
||||
|
||||
plugins.push('react-native-reanimated/plugin');
|
||||
|
||||
return {
|
||||
presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
|
||||
|
||||
plugins,
|
||||
};
|
||||
};
|
||||
216
apps/figgos/apps/mobile/contexts/AuthContext.tsx
Normal file
216
apps/figgos/apps/mobile/contexts/AuthContext.tsx
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { ActivityIndicator, View, Text } from 'react-native';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import {
|
||||
createAuthService,
|
||||
createTokenManager,
|
||||
setStorageAdapter,
|
||||
setDeviceAdapter,
|
||||
setNetworkAdapter,
|
||||
type UserData,
|
||||
} from '@manacore/shared-auth';
|
||||
|
||||
// Mana Core Auth URL from environment
|
||||
const MANA_AUTH_URL = process.env.EXPO_PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
|
||||
|
||||
// Create SecureStore adapter for React Native
|
||||
const createSecureStoreAdapter = () => ({
|
||||
async getItem<T>(key: string): Promise<T | null> {
|
||||
try {
|
||||
const value = await SecureStore.getItemAsync(key);
|
||||
return value ? JSON.parse(value) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async setItem(key: string, value: unknown): Promise<void> {
|
||||
await SecureStore.setItemAsync(key, JSON.stringify(value));
|
||||
},
|
||||
async removeItem(key: string): Promise<void> {
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
},
|
||||
});
|
||||
|
||||
// Create device adapter for React Native
|
||||
const createReactNativeDeviceAdapter = () => {
|
||||
let deviceId: string | null = null;
|
||||
|
||||
return {
|
||||
async getDeviceInfo() {
|
||||
if (!deviceId) {
|
||||
deviceId = await SecureStore.getItemAsync('@device/id');
|
||||
|
||||
if (!deviceId) {
|
||||
deviceId = `rn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
await SecureStore.setItemAsync('@device/id', deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deviceId,
|
||||
deviceName: 'React Native Device',
|
||||
deviceType: 'mobile',
|
||||
platform: 'react-native',
|
||||
};
|
||||
},
|
||||
async getStoredDeviceId() {
|
||||
return deviceId || (await SecureStore.getItemAsync('@device/id'));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Create network adapter
|
||||
const createReactNativeNetworkAdapter = () => ({
|
||||
async isDeviceConnected() {
|
||||
return true;
|
||||
},
|
||||
async hasStableConnection() {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize adapters
|
||||
setStorageAdapter(createSecureStoreAdapter());
|
||||
setDeviceAdapter(createReactNativeDeviceAdapter());
|
||||
setNetworkAdapter(createReactNativeNetworkAdapter());
|
||||
|
||||
// Create auth service
|
||||
const authService = createAuthService({ baseUrl: MANA_AUTH_URL });
|
||||
const tokenManager = createTokenManager(authService);
|
||||
|
||||
// Export for use in API client
|
||||
export { authService, tokenManager };
|
||||
|
||||
// Auth context type
|
||||
type AuthContextType = {
|
||||
user: UserData | null;
|
||||
loading: boolean;
|
||||
signIn: (email: string, password: string) => Promise<{ error: any | null }>;
|
||||
signUp: (
|
||||
email: string,
|
||||
password: string,
|
||||
username?: string
|
||||
) => Promise<{ error: any | null; data: any | null }>;
|
||||
signOut: () => Promise<void>;
|
||||
};
|
||||
|
||||
// Create auth context
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
// Hook to access auth context
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
return {
|
||||
user: null,
|
||||
loading: true,
|
||||
signIn: async () => ({ error: null }),
|
||||
signUp: async () => ({ error: null, data: null }),
|
||||
signOut: async () => {},
|
||||
};
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
// AuthProvider component
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [user, setUser] = useState<UserData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const initialize = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const authenticated = await authService.isAuthenticated();
|
||||
|
||||
if (authenticated) {
|
||||
const userData = await authService.getUserFromToken();
|
||||
setUser(userData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error initializing auth session:', error);
|
||||
setUser(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
initialize();
|
||||
}, []);
|
||||
|
||||
const signIn = async (email: string, password: string) => {
|
||||
try {
|
||||
const result = await authService.signIn(email, password);
|
||||
|
||||
if (!result.success) {
|
||||
return { error: { message: result.error } };
|
||||
}
|
||||
|
||||
const userData = await authService.getUserFromToken();
|
||||
setUser(userData);
|
||||
return { error: null };
|
||||
} catch (error: any) {
|
||||
return { error };
|
||||
}
|
||||
};
|
||||
|
||||
const signUp = async (email: string, password: string, _username?: string) => {
|
||||
try {
|
||||
const result = await authService.signUp(email, password);
|
||||
|
||||
if (!result.success) {
|
||||
return { data: null, error: { message: result.error } };
|
||||
}
|
||||
|
||||
const signInResult = await signIn(email, password);
|
||||
|
||||
if (signInResult.error) {
|
||||
return { data: null, error: signInResult.error };
|
||||
}
|
||||
|
||||
return { data: user, error: null };
|
||||
} catch (error) {
|
||||
return { data: null, error };
|
||||
}
|
||||
};
|
||||
|
||||
const signOut = async () => {
|
||||
try {
|
||||
const timeout = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Sign out timeout after 5s')), 5000)
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.race([authService.signOut(), timeout]);
|
||||
} catch {
|
||||
// Force local logout on failure
|
||||
}
|
||||
|
||||
setUser(null);
|
||||
} catch {
|
||||
setUser(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#1A1A2E',
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size="large" color="#6C5CE7" />
|
||||
<Text style={{ marginTop: 16, color: '#fff' }}>Loading...</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, loading, signIn, signUp, signOut }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
18
apps/figgos/apps/mobile/eas.json
Normal file
18
apps/figgos/apps/mobile/eas.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"cli": {
|
||||
"version": ">= 5.0.0"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"production": {}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
3
apps/figgos/apps/mobile/global.css
Normal file
3
apps/figgos/apps/mobile/global.css
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
9
apps/figgos/apps/mobile/metro.config.js
Normal file
9
apps/figgos/apps/mobile/metro.config.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDefaultConfig } = require('expo/metro-config');
|
||||
const { withNativeWind } = require('nativewind/metro');
|
||||
|
||||
/** @type {import('expo/metro-config').MetroConfig} */
|
||||
// eslint-disable-next-line no-undef
|
||||
const config = getDefaultConfig(__dirname);
|
||||
|
||||
module.exports = withNativeWind(config, { input: './global.css' });
|
||||
1
apps/figgos/apps/mobile/nativewind-env.d.ts
vendored
Normal file
1
apps/figgos/apps/mobile/nativewind-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="nativewind/types" />
|
||||
60
apps/figgos/apps/mobile/package.json
Normal file
60
apps/figgos/apps/mobile/package.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "@figgos/mobile",
|
||||
"version": "1.0.0",
|
||||
"main": "expo-router/entry",
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
"dev": "expo start --dev-client",
|
||||
"start": "expo start --dev-client",
|
||||
"ios": "expo run:ios",
|
||||
"android": "expo run:android",
|
||||
"build:dev": "eas build --profile development",
|
||||
"build:preview": "eas build --profile preview",
|
||||
"build:prod": "eas build --profile production",
|
||||
"prebuild": "expo prebuild",
|
||||
"lint": "eslint .",
|
||||
"format": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^14.0.0",
|
||||
"@manacore/shared-auth": "workspace:*",
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-navigation/bottom-tabs": "^7.0.5",
|
||||
"@react-navigation/native": "^7.0.3",
|
||||
"expo": "^52.0.39",
|
||||
"expo-constants": "~17.0.8",
|
||||
"expo-dev-client": "~5.0.4",
|
||||
"expo-dev-launcher": "^5.0.17",
|
||||
"expo-linking": "~7.0.5",
|
||||
"expo-router": "~4.0.6",
|
||||
"expo-secure-store": "~14.0.1",
|
||||
"expo-status-bar": "~2.0.1",
|
||||
"expo-system-ui": "~4.0.8",
|
||||
"expo-web-browser": "~14.0.2",
|
||||
"nativewind": "latest",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-native": "0.76.7",
|
||||
"react-native-gesture-handler": "~2.20.2",
|
||||
"react-native-reanimated": "3.16.2",
|
||||
"react-native-safe-area-context": "4.12.0",
|
||||
"react-native-screens": "~4.4.0",
|
||||
"react-native-web": "~0.19.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@types/react": "~18.3.12",
|
||||
"dotenv": "^16.4.7",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-universe": "^12.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "universe/native",
|
||||
"root": true
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
26
apps/figgos/apps/mobile/services/api.ts
Normal file
26
apps/figgos/apps/mobile/services/api.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { authService } from '~/contexts/AuthContext';
|
||||
|
||||
const BACKEND_URL = process.env.EXPO_PUBLIC_BACKEND_URL || 'http://localhost:3025';
|
||||
|
||||
export async function fetchApi<T = any>(path: string, options?: RequestInit): Promise<T> {
|
||||
const token = await authService.getAccessToken?.();
|
||||
|
||||
const response = await fetch(`${BACKEND_URL}${path}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...options?.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export const api = {
|
||||
health: () => fetchApi('/health'),
|
||||
};
|
||||
32
apps/figgos/apps/mobile/tailwind.config.js
Normal file
32
apps/figgos/apps/mobile/tailwind.config.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./app/**/*.{js,ts,tsx}', './components/**/*.{js,ts,tsx}'],
|
||||
presets: [require('nativewind/preset')],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: '#6C5CE7',
|
||||
secondary: '#A29BFE',
|
||||
background: '#F8F9FA',
|
||||
card: '#FFFFFF',
|
||||
textColor: '#2D3436',
|
||||
border: '#DFE6E9',
|
||||
accent: '#00B894',
|
||||
muted: '#B2BEC3',
|
||||
|
||||
dark: {
|
||||
primary: '#A29BFE',
|
||||
secondary: '#6C5CE7',
|
||||
background: '#1A1A2E',
|
||||
card: '#16213E',
|
||||
textColor: '#FFFFFF',
|
||||
border: '#2D3436',
|
||||
accent: '#55EFC4',
|
||||
muted: '#636E72',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
12
apps/figgos/apps/mobile/tsconfig.json
Normal file
12
apps/figgos/apps/mobile/tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts", "nativewind-env.d.ts"]
|
||||
}
|
||||
8
apps/figgos/package.json
Normal file
8
apps/figgos/package.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "figgos",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "turbo run dev"
|
||||
}
|
||||
}
|
||||
16
apps/figgos/packages/shared/package.json
Normal file
16
apps/figgos/packages/shared/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@figgos/shared",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
3
apps/figgos/packages/shared/src/index.ts
Normal file
3
apps/figgos/packages/shared/src/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// @figgos/shared - Shared types and constants
|
||||
// Will be populated as features are added
|
||||
export {};
|
||||
15
apps/figgos/packages/shared/tsconfig.json
Normal file
15
apps/figgos/packages/shared/tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
@ -87,7 +87,9 @@ services:
|
|||
mc mb --ignore-existing myminio/inventory-storage;
|
||||
mc mb --ignore-existing myminio/planta-storage;
|
||||
mc mb --ignore-existing myminio/projectdoc-storage;
|
||||
mc mb --ignore-existing myminio/figgos-storage;
|
||||
mc anonymous set download myminio/picture-storage;
|
||||
mc anonymous set download myminio/figgos-storage;
|
||||
mc anonymous set download myminio/planta-storage;
|
||||
mc anonymous set download myminio/inventory-storage;
|
||||
echo 'Buckets created successfully';
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@
|
|||
"dev:figgos:android": "pnpm --filter @figgos/mobile android",
|
||||
"figgos:db:push": "pnpm --filter @figgos/backend db:push",
|
||||
"figgos:db:studio": "pnpm --filter @figgos/backend db:studio",
|
||||
"dev:figgos:full": "./scripts/setup-databases.sh figgos && ./scripts/setup-databases.sh auth && concurrently -n auth,backend -c blue,green \"pnpm dev:auth\" \"pnpm dev:figgos:backend\"",
|
||||
"worldream:dev": "turbo run dev --filter=worldream...",
|
||||
"dev:worldream:web": "pnpm --filter @worldream/web dev",
|
||||
"context:dev": "turbo run dev --filter=context...",
|
||||
|
|
|
|||
718
pnpm-lock.yaml
generated
718
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -601,6 +601,34 @@ const APP_CONFIGS = [
|
|||
},
|
||||
},
|
||||
|
||||
// Figgos Backend (NestJS)
|
||||
{
|
||||
path: 'apps/figgos/apps/backend/.env',
|
||||
vars: {
|
||||
NODE_ENV: () => 'development',
|
||||
PORT: (env) => env.FIGGOS_BACKEND_PORT || '3025',
|
||||
DATABASE_URL: (env) => env.FIGGOS_DATABASE_URL,
|
||||
MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
|
||||
DEV_BYPASS_AUTH: () => 'true',
|
||||
DEV_USER_ID: () => '00000000-0000-0000-0000-000000000000',
|
||||
S3_ENDPOINT: (env) => env.S3_ENDPOINT || 'http://localhost:9000',
|
||||
S3_REGION: (env) => env.S3_REGION || 'us-east-1',
|
||||
S3_ACCESS_KEY: (env) => env.S3_ACCESS_KEY || 'minioadmin',
|
||||
S3_SECRET_KEY: (env) => env.S3_SECRET_KEY || 'minioadmin',
|
||||
S3_BUCKET: () => 'figgos-storage',
|
||||
CORS_ORIGINS: () => 'http://localhost:5181,http://localhost:8081',
|
||||
},
|
||||
},
|
||||
|
||||
// Figgos Mobile (Expo)
|
||||
{
|
||||
path: 'apps/figgos/apps/mobile/.env',
|
||||
vars: {
|
||||
EXPO_PUBLIC_BACKEND_URL: (env) => `http://localhost:${env.FIGGOS_BACKEND_PORT || '3025'}`,
|
||||
EXPO_PUBLIC_MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
|
||||
},
|
||||
},
|
||||
|
||||
// Worldream Web (SvelteKit)
|
||||
{
|
||||
path: 'games/worldream/apps/web/.env',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue