Repository navigation
Expand file tree
/
Copy pathtemplate.json
More file actions
24 lines (24 loc) · 16.6 KB
/
Copy pathtemplate.json
File metadata and controls
24 lines (24 loc) · 16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"id": "app",
"name": "Expo App (server, db, user)",
"description": "Expo Router mobile + Express/TRPC/Drizzle backend with Manus OAuth. Ships web preview + QR friendly Expo dev server scripts so webdev_init_project can satisfy \"build an app\" requests.",
"capabilities": [
"server",
"db",
"user",
"mobile",
"app"
],
"files": {
"components/screen-container.tsx": "import { View, type ViewProps } from \"react-native\";\nimport { SafeAreaView, type Edge } from \"react-native-safe-area-context\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface ScreenContainerProps extends ViewProps {\n /**\n * SafeArea edges to apply. Defaults to [\"top\", \"left\", \"right\"].\n * Bottom is typically handled by Tab Bar.\n */\n edges?: Edge[];\n /**\n * Tailwind className for the content area.\n */\n className?: string;\n /**\n * Additional className for the outer container (background layer).\n */\n containerClassName?: string;\n /**\n * Additional className for the SafeAreaView (content layer).\n */\n safeAreaClassName?: string;\n}\n\n/**\n * A container component that properly handles SafeArea and background colors.\n *\n * The outer View extends to full screen (including status bar area) with the background color,\n * while the inner SafeAreaView ensures content is within safe bounds.\n *\n * Usage:\n * ```tsx\n * <ScreenContainer className=\"p-4\">\n * <Text className=\"text-2xl font-bold text-foreground\">\n * Welcome\n * </Text>\n * </ScreenContainer>\n * ```\n */\nexport function ScreenContainer({\n children,\n edges = [\"top\", \"left\", \"right\"],\n className,\n containerClassName,\n safeAreaClassName,\n style,\n ...props\n}: ScreenContainerProps) {\n return (\n <View\n className={cn(\n \"flex-1\",\n \"bg-background\",\n containerClassName\n )}\n {...props}\n >\n <SafeAreaView\n edges={edges}\n className={cn(\"flex-1\", safeAreaClassName)}\n style={style}\n >\n <View className={cn(\"flex-1\", className)}>{children}</View>\n </SafeAreaView>\n </View>\n );\n}",
"app/(tabs)/_layout.tsx": "import { Tabs } from \"expo-router\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\n\nimport { HapticTab } from \"@/components/haptic-tab\";\nimport { IconSymbol } from \"@/components/ui/icon-symbol\";\nimport { Platform } from \"react-native\";\nimport { useColors } from \"@/hooks/use-colors\";\n\nexport default function TabLayout() {\n const colors = useColors();\n const insets = useSafeAreaInsets();\n const bottomPadding = Platform.OS === \"web\" ? 12 : Math.max(insets.bottom, 8);\n const tabBarHeight = 56 + bottomPadding;\n\n return (\n <Tabs\n screenOptions={{\n tabBarActiveTintColor: colors.tint,\n headerShown: false,\n tabBarButton: HapticTab,\n tabBarStyle: {\n paddingTop: 8,\n paddingBottom: bottomPadding,\n height: tabBarHeight,\n backgroundColor: colors.background,\n borderTopColor: colors.border,\n borderTopWidth: 0.5,\n },\n }}\n >\n <Tabs.Screen\n name=\"index\"\n options={{\n title: \"Home\",\n tabBarIcon: ({ color }) => <IconSymbol size={28} name=\"house.fill\" color={color} />,\n }}\n />\n </Tabs>\n );\n}",
"app/(tabs)/index.tsx": "import { ScrollView, Text, View, TouchableOpacity } from \"react-native\";\n\nimport { ScreenContainer } from \"@/components/screen-container\";\n\n/**\n * Home Screen - NativeWind Example\n *\n * This template uses NativeWind (Tailwind CSS for React Native).\n * You can use familiar Tailwind classes directly in className props.\n *\n * Key patterns:\n * - Use `className` instead of `style` for most styling\n * - Theme colors: use tokens directly (bg-background, text-foreground, bg-primary, etc.); no dark: prefix needed\n * - Responsive: standard Tailwind breakpoints work on web\n * - Custom colors defined in tailwind.config.js\n */\nexport default function HomeScreen() {\n return (\n <ScreenContainer className=\"p-6\">\n <ScrollView contentContainerStyle={{ flexGrow: 1 }}>\n <View className=\"flex-1 gap-8\">\n {/* Hero Section */}\n <View className=\"items-center gap-2\">\n <Text className=\"text-4xl font-bold text-foreground\">Welcome</Text>\n <Text className=\"text-base text-muted text-center\">\n Edit app/(tabs)/index.tsx to get started\n </Text>\n </View>\n\n {/* Example Card */}\n <View className=\"w-full max-w-sm self-center bg-surface rounded-2xl p-6 shadow-sm border border-border\">\n <Text className=\"text-lg font-semibold text-foreground mb-2\">NativeWind Ready</Text>\n <Text className=\"text-sm text-muted leading-relaxed\">\n Use Tailwind CSS classes directly in your React Native components.\n </Text>\n </View>\n\n {/* Example Button */}\n <View className=\"items-center\">\n <TouchableOpacity className=\"bg-primary px-6 py-3 rounded-full active:opacity-80\">\n <Text className=\"text-background font-semibold\">Get Started</Text>\n </TouchableOpacity>\n </View>\n </View>\n </ScrollView>\n </ScreenContainer>\n );\n}",
"lib/utils.ts": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Combines class names using clsx and tailwind-merge.\n * This ensures Tailwind classes are properly merged without conflicts.\n *\n * Usage:\n * ```tsx\n * cn(\"px-4 py-2\", isActive && \"bg-primary\", className)\n * ```\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}",
"hooks/use-colors.ts": "import { Colors, type ColorScheme, type ThemeColorPalette } from \"@/constants/theme\";\nimport { useColorScheme } from \"./use-color-scheme\";\n\n/**\n * Returns the current theme's color palette.\n * Usage: const colors = useColors(); then colors.text, colors.background, etc.\n */\nexport function useColors(colorSchemeOverride?: ColorScheme): ThemeColorPalette {\n const colorSchema = useColorScheme();\n const scheme = (colorSchemeOverride ?? colorSchema ?? \"light\") as ColorScheme;\n return Colors[scheme];\n}",
"components/ui/icon-symbol.tsx": "// Fallback for using MaterialIcons on Android and web.\n\nimport MaterialIcons from \"@expo/vector-icons/MaterialIcons\";\nimport { SymbolWeight, SymbolViewProps } from \"expo-symbols\";\nimport { ComponentProps } from \"react\";\nimport { OpaqueColorValue, type StyleProp, type TextStyle } from \"react-native\";\n\ntype IconMapping = Record<SymbolViewProps[\"name\"], ComponentProps<typeof MaterialIcons>[\"name\"]>;\ntype IconSymbolName = keyof typeof MAPPING;\n\n/**\n * Add your SF Symbols to Material Icons mappings here.\n * - see Material Icons in the [Icons Directory](https://icons.expo.fyi).\n * - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.\n */\nconst MAPPING = {\n \"house.fill\": \"home\",\n \"paperplane.fill\": \"send\",\n \"chevron.left.forwardslash.chevron.right\": \"code\",\n \"chevron.right\": \"chevron-right\",\n} as IconMapping;\n\n/**\n * An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.\n * This ensures a consistent look across platforms, and optimal resource usage.\n * Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.\n */\nexport function IconSymbol({\n name,\n size = 24,\n color,\n style,\n}: {\n name: IconSymbolName;\n size?: number;\n color: string | OpaqueColorValue;\n style?: StyleProp<TextStyle>;\n weight?: SymbolWeight;\n}) {\n return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;\n}",
"tailwind.config.js": "const { themeColors } = require(\"./theme.config\");\nconst plugin = require(\"tailwindcss/plugin\");\n\nconst tailwindColors = Object.fromEntries(\n Object.entries(themeColors).map(([name, swatch]) => [\n name,\n {\n DEFAULT: `var(--color-${name})`,\n light: swatch.light,\n dark: swatch.dark,\n },\n ]),\n);\n\n/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n darkMode: \"class\",\n // Scan all component and app files for Tailwind classes\n content: [\"./app/**/*.{js,ts,tsx}\", \"./components/**/*.{js,ts,tsx}\", \"./lib/**/*.{js,ts,tsx}\", \"./hooks/**/*.{js,ts,tsx}\"],\n\n presets: [require(\"nativewind/preset\")],\n theme: {\n extend: {\n colors: tailwindColors,\n },\n },\n plugins: [\n plugin(({ addVariant }) => {\n addVariant(\"light\", ':root:not([data-theme=\"dark\"]) &');\n addVariant(\"dark\", ':root[data-theme=\"dark\"] &');\n }),\n ],\n};",
"theme.config.js": "/** @type {const} */\nconst themeColors = {\n primary: { light: '#0a7ea4', dark: '#0a7ea4' },\n background: { light: '#ffffff', dark: '#151718' },\n surface: { light: '#f5f5f5', dark: '#1e2022' },\n foreground: { light: '#11181C', dark: '#ECEDEE' },\n muted: { light: '#687076', dark: '#9BA1A6' },\n border: { light: '#E5E7EB', dark: '#334155' },\n success: { light: '#22C55E', dark: '#4ADE80' },\n warning: { light: '#F59E0B', dark: '#FBBF24' },\n error: { light: '#EF4444', dark: '#F87171' },\n};\n\nmodule.exports = { themeColors };",
"app.config.ts": "// Load environment variables with proper priority (system > .env)\nimport \"./scripts/load-env.js\";\nimport type { ExpoConfig } from \"expo/config\";\n\n// Bundle ID format: space.manus.<project_name_dots>.<timestamp>\n// e.g., \"my-app\" created at 2024-01-15 10:30:45 -> \"space.manus.my.app.t20240115103045\"\n// Bundle ID can only contain letters, numbers, and dots\n// Android requires each dot-separated segment to start with a letter\nconst rawBundleId = \"com.app.mahjongaiapp\";\nconst bundleId =\n rawBundleId\n .replace(/[-_]/g, \".\") // Replace hyphens/underscores with dots\n .replace(/[^a-zA-Z0-9.]/g, \"\") // Remove invalid chars\n .replace(/\\.+/g, \".\") // Collapse consecutive dots\n .replace(/^\\.+|\\.+$/g, \"\") // Trim leading/trailing dots\n .toLowerCase()\n .split(\".\")\n .map((segment) => {\n // Android requires each segment to start with a letter\n // Prefix with 'x' if segment starts with a digit\n return /^[a-zA-Z]/.test(segment) ? segment : \"x\" + segment;\n })\n .join(\".\") || \"space.manus.app\";\n// Extract timestamp from bundle ID and prefix with \"manus\" for deep link scheme\n// e.g., \"space.manus.my.app.t20240115103045\" -> \"manus20240115103045\"\nconst timestamp = bundleId.split(\".\").pop()?.replace(/^t/, \"\") ?? \"\";\nconst schemeFromBundleId = `manus${timestamp}`;\n\nconst env = {\n // App branding - update these values directly (do not use env vars)\n appName: \"Mahjong AI\",\n appSlug: \"mahjong-ai-app\",\n // S3 URL of the app logo - set this to the URL returned by generate_image when creating custom logo\n // Leave empty to use the default icon from assets/images/icon.png\n logoUrl: \"\",\n scheme: schemeFromBundleId,\n iosBundleId: bundleId,\n androidPackage: bundleId,\n};\n\nconst config: ExpoConfig = {\n name: env.appName,\n slug: env.appSlug,\n version: \"1.0.0\",\n orientation: \"portrait\",\n icon: \"./assets/images/icon.png\",\n scheme: env.scheme,\n userInterfaceStyle: \"automatic\",\n newArchEnabled: true,\n ios: {\n supportsTablet: true,\n bundleIdentifier: env.iosBundleId,\n \"infoPlist\": {\n \"ITSAppUsesNonExemptEncryption\": false\n }\n },\n android: {\n adaptiveIcon: {\n backgroundColor: \"#E6F4FE\",\n foregroundImage: \"./assets/images/android-icon-foreground.png\",\n backgroundImage: \"./assets/images/android-icon-background.png\",\n monochromeImage: \"./assets/images/android-icon-monochrome.png\",\n },\n edgeToEdgeEnabled: true,\n predictiveBackGestureEnabled: false,\n package: env.androidPackage,\n permissions: [\"POST_NOTIFICATIONS\"],\n intentFilters: [\n {\n action: \"VIEW\",\n autoVerify: true,\n data: [\n {\n scheme: env.scheme,\n host: \"*\",\n },\n ],\n category: [\"BROWSABLE\", \"DEFAULT\"],\n },\n ],\n },\n web: {\n bundler: \"metro\",\n output: \"static\",\n favicon: \"./assets/images/favicon.png\",\n },\n plugins: [\n \"expo-router\",\n [\n \"expo-audio\",\n {\n microphonePermission: \"Allow $(PRODUCT_NAME) to access your microphone.\",\n },\n ],\n [\n \"expo-video\",\n {\n supportsBackgroundPlayback: true,\n supportsPictureInPicture: true,\n },\n ],\n [\n \"expo-splash-screen\",\n {\n image: \"./assets/images/splash-icon.png\",\n imageWidth: 200,\n resizeMode: \"contain\",\n backgroundColor: \"#ffffff\",\n dark: {\n backgroundColor: \"#000000\",\n },\n },\n ],\n [\n \"expo-build-properties\",\n {\n android: {\n buildArchs: [\"armeabi-v7a\", \"arm64-v8a\"],\n minSdkVersion: 24,\n },\n },\n ],\n ],\n experiments: {\n typedRoutes: true,\n reactCompiler: true,\n },\n};\n\nexport default config;",
"package.json": "{\n \"name\": \"app-template\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"main\": \"expo-router/entry\",\n \"scripts\": {\n \"dev\": \"concurrently -k \\\"pnpm dev:server\\\" \\\"pnpm dev:metro\\\"\",\n \"dev:server\": \"cross-env NODE_ENV=development tsx watch server/_core/index.ts\",\n \"dev:metro\": \"cross-env EXPO_USE_METRO_WORKSPACE_ROOT=1 npx expo start --web --port ${EXPO_PORT:-8081}\",\n \"build\": \"esbuild server/_core/index.ts --platform=node --packages=external --bundle --format=esm --outdir=dist\",\n \"start\": \"NODE_ENV=production node dist/index.js\",\n \"check\": \"tsc --noEmit\",\n \"lint\": \"expo lint\",\n \"format\": \"prettier --write .\",\n \"test\": \"vitest run\",\n \"db:push\": \"drizzle-kit generate && drizzle-kit migrate\",\n \"android\": \"expo start --android\",\n \"ios\": \"expo start --ios\",\n \"qr\": \"node scripts/generate_qr.mjs\"\n },\n \"dependencies\": {\n \"@expo/vector-icons\": \"^15.0.3\",\n \"@react-native-async-storage/async-storage\": \"^2.2.0\",\n \"@react-navigation/bottom-tabs\": \"^7.8.12\",\n \"@react-navigation/elements\": \"^2.9.2\",\n \"@react-navigation/native\": \"^7.1.25\",\n \"@tanstack/react-query\": \"^5.90.12\",\n \"@trpc/client\": \"11.7.2\",\n \"@trpc/react-query\": \"11.7.2\",\n \"@trpc/server\": \"11.7.2\",\n \"axios\": \"^1.13.2\",\n \"clsx\": \"^2.1.1\",\n \"cookie\": \"^1.1.1\",\n \"dotenv\": \"^16.6.1\",\n \"drizzle-orm\": \"^0.44.7\",\n \"expo\": \"~54.0.29\",\n \"expo-audio\": \"~1.1.0\",\n \"expo-build-properties\": \"^1.0.10\",\n \"expo-constants\": \"~18.0.12\",\n \"expo-font\": \"~14.0.10\",\n \"expo-haptics\": \"~15.0.8\",\n \"expo-image\": \"~3.0.11\",\n \"expo-keep-awake\": \"~15.0.8\",\n \"expo-linking\": \"~8.0.10\",\n \"expo-notifications\": \"~0.32.15\",\n \"expo-router\": \"~6.0.19\",\n \"expo-secure-store\": \"~15.0.8\",\n \"expo-splash-screen\": \"~31.0.12\",\n \"expo-status-bar\": \"~3.0.9\",\n \"expo-symbols\": \"~1.0.8\",\n \"expo-system-ui\": \"~6.0.9\",\n \"expo-video\": \"~3.0.15\",\n \"expo-web-browser\": \"~15.0.10\",\n \"express\": \"^4.22.1\",\n \"jose\": \"6.1.0\",\n \"mysql2\": \"^3.16.0\",\n \"nativewind\": \"^4.2.1\",\n \"react\": \"19.1.0\",\n \"react-dom\": \"19.1.0\",\n \"react-native\": \"0.81.5\",\n \"react-native-gesture-handler\": \"~2.28.0\",\n \"react-native-reanimated\": \"~4.1.6\",\n \"react-native-safe-area-context\": \"~5.6.2\",\n \"react-native-screens\": \"~4.16.0\",\n \"react-native-svg\": \"15.12.1\",\n \"react-native-web\": \"~0.21.2\",\n \"react-native-worklets\": \"0.5.1\",\n \"superjson\": \"^1.13.3\",\n \"tailwind-merge\": \"^2.6.0\",\n \"zod\": \"^4.2.1\"\n },\n \"devDependencies\": {\n \"@expo/ngrok\": \"^4.1.3\",\n \"@types/cookie\": \"^0.6.0\",\n \"@types/express\": \"^4.17.25\",\n \"@types/node\": \"^22.19.3\",\n \"@types/qrcode\": \"^1.5.6\",\n \"@types/react\": \"~19.1.17\",\n \"concurrently\": \"^9.2.1\",\n \"cross-env\": \"^7.0.3\",\n \"drizzle-kit\": \"^0.31.8\",\n \"esbuild\": \"^0.25.12\",\n \"eslint\": \"^9.39.2\",\n \"eslint-config-expo\": \"~10.0.0\",\n \"prettier\": \"^3.7.4\",\n \"qrcode\": \"^1.5.4\",\n \"tailwindcss\": \"^3.4.17\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"~5.9.3\",\n \"vitest\": \"^2.1.9\"\n },\n \"packageManager\": \"pnpm@9.12.0\"\n}"
}
}