refactor to turbo

This commit is contained in:
Jorrin
2024-02-03 20:33:27 +01:00
parent fc5c60f85b
commit 28467cdf24
113 changed files with 3744 additions and 8508 deletions

View File

@@ -0,0 +1,4 @@
{
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
}

47
apps/expo/app.config.ts Normal file
View File

@@ -0,0 +1,47 @@
import type { ExpoConfig } from "expo/config";
const defineConfig = (): ExpoConfig => ({
name: "movie-web",
slug: "mw-mobile",
scheme: "dev.movieweb.app",
version: "1.0.0",
orientation: "portrait",
icon: "./assets/icon.png",
userInterfaceStyle: "automatic",
splash: {
image: "./assets/images/splash.png",
resizeMode: "contain",
backgroundColor: "#ffffff",
},
updates: {
fallbackToCacheTimeout: 0,
},
assetBundlePatterns: ["**/*"],
ios: {
bundleIdentifier: "dev.movieweb.app",
supportsTablet: true,
},
android: {
package: "dev.movieweb.app",
adaptiveIcon: {
foregroundImage: "./assets/images/adaptive-icon.png",
backgroundColor: "#FFFFFF",
},
},
web: {
favicon: "./assets/images/favicon.png",
bundler: "metro",
},
// extra: {
// eas: {
// projectId: "your-eas-project-id",
// },
// },
experiments: {
tsconfigPaths: true,
typedRoutes: true,
},
plugins: ["expo-router"],
});
export default defineConfig;

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

11
apps/expo/babel.config.js Normal file
View File

@@ -0,0 +1,11 @@
/** @type {import("@babel/core").ConfigFunction} */
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
plugins: ["react-native-reanimated/plugin"],
};
};

View File

@@ -1,28 +1,31 @@
{
"cli": {
"version": ">= 4.1.2"
},
"build": {
"production": {
"android": {
"buildType": "app-bundle"
"base": {
"node": "18.16.1",
"ios": {
"resourceClass": "m-medium"
}
},
"development": {
"extends": "base",
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"extends": "base",
"distribution": "internal",
"ios": {
"simulator": true
},
"android": {
"buildType": "apk"
}
},
"production": {
"extends": "base"
}
},
"submit": {
"production": {}
},
"cli": {
"version": ">= 5.2.0"
}
}

60
apps/expo/metro.config.js Normal file
View File

@@ -0,0 +1,60 @@
// Learn more: https://docs.expo.dev/guides/monorepos/
const { getDefaultConfig } = require("expo/metro-config");
const { FileStore } = require("metro-cache");
const { withNativeWind } = require("nativewind/metro");
const path = require("path");
module.exports = withTurborepoManagedCache(
withMonorepoPaths(
withNativeWind(
getDefaultConfig(__dirname, {
isCSSEnabled: true,
}),
{
input: "./src/app/styles/global.css",
configPath: "./tailwind.config.ts",
},
),
),
);
/**
* Add the monorepo paths to the Metro config.
* This allows Metro to resolve modules from the monorepo.
*
* @see https://docs.expo.dev/guides/monorepos/#modify-the-metro-config
* @param {import('expo/metro-config').MetroConfig} config
* @returns {import('expo/metro-config').MetroConfig}
*/
function withMonorepoPaths(config) {
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
// #1 - Watch all files in the monorepo
config.watchFolders = [workspaceRoot];
// #2 - Resolve modules within the project's `node_modules` first, then all monorepo modules
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
return config;
}
/**
* Move the Metro cache to the `node_modules/.cache/metro` folder.
* This repository configured Turborepo to use this cache location as well.
* If you have any environment variables, you can configure Turborepo to invalidate it when needed.
*
* @see https://turbo.build/repo/docs/reference/configuration#env
* @param {import('expo/metro-config').MetroConfig} config
* @returns {import('expo/metro-config').MetroConfig}
*/
function withTurborepoManagedCache(config) {
config.cacheStores = [
new FileStore({ root: path.join(__dirname, "node_modules/.cache/metro") }),
];
return config;
}

66
apps/expo/package.json Normal file
View File

@@ -0,0 +1,66 @@
{
"name": "@movie-web/mobile",
"version": "0.1.0",
"private": true,
"main": "expo-router/entry",
"scripts": {
"clean": "git clean -xdf .expo .turbo node_modules",
"dev": "expo start -c --web",
"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.17.3",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"expo": "~50.0.5",
"expo-constants": "~15.4.5",
"expo-linking": "~6.2.2",
"expo-router": "~3.4.6",
"expo-splash-screen": "~0.26.4",
"expo-status-bar": "~1.11.1",
"expo-web-browser": "^12.8.2",
"nativewind": "~4.0.23",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.73.2",
"react-native-css-interop": "~0.0.13",
"react-native-gesture-handler": "~2.14.0",
"react-native-reanimated": "~3.6.2",
"react-native-safe-area-context": "~4.8.2",
"react-native-screens": "~3.29.0",
"react-native-web": "^0.19.10",
"tailwind-merge": "^2.2.1"
},
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/runtime": "^7.23.9",
"@movie-web/eslint-config": "workspace:^0.2.0",
"@movie-web/prettier-config": "workspace:^0.1.0",
"@movie-web/tailwind-config": "workspace:^0.1.0",
"@movie-web/tsconfig": "workspace:^0.1.0",
"@types/babel__core": "^7.20.5",
"@types/react": "^18.2.48",
"eslint": "^8.56.0",
"prettier": "^3.1.1",
"tailwindcss": "^3.4.0",
"typescript": "^5.3.3"
},
"eslintConfig": {
"root": true,
"extends": [
"@movie-web/eslint-config/base",
"@movie-web/eslint-config/react"
],
"ignorePatterns": [
"expo-plugins/**"
]
},
"prettier": "@movie-web/prettier-config"
}

View File

@@ -1,7 +1,8 @@
import { Tabs } from 'expo-router';
import { Tabs } from "expo-router";
import TabBarIcon from '@/components/TabBarIcon';
import Colors from '@/constants/Colors';
import Colors from "@movie-web/tailwind-config/colors";
import TabBarIcon from "~/components/TabBarIcon";
export default function TabLayout() {
return (
@@ -14,7 +15,7 @@ export default function TabLayout() {
tabBarActiveTintColor: Colors.primary[100],
tabBarStyle: {
backgroundColor: Colors.secondary[700],
borderTopColor: 'transparent',
borderTopColor: "transparent",
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
height: 80,
@@ -33,7 +34,7 @@ export default function TabLayout() {
<Tabs.Screen
name="index"
options={{
title: 'Home',
title: "Home",
tabBarIcon: ({ focused }) => (
<TabBarIcon name="home" focused={focused} />
),
@@ -42,7 +43,7 @@ export default function TabLayout() {
<Tabs.Screen
name="about"
options={{
title: 'About',
title: "About",
tabBarIcon: ({ focused }) => (
<TabBarIcon name="info-circle" focused={focused} />
),
@@ -51,8 +52,8 @@ export default function TabLayout() {
<Tabs.Screen
name="search"
options={{
title: 'Search',
tabBarLabel: '',
title: "Search",
tabBarLabel: "",
tabBarIcon: () => (
<TabBarIcon
className=" bg-primary-400 flex aspect-[1/1] h-14 items-center justify-center rounded-full text-center text-2xl text-white"
@@ -65,7 +66,7 @@ export default function TabLayout() {
<Tabs.Screen
name="settings"
options={{
title: 'Settings',
title: "Settings",
tabBarIcon: ({ focused }) => (
<TabBarIcon name="cog" focused={focused} />
),
@@ -74,7 +75,7 @@ export default function TabLayout() {
<Tabs.Screen
name="account"
options={{
title: 'Account',
title: "Account",
tabBarIcon: ({ focused }) => (
<TabBarIcon name="user" focused={focused} />
),

View File

@@ -1,5 +1,5 @@
import ScreenLayout from '@/components/layout/ScreenLayout';
import { Text } from '@/components/ui/Text';
import ScreenLayout from "~/components/layout/ScreenLayout";
import { Text } from "~/components/ui/Text";
export default function AboutScreen() {
return (

View File

@@ -1,13 +1,13 @@
import ScreenLayout from '@/components/layout/ScreenLayout';
import { Text } from '@/components/ui/Text';
export default function AccountScreen() {
return (
<ScreenLayout
title="Account"
subtitle="Manage your movie web account from here"
>
<Text>Hey Bro! what are you up to?</Text>
</ScreenLayout>
);
}
import ScreenLayout from "~/components/layout/ScreenLayout";
import { Text } from "~/components/ui/Text";
export default function AccountScreen() {
return (
<ScreenLayout
title="Account"
subtitle="Manage your movie web account from here"
>
<Text>Hey Bro! what are you up to?</Text>
</ScreenLayout>
);
}

View File

@@ -1,5 +1,5 @@
import ScreenLayout from '@/components/layout/ScreenLayout';
import { Text } from '@/components/ui/Text';
import ScreenLayout from "~/components/layout/ScreenLayout";
import { Text } from "~/components/ui/Text";
export default function HomeScreen() {
return (

View File

@@ -1,42 +1,41 @@
import { FontAwesome5 } from '@expo/vector-icons';
import { useFocusEffect } from 'expo-router';
import { useCallback, useRef, useState } from 'react';
import { View } from 'react-native';
import { TextInput } from 'react-native-gesture-handler';
import Colors from '@/constants/Colors';
export default function Searchbar() {
const [keyword, setKeyword] = useState('');
const inputRef = useRef<TextInput>(null);
useFocusEffect(
useCallback(() => {
// When the screen is focused
const focus = () => {
setTimeout(() => {
inputRef?.current?.focus();
}, 20);
};
focus();
return focus; // cleanup
}, []),
);
return (
<View className="border-primary-400 focus-within:border-primary-300 mb-6 mt-4 flex-row items-center rounded-full border">
<View className="ml-1 w-12 items-center justify-center">
<FontAwesome5 name="search" size={18} color={Colors.secondary[200]} />
</View>
<TextInput
value={keyword}
autoFocus
onChangeText={(text) => setKeyword(text)}
ref={inputRef}
placeholder="What are you looking for?"
placeholderTextColor={Colors.secondary[200]}
className="w-full rounded-3xl py-3 pr-5 text-white focus-visible:outline-none"
/>
</View>
);
}
import { useCallback, useRef, useState } from "react";
import { View } from "react-native";
import { TextInput } from "react-native-gesture-handler";
import { useFocusEffect } from "expo-router";
import { FontAwesome5 } from "@expo/vector-icons";
import Colors from "@movie-web/tailwind-config/colors";
export default function Searchbar() {
const [keyword, setKeyword] = useState("");
const inputRef = useRef<TextInput>(null);
useFocusEffect(
useCallback(() => {
// When the screen is focused
const focus = () => {
setTimeout(() => {
inputRef?.current?.focus();
}, 20);
};
focus();
return focus; // cleanup
}, []),
);
return (
<View className="border-primary-400 focus-within:border-primary-300 mb-6 mt-4 flex-row items-center rounded-full border">
<View className="ml-1 w-12 items-center justify-center">
<FontAwesome5 name="search" size={18} color={Colors.secondary[200]} />
</View>
<TextInput
value={keyword}
onChangeText={(text) => setKeyword(text)}
ref={inputRef}
placeholder="What are you looking for?"
placeholderTextColor={Colors.secondary[200]}
className="w-full rounded-3xl py-3 pr-5 text-white focus-visible:outline-none"
/>
</View>
);
}

View File

@@ -1,35 +1,34 @@
import { ScrollView, View } from 'react-native';
import Item from '@/components/item/item';
import ScreenLayout from '@/components/layout/ScreenLayout';
import { Text } from '@/components/ui/Text';
import Searchbar from './Searchbar';
export default function SearchScreen() {
return (
<ScrollView>
<ScreenLayout
title={
<View className="flex-row items-center">
<Text className="text-2xl font-bold">Search</Text>
</View>
}
subtitle="Looking for something?"
>
<Searchbar />
<View className="flex w-full flex-1 flex-row flex-wrap justify-start">
<View className="basis-1/2 px-3 pb-3">
<Item />
</View>
<View className="basis-1/2 px-3 pb-3">
<Item />
</View>
<View className="basis-1/2 px-3 pb-3">
<Item />
</View>
</View>
</ScreenLayout>
</ScrollView>
);
}
import { ScrollView, View } from "react-native";
import Item from "~/components/item/item";
import ScreenLayout from "~/components/layout/ScreenLayout";
import { Text } from "~/components/ui/Text";
import Searchbar from "./Searchbar";
export default function SearchScreen() {
return (
<ScrollView>
<ScreenLayout
title={
<View className="flex-row items-center">
<Text className="text-2xl font-bold">Search</Text>
</View>
}
subtitle="Looking for something?"
>
<Searchbar />
<View className="flex w-full flex-1 flex-row flex-wrap justify-start">
<View className="basis-1/2 px-3 pb-3">
<Item />
</View>
<View className="basis-1/2 px-3 pb-3">
<Item />
</View>
<View className="basis-1/2 px-3 pb-3">
<Item />
</View>
</View>
</ScreenLayout>
</ScrollView>
);
}

View File

@@ -1,10 +1,10 @@
import ScreenLayout from '@/components/layout/ScreenLayout';
import { Text } from '@/components/ui/Text';
export default function SettingsScreen() {
return (
<ScreenLayout title="Settings" subtitle="Need to change something?">
<Text>Settings would be listed in here. Coming soon</Text>
</ScreenLayout>
);
}
import ScreenLayout from "~/components/layout/ScreenLayout";
import { Text } from "~/components/ui/Text";
export default function SettingsScreen() {
return (
<ScreenLayout title="Settings" subtitle="Need to change something?">
<Text>Settings would be listed in here. Coming soon</Text>
</ScreenLayout>
);
}

View File

@@ -1,4 +1,4 @@
import { ScrollViewStyleReset } from 'expo-router/html';
import { ScrollViewStyleReset } from "expo-router/html";
// This file is web-only and used to configure the root HTML for every
// web page during static rendering.

View File

@@ -1,12 +1,12 @@
import { Link, Stack } from 'expo-router';
import { View } from 'react-native';
import { View } from "react-native";
import { Link, Stack } from "expo-router";
import { Text } from '@/components/ui/Text';
import { Text } from "~/components/ui/Text";
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: 'Oops!' }} />
<Stack.Screen options={{ title: "Oops!" }} />
<View className="flex-1 items-center justify-center p-5">
<Text className="text-lg font-bold">
This screen doesn&apos;t exist.

View File

@@ -0,0 +1,81 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { useEffect } from "react";
import { useColorScheme } from "react-native";
import { useFonts } from "expo-font";
import { SplashScreen, Stack } from "expo-router";
import FontAwesome from "@expo/vector-icons/FontAwesome";
import {
DarkTheme,
DefaultTheme,
ThemeProvider,
} from "@react-navigation/native";
import Colors from "@movie-web/tailwind-config/colors";
import "./styles/global.css";
export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
} from "expo-router";
export const unstable_settings = {
// Ensure that reloading on `/modal` keeps a back button present.
initialRouteName: "(tabs)/index",
};
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync().catch(() => {
/* reloading the app might trigger this, so it's safe to ignore */
});
export default function RootLayout() {
const [loaded, error] = useFonts({
OpenSansRegular: require("../../assets/fonts/OpenSans-Regular.ttf"),
OpenSansLight: require("../../assets/fonts/OpenSans-Light.ttf"),
OpenSansMedium: require("../../assets/fonts/OpenSans-Medium.ttf"),
OpenSansBold: require("../../assets/fonts/OpenSans-Bold.ttf"),
OpenSansSemiBold: require("../../assets/fonts/OpenSans-SemiBold.ttf"),
OpenSansExtra: require("../../assets/fonts/OpenSans-ExtraBold.ttf"),
...FontAwesome.font,
});
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
useEffect(() => {
if (error) throw error;
}, [error]);
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync().catch(() => {
/* reloading the app might trigger this, so it's safe to ignore */
});
}
}, [loaded]);
if (!loaded) {
return null;
}
return <RootLayoutNav />;
}
function RootLayoutNav() {
const colorScheme = useColorScheme();
return (
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
<Stack
screenOptions={{
gestureEnabled: true,
headerShown: false,
contentStyle: {
backgroundColor: Colors.background,
},
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
</ThemeProvider>
);
}

View File

@@ -0,0 +1,30 @@
import React from "react";
import { Platform } from "react-native";
import { Link } from "expo-router";
import * as WebBrowser from "expo-web-browser";
export function ExternalLink(
props: Omit<React.ComponentProps<typeof Link>, "href"> & { href: string },
) {
return (
<Link
hrefAttrs={{
// On web, launch the link in a new tab.
target: "_blank",
}}
{...props}
// @ts-expect-error href is not a valid prop for Link, but we're using it here to pass the URL to the web browser.
href={props.href}
onPress={(e) => {
if (Platform.OS !== "web") {
// Prevent the default behavior of linking to the default browser on native.
e.preventDefault();
// Open the link in an in-app browser.
WebBrowser.openBrowserAsync(props.href).catch((error) => {
console.error("Failed to open browser", error);
});
}
}}
/>
);
}

View File

@@ -1,6 +1,6 @@
import { FontAwesome } from '@expo/vector-icons';
import { FontAwesome } from "@expo/vector-icons";
import Colors from '@/constants/Colors';
import Colors from "@movie-web/tailwind-config/colors";
type Props = {
focused?: boolean;

View File

@@ -1,7 +1,7 @@
import { Image, View } from 'react-native';
import { Image, View } from "react-native";
import { Text } from '@/components/ui/Text';
import { TMDB_POSTER_PATH } from '@/constants/General';
import { TMDB_POSTER_PATH } from "~/app/constants/General";
import { Text } from "~/components/ui/Text";
export default function Item() {
return (

View File

@@ -1,20 +1,20 @@
import { View } from 'react-native';
import { View } from "react-native";
import { Text } from '@/components/ui/Text';
import { Text } from "~/components/ui/Text";
type Props = {
interface Props {
title?: React.ReactNode | string;
subtitle?: string;
children?: React.ReactNode;
};
}
export default function ScreenLayout({ title, subtitle, children }: Props) {
return (
<View className="bg-shade-900 flex-1 p-12">
{typeof title === 'string' && (
{typeof title === "string" && (
<Text className="text-2xl font-bold">{title}</Text>
)}
{typeof title !== 'string' && title}
{typeof title !== "string" && title}
<Text className="mt-1 text-sm font-bold">{subtitle}</Text>
<View className="py-3">{children}</View>
</View>

View File

@@ -0,0 +1,18 @@
import type { TextProps } from "react-native";
import { Text as RNText } from "react-native";
import { cva } from "class-variance-authority";
import { cn } from "~/app/lib/utils";
const textVariants = cva("text-white");
export function Text({ className, ...props }: TextProps) {
return (
<RNText
className={cn(className, textVariants(), {
"font-sans": !className?.includes("font-"),
})}
{...props}
/>
);
}

View File

@@ -0,0 +1,7 @@
import type { ClassValue } from "clsx";
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -0,0 +1,28 @@
import type { Config } from "tailwindcss";
// @ts-expect-error - no types
import nativewind from "nativewind/preset";
import baseConfig from "@movie-web/tailwind-config/native";
export default {
content: ["./src/**/*.{ts,tsx}"],
presets: [
baseConfig,
nativewind,
{
theme: {
extend: {
fontFamily: {
sans: ["OpenSansRegular"],
thin: ["OpenSansLight"],
normal: ["OpenSansRegular"],
medium: ["OpenSansMedium"],
semibold: ["OpenSansSemiBold"],
bold: ["OpenSansBold"],
extrabold: ["OpenSansExtra"],
},
},
},
},
],
} satisfies Config;

16
apps/expo/tsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"extends": ["@movie-web/tsconfig/base.json"],
"compilerOptions": {
"incremental": true,
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"],
"~/components/*": ["./src/app/components/*"]
},
"jsx": "react-native",
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
"types": ["nativewind/types"]
},
"include": ["src", "*.ts", "*.js", ".expo/types/**/*.ts", "expo-env.d.ts"],
"exclude": ["node_modules"]
}

View File

@@ -1,11 +0,0 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
},
],
],
"plugins": [],
}

View File

@@ -1,89 +0,0 @@
{
"testRunner": {
"args": {
"$0": "jest",
"config": "./jest.config.json"
},
"jest": {
"setupTimeout": 120000
}
},
"apps": {
"ios.debug": {
"type": "ios.app",
"build": "cd ../../apps/mobile/ios && xcodebuild -workspace movieweb.xcworkspace -scheme movieweb -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
"binaryPath": "../../apps/mobile/ios/build/Build/Products/Debug-iphonesimulator/movieweb.app"
},
"ios.release": {
"type": "ios.app",
"build": "cd ../../apps/mobile/ios && xcodebuild -workspace movieweb.xcworkspace -scheme movieweb -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
"binaryPath": "../../apps/mobile/ios/build/Build/Products/Release-iphonesimulator/movieweb.app"
},
"ios.local": {
"type": "ios.app",
"build": "pnpm exec nx run mobile:build --platform ios --profile preview --wait --local --no-interactive --output=../../apps/mobile/dist/movieweb.tar.gz",
"binaryPath": "../../apps/mobile/dist/movieweb.app"
},
"android.debug": {
"type": "android.apk",
"build": "cd ../../apps/mobile/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug",
"binaryPath": "../../apps/mobile/android/app/build/outputs/apk/debug/app-debug.apk"
},
"android.release": {
"type": "android.apk",
"build": "cd ../../apps/mobile/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release",
"binaryPath": "../../apps/mobile/android/app/build/outputs/apk/release/app-release.apk"
},
"android.local": {
"type": "android.apk",
"build": "pnpm exec nx run mobile:build --platform android --profile preview --wait --local --no-interactive --output=../../apps/mobile/dist/movieweb.apk",
"binaryPath": "../../apps/mobile/dist/movieweb.apk"
}
},
"devices": {
"simulator": {
"type": "ios.simulator",
"device": {
"type": "iPhone 14"
}
},
"emulator": {
"type": "android.emulator",
"device": {
"avdName": "Pixel_4a_API_30"
}
}
},
"configurations": {
"ios.sim.release": {
"device": "simulator",
"app": "ios.release"
},
"ios.sim.debug": {
"device": "simulator",
"app": "ios.debug"
},
"ios.sim.local": {
"device": "simulator",
"app": "ios.local"
},
"android.emu.release": {
"device": "emulator",
"app": "android.release"
},
"android.emu.debug": {
"device": "emulator",
"app": "android.debug"
},
"android.emu.local": {
"device": "emulator",
"app": "android.local"
}
}
}

View File

@@ -1,18 +0,0 @@
{
"extends": ["../../.eslintrc.js"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@@ -1,22 +0,0 @@
{
"preset": "../../jest.preset",
"rootDir": ".",
"testMatch": [
"<rootDir>/src/**/*.test.ts?(x)",
"<rootDir>/src/**/*.spec.ts?(x)"
],
"testTimeout": 120000,
"maxWorkers": 1,
"globalSetup": "detox/runners/jest/globalSetup",
"globalTeardown": "detox/runners/jest/globalTeardown",
"reporters": ["detox/runners/jest/reporter"],
"testEnvironment": "detox/runners/jest/testEnvironment",
"verbose": true,
"setupFilesAfterEnv": ["<rootDir>/test-setup.ts"],
"transform": {
"^.+\\.(ts|js|html)$": [
"ts-jest",
{ "tsconfig": "<rootDir>/tsconfig.e2e.json" }
]
}
}

View File

@@ -1,76 +0,0 @@
{
"name": "mobile-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/mobile-e2e/src",
"projectType": "application",
"targets": {
"build-ios": {
"executor": "@nx/detox:build",
"options": {
"detoxConfiguration": "ios.sim.local"
},
"configurations": {
"bare": {
"detoxConfiguration": "ios.sim.debug"
},
"production": {
"detoxConfiguration": "ios.sim.release"
}
}
},
"test-ios": {
"executor": "@nx/detox:test",
"options": {
"detoxConfiguration": "ios.sim.local",
"buildTarget": "mobile-e2e:build-ios"
},
"configurations": {
"bare": {
"detoxConfiguration": "ios.sim.debug",
"buildTarget": "mobile-e2e:build-ios:bare"
},
"production": {
"detoxConfiguration": "ios.sim.release",
"buildTarget": "mobile-e2e:build-ios:production"
}
}
},
"build-android": {
"executor": "@nx/detox:build",
"options": {
"detoxConfiguration": "android.emu.local"
},
"configurations": {
"bare": {
"detoxConfiguration": "android.emu.debug"
},
"production": {
"detoxConfiguration": "android.emu.release"
}
}
},
"test-android": {
"executor": "@nx/detox:test",
"options": {
"detoxConfiguration": "android.emu.local",
"buildTarget": "mobile-e2e:build-android"
},
"configurations": {
"bare": {
"detoxConfiguration": "android.emu.debug",
"buildTarget": "mobile-e2e:build-android:bare"
},
"production": {
"detoxConfiguration": "android.emu.release",
"buildTarget": "mobile-e2e:build-android:production"
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
},
"tags": [],
"implicitDependencies": ["mobile"]
}

View File

@@ -1,11 +0,0 @@
import { device, element, by, expect } from 'detox';
describe('movieweb', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should display welcome message', async () => {
await expect(element(by.id('heading'))).toHaveText('Welcome movie-web 👋');
});
});

View File

@@ -1,5 +0,0 @@
import { device } from 'detox';
beforeAll(async () => {
await device.launchApp();
});

View File

@@ -1,10 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"sourceMap": false,
"outDir": "../../dist/out-tsc",
"allowJs": true,
"types": ["node", "jest", "detox"]
},
"include": ["src/**/*.ts", "src/**/*.js"]
}

View File

@@ -1,10 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.e2e.json"
}
]
}

View File

@@ -1,37 +0,0 @@
module.exports = {
extends: ['../../.eslintrc.js'],
ignorePatterns: ['/*.js', '/*.d.ts'],
overrides: [
{
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
rules: {},
},
{
files: ['*.ts', '*.tsx'],
rules: {},
},
{
files: ['*.js', '*.jsx'],
rules: {},
},
],
rules: {
'react/jsx-uses-react': 'off',
'react/react-in-jsx-scope': 'off',
'react/require-default-props': 'off',
'react/destructuring-assignment': 'off',
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
'react/jsx-filename-extension': [
'error',
{ extensions: ['.js', '.tsx', '.jsx'] },
],
'react/jsx-props-no-spreading': 'off',
'react/no-unstable-nested-components': 'off',
'no-use-before-define': 'off',
},
};

View File

@@ -1,18 +0,0 @@
{
"extends": ["../../.eslintrc.js"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@@ -1,44 +0,0 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# dependencies
node_modules/
# Expo
.expo/
dist/
web-build/
android/
ios/
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
# macOS
.DS_Store
*.pem
# local env files
.env*.local
# typescript
*.tsbuildinfo
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli
expo-env.d.ts
# @end expo-cli

View File

@@ -1,49 +0,0 @@
{
"expo": {
"name": "movie-web",
"slug": "mw-mobile",
"version": "1.0.0",
"orientation": "portrait",
"scheme": "dev.movieweb.app",
"userInterfaceStyle": "automatic",
"icon": "./assets/images/icon.png",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"jsEngine": "jsc",
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "dev.movieweb.app"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
},
"package": "dev.movieweb.app"
},
"web": {
"favicon": "./assets/images/favicon.png",
"bundler": "metro"
},
"plugins": [
"expo-router",
[
"@config-plugins/detox",
{
"skipProguard": false,
"subdomains": ["10.0.2.2", "localhost"]
}
]
],
"experiments": {
"typedRoutes": true
}
}
}

View File

@@ -1,78 +0,0 @@
/* eslint-disable global-require */
import FontAwesome from '@expo/vector-icons/FontAwesome';
import {
DarkTheme,
DefaultTheme,
ThemeProvider,
} from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { SplashScreen, Stack } from 'expo-router';
import { useEffect } from 'react';
import { useColorScheme } from 'react-native';
import Colors from '@/constants/Colors';
import './styles/global.css';
export {
// Catch any errors thrown by the Layout component.
ErrorBoundary,
} from 'expo-router';
// eslint-disable-next-line camelcase
export const unstable_settings = {
// Ensure that reloading on `/modal` keeps a back button present.
initialRouteName: '(tabs)/index',
};
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [loaded, error] = useFonts({
OpenSansRegular: require('../assets/fonts/OpenSans-Regular.ttf'),
OpenSansLight: require('../assets/fonts/OpenSans-Light.ttf'),
OpenSansMedium: require('../assets/fonts/OpenSans-Medium.ttf'),
OpenSansBold: require('../assets/fonts/OpenSans-Bold.ttf'),
OpenSansSemiBold: require('../assets/fonts/OpenSans-SemiBold.ttf'),
OpenSansExtra: require('../assets/fonts/OpenSans-ExtraBold.ttf'),
...FontAwesome.font,
});
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
useEffect(() => {
if (error) throw error;
}, [error]);
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return null;
}
return <RootLayoutNav />;
}
function RootLayoutNav() {
const colorScheme = useColorScheme();
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack
screenOptions={{
gestureEnabled: true,
headerShown: false,
contentStyle: {
backgroundColor: Colors.background,
},
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
</ThemeProvider>
);
}

View File

@@ -1,27 +0,0 @@
import { Link } from 'expo-router';
import * as WebBrowser from 'expo-web-browser';
import React from 'react';
import { Platform } from 'react-native';
export function ExternalLink(
props: Omit<React.ComponentProps<typeof Link>, 'href'> & { href: string },
) {
return (
<Link
hrefAttrs={{
// On web, launch the link in a new tab.
target: '_blank',
}}
{...props}
href={props.href}
onPress={(e) => {
if (Platform.OS !== 'web') {
// Prevent the default behavior of linking to the default browser on native.
e.preventDefault();
// Open the link in an in-app browser.
WebBrowser.openBrowserAsync(props.href as string);
}
}}
/>
);
}

View File

@@ -1,17 +0,0 @@
import { cva } from 'class-variance-authority';
import { Text as RNText, TextProps } from 'react-native';
import { cn } from '@/lib/utils';
const textVariants = cva('text-white');
export function Text({ className, ...props }: TextProps) {
return (
<RNText
className={cn(className, textVariants(), {
'font-sans': !className?.includes('font-'),
})}
{...props}
/>
);
}

View File

@@ -1,14 +0,0 @@
export default {
primary: {
100: '#C082FF',
300: '#8D44D6',
400: '#7831BF',
},
secondary: {
50: '#676790',
200: '#3F3F60',
300: '#32324F',
700: '#131322',
},
background: '#0a0a12',
};

View File

@@ -1,6 +0,0 @@
import { ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -1,14 +0,0 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
'nativewind/babel',
],
plugins: [
// Required for expo-router
'expo-router/babel',
'react-native-reanimated/plugin',
],
};
};

View File

@@ -1,13 +0,0 @@
module.exports = {
displayName: 'mobile',
resolver: '@nx/jest/plugins/resolver',
preset: 'jest-expo',
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)',
],
moduleFileExtensions: ['ts', 'js', 'html', 'tsx', 'jsx'],
setupFilesAfterEnv: ['<rootDir>/test-setup.ts'],
moduleNameMapper: {
'\\.svg$': '@nx/expo/plugins/jest/svg-mock',
},
};

View File

@@ -1,48 +0,0 @@
const { withNxMetro } = require('@nx/expo');
const { getDefaultConfig } = require('@expo/metro-config');
const { mergeConfig } = require('metro-config');
const { withNativeWind } = require('nativewind/metro');
const exclusionList = require('metro-config/src/defaults/exclusionList');
const defaultConfig = getDefaultConfig(__dirname, {
isCSSEnabled: true,
});
const { assetExts, sourceExts } = defaultConfig.resolver;
/**
* Metro configuration
* https://facebook.github.io/metro/docs/configuration
*
* @type {import('metro-config').MetroConfig}
*/
const customConfig = {
transformer: {
babelTransformerPath: require.resolve('react-native-svg-transformer'),
},
resolver: {
assetExts: assetExts.filter((ext) => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg', 'mjs'],
blockList: exclusionList([/^(?!.*node_modules).*\/dist\/.*/]),
// unstable_enableSymlinks: true,
// unstable_enablePackageExports: true,
},
};
const nativeWindConfig = withNativeWind(
mergeConfig(defaultConfig, customConfig),
{
input: './app/styles/global.css',
configPath: './tailwind.config.ts',
},
);
const nxConfig = withNxMetro(nativeWindConfig, {
// Change this to true to see debugging info.
// Useful if you have issues resolving modules
debug: false,
// all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx', 'json'
extensions: [],
// Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules)
watchFolders: [],
});
module.exports = nxConfig;

View File

@@ -1,77 +0,0 @@
{
"name": "mobile",
"main": "expo-router/entry",
"version": "1.0.0",
"jest": {
"preset": "jest-expo"
},
"private": true,
"dependencies": {
"@babel/core": "*",
"@expo/metro-config": "*",
"@expo/vector-icons": "^13.0.0",
"@nx/expo": "*",
"@react-navigation/native": "^6.1.9",
"@rnx-kit/metro-config": "*",
"@rnx-kit/metro-resolver-symlinks": "*",
"@testing-library/jest-native": "*",
"@testing-library/react-native": "*",
"@types/react": "*",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"eslint-plugin-react": "*",
"eslint-plugin-react-hooks": "*",
"expo": "*",
"expo-font": "^11.4.0",
"expo-linking": "^5.0.2",
"expo-router": "^2.0.14",
"expo-splash-screen": "~0.20.5",
"expo-status-bar": "*",
"expo-system-ui": "^2.6.0",
"expo-web-browser": "^12.5.0",
"jest": "*",
"jest-expo": "*",
"metro": "*",
"metro-config": "*",
"nativewind": "^4.0.23",
"pod-install": "*",
"react": "*",
"react-dom": "18.2.0",
"react-native": "*",
"react-native-gesture-handler": "^2.14.1",
"react-native-reanimated": "~3.3.0",
"react-native-safe-area-context": "^4.8.2",
"react-native-screens": "^3.29.0",
"react-native-svg": "*",
"react-native-svg-transformer": "*",
"react-native-web": "^0.19.10",
"react-test-renderer": "*",
"tailwind-merge": "^2.2.1",
"tailwindcss": "3.3.2",
"typescript": "*",
"prettier-plugin-tailwindcss": "*"
},
"scripts": {
"eas-build-pre-install": "cd ../../ && node tools/scripts/eas-build-pre-install.mjs . apps/mobile && cp pnpm-lock.yaml apps/mobile",
"eas-build-post-install": "cd ../../ && node tools/scripts/eas-build-post-install.mjs . apps/mobile",
"android": "expo run:android",
"ios": "expo run:ios",
"test": "jest --watchAll",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"format": "prettier --write ."
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@rnx-kit/metro-config": "^1.3.14",
"@rnx-kit/metro-resolver-symlinks": "^0.1.34",
"@types/react": "18.0.28",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0",
"jest": "^29.4.1",
"jest-expo": "~49.0.0",
"pod-install": "^0.1.39",
"prettier-plugin-tailwindcss": "^0.5.11",
"react-test-renderer": "18.2.0",
"typescript": "~5.2.2"
}
}

View File

@@ -1,95 +0,0 @@
{
"name": "mobile",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/mobile/src",
"projectType": "application",
"targets": {
"start": {
"executor": "@nx/expo:start",
"dependsOn": ["ensure-symlink", "sync-deps"],
"options": {
"port": 8081,
"clear": true
}
},
"serve": {
"executor": "nx:run-commands",
"options": {
"command": "nx start mobile"
}
},
"run-ios": {
"executor": "@nx/expo:run",
"dependsOn": ["ensure-symlink", "sync-deps"],
"options": {
"platform": "ios"
}
},
"run-android": {
"executor": "@nx/expo:run",
"dependsOn": ["ensure-symlink", "sync-deps"],
"options": {
"platform": "android"
}
},
"build": {
"executor": "@nx/expo:build",
"options": {}
},
"submit": {
"executor": "@nx/expo:submit",
"options": {}
},
"build-list": {
"executor": "@nx/expo:build-list",
"options": {}
},
"sync-deps": {
"executor": "@nx/expo:sync-deps",
"options": {}
},
"ensure-symlink": {
"executor": "@nx/expo:ensure-symlink",
"options": {}
},
"prebuild": {
"executor": "@nx/expo:prebuild",
"dependsOn": ["ensure-symlink", "sync-deps"],
"options": {}
},
"install": {
"executor": "@nx/expo:install",
"options": {}
},
"update": {
"executor": "@nx/expo:update",
"options": {}
},
"export": {
"executor": "@nx/expo:export",
"dependsOn": ["ensure-symlink", "sync-deps"],
"options": {
"platform": "all",
"outputDir": "../../dist/apps/mobile"
}
},
"export-web": {
"executor": "@nx/expo:export",
"options": {
"bundler": "metro"
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "apps/mobile/jest.config.ts"
}
}
},
"tags": []
}

View File

@@ -1,27 +0,0 @@
// @ts-expect-error - no types
import nativewind from 'nativewind/preset';
import type { Config } from 'tailwindcss';
import colors from './app/constants/Colors';
export default {
content: ['./app/**/*.{js,jsx,ts,tsx}'],
presets: [
nativewind,
{
theme: {
extend: {
colors,
fontFamily: {
sans: ['OpenSansRegular'],
thin: ['OpenSansLight'],
normal: ['OpenSansRegular'],
medium: ['OpenSansMedium'],
semibold: ['OpenSansSemiBold'],
bold: ['OpenSansBold'],
extrabold: ['OpenSansExtra'],
},
},
},
},
],
} satisfies Config;

View File

@@ -1 +0,0 @@
import '@testing-library/jest-native/extend-expect';

View File

@@ -1,22 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"composite": true,
"outDir": "../../dist/out-tsc",
"types": ["node"],
},
"files": ["../../node_modules/@nx/expo/typings/svg.d.ts"],
"exclude": [
"jest.config.ts",
"**/*.spec.ts",
"**/*.spec.tsx",
"test-setup.ts"
],
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx",
"tailwind.config.ts"
]
}

View File

@@ -1,30 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"jsx": "react-native",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"skipLibCheck": true,
"resolveJsonModule": true,
"strict": true,
"declaration": true,
"allowJs": true,
"baseUrl": "./",
"paths": {
"@/*": [
"./app/*"
],
}
},
"files": [],
"include": [".expo/types/**/*.ts", "expo-env.d.ts"],
"references": [
{
"path": "./tsconfig.app.json",
},
{
"path": "./tsconfig.spec.json",
},
],
}

View File

@@ -1,21 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"composite": true,
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts"
]
}