feat(create-turbo): create https://github.com/juliusmarminge/acme-corp
This commit is contained in:
4
apps/expo/.expo-shared/assets.json
Normal file
4
apps/expo/.expo-shared/assets.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
|
||||
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
|
||||
}
|
||||
43
apps/expo/app.config.ts
Normal file
43
apps/expo/app.config.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { ExpoConfig } from "@expo/config";
|
||||
|
||||
const defineConfig = (): ExpoConfig => ({
|
||||
name: "expo",
|
||||
slug: "expo",
|
||||
scheme: "expo",
|
||||
version: "0.1.0",
|
||||
orientation: "portrait",
|
||||
icon: "./assets/icon.png",
|
||||
userInterfaceStyle: "light",
|
||||
splash: {
|
||||
image: "./assets/icon.png",
|
||||
resizeMode: "contain",
|
||||
backgroundColor: "#1F104A",
|
||||
},
|
||||
updates: {
|
||||
fallbackToCacheTimeout: 0,
|
||||
},
|
||||
assetBundlePatterns: ["**/*"],
|
||||
ios: {
|
||||
bundleIdentifier: "your.bundle.identifier",
|
||||
supportsTablet: true,
|
||||
},
|
||||
android: {
|
||||
package: "your.bundle.identifier",
|
||||
adaptiveIcon: {
|
||||
foregroundImage: "./assets/icon.png",
|
||||
backgroundColor: "#1F104A",
|
||||
},
|
||||
},
|
||||
// extra: {
|
||||
// eas: {
|
||||
// projectId: "your-eas-project-id",
|
||||
// },
|
||||
// },
|
||||
experiments: {
|
||||
tsconfigPaths: true,
|
||||
typedRoutes: true,
|
||||
},
|
||||
plugins: ["expo-router", "./expo-plugins/with-modify-gradle.js"],
|
||||
});
|
||||
|
||||
export default defineConfig;
|
||||
BIN
apps/expo/assets/icon.png
Normal file
BIN
apps/expo/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
15
apps/expo/babel.config.js
Normal file
15
apps/expo/babel.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/** @type {import("@babel/core").ConfigFunction} */
|
||||
module.exports = function (api) {
|
||||
api.cache.forever();
|
||||
|
||||
return {
|
||||
presets: [
|
||||
["babel-preset-expo", { jsxImportSource: "nativewind" }],
|
||||
"nativewind/babel",
|
||||
],
|
||||
plugins: [
|
||||
require.resolve("expo-router/babel"),
|
||||
require.resolve("react-native-reanimated/plugin"),
|
||||
],
|
||||
};
|
||||
};
|
||||
31
apps/expo/eas.json
Normal file
31
apps/expo/eas.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 4.1.2"
|
||||
},
|
||||
"build": {
|
||||
"base": {
|
||||
"node": "18.16.1",
|
||||
"ios": {
|
||||
"resourceClass": "m-medium"
|
||||
}
|
||||
},
|
||||
"development": {
|
||||
"extends": "base",
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"preview": {
|
||||
"extends": "base",
|
||||
"distribution": "internal",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"extends": "base"
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
44
apps/expo/expo-plugins/with-modify-gradle.js
Normal file
44
apps/expo/expo-plugins/with-modify-gradle.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// This plugin is required for fixing `.apk` build issue
|
||||
// It appends Expo and RN versions into the `build.gradle` file
|
||||
// References:
|
||||
// https://github.com/t3-oss/create-t3-turbo/issues/120
|
||||
// https://github.com/expo/expo/issues/18129
|
||||
|
||||
/** @type {import("@expo/config-plugins").ConfigPlugin} */
|
||||
const defineConfig = (config) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
return require("@expo/config-plugins").withProjectBuildGradle(
|
||||
config,
|
||||
(config) => {
|
||||
if (!config.modResults.contents.includes("ext.getPackageJsonVersion =")) {
|
||||
config.modResults.contents = config.modResults.contents.replace(
|
||||
"buildscript {",
|
||||
`buildscript {
|
||||
ext.getPackageJsonVersion = { packageName ->
|
||||
new File(['node', '--print', "JSON.parse(require('fs').readFileSync(require.resolve('\${packageName}/package.json'), 'utf-8')).version"].execute(null, rootDir).text.trim())
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.modResults.contents.includes("reactNativeVersion =")) {
|
||||
config.modResults.contents = config.modResults.contents.replace(
|
||||
"ext {",
|
||||
`ext {
|
||||
reactNativeVersion = "\${ext.getPackageJsonVersion('react-native')}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.modResults.contents.includes("expoPackageVersion =")) {
|
||||
config.modResults.contents = config.modResults.contents.replace(
|
||||
"ext {",
|
||||
`ext {
|
||||
expoPackageVersion = "\${ext.getPackageJsonVersion('expo')}"`,
|
||||
);
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = defineConfig;
|
||||
29
apps/expo/metro.config.js
Normal file
29
apps/expo/metro.config.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Learn more: https://docs.expo.dev/guides/monorepos/
|
||||
const { getDefaultConfig } = require("@expo/metro-config");
|
||||
const { withNativeWind } = require("nativewind/metro");
|
||||
|
||||
const path = require("path");
|
||||
|
||||
const projectRoot = __dirname;
|
||||
const workspaceRoot = path.resolve(projectRoot, "../..");
|
||||
|
||||
// Create the default Metro config
|
||||
const config = getDefaultConfig(projectRoot, { isCSSEnabled: true });
|
||||
|
||||
if (config.resolver) {
|
||||
// 1. Watch all files within the monorepo
|
||||
config.watchFolders = [workspaceRoot];
|
||||
// 2. Let Metro know where to resolve packages and in what order
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
|
||||
config.resolver.disableHierarchicalLookup = true;
|
||||
}
|
||||
|
||||
// @ts-expect-error - FIXME: type is mismatching?
|
||||
module.exports = withNativeWind(config, {
|
||||
input: "./src/styles.css",
|
||||
configPath: "./tailwind.config.ts",
|
||||
});
|
||||
68
apps/expo/package.json
Normal file
68
apps/expo/package.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "@acme/expo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "expo-router/entry",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .expo .turbo node_modules",
|
||||
"dev": "expo start --ios",
|
||||
"dev:android": "expo start --android",
|
||||
"dev:ios": "expo start --ios",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/metro-config": "^0.10.7",
|
||||
"@shopify/flash-list": "1.4.3",
|
||||
"@tanstack/react-query": "^5.17.15",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/react-query": "next",
|
||||
"@trpc/server": "next",
|
||||
"expo": "^49.0.22",
|
||||
"expo-constants": "~14.4.2",
|
||||
"expo-linking": "~5.0.2",
|
||||
"expo-router": "2.0.14",
|
||||
"expo-splash-screen": "~0.22.0",
|
||||
"expo-status-bar": "~1.7.1",
|
||||
"nativewind": "^4.0.23",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.73.1",
|
||||
"react-native-gesture-handler": "~2.12.0",
|
||||
"react-native-reanimated": "~3.3.0",
|
||||
"react-native-safe-area-context": "4.6.3",
|
||||
"react-native-screens": "~3.22.1",
|
||||
"superjson": "2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@acme/api": "0.1.0",
|
||||
"@acme/eslint-config": "0.2.0",
|
||||
"@acme/prettier-config": "0.1.0",
|
||||
"@acme/tailwind-config": "0.1.0",
|
||||
"@acme/tsconfig": "0.1.0",
|
||||
"@babel/core": "^7.23.7",
|
||||
"@babel/preset-env": "^7.23.8",
|
||||
"@babel/runtime": "^7.23.8",
|
||||
"@expo/config-plugins": "^7.8.4",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"@types/react": "^18.2.48",
|
||||
"eslint": "^8.56.0",
|
||||
"prettier": "^3.2.4",
|
||||
"tailwindcss": "3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@acme/eslint-config/base",
|
||||
"@acme/eslint-config/react"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"expo-plugins/**"
|
||||
]
|
||||
},
|
||||
"prettier": "@acme/prettier-config"
|
||||
}
|
||||
27
apps/expo/src/app/_layout.tsx
Normal file
27
apps/expo/src/app/_layout.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Stack } from "expo-router";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
|
||||
import { TRPCProvider } from "~/utils/api";
|
||||
|
||||
import "../styles.css";
|
||||
|
||||
// This is the main layout of the app
|
||||
// It wraps your pages with the providers they need
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<TRPCProvider>
|
||||
{/*
|
||||
The Stack component displays the current page.
|
||||
It also allows you to configure your screens
|
||||
*/}
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerStyle: {
|
||||
backgroundColor: "#f472b6",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<StatusBar />
|
||||
</TRPCProvider>
|
||||
);
|
||||
}
|
||||
145
apps/expo/src/app/index.tsx
Normal file
145
apps/expo/src/app/index.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
// import { useState } from "react";
|
||||
// import { Button, Pressable, Text, TextInput, View } from "react-native";
|
||||
// import { SafeAreaView } from "react-native-safe-area-context";
|
||||
// import { Link, Stack } from "expo-router";
|
||||
// import { FlashList } from "@shopify/flash-list";
|
||||
|
||||
// import type { RouterOutputs } from "~/utils/api";
|
||||
// import { api } from "~/utils/api";
|
||||
|
||||
// function PostCard(props: {
|
||||
// post: RouterOutputs["post"]["all"][number];
|
||||
// onDelete: () => void;
|
||||
// }) {
|
||||
// return (
|
||||
// <View className="flex flex-row rounded-lg bg-white/10 p-4">
|
||||
// <View className="flex-grow">
|
||||
// <Link
|
||||
// asChild
|
||||
// href={{
|
||||
// pathname: "/post/[id]",
|
||||
// params: { id: props.post.id },
|
||||
// }}
|
||||
// >
|
||||
// <Pressable>
|
||||
// <Text className="text-xl font-semibold text-pink-400">
|
||||
// {props.post.title}
|
||||
// </Text>
|
||||
// <Text className="mt-2 text-white">{props.post.content}</Text>
|
||||
// </Pressable>
|
||||
// </Link>
|
||||
// </View>
|
||||
// <Pressable onPress={props.onDelete}>
|
||||
// <Text className="font-bold uppercase text-pink-400">Delete</Text>
|
||||
// </Pressable>
|
||||
// </View>
|
||||
// );
|
||||
// }
|
||||
|
||||
// function CreatePost() {
|
||||
// const utils = api.useUtils();
|
||||
|
||||
// const [title, setTitle] = useState("");
|
||||
// const [content, setContent] = useState("");
|
||||
|
||||
// const { mutate, error } = api.post.create.useMutation({
|
||||
// async onSuccess() {
|
||||
// setTitle("");
|
||||
// setContent("");
|
||||
// await utils.post.all.invalidate();
|
||||
// },
|
||||
// });
|
||||
|
||||
// return (
|
||||
// <View className="mt-4">
|
||||
// <TextInput
|
||||
// className="mb-2 rounded bg-white/10 p-2 text-white"
|
||||
// placeholderTextColor="rgba(255, 255, 255, 0.5)"
|
||||
// value={title}
|
||||
// onChangeText={setTitle}
|
||||
// placeholder="Title"
|
||||
// />
|
||||
// {error?.data?.zodError?.fieldErrors.title && (
|
||||
// <Text className="mb-2 text-red-500">
|
||||
// {error.data.zodError.fieldErrors.title}
|
||||
// </Text>
|
||||
// )}
|
||||
// <TextInput
|
||||
// className="mb-2 rounded bg-white/10 p-2 text-white"
|
||||
// placeholderTextColor="rgba(255, 255, 255, 0.5)"
|
||||
// value={content}
|
||||
// onChangeText={setContent}
|
||||
// placeholder="Content"
|
||||
// />
|
||||
// {error?.data?.zodError?.fieldErrors.content && (
|
||||
// <Text className="mb-2 text-red-500">
|
||||
// {error.data.zodError.fieldErrors.content}
|
||||
// </Text>
|
||||
// )}
|
||||
// <Pressable
|
||||
// className="rounded bg-pink-400 p-2"
|
||||
// onPress={() => {
|
||||
// mutate({
|
||||
// title,
|
||||
// content,
|
||||
// });
|
||||
// }}
|
||||
// >
|
||||
// <Text className="font-semibold text-white">Publish post</Text>
|
||||
// </Pressable>
|
||||
// {error?.data?.code === "UNAUTHORIZED" && (
|
||||
// <Text className="mt-2 text-red-500">
|
||||
// You need to be logged in to create a post
|
||||
// </Text>
|
||||
// )}
|
||||
// </View>
|
||||
// );
|
||||
// }
|
||||
|
||||
// export default function Index() {
|
||||
// const utils = api.useUtils();
|
||||
|
||||
// const postQuery = api.post.all.useQuery();
|
||||
|
||||
// const deletePostMutation = api.post.delete.useMutation({
|
||||
// onSettled: () => utils.post.all.invalidate(),
|
||||
// });
|
||||
|
||||
// return (
|
||||
// <SafeAreaView className="bg-[#1F104A]">
|
||||
// {/* Changes page title visible on the header */}
|
||||
// <Stack.Screen options={{ title: "Home Page" }} />
|
||||
// <View className="h-full w-full p-4">
|
||||
// <Text className="pb-2 text-center text-5xl font-bold text-white">
|
||||
// Create <Text className="text-pink-400">T3</Text> Turbo
|
||||
// </Text>
|
||||
|
||||
// <Button
|
||||
// onPress={() => void utils.post.all.invalidate()}
|
||||
// title="Refresh posts"
|
||||
// color={"#f472b6"}
|
||||
// />
|
||||
|
||||
// <View className="py-2">
|
||||
// <Text className="font-semibold italic text-white">
|
||||
// Press on a post
|
||||
// </Text>
|
||||
// </View>
|
||||
|
||||
// <FlashList
|
||||
// data={postQuery.data}
|
||||
// estimatedItemSize={20}
|
||||
// ItemSeparatorComponent={() => <View className="h-2" />}
|
||||
// renderItem={(p) => (
|
||||
// <PostCard
|
||||
// post={p.item}
|
||||
// onDelete={() => deletePostMutation.mutate(p.item.id)}
|
||||
// />
|
||||
// )}
|
||||
// />
|
||||
|
||||
// <CreatePost />
|
||||
// </View>
|
||||
// </SafeAreaView>
|
||||
// );
|
||||
// }
|
||||
22
apps/expo/src/app/post/[id].tsx
Normal file
22
apps/expo/src/app/post/[id].tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
// import { SafeAreaView, Text, View } from "react-native";
|
||||
// import { Stack, useGlobalSearchParams } from "expo-router";
|
||||
|
||||
// import { api } from "~/utils/api";
|
||||
|
||||
// export default function Post() {
|
||||
// const { id } = useGlobalSearchParams();
|
||||
// if (!id || typeof id !== "string") throw new Error("unreachable");
|
||||
// const { data } = api.post.byId.useQuery({ id: parseInt(id) });
|
||||
|
||||
// if (!data) return null;
|
||||
|
||||
// return (
|
||||
// <SafeAreaView className="bg-[#1F104A]">
|
||||
// <Stack.Screen options={{ title: data.title }} />
|
||||
// <View className="h-full w-full p-4">
|
||||
// <Text className="py-2 text-3xl font-bold text-white">{data.title}</Text>
|
||||
// <Text className="py-4 text-white">{data.content}</Text>
|
||||
// </View>
|
||||
// </SafeAreaView>
|
||||
// );
|
||||
// }
|
||||
3
apps/expo/src/styles.css
Normal file
3
apps/expo/src/styles.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
1
apps/expo/src/types/nativewind-env.d.ts
vendored
Normal file
1
apps/expo/src/types/nativewind-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="nativewind/types" />
|
||||
76
apps/expo/src/utils/api.tsx
Normal file
76
apps/expo/src/utils/api.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { useState } from "react";
|
||||
import Constants from "expo-constants";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { httpBatchLink, loggerLink } from "@trpc/client";
|
||||
import { createTRPCReact } from "@trpc/react-query";
|
||||
import superjson from "superjson";
|
||||
|
||||
import type { AppRouter } from "@acme/api";
|
||||
|
||||
/**
|
||||
* A set of typesafe hooks for consuming your API.
|
||||
*/
|
||||
export const api = createTRPCReact<AppRouter>();
|
||||
export { type RouterInputs, type RouterOutputs } from "@acme/api";
|
||||
|
||||
/**
|
||||
* Extend this function when going to production by
|
||||
* setting the baseUrl to your production API URL.
|
||||
*/
|
||||
const getBaseUrl = () => {
|
||||
/**
|
||||
* Gets the IP address of your host-machine. If it cannot automatically find it,
|
||||
* you'll have to manually set it. NOTE: Port 3000 should work for most but confirm
|
||||
* you don't have anything else running on it, or you'd have to change it.
|
||||
*
|
||||
* **NOTE**: This is only for development. In production, you'll want to set the
|
||||
* baseUrl to your production API URL.
|
||||
*/
|
||||
const debuggerHost = Constants.expoConfig?.hostUri;
|
||||
const localhost = debuggerHost?.split(":")[0];
|
||||
|
||||
if (!localhost) {
|
||||
// return "https://turbo.t3.gg";
|
||||
throw new Error(
|
||||
"Failed to get localhost. Please point to your production server.",
|
||||
);
|
||||
}
|
||||
return `http://${localhost}:3000`;
|
||||
};
|
||||
|
||||
/**
|
||||
* A wrapper for your app that provides the TRPC context.
|
||||
* Use only in _app.tsx
|
||||
*/
|
||||
export function TRPCProvider(props: { children: React.ReactNode }) {
|
||||
const [queryClient] = useState(() => new QueryClient());
|
||||
const [trpcClient] = useState(() =>
|
||||
api.createClient({
|
||||
transformer: superjson, // TODO: Add transforming for Dinero.js
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: `${getBaseUrl()}/api/trpc`,
|
||||
headers() {
|
||||
const headers = new Map<string, string>();
|
||||
headers.set("x-trpc-source", "expo-react");
|
||||
return Object.fromEntries(headers);
|
||||
},
|
||||
}),
|
||||
loggerLink({
|
||||
enabled: (opts) =>
|
||||
process.env.NODE_ENV === "development" ||
|
||||
(opts.direction === "down" && opts.result instanceof Error),
|
||||
colorMode: "ansi",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<api.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{props.children}
|
||||
</QueryClientProvider>
|
||||
</api.Provider>
|
||||
);
|
||||
}
|
||||
10
apps/expo/tailwind.config.ts
Normal file
10
apps/expo/tailwind.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// @ts-expect-error - no types
|
||||
import nativewind from "nativewind/preset";
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
import baseConfig from "@acme/tailwind-config";
|
||||
|
||||
export default {
|
||||
content: ["./src/**/*.{ts,tsx}"],
|
||||
presets: [baseConfig, nativewind],
|
||||
} satisfies Config;
|
||||
21
apps/expo/tsconfig.json
Normal file
21
apps/expo/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@acme/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"],
|
||||
},
|
||||
"jsx": "react-native",
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
||||
"types": ["nativewind/types"],
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"*.ts",
|
||||
"index.tsx",
|
||||
"*.js",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts",
|
||||
],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
||||
Reference in New Issue
Block a user