Compare commits
98 Commits
pre-releas
...
authv2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f519cd7356 | ||
|
|
20babc2e41 | ||
|
|
6fc8efc1b5 | ||
|
|
0b9cc2e0aa | ||
|
|
610543c5b8 | ||
|
|
53dbfee385 | ||
|
|
9c577b98cd | ||
|
|
b2c03ca8c4 | ||
|
|
3feb3e82ff | ||
|
|
95e9bdb624 | ||
|
|
68127a95d0 | ||
|
|
690e65ac06 | ||
|
|
6f007a9470 | ||
|
|
23c588c717 | ||
|
|
494d902e79 | ||
|
|
eebee4db6a | ||
|
|
44f38f3438 | ||
|
|
950db502c2 | ||
|
|
33293f3b64 | ||
|
|
d5d448670d | ||
|
|
e3c3913c74 | ||
|
|
2ee2fcaa27 | ||
|
|
edc77c4bd5 | ||
|
|
0ce25f143b | ||
|
|
e35018a5fa | ||
|
|
0d05c927f1 | ||
|
|
4ef43e3477 | ||
|
|
d7ce078f17 | ||
|
|
95349d59f8 | ||
|
|
bb13b6e41d | ||
|
|
3c2da2f0db | ||
|
|
7bac26cf7f | ||
|
|
d3d9200acf | ||
|
|
60558ee664 | ||
|
|
6354135af4 | ||
|
|
69a0befb7b | ||
|
|
c48aa7358a | ||
|
|
01517d1463 | ||
|
|
cdd7e6454f | ||
|
|
2e030ea942 | ||
|
|
5207c00582 | ||
|
|
22fa01b9c5 | ||
|
|
f5a1e42dd6 | ||
|
|
58fe07351e | ||
|
|
25e79fd001 | ||
|
|
53868a4d50 | ||
|
|
21f6f4d933 | ||
|
|
21f691b6da | ||
|
|
45c46ff14a | ||
|
|
6dad0878da | ||
|
|
3b6552d728 | ||
|
|
e83ce5d6c8 | ||
|
|
b6c672a1ad | ||
|
|
e2cfa60caa | ||
|
|
fa8f4e03d2 | ||
|
|
ac3d9fa2c3 | ||
|
|
ff325ebf2a | ||
|
|
71825cb90a | ||
|
|
d1281e560a | ||
|
|
a1dc7f38ed | ||
|
|
581118b918 | ||
|
|
ab378b3d16 | ||
|
|
dd034643da | ||
|
|
bc2e531c05 | ||
|
|
334f6cf2d8 | ||
|
|
31296a6ce2 | ||
|
|
6fc8ae8293 | ||
|
|
a1d6e7147b | ||
|
|
0d111b15f8 | ||
|
|
95b9c4825f | ||
|
|
65e26d9ba4 | ||
|
|
cabce37133 | ||
|
|
20c4484066 | ||
|
|
17dc5fc37f | ||
|
|
05d907b675 | ||
|
|
62cb650356 | ||
|
|
9f833b3931 | ||
|
|
f0d123979c | ||
|
|
90078a4bce | ||
|
|
2d3aa76bf0 | ||
|
|
86bd86b966 | ||
|
|
bebd0ede64 | ||
|
|
14bdd496f7 | ||
|
|
91fcb3f6b6 | ||
|
|
ff94cadd7b | ||
|
|
60fe8f9106 | ||
|
|
5158581b0c | ||
|
|
ee511b4934 | ||
|
|
bc13653236 | ||
|
|
fe28e254a3 | ||
|
|
82179812de | ||
|
|
d42c2aa014 | ||
|
|
ff92316488 | ||
|
|
a93df6fcb2 | ||
|
|
b5962b4f81 | ||
|
|
f038c2d378 | ||
|
|
febafecfdd | ||
|
|
70c9a5db88 |
5
.changeset/chatty-cherries-poke.md
Normal file
5
.changeset/chatty-cherries-poke.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Drop node 14 polyfill for globalThis.crypto
|
||||
5
.changeset/clever-actors-turn.md
Normal file
5
.changeset/clever-actors-turn.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"jazz-tools": patch
|
||||
---
|
||||
|
||||
Make ensureLoaded throw when the resolved value is undefined
|
||||
5
.changeset/loud-spiders-reply.md
Normal file
5
.changeset/loud-spiders-reply.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-jazz-app": patch
|
||||
---
|
||||
|
||||
Correctly handle cd when the project name has spaces
|
||||
6
.changeset/proud-scissors-double.md
Normal file
6
.changeset/proud-scissors-double.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"jazz-tools": minor
|
||||
---
|
||||
|
||||
Group.addMember and Group.removeMember are not chainable anymore.
|
||||
Group.removeMember now returns the internal promise.
|
||||
5
.changeset/thick-bags-glow.md
Normal file
5
.changeset/thick-bags-glow.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Reduce the retries on coValue not found to two
|
||||
5
.changeset/thirty-lemons-call.md
Normal file
5
.changeset/thirty-lemons-call.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Add role mapping to Group.extend
|
||||
36
.github/actions/source-code/action.yml
vendored
Normal file
36
.github/actions/source-code/action.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Get and Build Source Code
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Enable latestcorepack
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
|
||||
npm install -g corepack@latest
|
||||
echo "After : corepack version => $(corepack --version)"
|
||||
corepack enable
|
||||
pnpm --version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: useblacksmith/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: useblacksmith/cache@v5
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: pnpm install --frozen-lockfile
|
||||
38
.github/workflows/build-examples.yaml
vendored
38
.github/workflows/build-examples.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build-examples:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
strategy:
|
||||
matrix:
|
||||
example: [
|
||||
@@ -23,41 +23,13 @@ jobs:
|
||||
]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Enable latestcorepack
|
||||
run: |
|
||||
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
|
||||
npm install -g corepack@latest
|
||||
echo "After : corepack version => $(corepack --version)"
|
||||
corepack enable
|
||||
pnpm --version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Pnpm Build
|
||||
run: |
|
||||
|
||||
57
.github/workflows/build-starters.yaml
vendored
57
.github/workflows/build-starters.yaml
vendored
@@ -2,56 +2,25 @@ name: Build Starters
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
build-starters:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
strategy:
|
||||
matrix:
|
||||
starter: [
|
||||
"react-demo-auth-tailwind",
|
||||
]
|
||||
starter: ["react-demo-auth-tailwind"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Enable latestcorepack
|
||||
run: |
|
||||
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
|
||||
npm install -g corepack@latest
|
||||
echo "After : corepack version => $(corepack --version)"
|
||||
corepack enable
|
||||
pnpm --version
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
|
||||
- name: Pnpm Build
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm turbo build;
|
||||
working-directory: ./starters/${{ matrix.starter }}
|
||||
- name: Pnpm Build
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm turbo build;
|
||||
working-directory: ./starters/${{ matrix.starter }}
|
||||
|
||||
2
.github/workflows/code-quality.yml
vendored
2
.github/workflows/code-quality.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
91
.github/workflows/e2e-rn-test.yml
vendored
Normal file
91
.github/workflows/e2e-rn-test.yml
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
name: End-to-End Tests for React Native
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- ".github/workflows/e2e-rn-test.yml"
|
||||
- "examples/chat-rn/**"
|
||||
- "examples/chat-rn-clerk/**"
|
||||
- "packages/jazz-react-native*/**"
|
||||
|
||||
jobs:
|
||||
e2e-tests:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Create Output Directory
|
||||
run: |
|
||||
mkdir -p ~/output
|
||||
|
||||
- name: Pnpm Build
|
||||
run: pnpm turbo build --filter="./packages/*"
|
||||
|
||||
- name: iOS Simulator
|
||||
id: ios-simulator
|
||||
uses: futureware-tech/simulator-action@v4
|
||||
with:
|
||||
os: iOS
|
||||
wait_for_boot: true
|
||||
|
||||
- name: chat-rn App Pre Build
|
||||
working-directory: ./examples/chat-rn
|
||||
run: |
|
||||
pnpm build
|
||||
pnpm expo prebuild --clean
|
||||
|
||||
- name: chat-rn App Build
|
||||
working-directory: ./examples/chat-rn/ios
|
||||
run: |
|
||||
xcodebuild -scheme "jazzchatrn" \
|
||||
-workspace jazzchatrn.xcworkspace \
|
||||
-archivePath $RUNNER_TEMP/jazzchatrn.xcarchive \
|
||||
-derivedDataPath $RUNNER_TEMP/build \
|
||||
-destination "id=${{ steps.ios-simulator.outputs.udid }}" \
|
||||
-configuration Release \
|
||||
-sdk iphonesimulator \
|
||||
build
|
||||
xcrun simctl install booted $RUNNER_TEMP/build/Build/Products/Release-iphonesimulator/jazzchatrn.app
|
||||
xcrun simctl spawn booted log stream --level debug | tee ~/output/sim.log &
|
||||
|
||||
|
||||
- name: Install Maestro
|
||||
run: |
|
||||
curl -fsSL "https://get.maestro.mobile.dev" | bash
|
||||
|
||||
- name: chat-rn App Test
|
||||
id: e2e_test
|
||||
working-directory: ./examples/chat-rn
|
||||
continue-on-error: true
|
||||
run: |
|
||||
export PATH="$PATH":"$HOME/.maestro/bin"
|
||||
export MAESTRO_DRIVER_STARTUP_TIMEOUT=300000 # setting to 5 mins 👀
|
||||
export MAESTRO_CLI_NO_ANALYTICS=1
|
||||
maestro test test/e2e/flow.yml
|
||||
|
||||
- name: Copy Maestro and Diagnostic Files
|
||||
if: steps.e2e_test.outcome != 'success'
|
||||
run: |
|
||||
cp -r ~/Library/Logs/DiagnosticReports/* ~/output
|
||||
cp -r ~/.maestro/tests/* ~/output
|
||||
|
||||
- name: Upload Output Files
|
||||
if: steps.e2e_test.outcome != 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: e2e-test-output
|
||||
path: ~/output/*
|
||||
retention-days: 5
|
||||
|
||||
- name: Exit with Test Result
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ steps.e2e_test.outcome }}" != "success" ]; then
|
||||
exit 1
|
||||
fi
|
||||
36
.github/workflows/jazz-run.yml
vendored
36
.github/workflows/jazz-run.yml
vendored
@@ -8,43 +8,16 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
timeout-minutes: 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Enable latestcorepack
|
||||
run: |
|
||||
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
|
||||
npm install -g corepack@latest
|
||||
echo "After : corepack version => $(corepack --version)"
|
||||
corepack enable
|
||||
pnpm --version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Build jazz-run
|
||||
run: pnpm exec turbo build && chmod +x dist/index.js;
|
||||
@@ -53,4 +26,3 @@ jobs:
|
||||
- name: Run create account
|
||||
run: ./dist/index.js account create --name "Jazz Run CI test"
|
||||
working-directory: ./packages/jazz-run
|
||||
|
||||
35
.github/workflows/playwright.yml
vendored
35
.github/workflows/playwright.yml
vendored
@@ -9,46 +9,19 @@ on:
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
project: ["tests/e2e", "examples/chat", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "examples/onboarding", "starters/react-demo-auth-tailwind"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Enable latestcorepack
|
||||
run: |
|
||||
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
|
||||
npm install -g corepack@latest
|
||||
echo "After : corepack version => $(corepack --version)"
|
||||
corepack enable
|
||||
pnpm --version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Pnpm Build
|
||||
run: pnpm turbo build
|
||||
|
||||
31
.github/workflows/pre-release.yml
vendored
31
.github/workflows/pre-release.yml
vendored
@@ -12,35 +12,8 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable latestcorepack
|
||||
run: |
|
||||
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
|
||||
npm install -g corepack@latest
|
||||
echo "After : corepack version => $(corepack --version)"
|
||||
corepack enable
|
||||
pnpm --version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Pnpm Build
|
||||
run: pnpm turbo build --filter="./packages/*"
|
||||
|
||||
35
.github/workflows/release.yml
vendored
35
.github/workflows/release.yml
vendored
@@ -17,40 +17,13 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable latestcorepack
|
||||
run: |
|
||||
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
|
||||
npm install -g corepack@latest
|
||||
echo "After : corepack version => $(corepack --version)"
|
||||
corepack enable
|
||||
pnpm --version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v3
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
|
||||
36
.github/workflows/unit-test.yml
vendored
36
.github/workflows/unit-test.yml
vendored
@@ -9,44 +9,20 @@ on:
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable latestcorepack
|
||||
run: |
|
||||
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
|
||||
npm install -g corepack@latest
|
||||
echo "After : corepack version => $(corepack --version)"
|
||||
corepack enable
|
||||
pnpm --version
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Source Code
|
||||
uses: ./.github/actions/source-code/
|
||||
|
||||
- name: Pnpm Build
|
||||
run: pnpm turbo build --filter="./packages/*"
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install
|
||||
|
||||
- name: Unit Tests
|
||||
run: pnpm test:ci
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,3 +19,5 @@ test-results
|
||||
.husky
|
||||
|
||||
.vscode/settings.json
|
||||
|
||||
.svelte-kit
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2024, Garden Computing, Inc.
|
||||
Copyright 2025, Garden Computing, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
@@ -17,4 +17,4 @@ For community and support, please join our [Discord](https://discord.gg/utDMjHYg
|
||||
- Community & support: [Discord](https://discord.gg/utDMjHYg42)
|
||||
- Updates: [X](https://x.com/jazz_tools) & [Email](https://garden.co/news)
|
||||
|
||||
Copyright 2024 — Garden Computing, Inc.
|
||||
Copyright 2025 — Garden Computing, Inc.
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# chat-rn-clerk
|
||||
|
||||
## 1.0.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.9.23
|
||||
- jazz-react-native-auth-clerk@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-react-native-media-images@0.9.23
|
||||
|
||||
## 1.0.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.63",
|
||||
"version": "1.0.64",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
@@ -72,7 +72,7 @@
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~52.0.2",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2"
|
||||
},
|
||||
"private": true
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 1.0.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.60",
|
||||
"version": "1.0.61",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
@@ -46,7 +46,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2"
|
||||
},
|
||||
"private": true
|
||||
|
||||
@@ -148,6 +148,7 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
}}
|
||||
textAlignVertical="center"
|
||||
onSubmitEditing={sendMessage}
|
||||
testID="username-input"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={createChat}
|
||||
@@ -190,10 +191,12 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
|
||||
placeholder="Type a message..."
|
||||
textAlignVertical="center"
|
||||
onSubmitEditing={sendMessage}
|
||||
testID="message-input"
|
||||
/>
|
||||
<TouchableOpacity
|
||||
onPress={sendMessage}
|
||||
className="bg-gray-300 text-white rounded-full h-8 w-8 items-center justify-center"
|
||||
testID="send-button"
|
||||
>
|
||||
<Text>↑</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
16
examples/chat-rn/test/e2e/erase_text.yml
Normal file
16
examples/chat-rn/test/e2e/erase_text.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
# this sub-flow exists to work around an ios issue where the text field is not
|
||||
# fully erased. The tap into the input field hits the middle, and clears all
|
||||
# text to the left. If there's more to the right, it slides left, and thus we
|
||||
# repeat this step. https://maestro.mobile.dev/api-reference/commands/erasetext
|
||||
appId: com.jazz.chatrn
|
||||
---
|
||||
- copyTextFrom:
|
||||
id: ${id}
|
||||
- repeat:
|
||||
times: 4
|
||||
commands:
|
||||
- tapOn:
|
||||
id: ${id}
|
||||
- eraseText
|
||||
- copyTextFrom:
|
||||
id: ${id}
|
||||
47
examples/chat-rn/test/e2e/flow.yml
Normal file
47
examples/chat-rn/test/e2e/flow.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
appId: com.jazz.chatrn
|
||||
---
|
||||
- launchApp
|
||||
|
||||
# # handle Expo screens (for local dev)
|
||||
# - assertVisible: "Continue"
|
||||
# - tapOn: "Continue"
|
||||
# - assertVisible: "Reload"
|
||||
# - tapOn: "Reload"
|
||||
|
||||
# login
|
||||
- assertVisible: "Anonymous user"
|
||||
- runFlow:
|
||||
label: "Erase existing username"
|
||||
file: erase_text.yml
|
||||
env:
|
||||
id: "username-input"
|
||||
- inputText: "boorad"
|
||||
- assertVisible: "boorad"
|
||||
|
||||
# start new chat
|
||||
- tapOn: "Start new chat"
|
||||
- assertVisible: "Share"
|
||||
- assertVisible: "Jazz Chat"
|
||||
- assertVisible: "Logout"
|
||||
|
||||
# send a message
|
||||
- runFlow:
|
||||
label: "Erase existing message"
|
||||
file: erase_text.yml
|
||||
env:
|
||||
id: "message-input"
|
||||
- inputText: "bro, low key, it do be like that tho"
|
||||
- tapOn:
|
||||
id: "send-button"
|
||||
- assertVisible: "bro, low key, it do be like that tho"
|
||||
|
||||
# get invite code
|
||||
- tapOn: "Share"
|
||||
- assertVisible: "Copied to clipboard"
|
||||
- tapOn: "OK"
|
||||
# this assert doesn't work. maestro.copiedText only populates from `copyTextFrom`
|
||||
# - assertTrue: ${maestro.copiedText.startsWith("co_z")}
|
||||
|
||||
# logout
|
||||
- tapOn: "Logout"
|
||||
- assertVisible: "Anonymous user"
|
||||
@@ -1,5 +1,13 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-vue@0.9.23
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.47",
|
||||
"version": "0.0.48",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -30,9 +30,9 @@
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10",
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-vue-devtools": "^7.4.6",
|
||||
"vue-tsc": "^2.1.6"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.144
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-browser-media-images@0.9.23
|
||||
|
||||
## 0.0.143
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.143",
|
||||
"version": "0.0.144",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -23,15 +23,15 @@
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"is-ci": "^3.0.1",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.43
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-react-auth-clerk@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 0.0.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.42",
|
||||
"version": "0.0.43",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -13,7 +13,7 @@
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-react-auth-clerk": "workspace:0.9.22",
|
||||
"jazz-react-auth-clerk": "workspace:0.9.23",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
@@ -25,6 +25,6 @@
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"globals": "^15.11.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 0.0.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.27",
|
||||
"version": "0.0.28",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -18,8 +18,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-vercel": "^5.5.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.1",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@types/is-ci": "^3.0.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.7.0",
|
||||
@@ -32,10 +32,10 @@
|
||||
"prettier-plugin-tailwindcss": "^0.6.5",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# form
|
||||
|
||||
## 0.0.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-browser-media-images@0.9.23
|
||||
|
||||
## 0.0.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.0.38",
|
||||
"version": "0.0.39",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
@@ -29,8 +29,8 @@
|
||||
"globals": "^15.11.0",
|
||||
"is-ci": "^3.0.1",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-browser-media-images@0.9.23
|
||||
|
||||
## 0.0.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.40",
|
||||
"version": "0.0.41",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -24,6 +24,6 @@
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"globals": "^15.11.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.102
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [70c9a5d]
|
||||
- cojson@0.9.23
|
||||
- cojson-transport-ws@0.9.23
|
||||
|
||||
## 0.0.101
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector-app",
|
||||
"private": true,
|
||||
"version": "0.0.101",
|
||||
"version": "0.0.102",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,12 +12,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.9.19",
|
||||
"cojson-transport-ws": "workspace:0.9.22",
|
||||
"cojson": "workspace:0.9.23",
|
||||
"cojson-transport-ws": "workspace:0.9.23",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
@@ -37,8 +37,8 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-inspector@0.9.23
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 0.0.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.64",
|
||||
"version": "0.0.65",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -21,9 +21,9 @@
|
||||
"@radix-ui/react-tooltip": "^1.1.6",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "workspace:0.9.22",
|
||||
"jazz-tools": "workspace:0.9.21",
|
||||
"jazz-inspector": "workspace:*",
|
||||
"jazz-react": "workspace:0.9.23",
|
||||
"jazz-tools": "workspace:0.9.23",
|
||||
"lucide-react": "^0.274.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -33,14 +33,14 @@
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import "./index.css";
|
||||
import { MusicaAccount } from "@/1_schema";
|
||||
import { apiKey } from "@/apiKey.ts";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import { onAnonymousUserDiscarded } from "./4_actions";
|
||||
import { onAnonymousAccountDiscarded } from "./4_actions";
|
||||
import { useUploadExampleData } from "./lib/useUploadExampleData";
|
||||
|
||||
/**
|
||||
@@ -77,7 +77,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
storage="indexedDB"
|
||||
AccountSchema={MusicaAccount}
|
||||
defaultProfileName="Anonymous unicorn"
|
||||
onAnonymousUserDiscarded={onAnonymousUserDiscarded}
|
||||
onAnonymousAccountDiscarded={onAnonymousAccountDiscarded}
|
||||
>
|
||||
<Main />
|
||||
<JazzInspector />
|
||||
|
||||
@@ -26,17 +26,14 @@ export async function uploadMusicTracks(
|
||||
files: Iterable<File>,
|
||||
isExampleTrack: boolean = false,
|
||||
) {
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [],
|
||||
},
|
||||
playlists: [],
|
||||
},
|
||||
});
|
||||
|
||||
if (!me) return;
|
||||
|
||||
for (const file of files) {
|
||||
// The ownership object defines the user that owns the created coValues
|
||||
// We are creating a group for each CoValue in order to be able to share them via Playlist
|
||||
@@ -62,19 +59,17 @@ export async function uploadMusicTracks(
|
||||
|
||||
// The newly created musicTrack can be associated to the
|
||||
// user track list using a simple push call
|
||||
me.root.rootPlaylist.tracks.push(musicTrack);
|
||||
root.rootPlaylist.tracks.push(musicTrack);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createNewPlaylist() {
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
playlists: [],
|
||||
},
|
||||
});
|
||||
|
||||
if (!me) throw new Error("Current playlist not resolved");
|
||||
|
||||
// Since playlists are meant to be shared we associate them
|
||||
// to a group which will contain the keys required to get
|
||||
// access to the "owned" values
|
||||
@@ -90,7 +85,7 @@ export async function createNewPlaylist() {
|
||||
|
||||
// Again, we associate the new playlist to the
|
||||
// user by pushing it into the playlists CoList
|
||||
me.root.playlists.push(playlist);
|
||||
root.playlists.push(playlist);
|
||||
|
||||
return playlist;
|
||||
}
|
||||
@@ -156,32 +151,28 @@ export async function updateMusicTrackTitle(track: MusicTrack, title: string) {
|
||||
}
|
||||
|
||||
export async function updateActivePlaylist(playlist?: Playlist) {
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
activePlaylist: {},
|
||||
rootPlaylist: {},
|
||||
},
|
||||
});
|
||||
|
||||
if (!me) return;
|
||||
|
||||
me.root.activePlaylist = playlist ?? me.root.rootPlaylist;
|
||||
root.activePlaylist = playlist ?? root.rootPlaylist;
|
||||
}
|
||||
|
||||
export async function updateActiveTrack(track: MusicTrack) {
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {},
|
||||
});
|
||||
|
||||
if (!me) return;
|
||||
|
||||
me.root.activeTrack = track;
|
||||
root.activeTrack = track;
|
||||
}
|
||||
|
||||
export async function onAnonymousUserDiscarded(
|
||||
export async function onAnonymousAccountDiscarded(
|
||||
anonymousAccount: MusicaAccount,
|
||||
) {
|
||||
const anonymousAccountWithPlaylist = await anonymousAccount.ensureLoaded({
|
||||
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [{}],
|
||||
@@ -189,9 +180,6 @@ export async function onAnonymousUserDiscarded(
|
||||
},
|
||||
});
|
||||
|
||||
if (!anonymousAccountWithPlaylist)
|
||||
throw new Error("Unable to load anonymous account with playlist");
|
||||
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
@@ -200,15 +188,11 @@ export async function onAnonymousUserDiscarded(
|
||||
},
|
||||
});
|
||||
|
||||
if (!me) throw new Error("Me not resolved");
|
||||
|
||||
const rootPlaylist = anonymousAccountWithPlaylist.root.rootPlaylist;
|
||||
|
||||
for (const track of rootPlaylist.tracks) {
|
||||
for (const track of anonymousAccountRoot.rootPlaylist.tracks) {
|
||||
if (track.isExampleTrack) continue;
|
||||
|
||||
const trackGroup = track._owner.castAs(Group);
|
||||
await trackGroup.addMember(me, "admin");
|
||||
trackGroup.addMember(me, "admin");
|
||||
|
||||
me.root.rootPlaylist.tracks.push(track);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ export function InvitePage() {
|
||||
},
|
||||
});
|
||||
|
||||
if (!me) return;
|
||||
|
||||
if (
|
||||
playlist &&
|
||||
!me.root.playlists.some((item) => playlist.id === item?.id)
|
||||
|
||||
@@ -9,8 +9,6 @@ export async function getNextTrack() {
|
||||
},
|
||||
});
|
||||
|
||||
if (!me) return;
|
||||
|
||||
const tracks = me.root.activePlaylist.tracks;
|
||||
const activeTrack = me.root._refs.activeTrack;
|
||||
|
||||
@@ -30,8 +28,6 @@ export async function getPrevTrack() {
|
||||
},
|
||||
});
|
||||
|
||||
if (!me) return;
|
||||
|
||||
const tracks = me.root.activePlaylist.tracks;
|
||||
const activeTrack = me.root._refs.activeTrack;
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ async function uploadOnboardingData() {
|
||||
root: {},
|
||||
});
|
||||
|
||||
if (!me) throw new Error("Me not resolved");
|
||||
|
||||
if (me.root.exampleDataLoaded) return;
|
||||
|
||||
me.root.exampleDataLoaded = true;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-example-onboarding
|
||||
|
||||
## 0.0.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-browser-media-images@0.9.23
|
||||
|
||||
## 0.0.44
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-onboarding",
|
||||
"private": true,
|
||||
"version": "0.0.44",
|
||||
"version": "0.0.45",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -21,7 +21,7 @@
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
@@ -29,8 +29,8 @@
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-run": "workspace:*",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# organization
|
||||
|
||||
## 0.0.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 0.0.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.36",
|
||||
"version": "0.0.37",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -28,8 +28,8 @@
|
||||
"autoprefixer": "^10.4.20",
|
||||
"globals": "^15.11.0",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.9.23
|
||||
|
||||
## 0.0.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.31",
|
||||
"version": "0.0.32",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -16,8 +16,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.1",
|
||||
"@sveltejs/kit": "^2.16.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@types/eslint": "^9.6.0",
|
||||
"eslint": "^9.7.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
@@ -29,7 +29,7 @@
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-svelte": "workspace:*"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 0.0.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.41",
|
||||
"version": "0.0.42",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -23,6 +23,6 @@
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"globals": "^15.11.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"globals": "^15.11.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.63
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 0.0.62
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.62",
|
||||
"version": "0.0.63",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,8 +12,8 @@
|
||||
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-react": "workspace:0.9.22",
|
||||
"jazz-tools": "workspace:0.9.21",
|
||||
"jazz-react": "workspace:0.9.23",
|
||||
"jazz-tools": "workspace:0.9.23",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-hook-form": "^7.41.5",
|
||||
@@ -26,8 +26,8 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.161
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-browser-media-images@0.9.23
|
||||
|
||||
## 0.0.160
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.160",
|
||||
"version": "0.0.161",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -15,13 +15,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.9.22",
|
||||
"jazz-react": "workspace:0.9.22",
|
||||
"jazz-tools": "workspace:0.9.21",
|
||||
"jazz-browser-media-images": "workspace:0.9.23",
|
||||
"jazz-react": "workspace:0.9.23",
|
||||
"jazz-tools": "workspace:0.9.23",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -33,7 +33,7 @@
|
||||
"uniqolor": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@types/node": "^22.5.1",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"@types/react": "^18.3.12",
|
||||
@@ -41,11 +41,11 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-run": "workspace:0.9.22",
|
||||
"jazz-run": "workspace:0.9.23",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10",
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-top-level-await": "^1.4.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-browser-media-images@0.9.23
|
||||
|
||||
## 0.0.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.40",
|
||||
"version": "0.0.41",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -25,6 +25,6 @@
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"globals": "^15.11.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
- jazz-vue@0.9.23
|
||||
|
||||
## 0.0.45
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.45",
|
||||
"version": "0.0.46",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -30,9 +30,9 @@
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10",
|
||||
"vite": "^6.0.11",
|
||||
"vite-plugin-vue-devtools": "^7.4.6",
|
||||
"vue-tsc": "^2.1.6"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.160
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 0.0.159
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.159",
|
||||
"version": "0.0.160",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,12 +12,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "workspace:0.9.22",
|
||||
"jazz-tools": "workspace:0.9.21",
|
||||
"jazz-react": "workspace:0.9.23",
|
||||
"jazz-tools": "workspace:0.9.23",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.3.1",
|
||||
@@ -35,8 +35,8 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.9.23
|
||||
- jazz-tools@0.9.23
|
||||
|
||||
## 0.0.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.37",
|
||||
"version": "0.0.38",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -25,6 +25,6 @@
|
||||
"@vitejs/plugin-react": "^4.3.3",
|
||||
"globals": "^15.11.0",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^5.4.10"
|
||||
"vite": "^6.0.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export function Copyright({
|
||||
className,
|
||||
companyName = "Garden Computing, Inc.",
|
||||
}: {
|
||||
companyName?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<p className={className}>
|
||||
© {new Date().getFullYear()} {companyName}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
MousePointerSquareDashedIcon,
|
||||
PencilLineIcon,
|
||||
ScanFace,
|
||||
ScrollIcon,
|
||||
SunIcon,
|
||||
TrashIcon,
|
||||
UploadCloudIcon,
|
||||
@@ -67,6 +68,7 @@ const icons = {
|
||||
permissions: FileLock2Icon,
|
||||
social: UsersIcon,
|
||||
spatialPresence: MousePointerSquareDashedIcon,
|
||||
tableOfContents: ScrollIcon,
|
||||
touchId: FingerprintIcon,
|
||||
upload: UploadCloudIcon,
|
||||
write: PencilLineIcon,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Icon } from "../atoms/Icon";
|
||||
|
||||
// TODO: add tabs feature, and remove CodeExampleTabs
|
||||
|
||||
function CopyButton({ code, size }: { code: string; size?: "sm" | "md" }) {
|
||||
function CopyButton({ code, size }: { code: string; size: "md" | "lg" }) {
|
||||
let [copyCount, setCopyCount] = useState(0);
|
||||
let copied = copyCount > 0;
|
||||
|
||||
@@ -27,8 +27,8 @@ function CopyButton({ code, size }: { code: string; size?: "sm" | "md" }) {
|
||||
copied
|
||||
? "bg-emerald-400/10 ring-1 ring-inset ring-emerald-400/20"
|
||||
: "bg-white/5 hover:bg-white/7.5 dark:bg-white/2.5 dark:hover:bg-white/5",
|
||||
size == "sm"
|
||||
? "right-1.5 top-1.5 py-[2px] pl-1 pr-2"
|
||||
size == "md"
|
||||
? "right-[8px] top-[8px] py-[2px] pl-1 pr-2"
|
||||
: "right-2 top-2 py-1 pl-2 pr-3 ",
|
||||
)}
|
||||
onClick={() => {
|
||||
@@ -48,7 +48,7 @@ function CopyButton({ code, size }: { code: string; size?: "sm" | "md" }) {
|
||||
name="copy"
|
||||
size="xs"
|
||||
className={clsx(
|
||||
size === "sm" ? "size-2" : "size-3",
|
||||
size === "md" ? "size-3" : "size-4",
|
||||
"stroke-stone-500 transition-colors group-hover/button:stroke-stone-600 dark:group-hover/button:stroke-stone-400",
|
||||
)}
|
||||
/>
|
||||
@@ -69,11 +69,11 @@ function CopyButton({ code, size }: { code: string; size?: "sm" | "md" }) {
|
||||
|
||||
export function CodeGroup({
|
||||
children,
|
||||
size,
|
||||
size = "md",
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
size?: "sm" | "md";
|
||||
size?: "md" | "lg";
|
||||
className?: string;
|
||||
}) {
|
||||
const textRef = useRef<HTMLPreElement | null>(null);
|
||||
@@ -86,13 +86,14 @@ export function CodeGroup({
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<div className={clsx(className, "group relative")}>
|
||||
<div className={clsx(className, "not-prose group relative")}>
|
||||
<pre
|
||||
className={clsx(
|
||||
"h-full border p-0 bg-stone-50 dark:bg-stone-925",
|
||||
"h-full overflow-x-auto",
|
||||
"border rounded-md p-0 bg-stone-50 dark:bg-stone-925",
|
||||
"text-black dark:text-white",
|
||||
{
|
||||
"text-sm": size === "sm",
|
||||
"text-sm": size === "md",
|
||||
},
|
||||
)}
|
||||
ref={textRef}
|
||||
|
||||
@@ -4,6 +4,7 @@ import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { ComponentType, ReactNode } from "react";
|
||||
import { Copyright } from "../atoms/Copyright";
|
||||
import { NewsletterForm } from "./NewsletterForm";
|
||||
import { SocialLinks, SocialLinksProps } from "./SocialLinks";
|
||||
|
||||
@@ -18,35 +19,19 @@ type FooterSection = {
|
||||
|
||||
type FooterProps = {
|
||||
logo: ReactNode;
|
||||
companyName: string;
|
||||
sections: FooterSection[];
|
||||
socials: SocialLinksProps;
|
||||
themeToggle: ComponentType<{ className?: string }>;
|
||||
};
|
||||
|
||||
function Copyright({
|
||||
className,
|
||||
companyName,
|
||||
}: {
|
||||
companyName: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<p className={clsx(className, "text-sm")}>
|
||||
© {new Date().getFullYear()} {companyName}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export function Footer({
|
||||
logo,
|
||||
companyName,
|
||||
sections,
|
||||
socials,
|
||||
themeToggle: ThemeToggle,
|
||||
}: FooterProps) {
|
||||
return (
|
||||
<footer className="w-full py-8 mt-12 md:mt-20">
|
||||
<footer className="w-full pt-8 pb-20 mt-12 md:mt-20 md:pb-8">
|
||||
<div className="container grid gap-8 md:gap-12">
|
||||
<div className="grid grid-cols-12 gap-y-3 sm:items-center pb-8 border-b">
|
||||
<div className="col-span-full sm:col-span-6 md:col-span-8">
|
||||
@@ -82,10 +67,7 @@ export function Footer({
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Copyright
|
||||
className="order-last col-span-full self-center md:col-span-10 md:order-none"
|
||||
companyName={companyName}
|
||||
/>
|
||||
<Copyright className="text-sm order-last col-span-full self-center md:col-span-10 md:order-none" />
|
||||
|
||||
<div className="col-span-full flex items-center justify-between gap-6 md:col-span-2">
|
||||
<SocialLinks {...socials}></SocialLinks>
|
||||
|
||||
@@ -10,14 +10,7 @@ import {
|
||||
import clsx from "clsx";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import {
|
||||
ComponentType,
|
||||
ReactNode,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { ComponentType, ReactNode, useEffect, useState } from "react";
|
||||
import { Icon } from "../atoms/Icon";
|
||||
import { BreadCrumb } from "../molecules/Breadcrumb";
|
||||
import { SocialLinks, SocialLinksProps } from "./SocialLinks";
|
||||
@@ -35,10 +28,16 @@ type NavItemProps = {
|
||||
type NavProps = {
|
||||
mainLogo: ReactNode;
|
||||
items: NavItemProps[];
|
||||
docNav?: ReactNode;
|
||||
cta?: ReactNode;
|
||||
socials?: SocialLinksProps;
|
||||
themeToggle: ComponentType<{ className?: string }>;
|
||||
sections?: NavSection[];
|
||||
};
|
||||
|
||||
export type NavSection = {
|
||||
name: string;
|
||||
content: ReactNode;
|
||||
icon: string;
|
||||
};
|
||||
|
||||
function NavItem({
|
||||
@@ -68,7 +67,7 @@ function NavItem({
|
||||
className,
|
||||
"text-sm px-2 lg:px-4 py-3 ",
|
||||
firstOnRight && "ml-auto",
|
||||
path === href ? "text-black dark:text-white" : "",
|
||||
path === href ? "text-stone-900 dark:text-white" : "",
|
||||
)}
|
||||
{...item}
|
||||
>
|
||||
@@ -81,8 +80,8 @@ function NavItem({
|
||||
<Popover className={clsx("relative", className, firstOnRight && "ml-auto")}>
|
||||
<PopoverButton
|
||||
className={clsx(
|
||||
"flex items-center gap-1.5 text-sm px-2 lg:px-4 py-3 max-sm:w-full text-stone-600 dark:text-stone-400 hover:text-black dark:hover:text-white transition-colors hover:transition-none focus-visible:outline-none",
|
||||
path === href ? "text-black dark:text-white" : "",
|
||||
"flex items-center gap-1.5 text-sm px-2 lg:px-4 py-3 max-sm:w-full hover:text-stone-900 dark:hover:text-white transition-colors hover:transition-none focus-visible:outline-none",
|
||||
path === href ? "text-stone-900 dark:text-white" : "",
|
||||
)}
|
||||
>
|
||||
<span>{title}</span>
|
||||
@@ -128,25 +127,57 @@ function NavItem({
|
||||
export function MobileNav({
|
||||
mainLogo,
|
||||
items,
|
||||
docNav,
|
||||
cta,
|
||||
socials,
|
||||
sections,
|
||||
themeToggle: ThemeToggle,
|
||||
}: NavProps) {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
const primarySection = {
|
||||
name: "Menu",
|
||||
icon: "menu",
|
||||
content: (
|
||||
<>
|
||||
<div className="flex items-center justify-between border-b p-3">
|
||||
<Link href="/" className="flex items-center">
|
||||
{mainLogo}
|
||||
</Link>
|
||||
|
||||
useLayoutEffect(() => {
|
||||
searchOpen && searchRef.current?.focus();
|
||||
}, [searchOpen]);
|
||||
<SocialLinks {...socials} className="ml-auto" />
|
||||
|
||||
<ThemeToggle className="ml-4 mr-1" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-3">
|
||||
{items
|
||||
.filter((item) => !("icon" in item))
|
||||
.map((item, i) => (
|
||||
<NavLink
|
||||
className="p-1"
|
||||
key={i}
|
||||
href={item.href}
|
||||
onClick={() => setActive(null)}
|
||||
newTab={item.newTab}
|
||||
>
|
||||
{item.title}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
};
|
||||
|
||||
const [active, setActive] = useState<string | null>();
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
setMenuOpen(false);
|
||||
setActive(null);
|
||||
}, [pathname]);
|
||||
|
||||
const toggle = (type: string) => {
|
||||
setActive(active == type ? null : type);
|
||||
};
|
||||
|
||||
const navSections = [primarySection, ...(sections || [])];
|
||||
return (
|
||||
<>
|
||||
<div className="md:hidden px-4 flex items-center self-stretch dark:text-white">
|
||||
@@ -155,9 +186,8 @@ export function MobileNav({
|
||||
</NavLinkLogo>
|
||||
<button
|
||||
className="flex gap-2 p-3 rounded-xl items-center"
|
||||
onMouseDown={() => {
|
||||
setMenuOpen((o) => !o);
|
||||
setSearchOpen(false);
|
||||
onClick={() => {
|
||||
setActive("Menu");
|
||||
}}
|
||||
aria-label="Open menu"
|
||||
>
|
||||
@@ -165,80 +195,70 @@ export function MobileNav({
|
||||
<BreadCrumb items={items} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => {
|
||||
setMenuOpen(false);
|
||||
setSearchOpen(false);
|
||||
setActive(null);
|
||||
}}
|
||||
className={clsx(
|
||||
menuOpen || searchOpen ? "block" : "hidden",
|
||||
"fixed top-0 bottom-0 left-0 right-0 bg-stone-200/80 dark:bg-black/80 w-full h-full z-20",
|
||||
!!active ? "block" : "hidden",
|
||||
"md:hidden fixed backdrop-blur-sm top-0 bottom-0 left-0 right-0 bg-stone-200/80 dark:bg-black/80 w-full h-full z-20",
|
||||
)}
|
||||
></div>
|
||||
<nav
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
"md:hidden fixed flex flex-col bottom-4 right-4 z-50",
|
||||
"bg-stone-50 dark:bg-stone-925 border rounded-lg shadow-lg",
|
||||
menuOpen || searchOpen ? "left-4" : "",
|
||||
"md:hidden bg-white border fixed z-50",
|
||||
"dark:bg-stone-925",
|
||||
|
||||
{
|
||||
"rounded-lg right-6 left-6 bottom-6 sm:max-w-lg sm:w-full shadow-md sm:left-1/2 sm:-translate-x-1/2 ":
|
||||
!!active,
|
||||
"rounded-full shadow-sm left-1/2 -translate-x-1/2 bottom-7":
|
||||
!active,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className={clsx(menuOpen ? "block" : "hidden", " px-2 pb-2")}>
|
||||
<div className="flex items-center w-full border-b">
|
||||
<NavLinkLogo
|
||||
prominent
|
||||
href="/"
|
||||
className="mr-auto"
|
||||
onClick={() => setMenuOpen(false)}
|
||||
>
|
||||
{mainLogo}
|
||||
</NavLinkLogo>
|
||||
|
||||
<SocialLinks className="px-2 gap-2" {...socials} />
|
||||
</div>
|
||||
|
||||
{pathname.startsWith("/docs") && docNav && (
|
||||
<div className="max-h-[calc(100dvh-15rem)] p-4 border-b overflow-x-auto">
|
||||
{docNav}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap justify-end py-2 gap-x-3 gap-y-1 border-b">
|
||||
{[{ title: "Home", href: "/" }, ...items]
|
||||
.filter((item) => !("icon" in item))
|
||||
.map((item, i) => (
|
||||
<NavLink
|
||||
className="p-1 text-sm"
|
||||
key={i}
|
||||
href={item.href}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
newTab={item.newTab}
|
||||
>
|
||||
{item.title}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center self-stretch justify-between">
|
||||
{(menuOpen || searchOpen) && <ThemeToggle className="p-3" />}
|
||||
<button
|
||||
className="flex gap-2 p-3 rounded-xl items-center"
|
||||
onMouseDown={() => {
|
||||
setMenuOpen((o) => !o);
|
||||
setSearchOpen(false);
|
||||
}}
|
||||
aria-label="Close menu"
|
||||
>
|
||||
{menuOpen || searchOpen ? (
|
||||
<Icon name="close" />
|
||||
) : (
|
||||
<>
|
||||
<Icon name="menu" />
|
||||
<BreadCrumb items={items} />
|
||||
</>
|
||||
{active && (
|
||||
<div
|
||||
className={clsx(
|
||||
"max-h-[calc(100vh-16rem)] overflow-y-auto",
|
||||
active === "Menu" ? "" : "p-3 pb-10",
|
||||
)}
|
||||
</button>
|
||||
>
|
||||
{navSections.map((section) =>
|
||||
section.name == active ? section.content : null,
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={clsx("flex justify-center py-1 px-1.5", {
|
||||
"border-t py-2": !!active,
|
||||
})}
|
||||
>
|
||||
{navSections.map(
|
||||
(section) =>
|
||||
section.content && (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(
|
||||
"flex items-center gap-1 px-2 py-1 text-sm rounded-md whitespace-nowrap",
|
||||
"text-stone-900 dark:text-white",
|
||||
{
|
||||
"bg-stone-100 dark:bg-stone-900": active === section.name,
|
||||
},
|
||||
)}
|
||||
onClick={() => toggle(section.name)}
|
||||
key={section.name}
|
||||
>
|
||||
<Icon name={section.icon} size="xs" />
|
||||
{section.name}
|
||||
</button>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -262,7 +282,7 @@ function NavLink({
|
||||
<Link
|
||||
href={href}
|
||||
className={clsx(
|
||||
"text-stone-600 dark:text-stone-400 hover:text-black dark:hover:text-white transition-colors hover:transition-none",
|
||||
"hover:text-stone-900 dark:hover:text-white transition-colors hover:transition-none",
|
||||
className,
|
||||
)}
|
||||
onClick={onClick}
|
||||
@@ -310,7 +330,7 @@ function NavLinkLogo({
|
||||
}
|
||||
|
||||
export function Nav(props: NavProps) {
|
||||
const { mainLogo, items, docNav, cta } = props;
|
||||
const { mainLogo, items, cta } = props;
|
||||
return (
|
||||
<>
|
||||
<div className="w-full border-b py-2 sticky top-0 z-50 bg-white dark:bg-stone-950 hidden md:block">
|
||||
|
||||
@@ -42,7 +42,12 @@ const socials = [
|
||||
|
||||
export function SocialLinks(props: SocialLinksProps & { className?: string }) {
|
||||
return (
|
||||
<div className={clsx(props.className, "inline-flex gap-6 items-center")}>
|
||||
<div
|
||||
className={clsx(
|
||||
props.className,
|
||||
"inline-flex gap-3 items-center sm:gap-6",
|
||||
)}
|
||||
>
|
||||
{socials.map(
|
||||
(social) =>
|
||||
props[social.key as keyof SocialLinksProps] && (
|
||||
|
||||
@@ -9,6 +9,7 @@ import { GcmpNav } from "@/components/Nav";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { Copyright } from "gcmp-design-system/src/app/components/atoms/Copyright";
|
||||
|
||||
// If loading a variable font, you don't need to specify the font weight
|
||||
const manrope = Manrope({
|
||||
@@ -98,7 +99,7 @@ export default function RootLayout({
|
||||
<GcmpNav />
|
||||
<main className="flex-1 w-full">{children}</main>
|
||||
<footer className="py-8 text-sm flex justify-between gap-3 w-full container mt-12 md:mt-20">
|
||||
<p>©2024 Garden Computing, Inc.</p>
|
||||
<Copyright />
|
||||
|
||||
<ThemeToggle className="hidden md:block" />
|
||||
</footer>
|
||||
|
||||
15
homepage/homepage/app/(docs)/api-reference/layout.tsx
Normal file
15
homepage/homepage/app/(docs)/api-reference/layout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ApiNav } from "@/components/docs/ApiNav";
|
||||
import DocsLayout from "@/components/docs/DocsLayout";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<DocsLayout nav={<ApiNav />} navIcon="package" navName="API Ref">
|
||||
<Prose className="overflow-x-hidden lg:flex-1 py-8">{children}</Prose>
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,6 @@
|
||||
import DocsLayout from "@/components/docs/DocsLayout";
|
||||
import { DocNav } from "@/components/docs/nav";
|
||||
|
||||
export const metadata = {
|
||||
title: {
|
||||
default: "Documentation",
|
||||
template: "%s | Jazz",
|
||||
},
|
||||
description: "Jazz guide and documentation.",
|
||||
};
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
@@ -16,7 +9,7 @@ export default function Layout({
|
||||
}) {
|
||||
return (
|
||||
<DocsLayout nav={<DocNav />}>
|
||||
<div className="flex justify-center lg:gap-5">{children}</div>
|
||||
<Prose className="max-w-3xl mx-auto lg:flex-1 py-8">{children}</Prose>
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import MdxSource from "@/components/docs/docs-intro.mdx";
|
||||
import { frameworks } from "@/lib/framework";
|
||||
import { JazzLogo } from "gcmp-design-system/src/app/components/atoms/logos/JazzLogo";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import MdxSource from "../../../../../components/docs/docs-intro.mdx";
|
||||
import { frameworks } from "../../../../../lib/framework";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
@@ -0,0 +1,406 @@
|
||||
export const metadata = { title: "Authentication methods" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Authentication in Jazz
|
||||
|
||||
Jazz authentication is based on cryptographic keys ("Account keys"). Their public part represents a user's identity, their secret part lets you act as that user.
|
||||
|
||||
When a user loads a Jazz application for the first time, we create a new Account by generating keys and storing them locally.
|
||||
|
||||
Without any additional steps the user can use Jazz normally, but they would be limited to use on only one device.
|
||||
|
||||
To make Accounts work across devices, you can store/retrieve the account keys from an authentication method by using the corresponding hooks and providers.
|
||||
|
||||
<ContentByFramework framework={["react", "vue", "svelte"]}>
|
||||
## Authentication with passkeys
|
||||
|
||||
Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides (because keys are managed by the device/operating system itself).
|
||||
|
||||
It is based on the [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) and is both very easy to use (using familiar FaceID/TouchID flows) and widely supported.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Using passkeys in Jazz is as easy as this:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
|
||||
const auth = usePasskeyAuth({ // Must be inside the JazzProvider!
|
||||
appName: "My super-cool web app",
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp(username);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
<button onClick={handleSignUp}>Sign up</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
|
||||
You can try our passkey authentication using our [passkey example](https://passkey-demo.jazz.tools/) or the [music player demo](https://music-demo.jazz.tools/).
|
||||
|
||||
</ContentByFramework>
|
||||
|
||||
## Passphrase-based authentication
|
||||
|
||||
Passphrase authentication lets users log into any device using a Bitcoin-style passphrase. This means users are themselves responsible for storing the passphrase safely.
|
||||
|
||||
The passphrase is generated from the local account certificate using a wordlist of your choice.
|
||||
|
||||
You can find a set of ready-to-use wordlists in the [bip39](https://github.com/bitcoinjs/bip39/tree/a7ecbfe2e60d0214ce17163d610cad9f7b23140c/src/wordlists) repository.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
For example:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { englishWordlist } from "./wordlist"
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({ // Must be inside the JazzProvider!
|
||||
wordlist: englishWordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>
|
||||
Your current passphrase
|
||||
<textarea
|
||||
readOnly
|
||||
value={auth.passphrase}
|
||||
rows={5}
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleSignUp}>I have stored my passphrase</button>
|
||||
<label>
|
||||
Log in with your passphrase
|
||||
<textarea
|
||||
value={loginPassphrase}
|
||||
onChange={(e) => setLoginPassphrase(e.target.value)}
|
||||
placeholder="Enter your passphrase"
|
||||
rows={5}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
For example:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { View, TextInput, Button, Text } from 'react-native';
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({
|
||||
wordlist: englishWordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") {
|
||||
return <Text>You are already signed in</Text>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Text>Your current passphrase</Text>
|
||||
<TextInput
|
||||
editable={false}
|
||||
value={auth.passphrase}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="I have stored my passphrase"
|
||||
onPress={handleSignUp}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<Text>Log in with your passphrase</Text>
|
||||
<TextInput
|
||||
value={loginPassphrase}
|
||||
onChangeText={setLoginPassphrase}
|
||||
placeholder="Enter your passphrase"
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
required
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="Log in"
|
||||
onPress={handleLogIn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
You can try our passphrase authentication using our [passphrase example](https://passphrase-demo.jazz.tools/) or the [todo list demo](https://todo-demo.jazz.tools/).
|
||||
|
||||
<ContentByFramework framework={["react", "react-native"]}>
|
||||
## Integration with Clerk
|
||||
|
||||
Jazz can be used with [Clerk](https://clerk.com/) to authenticate users.
|
||||
|
||||
This authentication method is not fully local-first, because the login and signup need to be done while online. Clerk and anyone who is an admin in the app's Clerk org are trusted with the user's key secret and could impersonate them.
|
||||
|
||||
However, once authenticated, your users won't need to interact with Clerk anymore, and are able to use all of Jazz's features without needing to be online.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
The clerk provider is not built into `jazz-react` and needs the `jazz-react-auth-clerk` package to be installed.
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
The clerk provider is not built into `jazz-react-native` and needs the `jazz-react-native-auth-clerk` package to be installed.
|
||||
</ContentByFramework>
|
||||
|
||||
After installing the package you can use the `JazzProviderWithClerk` component to wrap your app:
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import { JazzProviderWithClerk } from "jazz-react-auth-clerk";
|
||||
|
||||
function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
);
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
<ContentByFramework framework="react-native">
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import { JazzProviderWithClerk } from "jazz-react-native-auth-clerk";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Then you can use the [Clerk auth methods](https://clerk.com/docs/references/react/overview) to log in and sign up:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { SignInButton } from "@clerk/clerk-react";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <button onClick={() => logOut()}>Logout</button>;
|
||||
}
|
||||
|
||||
return <SignInButton />;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
Then you can use the [Clerk auth methods](https://clerk.com/docs/references/expo/overview) to log in and sign up:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { SignInButton } from "@clerk/clerk-expo";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react-native";
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <button onClick={() => logOut()}>Logout</button>;
|
||||
}
|
||||
|
||||
// Login code with Clerk Expo
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
</ContentByFramework>
|
||||
|
||||
## Migrating data from anonymous to authenticated account
|
||||
|
||||
You may want allow your users to use your app without authenticating (a poll response for example). When *signing up* their anonymous account is transparently upgraded using the provided auth method, keeping the data stored in the account intact.
|
||||
|
||||
However, a user may realise that they already have an existing account *after using the app anonymously and having already stored data in the anonymous account*.
|
||||
|
||||
When they now *log in*, by default the anonymous account will be discarded and this could lead to unexpected data loss.
|
||||
|
||||
To avoid this situation we provide the `onAnonymousAccountDiscarded` handler to migrate the data from the anonymous account to the existing authenticated one.
|
||||
|
||||
This is an example from our [music player](https://github.com/garden-co/jazz/tree/main/examples/music-player):
|
||||
<CodeGroup>
|
||||
```ts
|
||||
export async function onAnonymousAccountDiscarded(
|
||||
anonymousAccount: MusicaAccount,
|
||||
) {
|
||||
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [{}],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const track of anonymousAccountRoot.rootPlaylist.tracks) {
|
||||
if (track.isExampleTrack) continue;
|
||||
|
||||
const trackGroup = track._owner.castAs(Group);
|
||||
trackGroup.addMember(me, "admin");
|
||||
|
||||
me.root.rootPlaylist.tracks.push(track);
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To see how this works in reality we suggest you to try
|
||||
to upload a song in the [music player demo](https://music-demo.jazz.tools/) and then
|
||||
try to log in with an existing account.
|
||||
|
||||
## Disable network sync for anonymous users
|
||||
|
||||
You can disable network sync to make your app local-only under specific circumstances.
|
||||
|
||||
For example, you may want to give the opportunity to non-authenticated users to try your app locally-only (incurring no sync traffic), then enable the network sync only when the user is authenticated:
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// This makes the app work in local mode when the user is anonymous
|
||||
when: "signedUp",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
For more complex behaviours, you can manually control sync by statefully switching when between `"always"` and `"never"`.
|
||||
@@ -246,20 +246,14 @@ Let's not forget to update the `AccountSchema`.
|
||||
import { JazzProvider } from "jazz-react"; // old
|
||||
import { JazzAccount } from "./schema";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) { // old
|
||||
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName }); // old
|
||||
|
||||
export function MyJazzProvider({ children }: { children: React.ReactNode }) { // old
|
||||
return ( // old
|
||||
<> // old
|
||||
<JazzProvider // old
|
||||
auth={passkeyAuth} // old
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com" // old
|
||||
AccountSchema={JazzAccount}
|
||||
>// old
|
||||
{children} // old
|
||||
</JazzProvider> // old
|
||||
<PasskeyAuthBasicUI state={passKeyState} /> // old
|
||||
</> // old
|
||||
<JazzProvider // old
|
||||
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
|
||||
AccountSchema={JazzAccount}
|
||||
>// old
|
||||
{children} // old
|
||||
</JazzProvider> // old
|
||||
); // old
|
||||
} // old
|
||||
|
||||
@@ -104,8 +104,6 @@ const onAccept = async (organizationId: ID<Organization>) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!me) throw new Error("Failed to load account data");
|
||||
|
||||
const organization = await Organization.load(organizationId, []);
|
||||
|
||||
if (!organization) throw new Error("Failed to load organization data");
|
||||
@@ -0,0 +1,163 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
|
||||
export const metadata = { title: "Group inheritance" };
|
||||
|
||||
# Group Inheritance
|
||||
|
||||
Groups can inherit members from other groups using the `extend` method.
|
||||
|
||||
When a group extends another group, members of the parent group will become automatically part of the child group.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
Here's how to extend a group:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const playlistGroup = Group.create();
|
||||
const trackGroup = Group.create();
|
||||
|
||||
// This way track becomes visible to the members of playlist
|
||||
trackGroup.extend(playlistGroup);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
When you extend a group:
|
||||
- Members of the parent group get access to the child group
|
||||
- Their roles are inherited (with some exceptions, see [below](#role-inheritance-rules))
|
||||
- Removing a member from the parent group also removes their access to child groups
|
||||
|
||||
## Inheriting members but overriding their role
|
||||
|
||||
In some cases you might want to inherit all members from a parent group but override/flatten their roles to the same specific role in the child group. You can do so by passing an "override role" as a second argument to `extend`:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const organizationGroup = Group.create();
|
||||
organizationGroup.addMember(bob, "admin");
|
||||
|
||||
const billingGroup = Group.create();
|
||||
|
||||
// This way the members of the organization can only read the billing data
|
||||
billingGroup.extend(organizationGroup, "reader");
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
The "override role" works in both directions:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const parentGroup = Group.create();
|
||||
parentGroup.addMember(bob, "reader");
|
||||
parentGroup.addMember(alice, "admin");
|
||||
|
||||
const childGroup = Group.create();
|
||||
childGroup.extend(parentGroup, "writer");
|
||||
|
||||
// Bob and Alice are now writers in the child group
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Multiple Levels of Inheritance
|
||||
|
||||
Groups can be extended multiple levels deep:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const grandParentGroup = Group.create();
|
||||
const parentGroup = Group.create();
|
||||
const childGroup = Group.create();
|
||||
|
||||
childGroup.extend(parentGroup);
|
||||
parentGroup.extend(grandParentGroup);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Members of the grandparent group will get access to all descendant groups based on their roles.
|
||||
|
||||
## Permission Changes
|
||||
|
||||
When you remove a member from a parent group, they automatically lose access to all child groups. We handle key rotation automatically to ensure security.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
// Remove member from parent
|
||||
await parentGroup.removeMember(bob);
|
||||
|
||||
// Bob loses access to both parent and child groups
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Role Inheritance Rules
|
||||
|
||||
If the account is already a member of the child group, it will get the more permissive role:
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const parentGroup = Group.create();
|
||||
parentGroup.addMember(bob, "reader");
|
||||
|
||||
const childGroup = Group.create();
|
||||
parentGroup.addMember(bob, "writer");
|
||||
childGroup.extend(parentGroup);
|
||||
|
||||
// Bob stays a writer because his role is higher
|
||||
// than the inherited reader role.
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
When extending groups, only admin, writer and reader roles are inherited:
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const parentGroup = Group.create();
|
||||
parentGroup.addMember(bob, "writeOnly");
|
||||
|
||||
const childGroup = Group.create();
|
||||
childGroup.extend(parentGroup);
|
||||
|
||||
// Bob does not become a member of the child group
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To extend a group:
|
||||
|
||||
1. The current account must be an admin in the child group
|
||||
2. The current account must be a member of the parent group
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
const companyGroup = company._owner.castAs(Group)
|
||||
const teamGroup = Group.create();
|
||||
|
||||
// Works only if I'm a member of companyGroup
|
||||
teamGroup.extend(companyGroup);
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Example: Team Hierarchy
|
||||
|
||||
Here's a practical example of using group inheritance for team permissions:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
// Company-wide group
|
||||
const companyGroup = Group.create();
|
||||
companyGroup.addMember(CEO, "admin");
|
||||
|
||||
// Team group with elevated permissions
|
||||
const teamGroup = Group.create();
|
||||
teamGroup.extend(companyGroup); // Inherits company-wide access
|
||||
teamGroup.addMember(teamLead, "admin");
|
||||
teamGroup.addMember(developer, "writer");
|
||||
|
||||
// Project group with specific permissions
|
||||
const projectGroup = Group.create();
|
||||
projectGroup.extend(teamGroup); // Inherits team permissions
|
||||
projectGroup.addMember(client, "reader"); // Client can only read project items
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
This creates a hierarchy where:
|
||||
- The CEO has admin access to everything
|
||||
- Team members get writer access to team and project content
|
||||
- Team leads get admin access to team and project content
|
||||
- The client can only read project content
|
||||
@@ -50,36 +50,16 @@ Collaborative Values (CoValues), build a UI and subscribe to changes, set permis
|
||||
import ReactDOM from "react-dom/client"; // old
|
||||
import App from "./App.tsx"; // old
|
||||
import "./index.css"; // old
|
||||
import {
|
||||
JazzProvider,
|
||||
useDemoAuth,
|
||||
DemoAuthBasicUI,
|
||||
} from "jazz-react";
|
||||
// old
|
||||
import { JazzProvider } from "jazz-react"; // old
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const [auth, authState] = useDemoAuth();
|
||||
|
||||
return (
|
||||
<>
|
||||
<JazzProvider
|
||||
auth={auth}
|
||||
// replace `you@example.com` with your email as a temporary API key
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
>
|
||||
{children}
|
||||
</JazzProvider>
|
||||
<DemoAuthBasicUI appName="Circular" state={authState} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// old
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render( // old
|
||||
<React.StrictMode> // old
|
||||
<JazzAndAuth>
|
||||
<JazzProvider
|
||||
// replace `you@example.com` with your email as a temporary API key
|
||||
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
|
||||
>
|
||||
<App />
|
||||
</JazzAndAuth>
|
||||
</JazzProvider>
|
||||
</React.StrictMode>// old
|
||||
); // old
|
||||
```
|
||||
@@ -349,7 +329,7 @@ This works because CoValues
|
||||
|
||||
1. Directly on an instance:
|
||||
|
||||
<CodeGroup size="sm">
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const unsub = issue.subscribe([], (updatedIssue) => console.log(updatedIssue));
|
||||
```
|
||||
@@ -357,7 +337,7 @@ This works because CoValues
|
||||
|
||||
2. If you only have an ID (this will load the issue if needed):
|
||||
|
||||
<CodeGroup size="sm">
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const unsub = Issue.subscribe(issueID, me, [], (updatedIssue) => {
|
||||
console.log(updatedIssue);
|
||||
@@ -373,7 +353,7 @@ This works because CoValues
|
||||
|
||||
|
||||
By the way, `useCoState` is basically just an optimized version of
|
||||
<CodeGroup size="sm">
|
||||
<CodeGroup>
|
||||
```ts
|
||||
function useCoState<V extends CoValue>(Schema: CoValueClass<V>, id?: ID<V>): V | undefined {
|
||||
const [value, setValue] = useState<V>();
|
||||
@@ -1,46 +1,11 @@
|
||||
import DocsLayout from "@/app/docs/[framework]/(others)/layout";
|
||||
import DocsLayout from "@/components/docs/DocsLayout";
|
||||
import { TableOfContents } from "@/components/docs/TableOfContents";
|
||||
import ComingSoonPage from "@/components/docs/coming-soon.mdx";
|
||||
import { DocNav } from "@/components/docs/nav";
|
||||
import { docNavigationItems } from "@/lib/docNavigationItems.js";
|
||||
import { Framework, frameworks } from "@/lib/framework";
|
||||
import type { Toc } from "@stefanprobst/rehype-extract-toc";
|
||||
import { Prose } from "gcmp-design-system/src/app/components/molecules/Prose";
|
||||
import { Metadata } from "next";
|
||||
|
||||
async function getMdxSource(slugPath: string, framework: string) {
|
||||
try {
|
||||
return await import(`./${slugPath}.mdx`);
|
||||
} catch (error) {
|
||||
return await import(`./${slugPath}/${framework}.mdx`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params: { slug, framework },
|
||||
}: {
|
||||
params: { slug: string[]; framework: string };
|
||||
}): Promise<Metadata> {
|
||||
const slugPath = slug.join("/");
|
||||
const title = "Coming soon";
|
||||
try {
|
||||
const mdxSource = await getMdxSource(slugPath, framework);
|
||||
const title = mdxSource.metadata.title;
|
||||
|
||||
return {
|
||||
title,
|
||||
openGraph: {
|
||||
title,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
title,
|
||||
openGraph: {
|
||||
title,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params: { slug, framework },
|
||||
@@ -48,24 +13,31 @@ export default async function Page({
|
||||
const slugPath = slug.join("/");
|
||||
|
||||
try {
|
||||
const mdxSource = await getMdxSource(slugPath, framework);
|
||||
let mdxSource;
|
||||
try {
|
||||
mdxSource = await import(`./${slugPath}.mdx`);
|
||||
} catch (error) {
|
||||
mdxSource = await import(`./${slugPath}/${framework}.mdx`);
|
||||
}
|
||||
|
||||
const { default: Content, tableOfContents } = mdxSource;
|
||||
|
||||
// Exclude h1 from table of contents
|
||||
const tocItems = (tableOfContents as Toc)?.[0]?.children;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocsLayout toc={tocItems} nav={<DocNav />}>
|
||||
<Prose className="overflow-x-hidden lg:flex-1 py-8">
|
||||
<Content />
|
||||
</Prose>
|
||||
{tocItems && <TableOfContents items={tocItems} />}
|
||||
</>
|
||||
</DocsLayout>
|
||||
);
|
||||
} catch (error) {
|
||||
return (
|
||||
<DocsLayout>
|
||||
<ComingSoonPage />
|
||||
<DocsLayout nav={<DocNav />}>
|
||||
<Prose className="overflow-x-hidden lg:flex-1 py-8">
|
||||
<ComingSoonPage className="max-w-3xl mx-auto" />
|
||||
</Prose>
|
||||
</DocsLayout>
|
||||
);
|
||||
}
|
||||
@@ -192,24 +192,17 @@ Wrap your app components with the `JazzProvider:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import { JazzProvider, useDemoAuth, DemoAuthBasicUI } from "jazz-react-native";
|
||||
import { JazzProvider } from "jazz-react-native";
|
||||
import { MyAppAccount } from "./schema";
|
||||
|
||||
|
||||
export function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const [auth, state] = useDemoAuth();
|
||||
|
||||
export function MyJazzProvider({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<JazzProvider
|
||||
auth={auth}
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
AccountSchema={MyAppAccount}
|
||||
>
|
||||
{children}
|
||||
</JazzProvider>
|
||||
<DemoAuthBasicUI appName="My App" state={state} />
|
||||
</>
|
||||
<JazzProvider
|
||||
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
|
||||
AccountSchema={MyAppAccount}
|
||||
>
|
||||
{children}
|
||||
</JazzProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,62 +4,34 @@ import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
# <span id="react">React</span>
|
||||
|
||||
First you define a context providing component (typically called `JazzAndAuth`) that uses:
|
||||
|
||||
- the Jazz context provider with your app configs
|
||||
|
||||
- the hooks and default/custom UI of one of the [Auth Methods](/docs/react/authentication/auth-methods).
|
||||
|
||||
This is also where you specify the sync & storage server to connect to (see [Sync and storage](/docs/react/sync-and-storage)).
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { JazzProvider } from "jazz-react";// old
|
||||
import { MyAppAccount } from "./schema";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName });
|
||||
|
||||
return (
|
||||
<>
|
||||
<JazzProvider
|
||||
auth={passkeyAuth}
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
AccountSchema={MyAppAccount}
|
||||
>
|
||||
{children}
|
||||
</JazzProvider>
|
||||
<PasskeyAuthBasicUI state={passKeyState} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module "jazz-react" {
|
||||
interface Register {
|
||||
Account: MyAppAccount;
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
With `JazzAndAuth` defined, you can wrap your app in it and then use the hooks within your App.
|
||||
Wrap your application with `<JazzProvider />`, this is where you specify the sync & storage server to connect to (see [Sync and storage](/docs/react/sync-and-storage)).
|
||||
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { JazzProvider } from "jazz-react";
|
||||
import { MyAppAccount } from "./schema";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render( // old
|
||||
<React.StrictMode> // old
|
||||
<JazzAndAuth>
|
||||
<App />
|
||||
</JazzAndAuth>
|
||||
</React.StrictMode>// old
|
||||
<JazzProvider
|
||||
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
|
||||
AccountSchema={MyAppAccount}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
);// old
|
||||
|
||||
// Register the Account schema so `useAccount` returns our custom `MyAppAccount`
|
||||
declare module "jazz-react" {
|
||||
interface Register {
|
||||
Account: MyAppAccount;
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
|
||||
## Next.js
|
||||
|
||||
### Client-side only
|
||||
@@ -73,25 +45,18 @@ The easiest way to use Jazz with Next.JS is to only use it on the client side. Y
|
||||
```tsx
|
||||
"use client"
|
||||
import { JazzProvider } from "jazz-react"; // old
|
||||
import { PasskeyAuthBasicUI, usePasskeyAuth } from "jazz-react";// old
|
||||
import { MyAppAccount } from "./schema"; // old
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) { // old
|
||||
const [passkeyAuth, passKeyState] = usePasskeyAuth({ appName }); // old
|
||||
|
||||
return ( // old
|
||||
<> // old
|
||||
<JazzProvider // old
|
||||
auth={passkeyAuth} // old
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com" // old
|
||||
AccountSchema={MyAppAccount} // old
|
||||
>
|
||||
{children} // old
|
||||
</JazzProvider> // old
|
||||
<PasskeyAuthBasicUI state={passKeyState} /> // old
|
||||
</> // old
|
||||
); // old
|
||||
} // old
|
||||
export function MyJazzProvider(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<JazzProvider
|
||||
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
|
||||
AccountSchema={MyAppAccount}
|
||||
>
|
||||
{props.children}
|
||||
</JazzProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -56,12 +56,11 @@ export class MyAccount extends Account {
|
||||
import { JazzProvider } from 'jazz-svelte';
|
||||
|
||||
// Example configuration for authentication and peer connection
|
||||
let auth = null; // Replace with your auth implementation
|
||||
let peer = "wss://your-peer-endpoint";
|
||||
let sync = { peer: "wss://cloud.jazz.tools/?key=you@example.com" };
|
||||
let AccountSchema = MyAccount;
|
||||
</script>
|
||||
|
||||
<JazzProvider {auth} {peer} {AccountSchema}>
|
||||
<JazzProvider {sync} {AccountSchema}>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
@@ -108,7 +108,7 @@ Update the `src/main.ts` file to integrate Jazz:
|
||||
<CodeGroup>
|
||||
```typescript
|
||||
import "./assets/main.css";
|
||||
import { DemoAuthBasicUI, useDemoAuth, JazzProvider } from "jazz-vue";
|
||||
import { JazzProvider } from "jazz-vue";
|
||||
import { createApp, defineComponent, h } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
@@ -123,8 +123,6 @@ declare module "jazz-vue" {
|
||||
const RootComponent = defineComponent({
|
||||
name: "RootComponent",
|
||||
setup() {
|
||||
const { authMethod, state } = useDemoAuth();
|
||||
|
||||
return () => [
|
||||
h(
|
||||
JazzProvider,
|
||||
@@ -137,12 +135,6 @@ const RootComponent = defineComponent({
|
||||
default: () => h(App),
|
||||
},
|
||||
),
|
||||
|
||||
state.state !== "signedIn" &&
|
||||
h(DemoAuthBasicUI, {
|
||||
appName: "Jazz Vue Todo",
|
||||
state,
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
@@ -192,8 +192,6 @@ export class MyAppAccount extends Account {
|
||||
root: {},
|
||||
});
|
||||
|
||||
if (!result) throw new Error("Root missing!"); // this should never happen
|
||||
|
||||
const { root } = result;
|
||||
|
||||
// we specifically need to check for undefined,
|
||||
@@ -0,0 +1,377 @@
|
||||
import { ContentByFramework, CodeGroup } from '@/components/forMdx'
|
||||
|
||||
export const metadata = { title: "Jazz 0.10.0 is out!" };
|
||||
|
||||
# Jazz 0.10.0 is out!
|
||||
|
||||
<h2 className="not-prose text-sm text-stone-600 dark:text-stone-400 mb-5 pb-2 border-b">
|
||||
11 February 2025
|
||||
</h2>
|
||||
|
||||
<div>
|
||||
For Jazz 0.10.0 we have been focusing on enhancing authentication to make it optional, more flexible and easier to use.
|
||||
|
||||
The default is now anonymous auth, which means that you can build the functionality of your app first and figure out auth later. For users this means that they can start using your app right away on one device -- and once you integrate an auth method, users can sign up and their anonymous accounts are transparently upgraded to authenticated accounts that work across devices.
|
||||
|
||||
There are also some other minor improvements that will make your Jazz experience even better!
|
||||
|
||||
<h3>What's new?</h3>
|
||||
Here is what's changed in this release:
|
||||
- [New authentication flow](/docs/upgrade/0-10-0#new-authentication-flow): Now with anonymous auth, redesigned to make Jazz easier to start with and be more flexible.
|
||||
- [Local-only mode](/docs/upgrade/0-10-0#local-only-mode): Users can now explore your app in local-only mode before signing up.
|
||||
- [Improvements on the loading APIs](/docs/upgrade/0-10-0#improved-loading-api); `ensureLoaded` now always returns a value and `useCoState` now returns `null` if the value is not found.
|
||||
- [Jazz Workers on native WebSockets](/docs/upgrade/0-10-0#native-websocket-for-jazz-workers): Improves compatibility with a wider set of Javascript runtimes.
|
||||
- [Group inheritance with role mapping](/docs/upgrade/0-10-0#group-inheritance): Groups can now inherit members from other groups with a fixed role.
|
||||
- Support for Node 14 dropped on cojson.
|
||||
- Bugfix: `Group.removeMember` now returns a promise.
|
||||
</div>
|
||||
|
||||
<h3 id="new-authentication-flow">New authentication flow</h3>
|
||||
<div>
|
||||
Up until now authentication has been the first part to figure out when building a Jazz app, and this was a stumbling block for many.
|
||||
|
||||
Now it is no longer required and setting up a Jazz app is as easy as writing this:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
<JazzProvider
|
||||
auth={authMethod} // removed // *bin*
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com" // moved into sync // *bin*
|
||||
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }} // *add*
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
New users are initially authenticated as "anonymous" and you can sign them up later with the hooks/providers of your chosen auth method.
|
||||
|
||||
Your auth code can now blend into your component tree:
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
const [isSignUp, setIsSignUp] = useState(true);
|
||||
|
||||
const auth = usePasskeyAuth({ // Must be inside the JazzProvider!
|
||||
appName: "My super-cool web app",
|
||||
});
|
||||
|
||||
const handleViewChange = () => {
|
||||
setIsSignUp(!isSignUp);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (isSignUp) {
|
||||
await auth.signUp(username);
|
||||
} else {
|
||||
await auth.logIn();
|
||||
}
|
||||
onOpenChange(false);
|
||||
};
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
<ContentByFramework framework="react-native">
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
const [isSignUp, setIsSignUp] = useState(true);
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({ // Must be inside the JazzProvider!
|
||||
wordlist,
|
||||
});
|
||||
|
||||
const handleViewChange = () => {
|
||||
setIsSignUp(!isSignUp);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (isSignUp) {
|
||||
await auth.signUp();
|
||||
} else {
|
||||
await auth.logIn(loginPassphrase);
|
||||
}
|
||||
onOpenChange(false);
|
||||
};
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
<ContentByFramework framework="svelte">
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { usePasskeyAuth } from 'jazz-svelte';
|
||||
|
||||
const auth = usePasskeyAuth({ appName: "My super-cool web app" });
|
||||
|
||||
let error = $state<string | undefined>(undefined);
|
||||
|
||||
function signUp(e: Event) {
|
||||
const formData = new FormData(e.currentTarget as HTMLFormElement);
|
||||
const name = formData.get('name') as string;
|
||||
|
||||
if (!name) {
|
||||
error = 'Name is required';
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
error = undefined;
|
||||
auth.current.signUp(name).catch((e) => {
|
||||
error = e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function logIn() {
|
||||
error = undefined;
|
||||
auth.current.logIn().catch((e) => {
|
||||
error = e.message;
|
||||
});
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
<ContentByFramework framework="vue">
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { usePasskeyAuth } from 'jazz-vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const auth = usePasskeyAuth({ appName: "My super-cool web app" });
|
||||
const error = ref<string | undefined>(undefined);
|
||||
|
||||
function signUp(e: Event) {
|
||||
const formData = new FormData(e.currentTarget as HTMLFormElement);
|
||||
const name = formData.get('name') as string;
|
||||
|
||||
if (!name) {
|
||||
error.value = 'Name is required';
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
error.value = undefined;
|
||||
auth.value.signUp(name).catch((e) => {
|
||||
error.value = e.message;
|
||||
});
|
||||
}
|
||||
|
||||
function logIn(e: Event) {
|
||||
error.value = undefined;
|
||||
e.preventDefault();
|
||||
auth.value.logIn().catch((e) => {
|
||||
error.value = e.message;
|
||||
});
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
When a user signs up with one of the auth methods we upgrade the current anonymous account to an authenticated account by storing the account's secret keys in the auth method.
|
||||
|
||||
<ContentByFramework framework={["react"]}>
|
||||
To check if the user has registered you can use the new `useIsAuthenticated` hook:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
export function AuthButton() {
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const { logOut } = useAccount();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={logOut}
|
||||
>
|
||||
Sign out
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => setOpen(true)}>
|
||||
Sign up
|
||||
</Button>
|
||||
<AuthModal open={open} onOpenChange={setOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
<ContentByFramework framework={["react-native"]}>
|
||||
To check if the user has registered you can use the new `useIsAuthenticated` hook:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
export function AuthButton() {
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const { logOut } = useAccount();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
onPress={logOut}
|
||||
>
|
||||
Sign out
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onPress={() => setOpen(true)}>
|
||||
Sign up
|
||||
</Button>
|
||||
<AuthModal open={open} onOpenChange={setOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework={["react"]}>
|
||||
If you do want to require your users to register before using your app, you can do it by moving the auth code outside of your app and conditionally rendering your app as a child -- or apply this pattern for specific parts of the app you'd like to gate behind authentication.
|
||||
|
||||
Our provided "Basic UIs" for the different auth methods render their children conditionally by default, so you can either nest them in your app without children, or nest your app or gated parts of your app inside *them*, depending on the desired behavior.
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { JazzProvider, PasskeyAuthBasicUI } from "jazz-react";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<JazzProvider
|
||||
sync={{ peer: "wss://cloud.jazz.tools/?key=you@example.com" }}
|
||||
>
|
||||
<PasskeyAuthBasicUI appName="My super-cool web app">
|
||||
<App />
|
||||
</PasskeyAuthBasicUI>
|
||||
</JazzProvider>
|
||||
);
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
For the changes related to the specific auth providers see the updated [authentication docs](/docs/authentication/overview).
|
||||
</div>
|
||||
|
||||
<h3 id="local-only-mode">Local-only mode</h3>
|
||||
<div>
|
||||
If you are ok with data not being persisted on the sync server for anonymous users, you can now set your app to local-only depending on the user's authentication state.
|
||||
|
||||
With `sync.when` set to `"signedUp"` the app will work in local-only mode when the user is anonymous and unlock the multiplayer/multi-device features and cloud persistence when they sign up:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// This makes the app work in local mode when the user is anonymous
|
||||
when: "signedUp",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
You can control when Jazz will sync by switching the `when` config to `"always"` or `"never"`.
|
||||
</div>
|
||||
|
||||
<h3 id="improved-loading-api">Improvements on the loading APIs</h3>
|
||||
<div>
|
||||
Before 0.10.0 `ensureLoaded` was returning a nullable value forcing the Typescript code to always include null checks:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
export async function updateActiveTrack(track: MusicTrack) { // No need to pass the account
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {},
|
||||
});
|
||||
|
||||
if (!me) { // Technically can never happen
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
me.root.activeTrack = track;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
Now `ensureLoaded` always returns a value, making it's usage leaner:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
export async function updateActiveTrack(track: MusicTrack) { // No need to pass the account
|
||||
const { root } = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {},
|
||||
});
|
||||
|
||||
// Null checks are no more required
|
||||
root.activeTrack = track;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
It will throw if the value is not found, which can happen if the user tries to resolve a value that is not available.
|
||||
|
||||
`useCoState` now returns `null` if the value is not found, making it now possible to check against not-found values:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const value = useCoState(MusicTrack, id, {});
|
||||
|
||||
if (value === null) {
|
||||
return <div>Track not found</div>;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</div>
|
||||
|
||||
<h3 id="native-websocket-for-jazz-workers">Jazz Workers on native WebSockets</h3>
|
||||
<div>
|
||||
We have removed the dependency on `ws` and switched to the native WebSocket API for Jazz Workers.
|
||||
|
||||
This improves the compatibility with a wider set of Javascript runtimes adding drop-in support for Deno, Bun, Browsers and Cloudflare Durable Objects.
|
||||
|
||||
If you are using a Node.js version lower than 22 you will need to install the `ws` package and provide the WebSocket constructor:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
import { WebSocket } from "ws";
|
||||
import { startWorker } from "jazz-nodejs";
|
||||
|
||||
const { worker } = await startWorker({
|
||||
WebSocket,
|
||||
});
|
||||
```
|
||||
</CodeGroup>
|
||||
</div>
|
||||
|
||||
<h3 id="group-inheritance">Group inheritance with role mapping</h3>
|
||||
<div>
|
||||
You can override the inherited role by passing a second argument to `extend`.
|
||||
|
||||
This can be used to give users limited access to a child group:
|
||||
<CodeGroup>
|
||||
```ts
|
||||
const organization = Group.create();
|
||||
const billing = Group.create();
|
||||
|
||||
billing.extend(organization, "reader");
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
This way the members of the organization can only read the billing data, even if they are admins in the organization group.
|
||||
|
||||
More about the group inheritance can be found in the [dedicated docs page](/docs/groups/inheritance).
|
||||
</div>
|
||||
@@ -17,12 +17,12 @@ export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
import { useState } from "react";
|
||||
import { Issue } from "./schema";
|
||||
import { IssueComponent } from "./components/Issue.tsx";
|
||||
import { useState } from "react";
|
||||
import { Issue } from "./schema";
|
||||
import { IssueComponent } from "./components/Issue.tsx";
|
||||
function App() {
|
||||
const [issue, setIssue] = useState<Issue>();
|
||||
|
||||
const [issue, setIssue] = useState<Issue>();
|
||||
|
||||
const createIssue = () => {
|
||||
setIssue(Issue.create(
|
||||
{
|
||||
@@ -33,13 +33,13 @@ export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
}, // The owner defaults now to a group managed by the current user!
|
||||
));
|
||||
};
|
||||
|
||||
|
||||
if (issue) {
|
||||
return <IssueComponent issue={issue} />;
|
||||
} else {
|
||||
return <IssueComponent issue={issue} />;
|
||||
} else {
|
||||
return <button onClick={createIssue}>Create Issue</button>;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
@@ -71,4 +71,4 @@ export const metadata = { title: "Upgrade to Jazz 0.9.0" };
|
||||
Everything is backward compatible, so no upgrade steps are required.
|
||||
|
||||
With this Jazz API becomes way more lean and more is coming!
|
||||
</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user