Compare commits

87 Commits
master ... dev

Author SHA1 Message Date
Jorrin
a3f184979e Merge pull request #8 from castdrian/feat-tmdb
feat: tmdb package & github action builds
2024-02-04 19:55:43 +01:00
Jorrin
450ef4dc90 add no cache option and cleanup 2024-02-04 19:53:45 +01:00
Adrian Castro
4e60002bac fix: search button css on ios is a round boi now 2024-02-04 19:46:41 +01:00
Adrian Castro
7941a35111 chore: don't run this for all pr events 2024-02-04 19:34:50 +01:00
Adrian Castro
519c85a3ac chore: clean stuff in item component 2024-02-04 19:30:46 +01:00
Adrian Castro
0134c0ff92 refactor: abstract build and release workflows into seperate workflows 2024-02-04 19:18:24 +01:00
Adrian Castro
4478366855 refactor: address code review 2024-02-04 18:36:21 +01:00
Adrian Castro
54e270bf17 chore: run prettier 2024-02-04 17:48:04 +01:00
Adrian Castro
cf4076d613 chore: catch search 2024-02-04 17:46:26 +01:00
Adrian Castro
ef78cc3447 chore: cleanup 2024-02-04 17:38:57 +01:00
Adrian Castro
1b9fbb4120 chore: use typedef instead of inline type 2024-02-04 17:38:03 +01:00
Adrian Castro
e88b7d2051 chore: run prettier 2024-02-04 16:52:27 +01:00
Adrian Castro
aa0e374bca chore: more cleanup 2024-02-04 16:44:55 +01:00
Adrian Castro
271cca3cd5 chore: cleanup 2024-02-04 16:34:21 +01:00
Adrian Castro
7c9247dc2c chore: cleanup 2024-02-04 16:25:36 +01:00
Adrian Castro
dc6e3f5a7f chore: run prettier 2024-02-04 16:15:21 +01:00
Adrian Castro
e3f74aac09 feat: implement search via tmdb package 2024-02-04 16:14:16 +01:00
Adrian Castro
b4e9ff5086 chore: stuff 2024-02-04 14:52:10 +01:00
Adrian Castro
c4a56c1a2a chore: run prettier 2024-02-04 14:00:12 +01:00
Adrian Castro
3a4df634cf feat: implement tmdb package 2024-02-04 13:58:33 +01:00
Adrian Castro
c2b6b6a555 chore: bump setup node action version 2024-02-04 13:43:08 +01:00
Adrian Castro
360bdf4f23 fix: adjust workflow paths 2024-02-04 13:32:07 +01:00
Adrian Castro
1a9f955a37 feat: init tmdb package 2024-02-04 12:30:09 +01:00
Adrian Castro
94ef89a95f chore: use actual name of the built executable 2024-02-04 12:26:33 +01:00
Adrian Castro
0c1e67291a chore: run prettier 2024-02-04 12:17:59 +01:00
Adrian Castro
b800574a26 fix: additional ci build config stuffs 2024-02-04 12:03:06 +01:00
Adrian Castro
6e53e00757 feat: ci builds 2024-02-04 11:57:24 +01:00
Adrian Castro
a4777e442e chore: metadata 2024-02-04 11:40:24 +01:00
Jorrin
415a2541fe fix ci on master 2024-02-03 22:20:54 +01:00
Jorrin
ea30054508 Merge pull request #7 from movie-web/turbo
refactor to turbo
2024-02-03 21:48:03 +01:00
Jorrin
d59b485167 fix 2024-02-03 21:43:14 +01:00
Jorrin
a56dac54fe format and remove unused externallink component 2024-02-03 21:38:58 +01:00
Jorrin
d261779b6d Update ci.yml 2024-02-03 21:34:53 +01:00
Jorrin
82172727e9 update deps and fix navbar icon 2024-02-03 21:31:19 +01:00
Jorrin
add7c1841d Update config.ts 2024-02-03 20:40:45 +01:00
Jorrin
df8bc8a83f Update LICENSE 2024-02-03 20:40:01 +01:00
Jorrin
28467cdf24 refactor to turbo 2024-02-03 20:33:27 +01:00
Jorrin
fc5c60f85b Merge pull request #6 from movie-web/nativewind
Nativewind
2024-01-30 19:12:01 +01:00
Jorrin
f5a5929972 Merge branch 'nativewind' of https://github.com/movie-web/native-app into nativewind 2024-01-29 10:49:38 +01:00
Jorrin
cf17593b57 remove unnecessary classes 2024-01-29 10:49:32 +01:00
Jorrin
e7d7b046db Merge branch 'dev' into nativewind 2024-01-29 10:36:07 +01:00
Jorrin
a7608b878d cleanup 2024-01-29 10:34:51 +01:00
Jorrin
5baf4d6b86 helpppp 2024-01-29 10:09:00 +01:00
Jorrin
e83bf1c806 test 2024-01-29 00:06:03 +01:00
Jorrin
865cd632d6 pain 2024-01-28 23:58:54 +01:00
Jorrin
89d1310eac help 2024-01-28 21:53:06 +01:00
Jorrin
8977e3ea2c upgrade to v4 2024-01-27 23:20:08 +01:00
Jorrin
4c634abc1e upgrade to native wind v4 2024-01-27 13:53:41 +01:00
Jorrin
26a1b623e7 replace all stylesheets with tailwind classes 2024-01-23 21:56:17 +01:00
Jorrin
5e47c5e5f7 Merge pull request #5 from castdrian/patch-1
fix(workflow): bump version before building lol
2024-01-23 07:59:34 +01:00
Adrian Castro
9c310c01c8 fix(workflow): bump version before building lol 2024-01-23 01:31:41 +01:00
Jorrin
8a48a1cce4 first setup 2024-01-22 22:43:19 +01:00
Jorrin
910c3f4b3b fix deps 2024-01-22 19:55:23 +01:00
Jorrin
37d21eea56 Merge pull request #4 from callmearta/tabs
[feat] tabs added
2024-01-22 18:17:20 +01:00
Jorrin
cd2c07f586 Merge branch 'dev' into tabs 2024-01-22 18:17:05 +01:00
Arta
4041b9b393 [fix] fix comments on PR 2024-01-20 15:36:57 +03:30
Arta
ad82e72969 [ui] font added, movie item added, pages layout 2024-01-18 15:41:42 +03:30
Arta
1865d2e6a8 [feat] tabs added 2024-01-15 17:30:21 +03:30
Jorrin
8e2cf0f28d Merge pull request #2 from movie-web/setup
Added basic expo skeleton app
2024-01-15 13:48:23 +01:00
Jorrin
eaa9706244 Merge pull request #3 from castdrian/build-workflow
feat: build pipelines & nx-expo app link
2024-01-15 13:39:28 +01:00
castdrian
d887e9f207 chore: adjust ios configs in generated configs 2024-01-15 13:17:18 +01:00
castdrian
826ae13777 chore: remove redundant autogenerated file 2024-01-15 12:48:32 +01:00
castdrian
6ec1a3fb64 feat: release job in workflow 2024-01-15 10:22:53 +01:00
castdrian
f2f46368d9 fix: fix the asset paths 2024-01-15 09:07:48 +01:00
castdrian
e64a52f5c3 fix: fix the routing back to how it was 2024-01-15 09:03:43 +01:00
castdrian
314e739af5 fix: restore the expo router stuff that the autogenerated stuff undid 2024-01-15 08:43:44 +01:00
castdrian
9f4cb15eba chore: cleanup 2024-01-15 07:11:04 +01:00
castdrian
8142f312b6 chore: unstage ignored files 2024-01-15 07:08:29 +01:00
castdrian
1f3c358f0a feat: a bit of build documentation because ios is crazy stuff 2024-01-15 07:02:58 +01:00
castdrian
5ffee47224 feat: android build 2024-01-15 05:58:36 +01:00
Adrian Castro
53297b820c chore: update workflow 2024-01-15 05:17:34 +01:00
castdrian
54558e9799 chore: bump dependency that was at fault all this time 2024-01-15 05:02:21 +01:00
castdrian
74e9954b9c chore: workflow 2024-01-15 04:55:04 +01:00
castdrian
49318dca38 chore: change jsengine to jsc in top level since it fails to build otherwise 2024-01-15 00:40:27 +01:00
castdrian
dcdb59ddd5 chore: disable hermes on ios 2024-01-15 00:28:11 +01:00
castdrian
20a0fbbcfb chore: add pod-install 2024-01-14 21:32:22 +01:00
castdrian
9238d58900 chore: prebuild config 2024-01-14 21:25:56 +01:00
castdrian
9f37eaa006 feat: actually link expo and nx 2024-01-14 21:24:49 +01:00
castdrian
8f673cc7f3 chore: bump ios target to required minimum 2024-01-14 20:40:20 +01:00
castdrian
ea372b1437 chore: add expo build properties plugin 2024-01-14 20:30:43 +01:00
castdrian
817b9ad771 chore: initial setup for automated builds 2024-01-14 19:17:39 +01:00
Jorrin
2e1e239be5 remove unused extension 2024-01-14 15:07:28 +01:00
Jorrin
3be4830711 fix license and readme 2024-01-14 15:05:13 +01:00
Jorrin
326ff1fe92 add expo app 2024-01-14 14:56:39 +01:00
Jorrin
833a1c8ecd init 2024-01-14 02:26:22 +01:00
mrjvs
38e0dd87e0 Folder structure 2024-01-13 22:01:07 +01:00
mrjvs
8de5672832 add github files 2024-01-13 21:57:01 +01:00
83 changed files with 12229 additions and 2 deletions

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @movie-web/core

1
.github/CODE_OF_CONDUCT.md vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
node-linker=hoisted

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
20.11

9
.vscode/extensions.json vendored Normal file
View 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
View 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"
]
}

View File

@@ -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).

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: "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;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

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"],
};
};

31
apps/expo/eas.json Normal file
View 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
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;
}

70
apps/expo/package.json Normal file
View 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"
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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>
);
}

View 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,
}));
}

View 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>
);
}

View 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;
}
}`;

View 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&apos;t exist.
</Text>
<Link href="/" className="mt-4 py-4">
<Text className="text-sm text-sky-500">Go to home screen!</Text>
</Link>
</View>
</>
);
}

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)",
};
// 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,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}
/>
);
}

View 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>
);
}

View 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>
);
}

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

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,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1 @@
/// <reference types="nativewind/types" />

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"],
}

36
package.json Normal file
View 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"
}
}
}

View 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"
}
}

View 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;
}
}

View File

@@ -0,0 +1,4 @@
export const name = "tmdb";
export * from "./search";
export * from "./details";
export * from "./util";

View 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}`);
}
}

View 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}`;
}

View File

@@ -0,0 +1,8 @@
{
"extends": "@movie-web/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
},
"include": ["*.ts", "src"],
"exclude": ["node_modules"],
}

View 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

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
packages:
- apps/*
- packages/*
- tooling/*

43
tooling/eslint/base.js Normal file
View 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;

View 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
View 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;

View File

@@ -0,0 +1,8 @@
{
"extends": "@movie-web/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
},
"include": ["."],
"exclude": ["node_modules"],
}

View File

@@ -0,0 +1,3 @@
{
"name": "@movie-web/github"
}

View 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
View 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;

View 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"
}

View 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
View 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;

View 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",
};

View File

@@ -0,0 +1,9 @@
import type { Config } from "tailwindcss";
import base from "./base";
export default {
content: base.content,
presets: [base],
theme: {},
} satisfies Config;

View 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"
}

View 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
View 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;

View 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"]
}

View File

@@ -0,0 +1,8 @@
{
"name": "@movie-web/tsconfig",
"private": true,
"version": "0.1.0",
"files": [
"base.json"
]
}

38
turbo.json Normal file
View 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
}
}
}

View 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";
},
],
});
}

View 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"
}

View File

@@ -0,0 +1,8 @@
{
"extends": "@movie-web/tsconfig/base.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["*.ts", "src"],
"exclude": ["node_modules"]
}