Compare commits
126 Commits
docs/toc
...
cojson-sto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6921e621d7 | ||
|
|
1b0ef401fb | ||
|
|
1833983b8d | ||
|
|
149ca97c48 | ||
|
|
f01a7621b0 | ||
|
|
ec7c416097 | ||
|
|
0f30eeaec6 | ||
|
|
5a3cf04ba7 | ||
|
|
d6e744d948 | ||
|
|
23b3acb58c | ||
|
|
f32d0c1fad | ||
|
|
a69ed0b7cd | ||
|
|
b4d7024b98 | ||
|
|
c0395dd0a3 | ||
|
|
51d7ca09d9 | ||
|
|
cf0a38d6bf | ||
|
|
dd9b13fbaa | ||
|
|
6a982a29cb | ||
|
|
ebc1b03158 | ||
|
|
c6931b82a0 | ||
|
|
279e2202ba | ||
|
|
ec2324519e | ||
|
|
5932f50c68 | ||
|
|
00906e5d08 | ||
|
|
7bc5fca440 | ||
|
|
e30eb224ae | ||
|
|
983ea7cf03 | ||
|
|
ad2d453e33 | ||
|
|
15d711f6de | ||
|
|
7441a7d3d8 | ||
|
|
0d756209e9 | ||
|
|
b20c2ca173 | ||
|
|
987a186db3 | ||
|
|
f4e0b59fa1 | ||
|
|
0d2112d8d0 | ||
|
|
6fdab780a9 | ||
|
|
159e2eb7f6 | ||
|
|
9daa50dd7d | ||
|
|
9c2aadb7d5 | ||
|
|
7d0d81b16e | ||
|
|
760a1c10c5 | ||
|
|
fac73f0e44 | ||
|
|
80b04e78c9 | ||
|
|
b23c0d75fe | ||
|
|
d4319d8a0e | ||
|
|
52cd4a9a0f | ||
|
|
3ef3ff3db9 | ||
|
|
549ec2047f | ||
|
|
ac241981f6 | ||
|
|
b3504dce9c | ||
|
|
f859d9f1c5 | ||
|
|
d433cf473f | ||
|
|
c6cd6cba20 | ||
|
|
9c7333d4f0 | ||
|
|
970f870245 | ||
|
|
a2dbaf86d2 | ||
|
|
b6162f0fc4 | ||
|
|
91cfa6aa8f | ||
|
|
cd152b83ae | ||
|
|
bac3ddddf5 | ||
|
|
b934fab89e | ||
|
|
dc4ea86819 | ||
|
|
4fd6bbd41d | ||
|
|
e4c1f0eefd | ||
|
|
e8a509e41c | ||
|
|
53560b7ce1 | ||
|
|
2aa2304d4d | ||
|
|
d4c8443c4a | ||
|
|
f9faf719d4 | ||
|
|
1a32e0c73f | ||
|
|
0ea3ef31ac | ||
|
|
d02613e2ec | ||
|
|
8ff036e5ff | ||
|
|
d0733e2a3b | ||
|
|
ad4f1b74f6 | ||
|
|
04d97e4a2d | ||
|
|
3f9e749122 | ||
|
|
aa1dd3e10a | ||
|
|
9f0deeb31c | ||
|
|
4f21ba9a5f | ||
|
|
bdc541e35f | ||
|
|
7350915006 | ||
|
|
d678ddbe11 | ||
|
|
7e92f85117 | ||
|
|
ffbeb0af43 | ||
|
|
1eba2a66af | ||
|
|
854ade8c06 | ||
|
|
ccb060262f | ||
|
|
981ad88fa1 | ||
|
|
8caaca1267 | ||
|
|
2af107c676 | ||
|
|
751a7394ff | ||
|
|
b1c0937bd2 | ||
|
|
d5f9c57ed3 | ||
|
|
f689a5871b | ||
|
|
092359f37f | ||
|
|
0fb71c1938 | ||
|
|
60d150c8d0 | ||
|
|
2dbfaf3ee0 | ||
|
|
a48f357928 | ||
|
|
a6e264d37b | ||
|
|
be0a09a222 | ||
|
|
4230cbf957 | ||
|
|
12ae46d10a | ||
|
|
4e6a65de9d | ||
|
|
4006544d11 | ||
|
|
67c311b458 | ||
|
|
e796a6f2ba | ||
|
|
f6cecfaaf9 | ||
|
|
572007139b | ||
|
|
47f3b72977 | ||
|
|
d9036bc47b | ||
|
|
4f61da553e | ||
|
|
ae11cbfded | ||
|
|
ef83947eb5 | ||
|
|
750cfa1b82 | ||
|
|
244e5ee582 | ||
|
|
4968e194dc | ||
|
|
36e53ee462 | ||
|
|
7e2e89be6d | ||
|
|
232801efdc | ||
|
|
82d81975d3 | ||
|
|
d998fdf6f6 | ||
|
|
6cdedd63d9 | ||
|
|
73630b4d94 | ||
|
|
cb8dfcadbc |
@@ -1,30 +1,30 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [
|
||||
[
|
||||
"cojson",
|
||||
"jazz-tools",
|
||||
"jazz-browser",
|
||||
"jazz-browser-media-images",
|
||||
"jazz-browser-auth-clerk",
|
||||
"jazz-react-auth-clerk",
|
||||
"jazz-react",
|
||||
"jazz-react-native",
|
||||
"jazz-nodejs",
|
||||
"jazz-run",
|
||||
"cojson-transport-ws",
|
||||
"cojson-storage-indexeddb",
|
||||
"cojson-storage-sqlite"
|
||||
]
|
||||
],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [],
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
}
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [
|
||||
[
|
||||
"cojson",
|
||||
"jazz-tools",
|
||||
"jazz-browser",
|
||||
"jazz-browser-media-images",
|
||||
"jazz-browser-auth-clerk",
|
||||
"jazz-react-auth-clerk",
|
||||
"jazz-react",
|
||||
"jazz-react-native",
|
||||
"jazz-nodejs",
|
||||
"jazz-run",
|
||||
"cojson-transport-ws",
|
||||
"cojson-storage-indexeddb",
|
||||
"cojson-storage-sqlite"
|
||||
]
|
||||
],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [],
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
}
|
||||
}
|
||||
|
||||
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
# EditorConfig is awesome: https://editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# Formatted workspace with biome
|
||||
be0a09a22295cd5d2ee3ef323e2d999da8a14110
|
||||
18
.github/workflows/code-quality.yml
vendored
Normal file
18
.github/workflows/code-quality.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Code quality
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Biome
|
||||
uses: biomejs/setup-biome@v2
|
||||
with:
|
||||
version: latest
|
||||
- name: Run Biome
|
||||
run: biome ci .
|
||||
2
.github/workflows/jazz-run.yml
vendored
2
.github/workflows/jazz-run.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
21
.github/workflows/monorepo-linting.yml
vendored
21
.github/workflows/monorepo-linting.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Monorepo linting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
monorepo-linting:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
- name: Run sherif
|
||||
run: npx sherif@1.0.0
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -4,4 +4,15 @@ lerna-debug.log
|
||||
docsTmp
|
||||
.DS_Store
|
||||
.turbo
|
||||
coverage
|
||||
coverage
|
||||
|
||||
# Next.js
|
||||
**/.next
|
||||
|
||||
# Vite output
|
||||
**/dist
|
||||
|
||||
# Playwright
|
||||
test-results
|
||||
|
||||
.husky
|
||||
@@ -1,9 +0,0 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
const config = {
|
||||
trailingComma: "all",
|
||||
tabWidth: 4,
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
};
|
||||
|
||||
export default config;
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
@@ -36,6 +36,8 @@ We welcome all ideas! If you have suggestions, feel free to open an issue marked
|
||||
|
||||
### 5. Local Setup
|
||||
|
||||
You'll need Node.js 20.x or 22.x installed (we're working on support for 23.x), and pnpm 9.x installed. If you're using nix, run `nix develop` to get a shell with the correct versions of everything installed.
|
||||
|
||||
1. **Clone the repository**:
|
||||
```bash
|
||||
git clone https://github.com/gardencmp/jazz.git
|
||||
@@ -53,7 +55,13 @@ We welcome all ideas! If you have suggestions, feel free to open an issue marked
|
||||
|
||||
### 6. Testing
|
||||
|
||||
Please write tests for any new features or bug fixes. We use Vitest for unit tests. Make sure all tests pass before submitting a pull request.
|
||||
Please write tests for any new features or bug fixes. We use Vitest for unit tests, and Playwright for e2e tests. Make sure all tests pass before submitting a pull request.
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
NB: You'll need to run `pnpm exec playwright install` to install the Playwright browsers before first run.
|
||||
|
||||
### 7. Communication
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -1,9 +1,17 @@
|
||||
# Jazz - Instant sync
|
||||
|
||||
|
||||
# Jazz - Build local-first apps
|
||||
|
||||
**Jazz is an open-source toolkit for building apps with *distributed state.***
|
||||
|
||||
## Getting started
|
||||
|
||||
We recommend reading the [homepage](https://jazz.tools) and [docs](https://jazz.tools/docs) to get an overview of what Jazz is and how it works.
|
||||
|
||||
If you're interested in contributing, please read [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
For community and support, please join our [Discord](https://discord.gg/utDMjHYg42).
|
||||
|
||||
---
|
||||
|
||||
- Homepage: [jazz.tools](https://jazz.tools)
|
||||
- Docs: [jazz.tools/docs](https://jazz.tools/docs)
|
||||
- Community & support: [Discord](https://discord.gg/utDMjHYg42)
|
||||
|
||||
25
biome.json
Normal file
25
biome.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": ["jazz-tools.json"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space"
|
||||
},
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": false,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +1,155 @@
|
||||
# @jazz-e2e/binarycostream
|
||||
|
||||
## 0.0.99
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f30eea]
|
||||
- Updated dependencies [149ca97]
|
||||
- cojson@0.8.21
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9c2aadb]
|
||||
- cojson@0.8.19
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d4319d8]
|
||||
- cojson@0.8.18
|
||||
- jazz-react@0.8.18
|
||||
- jazz-tools@0.8.18
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d433cf4]
|
||||
- cojson@0.8.17
|
||||
- jazz-react@0.8.17
|
||||
- jazz-tools@0.8.17
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2af107c]
|
||||
- Updated dependencies [b934fab]
|
||||
- jazz-react@0.8.16
|
||||
- cojson@0.8.16
|
||||
- jazz-tools@0.8.16
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [cce679b]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react@0.8.15
|
||||
- Updated dependencies [cce679b]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react@0.8.15
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react@0.8.14
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react@0.8.14
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react@0.8.13
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react@0.8.13
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6ed75eb]
|
||||
- Updated dependencies [3cc6aee]
|
||||
- cojson@0.8.12
|
||||
- jazz-react@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
- Updated dependencies [6ed75eb]
|
||||
- Updated dependencies [3cc6aee]
|
||||
- cojson@0.8.12
|
||||
- jazz-react@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.7
|
||||
- jazz-react@0.8.7
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.6
|
||||
- jazz-react@0.8.6
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [c3f4e6b]
|
||||
- Updated dependencies [d9152ed]
|
||||
- jazz-tools@0.8.5
|
||||
- cojson@0.8.5
|
||||
- jazz-react@0.8.5
|
||||
- Updated dependencies [c3f4e6b]
|
||||
- Updated dependencies [d9152ed]
|
||||
- jazz-tools@0.8.5
|
||||
- cojson@0.8.5
|
||||
- jazz-react@0.8.5
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.1
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.1
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.8.3
|
||||
- jazz-react@0.8.3
|
||||
- jazz-tools@0.8.3
|
||||
- Updated dependencies
|
||||
- cojson@0.8.3
|
||||
- jazz-react@0.8.3
|
||||
- jazz-tools@0.8.3
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a075f90]
|
||||
- jazz-tools@0.8.2
|
||||
- jazz-react@0.8.2
|
||||
- Updated dependencies [a075f90]
|
||||
- jazz-tools@0.8.2
|
||||
- jazz-react@0.8.2
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.8.1
|
||||
- jazz-react@0.8.1
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.8.1
|
||||
- jazz-react@0.8.1
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
{
|
||||
"name": "@jazz-e2e/binarycostream",
|
||||
"private": true,
|
||||
"version": "0.0.93",
|
||||
"version": "0.0.99",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "playwright test",
|
||||
"test:ui": "playwright test --ui"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,mdx,json}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"cojson": "workspace:0.8.12",
|
||||
"cojson": "workspace:0.8.21",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-react": "workspace:0.8.15",
|
||||
"jazz-tools": "workspace:0.8.15",
|
||||
"jazz-react": "workspace:0.8.21",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
||||
@@ -12,42 +12,42 @@ import isCI from "is-ci";
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: isCI,
|
||||
/* Retry on CI only */
|
||||
retries: isCI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: isCI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: isCI,
|
||||
/* Retry on CI only */
|
||||
retries: isCI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: isCI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:5173/",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:5173/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
permissions: ["clipboard-read", "clipboard-write"],
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
permissions: ["clipboard-read", "clipboard-write"],
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm preview --port 5173",
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm preview --port 5173",
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { Account, BinaryCoStream, ID } from "jazz-tools";
|
||||
import { useEffect } from "react";
|
||||
import { useAccount, useCoState } from "./jazz";
|
||||
import { UploadedFile } from "./schema";
|
||||
import { waitForCoValue } from "./lib/waitForCoValue";
|
||||
import { UploadedFile } from "./schema";
|
||||
|
||||
async function getUploadedFile(
|
||||
me: Account,
|
||||
uploadedFileId: ID<UploadedFile>) {
|
||||
const uploadedFile = await waitForCoValue(UploadedFile, uploadedFileId, me, Boolean, {})
|
||||
async function getUploadedFile(me: Account, uploadedFileId: ID<UploadedFile>) {
|
||||
const uploadedFile = await waitForCoValue(
|
||||
UploadedFile,
|
||||
uploadedFileId,
|
||||
me,
|
||||
Boolean,
|
||||
{},
|
||||
);
|
||||
|
||||
uploadedFile.coMapDownloaded = true;
|
||||
|
||||
@@ -21,7 +25,7 @@ export function DownloaderPeer(props: { testCoMapId: ID<UploadedFile> }) {
|
||||
const testCoMap = useCoState(UploadedFile, props.testCoMapId, {});
|
||||
|
||||
useEffect(() => {
|
||||
getUploadedFile(account.me, props.testCoMapId).then(value => {
|
||||
getUploadedFile(account.me, props.testCoMapId).then((value) => {
|
||||
value.syncCompleted = true;
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ID } from "jazz-tools";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAccount, useCoState } from "./jazz";
|
||||
import { BytesRadioGroup } from "./lib/BytesRadioGroup";
|
||||
import { createCredentiallessIframe } from "./lib/createCredentiallessIframe";
|
||||
import { generateTestFile } from "./lib/generateTestFile";
|
||||
import { getDownloaderPeerUrl } from "./lib/getDownloaderPeerUrl";
|
||||
import { UploadedFile } from "./schema";
|
||||
import { waitForCoValue } from "./lib/waitForCoValue";
|
||||
import { getDefaultFileSize, getIsAutoUpload } from "./lib/searchParams";
|
||||
import { BytesRadioGroup } from "./lib/BytesRadioGroup";
|
||||
import { waitForCoValue } from "./lib/waitForCoValue";
|
||||
import { UploadedFile } from "./schema";
|
||||
|
||||
export function UploaderPeer() {
|
||||
const account = useAccount();
|
||||
@@ -41,7 +41,7 @@ export function UploaderPeer() {
|
||||
file.id,
|
||||
account.me,
|
||||
(value) => value.syncCompleted,
|
||||
{}
|
||||
{},
|
||||
);
|
||||
|
||||
iframe.remove();
|
||||
@@ -75,12 +75,8 @@ export function UploaderPeer() {
|
||||
</div>
|
||||
)}
|
||||
{testFile?.coMapDownloaded && (
|
||||
<div data-testid="co-map-downloaded">
|
||||
CoMap synced!
|
||||
</div>
|
||||
<div data-testid="co-map-downloaded">CoMap synced!</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { DownloaderPeer } from "./DownloaderPeer";
|
||||
import { UploaderPeer } from "./UploaderPeer";
|
||||
import { getValueId } from "./lib/searchParams";
|
||||
import { AuthAndJazz } from "./jazz";
|
||||
import { getValueId } from "./lib/searchParams";
|
||||
|
||||
function Main() {
|
||||
const valueId = getValueId();
|
||||
@@ -17,8 +17,8 @@ function Main() {
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<AuthAndJazz>
|
||||
<Main />
|
||||
</AuthAndJazz>
|
||||
<AuthAndJazz>
|
||||
<Main />
|
||||
</AuthAndJazz>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createJazzReactApp, useDemoAuth } from "jazz-react";
|
||||
import { getValueId } from "./lib/searchParams";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { getValueId } from "./lib/searchParams";
|
||||
|
||||
const key = getValueId()
|
||||
? `downloader-e2e@jazz.tools`
|
||||
@@ -19,17 +19,20 @@ export function AuthAndJazz({ children }: { children: React.ReactNode }) {
|
||||
|
||||
useEffect(() => {
|
||||
if (state.state === "ready" && !signedUp.current) {
|
||||
state.signUp('Mister X');
|
||||
state.signUp("Mister X");
|
||||
signedUp.current = true;
|
||||
}
|
||||
}, [state.state])
|
||||
}, [state.state]);
|
||||
|
||||
return (
|
||||
<Jazz.Provider auth={auth} peer={
|
||||
localSync
|
||||
? `ws://localhost:4200?key=${key}`
|
||||
: `wss://cloud.jazz.tools/?key=${key}`
|
||||
}>
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
peer={
|
||||
localSync
|
||||
? `ws://localhost:4200?key=${key}`
|
||||
: `wss://cloud.jazz.tools/?key=${key}`
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
);
|
||||
|
||||
@@ -4,46 +4,54 @@ export function BytesRadioGroup(props: {
|
||||
}) {
|
||||
return (
|
||||
<p>
|
||||
<BytesRadioInput
|
||||
label="1KB"
|
||||
value={1e3}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange} />
|
||||
<BytesRadioInput
|
||||
label="10KB"
|
||||
value={1e4}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange} />
|
||||
<BytesRadioInput
|
||||
label="1KB"
|
||||
value={1e3}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<BytesRadioInput
|
||||
label="10KB"
|
||||
value={1e4}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<BytesRadioInput
|
||||
label="100KB"
|
||||
value={1e5}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange} />
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<BytesRadioInput
|
||||
label="150KB"
|
||||
value={1e5 + 5e4}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange} />
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<BytesRadioInput
|
||||
label="200KB"
|
||||
value={2e6}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange} />
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<BytesRadioInput
|
||||
label="500KB"
|
||||
value={5e6}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange} />
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<BytesRadioInput
|
||||
label="1MB"
|
||||
value={1e6}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange} />
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<BytesRadioInput
|
||||
label="10MB"
|
||||
value={1e7}
|
||||
selectedValue={props.selectedValue}
|
||||
onChange={props.onChange} />
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -60,7 +68,8 @@ function BytesRadioInput(props: {
|
||||
name="bytes"
|
||||
value={props.value}
|
||||
checked={props.value === props.selectedValue}
|
||||
onChange={() => props.onChange(props.value)} />
|
||||
onChange={() => props.onChange(props.value)}
|
||||
/>
|
||||
{props.label}
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Account, Group, BinaryCoStream } from "jazz-tools";
|
||||
import { Account, BinaryCoStream, Group } from "jazz-tools";
|
||||
import { UploadedFile } from "../schema";
|
||||
|
||||
export async function generateTestFile(me: Account, bytes: number) {
|
||||
@@ -9,13 +9,13 @@ export async function generateTestFile(me: Account, bytes: number) {
|
||||
const testFile = UploadedFile.create(
|
||||
{
|
||||
file: await BinaryCoStream.createFromBlob(
|
||||
new Blob(['1'.repeat(bytes)], { type: 'image/png' }),
|
||||
ownership
|
||||
new Blob(["1".repeat(bytes)], { type: "image/png" }),
|
||||
ownership,
|
||||
),
|
||||
syncCompleted: false,
|
||||
coMapDownloaded: false,
|
||||
},
|
||||
ownership
|
||||
ownership,
|
||||
);
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
@@ -24,4 +24,3 @@ export async function generateTestFile(me: Account, bytes: number) {
|
||||
|
||||
return testFile;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { UploadedFile } from "src/schema";
|
||||
|
||||
|
||||
export function getDownloaderPeerUrl(value: UploadedFile) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("valueId", value.id);
|
||||
|
||||
@@ -2,13 +2,19 @@ import { ID } from "jazz-tools";
|
||||
import { UploadedFile } from "../schema";
|
||||
|
||||
export function getValueId() {
|
||||
return new URLSearchParams(location.search).get("valueId") as ID<UploadedFile> | undefined ?? undefined;
|
||||
return (
|
||||
(new URLSearchParams(location.search).get("valueId") as
|
||||
| ID<UploadedFile>
|
||||
| undefined) ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function getIsAutoUpload() {
|
||||
return new URLSearchParams(location.search).has("auto");
|
||||
return new URLSearchParams(location.search).has("auto");
|
||||
}
|
||||
|
||||
export function getDefaultFileSize() {
|
||||
return parseInt(new URLSearchParams(location.search).get("fileSize") ?? 1e3.toString());
|
||||
}
|
||||
return parseInt(
|
||||
new URLSearchParams(location.search).get("fileSize") ?? (1e3).toString(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function waitForCoValue<T extends CoValue>(
|
||||
valueId: ID<T>,
|
||||
account: Account,
|
||||
predicate: (value: T) => boolean,
|
||||
depth: DepthsIn<T>
|
||||
depth: DepthsIn<T>,
|
||||
) {
|
||||
return new Promise<T>((resolve) => {
|
||||
function subscribe() {
|
||||
@@ -30,7 +30,7 @@ export function waitForCoValue<T extends CoValue>(
|
||||
() => {
|
||||
unsubscribe();
|
||||
setTimeout(subscribe, 100);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BinaryCoStream, co, CoMap } from "jazz-tools";
|
||||
import { BinaryCoStream, CoMap, co } from "jazz-tools";
|
||||
|
||||
export class UploadedFile extends CoMap {
|
||||
file = co.ref(BinaryCoStream);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("BinaryCoStream - Sync", () => {
|
||||
test("should sync a file between the two peers", async ({ page }) => {
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
minify: false
|
||||
}
|
||||
})
|
||||
minify: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,90 +1,147 @@
|
||||
# @jazz-e2e/covalues
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f30eea]
|
||||
- Updated dependencies [149ca97]
|
||||
- cojson@0.8.21
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9c2aadb]
|
||||
- cojson@0.8.19
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d4319d8]
|
||||
- cojson@0.8.18
|
||||
- jazz-react@0.8.18
|
||||
- jazz-tools@0.8.18
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d433cf4]
|
||||
- cojson@0.8.17
|
||||
- jazz-react@0.8.17
|
||||
- jazz-tools@0.8.17
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2af107c]
|
||||
- Updated dependencies [b934fab]
|
||||
- jazz-react@0.8.16
|
||||
- cojson@0.8.16
|
||||
- jazz-tools@0.8.16
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [cce679b]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react@0.8.15
|
||||
- Updated dependencies [cce679b]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react@0.8.15
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react@0.8.14
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react@0.8.14
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react@0.8.13
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react@0.8.13
|
||||
|
||||
## 0.0.89
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [6ed75eb]
|
||||
- Updated dependencies [3cc6aee]
|
||||
- cojson@0.8.12
|
||||
- jazz-react@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
- Updated dependencies [6ed75eb]
|
||||
- Updated dependencies [3cc6aee]
|
||||
- cojson@0.8.12
|
||||
- jazz-react@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
|
||||
## 0.0.88
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- Updated dependencies [1ed4ab5]
|
||||
- cojson@0.8.11
|
||||
- jazz-react@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 0.0.87
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.7
|
||||
- jazz-react@0.8.7
|
||||
|
||||
## 0.0.86
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.6
|
||||
- jazz-react@0.8.6
|
||||
|
||||
## 0.0.85
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [c3f4e6b]
|
||||
- Updated dependencies [d9152ed]
|
||||
- jazz-tools@0.8.5
|
||||
- cojson@0.8.5
|
||||
- jazz-react@0.8.5
|
||||
- Updated dependencies [c3f4e6b]
|
||||
- Updated dependencies [d9152ed]
|
||||
- jazz-tools@0.8.5
|
||||
- cojson@0.8.5
|
||||
- jazz-react@0.8.5
|
||||
|
||||
## 0.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.1
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.1
|
||||
|
||||
## 0.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.8.3
|
||||
- jazz-react@0.8.3
|
||||
- jazz-tools@0.8.3
|
||||
- Updated dependencies
|
||||
- cojson@0.8.3
|
||||
- jazz-react@0.8.3
|
||||
- jazz-tools@0.8.3
|
||||
|
||||
## 0.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [a075f90]
|
||||
- jazz-tools@0.8.2
|
||||
- jazz-react@0.8.2
|
||||
- Updated dependencies [a075f90]
|
||||
- jazz-tools@0.8.2
|
||||
- jazz-react@0.8.2
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
{
|
||||
"name": "@jazz-e2e/covalues",
|
||||
"private": true,
|
||||
"version": "0.0.92",
|
||||
"version": "0.0.98",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"test": "playwright test",
|
||||
"test:ui": "playwright test --ui"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,mdx,json}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"cojson": "workspace:*",
|
||||
"hash-slash": "workspace:*",
|
||||
|
||||
@@ -12,42 +12,42 @@ import isCI from "is-ci";
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: isCI,
|
||||
/* Retry on CI only */
|
||||
retries: isCI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: isCI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: isCI,
|
||||
/* Retry on CI only */
|
||||
retries: isCI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: isCI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:5173/",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:5173/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
permissions: ["clipboard-read", "clipboard-write"],
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
permissions: ["clipboard-read", "clipboard-write"],
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm preview --port 5173",
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm preview --port 5173",
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { AuthAndJazz } from "./jazz";
|
||||
import { TestInput } from "./pages/TestInput";
|
||||
import { ResumeSyncState } from "./pages/ResumeSyncState";
|
||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||
import { AuthAndJazz } from "./jazz";
|
||||
import { ResumeSyncState } from "./pages/ResumeSyncState";
|
||||
import { TestInput } from "./pages/TestInput";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/test-input",
|
||||
element: <TestInput />,
|
||||
path: "/test-input",
|
||||
element: <TestInput />,
|
||||
},
|
||||
{
|
||||
path: "/resume-sync",
|
||||
element: <ResumeSyncState />,
|
||||
path: "/resume-sync",
|
||||
element: <ResumeSyncState />,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
element: <TestInput />,
|
||||
path: "/",
|
||||
element: <TestInput />,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<AuthAndJazz>
|
||||
<RouterProvider router={router} />
|
||||
</AuthAndJazz>
|
||||
<AuthAndJazz>
|
||||
<RouterProvider router={router} />
|
||||
</AuthAndJazz>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
@@ -5,39 +5,39 @@ const key = `test-comap@jazz.tools`;
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
const peer =
|
||||
(url.searchParams.get("peer") as `ws://${string}`) ??
|
||||
`wss://cloud.jazz.tools/`;
|
||||
(url.searchParams.get("peer") as `ws://${string}`) ??
|
||||
`wss://cloud.jazz.tools/`;
|
||||
|
||||
const Jazz = createJazzReactApp();
|
||||
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
|
||||
function getUserInfo() {
|
||||
return url.searchParams.get("userName") ?? "Mister X";
|
||||
return url.searchParams.get("userName") ?? "Mister X";
|
||||
}
|
||||
|
||||
export function AuthAndJazz({ children }: { children: React.ReactNode }) {
|
||||
const [auth, state] = useDemoAuth();
|
||||
const [auth, state] = useDemoAuth();
|
||||
|
||||
const signedUp = useRef(false);
|
||||
const signedUp = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (state.state === "ready" && !signedUp.current) {
|
||||
const userName = getUserInfo();
|
||||
useEffect(() => {
|
||||
if (state.state === "ready" && !signedUp.current) {
|
||||
const userName = getUserInfo();
|
||||
|
||||
if (state.existingUsers.includes(userName)) {
|
||||
state.logInAs(userName);
|
||||
} else {
|
||||
state.signUp(userName);
|
||||
}
|
||||
if (state.existingUsers.includes(userName)) {
|
||||
state.logInAs(userName);
|
||||
} else {
|
||||
state.signUp(userName);
|
||||
}
|
||||
|
||||
signedUp.current = true;
|
||||
}
|
||||
}, [state.state]);
|
||||
signedUp.current = true;
|
||||
}
|
||||
}, [state.state]);
|
||||
|
||||
return (
|
||||
<Jazz.Provider auth={auth} peer={`${peer}?key=${key}`}>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
);
|
||||
return (
|
||||
<Jazz.Provider auth={auth} peer={`${peer}?key=${key}`}>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import { co, CoMap, Group, ID } from "jazz-tools";
|
||||
import { useAccount, useCoState } from "../jazz";
|
||||
import { CoMap, Group, ID, co } from "jazz-tools";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAccount, useCoState } from "../jazz";
|
||||
|
||||
export class ResumeSyncCoMap extends CoMap {
|
||||
value = co.string;
|
||||
value = co.string;
|
||||
}
|
||||
|
||||
function getIdParam() {
|
||||
const url = new URL(window.location.href);
|
||||
return (url.searchParams.get("id") as ID<ResumeSyncCoMap>) ?? undefined;
|
||||
const url = new URL(window.location.href);
|
||||
return (url.searchParams.get("id") as ID<ResumeSyncCoMap>) ?? undefined;
|
||||
}
|
||||
|
||||
export function ResumeSyncState() {
|
||||
const [id, setId] = useState(getIdParam);
|
||||
const coMap = useCoState(ResumeSyncCoMap, id);
|
||||
const { me } = useAccount();
|
||||
const [id, setId] = useState(getIdParam);
|
||||
const coMap = useCoState(ResumeSyncCoMap, id);
|
||||
const { me } = useAccount();
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("id", id);
|
||||
history.pushState({}, "", url.toString());
|
||||
}
|
||||
}, [id])
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("id", id);
|
||||
history.pushState({}, "", url.toString());
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!me || id) return;
|
||||
useEffect(() => {
|
||||
if (!me || id) return;
|
||||
|
||||
const group = Group.create({ owner: me });
|
||||
const group = Group.create({ owner: me });
|
||||
|
||||
group.addMember("everyone", "writer");
|
||||
group.addMember("everyone", "writer");
|
||||
|
||||
setId(ResumeSyncCoMap.create({ value: "" }, { owner: group }).id);
|
||||
}, [me]);
|
||||
setId(ResumeSyncCoMap.create({ value: "" }, { owner: group }).id);
|
||||
}, [me]);
|
||||
|
||||
if (!coMap) return null;
|
||||
if (!coMap) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Resume Sync State</h1>
|
||||
<p data-testid="id">{coMap.id}</p>
|
||||
<label htmlFor="value">Value</label>
|
||||
<input
|
||||
id="value"
|
||||
value={coMap.value ?? ""}
|
||||
onChange={(e) => {
|
||||
coMap.value = e.target.value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h1>Resume Sync State</h1>
|
||||
<p data-testid="id">{coMap.id}</p>
|
||||
<label htmlFor="value">Value</label>
|
||||
<input
|
||||
id="value"
|
||||
value={coMap.value ?? ""}
|
||||
onChange={(e) => {
|
||||
coMap.value = e.target.value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { co, CoMap, Group, ID } from "jazz-tools";
|
||||
import { useAccount, useCoState } from "../jazz";
|
||||
import { CoMap, Group, ID, co } from "jazz-tools";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAccount, useCoState } from "../jazz";
|
||||
|
||||
export class InputTestCoMap extends CoMap {
|
||||
title = co.string;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("ResumeSyncState", () => {
|
||||
test.skip("should resume the sync even after a page reload", async ({ page, browser }) => {
|
||||
test.skip("should resume the sync even after a page reload", async ({
|
||||
page,
|
||||
browser,
|
||||
}) => {
|
||||
const context = page.context();
|
||||
|
||||
await page.goto("/resume-sync?userName=SuperMario");
|
||||
@@ -20,7 +23,7 @@ test.describe("ResumeSyncState", () => {
|
||||
|
||||
// Navigate away from the page
|
||||
await page.goto(`about:blank`);
|
||||
|
||||
|
||||
await setTimeout(1000);
|
||||
await context.setOffline(false);
|
||||
|
||||
@@ -40,8 +43,10 @@ test.describe("ResumeSyncState", () => {
|
||||
|
||||
// The initial user should have synced the value even if the coValue was not loaded
|
||||
// when the user is back online
|
||||
await expect(newUserPage.getByRole("textbox", { name: "Value" })).toHaveValue("Mammamia!", {
|
||||
timeout: 20_000
|
||||
await expect(
|
||||
newUserPage.getByRole("textbox", { name: "Value" }),
|
||||
).toHaveValue("Mammamia!", {
|
||||
timeout: 20_000,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("CoMap - TestInput", () => {
|
||||
test("should keep the cursor position when typing", async ({ page }) => {
|
||||
await page.goto("/test-input");
|
||||
|
||||
await page.getByRole("textbox").fill("xx");
|
||||
await page.getByRole('textbox').press('ArrowLeft');
|
||||
await page.getByRole("textbox").press("ArrowLeft");
|
||||
await page.getByRole("textbox").press("y");
|
||||
await page.getByRole("textbox").press("y");
|
||||
|
||||
await expect(page.getByRole('textbox')).toHaveValue("xyyx");
|
||||
await expect(page.getByRole("textbox")).toHaveValue("xyyx");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
minify: false
|
||||
}
|
||||
})
|
||||
minify: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"prettier",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"plugins": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": ["error"]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 80,
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
@@ -1,5 +1,58 @@
|
||||
# jazz-example-book-shelf
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
- jazz-browser-media-images@0.8.21
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
- jazz-browser-media-images@0.8.20
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
- jazz-browser-media-images@0.8.19
|
||||
|
||||
## 0.1.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.18
|
||||
- jazz-tools@0.8.18
|
||||
- jazz-browser-media-images@0.8.18
|
||||
|
||||
## 0.1.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.17
|
||||
- jazz-tools@0.8.17
|
||||
- jazz-browser-media-images@0.8.17
|
||||
|
||||
## 0.1.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2af107c]
|
||||
- jazz-react@0.8.16
|
||||
- jazz-browser-media-images@0.8.16
|
||||
- jazz-tools@0.8.16
|
||||
|
||||
## 0.1.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "jazz-example-book-shelf",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.14",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write ."
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.8.15",
|
||||
"jazz-react": "workspace:0.8.15",
|
||||
"jazz-tools": "workspace:0.8.15",
|
||||
"jazz-browser-media-images": "workspace:0.8.21",
|
||||
"jazz-react": "workspace:0.8.21",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"next": "14.2.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
@@ -22,13 +22,7 @@
|
||||
"@types/node": "^22.5.1",
|
||||
"@types/react": "^18.2.19",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-next": "14.2.5",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"postcss": "^8.4.27",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import { BookReview, ListOfBookReviews } from "@/schema";
|
||||
import { Button } from "@/components/Button";
|
||||
import { Container } from "@/components/Container";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import { useRouter } from "next/navigation";
|
||||
import RatingInput from "@/components/RatingInput";
|
||||
import { BookReview, ListOfBookReviews } from "@/schema";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
import { Group, ImageDefinition } from "jazz-tools";
|
||||
import { Container } from "@/components/Container";
|
||||
import { Button } from "@/components/Button";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ChangeEvent, useState } from "react";
|
||||
|
||||
export default function AddBookReview() {
|
||||
const { me } = useAccount();
|
||||
@@ -18,7 +18,7 @@ export default function AddBookReview() {
|
||||
const [review, setReview] = useState("");
|
||||
const [rating, setRating] = useState(0);
|
||||
const [dateRead, setDateRead] = useState(
|
||||
new Date().toISOString().split("T")[0]
|
||||
new Date().toISOString().split("T")[0],
|
||||
);
|
||||
const [coverImage, setCoverImage] = useState<ImageDefinition | undefined>();
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function AddBookReview() {
|
||||
group.addMember("everyone", "reader");
|
||||
|
||||
if (file) {
|
||||
createImage(file, { owner: group }).then(image => {
|
||||
createImage(file, { owner: group }).then((image) => {
|
||||
setCoverImage(image);
|
||||
});
|
||||
}
|
||||
@@ -59,7 +59,7 @@ export default function AddBookReview() {
|
||||
},
|
||||
{
|
||||
owner: me.profile._owner,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!me.profile.bookReviews) {
|
||||
@@ -88,7 +88,7 @@ export default function AddBookReview() {
|
||||
type="text"
|
||||
value={title}
|
||||
required
|
||||
onChange={e => setTitle(e.target.value)}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
></input>
|
||||
</label>
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function AddBookReview() {
|
||||
type="text"
|
||||
value={author}
|
||||
required
|
||||
onChange={e => setAuthor(e.target.value)}
|
||||
onChange={(e) => setAuthor(e.target.value)}
|
||||
></input>
|
||||
</label>
|
||||
|
||||
@@ -116,7 +116,10 @@ export default function AddBookReview() {
|
||||
|
||||
<div className="grid gap-1 text-sm text-gray-600">
|
||||
Rating
|
||||
<RatingInput value={rating} onChange={rating => setRating(rating)} />
|
||||
<RatingInput
|
||||
value={rating}
|
||||
onChange={(rating) => setRating(rating)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="grid gap-1 text-sm text-gray-600">
|
||||
@@ -124,7 +127,7 @@ export default function AddBookReview() {
|
||||
<textarea
|
||||
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||
value={review}
|
||||
onChange={e => setReview(e.target.value)}
|
||||
onChange={(e) => setReview(e.target.value)}
|
||||
></textarea>
|
||||
</label>
|
||||
<Button variant="primary" type="submit">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import { BookReview } from "@/schema";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import { BookCover } from "@/components/BookCover";
|
||||
import clsx from "clsx";
|
||||
import RatingInput from "@/components/RatingInput";
|
||||
import Rating from "@/components/Rating";
|
||||
import { Container } from "@/components/Container";
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import Rating from "@/components/Rating";
|
||||
import RatingInput from "@/components/RatingInput";
|
||||
import { BookReview } from "@/schema";
|
||||
import clsx from "clsx";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
|
||||
const BookReviewTitle = ({
|
||||
bookReview,
|
||||
@@ -28,9 +28,9 @@ const BookReviewTitle = ({
|
||||
placeholder="Book title"
|
||||
className={clsx(
|
||||
className,
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm"
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||
)}
|
||||
onChange={e => (bookReview.title = e.target.value)}
|
||||
onChange={(e) => (bookReview.title = e.target.value)}
|
||||
></input>
|
||||
);
|
||||
};
|
||||
@@ -54,9 +54,9 @@ const BookReviewAuthor = ({
|
||||
placeholder="Author"
|
||||
className={clsx(
|
||||
className,
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm"
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||
)}
|
||||
onChange={e => (bookReview.author = e.target.value)}
|
||||
onChange={(e) => (bookReview.author = e.target.value)}
|
||||
></input>
|
||||
);
|
||||
};
|
||||
@@ -85,9 +85,9 @@ const BookReviewDateRead = ({
|
||||
placeholder="Date read"
|
||||
className={clsx(
|
||||
className,
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm"
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||
)}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
const date = new Date(e.target.value);
|
||||
bookReview.dateRead = date;
|
||||
}}
|
||||
@@ -116,9 +116,9 @@ const BookReviewReview = ({
|
||||
placeholder="Write your review here..."
|
||||
className={clsx(
|
||||
className,
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm"
|
||||
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||
)}
|
||||
onChange={e => (bookReview.review = e.target.value)}
|
||||
onChange={(e) => (bookReview.review = e.target.value)}
|
||||
></textarea>
|
||||
);
|
||||
};
|
||||
@@ -139,7 +139,7 @@ const BookReviewRating = ({
|
||||
return (
|
||||
<RatingInput
|
||||
className={clsx(className, "p-2")}
|
||||
onChange={rating => (bookReview.rating = rating)}
|
||||
onChange={(rating) => (bookReview.rating = rating)}
|
||||
value={bookReview.rating}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { JazzAndAuth } from "@/components/JazzAndAuth";
|
||||
import { Nav } from "@/components/Nav";
|
||||
import { Fraunces } from "next/font/google";
|
||||
import clsx from "clsx";
|
||||
import { Fraunces } from "next/font/google";
|
||||
|
||||
const fraunces = Fraunces({
|
||||
subsets: ["latin"],
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { Container } from "@/components/Container";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import UserProfile from "@/components/UserProfile";
|
||||
import { JazzAccount } from "@/schema";
|
||||
import { ID } from "jazz-tools";
|
||||
import { Container } from "@/components/Container";
|
||||
|
||||
export default function Home() {
|
||||
const { me } = useAccount();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Container } from "@/components/Container";
|
||||
import UserProfile from "@/components/UserProfile";
|
||||
import { JazzAccount } from "@/schema";
|
||||
import { ID } from "jazz-tools";
|
||||
import { Container } from "@/components/Container";
|
||||
|
||||
export default function Page({ params }: { params: { slug: string } }) {
|
||||
const { slug } = params;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ProgressiveImg } from "jazz-react";
|
||||
import { BookReview } from "@/schema";
|
||||
import { ChangeEvent, useRef, useState } from "react";
|
||||
import { Group, ImageDefinition } from "jazz-tools";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import clsx from "clsx";
|
||||
import PlusIcon from "@/components/icons/PlusIcon";
|
||||
import { Button } from "@/components/Button";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import PlusIcon from "@/components/icons/PlusIcon";
|
||||
import { BookReview } from "@/schema";
|
||||
import clsx from "clsx";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
import { ProgressiveImg } from "jazz-react";
|
||||
import { Group, ImageDefinition } from "jazz-tools";
|
||||
import { ChangeEvent, useRef, useState } from "react";
|
||||
|
||||
const BookCoverContainer = ({
|
||||
children,
|
||||
@@ -68,7 +68,7 @@ export function BookCoverInput({ bookReview }: { bookReview: BookReview }) {
|
||||
const file = event.currentTarget.files?.[0];
|
||||
|
||||
if (file) {
|
||||
createImage(file, { owner: me.profile._owner }).then(image => {
|
||||
createImage(file, { owner: me.profile._owner }).then((image) => {
|
||||
bookReview.cover = image;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BookReview } from "@/schema";
|
||||
import Rating from "@/components/Rating";
|
||||
import { Group } from "jazz-tools";
|
||||
import RatingInput from "@/components/RatingInput";
|
||||
import { BookCover } from "@/components/BookCover";
|
||||
import Rating from "@/components/Rating";
|
||||
import RatingInput from "@/components/RatingInput";
|
||||
import { BookReview } from "@/schema";
|
||||
import { Group } from "jazz-tools";
|
||||
|
||||
export function BookReviewHeader({ bookReview }: { bookReview: BookReview }) {
|
||||
const { title, author, rating, review, dateRead } = bookReview;
|
||||
@@ -19,7 +19,7 @@ export function BookReviewHeader({ bookReview }: { bookReview: BookReview }) {
|
||||
|
||||
{bookReview._owner.castAs(Group).myRole() === "admin" ? (
|
||||
<RatingInput
|
||||
onChange={rating => (bookReview.rating = rating)}
|
||||
onChange={(rating) => (bookReview.rating = rating)}
|
||||
value={rating}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { BookReview } from "../schema";
|
||||
import { ID } from "jazz-tools";
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import Link from "next/link";
|
||||
import { BookCover, BookCoverReadOnly } from "@/components/BookCover";
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import StarIcon from "@/components/icons/StarIcon";
|
||||
import { ID } from "jazz-tools";
|
||||
import Link from "next/link";
|
||||
import { BookReview } from "../schema";
|
||||
|
||||
export function BookReviewThumbnail({ id }: { id: ID<BookReview> }) {
|
||||
const bookReview = useCoState(BookReview, id);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import type { ComponentProps } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface Props {
|
||||
variant?: "primary" | "secondary" | "tertiary";
|
||||
@@ -43,7 +43,7 @@ export function Button(props: AnchorProps | ButtonProps) {
|
||||
customClassName,
|
||||
variantClassNames.base,
|
||||
variantClassNames[variant],
|
||||
sizeClassNames[size]
|
||||
sizeClassNames[size],
|
||||
);
|
||||
|
||||
if (!!(props as AnchorProps).href) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { createJazzReactApp, useDemoAuth, DemoAuthBasicUI } from "jazz-react";
|
||||
import { JazzAccount } from "@/schema";
|
||||
import { DemoAuthBasicUI, createJazzReactApp, useDemoAuth } from "jazz-react";
|
||||
|
||||
const Jazz = createJazzReactApp({
|
||||
AccountSchema: JazzAccount,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/Button";
|
||||
import { Container } from "@/components/Container";
|
||||
import { useAccount } from "@/components/JazzAndAuth";
|
||||
import Link from "next/link";
|
||||
import { Container } from "@/components/Container";
|
||||
import { Button } from "@/components/Button";
|
||||
|
||||
export function Nav() {
|
||||
const { me, logOut } = useAccount();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import StarOutlineIcon from "@/components/icons/StarOutlineIcon";
|
||||
import StarIcon from "@/components/icons/StarIcon";
|
||||
import StarOutlineIcon from "@/components/icons/StarOutlineIcon";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface RatingInputProps {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import { JazzAccount, JazzProfile, ListOfBookReviews } from "@/schema";
|
||||
import { BookReviewThumbnail } from "@/components/BookReviewThumbnail";
|
||||
import { Button } from "@/components/Button";
|
||||
import { useCoState } from "@/components/JazzAndAuth";
|
||||
import { JazzAccount, JazzProfile, ListOfBookReviews } from "@/schema";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
|
||||
export default function UserProfile({ id }: { id: ID<JazzAccount> }) {
|
||||
const user = useCoState(JazzAccount, id);
|
||||
@@ -13,7 +13,7 @@ export default function UserProfile({ id }: { id: ID<JazzAccount> }) {
|
||||
const bookReviews = useCoState(
|
||||
ListOfBookReviews,
|
||||
user?.profile?._refs.bookReviews?.id,
|
||||
[{}]
|
||||
[{}],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -30,7 +30,7 @@ export default function UserProfile({ id }: { id: ID<JazzAccount> }) {
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-4">
|
||||
{bookReviews?.map(bookReview => (
|
||||
{bookReviews?.map((bookReview) => (
|
||||
<BookReviewThumbnail key={bookReview.id} id={bookReview.id} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
CoMap,
|
||||
co,
|
||||
CoList,
|
||||
Account,
|
||||
Profile,
|
||||
ImageDefinition,
|
||||
CoList,
|
||||
CoMap,
|
||||
Encoders,
|
||||
ImageDefinition,
|
||||
Profile,
|
||||
co,
|
||||
} from "jazz-tools";
|
||||
|
||||
export class BookReview extends CoMap {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
],
|
||||
ignorePatterns: ["dist", ".eslintrc.cjs"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["react-refresh"],
|
||||
rules: {},
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 80
|
||||
}
|
||||
@@ -1,5 +1,68 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f30eea]
|
||||
- Updated dependencies [149ca97]
|
||||
- cojson@0.8.21
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
- jazz-react-auth-clerk@0.8.21
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
- jazz-react-auth-clerk@0.8.20
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9c2aadb]
|
||||
- cojson@0.8.19
|
||||
- jazz-react@0.8.19
|
||||
- jazz-react-auth-clerk@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d4319d8]
|
||||
- cojson@0.8.18
|
||||
- jazz-react@0.8.18
|
||||
- jazz-react-auth-clerk@0.8.18
|
||||
- jazz-tools@0.8.18
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [d433cf4]
|
||||
- cojson@0.8.17
|
||||
- jazz-react@0.8.17
|
||||
- jazz-react-auth-clerk@0.8.17
|
||||
- jazz-tools@0.8.17
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [2af107c]
|
||||
- Updated dependencies [b934fab]
|
||||
- jazz-react@0.8.16
|
||||
- cojson@0.8.16
|
||||
- jazz-react-auth-clerk@0.8.16
|
||||
- jazz-tools@0.8.16
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
{
|
||||
"name": "jazz-example-chat-clerk",
|
||||
"private": true,
|
||||
"version": "0.0.92",
|
||||
"version": "0.0.98",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"format": "echo 'chat example is codegolfed'",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": "eslint --fix",
|
||||
"*.{js,jsx,mdx,json}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
@@ -21,11 +17,11 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.8.12",
|
||||
"cojson": "workspace:0.8.21",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"jazz-react": "workspace:0.8.15",
|
||||
"jazz-react-auth-clerk": "workspace:0.8.15",
|
||||
"jazz-tools": "workspace:0.8.15",
|
||||
"jazz-react": "workspace:0.8.21",
|
||||
"jazz-react-auth-clerk": "workspace:0.8.21",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
@@ -41,13 +37,8 @@
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"@types/react": "^18.2.19",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import { useIframeHashRouter } from "hash-slash";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import { ChatScreen } from "./chatScreen.tsx";
|
||||
import { useAccount } from "./main.tsx";
|
||||
import { Chat } from "./schema.ts";
|
||||
import { ChatScreen } from "./chatScreen.tsx";
|
||||
import { AppContainer, TopBar } from "./ui.tsx";
|
||||
|
||||
export function App() {
|
||||
@@ -24,7 +24,7 @@ export function App() {
|
||||
</TopBar>
|
||||
{router.route({
|
||||
"/": () => createChat() as never,
|
||||
"/chat/:id": id => <ChatScreen chatID={id as ID<Chat>} />,
|
||||
"/chat/:id": (id) => <ChatScreen chatID={id as ID<Chat>} />,
|
||||
})}
|
||||
</AppContainer>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ID } from "jazz-tools";
|
||||
import { Chat, Message } from "./schema.ts";
|
||||
import { useCoState } from "./main.tsx";
|
||||
import { Chat, Message } from "./schema.ts";
|
||||
import {
|
||||
BubbleBody,
|
||||
BubbleContainer,
|
||||
@@ -16,12 +16,12 @@ export function ChatScreen(props: { chatID: ID<Chat> }) {
|
||||
return chat ? (
|
||||
<ChatContainer>
|
||||
{chat.length > 0 ? (
|
||||
chat.map(msg => <ChatBubble msg={msg} key={msg.id} />)
|
||||
chat.map((msg) => <ChatBubble msg={msg} key={msg.id} />)
|
||||
) : (
|
||||
<EmptyChatMessage />
|
||||
)}
|
||||
<ChatInput
|
||||
onSubmit={text => {
|
||||
onSubmit={(text) => {
|
||||
chat.push(Message.create({ text }, { owner: chat._owner }));
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createJazzReactApp } from "jazz-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { createJazzReactApp } from "jazz-react";
|
||||
import { App } from "./app.tsx";
|
||||
|
||||
import { ClerkProvider, SignInButton, useClerk } from "@clerk/clerk-react";
|
||||
@@ -15,7 +15,7 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.errors.map(error => (
|
||||
{state.errors.map((error) => (
|
||||
<div key={error}>{error}</div>
|
||||
))}
|
||||
{auth ? (
|
||||
@@ -42,5 +42,5 @@ createRoot(document.getElementById("root")!).render(
|
||||
<App />
|
||||
</JazzAndAuth>
|
||||
</ClerkProvider>
|
||||
</StrictMode>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CoMap, CoList, co } from "jazz-tools";
|
||||
import { CoList, CoMap, co } from "jazz-tools";
|
||||
|
||||
export class Message extends CoMap {
|
||||
text = co.string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import path from "path";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// https://docs.expo.dev/guides/using-eslint/
|
||||
module.exports = {
|
||||
extends: 'expo',
|
||||
};
|
||||
@@ -1,74 +1,129 @@
|
||||
# chat-rn-clerk
|
||||
|
||||
## 1.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react-auth-clerk@0.8.21
|
||||
- jazz-react-native@0.8.21
|
||||
- jazz-react-native-media-images@0.8.17
|
||||
|
||||
## 1.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- jazz-react-native-media-images@0.8.16
|
||||
- jazz-react-native@0.8.20
|
||||
- jazz-react-auth-clerk@0.8.20
|
||||
|
||||
## 1.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.19
|
||||
- jazz-react-native@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
- jazz-react-native-media-images@0.8.15
|
||||
|
||||
## 1.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.18
|
||||
- jazz-react-native@0.8.18
|
||||
- jazz-tools@0.8.18
|
||||
- jazz-react-native-media-images@0.8.14
|
||||
|
||||
## 1.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.17
|
||||
- jazz-react-native@0.8.17
|
||||
- jazz-tools@0.8.17
|
||||
- jazz-react-native-media-images@0.8.13
|
||||
|
||||
## 1.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.16
|
||||
- jazz-react-native@0.8.16
|
||||
- jazz-tools@0.8.16
|
||||
- jazz-react-native-media-images@0.8.12
|
||||
|
||||
## 1.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [cce679b]
|
||||
- Updated dependencies [221c58f]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react-auth-clerk@0.8.15
|
||||
- jazz-react-native@0.8.15
|
||||
- jazz-react-native-media-images@0.8.11
|
||||
- Updated dependencies [cce679b]
|
||||
- Updated dependencies [221c58f]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react-auth-clerk@0.8.15
|
||||
- jazz-react-native@0.8.15
|
||||
- jazz-react-native-media-images@0.8.11
|
||||
|
||||
## 1.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react-auth-clerk@0.8.14
|
||||
- jazz-react-native@0.8.14
|
||||
- jazz-react-native-media-images@0.8.10
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react-auth-clerk@0.8.14
|
||||
- jazz-react-native@0.8.14
|
||||
- jazz-react-native-media-images@0.8.10
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react-auth-clerk@0.8.13
|
||||
- jazz-react-native@0.8.13
|
||||
- jazz-react-native-media-images@0.8.9
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react-auth-clerk@0.8.13
|
||||
- jazz-react-native@0.8.13
|
||||
- jazz-react-native-media-images@0.8.9
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.12
|
||||
- jazz-react-native@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
- jazz-react-native-media-images@0.8.8
|
||||
- jazz-react-auth-clerk@0.8.12
|
||||
- jazz-react-native@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
- jazz-react-native-media-images@0.8.8
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.11
|
||||
- jazz-react-native@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- jazz-react-native-media-images@0.8.7
|
||||
- jazz-react-auth-clerk@0.8.11
|
||||
- jazz-react-native@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- jazz-react-native-media-images@0.8.7
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
|
||||
- Updated dependencies [b7639cf]
|
||||
- jazz-react-native@0.8.8
|
||||
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
|
||||
- Updated dependencies [b7639cf]
|
||||
- jazz-react-native@0.8.8
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [32b05b6]
|
||||
- jazz-react-native-media-images@0.8.6
|
||||
- jazz-react-native@0.8.7
|
||||
- jazz-react-auth-clerk@0.8.7
|
||||
- Updated dependencies [32b05b6]
|
||||
- jazz-react-native-media-images@0.8.6
|
||||
- jazz-react-native@0.8.7
|
||||
- jazz-react-auth-clerk@0.8.7
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.6
|
||||
- jazz-react-auth-clerk@0.8.6
|
||||
- jazz-react-native@0.8.6
|
||||
- jazz-react-auth-clerk@0.8.6
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Redirect, Stack } from "expo-router";
|
||||
import React from "react";
|
||||
import { useAuth } from "../../src/auth-context";
|
||||
import { Redirect, Stack } from "expo-router";
|
||||
|
||||
export default function HomeLayout() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Redirect href={"/chat"} />;
|
||||
}
|
||||
if (isAuthenticated) {
|
||||
return <Redirect href={"/chat"} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{ headerShown: false, headerBackVisible: true }}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Stack screenOptions={{ headerShown: false, headerBackVisible: true }} />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import React from "react";
|
||||
import { SignedOut } from "@clerk/clerk-expo";
|
||||
import { Link } from "expo-router";
|
||||
import React from "react";
|
||||
import { Text, View } from "react-native";
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-100 p-6">
|
||||
<SignedOut>
|
||||
<View className="bg-white p-6 rounded-lg shadow-lg w-11/12 max-w-md">
|
||||
<Text className="text-2xl font-bold text-center text-gray-900 mb-4">
|
||||
Jazz 🤝 Clerk 🤝 Expo
|
||||
</Text>
|
||||
<Link href="/sign-in" className="mb-4">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign In
|
||||
</Text>
|
||||
</Link>
|
||||
<Link href="/sign-in-oauth" className="mb-4">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign In OAuth
|
||||
</Text>
|
||||
</Link>
|
||||
<Link href="/sign-up">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign Up
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</SignedOut>
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-100 p-6">
|
||||
<SignedOut>
|
||||
<View className="bg-white p-6 rounded-lg shadow-lg w-11/12 max-w-md">
|
||||
<Text className="text-2xl font-bold text-center text-gray-900 mb-4">
|
||||
Jazz 🤝 Clerk 🤝 Expo
|
||||
</Text>
|
||||
<Link href="/sign-in" className="mb-4">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign In
|
||||
</Text>
|
||||
</Link>
|
||||
<Link href="/sign-in-oauth" className="mb-4">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign In OAuth
|
||||
</Text>
|
||||
</Link>
|
||||
<Link href="/sign-up">
|
||||
<Text className="text-center text-blue-600 underline text-lg">
|
||||
Sign Up
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
);
|
||||
</SignedOut>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,19 +2,19 @@ import { Redirect, Stack } from "expo-router";
|
||||
import { useAuth } from "../../src/auth-context";
|
||||
|
||||
export default function UnAuthenticatedLayout() {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Redirect href={"/chat"} />;
|
||||
}
|
||||
if (isAuthenticated) {
|
||||
return <Redirect href={"/chat"} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
headerBackVisible: true,
|
||||
headerTitle: "",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
headerBackVisible: true,
|
||||
headerTitle: "",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
import React from "react";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import { Text, View, TouchableOpacity } from "react-native";
|
||||
import { Link } from "expo-router";
|
||||
import { useOAuth } from "@clerk/clerk-expo";
|
||||
import * as Linking from "expo-linking";
|
||||
import { Link } from "expo-router";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import React from "react";
|
||||
import { Text, TouchableOpacity, View } from "react-native";
|
||||
|
||||
export const useWarmUpBrowser = () => {
|
||||
React.useEffect(() => {
|
||||
// Warm up the android browser to improve UX
|
||||
// https://docs.expo.dev/guides/authentication/#improving-user-experience
|
||||
void WebBrowser.warmUpAsync();
|
||||
return () => {
|
||||
void WebBrowser.coolDownAsync();
|
||||
};
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
// Warm up the android browser to improve UX
|
||||
// https://docs.expo.dev/guides/authentication/#improving-user-experience
|
||||
void WebBrowser.warmUpAsync();
|
||||
return () => {
|
||||
void WebBrowser.coolDownAsync();
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
WebBrowser.maybeCompleteAuthSession();
|
||||
|
||||
const SignInWithOAuth = () => {
|
||||
useWarmUpBrowser();
|
||||
useWarmUpBrowser();
|
||||
|
||||
const { startOAuthFlow } = useOAuth({ strategy: "oauth_google" });
|
||||
const { startOAuthFlow } = useOAuth({ strategy: "oauth_google" });
|
||||
|
||||
const onPress = React.useCallback(async () => {
|
||||
try {
|
||||
const { createdSessionId, signIn, signUp, setActive } =
|
||||
await startOAuthFlow({
|
||||
redirectUrl: Linking.createURL("/", {
|
||||
scheme: "jazz-chat-rn-clerk",
|
||||
}),
|
||||
});
|
||||
const onPress = React.useCallback(async () => {
|
||||
try {
|
||||
const { createdSessionId, signIn, signUp, setActive } =
|
||||
await startOAuthFlow({
|
||||
redirectUrl: Linking.createURL("/", {
|
||||
scheme: "jazz-chat-rn-clerk",
|
||||
}),
|
||||
});
|
||||
|
||||
if (createdSessionId) {
|
||||
setActive!({ session: createdSessionId });
|
||||
} else {
|
||||
// Use signIn or signUp for next steps such as MFA
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("OAuth error", err);
|
||||
}
|
||||
}, []);
|
||||
if (createdSessionId) {
|
||||
setActive!({ session: createdSessionId });
|
||||
} else {
|
||||
// Use signIn or signUp for next steps such as MFA
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("OAuth error", err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-lg items-center">
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
className="w-full bg-red-500 py-3 rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Sign in with Google
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Link href="/" className="mt-4">
|
||||
<Text className="text-blue-600 text-lg font-semibold underline mb-6">
|
||||
Back to Home
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-lg items-center">
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
className="w-full bg-red-500 py-3 rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Sign in with Google
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<Link href="/" className="mt-4">
|
||||
<Text className="text-blue-600 text-lg font-semibold underline mb-6">
|
||||
Back to Home
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
export default SignInWithOAuth;
|
||||
|
||||
@@ -1,91 +1,79 @@
|
||||
import { useSignIn } from "@clerk/clerk-expo";
|
||||
import { Text, TextInput, View, TouchableOpacity } from "react-native";
|
||||
import React from "react";
|
||||
import { Link } from "expo-router";
|
||||
import React from "react";
|
||||
import { Text, TextInput, TouchableOpacity, View } from "react-native";
|
||||
|
||||
export default function SignInPage() {
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
const [emailAddress, setEmailAddress] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [errorMessage, setErrorMessage] = React.useState("");
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
const [emailAddress, setEmailAddress] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [errorMessage, setErrorMessage] = React.useState("");
|
||||
|
||||
const onSignInPress = React.useCallback(async () => {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
const onSignInPress = React.useCallback(async () => {
|
||||
if (!isLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
setErrorMessage("");
|
||||
setErrorMessage("");
|
||||
|
||||
try {
|
||||
const signInAttempt = await signIn.create({
|
||||
identifier: emailAddress,
|
||||
password,
|
||||
});
|
||||
try {
|
||||
const signInAttempt = await signIn.create({
|
||||
identifier: emailAddress,
|
||||
password,
|
||||
});
|
||||
|
||||
if (signInAttempt.status === "complete") {
|
||||
await setActive({ session: signInAttempt.createdSessionId });
|
||||
} else {
|
||||
console.error(JSON.stringify(signInAttempt, null, 2));
|
||||
setErrorMessage("Invalid credentials. Please try again.");
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
if (err.errors && err.errors[0]?.message) {
|
||||
setErrorMessage(err.errors[0].message);
|
||||
} else {
|
||||
setErrorMessage(
|
||||
"An unexpected error occurred. Please try again.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [isLoaded, emailAddress, password]);
|
||||
if (signInAttempt.status === "complete") {
|
||||
await setActive({ session: signInAttempt.createdSessionId });
|
||||
} else {
|
||||
console.error(JSON.stringify(signInAttempt, null, 2));
|
||||
setErrorMessage("Invalid credentials. Please try again.");
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
if (err.errors && err.errors[0]?.message) {
|
||||
setErrorMessage(err.errors[0].message);
|
||||
} else {
|
||||
setErrorMessage("An unexpected error occurred. Please try again.");
|
||||
}
|
||||
}
|
||||
}, [isLoaded, emailAddress, password]);
|
||||
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-md">
|
||||
<Text className="text-3xl font-bold text-center text-gray-800 mb-6">
|
||||
Sign In
|
||||
</Text>
|
||||
{errorMessage ? (
|
||||
<Text className="text-red-500 text-center mb-4">
|
||||
{errorMessage}
|
||||
</Text>
|
||||
) : null}
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
value={emailAddress}
|
||||
placeholder="Email..."
|
||||
onChangeText={(emailAddress) =>
|
||||
setEmailAddress(emailAddress)
|
||||
}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TextInput
|
||||
value={password}
|
||||
placeholder="Password..."
|
||||
secureTextEntry={true}
|
||||
onChangeText={(password) => setPassword(password)}
|
||||
className="w-full h-12 mb-6 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onSignInPress}
|
||||
className="w-full h-12 bg-blue-600 rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Sign In
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<View className="flex-row items-center justify-center mt-4">
|
||||
<Text className="text-gray-600">
|
||||
Don't have an account?
|
||||
</Text>
|
||||
<Link href="/sign-up">
|
||||
<Text className="text-blue-500 ml-2 font-semibold">
|
||||
Sign up
|
||||
</Text>
|
||||
</Link>
|
||||
</View>
|
||||
</View>
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-md">
|
||||
<Text className="text-3xl font-bold text-center text-gray-800 mb-6">
|
||||
Sign In
|
||||
</Text>
|
||||
{errorMessage ? (
|
||||
<Text className="text-red-500 text-center mb-4">{errorMessage}</Text>
|
||||
) : null}
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
value={emailAddress}
|
||||
placeholder="Email..."
|
||||
onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TextInput
|
||||
value={password}
|
||||
placeholder="Password..."
|
||||
secureTextEntry={true}
|
||||
onChangeText={(password) => setPassword(password)}
|
||||
className="w-full h-12 mb-6 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onSignInPress}
|
||||
className="w-full h-12 bg-blue-600 rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">Sign In</Text>
|
||||
</TouchableOpacity>
|
||||
<View className="flex-row items-center justify-center mt-4">
|
||||
<Text className="text-gray-600">Don't have an account?</Text>
|
||||
<Link href="/sign-up">
|
||||
<Text className="text-blue-500 ml-2 font-semibold">Sign up</Text>
|
||||
</Link>
|
||||
</View>
|
||||
);
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,128 +1,120 @@
|
||||
import * as React from "react";
|
||||
import { TextInput, View, Text, TouchableOpacity } from "react-native";
|
||||
import { useSignUp } from "@clerk/clerk-expo";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import * as React from "react";
|
||||
import { Text, TextInput, TouchableOpacity, View } from "react-native";
|
||||
|
||||
export default function SignUpPage() {
|
||||
const { isLoaded, signUp, setActive } = useSignUp();
|
||||
const { isLoaded, signUp, setActive } = useSignUp();
|
||||
|
||||
const [emailAddress, setEmailAddress] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [pendingVerification, setPendingVerification] = React.useState(false);
|
||||
const [code, setCode] = React.useState("");
|
||||
const [errorMessage, setErrorMessage] = React.useState("");
|
||||
const navigation = useNavigation();
|
||||
const [emailAddress, setEmailAddress] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [pendingVerification, setPendingVerification] = React.useState(false);
|
||||
const [code, setCode] = React.useState("");
|
||||
const [errorMessage, setErrorMessage] = React.useState("");
|
||||
const navigation = useNavigation();
|
||||
|
||||
const onSignUpPress = async () => {
|
||||
if (!isLoaded) return;
|
||||
const onSignUpPress = async () => {
|
||||
if (!isLoaded) return;
|
||||
|
||||
setErrorMessage("");
|
||||
setErrorMessage("");
|
||||
|
||||
try {
|
||||
await signUp.create({
|
||||
emailAddress,
|
||||
password,
|
||||
});
|
||||
try {
|
||||
await signUp.create({
|
||||
emailAddress,
|
||||
password,
|
||||
});
|
||||
|
||||
await signUp.prepareEmailAddressVerification({
|
||||
strategy: "email_code",
|
||||
});
|
||||
await signUp.prepareEmailAddressVerification({
|
||||
strategy: "email_code",
|
||||
});
|
||||
|
||||
setPendingVerification(true);
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
if (err.errors && err.errors[0]?.message) {
|
||||
setErrorMessage(err.errors[0].message);
|
||||
} else {
|
||||
setErrorMessage(
|
||||
"An unexpected error occurred. Please try again.",
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
setPendingVerification(true);
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
if (err.errors && err.errors[0]?.message) {
|
||||
setErrorMessage(err.errors[0].message);
|
||||
} else {
|
||||
setErrorMessage("An unexpected error occurred. Please try again.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onPressVerify = async () => {
|
||||
if (!isLoaded) return;
|
||||
const onPressVerify = async () => {
|
||||
if (!isLoaded) return;
|
||||
|
||||
setErrorMessage("");
|
||||
setErrorMessage("");
|
||||
|
||||
try {
|
||||
const completeSignUp = await signUp.attemptEmailAddressVerification(
|
||||
{
|
||||
code,
|
||||
},
|
||||
);
|
||||
try {
|
||||
const completeSignUp = await signUp.attemptEmailAddressVerification({
|
||||
code,
|
||||
});
|
||||
|
||||
if (completeSignUp.status === "complete") {
|
||||
await setActive({ session: completeSignUp.createdSessionId });
|
||||
} else {
|
||||
console.error(JSON.stringify(completeSignUp, null, 2));
|
||||
setErrorMessage("Failed to verify. Please check your code.");
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
setErrorMessage("Invalid verification code. Please try again.");
|
||||
}
|
||||
};
|
||||
if (completeSignUp.status === "complete") {
|
||||
await setActive({ session: completeSignUp.createdSessionId });
|
||||
} else {
|
||||
console.error(JSON.stringify(completeSignUp, null, 2));
|
||||
setErrorMessage("Failed to verify. Please check your code.");
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(JSON.stringify(err, null, 2));
|
||||
setErrorMessage("Invalid verification code. Please try again.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-lg">
|
||||
<Text className="text-3xl font-bold text-center text-gray-800 mb-6">
|
||||
{pendingVerification ? "Verify Email" : "Sign Up"}
|
||||
</Text>
|
||||
{errorMessage ? (
|
||||
<Text className="text-red-500 text-center mb-4">
|
||||
{errorMessage}
|
||||
</Text>
|
||||
) : null}
|
||||
return (
|
||||
<View className="flex-1 justify-center items-center bg-gray-50 p-6">
|
||||
<View className="bg-white w-11/12 max-w-md p-8 rounded-lg shadow-lg">
|
||||
<Text className="text-3xl font-bold text-center text-gray-800 mb-6">
|
||||
{pendingVerification ? "Verify Email" : "Sign Up"}
|
||||
</Text>
|
||||
{errorMessage ? (
|
||||
<Text className="text-red-500 text-center mb-4">{errorMessage}</Text>
|
||||
) : null}
|
||||
|
||||
{!pendingVerification && (
|
||||
<>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
value={emailAddress}
|
||||
placeholder="Email..."
|
||||
onChangeText={(email) => setEmailAddress(email)}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TextInput
|
||||
value={password}
|
||||
placeholder="Password..."
|
||||
secureTextEntry={true}
|
||||
onChangeText={(password) => setPassword(password)}
|
||||
className="w-full h-12 mb-6 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onSignUpPress}
|
||||
className="w-full h-12 bg-blue-600 rounded-lg flex justify-center items-center mb-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Sign Up
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
{!pendingVerification && (
|
||||
<>
|
||||
<TextInput
|
||||
autoCapitalize="none"
|
||||
value={emailAddress}
|
||||
placeholder="Email..."
|
||||
onChangeText={(email) => setEmailAddress(email)}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TextInput
|
||||
value={password}
|
||||
placeholder="Password..."
|
||||
secureTextEntry={true}
|
||||
onChangeText={(password) => setPassword(password)}
|
||||
className="w-full h-12 mb-6 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onSignUpPress}
|
||||
className="w-full h-12 bg-blue-600 rounded-lg flex justify-center items-center mb-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">Sign Up</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
|
||||
{pendingVerification && (
|
||||
<>
|
||||
<TextInput
|
||||
value={code}
|
||||
placeholder="Verification Code..."
|
||||
onChangeText={(code) => setCode(code)}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onPressVerify}
|
||||
className="w-full h-12 bg-green-600 rounded-lg flex justify-center items-center mb-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Verify Email
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
{pendingVerification && (
|
||||
<>
|
||||
<TextInput
|
||||
value={code}
|
||||
placeholder="Verification Code..."
|
||||
onChangeText={(code) => setCode(code)}
|
||||
className="w-full h-12 mb-4 px-4 bg-gray-100 border border-gray-300 rounded-lg focus:border-blue-500 focus:outline-none"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={onPressVerify}
|
||||
className="w-full h-12 bg-green-600 rounded-lg flex justify-center items-center mb-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold">
|
||||
Verify Email
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,31 +6,29 @@ import { type PropsWithChildren } from "react";
|
||||
* 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 }: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
|
||||
{/*
|
||||
{/*
|
||||
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 />
|
||||
<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>
|
||||
);
|
||||
{/* 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 = `
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Link, Stack } from "expo-router";
|
||||
import { StyleSheet, View, Text } from "react-native";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
return (
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import { useFonts } from "expo-font";
|
||||
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
|
||||
import { useFonts } from "expo-font";
|
||||
import { Slot } from "expo-router";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import React, { useEffect } from "react";
|
||||
import { Slot } from "expo-router";
|
||||
import { tokenCache } from "../cache";
|
||||
import { JazzAndAuth } from "../src/auth-context";
|
||||
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded] = useFonts({
|
||||
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||
});
|
||||
const [loaded] = useFonts({
|
||||
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!publishableKey) {
|
||||
throw new Error(
|
||||
"Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env",
|
||||
);
|
||||
}
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
return (
|
||||
<ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
if (!publishableKey) {
|
||||
throw new Error(
|
||||
"Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env",
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,173 +1,168 @@
|
||||
import React, {
|
||||
SafeAreaView,
|
||||
View,
|
||||
Text,
|
||||
Alert,
|
||||
TouchableOpacity,
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
TextInput,
|
||||
Button,
|
||||
} from "react-native";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { useState, useEffect, useLayoutEffect } from "react";
|
||||
import { useAccount, useCoState } from "@/src/jazz";
|
||||
import { Chat, Message } from "@/src/schema";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||
import clsx from "clsx";
|
||||
import { useNavigation, useFocusEffect } from "@react-navigation/native";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import { useLocalSearchParams } from "expo-router";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import { useEffect, useLayoutEffect, useState } from "react";
|
||||
import React, {
|
||||
SafeAreaView,
|
||||
View,
|
||||
Text,
|
||||
Alert,
|
||||
TouchableOpacity,
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
TextInput,
|
||||
Button,
|
||||
} from "react-native";
|
||||
|
||||
export default function Conversation() {
|
||||
const { chatId } = useLocalSearchParams();
|
||||
const { me } = useAccount();
|
||||
const [chat, setChat] = useState<Chat>();
|
||||
const [message, setMessage] = useState("");
|
||||
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
||||
const navigation = useNavigation();
|
||||
const { chatId } = useLocalSearchParams();
|
||||
const { me } = useAccount();
|
||||
const [chat, setChat] = useState<Chat>();
|
||||
const [message, setMessage] = useState("");
|
||||
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
||||
const navigation = useNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
if (chat) return;
|
||||
if (chatId === "new") {
|
||||
createChat();
|
||||
} else {
|
||||
loadChat(chatId as ID<Chat>);
|
||||
}
|
||||
}, [chat]);
|
||||
useEffect(() => {
|
||||
if (chat) return;
|
||||
if (chatId === "new") {
|
||||
createChat();
|
||||
} else {
|
||||
loadChat(chatId as ID<Chat>);
|
||||
}
|
||||
}, [chat]);
|
||||
|
||||
// Effect to dynamically set header options
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerTitle: "Chat",
|
||||
headerRight: () =>
|
||||
chat ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (chat?.id) {
|
||||
Clipboard.setStringAsync(
|
||||
`https://chat.jazz.tools/#/chat/${chat.id}`,
|
||||
);
|
||||
Alert.alert(
|
||||
"Copied to clipboard",
|
||||
`Chat ID: ${chat.id}`,
|
||||
);
|
||||
}
|
||||
}}
|
||||
title="Share"
|
||||
/>
|
||||
) : null,
|
||||
});
|
||||
}, [navigation, chat]);
|
||||
// Effect to dynamically set header options
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerTitle: "Chat",
|
||||
headerRight: () =>
|
||||
chat ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (chat?.id) {
|
||||
Clipboard.setStringAsync(
|
||||
`https://chat.jazz.tools/#/chat/${chat.id}`,
|
||||
);
|
||||
Alert.alert("Copied to clipboard", `Chat ID: ${chat.id}`);
|
||||
}
|
||||
}}
|
||||
title="Share"
|
||||
/>
|
||||
) : null,
|
||||
});
|
||||
}, [navigation, chat]);
|
||||
|
||||
const createChat = () => {
|
||||
const group = Group.create({ owner: me });
|
||||
group.addMember("everyone", "writer");
|
||||
const chat = Chat.create([], { owner: group });
|
||||
setChat(chat);
|
||||
};
|
||||
const createChat = () => {
|
||||
const group = Group.create({ owner: me });
|
||||
group.addMember("everyone", "writer");
|
||||
const chat = Chat.create([], { owner: group });
|
||||
setChat(chat);
|
||||
};
|
||||
|
||||
const loadChat = async (chatId: ID<Chat>) => {
|
||||
try {
|
||||
const chat = await Chat.load(chatId, me, []);
|
||||
setChat(chat);
|
||||
} catch (error) {
|
||||
console.log("Error loading chat", error);
|
||||
Alert.alert("Error", `Error loading chat: ${error}`);
|
||||
}
|
||||
};
|
||||
const loadChat = async (chatId: ID<Chat>) => {
|
||||
try {
|
||||
const chat = await Chat.load(chatId, me, []);
|
||||
setChat(chat);
|
||||
} catch (error) {
|
||||
console.log("Error loading chat", error);
|
||||
Alert.alert("Error", `Error loading chat: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!chat) return;
|
||||
if (message.trim()) {
|
||||
chat.push(
|
||||
Message.create({ text: message }, { owner: chat._owner }),
|
||||
);
|
||||
setMessage("");
|
||||
}
|
||||
};
|
||||
|
||||
const renderMessageItem = ({ item }: { item: Message }) => {
|
||||
const isMe = item._edits.text.by?.isMe;
|
||||
return (
|
||||
<View
|
||||
className={clsx(
|
||||
`rounded-xl px-3 py-2 max-w-[75%] my-1`,
|
||||
isMe ? `bg-blue-500 self-end` : `bg-gray-200 self-start`,
|
||||
)}
|
||||
>
|
||||
{!isMe ? (
|
||||
<Text
|
||||
className={clsx(
|
||||
`text-xs text-gray-500 mb-1`,
|
||||
isMe ? "text-right" : "text-left",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.by?.profile?.name}
|
||||
</Text>
|
||||
) : null}
|
||||
<View
|
||||
className={clsx(
|
||||
"flex relative items-end justify-between",
|
||||
isMe ? "flex-row" : "flex-row",
|
||||
)}
|
||||
>
|
||||
<Text
|
||||
className={clsx(
|
||||
!isMe ? "text-black" : "text-gray-200",
|
||||
`text-md max-w-[85%]`,
|
||||
)}
|
||||
>
|
||||
{item.text}
|
||||
</Text>
|
||||
<Text
|
||||
className={clsx(
|
||||
"text-[10px] text-right ml-2",
|
||||
!isMe ? "mt-2 text-gray-500" : "mt-1 text-gray-200",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.madeAt.getHours()}:
|
||||
{item._edits.text.madeAt.getMinutes()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
const sendMessage = () => {
|
||||
if (!chat) return;
|
||||
if (message.trim()) {
|
||||
chat.push(Message.create({ text: message }, { owner: chat._owner }));
|
||||
setMessage("");
|
||||
}
|
||||
};
|
||||
|
||||
const renderMessageItem = ({ item }: { item: Message }) => {
|
||||
const isMe = item._edits.text.by?.isMe;
|
||||
return (
|
||||
<View className="flex-1 bg-gray-50">
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 8,
|
||||
}}
|
||||
className="flex"
|
||||
data={loadedChat}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderMessageItem}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
keyboardVerticalOffset={110}
|
||||
behavior="padding"
|
||||
className="p-3 bg-white border-t border-gray-300"
|
||||
>
|
||||
<SafeAreaView className="flex-row items-center gap-2">
|
||||
<TextInput
|
||||
className="flex-1 rounded-full h-10 px-4 bg-gray-100 border border-gray-300 focus:border-blue-500 focus:bg-white"
|
||||
value={message}
|
||||
onChangeText={setMessage}
|
||||
placeholder="Type a message..."
|
||||
textAlignVertical="center"
|
||||
onSubmitEditing={sendMessage}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={sendMessage}
|
||||
className="bg-blue-500 rounded-full h-10 w-10 items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-xl">↑</Text>
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
<View
|
||||
className={clsx(
|
||||
`rounded-xl px-3 py-2 max-w-[75%] my-1`,
|
||||
isMe ? `bg-blue-500 self-end` : `bg-gray-200 self-start`,
|
||||
)}
|
||||
>
|
||||
{!isMe ? (
|
||||
<Text
|
||||
className={clsx(
|
||||
`text-xs text-gray-500 mb-1`,
|
||||
isMe ? "text-right" : "text-left",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.by?.profile?.name}
|
||||
</Text>
|
||||
) : null}
|
||||
<View
|
||||
className={clsx(
|
||||
"flex relative items-end justify-between",
|
||||
isMe ? "flex-row" : "flex-row",
|
||||
)}
|
||||
>
|
||||
<Text
|
||||
className={clsx(
|
||||
!isMe ? "text-black" : "text-gray-200",
|
||||
`text-md max-w-[85%]`,
|
||||
)}
|
||||
>
|
||||
{item.text}
|
||||
</Text>
|
||||
<Text
|
||||
className={clsx(
|
||||
"text-[10px] text-right ml-2",
|
||||
!isMe ? "mt-2 text-gray-500" : "mt-1 text-gray-200",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.madeAt.getHours()}:
|
||||
{item._edits.text.madeAt.getMinutes()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-gray-50">
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 8,
|
||||
}}
|
||||
className="flex"
|
||||
data={loadedChat}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderMessageItem}
|
||||
/>
|
||||
<KeyboardAvoidingView
|
||||
keyboardVerticalOffset={110}
|
||||
behavior="padding"
|
||||
className="p-3 bg-white border-t border-gray-300"
|
||||
>
|
||||
<SafeAreaView className="flex-row items-center gap-2">
|
||||
<TextInput
|
||||
className="flex-1 rounded-full h-10 px-4 bg-gray-100 border border-gray-300 focus:border-blue-500 focus:bg-white"
|
||||
value={message}
|
||||
onChangeText={setMessage}
|
||||
placeholder="Type a message..."
|
||||
textAlignVertical="center"
|
||||
onSubmitEditing={sendMessage}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={sendMessage}
|
||||
className="bg-blue-500 rounded-full h-10 w-10 items-center justify-center"
|
||||
>
|
||||
<Text className="text-white text-xl">↑</Text>
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
import { Stack } from "expo-router";
|
||||
import React from "react";
|
||||
|
||||
export default function ChatLayout() {
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
headerBackVisible: true,
|
||||
headerTitle: "",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
headerBackVisible: true,
|
||||
headerTitle: "",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,85 +1,85 @@
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useRouter } from "expo-router";
|
||||
import { ID } from "jazz-tools";
|
||||
import { useLayoutEffect } from "react";
|
||||
import React, {
|
||||
Button,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
Alert,
|
||||
Button,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
import { ID } from "jazz-tools";
|
||||
import { useRouter } from "expo-router";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
import { Chat } from "../../src/schema";
|
||||
import { useAccount } from "../../src/jazz";
|
||||
import { useUser } from "@clerk/clerk-expo";
|
||||
import { useAccount } from "../../src/jazz";
|
||||
import { Chat } from "../../src/schema";
|
||||
|
||||
export default function ChatScreen() {
|
||||
const { logOut } = useAccount();
|
||||
const router = useRouter();
|
||||
const navigation = useNavigation();
|
||||
const { user } = useUser();
|
||||
const { logOut } = useAccount();
|
||||
const router = useRouter();
|
||||
const navigation = useNavigation();
|
||||
const { user } = useUser();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerTitle: "Chat",
|
||||
headerRight: () => <Button onPress={logOut} title="Logout" />,
|
||||
});
|
||||
}, [navigation]);
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerTitle: "Chat",
|
||||
headerRight: () => <Button onPress={logOut} title="Logout" />,
|
||||
});
|
||||
}, [navigation]);
|
||||
|
||||
const loadChat = async (chatId: ID<Chat> | "new") => {
|
||||
router.navigate(`/chat/${chatId}`);
|
||||
};
|
||||
const loadChat = async (chatId: ID<Chat> | "new") => {
|
||||
router.navigate(`/chat/${chatId}`);
|
||||
};
|
||||
|
||||
const joinChat = () => {
|
||||
Alert.prompt(
|
||||
"Join Chat",
|
||||
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
|
||||
[
|
||||
{
|
||||
text: "Cancel",
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Join",
|
||||
onPress: (chatId) => {
|
||||
if (chatId) {
|
||||
loadChat(chatId as ID<Chat>);
|
||||
} else {
|
||||
Alert.alert("Error", "Chat ID cannot be empty.");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"plain-text",
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-gray-50">
|
||||
<View className="flex-1 justify-center items-center px-6">
|
||||
<View className="w-full max-w-sm bg-white p-8 rounded-lg shadow-lg">
|
||||
<Text className="text-xl font-semibold text-gray-800">
|
||||
Welcome, {user?.emailAddresses[0].emailAddress}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => loadChat("new")}
|
||||
className="w-full bg-blue-600 py-4 rounded-md mb-4 mt-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold text-center">
|
||||
Start New Chat
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={joinChat}
|
||||
className="w-full bg-green-500 py-4 rounded-md"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold text-center">
|
||||
Join Chat
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
const joinChat = () => {
|
||||
Alert.prompt(
|
||||
"Join Chat",
|
||||
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
|
||||
[
|
||||
{
|
||||
text: "Cancel",
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Join",
|
||||
onPress: (chatId) => {
|
||||
if (chatId) {
|
||||
loadChat(chatId as ID<Chat>);
|
||||
} else {
|
||||
Alert.alert("Error", "Chat ID cannot be empty.");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"plain-text",
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-gray-50">
|
||||
<View className="flex-1 justify-center items-center px-6">
|
||||
<View className="w-full max-w-sm bg-white p-8 rounded-lg shadow-lg">
|
||||
<Text className="text-xl font-semibold text-gray-800">
|
||||
Welcome, {user?.emailAddresses[0].emailAddress}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => loadChat("new")}
|
||||
className="w-full bg-blue-600 py-4 rounded-md mb-4 mt-4"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold text-center">
|
||||
Start New Chat
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={joinChat}
|
||||
className="w-full bg-green-500 py-4 rounded-md"
|
||||
>
|
||||
<Text className="text-white text-lg font-semibold text-center">
|
||||
Join Chat
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
plugins: [
|
||||
"nativewind/babel",
|
||||
"@babel/plugin-transform-class-static-block",
|
||||
],
|
||||
};
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
plugins: ["nativewind/babel", "@babel/plugin-transform-class-static-block"],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,35 +2,35 @@ import * as SecureStore from "expo-secure-store";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export interface TokenCache {
|
||||
getToken: (key: string) => Promise<string | undefined | null>;
|
||||
saveToken: (key: string, token: string) => Promise<void>;
|
||||
clearToken?: (key: string) => void;
|
||||
getToken: (key: string) => Promise<string | undefined | null>;
|
||||
saveToken: (key: string, token: string) => Promise<void>;
|
||||
clearToken?: (key: string) => void;
|
||||
}
|
||||
|
||||
const createTokenCache = (): TokenCache => {
|
||||
return {
|
||||
getToken: async (key: string) => {
|
||||
try {
|
||||
const item = await SecureStore.getItemAsync(key);
|
||||
if (item) {
|
||||
console.log(`${key} was used 🔐 \n`);
|
||||
} else {
|
||||
console.log("No values stored under key: " + key);
|
||||
}
|
||||
return item;
|
||||
} catch (error) {
|
||||
console.error("secure store get item error: ", error);
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
saveToken: (key: string, token: string) => {
|
||||
return SecureStore.setItemAsync(key, token);
|
||||
},
|
||||
};
|
||||
return {
|
||||
getToken: async (key: string) => {
|
||||
try {
|
||||
const item = await SecureStore.getItemAsync(key);
|
||||
if (item) {
|
||||
console.log(`${key} was used 🔐 \n`);
|
||||
} else {
|
||||
console.log("No values stored under key: " + key);
|
||||
}
|
||||
return item;
|
||||
} catch (error) {
|
||||
console.error("secure store get item error: ", error);
|
||||
await SecureStore.deleteItemAsync(key);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
saveToken: (key: string, token: string) => {
|
||||
return SecureStore.setItemAsync(key, token);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// SecureStore is not supported on the web
|
||||
// https://github.com/expo/expo/issues/7744#issuecomment-611093485
|
||||
export const tokenCache =
|
||||
Platform.OS !== "web" ? createTokenCache() : undefined;
|
||||
Platform.OS !== "web" ? createTokenCache() : undefined;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 12.5.1",
|
||||
"appVersionSource": "remote"
|
||||
"cli": {
|
||||
"version": ">= 12.5.1",
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"ios-simulator": {
|
||||
"extends": "development",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"production": {
|
||||
"autoIncrement": true
|
||||
}
|
||||
"ios-simulator": {
|
||||
"extends": "development",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"production": {
|
||||
"autoIncrement": true
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ const config = getDefaultConfig(projectRoot);
|
||||
config.watchFolders = [workspaceRoot];
|
||||
// #2 - Try resolving with project modules first, then workspace modules
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
|
||||
config.resolver.unstable_enablePackageExports = true;
|
||||
@@ -23,9 +23,9 @@ config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
|
||||
|
||||
// Use turborepo to restore the cache when possible
|
||||
config.cacheStores = [
|
||||
new FileStore({
|
||||
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
|
||||
}),
|
||||
new FileStore({
|
||||
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
|
||||
}),
|
||||
];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -1,80 +1,79 @@
|
||||
{
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.8",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"test": "jest --watchAll",
|
||||
"lint": "expo lint"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@bam.tech/react-native-image-resizer": "^3.0.10",
|
||||
"@clerk/clerk-expo": "^2.2.21",
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-native-community/netinfo": "^11.3.1",
|
||||
"@react-navigation/native": "^6.1.18",
|
||||
"@react-navigation/native-stack": "^6.11.0",
|
||||
"base-64": "^1.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"clsx": "^2.0.0",
|
||||
"expo": "~51.0.37",
|
||||
"expo-build-properties": "~0.12.5",
|
||||
"expo-clipboard": "~6.0.3",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-crypto": "~13.0.2",
|
||||
"expo-dev-client": "~4.0.28",
|
||||
"expo-file-system": "^17.0.1",
|
||||
"expo-font": "~12.0.4",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-router": "~3.5.23",
|
||||
"expo-secure-store": "~13.0.2",
|
||||
"expo-splash-screen": "~0.27.5",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-system-ui": "~3.0.7",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"jazz-react-auth-clerk": "workspace:*",
|
||||
"jazz-react-native": "workspace:*",
|
||||
"jazz-react-native-media-images": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"nativewind": "^2.0.11",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-native": "~0.74.5",
|
||||
"react-native-fetch-api": "^3.0.0",
|
||||
"react-native-gesture-handler": "~2.16.1",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-mmkv": "3.0.1",
|
||||
"react-native-polyfill-globals": "^3.1.0",
|
||||
"react-native-quick-base64": "^2.1.2",
|
||||
"react-native-reanimated": "~3.10.1",
|
||||
"react-native-safe-area-context": "4.10.5",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-web": "~0.19.10",
|
||||
"text-encoding": "^0.7.0",
|
||||
"web-streams-polyfill": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.7",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/react": "^18.2.19",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-expo": "^7.1.2",
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~51.0.3",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.14",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"test": "jest --watchAll"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@bam.tech/react-native-image-resizer": "^3.0.10",
|
||||
"@clerk/clerk-expo": "^2.2.21",
|
||||
"@expo/vector-icons": "^14.0.2",
|
||||
"@react-native-community/netinfo": "^11.3.1",
|
||||
"@react-navigation/native": "^6.1.18",
|
||||
"@react-navigation/native-stack": "^6.11.0",
|
||||
"base-64": "^1.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"clsx": "^2.0.0",
|
||||
"expo": "~51.0.37",
|
||||
"expo-build-properties": "~0.12.5",
|
||||
"expo-clipboard": "~6.0.3",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-crypto": "~13.0.2",
|
||||
"expo-dev-client": "~4.0.28",
|
||||
"expo-file-system": "^17.0.1",
|
||||
"expo-font": "~12.0.4",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-router": "~3.5.23",
|
||||
"expo-secure-store": "~13.0.2",
|
||||
"expo-splash-screen": "~0.27.5",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-system-ui": "~3.0.7",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"jazz-react-auth-clerk": "workspace:*",
|
||||
"jazz-react-native": "workspace:*",
|
||||
"jazz-react-native-media-images": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"nativewind": "^2.0.11",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-native": "~0.74.5",
|
||||
"react-native-fetch-api": "^3.0.0",
|
||||
"react-native-gesture-handler": "~2.16.1",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-mmkv": "3.0.1",
|
||||
"react-native-polyfill-globals": "^3.1.0",
|
||||
"react-native-quick-base64": "^2.1.2",
|
||||
"react-native-reanimated": "~3.10.1",
|
||||
"react-native-safe-area-context": "4.10.5",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-web": "~0.19.10",
|
||||
"text-encoding": "^0.7.0",
|
||||
"web-streams-polyfill": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.7",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/react": "^18.2.19",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~51.0.3",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import "react-native-polyfill-globals/auto";
|
||||
import "@azure/core-asynciterator-polyfill";
|
||||
import { ReadableStream } from "web-streams-polyfill/ponyfill/es6";
|
||||
import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
|
||||
import { Buffer } from "buffer";
|
||||
import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
|
||||
import { ReadableStream } from "web-streams-polyfill/ponyfill/es6";
|
||||
|
||||
polyfillGlobal("Buffer", () => Buffer);
|
||||
polyfillGlobal("ReadableStream", () => ReadableStream);
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
import React, {
|
||||
useContext,
|
||||
createContext,
|
||||
useEffect,
|
||||
useState,
|
||||
PropsWithChildren,
|
||||
} from "react";
|
||||
import { useClerk, useUser } from "@clerk/clerk-expo";
|
||||
import { useJazzClerkAuth } from "jazz-react-auth-clerk";
|
||||
import { Jazz } from "./jazz";
|
||||
import React, {
|
||||
useContext,
|
||||
createContext,
|
||||
useEffect,
|
||||
useState,
|
||||
PropsWithChildren,
|
||||
} from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { Jazz } from "./jazz";
|
||||
|
||||
const AuthContext = createContext<{
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
}>({
|
||||
isAuthenticated: false,
|
||||
isLoading: true,
|
||||
isAuthenticated: false,
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
export function useAuth() {
|
||||
return useContext(AuthContext);
|
||||
return useContext(AuthContext);
|
||||
}
|
||||
|
||||
export function JazzAndAuth({ children }: PropsWithChildren) {
|
||||
const { isSignedIn, isLoaded: isClerkLoaded } = useUser();
|
||||
const clerk = useClerk();
|
||||
const [auth, state] = useJazzClerkAuth(clerk);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const { isSignedIn, isLoaded: isClerkLoaded } = useUser();
|
||||
const clerk = useClerk();
|
||||
const [auth, state] = useJazzClerkAuth(clerk);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSignedIn && isClerkLoaded && auth) {
|
||||
setIsAuthenticated(true);
|
||||
} else {
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
}, [isSignedIn, isClerkLoaded, auth]);
|
||||
useEffect(() => {
|
||||
if (isSignedIn && isClerkLoaded && auth) {
|
||||
setIsAuthenticated(true);
|
||||
} else {
|
||||
setIsAuthenticated(false);
|
||||
}
|
||||
}, [isSignedIn, isClerkLoaded, auth]);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{ isAuthenticated, isLoading: !isClerkLoaded || !auth }}
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{ isAuthenticated, isLoading: !isClerkLoaded || !auth }}
|
||||
>
|
||||
{state?.errors?.length > 0 &&
|
||||
state.errors.map((error) => (
|
||||
<View key={error}>
|
||||
<Text style={{ color: "red" }}>{error}</Text>
|
||||
</View>
|
||||
))}
|
||||
{auth ? (
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
peer="wss://cloud.jazz.tools/?key=chat-rn-clerk-example-jazz@gcmp.io"
|
||||
storage={undefined}
|
||||
>
|
||||
{state?.errors?.length > 0 &&
|
||||
state.errors.map((error) => (
|
||||
<View key={error}>
|
||||
<Text style={{ color: "red" }}>{error}</Text>
|
||||
</View>
|
||||
))}
|
||||
{auth ? (
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
peer="wss://cloud.jazz.tools/?key=chat-rn-clerk-example-jazz@gcmp.io"
|
||||
storage={undefined}
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CoMap, CoList, co } from "jazz-tools";
|
||||
import { CoList, CoMap, co } from "jazz-tools";
|
||||
|
||||
export class Message extends CoMap {
|
||||
text = co.string;
|
||||
text = co.string;
|
||||
}
|
||||
|
||||
export class Chat extends CoList.Of(co.ref(Message)) {}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./app/**/*.{js,jsx,ts,tsx}",
|
||||
"./components/**/*.{js,jsx,ts,tsx}",
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
content: [
|
||||
"./app/**/*.{js,jsx,ts,tsx}",
|
||||
"./components/**/*.{js,jsx,ts,tsx}",
|
||||
"./src/**/*.{js,jsx,ts,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
@@ -4,13 +4,8 @@
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
]
|
||||
"include": ["**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
|
||||
@@ -1,77 +1,120 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react-native@0.8.21
|
||||
|
||||
## 1.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- jazz-react-native@0.8.20
|
||||
|
||||
## 1.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 1.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.18
|
||||
- jazz-tools@0.8.18
|
||||
|
||||
## 1.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.17
|
||||
- jazz-tools@0.8.17
|
||||
|
||||
## 1.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.16
|
||||
- jazz-tools@0.8.16
|
||||
|
||||
## 1.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [cce679b]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react-native@0.8.15
|
||||
- Updated dependencies [cce679b]
|
||||
- jazz-tools@0.8.15
|
||||
- jazz-react-native@0.8.15
|
||||
|
||||
## 1.0.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react-native@0.8.14
|
||||
- Updated dependencies [36273b3]
|
||||
- jazz-tools@0.8.14
|
||||
- jazz-react-native@0.8.14
|
||||
|
||||
## 1.0.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react-native@0.8.13
|
||||
- Updated dependencies [fd011d7]
|
||||
- jazz-tools@0.8.13
|
||||
- jazz-react-native@0.8.13
|
||||
|
||||
## 1.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
- jazz-react-native@0.8.12
|
||||
- jazz-tools@0.8.12
|
||||
|
||||
## 1.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
- jazz-react-native@0.8.11
|
||||
- jazz-tools@0.8.11
|
||||
|
||||
## 1.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
|
||||
- Updated dependencies [b7639cf]
|
||||
- jazz-react-native@0.8.8
|
||||
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
|
||||
- Updated dependencies [b7639cf]
|
||||
- jazz-react-native@0.8.8
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [32b05b6]
|
||||
- jazz-react-native@0.8.7
|
||||
- Updated dependencies [32b05b6]
|
||||
- jazz-react-native@0.8.7
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.6
|
||||
- jazz-react-native@0.8.6
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [c3f4e6b]
|
||||
- Updated dependencies [d9152ed]
|
||||
- jazz-react-native@0.8.5
|
||||
- jazz-tools@0.8.5
|
||||
- Updated dependencies [c3f4e6b]
|
||||
- Updated dependencies [d9152ed]
|
||||
- jazz-react-native@0.8.5
|
||||
- jazz-tools@0.8.5
|
||||
|
||||
## 1.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react-native@0.8.3
|
||||
- jazz-tools@0.8.3
|
||||
- Updated dependencies
|
||||
- jazz-react-native@0.8.3
|
||||
- jazz-tools@0.8.3
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
plugins: [
|
||||
"nativewind/babel",
|
||||
"@babel/plugin-transform-class-static-block",
|
||||
],
|
||||
};
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
plugins: ["nativewind/babel", "@babel/plugin-transform-class-static-block"],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 12.5.1",
|
||||
"appVersionSource": "remote"
|
||||
"cli": {
|
||||
"version": ">= 12.5.1",
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"developmentClient": true,
|
||||
"distribution": "internal"
|
||||
},
|
||||
"ios-simulator": {
|
||||
"extends": "development",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"production": {
|
||||
"autoIncrement": true
|
||||
}
|
||||
"ios-simulator": {
|
||||
"extends": "development",
|
||||
"ios": {
|
||||
"simulator": true
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
"preview": {
|
||||
"distribution": "internal"
|
||||
},
|
||||
"production": {
|
||||
"autoIncrement": true
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ const config = getDefaultConfig(projectRoot);
|
||||
config.watchFolders = [workspaceRoot];
|
||||
// #2 - Try resolving with project modules first, then workspace modules
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
|
||||
config.resolver.unstable_enablePackageExports = true;
|
||||
@@ -23,9 +23,9 @@ config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
|
||||
|
||||
// Use turborepo to restore the cache when possible
|
||||
config.cacheStores = [
|
||||
new FileStore({
|
||||
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
|
||||
}),
|
||||
new FileStore({
|
||||
root: path.join(projectRoot, "node_modules", ".cache", "metro"),
|
||||
}),
|
||||
];
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -1,52 +1,54 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.10",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@react-native-community/netinfo": "^11.3.1",
|
||||
"@react-navigation/native": "^6.1.18",
|
||||
"@react-navigation/native-stack": "^6.11.0",
|
||||
"base-64": "^1.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"expo": "~51.0.37",
|
||||
"expo-build-properties": "~0.12.5",
|
||||
"expo-clipboard": "~6.0.3",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-crypto": "~13.0.2",
|
||||
"expo-dev-client": "~4.0.28",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-secure-store": "~13.0.2",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"jazz-react-native": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"nativewind": "^2.0.11",
|
||||
"react": "^18.2.0",
|
||||
"react-native": "~0.74.5",
|
||||
"react-native-fetch-api": "^3.0.0",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-mmkv": "3.0.1",
|
||||
"react-native-polyfill-globals": "^3.1.0",
|
||||
"react-native-safe-area-context": "4.10.5",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"text-encoding": "^0.7.0",
|
||||
"web-streams-polyfill": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.7",
|
||||
"@types/react": "^18.2.19",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.16",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@react-native-community/netinfo": "^11.3.1",
|
||||
"@react-navigation/native": "^6.1.18",
|
||||
"@react-navigation/native-stack": "^6.11.0",
|
||||
"base-64": "^1.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"expo": "~51.0.37",
|
||||
"expo-build-properties": "~0.12.5",
|
||||
"expo-clipboard": "~6.0.3",
|
||||
"expo-constants": "~16.0.2",
|
||||
"expo-crypto": "~13.0.2",
|
||||
"expo-dev-client": "~4.0.28",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-secure-store": "~13.0.2",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"jazz-react-native": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"nativewind": "^2.0.11",
|
||||
"react": "^18.2.0",
|
||||
"react-native": "~0.74.5",
|
||||
"react-native-fetch-api": "^3.0.0",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-mmkv": "3.0.1",
|
||||
"react-native-polyfill-globals": "^3.1.0",
|
||||
"react-native-safe-area-context": "4.10.5",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"text-encoding": "^0.7.0",
|
||||
"web-streams-polyfill": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.7",
|
||||
"@types/react": "^18.2.19",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "react-native-polyfill-globals/auto";
|
||||
import "@azure/core-asynciterator-polyfill";
|
||||
import { ReadableStream } from "web-streams-polyfill/ponyfill/es6";
|
||||
import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
|
||||
import { ReadableStream } from "web-streams-polyfill/ponyfill/es6";
|
||||
|
||||
polyfillGlobal("ReadableStream", () => ReadableStream);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { StrictMode, useEffect, useState } from "react";
|
||||
import {
|
||||
NavigationContainer,
|
||||
useNavigationContainerRef,
|
||||
NavigationContainer,
|
||||
useNavigationContainerRef,
|
||||
} from "@react-navigation/native";
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
import * as Linking from "expo-linking";
|
||||
import React, { StrictMode, useEffect, useState } from "react";
|
||||
import HandleInviteScreen from "./invite";
|
||||
|
||||
import { useDemoAuth, DemoAuthBasicUI } from "jazz-react-native";
|
||||
import { DemoAuthBasicUI, useDemoAuth } from "jazz-react-native";
|
||||
import ChatScreen from "./chat";
|
||||
import { Jazz } from "./jazz";
|
||||
|
||||
@@ -16,64 +16,64 @@ const Stack = createNativeStackNavigator();
|
||||
const prefix = Linking.createURL("/");
|
||||
|
||||
const linking = {
|
||||
prefixes: [prefix],
|
||||
config: {
|
||||
screens: {
|
||||
HandleInviteScreen: {
|
||||
path: "router/invite/:valueHint?/:valueID/:inviteSecret",
|
||||
},
|
||||
},
|
||||
prefixes: [prefix],
|
||||
config: {
|
||||
screens: {
|
||||
HandleInviteScreen: {
|
||||
path: "router/invite/:valueHint?/:valueID/:inviteSecret",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function App() {
|
||||
const [auth, state] = useDemoAuth();
|
||||
const [initialRoute, setInitialRoute] = useState<
|
||||
"ChatScreen" | "HandleInviteScreen"
|
||||
>("ChatScreen");
|
||||
const navigationRef = useNavigationContainerRef();
|
||||
const [auth, state] = useDemoAuth();
|
||||
const [initialRoute, setInitialRoute] = useState<
|
||||
"ChatScreen" | "HandleInviteScreen"
|
||||
>("ChatScreen");
|
||||
const navigationRef = useNavigationContainerRef();
|
||||
|
||||
useEffect(() => {
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url) {
|
||||
if (url && url.includes("invite")) {
|
||||
setInitialRoute("HandleInviteScreen");
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url) {
|
||||
if (url && url.includes("invite")) {
|
||||
setInitialRoute("HandleInviteScreen");
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!auth) {
|
||||
return null;
|
||||
}
|
||||
if (!auth) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StrictMode>
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
peer="wss://cloud.jazz.tools/?key=chat-rn-example-jazz@gcmp.io"
|
||||
storage={undefined}
|
||||
>
|
||||
<NavigationContainer linking={linking} ref={navigationRef}>
|
||||
<Stack.Navigator initialRouteName={initialRoute}>
|
||||
<Stack.Screen
|
||||
options={{ title: "Jazz Chat" }}
|
||||
name="ChatScreen"
|
||||
// @ts-ignore
|
||||
component={ChatScreen}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="HandleInviteScreen"
|
||||
component={HandleInviteScreen}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
</Jazz.Provider>
|
||||
{state.state !== "signedIn" ? (
|
||||
<DemoAuthBasicUI appName="Jazz Chat" state={state} />
|
||||
) : null}
|
||||
</StrictMode>
|
||||
);
|
||||
return (
|
||||
<StrictMode>
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
peer="wss://cloud.jazz.tools/?key=chat-rn-example-jazz@gcmp.io"
|
||||
storage={undefined}
|
||||
>
|
||||
<NavigationContainer linking={linking} ref={navigationRef}>
|
||||
<Stack.Navigator initialRouteName={initialRoute}>
|
||||
<Stack.Screen
|
||||
options={{ title: "Jazz Chat" }}
|
||||
name="ChatScreen"
|
||||
// @ts-ignore
|
||||
component={ChatScreen}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="HandleInviteScreen"
|
||||
component={HandleInviteScreen}
|
||||
/>
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
</Jazz.Provider>
|
||||
{state.state !== "signedIn" ? (
|
||||
<DemoAuthBasicUI appName="Jazz Chat" state={state} />
|
||||
) : null}
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,207 +1,196 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import React, {
|
||||
Button,
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
SafeAreaView,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import clsx from "clsx";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import { useEffect, useState } from "react";
|
||||
import React, {
|
||||
Button,
|
||||
FlatList,
|
||||
KeyboardAvoidingView,
|
||||
SafeAreaView,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
Alert,
|
||||
} from "react-native";
|
||||
|
||||
import { Chat, Message } from "./schema";
|
||||
import { useAccount, useCoState } from "./jazz";
|
||||
import { Chat, Message } from "./schema";
|
||||
|
||||
export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
const { me, logOut } = useAccount();
|
||||
const [chat, setChat] = useState<Chat>();
|
||||
const [message, setMessage] = useState("");
|
||||
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
||||
const { me, logOut } = useAccount();
|
||||
const [chat, setChat] = useState<Chat>();
|
||||
const [message, setMessage] = useState("");
|
||||
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerRight: () => <Button onPress={logOut} title="Logout" />,
|
||||
headerLeft: () =>
|
||||
chat ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (chat?.id) {
|
||||
Clipboard.setStringAsync(
|
||||
`https://chat.jazz.tools/#/chat/${chat.id}`,
|
||||
);
|
||||
Alert.alert(
|
||||
"Copied to clipboard",
|
||||
`Chat ID: ${chat.id}`,
|
||||
);
|
||||
}
|
||||
}}
|
||||
title="Share"
|
||||
/>
|
||||
) : null,
|
||||
});
|
||||
}, [navigation, chat]);
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerRight: () => <Button onPress={logOut} title="Logout" />,
|
||||
headerLeft: () =>
|
||||
chat ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (chat?.id) {
|
||||
Clipboard.setStringAsync(
|
||||
`https://chat.jazz.tools/#/chat/${chat.id}`,
|
||||
);
|
||||
Alert.alert("Copied to clipboard", `Chat ID: ${chat.id}`);
|
||||
}
|
||||
}}
|
||||
title="Share"
|
||||
/>
|
||||
) : null,
|
||||
});
|
||||
}, [navigation, chat]);
|
||||
|
||||
const createChat = () => {
|
||||
const group = Group.create({ owner: me });
|
||||
group.addMember("everyone", "writer");
|
||||
const chat = Chat.create([], { owner: group });
|
||||
setChat(chat);
|
||||
};
|
||||
const createChat = () => {
|
||||
const group = Group.create({ owner: me });
|
||||
group.addMember("everyone", "writer");
|
||||
const chat = Chat.create([], { owner: group });
|
||||
setChat(chat);
|
||||
};
|
||||
|
||||
const loadChat = async (chatId: ID<Chat>) => {
|
||||
try {
|
||||
const chat = await Chat.load(chatId, me, []);
|
||||
setChat(chat);
|
||||
} catch (error) {
|
||||
console.log("Error loading chat", error);
|
||||
Alert.alert("Error", `Error loading chat: ${error}`);
|
||||
}
|
||||
};
|
||||
const loadChat = async (chatId: ID<Chat>) => {
|
||||
try {
|
||||
const chat = await Chat.load(chatId, me, []);
|
||||
setChat(chat);
|
||||
} catch (error) {
|
||||
console.log("Error loading chat", error);
|
||||
Alert.alert("Error", `Error loading chat: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const joinChat = () => {
|
||||
Alert.prompt(
|
||||
"Join Chat",
|
||||
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
|
||||
[
|
||||
{
|
||||
text: "Cancel",
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Join",
|
||||
onPress: (chatId) => {
|
||||
if (chatId) {
|
||||
loadChat(chatId as ID<Chat>);
|
||||
} else {
|
||||
Alert.alert("Error", "Chat ID cannot be empty.");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"plain-text",
|
||||
);
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!chat) return;
|
||||
if (message.trim()) {
|
||||
chat.push(
|
||||
Message.create({ text: message }, { owner: chat._owner }),
|
||||
);
|
||||
setMessage("");
|
||||
}
|
||||
};
|
||||
|
||||
const renderMessageItem = ({ item }: { item: Message }) => {
|
||||
const isMe = item._edits.text.by?.isMe;
|
||||
return (
|
||||
<View
|
||||
className={clsx(
|
||||
`rounded-lg p-1 px-1.5 max-w-[80%] `,
|
||||
|
||||
isMe
|
||||
? `bg-gray-200 self-end text-right`
|
||||
: `bg-gray-300 self-start `,
|
||||
)}
|
||||
>
|
||||
{!isMe ? (
|
||||
<Text
|
||||
className={clsx(
|
||||
`text-xs text-gray-500`,
|
||||
isMe ? "text-right" : "text-left",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.by?.profile?.name}
|
||||
</Text>
|
||||
) : null}
|
||||
<View
|
||||
className={clsx(
|
||||
"flex relative items-end justify-between",
|
||||
isMe ? "flex-row" : "flex-row",
|
||||
)}
|
||||
>
|
||||
<Text className={clsx(`text-black text-md max-w-[85%]`)}>
|
||||
{item.text}
|
||||
</Text>
|
||||
<Text
|
||||
className={clsx(
|
||||
"text-[10px] text-gray-500 text-right ml-2",
|
||||
!isMe ? "mt-2" : "mt-1",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.madeAt.getHours()}:
|
||||
{item._edits.text.madeAt.getMinutes()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex flex-col h-full">
|
||||
{!chat ? (
|
||||
<View className="flex flex-col h-full items-center justify-center">
|
||||
<TouchableOpacity
|
||||
onPress={createChat}
|
||||
className="bg-blue-500 p-4 rounded-md"
|
||||
>
|
||||
<Text className="text-white font-semibold">
|
||||
Start new chat
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={joinChat}
|
||||
className="bg-green-500 p-4 rounded-md mt-4"
|
||||
>
|
||||
<Text className="text-white font-semibold">
|
||||
Join chat
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
flex: 1,
|
||||
gap: 6,
|
||||
padding: 8,
|
||||
}}
|
||||
className="flex"
|
||||
data={loadedChat}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderMessageItem}
|
||||
/>
|
||||
|
||||
<KeyboardAvoidingView
|
||||
keyboardVerticalOffset={110}
|
||||
behavior="padding"
|
||||
className="p-3 bg-white border-t border-gray-300"
|
||||
>
|
||||
<SafeAreaView className="flex flex-row items-center gap-2">
|
||||
<TextInput
|
||||
className="rounded-full h-8 py-0 px-2 border border-gray-200 block flex-1"
|
||||
value={message}
|
||||
onChangeText={setMessage}
|
||||
placeholder="Type a message..."
|
||||
textAlignVertical="center"
|
||||
onSubmitEditing={sendMessage}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={sendMessage}
|
||||
className="bg-gray-300 text-white rounded-full h-8 w-8 items-center justify-center"
|
||||
>
|
||||
<Text>↑</Text>
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
const joinChat = () => {
|
||||
Alert.prompt(
|
||||
"Join Chat",
|
||||
"Enter the Chat ID (example: co_zBGEHYvRfGuT2YSBraY3njGjnde)",
|
||||
[
|
||||
{
|
||||
text: "Cancel",
|
||||
style: "cancel",
|
||||
},
|
||||
{
|
||||
text: "Join",
|
||||
onPress: (chatId) => {
|
||||
if (chatId) {
|
||||
loadChat(chatId as ID<Chat>);
|
||||
} else {
|
||||
Alert.alert("Error", "Chat ID cannot be empty.");
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"plain-text",
|
||||
);
|
||||
};
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!chat) return;
|
||||
if (message.trim()) {
|
||||
chat.push(Message.create({ text: message }, { owner: chat._owner }));
|
||||
setMessage("");
|
||||
}
|
||||
};
|
||||
|
||||
const renderMessageItem = ({ item }: { item: Message }) => {
|
||||
const isMe = item._edits.text.by?.isMe;
|
||||
return (
|
||||
<View
|
||||
className={clsx(
|
||||
`rounded-lg p-1 px-1.5 max-w-[80%] `,
|
||||
|
||||
isMe ? `bg-gray-200 self-end text-right` : `bg-gray-300 self-start `,
|
||||
)}
|
||||
>
|
||||
{!isMe ? (
|
||||
<Text
|
||||
className={clsx(
|
||||
`text-xs text-gray-500`,
|
||||
isMe ? "text-right" : "text-left",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.by?.profile?.name}
|
||||
</Text>
|
||||
) : null}
|
||||
<View
|
||||
className={clsx(
|
||||
"flex relative items-end justify-between",
|
||||
isMe ? "flex-row" : "flex-row",
|
||||
)}
|
||||
>
|
||||
<Text className={clsx(`text-black text-md max-w-[85%]`)}>
|
||||
{item.text}
|
||||
</Text>
|
||||
<Text
|
||||
className={clsx(
|
||||
"text-[10px] text-gray-500 text-right ml-2",
|
||||
!isMe ? "mt-2" : "mt-1",
|
||||
)}
|
||||
>
|
||||
{item._edits.text.madeAt.getHours()}:
|
||||
{item._edits.text.madeAt.getMinutes()}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="flex flex-col h-full">
|
||||
{!chat ? (
|
||||
<View className="flex flex-col h-full items-center justify-center">
|
||||
<TouchableOpacity
|
||||
onPress={createChat}
|
||||
className="bg-blue-500 p-4 rounded-md"
|
||||
>
|
||||
<Text className="text-white font-semibold">Start new chat</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={joinChat}
|
||||
className="bg-green-500 p-4 rounded-md mt-4"
|
||||
>
|
||||
<Text className="text-white font-semibold">Join chat</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
flex: 1,
|
||||
gap: 6,
|
||||
padding: 8,
|
||||
}}
|
||||
className="flex"
|
||||
data={loadedChat}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={renderMessageItem}
|
||||
/>
|
||||
|
||||
<KeyboardAvoidingView
|
||||
keyboardVerticalOffset={110}
|
||||
behavior="padding"
|
||||
className="p-3 bg-white border-t border-gray-300"
|
||||
>
|
||||
<SafeAreaView className="flex flex-row items-center gap-2">
|
||||
<TextInput
|
||||
className="rounded-full h-8 py-0 px-2 border border-gray-200 block flex-1"
|
||||
value={message}
|
||||
onChangeText={setMessage}
|
||||
placeholder="Type a message..."
|
||||
textAlignVertical="center"
|
||||
onSubmitEditing={sendMessage}
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={sendMessage}
|
||||
className="bg-gray-300 text-white rounded-full h-8 w-8 items-center justify-center"
|
||||
>
|
||||
<Text>↑</Text>
|
||||
</TouchableOpacity>
|
||||
</SafeAreaView>
|
||||
</KeyboardAvoidingView>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import { useAcceptInvite } from "./jazz";
|
||||
import { Chat } from "./schema";
|
||||
|
||||
type ChatScreenParams = {
|
||||
valueHint?: string;
|
||||
valueID?: string;
|
||||
inviteSecret?: string;
|
||||
valueHint?: string;
|
||||
valueID?: string;
|
||||
inviteSecret?: string;
|
||||
};
|
||||
|
||||
export default function HandleInviteScreen({
|
||||
navigation,
|
||||
navigation,
|
||||
}: {
|
||||
navigation: any;
|
||||
navigation: any;
|
||||
}) {
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: Chat,
|
||||
onAccept: async (chatId) => {
|
||||
navigation.navigate("ChatScreen", { chatId });
|
||||
},
|
||||
});
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: Chat,
|
||||
onAccept: async (chatId) => {
|
||||
navigation.navigate("ChatScreen", { chatId });
|
||||
},
|
||||
});
|
||||
|
||||
return <Text>Accepting invite...</Text>;
|
||||
return <Text>Accepting invite...</Text>;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user