mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 12:23:24 +00:00
Compare commits
87 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a3f184979e | ||
|
450ef4dc90 | ||
|
4e60002bac | ||
|
7941a35111 | ||
|
519c85a3ac | ||
|
0134c0ff92 | ||
|
4478366855 | ||
|
54e270bf17 | ||
|
cf4076d613 | ||
|
ef78cc3447 | ||
|
1b9fbb4120 | ||
|
e88b7d2051 | ||
|
aa0e374bca | ||
|
271cca3cd5 | ||
|
7c9247dc2c | ||
|
dc6e3f5a7f | ||
|
e3f74aac09 | ||
|
b4e9ff5086 | ||
|
c4a56c1a2a | ||
|
3a4df634cf | ||
|
c2b6b6a555 | ||
|
360bdf4f23 | ||
|
1a9f955a37 | ||
|
94ef89a95f | ||
|
0c1e67291a | ||
|
b800574a26 | ||
|
6e53e00757 | ||
|
a4777e442e | ||
|
415a2541fe | ||
|
ea30054508 | ||
|
d59b485167 | ||
|
a56dac54fe | ||
|
d261779b6d | ||
|
82172727e9 | ||
|
add7c1841d | ||
|
df8bc8a83f | ||
|
28467cdf24 | ||
|
fc5c60f85b | ||
|
f5a5929972 | ||
|
cf17593b57 | ||
|
e7d7b046db | ||
|
a7608b878d | ||
|
5baf4d6b86 | ||
|
e83bf1c806 | ||
|
865cd632d6 | ||
|
89d1310eac | ||
|
8977e3ea2c | ||
|
4c634abc1e | ||
|
26a1b623e7 | ||
|
5e47c5e5f7 | ||
|
9c310c01c8 | ||
|
8a48a1cce4 | ||
|
910c3f4b3b | ||
|
37d21eea56 | ||
|
cd2c07f586 | ||
|
4041b9b393 | ||
|
ad82e72969 | ||
|
1865d2e6a8 | ||
|
8e2cf0f28d | ||
|
eaa9706244 | ||
|
d887e9f207 | ||
|
826ae13777 | ||
|
6ec1a3fb64 | ||
|
f2f46368d9 | ||
|
e64a52f5c3 | ||
|
314e739af5 | ||
|
9f4cb15eba | ||
|
8142f312b6 | ||
|
1f3c358f0a | ||
|
5ffee47224 | ||
|
53297b820c | ||
|
54558e9799 | ||
|
74e9954b9c | ||
|
49318dca38 | ||
|
dcdb59ddd5 | ||
|
20a0fbbcfb | ||
|
9238d58900 | ||
|
9f37eaa006 | ||
|
8f673cc7f3 | ||
|
ea372b1437 | ||
|
817b9ad771 | ||
|
2e1e239be5 | ||
|
3be4830711 | ||
|
326ff1fe92 | ||
|
833a1c8ecd | ||
|
38e0dd87e0 | ||
|
8de5672832 |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @movie-web/core
|
1
.github/CODE_OF_CONDUCT.md
vendored
Normal file
1
.github/CODE_OF_CONDUCT.md
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Please visit the [main document at primary repository](https://github.com/movie-web/movie-web/blob/dev/.github/CODE_OF_CONDUCT.md).
|
1
.github/CONTRIBUTING.md
vendored
Normal file
1
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Please visit the [main document at primary repository](https://github.com/movie-web/movie-web/blob/dev/.github/CONTRIBUTING.md).
|
15
.github/SECURITY.md
vendored
Normal file
15
.github/SECURITY.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The movie-web maintainers only support the latest version of movie-web published at https://movie-web.app.
|
||||
This published version is equivalent to the master branch.
|
||||
|
||||
Support is not provided for any forks or mirrors of movie-web.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
There are two ways you can contact the movie-web maintainers to report a vulnerability:
|
||||
|
||||
- Email [security@movie-web.app](mailto:security@movie-web.app)
|
||||
- Report the vulnerability in the [movie-web Discord server](https://discord.movie-web.app)
|
6
.github/pull_request_template.md
vendored
Normal file
6
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
This pull request resolves #XXX
|
||||
|
||||
- [ ] I have read and agreed to the [code of conduct](https://github.com/movie-web/movie-web/blob/dev/.github/CODE_OF_CONDUCT.md).
|
||||
- [ ] I have read and complied with the [contributing guidelines](https://github.com/movie-web/movie-web/blob/dev/.github/CONTRIBUTING.md).
|
||||
- [ ] What I'm implementing was assigned to me and is an [approved issue](https://github.com/movie-web/movie-web/issues?q=is%3Aopen+is%3Aissue+label%3Aapproved). For reference, please take a look at our [GitHub projects](https://github.com/movie-web/movie-web/projects).
|
||||
- [ ] I have tested all of my changes.
|
13
.github/renovate.json
vendored
Normal file
13
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:base"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchPackagePatterns": ["^@movie-web/"],
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"updateInternalDeps": true,
|
||||
"rangeStrategy": "bump",
|
||||
"automerge": true
|
||||
}
|
95
.github/workflows/build-mobile.yml
vendored
Normal file
95
.github/workflows/build-mobile.yml
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
name: build mobile app
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build Android app
|
||||
run: cd apps/expo && pnpm run apk
|
||||
|
||||
- name: Rename apk
|
||||
run: cd apps/expo && mv android/app/build/outputs/apk/release/app-release.apk android/app/build/outputs/apk/release/movie-web.apk
|
||||
|
||||
- name: Upload movie-web.apk as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: apk
|
||||
path: ./apps/expo/android/app/build/outputs/apk/release/movie-web.apk
|
||||
|
||||
build-ios:
|
||||
runs-on: macos-14
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Xcode Select Version
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.1.0'
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build iOS app
|
||||
run: cd apps/expo && pnpm run ipa
|
||||
|
||||
- name: Export .ipa from .app
|
||||
run: |
|
||||
cd apps/expo
|
||||
mkdir -p ios/build/Build/Products/Release-iphoneos/Payload
|
||||
mv ios/build/Build/Products/Release-iphoneos/movieweb.app ios/build/Build/Products/Release-iphoneos/Payload/
|
||||
cd ios/build/Build/Products/Release-iphoneos
|
||||
zip -r ../../../movie-web.ipa Payload
|
||||
|
||||
- name: Upload movie-web.ipa as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ipa
|
||||
path: ./apps/expo/ios/build/movie-web.ipa
|
53
.github/workflows/ci.yml
vendored
Normal file
53
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
push:
|
||||
branches: ["master"]
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
# You can leverage Vercel Remote Caching with Turbo to speed up your builds
|
||||
# @link https://turborepo.org/docs/core-concepts/remote-caching#remote-caching-on-vercel-builds
|
||||
env:
|
||||
FORCE_COLOR: 3
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint && pnpm lint:ws
|
||||
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Format
|
||||
run: pnpm format
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm typecheck
|
143
.github/workflows/release-mobile.yml
vendored
Normal file
143
.github/workflows/release-mobile.yml
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
name: release mobile app
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump-version:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Automated Version Bump
|
||||
uses: phips28/gh-action-bump-version@v10.1.1
|
||||
with:
|
||||
skip-tag: 'true'
|
||||
commit-message: 'chore: bump mobile version to {{version}} [skip ci]'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [bump-version]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build Android app
|
||||
run: cd apps/expo && pnpm run apk
|
||||
|
||||
- name: Rename apk
|
||||
run: cd apps/expo && mv android/app/build/outputs/apk/release/app-release.apk android/app/build/outputs/apk/release/movie-web.apk
|
||||
|
||||
- name: Upload movie-web.apk as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: apk
|
||||
path: ./apps/expo/android/app/build/outputs/apk/release/movie-web.apk
|
||||
|
||||
build-ios:
|
||||
runs-on: macos-14
|
||||
needs: [bump-version]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Xcode Select Version
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.1.0'
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 21
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
with:
|
||||
version: 8
|
||||
run_install: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build iOS app
|
||||
run: cd apps/expo && pnpm run ipa
|
||||
|
||||
- name: Export .ipa from .app
|
||||
run: |
|
||||
cd apps/expo
|
||||
mkdir -p ios/build/Build/Products/Release-iphoneos/Payload
|
||||
mv ios/build/Build/Products/Release-iphoneos/movieweb.app ios/build/Build/Products/Release-iphoneos/Payload/
|
||||
cd ios/build/Build/Products/Release-iphoneos
|
||||
zip -r ../../../movie-web.ipa Payload
|
||||
|
||||
- name: Upload movie-web.ipa as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ipa
|
||||
path: ./apps/expo/ios/build/movie-web.ipa
|
||||
|
||||
release-app:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-android, build-ios]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Get package version
|
||||
id: package-version
|
||||
uses: martinbeentjes/npm-get-version-action@v1.3.1
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: v${{ steps.package-version.outputs.current-version }}
|
||||
files: |
|
||||
movie-web.apk
|
||||
movie-web.ipa
|
||||
fail_on_unmatched_files: true
|
||||
token: ${{ env.GITHUB_TOKEN }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# nitro
|
||||
.nitro/
|
||||
.output/
|
||||
|
||||
# expo
|
||||
.expo/
|
||||
dist/
|
||||
expo-env.d.ts
|
||||
apps/expo/.gitignore
|
||||
ios/
|
||||
android/
|
||||
|
||||
# production
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# turbo
|
||||
.turbo
|
9
.vscode/extensions.json
vendored
Normal file
9
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"expo.vscode-expo-tools",
|
||||
"esbenp.prettier-vscode",
|
||||
"yoavbls.pretty-ts-errors",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
25
.vscode/settings.json
vendored
Normal file
25
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"eslint.workingDirectories": [
|
||||
{
|
||||
"mode": "auto"
|
||||
}
|
||||
],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"eslint.format.enable": true,
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
],
|
||||
"typescript.preferences.autoImportFileExcludePatterns": [
|
||||
// Should import Text from UI components instead
|
||||
"react-native/Libraries/Text/Text.d.ts"
|
||||
]
|
||||
}
|
72
README.md
72
README.md
@@ -1,2 +1,70 @@
|
||||
# native-app
|
||||
The native app version of movie-web
|
||||
# movie-web native-app
|
||||
|
||||
## About
|
||||
|
||||
It uses [Turborepo](https://turborepo.org) and contains:
|
||||
|
||||
```text
|
||||
.github
|
||||
└─ workflows
|
||||
└─ CI with pnpm cache setup
|
||||
.vscode
|
||||
└─ Recommended extensions and settings for VSCode users
|
||||
apps
|
||||
└─ expo
|
||||
├─ Expo SDK 50
|
||||
├─ React Native using React 18
|
||||
├─ Navigation using Expo Router
|
||||
├─ Tailwind using Nativewind
|
||||
└─ Typesafe API calls using tRPC
|
||||
packages
|
||||
├─ tmdb
|
||||
└─ Typesafe API calls to The Movie Database
|
||||
tooling
|
||||
├─ eslint
|
||||
| └─ shared, fine-grained, eslint presets
|
||||
├─ prettier
|
||||
| └─ shared prettier configuration
|
||||
├─ tailwind
|
||||
| └─ shared tailwind configuration
|
||||
└─ typescript
|
||||
└─ shared tsconfig you can extend from
|
||||
```
|
||||
|
||||
### Configure Expo `dev`-script
|
||||
|
||||
#### Use iOS Simulator
|
||||
|
||||
1. Make sure you have XCode and XCommand Line Tools installed [as shown on expo docs](https://docs.expo.dev/workflow/ios-simulator).
|
||||
|
||||
> **NOTE:** If you just installed XCode, or if you have updated it, you need to open the simulator manually once. Run `npx expo start` in the root dir, and then enter `I` to launch Expo Go. After the manual launch, you can run `pnpm dev` in the root directory.
|
||||
|
||||
```diff
|
||||
+ "dev": "expo start --ios",
|
||||
```
|
||||
|
||||
2. Run `pnpm dev` at the project root folder.
|
||||
|
||||
#### Use Android Emulator
|
||||
|
||||
1. Install Android Studio tools [as shown on expo docs](https://docs.expo.dev/workflow/android-studio-emulator).
|
||||
|
||||
2. Change the `dev` script at `apps/expo/package.json` to open the Android emulator.
|
||||
|
||||
```diff
|
||||
+ "dev": "expo start --android",
|
||||
```
|
||||
|
||||
3. Run `pnpm dev` at the project root folder.
|
||||
|
||||
> **TIP:** It might be easier to run each app in separate terminal windows so you get the logs from each app separately. This is also required if you want your terminals to be interactive, e.g. to access the Expo QR code. You can run `pnpm --filter expo dev` and `pnpm --filter nextjs dev` to run each app in a separate terminal window.
|
||||
|
||||
### 3. When it's time to add a new package
|
||||
|
||||
To add a new package, simply run `pnpm turbo gen init` in the monorepo root. This will prompt you for a package name as well as if you want to install any dependencies to the new package (of course you can also do this yourself later).
|
||||
|
||||
The generator sets up the `package.json`, `tsconfig.json` and a `index.ts`, as well as configures all the necessary configurations for tooling around your package such as formatting, linting and typechecking. When the package is created, you're ready to go build out the package.
|
||||
|
||||
### References
|
||||
|
||||
This app is based on [create-t3-turbo](https://github.com/t3-oss/create-t3-turbo) and [Turborepo](https://turborepo.org).
|
||||
|
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
|
||||
}
|
47
apps/expo/app.config.ts
Normal file
47
apps/expo/app.config.ts
Normal 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: "0.1.0",
|
||||
orientation: "portrait",
|
||||
icon: "./assets/images/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;
|
BIN
apps/expo/assets/fonts/OpenSans-Bold.ttf
Normal file
BIN
apps/expo/assets/fonts/OpenSans-Bold.ttf
Normal file
Binary file not shown.
BIN
apps/expo/assets/fonts/OpenSans-ExtraBold.ttf
Normal file
BIN
apps/expo/assets/fonts/OpenSans-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
apps/expo/assets/fonts/OpenSans-Light.ttf
Normal file
BIN
apps/expo/assets/fonts/OpenSans-Light.ttf
Normal file
Binary file not shown.
BIN
apps/expo/assets/fonts/OpenSans-Medium.ttf
Normal file
BIN
apps/expo/assets/fonts/OpenSans-Medium.ttf
Normal file
Binary file not shown.
BIN
apps/expo/assets/fonts/OpenSans-Regular.ttf
Normal file
BIN
apps/expo/assets/fonts/OpenSans-Regular.ttf
Normal file
Binary file not shown.
BIN
apps/expo/assets/fonts/OpenSans-SemiBold.ttf
Normal file
BIN
apps/expo/assets/fonts/OpenSans-SemiBold.ttf
Normal file
Binary file not shown.
BIN
apps/expo/assets/fonts/SpaceMono-Regular.ttf
Normal file
BIN
apps/expo/assets/fonts/SpaceMono-Regular.ttf
Normal file
Binary file not shown.
BIN
apps/expo/assets/images/adaptive-icon.png
Normal file
BIN
apps/expo/assets/images/adaptive-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
apps/expo/assets/images/favicon.png
Normal file
BIN
apps/expo/assets/images/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
apps/expo/assets/images/icon.png
Normal file
BIN
apps/expo/assets/images/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
apps/expo/assets/images/splash.png
Normal file
BIN
apps/expo/assets/images/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
11
apps/expo/babel.config.js
Normal file
11
apps/expo/babel.config.js
Normal 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"],
|
||||
};
|
||||
};
|
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": {}
|
||||
}
|
||||
}
|
60
apps/expo/metro.config.js
Normal file
60
apps/expo/metro.config.js
Normal 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;
|
||||
}
|
70
apps/expo/package.json
Normal file
70
apps/expo/package.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"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",
|
||||
"dev:android": "expo start -c --android",
|
||||
"dev:ios": "expo start -c --ios",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"apk": "expo prebuild --platform=android && cd android && ./gradlew assembleRelease",
|
||||
"ipa": "expo prebuild --platform=ios && cd ios && xcodebuild -workspace movieweb.xcworkspace -scheme movieweb -sdk iphoneos -configuration Release -derivedDataPath build -destination generic/platform=iOS CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO",
|
||||
"ipa:sim": "expo prebuild --platform=ios && cd ios && xcodebuild -workspace movieweb.xcworkspace -scheme movieweb -sdk iphonesimulator -configuration Release -derivedDataPath build -destination \"generic/platform=iOS Simulator\" CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/metro-config": "^0.17.3",
|
||||
"@movie-web/tmdb": "*",
|
||||
"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.22",
|
||||
"react-native-gesture-handler": "~2.14.1",
|
||||
"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"
|
||||
}
|
85
apps/expo/src/app/(tabs)/_layout.tsx
Normal file
85
apps/expo/src/app/(tabs)/_layout.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { View } from "react-native";
|
||||
import { Tabs } from "expo-router";
|
||||
|
||||
import Colors from "@movie-web/tailwind-config/colors";
|
||||
|
||||
import TabBarIcon from "~/components/TabBarIcon";
|
||||
|
||||
export default function TabLayout() {
|
||||
return (
|
||||
<Tabs
|
||||
sceneContainerStyle={{
|
||||
backgroundColor: Colors.background,
|
||||
}}
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
tabBarActiveTintColor: Colors.primary[100],
|
||||
tabBarStyle: {
|
||||
backgroundColor: Colors.secondary[700],
|
||||
borderTopColor: "transparent",
|
||||
borderTopRightRadius: 20,
|
||||
borderTopLeftRadius: 20,
|
||||
height: 80,
|
||||
},
|
||||
tabBarItemStyle: {
|
||||
paddingVertical: 18,
|
||||
height: 82,
|
||||
},
|
||||
tabBarLabelStyle: [
|
||||
{
|
||||
marginTop: 2,
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Home",
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabBarIcon name="home" focused={focused} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="about"
|
||||
options={{
|
||||
title: "About",
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabBarIcon name="info-circle" focused={focused} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="search"
|
||||
options={{
|
||||
title: "Search",
|
||||
tabBarLabel: "",
|
||||
tabBarIcon: () => (
|
||||
<View className="android:top-2 ios:top-2 flex h-14 w-14 items-center justify-center overflow-hidden rounded-full bg-primary-400 text-center align-middle text-2xl text-white">
|
||||
<TabBarIcon name="search" color="#FFF" />
|
||||
</View>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="settings"
|
||||
options={{
|
||||
title: "Settings",
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabBarIcon name="cog" focused={focused} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="account"
|
||||
options={{
|
||||
title: "Account",
|
||||
tabBarIcon: ({ focused }) => (
|
||||
<TabBarIcon name="user" focused={focused} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
16
apps/expo/src/app/(tabs)/about.tsx
Normal file
16
apps/expo/src/app/(tabs)/about.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function AboutScreen() {
|
||||
return (
|
||||
<ScreenLayout
|
||||
title="About"
|
||||
subtitle="What is movie-web and how content is served?"
|
||||
>
|
||||
<Text>
|
||||
No content is served from movie-web directly and movie web does not host
|
||||
anything.
|
||||
</Text>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
13
apps/expo/src/app/(tabs)/account.tsx
Normal file
13
apps/expo/src/app/(tabs)/account.tsx
Normal file
@@ -0,0 +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>
|
||||
);
|
||||
}
|
10
apps/expo/src/app/(tabs)/index.tsx
Normal file
10
apps/expo/src/app/(tabs)/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function HomeScreen() {
|
||||
return (
|
||||
<ScreenLayout title="Home" subtitle="This is where all magic happens">
|
||||
<Text>Movies will be listed here</Text>
|
||||
</ScreenLayout>
|
||||
);
|
||||
}
|
49
apps/expo/src/app/(tabs)/search/Searchbar.tsx
Normal file
49
apps/expo/src/app/(tabs)/search/Searchbar.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { TextInput, View } from "react-native";
|
||||
import { useFocusEffect } from "expo-router";
|
||||
import { FontAwesome5 } from "@expo/vector-icons";
|
||||
|
||||
import Colors from "@movie-web/tailwind-config/colors";
|
||||
|
||||
export default function Searchbar({
|
||||
onSearchChange,
|
||||
}: {
|
||||
onSearchChange: (text: string) => void;
|
||||
}) {
|
||||
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
|
||||
}, []),
|
||||
);
|
||||
|
||||
const handleChange = (text: string) => {
|
||||
setKeyword(text);
|
||||
onSearchChange(text);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="mb-6 mt-4 flex-row items-center rounded-full border border-primary-400 focus-within:border-primary-300">
|
||||
<View className="ml-1 w-12 items-center justify-center">
|
||||
<FontAwesome5 name="search" size={18} color={Colors.secondary[200]} />
|
||||
</View>
|
||||
<TextInput
|
||||
value={keyword}
|
||||
onChangeText={handleChange}
|
||||
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>
|
||||
);
|
||||
}
|
59
apps/expo/src/app/(tabs)/search/_layout.tsx
Normal file
59
apps/expo/src/app/(tabs)/search/_layout.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { useState } from "react";
|
||||
import { ScrollView, View } from "react-native";
|
||||
|
||||
import { getMediaPoster, searchTitle } from "@movie-web/tmdb";
|
||||
|
||||
import type { ItemData } from "~/components/item/item";
|
||||
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() {
|
||||
const [searchResults, setSearchResults] = useState<ItemData[]>([]);
|
||||
|
||||
const handleSearchChange = async (query: string) => {
|
||||
if (query.length > 0) {
|
||||
const results = await fetchSearchResults(query).catch(() => []);
|
||||
setSearchResults(results);
|
||||
} else {
|
||||
setSearchResults([]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<ScreenLayout
|
||||
title={
|
||||
<View className="flex-row items-center">
|
||||
<Text className="text-2xl font-bold">Search</Text>
|
||||
</View>
|
||||
}
|
||||
subtitle="Looking for something?"
|
||||
>
|
||||
<Searchbar onSearchChange={handleSearchChange} />
|
||||
<View className="flex w-full flex-1 flex-row flex-wrap justify-start">
|
||||
{searchResults.map((item, index) => (
|
||||
<View key={index} className="basis-1/2 px-3 pb-3">
|
||||
<Item data={item} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</ScreenLayout>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchSearchResults(query: string): Promise<ItemData[]> {
|
||||
const results = await searchTitle(query);
|
||||
|
||||
return results.map((result) => ({
|
||||
id: result.id.toString(),
|
||||
title: result.media_type === "tv" ? result.name : result.title,
|
||||
posterUrl: getMediaPoster(result.poster_path),
|
||||
year: new Date(
|
||||
result.media_type === "tv" ? result.first_air_date : result.release_date,
|
||||
).getFullYear(),
|
||||
type: result.media_type,
|
||||
}));
|
||||
}
|
10
apps/expo/src/app/(tabs)/settings.tsx
Normal file
10
apps/expo/src/app/(tabs)/settings.tsx
Normal file
@@ -0,0 +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>
|
||||
);
|
||||
}
|
46
apps/expo/src/app/+html.tsx
Normal file
46
apps/expo/src/app/+html.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
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.
|
||||
// The contents of this function only run in Node.js environments and
|
||||
// do not have access to the DOM or browser APIs.
|
||||
export default function Root({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
{/*
|
||||
This viewport disables scaling which makes the mobile website act more like a native app.
|
||||
However this does reduce built-in accessibility. If you want to enable scaling, use this instead:
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
*/}
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover"
|
||||
/>
|
||||
{/*
|
||||
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
|
||||
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
|
||||
*/}
|
||||
<ScrollViewStyleReset />
|
||||
|
||||
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
|
||||
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
|
||||
{/* Add any additional <head> elements that you want globally available on web... */}
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
const responsiveBackground = `
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
}`;
|
21
apps/expo/src/app/[...missing].tsx
Normal file
21
apps/expo/src/app/[...missing].tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { View } from "react-native";
|
||||
import { Link, Stack } from "expo-router";
|
||||
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: "Oops!" }} />
|
||||
<View className="flex-1 items-center justify-center p-5">
|
||||
<Text className="text-lg font-bold">
|
||||
This screen doesn't exist.
|
||||
</Text>
|
||||
|
||||
<Link href="/" className="mt-4 py-4">
|
||||
<Text className="text-sm text-sky-500">Go to home screen!</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
}
|
81
apps/expo/src/app/_layout.tsx
Normal file
81
apps/expo/src/app/_layout.tsx
Normal 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)",
|
||||
};
|
||||
|
||||
// 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>
|
||||
);
|
||||
}
|
17
apps/expo/src/app/components/TabBarIcon.tsx
Normal file
17
apps/expo/src/app/components/TabBarIcon.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { FontAwesome } from "@expo/vector-icons";
|
||||
|
||||
import Colors from "@movie-web/tailwind-config/colors";
|
||||
|
||||
type Props = {
|
||||
focused?: boolean;
|
||||
} & React.ComponentProps<typeof FontAwesome>;
|
||||
|
||||
export default function TabBarIcon({ focused, ...rest }: Props) {
|
||||
return (
|
||||
<FontAwesome
|
||||
color={focused ? Colors.primary[300] : Colors.secondary[300]}
|
||||
size={24}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
36
apps/expo/src/app/components/item/item.tsx
Normal file
36
apps/expo/src/app/components/item/item.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Image, View } from "react-native";
|
||||
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
export interface ItemData {
|
||||
id: string;
|
||||
title: string;
|
||||
type: "movie" | "tv";
|
||||
year: number;
|
||||
posterUrl: string;
|
||||
}
|
||||
|
||||
export default function Item({ data }: { data: ItemData }) {
|
||||
const { title, type, year, posterUrl } = data;
|
||||
|
||||
return (
|
||||
<View className="w-full">
|
||||
<View className="mb-2 aspect-[9/14] w-full overflow-hidden rounded-2xl">
|
||||
<Image
|
||||
source={{
|
||||
uri: posterUrl,
|
||||
}}
|
||||
className="h-full w-full"
|
||||
/>
|
||||
</View>
|
||||
<Text className="font-bold">{title}</Text>
|
||||
<View className="flex-row items-center gap-3">
|
||||
<Text className="text-xs text-gray-600">
|
||||
{type === "tv" ? "Show" : "Movie"}
|
||||
</Text>
|
||||
<View className="h-1 w-1 rounded-3xl bg-gray-600" />
|
||||
<Text className="text-sm text-gray-600">{year}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
22
apps/expo/src/app/components/layout/ScreenLayout.tsx
Normal file
22
apps/expo/src/app/components/layout/ScreenLayout.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { View } from "react-native";
|
||||
|
||||
import { Text } from "~/components/ui/Text";
|
||||
|
||||
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" && (
|
||||
<Text className="text-2xl font-bold">{title}</Text>
|
||||
)}
|
||||
{typeof title !== "string" && title}
|
||||
<Text className="mt-1 text-sm font-bold">{subtitle}</Text>
|
||||
<View className="py-3">{children}</View>
|
||||
</View>
|
||||
);
|
||||
}
|
18
apps/expo/src/app/components/ui/Text.tsx
Normal file
18
apps/expo/src/app/components/ui/Text.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
0
apps/expo/src/app/constants/.gitkeep
Normal file
0
apps/expo/src/app/constants/.gitkeep
Normal file
7
apps/expo/src/app/lib/utils.ts
Normal file
7
apps/expo/src/app/lib/utils.ts
Normal 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));
|
||||
}
|
3
apps/expo/src/app/styles/global.css
Normal file
3
apps/expo/src/app/styles/global.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" />
|
28
apps/expo/tailwind.config.ts
Normal file
28
apps/expo/tailwind.config.ts
Normal 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
16
apps/expo/tsconfig.json
Normal 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"],
|
||||
}
|
36
package.json
Normal file
36
package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@movie-web/native",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=20.11.0"
|
||||
},
|
||||
"packageManager": "pnpm@8.15.0",
|
||||
"scripts": {
|
||||
"build": "turbo build",
|
||||
"clean": "git clean -xdf node_modules",
|
||||
"clean:workspaces": "turbo clean",
|
||||
"db:push": "pnpm -F db push",
|
||||
"db:studio": "pnpm -F db studio",
|
||||
"dev": "turbo dev --parallel",
|
||||
"format": "turbo format --continue -- --cache --cache-location node_modules/.cache/.prettiercache",
|
||||
"format:fix": "turbo format --continue -- --write --cache --cache-location node_modules/.cache/.prettiercache",
|
||||
"lint": "turbo lint --continue -- --cache --cache-location node_modules/.cache/.eslintcache",
|
||||
"lint:fix": "turbo lint --continue -- --fix --cache --cache-location node_modules/.cache/.eslintcache",
|
||||
"lint:ws": "pnpm dlx sherif@latest",
|
||||
"postinstall": "pnpm lint:ws",
|
||||
"typecheck": "turbo typecheck"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@movie-web/prettier-config": "workspace:^0.1.0",
|
||||
"@turbo/gen": "^1.11.3",
|
||||
"prettier": "^3.1.1",
|
||||
"turbo": "^1.11.3",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"prettier": "@movie-web/prettier-config",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"nativewind@4.0.23": "patches/nativewind@4.0.23.patch"
|
||||
}
|
||||
}
|
||||
}
|
34
packages/tmdb/package.json
Normal file
34
packages/tmdb/package.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@movie-web/tmdb",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@movie-web/eslint-config": "workspace:^0.2.0",
|
||||
"@movie-web/prettier-config": "workspace:^0.1.0",
|
||||
"@movie-web/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^8.56.0",
|
||||
"prettier": "^3.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"@movie-web/eslint-config/base"
|
||||
]
|
||||
},
|
||||
"prettier": "@movie-web/prettier-config",
|
||||
"dependencies": {
|
||||
"tmdb-ts": "^1.6.1"
|
||||
}
|
||||
}
|
24
packages/tmdb/src/details.ts
Normal file
24
packages/tmdb/src/details.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { MovieDetails, TvShowDetails } from "tmdb-ts";
|
||||
|
||||
import { tmdb } from "./util";
|
||||
|
||||
export async function fetchMediaDetails(
|
||||
id: string,
|
||||
type: "movie" | "tv",
|
||||
): Promise<
|
||||
{ type: "movie" | "tv"; result: TvShowDetails | MovieDetails } | undefined
|
||||
> {
|
||||
try {
|
||||
const result =
|
||||
type === "movie"
|
||||
? await tmdb.movies.details(parseInt(id, 10))
|
||||
: await tmdb.tvShows.details(parseInt(id, 10));
|
||||
|
||||
return {
|
||||
type,
|
||||
result,
|
||||
};
|
||||
} catch (ex) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
4
packages/tmdb/src/index.ts
Normal file
4
packages/tmdb/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const name = "tmdb";
|
||||
export * from "./search";
|
||||
export * from "./details";
|
||||
export * from "./util";
|
22
packages/tmdb/src/search.ts
Normal file
22
packages/tmdb/src/search.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { MovieWithMediaType, TVWithMediaType } from "tmdb-ts";
|
||||
|
||||
import { tmdb } from "./util";
|
||||
|
||||
export async function searchTitle(query: string) {
|
||||
try {
|
||||
const rawResults = await tmdb.search.multi({
|
||||
query,
|
||||
page: 1,
|
||||
include_adult: false,
|
||||
});
|
||||
const results = rawResults.results.filter(
|
||||
(result) => result.media_type === "tv" || result.media_type === "movie",
|
||||
);
|
||||
|
||||
if (!results.length) throw new Error("No results found");
|
||||
|
||||
return results as unknown as MovieWithMediaType[] | TVWithMediaType[];
|
||||
} catch (ex) {
|
||||
throw new Error(`Error searching for title: ${(ex as Error).message}`);
|
||||
}
|
||||
}
|
9
packages/tmdb/src/util.ts
Normal file
9
packages/tmdb/src/util.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { TMDB } from "tmdb-ts";
|
||||
|
||||
const TMDB_API_KEY =
|
||||
"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYTM1ZTgyMzE4OTc0NTgxNDJmZjljZTE4ODExNWRlNiIsInN1YiI6IjY0OTM0ZDQ1ODliNTYxMDExYzliZDVhMiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.AzWnIcxPNgDwGdzeIZ_C3mRC_5_qy-Z-SRPglLjzlNc";
|
||||
export const tmdb = new TMDB(TMDB_API_KEY);
|
||||
|
||||
export function getMediaPoster(posterPath: string): string {
|
||||
return `https://image.tmdb.org/t/p/w185/${posterPath}`;
|
||||
}
|
8
packages/tmdb/tsconfig.json
Normal file
8
packages/tmdb/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@movie-web/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
13
patches/nativewind@4.0.23.patch
Normal file
13
patches/nativewind@4.0.23.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/dist/metro/transformer.js b/dist/metro/transformer.js
|
||||
index 1bda43b116d02834db01a42e64dd302e3d3fe785..8ff7f8a324cd9a8531df915a704d604828959e78 100644
|
||||
--- a/dist/metro/transformer.js
|
||||
+++ b/dist/metro/transformer.js
|
||||
@@ -16,7 +16,7 @@ new globalThis.WebSocket(\`\${url}:${config.nativewind.fastRefreshPort}\`).addEv
|
||||
StyleSheet.registerCompiled(JSON.parse('${config.nativewind.parsedOutput}'));`, "utf8"), options);
|
||||
}
|
||||
else if (options.platform === "web") {
|
||||
- return metro_transform_worker_1.default.transform(config, projectRoot, filename, Buffer.from(`require('${config.nativewind.outputPath}');`, "utf8"), options);
|
||||
+ return metro_transform_worker_1.default.transform(config, projectRoot, filename, Buffer.from(`require('${config.nativewind.outputPath.replace(/\\/g, '\\\\')}');`, "utf8"), options);
|
||||
}
|
||||
else {
|
||||
data = Buffer.from(config.nativewind.rawOutput, "utf8");
|
10313
pnpm-lock.yaml
generated
Normal file
10313
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
- apps/*
|
||||
- packages/*
|
||||
- tooling/*
|
43
tooling/eslint/base.js
Normal file
43
tooling/eslint/base.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
const config = {
|
||||
extends: [
|
||||
"turbo",
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended-type-checked",
|
||||
"plugin:@typescript-eslint/stylistic-type-checked",
|
||||
"prettier",
|
||||
],
|
||||
env: {
|
||||
es2022: true,
|
||||
node: true,
|
||||
},
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: { project: true },
|
||||
plugins: ["@typescript-eslint", "import"],
|
||||
rules: {
|
||||
"turbo/no-undeclared-env-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
||||
],
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
"warn",
|
||||
{ prefer: "type-imports", fixStyle: "separate-type-imports" },
|
||||
],
|
||||
"@typescript-eslint/no-misused-promises": [
|
||||
2,
|
||||
{ checksVoidReturn: { attributes: false } },
|
||||
],
|
||||
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
|
||||
},
|
||||
ignorePatterns: [
|
||||
"**/*.config.js",
|
||||
"**/*.config.cjs",
|
||||
"**/.eslintrc.cjs",
|
||||
"dist",
|
||||
"pnpm-lock.yaml",
|
||||
],
|
||||
reportUnusedDisableDirectives: true,
|
||||
};
|
||||
|
||||
module.exports = config;
|
41
tooling/eslint/package.json
Normal file
41
tooling/eslint/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@movie-web/eslint-config",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"./base.js",
|
||||
"./react.js"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.1",
|
||||
"@typescript-eslint/parser": "^6.19.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "^1.11.3",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@movie-web/prettier-config": "workspace:^0.1.0",
|
||||
"@movie-web/tsconfig": "workspace:^0.1.0",
|
||||
"@types/eslint": "^8.56.2",
|
||||
"eslint": "^8.56.0",
|
||||
"prettier": "^3.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"./base.js"
|
||||
]
|
||||
},
|
||||
"prettier": "@movie-web/prettier-config"
|
||||
}
|
24
tooling/eslint/react.js
vendored
Normal file
24
tooling/eslint/react.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
const config = {
|
||||
extends: [
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
],
|
||||
rules: {
|
||||
"react/prop-types": "off",
|
||||
},
|
||||
globals: {
|
||||
React: "writable",
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
8
tooling/eslint/tsconfig.json
Normal file
8
tooling/eslint/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@movie-web/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
3
tooling/github/package.json
Normal file
3
tooling/github/package.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "@movie-web/github"
|
||||
}
|
17
tooling/github/setup/action.yml
Normal file
17
tooling/github/setup/action.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
name: "Setup and install"
|
||||
description: "Common setup steps for Actions"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- shell: bash
|
||||
run: pnpm add -g turbo
|
||||
|
||||
- shell: bash
|
||||
run: pnpm install
|
35
tooling/prettier/index.js
Normal file
35
tooling/prettier/index.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
/** @typedef {import("prettier").Config} PrettierConfig */
|
||||
/** @typedef {import("prettier-plugin-tailwindcss").PluginOptions} TailwindConfig */
|
||||
/** @typedef {import("@ianvs/prettier-plugin-sort-imports").PluginConfig} SortImportsConfig */
|
||||
|
||||
/** @type { PrettierConfig | SortImportsConfig | TailwindConfig } */
|
||||
const config = {
|
||||
plugins: [
|
||||
"@ianvs/prettier-plugin-sort-imports",
|
||||
"prettier-plugin-tailwindcss",
|
||||
],
|
||||
tailwindConfig: fileURLToPath(
|
||||
new URL("../../tooling/tailwind/web.ts", import.meta.url),
|
||||
),
|
||||
tailwindFunctions: ["cn", "cva"],
|
||||
importOrder: [
|
||||
"<TYPES>",
|
||||
"^(react/(.*)$)|^(react$)|^(react-native(.*)$)",
|
||||
"^(expo(.*)$)|^(expo$)",
|
||||
"<THIRD_PARTY_MODULES>",
|
||||
"",
|
||||
"<TYPES>^@movie-web",
|
||||
"^@movie-web/(.*)$",
|
||||
"",
|
||||
"<TYPES>^[.|..|~]",
|
||||
"^~/",
|
||||
"^[../]",
|
||||
"^[./]",
|
||||
],
|
||||
importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"],
|
||||
importOrderTypeScriptVersion: "4.4.0",
|
||||
};
|
||||
|
||||
export default config;
|
24
tooling/prettier/package.json
Normal file
24
tooling/prettier/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@movie-web/prettier-config",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-tailwindcss": "^0.5.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@movie-web/tsconfig": "workspace:^0.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"prettier": "@movie-web/prettier-config"
|
||||
}
|
8
tooling/prettier/tsconfig.json
Normal file
8
tooling/prettier/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@movie-web/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
12
tooling/tailwind/base.ts
Normal file
12
tooling/tailwind/base.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
import colors from "./colors";
|
||||
|
||||
export default {
|
||||
content: ["src/**/*.{ts,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors,
|
||||
},
|
||||
},
|
||||
} satisfies Config;
|
14
tooling/tailwind/colors.ts
Normal file
14
tooling/tailwind/colors.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
primary: {
|
||||
100: "#C082FF",
|
||||
300: "#8D44D6",
|
||||
400: "#7831BF",
|
||||
},
|
||||
secondary: {
|
||||
50: "#676790",
|
||||
200: "#3F3F60",
|
||||
300: "#32324F",
|
||||
700: "#131322",
|
||||
},
|
||||
background: "#0a0a12",
|
||||
};
|
9
tooling/tailwind/native.ts
Normal file
9
tooling/tailwind/native.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
import base from "./base";
|
||||
|
||||
export default {
|
||||
content: base.content,
|
||||
presets: [base],
|
||||
theme: {},
|
||||
} satisfies Config;
|
39
tooling/tailwind/package.json
Normal file
39
tooling/tailwind/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@movie-web/tailwind-config",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./native": "./native.ts",
|
||||
"./web": "./web.ts",
|
||||
"./colors": "./colors.ts"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.32",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@movie-web/eslint-config": "workspace:^0.2.0",
|
||||
"@movie-web/prettier-config": "workspace:^0.1.0",
|
||||
"@movie-web/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^8.56.0",
|
||||
"prettier": "^3.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@movie-web/eslint-config/base"
|
||||
]
|
||||
},
|
||||
"prettier": "@movie-web/prettier-config"
|
||||
}
|
8
tooling/tailwind/tsconfig.json
Normal file
8
tooling/tailwind/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@movie-web/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
40
tooling/tailwind/web.ts
Normal file
40
tooling/tailwind/web.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
import animate from "tailwindcss-animate";
|
||||
|
||||
import base from "./base";
|
||||
|
||||
export default {
|
||||
content: base.content,
|
||||
presets: [base],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [animate],
|
||||
} satisfies Config;
|
21
tooling/typescript/base.json
Normal file
21
tooling/typescript/base.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noUncheckedIndexedAccess": true
|
||||
},
|
||||
"exclude": ["node_modules", "build", "dist", ".expo"]
|
||||
}
|
8
tooling/typescript/package.json
Normal file
8
tooling/typescript/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@movie-web/tsconfig",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"files": [
|
||||
"base.json"
|
||||
]
|
||||
}
|
38
turbo.json
Normal file
38
turbo.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "https://turborepo.org/schema.json",
|
||||
"globalDependencies": ["**/.env"],
|
||||
"pipeline": {
|
||||
"topo": {
|
||||
"dependsOn": ["^topo"]
|
||||
},
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": [
|
||||
".expo/**",
|
||||
".output/**"
|
||||
]
|
||||
},
|
||||
"dev": {
|
||||
"persistent": true,
|
||||
"cache": false
|
||||
},
|
||||
"format": {
|
||||
"outputs": ["node_modules/.cache/.prettiercache"],
|
||||
"outputMode": "new-only"
|
||||
},
|
||||
"lint": {
|
||||
"dependsOn": ["^topo"],
|
||||
"outputs": ["node_modules/.cache/.eslintcache"]
|
||||
},
|
||||
"typecheck": {
|
||||
"dependsOn": ["^topo"],
|
||||
"outputs": ["node_modules/.cache/tsbuildinfo.json"]
|
||||
},
|
||||
"clean": {
|
||||
"cache": false
|
||||
},
|
||||
"//#clean": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
}
|
90
turbo/generators/config.ts
Normal file
90
turbo/generators/config.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import type { PlopTypes } from "@turbo/gen";
|
||||
|
||||
interface PackageJson {
|
||||
name: string;
|
||||
scripts: Record<string, string>;
|
||||
dependencies: Record<string, string>;
|
||||
devDependencies: Record<string, string>;
|
||||
}
|
||||
|
||||
export default function generator(plop: PlopTypes.NodePlopAPI): void {
|
||||
plop.setGenerator("init", {
|
||||
description: "Generate a new package for the movie-web native-app Monorepo",
|
||||
prompts: [
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message:
|
||||
"What is the name of the package? (You can skip the `@movie-web/` prefix)",
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "deps",
|
||||
message:
|
||||
"Enter a space separated list of dependencies you would like to install",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
(answers) => {
|
||||
if ("name" in answers && typeof answers.name === "string") {
|
||||
if (answers.name.startsWith("@movie-web/")) {
|
||||
answers.name = answers.name.replace("@movie-web/", "");
|
||||
}
|
||||
}
|
||||
return "Config sanitized";
|
||||
},
|
||||
{
|
||||
type: "add",
|
||||
path: "packages/{{ name }}/package.json",
|
||||
templateFile: "templates/package.json.hbs",
|
||||
},
|
||||
{
|
||||
type: "add",
|
||||
path: "packages/{{ name }}/tsconfig.json",
|
||||
templateFile: "templates/tsconfig.json.hbs",
|
||||
},
|
||||
{
|
||||
type: "add",
|
||||
path: "packages/{{ name }}/src/index.ts",
|
||||
template: "export const name = '{{ name }}';",
|
||||
},
|
||||
{
|
||||
type: "modify",
|
||||
path: "packages/{{ name }}/package.json",
|
||||
async transform(content, answers) {
|
||||
if ("deps" in answers && typeof answers.deps === "string") {
|
||||
const pkg = JSON.parse(content) as PackageJson;
|
||||
for (const dep of answers.deps.split(" ").filter(Boolean)) {
|
||||
const version = await fetch(
|
||||
`https://registry.npmjs.org/-/package/${dep}/dist-tags`,
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((json) => json.latest);
|
||||
if (!pkg.dependencies) pkg.dependencies = {};
|
||||
pkg.dependencies[dep] = `^${version}`;
|
||||
}
|
||||
return JSON.stringify(pkg, null, 2);
|
||||
}
|
||||
return content;
|
||||
},
|
||||
},
|
||||
async (answers) => {
|
||||
/**
|
||||
* Install deps and format everything
|
||||
*/
|
||||
if ("name" in answers && typeof answers.name === "string") {
|
||||
// execSync("pnpm dlx sherif@latest --fix", {
|
||||
// stdio: "inherit",
|
||||
// });
|
||||
execSync("pnpm i", { stdio: "inherit" });
|
||||
execSync(
|
||||
`pnpm prettier --write packages/${answers.name}/** --list-different`,
|
||||
);
|
||||
return "Package scaffolded";
|
||||
}
|
||||
return "Package not scaffolded";
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
30
turbo/generators/templates/package.json.hbs
Normal file
30
turbo/generators/templates/package.json.hbs
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@movie-web/{{ name }}",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@movie-web/eslint-config": "workspace:^0.2.0",
|
||||
"@movie-web/prettier-config": "workspace:^0.1.0",
|
||||
"@movie-web/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^8.56.0",
|
||||
"prettier": "^3.1.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"@movie-web/eslint-config/base"
|
||||
]
|
||||
},
|
||||
"prettier": "@movie-web/prettier-config"
|
||||
}
|
8
turbo/generators/templates/tsconfig.json.hbs
Normal file
8
turbo/generators/templates/tsconfig.json.hbs
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@movie-web/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Reference in New Issue
Block a user