Compare commits
43 Commits
fix/code-i
...
poc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0589bf275f | ||
|
|
2f6ca4cdf4 | ||
|
|
cfbe745de3 | ||
|
|
7b96fd719b | ||
|
|
c391180093 | ||
|
|
8ea0858571 | ||
|
|
d1c0981024 | ||
|
|
bb0158ec51 | ||
|
|
8a40c02c52 | ||
|
|
b95937511f | ||
|
|
1967c736c5 | ||
|
|
b84edecb50 | ||
|
|
5631d53e3f | ||
|
|
244fd84a88 | ||
|
|
d4e93afdf9 | ||
|
|
26d4fa985c | ||
|
|
cefc6e27de | ||
|
|
452c284a01 | ||
|
|
191a7f33b1 | ||
|
|
d7be246f75 | ||
|
|
5c8717543c | ||
|
|
02cd6fe4b7 | ||
|
|
526a26a39d | ||
|
|
60adbffc26 | ||
|
|
d8cabe3fa6 | ||
|
|
928ac67a06 | ||
|
|
0458e12721 | ||
|
|
df59b53000 | ||
|
|
891baf2053 | ||
|
|
3a55c8a627 | ||
|
|
5a9770242f | ||
|
|
f6bbe18a53 | ||
|
|
0712546277 | ||
|
|
14b70aa445 | ||
|
|
47275a1340 | ||
|
|
ca54b4c1a8 | ||
|
|
86a2c914d3 | ||
|
|
d9c250386e | ||
|
|
73d5f18cb8 | ||
|
|
84e17a9189 | ||
|
|
0e8b04579a | ||
|
|
56a967cce6 | ||
|
|
f823b2a307 |
@@ -11,16 +11,13 @@
|
|||||||
"cojson-storage-sqlite",
|
"cojson-storage-sqlite",
|
||||||
"cojson-transport-ws",
|
"cojson-transport-ws",
|
||||||
"jazz-browser",
|
"jazz-browser",
|
||||||
"jazz-auth-clerk",
|
"jazz-browser-auth-clerk",
|
||||||
"jazz-browser-media-images",
|
"jazz-browser-media-images",
|
||||||
"jazz-expo",
|
|
||||||
"jazz-inspector",
|
|
||||||
"jazz-nodejs",
|
"jazz-nodejs",
|
||||||
"jazz-react",
|
"jazz-react",
|
||||||
"jazz-react-core",
|
|
||||||
"jazz-react-auth-clerk",
|
"jazz-react-auth-clerk",
|
||||||
"jazz-react-native-core",
|
|
||||||
"jazz-react-native",
|
"jazz-react-native",
|
||||||
|
"jazz-react-native-auth-clerk",
|
||||||
"jazz-react-native-media-images",
|
"jazz-react-native-media-images",
|
||||||
"jazz-run",
|
"jazz-run",
|
||||||
"jazz-svelte",
|
"jazz-svelte",
|
||||||
|
|||||||
5
.changeset/sharp-needles-sin.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"cojson": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Apply time travel when checking the roles on a parent group
|
||||||
39
.github/actions/android-emulator/action.yml
vendored
@@ -1,39 +0,0 @@
|
|||||||
name: Setup Android Emulator
|
|
||||||
|
|
||||||
inputs:
|
|
||||||
api-level:
|
|
||||||
description: 'API level to use for the emulator'
|
|
||||||
required: true
|
|
||||||
default: '29'
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "composite"
|
|
||||||
steps:
|
|
||||||
- name: Enable KVM
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
|
||||||
sudo udevadm control --reload-rules
|
|
||||||
sudo udevadm trigger --name-match=kvm
|
|
||||||
|
|
||||||
- name: Gradle cache
|
|
||||||
uses: gradle/actions/setup-gradle@v4
|
|
||||||
|
|
||||||
- name: AVD cache
|
|
||||||
uses: useblacksmith/cache@v5
|
|
||||||
id: avd-cache
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.android/avd/*
|
|
||||||
~/.android/adb*
|
|
||||||
key: avd-${{ inputs.api-level }}
|
|
||||||
|
|
||||||
- name: Create AVD and Generate Snapshot for Caching
|
|
||||||
if: steps.avd-cache.outputs.cache-hit != 'true'
|
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
|
||||||
with:
|
|
||||||
api-level: ${{ inputs.api-level }}
|
|
||||||
force-avd-creation: false
|
|
||||||
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-metrics
|
|
||||||
disable-animations: false
|
|
||||||
script: echo "Generated AVD snapshot for caching."
|
|
||||||
36
.github/actions/source-code/action.yml
vendored
@@ -1,36 +0,0 @@
|
|||||||
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
|
|
||||||
34
.github/workflows/build-examples.yaml
vendored
@@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-examples:
|
build-examples:
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
example: [
|
example: [
|
||||||
@@ -19,16 +19,40 @@ jobs:
|
|||||||
"pets",
|
"pets",
|
||||||
"reactions",
|
"reactions",
|
||||||
"todo",
|
"todo",
|
||||||
|
"onboarding",
|
||||||
]
|
]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- uses: actions/checkout@v3
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Source Code
|
- name: Enable corepack
|
||||||
uses: ./.github/actions/source-code/
|
run: corepack enable
|
||||||
|
|
||||||
|
- 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
|
- name: Pnpm Build
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
26
.github/workflows/build-starters.yaml
vendored
@@ -1,26 +0,0 @@
|
|||||||
name: Build Starters
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: ["main"]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-starters:
|
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
starter: ["react-passkey-auth"]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Setup Source Code
|
|
||||||
uses: ./.github/actions/source-code/
|
|
||||||
|
|
||||||
- name: Pnpm Build
|
|
||||||
run: |
|
|
||||||
pnpm install
|
|
||||||
pnpm turbo build;
|
|
||||||
working-directory: ./starters/${{ matrix.starter }}
|
|
||||||
2
.github/workflows/code-quality.yml
vendored
@@ -6,7 +6,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
quality:
|
quality:
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
84
.github/workflows/e2e-rn-test.yml
vendored
@@ -1,84 +0,0 @@
|
|||||||
name: End-to-End Tests for React Native
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
paths:
|
|
||||||
- ".github/actions/android-emulator/**"
|
|
||||||
- ".github/actions/source-code/**"
|
|
||||||
- ".github/workflows/e2e-rn-test.yml"
|
|
||||||
- "examples/chat-rn-expo/**"
|
|
||||||
- "packages/**"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
e2e-tests:
|
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
|
||||||
|
|
||||||
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: Setup JDK
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: corretto
|
|
||||||
java-version: 22
|
|
||||||
cache: gradle
|
|
||||||
|
|
||||||
- name: Pnpm Build
|
|
||||||
run: pnpm turbo build --filter="./packages/*"
|
|
||||||
|
|
||||||
- name: chat-rn-expo App Pre Build
|
|
||||||
working-directory: ./examples/chat-rn-expo
|
|
||||||
run: |
|
|
||||||
pnpm build
|
|
||||||
pnpm expo prebuild --clean
|
|
||||||
|
|
||||||
- name: Install Maestro
|
|
||||||
run: |
|
|
||||||
curl -fsSL "https://get.maestro.mobile.dev" | bash
|
|
||||||
|
|
||||||
- name: Setup Android Emulator
|
|
||||||
id: android-emulator
|
|
||||||
uses: ./.github/actions/android-emulator/
|
|
||||||
with:
|
|
||||||
api-level: 29
|
|
||||||
|
|
||||||
- name: Test App
|
|
||||||
uses: reactivecircus/android-emulator-runner@v2
|
|
||||||
id: e2e_test
|
|
||||||
continue-on-error: true
|
|
||||||
with:
|
|
||||||
api-level: 29
|
|
||||||
force-avd-creation: false
|
|
||||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-metrics
|
|
||||||
disable-animations: true
|
|
||||||
working-directory: ./examples/chat-rn-expo/
|
|
||||||
script: ./test/e2e/run.sh
|
|
||||||
|
|
||||||
- name: Copy Maestro Output
|
|
||||||
if: steps.e2e_test.outcome != 'success'
|
|
||||||
run: |
|
|
||||||
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
|
|
||||||
31
.github/workflows/jazz-run.yml
vendored
@@ -8,16 +8,38 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Source Code
|
- name: Enable corepack
|
||||||
uses: ./.github/actions/source-code/
|
run: corepack enable
|
||||||
|
|
||||||
|
- 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: Build jazz-run
|
- name: Build jazz-run
|
||||||
run: pnpm exec turbo build && chmod +x dist/index.js;
|
run: pnpm exec turbo build && chmod +x dist/index.js;
|
||||||
@@ -26,3 +48,4 @@ jobs:
|
|||||||
- name: Run create account
|
- name: Run create account
|
||||||
run: ./dist/index.js account create --name "Jazz Run CI test"
|
run: ./dist/index.js account create --name "Jazz Run CI test"
|
||||||
working-directory: ./packages/jazz-run
|
working-directory: ./packages/jazz-run
|
||||||
|
|
||||||
36
.github/workflows/playwright.yml
vendored
@@ -9,19 +9,45 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
project: ["tests/e2e", "examples/chat", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "starters/react-passkey-auth"]
|
project: ["tests/e2e", "examples/chat", "examples/music-player", "examples/pets", "examples/onboarding"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Setup Source Code
|
- name: Enable corepack
|
||||||
uses: ./.github/actions/source-code/
|
run: corepack enable
|
||||||
|
|
||||||
|
- 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 .env
|
||||||
|
run: echo "VITE_WS_PEER=ws://localhost:4200/" >> .env
|
||||||
|
working-directory: ./${{ matrix.project }}
|
||||||
|
|
||||||
- name: Pnpm Build
|
- name: Pnpm Build
|
||||||
run: pnpm turbo build
|
run: pnpm turbo build
|
||||||
|
|||||||
102
.github/workflows/pre-release.yml
vendored
@@ -1,102 +0,0 @@
|
|||||||
name: Pre-Publish tagged Pull Requests
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened, labeled]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pre-release:
|
|
||||||
if: contains(github.event.pull_request.labels.*.name, 'pre-release')
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Source Code
|
|
||||||
uses: ./.github/actions/source-code/
|
|
||||||
|
|
||||||
- name: Pnpm Build
|
|
||||||
run: pnpm turbo build --filter="./packages/*"
|
|
||||||
|
|
||||||
- name: Pre publish
|
|
||||||
run: pnpm exec pkg-pr-new publish --json output.json --comment=off "./packages/*"
|
|
||||||
|
|
||||||
- name: Post or update comment
|
|
||||||
uses: actions/github-script@v6
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const output = JSON.parse(fs.readFileSync('output.json', 'utf8'));
|
|
||||||
|
|
||||||
const packages = output.packages
|
|
||||||
.map((p) => `- ${p.name}: ${p.url}`)
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
const sha =
|
|
||||||
context.event_name === 'pull_request'
|
|
||||||
? context.payload.pull_request.head.sha
|
|
||||||
: context.payload.after;
|
|
||||||
|
|
||||||
const resolutions = Object.fromEntries(
|
|
||||||
output.packages.map((p) => [p.name, p.url])
|
|
||||||
);
|
|
||||||
|
|
||||||
const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${sha}`;
|
|
||||||
|
|
||||||
const body = `## Jazz pre-release
|
|
||||||
|
|
||||||
### Packages:
|
|
||||||
|
|
||||||
\`\`\`json
|
|
||||||
${JSON.stringify(resolutions, null, 4)}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
[View Commit](${commitUrl})`;
|
|
||||||
|
|
||||||
async function logPublishInfo() {
|
|
||||||
console.log('\n' + '='.repeat(50));
|
|
||||||
console.log('Publish Information');
|
|
||||||
console.log('='.repeat(50));
|
|
||||||
console.log('\nPublished Packages:');
|
|
||||||
console.log(output.packages);
|
|
||||||
console.log('\nTemplates:');
|
|
||||||
console.log(templates);
|
|
||||||
console.log(`\nCommit URL: ${commitUrl}`);
|
|
||||||
console.log('\n' + '='.repeat(50));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.eventName === 'pull_request') {
|
|
||||||
if (context.issue.number) {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
body: body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (context.eventName === 'push') {
|
|
||||||
const pullRequests = await github.rest.pulls.list({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
state: 'open',
|
|
||||||
head: `${context.repo.owner}:${context.ref.replace(
|
|
||||||
'refs/heads/',
|
|
||||||
''
|
|
||||||
)}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (pullRequests.data.length > 0) {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
issue_number: pullRequests.data[0].number,
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
body: body,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'No open pull request found for this push. Logging publish information to console:'
|
|
||||||
);
|
|
||||||
await logPublishInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
.github/workflows/release.yml
vendored
@@ -4,29 +4,41 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
debug_enabled:
|
|
||||||
type: boolean
|
|
||||||
description: "Run tmate session for debugging"
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
|
|
||||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Setup Source Code
|
- name: Enable corepack
|
||||||
uses: ./.github/actions/source-code/
|
run: corepack enable
|
||||||
|
|
||||||
- name: Build packages
|
- name: Install Node.js
|
||||||
run: pnpm exec turbo run build --filter='./packages/*'
|
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: Create Release Pull Request or Publish to npm
|
- name: Create Release Pull Request or Publish to npm
|
||||||
id: changesets
|
id: changesets
|
||||||
@@ -36,11 +48,4 @@ jobs:
|
|||||||
publish: pnpm release
|
publish: pnpm release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
# Enable tmate debugging only if the workflow is manually triggered, debug_enabled is true, and the workflow failed
|
|
||||||
- name: Setup tmate session for debugging
|
|
||||||
if: ${{ failure() && github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
|
|
||||||
uses: mxschmitt/action-tmate@v3
|
|
||||||
with:
|
|
||||||
timeout-minutes: 15
|
|
||||||
33
.github/workflows/unit-test.yml
vendored
@@ -9,20 +9,39 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
unit-tests:
|
unit-tests:
|
||||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Source Code
|
- name: Enable corepack
|
||||||
uses: ./.github/actions/source-code/
|
run: corepack enable
|
||||||
|
|
||||||
|
- 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: Pnpm Build
|
- name: Pnpm Build
|
||||||
run: pnpm turbo build --filter="./packages/*"
|
run: pnpm turbo build
|
||||||
|
|
||||||
- name: Install Playwright Browsers
|
|
||||||
run: pnpm exec playwright install
|
|
||||||
|
|
||||||
- name: Unit Tests
|
- name: Unit Tests
|
||||||
run: pnpm test:ci
|
run: pnpm test:ci
|
||||||
|
|||||||
15
.gitignore
vendored
@@ -5,29 +5,16 @@ docsTmp
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.turbo
|
.turbo
|
||||||
coverage
|
coverage
|
||||||
.direnv
|
|
||||||
|
|
||||||
# Typescript
|
|
||||||
**/*.tsbuildinfo
|
|
||||||
|
|
||||||
# Next.js
|
# Next.js
|
||||||
**/.next
|
**/.next
|
||||||
|
|
||||||
# Vite output
|
# Vite output
|
||||||
**/dist
|
**/dist
|
||||||
__screenshots__
|
|
||||||
|
|
||||||
# Playwright
|
# Playwright
|
||||||
test-results
|
test-results
|
||||||
|
|
||||||
.husky
|
.husky
|
||||||
|
|
||||||
.vscode/*
|
.vscode/settings.json
|
||||||
|
|
||||||
.svelte-kit
|
|
||||||
|
|
||||||
.cursorrules
|
|
||||||
.windsurfrules
|
|
||||||
|
|
||||||
|
|
||||||
.idea
|
|
||||||
|
|||||||
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
8
.idea/jazz.iml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/jazz.iml" filepath="$PROJECT_DIR$/.idea/jazz.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
19
.idea/php.xml
generated
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="MessDetectorOptionsConfiguration">
|
|
||||||
<option name="transferred" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="PHPCSFixerOptionsConfiguration">
|
|
||||||
<option name="transferred" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
|
||||||
<option name="highlightLevel" value="WARNING" />
|
|
||||||
<option name="transferred" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="PhpStanOptionsConfiguration">
|
|
||||||
<option name="transferred" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="PsalmOptionsConfiguration">
|
|
||||||
<option name="transferred" value="true" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/prettier.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="PrettierConfiguration">
|
|
||||||
<option name="myConfigurationMode" value="AUTOMATIC" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1 +1 @@
|
|||||||
22
|
20
|
||||||
@@ -36,7 +36,7 @@ We welcome all ideas! If you have suggestions, feel free to open an issue marked
|
|||||||
|
|
||||||
### 5. Local Setup
|
### 5. Local Setup
|
||||||
|
|
||||||
You'll need Node.js 22.x installed (we're working on support for 23.x), and pnpm 9.x installed. If you're using nix, run `nix develop` to get a shell with the correct versions of everything installed.
|
You'll need Node.js 20.x or 22.x installed (we're working on support for 23.x), and pnpm 9.x installed. If you're using nix, run `nix develop` to get a shell with the correct versions of everything installed.
|
||||||
|
|
||||||
1. **Clone the repository**:
|
1. **Clone the repository**:
|
||||||
```bash
|
```bash
|
||||||
@@ -48,25 +48,7 @@ You'll need Node.js 22.x installed (we're working on support for 23.x), and pnpm
|
|||||||
pnpm install
|
pnpm install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Install homepage dependencies**:
|
3. **Run tests** to verify everything is working:
|
||||||
|
|
||||||
```bash
|
|
||||||
cd homepage && pnpm install
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Go back to the project root**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ..
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Build the packages**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm build
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Run tests** to verify everything is working:
|
|
||||||
```bash
|
```bash
|
||||||
pnpm test
|
pnpm test
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2025, Garden Computing, Inc.
|
Copyright 2024, Garden Computing, Inc.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
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
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
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
|
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)
|
- Community & support: [Discord](https://discord.gg/utDMjHYg42)
|
||||||
- Updates: [X](https://x.com/jazz_tools) & [Email](https://garden.co/news)
|
- Updates: [X](https://x.com/jazz_tools) & [Email](https://garden.co/news)
|
||||||
|
|
||||||
Copyright 2025 — Garden Computing, Inc.
|
Copyright 2024 — Garden Computing, Inc.
|
||||||
|
|||||||
57
biome.json
@@ -12,9 +12,7 @@
|
|||||||
"**/ios/**",
|
"**/ios/**",
|
||||||
"**/android/**",
|
"**/android/**",
|
||||||
"packages/jazz-svelte/**",
|
"packages/jazz-svelte/**",
|
||||||
"examples/*svelte*/**",
|
"examples/*svelte*/**"
|
||||||
"homepage/homepage/**",
|
|
||||||
"**/package.json"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
@@ -27,56 +25,7 @@
|
|||||||
"linter": {
|
"linter": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true,
|
"recommended": true
|
||||||
"correctness": {
|
|
||||||
"useImportExtensions": {
|
|
||||||
"level": "error",
|
|
||||||
"options": {
|
|
||||||
"suggestedExtensions": {
|
|
||||||
"ts": {
|
|
||||||
"module": "js",
|
|
||||||
"component": "jsx"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"include": ["packages/**/src/**"],
|
|
||||||
"linter": {
|
|
||||||
"enabled": true,
|
|
||||||
"rules": {
|
|
||||||
"recommended": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": ["packages/cojson-storage*/**", "cojson-transport-ws/**"],
|
|
||||||
"linter": {
|
|
||||||
"enabled": true,
|
|
||||||
"rules": {
|
|
||||||
"recommended": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"include": ["packages/**/src/tests/**"],
|
|
||||||
"linter": {
|
|
||||||
"rules": {
|
|
||||||
"correctness": {
|
|
||||||
"useImportExtensions": "off"
|
|
||||||
},
|
|
||||||
"style": {
|
|
||||||
"noNonNullAssertion": "off"
|
|
||||||
},
|
|
||||||
"suspicious": {
|
|
||||||
"noExplicitAny": "info"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
examples/book-shelf/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
246
examples/book-shelf/CHANGELOG.md
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
# jazz-example-book-shelf
|
||||||
|
|
||||||
|
## 0.1.29
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.37
|
||||||
|
- jazz-tools@0.8.37
|
||||||
|
- jazz-browser-media-images@0.8.37
|
||||||
|
|
||||||
|
## 0.1.28
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [441fe27]
|
||||||
|
- jazz-tools@0.8.36
|
||||||
|
- jazz-react@0.8.36
|
||||||
|
- jazz-browser-media-images@0.8.36
|
||||||
|
|
||||||
|
## 0.1.27
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [9212ab8]
|
||||||
|
- Updated dependencies [8b87117]
|
||||||
|
- jazz-react@0.8.35
|
||||||
|
- jazz-tools@0.8.35
|
||||||
|
- jazz-browser-media-images@0.8.35
|
||||||
|
|
||||||
|
## 0.1.26
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.34
|
||||||
|
- jazz-tools@0.8.34
|
||||||
|
- jazz-browser-media-images@0.8.34
|
||||||
|
|
||||||
|
## 0.1.25
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-browser-media-images@0.8.33
|
||||||
|
- jazz-react@0.8.33
|
||||||
|
|
||||||
|
## 0.1.24
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [df42b2b]
|
||||||
|
- jazz-tools@0.8.32
|
||||||
|
- jazz-react@0.8.32
|
||||||
|
- jazz-browser-media-images@0.8.32
|
||||||
|
|
||||||
|
## 0.1.23
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.31
|
||||||
|
- jazz-tools@0.8.31
|
||||||
|
- jazz-browser-media-images@0.8.31
|
||||||
|
|
||||||
|
## 0.1.22
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.30
|
||||||
|
- jazz-tools@0.8.30
|
||||||
|
- jazz-browser-media-images@0.8.30
|
||||||
|
|
||||||
|
## 0.1.21
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.29
|
||||||
|
- jazz-tools@0.8.29
|
||||||
|
- jazz-browser-media-images@0.8.29
|
||||||
|
|
||||||
|
## 0.1.20
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.28
|
||||||
|
- jazz-tools@0.8.28
|
||||||
|
- jazz-browser-media-images@0.8.28
|
||||||
|
|
||||||
|
## 0.1.19
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.27
|
||||||
|
- jazz-tools@0.8.27
|
||||||
|
- jazz-browser-media-images@0.8.27
|
||||||
|
|
||||||
|
## 0.1.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [59d37df]
|
||||||
|
- jazz-react@0.8.26
|
||||||
|
|
||||||
|
## 0.1.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-browser-media-images@0.8.24
|
||||||
|
- jazz-react@0.8.24
|
||||||
|
|
||||||
|
## 0.1.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [d348c2d]
|
||||||
|
- Updated dependencies [6902b5b]
|
||||||
|
- Updated dependencies [1a0cd3d]
|
||||||
|
- jazz-tools@0.8.23
|
||||||
|
- jazz-react@0.8.23
|
||||||
|
- jazz-browser-media-images@0.8.23
|
||||||
|
|
||||||
|
## 0.1.15
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [59cc64d]
|
||||||
|
- jazz-react@0.8.22
|
||||||
|
- jazz-browser-media-images@0.8.22
|
||||||
|
|
||||||
|
## 0.1.14
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [149ca97]
|
||||||
|
- jazz-tools@0.8.21
|
||||||
|
- jazz-react@0.8.21
|
||||||
|
- jazz-browser-media-images@0.8.21
|
||||||
|
|
||||||
|
## 0.1.13
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [dd9b13f]
|
||||||
|
- Updated dependencies [a69ed0b]
|
||||||
|
- Updated dependencies [3ef3ff3]
|
||||||
|
- Updated dependencies [c6931b8]
|
||||||
|
- jazz-react@0.8.20
|
||||||
|
- jazz-browser-media-images@0.8.20
|
||||||
|
|
||||||
|
## 0.1.12
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.19
|
||||||
|
- jazz-tools@0.8.19
|
||||||
|
- jazz-browser-media-images@0.8.19
|
||||||
|
|
||||||
|
## 0.1.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.18
|
||||||
|
- jazz-tools@0.8.18
|
||||||
|
- jazz-browser-media-images@0.8.18
|
||||||
|
|
||||||
|
## 0.1.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.17
|
||||||
|
- jazz-tools@0.8.17
|
||||||
|
- jazz-browser-media-images@0.8.17
|
||||||
|
|
||||||
|
## 0.1.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [2af107c]
|
||||||
|
- jazz-react@0.8.16
|
||||||
|
- jazz-browser-media-images@0.8.16
|
||||||
|
- jazz-tools@0.8.16
|
||||||
|
|
||||||
|
## 0.1.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [cce679b]
|
||||||
|
- jazz-tools@0.8.15
|
||||||
|
- jazz-browser-media-images@0.8.15
|
||||||
|
- jazz-react@0.8.15
|
||||||
|
|
||||||
|
## 0.1.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [36273b3]
|
||||||
|
- jazz-tools@0.8.14
|
||||||
|
- jazz-browser-media-images@0.8.14
|
||||||
|
- jazz-react@0.8.14
|
||||||
|
|
||||||
|
## 0.1.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [fd011d7]
|
||||||
|
- jazz-tools@0.8.13
|
||||||
|
- jazz-browser-media-images@0.8.13
|
||||||
|
- jazz-react@0.8.13
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [3cc6aee]
|
||||||
|
- jazz-react@0.8.12
|
||||||
|
- jazz-tools@0.8.12
|
||||||
|
- jazz-browser-media-images@0.8.12
|
||||||
|
|
||||||
|
## 0.1.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react@0.8.11
|
||||||
|
- jazz-tools@0.8.11
|
||||||
|
- jazz-browser-media-images@0.8.11
|
||||||
|
|
||||||
|
## 0.1.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-browser-media-images@0.8.7
|
||||||
|
- jazz-react@0.8.7
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-browser-media-images@0.8.6
|
||||||
|
- jazz-react@0.8.6
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [c3f4e6b]
|
||||||
|
- Updated dependencies [d9152ed]
|
||||||
|
- jazz-tools@0.8.5
|
||||||
|
- jazz-browser-media-images@0.8.5
|
||||||
|
- jazz-react@0.8.5
|
||||||
4
examples/book-shelf/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM caddy:2.7.3-alpine
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/garden-co/jazz"
|
||||||
|
|
||||||
|
COPY ./dist /usr/share/caddy/
|
||||||
41
examples/book-shelf/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Book shelf example with Jazz and Next.js
|
||||||
|
|
||||||
|
This is an example of a book shelf where you can add and review books.
|
||||||
|
It shows you how to create and edit data, upload images, and share data publicly.
|
||||||
|
|
||||||
|
Live version: https://books-demo.jazz.tools
|
||||||
|
|
||||||
|
## Installing & running the example locally
|
||||||
|
|
||||||
|
(This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
|
||||||
|
|
||||||
|
Start by downloading the [jazz repository](https://github.com/garden-co/jazz):
|
||||||
|
```bash
|
||||||
|
npx degit gardencmp/jazz jazz
|
||||||
|
```
|
||||||
|
|
||||||
|
Go to the book-shelf example directory:
|
||||||
|
```bash
|
||||||
|
cd jazz/examples/book-shelf
|
||||||
|
```
|
||||||
|
|
||||||
|
Install and build dependencies:
|
||||||
|
```bash
|
||||||
|
pnpm i && npx turbo build
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the dev server:
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Questions / problems / feedback
|
||||||
|
|
||||||
|
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration: sync server
|
||||||
|
|
||||||
|
By default, the example app uses [Jazz Cloud](https://jazz.tools/cloud) (`wss://cloud.jazz.tools`) - so cross-device use, invites and collaboration should just work.
|
||||||
|
|
||||||
|
You can also run a local sync server by running `npx jazz-run sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?peer=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/components/JazzAndAuth.tsx](./src/components/JazzAndAuth.tsx).
|
||||||
56
examples/book-shelf/job-template.nomad
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
job "example-bookShelf$BRANCH_SUFFIX" {
|
||||||
|
region = "global"
|
||||||
|
datacenters = ["*"]
|
||||||
|
|
||||||
|
group "static" {
|
||||||
|
count = 4
|
||||||
|
|
||||||
|
network {
|
||||||
|
port "http" {
|
||||||
|
to = 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint {
|
||||||
|
attribute = "${node.class}"
|
||||||
|
operator = "="
|
||||||
|
value = "cloud"
|
||||||
|
}
|
||||||
|
|
||||||
|
spread {
|
||||||
|
attribute = "${node.datacenter}"
|
||||||
|
weight = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint {
|
||||||
|
distinct_hosts = true
|
||||||
|
}
|
||||||
|
|
||||||
|
task "server" {
|
||||||
|
driver = "docker"
|
||||||
|
|
||||||
|
config {
|
||||||
|
image = "$DOCKER_TAG"
|
||||||
|
ports = ["http"]
|
||||||
|
|
||||||
|
auth = {
|
||||||
|
username = "$DOCKER_USER"
|
||||||
|
password = "$DOCKER_PASSWORD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service {
|
||||||
|
tags = ["public"]
|
||||||
|
name = "example-bookShelf$BRANCH_SUFFIX"
|
||||||
|
port = "http"
|
||||||
|
provider = "consul"
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
cpu = 50 # MHz
|
||||||
|
memory = 50 # MB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# deploy bump 4
|
||||||
5
examples/book-shelf/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||||
4
examples/book-shelf/next.config.mjs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
29
examples/book-shelf/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "jazz-example-book-shelf",
|
||||||
|
"version": "0.1.29",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"format-and-lint": "biome check .",
|
||||||
|
"format-and-lint:fix": "biome check . --write",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"jazz-browser-media-images": "workspace:0.8.37",
|
||||||
|
"jazz-react": "workspace:0.8.37",
|
||||||
|
"jazz-tools": "workspace:0.8.37",
|
||||||
|
"next": "14.2.5",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.5.1",
|
||||||
|
"@types/react": "^18.2.19",
|
||||||
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"postcss": "^8.4.27",
|
||||||
|
"tailwindcss": "3.3.2",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
examples/book-shelf/postcss.config.mjs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('postcss-load-config').Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
139
examples/book-shelf/src/app/add/page.tsx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { useAccount } from "@/components/JazzAndAuth";
|
||||||
|
import RatingInput from "@/components/RatingInput";
|
||||||
|
import { BookReview, ListOfBookReviews } from "@/schema";
|
||||||
|
import { createImage } from "jazz-browser-media-images";
|
||||||
|
import { Group, ImageDefinition } from "jazz-tools";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { ChangeEvent, useState } from "react";
|
||||||
|
|
||||||
|
export default function AddBookReview() {
|
||||||
|
const { me } = useAccount();
|
||||||
|
|
||||||
|
const [title, setTitle] = useState("");
|
||||||
|
const [author, setAuthor] = useState("");
|
||||||
|
const [review, setReview] = useState("");
|
||||||
|
const [rating, setRating] = useState(0);
|
||||||
|
const [dateRead, setDateRead] = useState(
|
||||||
|
new Date().toISOString().split("T")[0],
|
||||||
|
);
|
||||||
|
const [coverImage, setCoverImage] = useState<ImageDefinition | undefined>();
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const onDateChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const date = new Date(event.currentTarget.value)
|
||||||
|
.toISOString()
|
||||||
|
.split("T")[0];
|
||||||
|
setDateRead(date);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.currentTarget.files?.[0];
|
||||||
|
const group = Group.create({ owner: me });
|
||||||
|
group.addMember("everyone", "reader");
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
createImage(file, { owner: group }).then((image) => {
|
||||||
|
setCoverImage(image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (!me?.profile) return;
|
||||||
|
|
||||||
|
const bookReview = BookReview.create(
|
||||||
|
{
|
||||||
|
title,
|
||||||
|
author,
|
||||||
|
review,
|
||||||
|
rating,
|
||||||
|
dateRead: new Date(dateRead),
|
||||||
|
cover: coverImage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
owner: me.profile._owner,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!me.profile.bookReviews) {
|
||||||
|
me.profile.bookReviews = ListOfBookReviews.create([], {
|
||||||
|
owner: me.profile._owner,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
me.profile.bookReviews.push(bookReview);
|
||||||
|
router.push("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="grid max-w-lg gap-8 py-8">
|
||||||
|
<h1 className="text-lg font-semibold text-black">Add book review</h1>
|
||||||
|
<form action="" className="grid gap-4" onSubmit={onSubmit}>
|
||||||
|
<label className="grid gap-1 text-sm text-gray-600">
|
||||||
|
Cover
|
||||||
|
<input type="file" onChange={onImageChange} />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="grid gap-1 text-sm text-gray-600">
|
||||||
|
Title
|
||||||
|
<input
|
||||||
|
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||||
|
type="text"
|
||||||
|
value={title}
|
||||||
|
required
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
></input>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="grid gap-1 text-sm text-gray-600">
|
||||||
|
Author
|
||||||
|
<input
|
||||||
|
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||||
|
type="text"
|
||||||
|
value={author}
|
||||||
|
required
|
||||||
|
onChange={(e) => setAuthor(e.target.value)}
|
||||||
|
></input>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="grid gap-1 text-sm text-gray-600">
|
||||||
|
Date read
|
||||||
|
<input
|
||||||
|
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||||
|
type="date"
|
||||||
|
value={dateRead}
|
||||||
|
required
|
||||||
|
onChange={onDateChange}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div className="grid gap-1 text-sm text-gray-600">
|
||||||
|
Rating
|
||||||
|
<RatingInput
|
||||||
|
value={rating}
|
||||||
|
onChange={(rating) => setRating(rating)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="grid gap-1 text-sm text-gray-600">
|
||||||
|
Review
|
||||||
|
<textarea
|
||||||
|
className="rounded border border-gray-300 px-2 py-1 shadow-sm"
|
||||||
|
value={review}
|
||||||
|
onChange={(e) => setReview(e.target.value)}
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
172
examples/book-shelf/src/app/book/[slug]/page.tsx
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { BookCover } from "@/components/BookCover";
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { useCoState } from "@/components/JazzAndAuth";
|
||||||
|
import Rating from "@/components/Rating";
|
||||||
|
import RatingInput from "@/components/RatingInput";
|
||||||
|
import { BookReview } from "@/schema";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { Group, ID } from "jazz-tools";
|
||||||
|
|
||||||
|
const BookReviewTitle = ({
|
||||||
|
bookReview,
|
||||||
|
readOnly,
|
||||||
|
}: {
|
||||||
|
bookReview: BookReview;
|
||||||
|
readOnly: boolean;
|
||||||
|
}) => {
|
||||||
|
const className = "font-serif text-2xl font-bold lg:text-4xl";
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
return <h1 className={className}>{bookReview.title}</h1>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
value={bookReview.title}
|
||||||
|
placeholder="Book title"
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||||
|
)}
|
||||||
|
onChange={(e) => (bookReview.title = e.target.value)}
|
||||||
|
></input>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BookReviewAuthor = ({
|
||||||
|
bookReview,
|
||||||
|
readOnly,
|
||||||
|
}: {
|
||||||
|
bookReview: BookReview;
|
||||||
|
readOnly: boolean;
|
||||||
|
}) => {
|
||||||
|
const className = "text-gray-700";
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
return <p className={className}>by {bookReview.author}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
value={bookReview.author}
|
||||||
|
placeholder="Author"
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||||
|
)}
|
||||||
|
onChange={(e) => (bookReview.author = e.target.value)}
|
||||||
|
></input>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BookReviewDateRead = ({
|
||||||
|
bookReview,
|
||||||
|
readOnly,
|
||||||
|
}: {
|
||||||
|
bookReview: BookReview;
|
||||||
|
readOnly: boolean;
|
||||||
|
}) => {
|
||||||
|
const className = "text-gray-700 max-w-[10rem]";
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
return (
|
||||||
|
bookReview.dateRead && (
|
||||||
|
<p className={className}>{bookReview.dateRead.toLocaleDateString()}</p>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={bookReview.dateRead?.toISOString().split("T")[0]}
|
||||||
|
placeholder="Date read"
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||||
|
)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const date = new Date(e.target.value);
|
||||||
|
bookReview.dateRead = date;
|
||||||
|
}}
|
||||||
|
></input>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BookReviewReview = ({
|
||||||
|
bookReview,
|
||||||
|
readOnly,
|
||||||
|
}: {
|
||||||
|
bookReview: BookReview;
|
||||||
|
readOnly: boolean;
|
||||||
|
}) => {
|
||||||
|
const className = "text-sm leading-relaxed text-gray-600";
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
if (bookReview.review) {
|
||||||
|
return <p className={className}>{bookReview.review}</p>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<textarea
|
||||||
|
value={bookReview.review}
|
||||||
|
placeholder="Write your review here..."
|
||||||
|
className={clsx(
|
||||||
|
className,
|
||||||
|
"w-full rounded border border-transparent px-2 py-1 hover:border-gray-300 hover:shadow-sm",
|
||||||
|
)}
|
||||||
|
onChange={(e) => (bookReview.review = e.target.value)}
|
||||||
|
></textarea>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BookReviewRating = ({
|
||||||
|
bookReview,
|
||||||
|
readOnly,
|
||||||
|
}: {
|
||||||
|
bookReview: BookReview;
|
||||||
|
readOnly: boolean;
|
||||||
|
}) => {
|
||||||
|
const className = "text-2xl sm:mx-0";
|
||||||
|
|
||||||
|
if (readOnly) {
|
||||||
|
return <Rating className={className} rating={bookReview.rating} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RatingInput
|
||||||
|
className={clsx(className, "p-2")}
|
||||||
|
onChange={(rating) => (bookReview.rating = rating)}
|
||||||
|
value={bookReview.rating}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page({ params }: { params: { slug: string } }) {
|
||||||
|
const bookReview = useCoState(BookReview, params.slug as ID<BookReview>);
|
||||||
|
|
||||||
|
if (!bookReview) return <></>;
|
||||||
|
|
||||||
|
const readOnly = !(bookReview._owner.castAs(Group).myRole() === "admin");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="grid gap-12 py-8">
|
||||||
|
<div className="flex flex-col gap-6 sm:flex-row md:gap-10">
|
||||||
|
<div className="w-[180px]">
|
||||||
|
<BookCover bookReview={bookReview} readOnly={readOnly} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="-mx-2 grid max-w-lg flex-1 gap-3 sm:mx-0">
|
||||||
|
<BookReviewTitle bookReview={bookReview} readOnly={readOnly} />
|
||||||
|
<BookReviewAuthor bookReview={bookReview} readOnly={readOnly} />
|
||||||
|
<BookReviewDateRead bookReview={bookReview} readOnly={readOnly} />
|
||||||
|
<BookReviewRating bookReview={bookReview} readOnly={readOnly} />
|
||||||
|
<BookReviewReview bookReview={bookReview} readOnly={readOnly} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
BIN
examples/book-shelf/src/app/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
13
examples/book-shelf/src/app/globals.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:focus {
|
||||||
|
@apply outline-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
@apply ring-2 ring-purple-600/75 dark:ring-2 dark:ring-white/75;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
examples/book-shelf/src/app/layout.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
import { JazzAndAuth } from "@/components/JazzAndAuth";
|
||||||
|
import { Nav } from "@/components/Nav";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { Fraunces } from "next/font/google";
|
||||||
|
|
||||||
|
const fraunces = Fraunces({
|
||||||
|
subsets: ["latin"],
|
||||||
|
display: "swap",
|
||||||
|
variable: "--font-fraunces",
|
||||||
|
});
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Jazz Book Shelf",
|
||||||
|
description: "Jazz Book Shelf",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={clsx(inter.className, fraunces.variable)}>
|
||||||
|
<JazzAndAuth>
|
||||||
|
<header>
|
||||||
|
<Nav />
|
||||||
|
</header>
|
||||||
|
<main>{children}</main>
|
||||||
|
</JazzAndAuth>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
examples/book-shelf/src/app/page.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { useAccount } from "@/components/JazzAndAuth";
|
||||||
|
import UserProfile from "@/components/UserProfile";
|
||||||
|
import { JazzAccount } from "@/schema";
|
||||||
|
import { ID } from "jazz-tools";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const { me } = useAccount();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="grid gap-12 py-8">
|
||||||
|
<UserProfile id={me?.id as ID<JazzAccount>} />
|
||||||
|
|
||||||
|
<label className="flex flex-wrap items-center gap-3">
|
||||||
|
Share your profile:
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full rounded border p-1"
|
||||||
|
value={`${window.location.origin}/user/${me?.id}`}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
examples/book-shelf/src/app/user/[slug]/page.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import UserProfile from "@/components/UserProfile";
|
||||||
|
import { JazzAccount } from "@/schema";
|
||||||
|
import { ID } from "jazz-tools";
|
||||||
|
|
||||||
|
export default function Page({ params }: { params: { slug: string } }) {
|
||||||
|
const { slug } = params;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container className="py-8">
|
||||||
|
<UserProfile id={slug as ID<JazzAccount>} />
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
134
examples/book-shelf/src/components/BookCover.tsx
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { useAccount } from "@/components/JazzAndAuth";
|
||||||
|
import PlusIcon from "@/components/icons/PlusIcon";
|
||||||
|
import { BookReview } from "@/schema";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { createImage } from "jazz-browser-media-images";
|
||||||
|
import { ProgressiveImg } from "jazz-react";
|
||||||
|
import { Group, ImageDefinition } from "jazz-tools";
|
||||||
|
import { ChangeEvent, useRef, useState } from "react";
|
||||||
|
|
||||||
|
const BookCoverContainer = ({
|
||||||
|
children,
|
||||||
|
className = "",
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={clsx("h-[240px] lg:h-[260px]", className)}>{children}</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const MockCover = ({ bookReview }: { bookReview: BookReview }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full flex-col items-center justify-center gap-3 rounded-l-sm rounded-r-md bg-gray-100 px-3 text-center shadow-lg">
|
||||||
|
<p className="font-medium">{bookReview.title}</p>
|
||||||
|
<p className="text-xs">{bookReview.author}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BookCoverReadOnly({
|
||||||
|
bookReview,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
bookReview: BookReview;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
if (bookReview.cover) {
|
||||||
|
return (
|
||||||
|
<BookCoverContainer className={className}>
|
||||||
|
<ProgressiveImg image={bookReview.cover}>
|
||||||
|
{({ src }) => (
|
||||||
|
<img
|
||||||
|
className="max-h-full max-w-full rounded-l-sm rounded-r-md shadow-lg"
|
||||||
|
src={src}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ProgressiveImg>
|
||||||
|
</BookCoverContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BookCoverContainer className={className}>
|
||||||
|
<MockCover bookReview={bookReview} />
|
||||||
|
</BookCoverContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BookCoverInput({ bookReview }: { bookReview: BookReview }) {
|
||||||
|
const { me } = useAccount();
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const onImageChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!me?.profile) return;
|
||||||
|
|
||||||
|
const file = event.currentTarget.files?.[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
createImage(file, { owner: me.profile._owner }).then((image) => {
|
||||||
|
bookReview.cover = image;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUploadClick = () => {
|
||||||
|
inputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const remove = () => {
|
||||||
|
bookReview.cover = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bookReview.cover) {
|
||||||
|
return (
|
||||||
|
<div className="group relative inline-block">
|
||||||
|
<BookCoverReadOnly
|
||||||
|
className="transition-opacity group-hover:opacity-40"
|
||||||
|
bookReview={bookReview}
|
||||||
|
/>
|
||||||
|
<div className="absolute left-0 top-0 hidden h-full w-full items-center justify-center rounded-l-sm rounded-r-md group-hover:flex">
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
type="button"
|
||||||
|
className="shadow"
|
||||||
|
onClick={remove}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BookCoverContainer className="flex w-[180px] flex-col justify-center rounded-l-sm rounded-r-md bg-gray-100 p-3 shadow-lg">
|
||||||
|
<button
|
||||||
|
className="flex h-full w-full flex-col items-center justify-center gap-3 text-gray-500 transition-colors hover:text-gray-600"
|
||||||
|
type="button"
|
||||||
|
onClick={onUploadClick}
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-10 w-10" />
|
||||||
|
Upload book cover
|
||||||
|
</button>
|
||||||
|
<label className="sr-only">
|
||||||
|
Cover
|
||||||
|
<input ref={inputRef} type="file" onChange={onImageChange} />
|
||||||
|
</label>
|
||||||
|
</BookCoverContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BookCover({
|
||||||
|
bookReview,
|
||||||
|
readOnly,
|
||||||
|
}: {
|
||||||
|
bookReview: BookReview;
|
||||||
|
readOnly?: boolean;
|
||||||
|
}) {
|
||||||
|
if (readOnly) return <BookCoverReadOnly bookReview={bookReview} />;
|
||||||
|
|
||||||
|
return <BookCoverInput bookReview={bookReview} />;
|
||||||
|
}
|
||||||
30
examples/book-shelf/src/components/BookReviewHeader.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { BookCover } from "@/components/BookCover";
|
||||||
|
import Rating from "@/components/Rating";
|
||||||
|
import RatingInput from "@/components/RatingInput";
|
||||||
|
import { BookReview } from "@/schema";
|
||||||
|
import { Group } from "jazz-tools";
|
||||||
|
|
||||||
|
export function BookReviewHeader({ bookReview }: { bookReview: BookReview }) {
|
||||||
|
const { title, author, rating, review, dateRead } = bookReview;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-5">
|
||||||
|
<div>
|
||||||
|
<h1 className="mb-1 font-serif text-2xl font-bold md:mb-3 lg:text-4xl">
|
||||||
|
{title}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-gray-500">by {author}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{bookReview._owner.castAs(Group).myRole() === "admin" ? (
|
||||||
|
<RatingInput
|
||||||
|
onChange={(rating) => (bookReview.rating = rating)}
|
||||||
|
value={rating}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Rating className="mx-auto text-2xl sm:mx-0" rating={rating} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
35
examples/book-shelf/src/components/BookReviewThumbnail.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { BookCover, BookCoverReadOnly } from "@/components/BookCover";
|
||||||
|
import { useCoState } from "@/components/JazzAndAuth";
|
||||||
|
import StarIcon from "@/components/icons/StarIcon";
|
||||||
|
import { ID } from "jazz-tools";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { BookReview } from "../schema";
|
||||||
|
|
||||||
|
export function BookReviewThumbnail({ id }: { id: ID<BookReview> }) {
|
||||||
|
const bookReview = useCoState(BookReview, id);
|
||||||
|
|
||||||
|
if (!bookReview) return <></>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="inline-flex shrink-0 gap-4 rounded border p-4 sm:block sm:space-y-6 sm:border-0 sm:p-0 md:w-[200px]">
|
||||||
|
<Link href={`/book/${bookReview.id}`} className="sm:block sm:flex-1">
|
||||||
|
<BookCoverReadOnly bookReview={bookReview} />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<Link href={`/book/${bookReview.id}`}>
|
||||||
|
<h2 className="mb-1 text-sm font-medium">{bookReview.title}</h2>
|
||||||
|
</Link>
|
||||||
|
<div className="mb-2 flex flex-col gap-2 text-sm text-gray-500 sm:flex-row sm:items-center">
|
||||||
|
<p>{bookReview.author}</p>
|
||||||
|
<div className="flex items-center gap-0.5 text-xs font-semibold leading-none">
|
||||||
|
<StarIcon className="-mt-px text-base text-yellow-400" />
|
||||||
|
{bookReview.rating}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
65
examples/book-shelf/src/components/Button.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
import Link from "next/link";
|
||||||
|
import type { ComponentProps } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
variant?: "primary" | "secondary" | "tertiary";
|
||||||
|
className?: string;
|
||||||
|
size?: "sm" | "md" | "lg";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnchorProps
|
||||||
|
extends Props,
|
||||||
|
React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||||
|
href: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ButtonProps = ComponentProps<"button"> & Props;
|
||||||
|
|
||||||
|
export function Button(props: AnchorProps | ButtonProps) {
|
||||||
|
const {
|
||||||
|
className: customClassName = "",
|
||||||
|
variant = "primary",
|
||||||
|
children,
|
||||||
|
size = "md",
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const variantClassNames = {
|
||||||
|
base: "inline-flex gap-2 items-center justify-center rounded-full overflow-hidden transition-colors",
|
||||||
|
primary:
|
||||||
|
"bg-purple-300 font-medium text-purple-950 px-4 py-2 rounded-full hover:bg-purple-200",
|
||||||
|
secondary:
|
||||||
|
"rounded-full bg-slate-100 font-medium text-slate-600 hover:bg-slate-200",
|
||||||
|
tertiary: "rounded-full bg-white text-purple-950 font-medium",
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeClassNames = {
|
||||||
|
sm: "py-1.5 px-3 text-sm",
|
||||||
|
md: "py-2 px-5",
|
||||||
|
lg: "py-2 md:py-2.5 px-6 md:text-lg",
|
||||||
|
};
|
||||||
|
|
||||||
|
const className = clsx(
|
||||||
|
customClassName,
|
||||||
|
variantClassNames.base,
|
||||||
|
variantClassNames[variant],
|
||||||
|
sizeClassNames[size],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!!(props as AnchorProps).href) {
|
||||||
|
const anchorProps = props as AnchorProps;
|
||||||
|
return (
|
||||||
|
<Link href={anchorProps.href} className={className}>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonProps = props as ButtonProps;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button {...buttonProps} className={className}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
examples/book-shelf/src/components/Container.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
export function Container({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={clsx("mx-auto max-w-4xl px-4", className)}>{children}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
examples/book-shelf/src/components/JazzAndAuth.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { JazzAccount } from "@/schema";
|
||||||
|
import { DemoAuthBasicUI, createJazzReactApp, useDemoAuth } from "jazz-react";
|
||||||
|
|
||||||
|
const Jazz = createJazzReactApp({
|
||||||
|
AccountSchema: JazzAccount,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { useAccount, useCoState } = Jazz;
|
||||||
|
|
||||||
|
export function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||||
|
const [auth, authState] = useDemoAuth();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Jazz.Provider
|
||||||
|
auth={auth}
|
||||||
|
// replace `you@example.com` with your email as a temporary API key
|
||||||
|
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Jazz.Provider>
|
||||||
|
{authState.state !== "signedIn" && (
|
||||||
|
<DemoAuthBasicUI appName="Jazz Book Shelf" state={authState} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
26
examples/book-shelf/src/components/Nav.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { Container } from "@/components/Container";
|
||||||
|
import { useAccount } from "@/components/JazzAndAuth";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export function Nav() {
|
||||||
|
const { me, logOut } = useAccount();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className="border-b py-3">
|
||||||
|
<Container className="flex items-center justify-between gap-12 text-sm">
|
||||||
|
<Link href="/" className="font-serif text-lg font-semibold">
|
||||||
|
Jazz Book Shelf
|
||||||
|
</Link>
|
||||||
|
<div className="flex items-center gap-4 text-sm">
|
||||||
|
<p>{me?.profile?.name}</p>
|
||||||
|
<Button variant="secondary" onClick={logOut}>
|
||||||
|
Log out
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
21
examples/book-shelf/src/components/Rating.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import StarIcon from "@/components/icons/StarIcon";
|
||||||
|
import StarOutlineIcon from "@/components/icons/StarOutlineIcon";
|
||||||
|
|
||||||
|
export default function Rating({
|
||||||
|
rating,
|
||||||
|
className = "",
|
||||||
|
}: {
|
||||||
|
rating?: number;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const max = 5;
|
||||||
|
const outline = max - (rating || 0);
|
||||||
|
return (
|
||||||
|
<div className={`inline-flex gap-0.5 text-yellow-400 ${className}`}>
|
||||||
|
{rating ? [...Array(rating)].map((x, i) => <StarIcon key={i} />) : <></>}
|
||||||
|
{[...Array(outline)].map((x, i) => (
|
||||||
|
<StarOutlineIcon key={i} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
47
examples/book-shelf/src/components/RatingInput.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import StarIcon from "@/components/icons/StarIcon";
|
||||||
|
import StarOutlineIcon from "@/components/icons/StarOutlineIcon";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
interface RatingInputProps {
|
||||||
|
value?: number;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RatingInput({
|
||||||
|
value = 0,
|
||||||
|
onChange,
|
||||||
|
className,
|
||||||
|
}: RatingInputProps) {
|
||||||
|
const handleChange = (newRating: number) => {
|
||||||
|
onChange(newRating > 5 ? 5 : newRating);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(className, "flex gap-0.5 text-2xl text-yellow-400")}>
|
||||||
|
{[...Array(5)].map((_, i) => {
|
||||||
|
return i < value ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="focus:outline-none"
|
||||||
|
onClick={() => handleChange(i + 1)}
|
||||||
|
key={i}
|
||||||
|
>
|
||||||
|
<StarIcon />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="focus:outline-none"
|
||||||
|
onClick={() => handleChange(i + 1)}
|
||||||
|
key={i}
|
||||||
|
>
|
||||||
|
<StarOutlineIcon />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
39
examples/book-shelf/src/components/UserProfile.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { BookReviewThumbnail } from "@/components/BookReviewThumbnail";
|
||||||
|
import { Button } from "@/components/Button";
|
||||||
|
import { useCoState } from "@/components/JazzAndAuth";
|
||||||
|
import { JazzAccount, JazzProfile, ListOfBookReviews } from "@/schema";
|
||||||
|
import { Group, ID } from "jazz-tools";
|
||||||
|
|
||||||
|
export default function UserProfile({ id }: { id: ID<JazzAccount> }) {
|
||||||
|
const user = useCoState(JazzAccount, id);
|
||||||
|
const profile = useCoState(JazzProfile, user?.profile?.id);
|
||||||
|
|
||||||
|
const bookReviews = useCoState(
|
||||||
|
ListOfBookReviews,
|
||||||
|
user?.profile?._refs.bookReviews?.id,
|
||||||
|
[{}],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="font-serif text-lg font-medium sm:text-2xl">
|
||||||
|
{profile?.name}'s book shelf
|
||||||
|
</h1>
|
||||||
|
{profile?._owner.castAs(Group).myRole() === "admin" && (
|
||||||
|
<Button href="/add" variant="primary">
|
||||||
|
Add book
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-4">
|
||||||
|
{bookReviews?.map((bookReview) => (
|
||||||
|
<BookReviewThumbnail key={bookReview.id} id={bookReview.id} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
examples/book-shelf/src/components/icons/PlusIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export default function PlusIcon({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className={className}
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M12 4.5v15m7.5-7.5h-15"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
18
examples/book-shelf/src/components/icons/StarIcon.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export default function StarIcon({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
className={className}
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
d="M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.006 5.404.434c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.434 2.082-5.005Z"
|
||||||
|
clipRule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
examples/book-shelf/src/components/icons/StarOutlineIcon.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export default function StarOutlineIcon({ className }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className={className}
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
examples/book-shelf/src/schema.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
Account,
|
||||||
|
CoList,
|
||||||
|
CoMap,
|
||||||
|
Encoders,
|
||||||
|
ImageDefinition,
|
||||||
|
Profile,
|
||||||
|
co,
|
||||||
|
} from "jazz-tools";
|
||||||
|
|
||||||
|
export class BookReview extends CoMap {
|
||||||
|
title = co.string;
|
||||||
|
author = co.string;
|
||||||
|
rating = co.number;
|
||||||
|
dateRead? = co.encoded(Encoders.Date);
|
||||||
|
review? = co.string;
|
||||||
|
cover? = co.ref(ImageDefinition, { optional: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ListOfBookReviews extends CoList.Of(co.ref(BookReview)) {}
|
||||||
|
|
||||||
|
/** The profile is an app-specific per-user public `CoMap`
|
||||||
|
* where you can store top-level objects for that user */
|
||||||
|
export class JazzProfile extends Profile {
|
||||||
|
bookReviews = co.ref(ListOfBookReviews);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JazzAccount extends Account {
|
||||||
|
profile = co.ref(JazzProfile);
|
||||||
|
|
||||||
|
/** The account migration is run on account creation and on every log-in.
|
||||||
|
* You can use it to set up the account root and any other initial CoValues you need.
|
||||||
|
*/
|
||||||
|
migrate(this: JazzAccount, creationProps?: { name: string }) {
|
||||||
|
super.migrate(creationProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
examples/book-shelf/tailwind.config.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
const colors = require("tailwindcss/colors");
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
content: [
|
||||||
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
serif: ["var(--font-fraunces)"],
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
purple: {
|
||||||
|
...colors.purple,
|
||||||
|
950: "#211f5a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
@@ -3,27 +3,26 @@
|
|||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
/* Bundler mode */
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"jsx": "preserve",
|
||||||
"jsx": "react-jsx",
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
/* Linting */
|
{
|
||||||
"strict": true,
|
"name": "next"
|
||||||
"noUnusedLocals": true,
|
}
|
||||||
"noUnusedParameters": true,
|
],
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
262
examples/chat-rn-clerk/CHANGELOG.md
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
# chat-rn-clerk
|
||||||
|
|
||||||
|
## 1.0.29
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-native@0.8.37
|
||||||
|
- jazz-react-native-auth-clerk@0.8.37
|
||||||
|
- jazz-tools@0.8.37
|
||||||
|
- jazz-react-native-media-images@0.8.28
|
||||||
|
|
||||||
|
## 1.0.28
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- c84764a: feat: added jazz-react-native-auth-clerk package
|
||||||
|
- Updated dependencies [c84764a]
|
||||||
|
- Updated dependencies [441fe27]
|
||||||
|
- jazz-react-native-auth-clerk@0.8.36
|
||||||
|
- jazz-react-native@0.8.36
|
||||||
|
- jazz-tools@0.8.36
|
||||||
|
- jazz-react-native-media-images@0.8.27
|
||||||
|
|
||||||
|
## 1.0.27
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [8b87117]
|
||||||
|
- jazz-tools@0.8.35
|
||||||
|
- jazz-react-auth-clerk@0.8.35
|
||||||
|
- jazz-react-native@0.8.35
|
||||||
|
- jazz-react-native-media-images@0.8.26
|
||||||
|
|
||||||
|
## 1.0.26
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [9ca25d1]
|
||||||
|
- jazz-react-auth-clerk@0.8.34
|
||||||
|
- jazz-react-native@0.8.34
|
||||||
|
- jazz-tools@0.8.34
|
||||||
|
- jazz-react-native-media-images@0.8.25
|
||||||
|
|
||||||
|
## 1.0.25
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.33
|
||||||
|
|
||||||
|
## 1.0.24
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [1a4bda0]
|
||||||
|
- Updated dependencies [df42b2b]
|
||||||
|
- jazz-react-auth-clerk@0.8.32
|
||||||
|
- jazz-tools@0.8.32
|
||||||
|
- jazz-react-native@0.8.32
|
||||||
|
- jazz-react-native-media-images@0.8.24
|
||||||
|
|
||||||
|
## 1.0.23
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.31
|
||||||
|
- jazz-react-native@0.8.31
|
||||||
|
- jazz-tools@0.8.31
|
||||||
|
- jazz-react-native-media-images@0.8.23
|
||||||
|
|
||||||
|
## 1.0.22
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.30
|
||||||
|
- jazz-react-native@0.8.30
|
||||||
|
- jazz-tools@0.8.30
|
||||||
|
- jazz-react-native-media-images@0.8.22
|
||||||
|
|
||||||
|
## 1.0.21
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-native@0.8.29
|
||||||
|
- jazz-react-auth-clerk@0.8.29
|
||||||
|
- jazz-tools@0.8.29
|
||||||
|
- jazz-react-native-media-images@0.8.21
|
||||||
|
|
||||||
|
## 1.0.20
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.28
|
||||||
|
- jazz-react-native@0.8.28
|
||||||
|
- jazz-tools@0.8.28
|
||||||
|
- jazz-react-native-media-images@0.8.20
|
||||||
|
|
||||||
|
## 1.0.19
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.27
|
||||||
|
- jazz-react-native@0.8.27
|
||||||
|
- jazz-tools@0.8.27
|
||||||
|
- jazz-react-native-media-images@0.8.19
|
||||||
|
|
||||||
|
## 1.0.18
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.26
|
||||||
|
|
||||||
|
## 1.0.17
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.24
|
||||||
|
|
||||||
|
## 1.0.16
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [d348c2d]
|
||||||
|
- Updated dependencies [6902b5b]
|
||||||
|
- Updated dependencies [1a0cd3d]
|
||||||
|
- jazz-tools@0.8.23
|
||||||
|
- jazz-react-auth-clerk@0.8.23
|
||||||
|
- jazz-react-native@0.8.23
|
||||||
|
- jazz-react-native-media-images@0.8.18
|
||||||
|
|
||||||
|
## 1.0.15
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.22
|
||||||
|
|
||||||
|
## 1.0.14
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [149ca97]
|
||||||
|
- jazz-tools@0.8.21
|
||||||
|
- jazz-react-auth-clerk@0.8.21
|
||||||
|
- jazz-react-native@0.8.21
|
||||||
|
- jazz-react-native-media-images@0.8.17
|
||||||
|
|
||||||
|
## 1.0.13
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [3ef3ff3]
|
||||||
|
- jazz-react-native-media-images@0.8.16
|
||||||
|
- jazz-react-native@0.8.20
|
||||||
|
- jazz-react-auth-clerk@0.8.20
|
||||||
|
|
||||||
|
## 1.0.12
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.19
|
||||||
|
- jazz-react-native@0.8.19
|
||||||
|
- jazz-tools@0.8.19
|
||||||
|
- jazz-react-native-media-images@0.8.15
|
||||||
|
|
||||||
|
## 1.0.11
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.18
|
||||||
|
- jazz-react-native@0.8.18
|
||||||
|
- jazz-tools@0.8.18
|
||||||
|
- jazz-react-native-media-images@0.8.14
|
||||||
|
|
||||||
|
## 1.0.10
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.17
|
||||||
|
- jazz-react-native@0.8.17
|
||||||
|
- jazz-tools@0.8.17
|
||||||
|
- jazz-react-native-media-images@0.8.13
|
||||||
|
|
||||||
|
## 1.0.9
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.16
|
||||||
|
- jazz-react-native@0.8.16
|
||||||
|
- jazz-tools@0.8.16
|
||||||
|
- jazz-react-native-media-images@0.8.12
|
||||||
|
|
||||||
|
## 1.0.8
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [cce679b]
|
||||||
|
- Updated dependencies [221c58f]
|
||||||
|
- jazz-tools@0.8.15
|
||||||
|
- jazz-react-auth-clerk@0.8.15
|
||||||
|
- jazz-react-native@0.8.15
|
||||||
|
- jazz-react-native-media-images@0.8.11
|
||||||
|
|
||||||
|
## 1.0.7
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [36273b3]
|
||||||
|
- jazz-tools@0.8.14
|
||||||
|
- jazz-react-auth-clerk@0.8.14
|
||||||
|
- jazz-react-native@0.8.14
|
||||||
|
- jazz-react-native-media-images@0.8.10
|
||||||
|
|
||||||
|
## 1.0.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [fd011d7]
|
||||||
|
- jazz-tools@0.8.13
|
||||||
|
- jazz-react-auth-clerk@0.8.13
|
||||||
|
- jazz-react-native@0.8.13
|
||||||
|
- jazz-react-native-media-images@0.8.9
|
||||||
|
|
||||||
|
## 1.0.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.12
|
||||||
|
- jazz-react-native@0.8.12
|
||||||
|
- jazz-tools@0.8.12
|
||||||
|
- jazz-react-native-media-images@0.8.8
|
||||||
|
|
||||||
|
## 1.0.4
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-auth-clerk@0.8.11
|
||||||
|
- jazz-react-native@0.8.11
|
||||||
|
- jazz-tools@0.8.11
|
||||||
|
- jazz-react-native-media-images@0.8.7
|
||||||
|
|
||||||
|
## 1.0.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- b7639cf: feat(react-native): replaced react-native-mmkv with expo-secure-store and initialize it by default as kvStore in createJazzRNApp() (BREAKING)
|
||||||
|
- Updated dependencies [b7639cf]
|
||||||
|
- jazz-react-native@0.8.8
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [32b05b6]
|
||||||
|
- jazz-react-native-media-images@0.8.6
|
||||||
|
- jazz-react-native@0.8.7
|
||||||
|
- jazz-react-auth-clerk@0.8.7
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- jazz-react-native@0.8.6
|
||||||
|
- jazz-react-auth-clerk@0.8.6
|
||||||
@@ -11,13 +11,13 @@ pnpm i
|
|||||||
pnpm run build
|
pnpm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Inside the `examples/chat-rn-expo-clerk` Directory
|
### 2. Inside the `examples/chat-rn-clerk` Directory
|
||||||
|
|
||||||
Next, navigate to the specific example project and run the following commands:
|
Next, navigate to the specific example project and run the following commands:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm expo prebuild
|
pnpm expo prebuild
|
||||||
pnpx pod-install
|
npx pod-install
|
||||||
pnpm expo run:ios
|
pnpm expo run:ios
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "jazz-chat-rn-expo-clerk",
|
"name": "jazz-chat-rn-clerk",
|
||||||
"scheme": "jazz-chat-rn-expo-clerk",
|
"scheme": "jazz-chat-rn-clerk",
|
||||||
"slug": "jazz-chat-rn-expo-clerk",
|
"slug": "jazz-chat-rn-clerk",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/images/icon.png",
|
"icon": "./assets/images/icon.png",
|
||||||
@@ -23,18 +23,21 @@
|
|||||||
},
|
},
|
||||||
"package": "com.jazz.chatrnclerk"
|
"package": "com.jazz.chatrnclerk"
|
||||||
},
|
},
|
||||||
"newArchEnabled": true,
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
[
|
||||||
|
"expo-build-properties",
|
||||||
|
{
|
||||||
|
"ios": {
|
||||||
|
"newArchEnabled": true
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
"newArchEnabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"expo-secure-store",
|
"expo-secure-store",
|
||||||
"expo-font",
|
"expo-font",
|
||||||
"expo-router",
|
"expo-router"
|
||||||
"expo-sqlite",
|
|
||||||
[
|
|
||||||
"expo-image-picker",
|
|
||||||
{
|
|
||||||
"photosPermission": "The app accesses your photos to let you share them with your friends."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
"extra": {
|
"extra": {
|
||||||
"eas": {
|
"eas": {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Redirect, Stack } from "expo-router";
|
import { Redirect, Stack } from "expo-router";
|
||||||
import { useIsAuthenticated } from "jazz-expo";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { useAuth } from "../../src/auth-context";
|
||||||
|
|
||||||
export default function HomeLayout() {
|
export default function HomeLayout() {
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return <Redirect href={"/chat"} />;
|
return <Redirect href={"/chat"} />;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Redirect, Stack } from "expo-router";
|
import { Redirect, Stack } from "expo-router";
|
||||||
import { useIsAuthenticated } from "jazz-expo";
|
import { useAuth } from "../../src/auth-context";
|
||||||
|
|
||||||
export default function UnAuthenticatedLayout() {
|
export default function UnAuthenticatedLayout() {
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return <Redirect href={"/chat"} />;
|
return <Redirect href={"/chat"} />;
|
||||||
@@ -28,7 +28,7 @@ const SignInWithOAuth = () => {
|
|||||||
const { createdSessionId, signIn, signUp, setActive } =
|
const { createdSessionId, signIn, signUp, setActive } =
|
||||||
await startOAuthFlow({
|
await startOAuthFlow({
|
||||||
redirectUrl: Linking.createURL("/", {
|
redirectUrl: Linking.createURL("/", {
|
||||||
scheme: "jazz-chat-rn-expo-clerk",
|
scheme: "jazz-chat-rn-clerk",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import "../global.css";
|
|
||||||
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
|
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
|
||||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
|
||||||
import { useFonts } from "expo-font";
|
import { useFonts } from "expo-font";
|
||||||
import { Slot } from "expo-router";
|
import { Slot } from "expo-router";
|
||||||
import * as SplashScreen from "expo-splash-screen";
|
import * as SplashScreen from "expo-splash-screen";
|
||||||
@@ -34,11 +32,7 @@ export default function RootLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ClerkProvider
|
<ClerkProvider tokenCache={tokenCache} publishableKey={publishableKey}>
|
||||||
tokenCache={tokenCache}
|
|
||||||
publishableKey={publishableKey}
|
|
||||||
__experimental_resourceCache={secureStore}
|
|
||||||
>
|
|
||||||
<ClerkLoaded>
|
<ClerkLoaded>
|
||||||
<JazzAndAuth>
|
<JazzAndAuth>
|
||||||
<Slot />
|
<Slot />
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
|
import { useAccount, useCoState } from "@/src/jazz";
|
||||||
import { Chat, Message } from "@/src/schema";
|
import { Chat, Message } from "@/src/schema";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useFocusEffect, useNavigation } from "@react-navigation/native";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import * as Clipboard from "expo-clipboard";
|
import * as Clipboard from "expo-clipboard";
|
||||||
import * as ImagePicker from "expo-image-picker";
|
|
||||||
import { useLocalSearchParams } from "expo-router";
|
import { useLocalSearchParams } from "expo-router";
|
||||||
import { useAccount, useCoState } from "jazz-expo";
|
|
||||||
import { ProgressiveImg } from "jazz-expo";
|
|
||||||
import { createImage } from "jazz-react-native-media-images";
|
|
||||||
import { Group, ID } from "jazz-tools";
|
import { Group, ID } from "jazz-tools";
|
||||||
import { useEffect, useLayoutEffect, useState } from "react";
|
import { useEffect, useLayoutEffect, useState } from "react";
|
||||||
import React, {
|
import React, {
|
||||||
@@ -19,8 +16,6 @@ import React, {
|
|||||||
KeyboardAvoidingView,
|
KeyboardAvoidingView,
|
||||||
TextInput,
|
TextInput,
|
||||||
Button,
|
Button,
|
||||||
Image,
|
|
||||||
ActivityIndicator,
|
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
|
||||||
export default function Conversation() {
|
export default function Conversation() {
|
||||||
@@ -28,9 +23,8 @@ export default function Conversation() {
|
|||||||
const { me } = useAccount();
|
const { me } = useAccount();
|
||||||
const [chat, setChat] = useState<Chat>();
|
const [chat, setChat] = useState<Chat>();
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
const loadedChat = useCoState(Chat, chat?.id, { resolve: { $each: true } });
|
const loadedChat = useCoState(Chat, chat?.id, [{}]);
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chat) return;
|
if (chat) return;
|
||||||
@@ -71,7 +65,7 @@ export default function Conversation() {
|
|||||||
|
|
||||||
const loadChat = async (chatId: ID<Chat>) => {
|
const loadChat = async (chatId: ID<Chat>) => {
|
||||||
try {
|
try {
|
||||||
const chat = await Chat.load(chatId, me);
|
const chat = await Chat.load(chatId, me, []);
|
||||||
setChat(chat);
|
setChat(chat);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error loading chat", error);
|
console.log("Error loading chat", error);
|
||||||
@@ -87,32 +81,6 @@ export default function Conversation() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImageUpload = async () => {
|
|
||||||
try {
|
|
||||||
const result = await ImagePicker.launchImageLibraryAsync({
|
|
||||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
||||||
base64: true,
|
|
||||||
quality: 0.7,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.canceled && result.assets[0].base64 && chat) {
|
|
||||||
setIsUploading(true);
|
|
||||||
const base64Uri = `data:image/jpeg;base64,${result.assets[0].base64}`;
|
|
||||||
|
|
||||||
const image = await createImage(base64Uri, {
|
|
||||||
owner: chat._owner,
|
|
||||||
maxSize: 2048,
|
|
||||||
});
|
|
||||||
|
|
||||||
chat.push(Message.create({ text: "", image }, { owner: chat._owner }));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
Alert.alert("Error", "Failed to upload image");
|
|
||||||
} finally {
|
|
||||||
setIsUploading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderMessageItem = ({ item }: { item: Message }) => {
|
const renderMessageItem = ({ item }: { item: Message }) => {
|
||||||
const isMe = item._edits.text.by?.isMe;
|
const isMe = item._edits.text.by?.isMe;
|
||||||
return (
|
return (
|
||||||
@@ -138,35 +106,22 @@ export default function Conversation() {
|
|||||||
isMe ? "flex-row" : "flex-row",
|
isMe ? "flex-row" : "flex-row",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.image && (
|
<Text
|
||||||
<ProgressiveImg image={item.image} maxWidth={1024}>
|
className={clsx(
|
||||||
{({ src, res, originalSize }) => (
|
!isMe ? "text-black" : "text-gray-200",
|
||||||
<Image
|
`text-md max-w-[85%]`,
|
||||||
source={{ uri: src }}
|
)}
|
||||||
className="w-48 h-48 rounded-lg mb-2"
|
>
|
||||||
resizeMode="cover"
|
{item.text}
|
||||||
/>
|
</Text>
|
||||||
)}
|
|
||||||
</ProgressiveImg>
|
|
||||||
)}
|
|
||||||
{item.text && (
|
|
||||||
<Text
|
|
||||||
className={clsx(
|
|
||||||
!isMe ? "text-black" : "text-gray-200",
|
|
||||||
`text-md max-w-[85%]`,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{item.text}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<Text
|
<Text
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"text-[10px] text-right ml-2",
|
"text-[10px] text-right ml-2",
|
||||||
!isMe ? "mt-2 text-gray-500" : "mt-1 text-gray-200",
|
!isMe ? "mt-2 text-gray-500" : "mt-1 text-gray-200",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item._edits.text.madeAt?.getHours().toString().padStart(2, "0")}:
|
{item._edits.text.madeAt.getHours()}:
|
||||||
{item._edits.text.madeAt?.getMinutes().toString().padStart(2, "0")}
|
{item._edits.text.madeAt.getMinutes()}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -192,17 +147,6 @@ export default function Conversation() {
|
|||||||
className="p-3 bg-white border-t border-gray-300"
|
className="p-3 bg-white border-t border-gray-300"
|
||||||
>
|
>
|
||||||
<SafeAreaView className="flex-row items-center gap-2">
|
<SafeAreaView className="flex-row items-center gap-2">
|
||||||
<TouchableOpacity
|
|
||||||
onPress={handleImageUpload}
|
|
||||||
disabled={isUploading}
|
|
||||||
className="h-10 w-10 items-center justify-center"
|
|
||||||
>
|
|
||||||
{isUploading ? (
|
|
||||||
<ActivityIndicator size="small" color="#0000ff" />
|
|
||||||
) : (
|
|
||||||
<Text className="text-2xl">🖼️</Text>
|
|
||||||
)}
|
|
||||||
</TouchableOpacity>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
className="flex-1 rounded-full h-10 px-4 bg-gray-100 border border-gray-300 focus:border-blue-500 focus:bg-white"
|
className="flex-1 rounded-full h-10 px-4 bg-gray-100 border border-gray-300 focus:border-blue-500 focus:bg-white"
|
||||||
value={message}
|
value={message}
|
||||||
@@ -11,7 +11,7 @@ import React, {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
|
|
||||||
import { useUser } from "@clerk/clerk-expo";
|
import { useUser } from "@clerk/clerk-expo";
|
||||||
import { useAccount } from "jazz-expo";
|
import { useAccount } from "../../src/jazz";
|
||||||
import { Chat } from "../../src/schema";
|
import { Chat } from "../../src/schema";
|
||||||
|
|
||||||
export default function ChatScreen() {
|
export default function ChatScreen() {
|
||||||
@@ -20,15 +20,10 @@ export default function ChatScreen() {
|
|||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
|
||||||
function handleLogOut() {
|
|
||||||
logOut();
|
|
||||||
router.navigate("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
headerTitle: "Chat",
|
headerTitle: "Chat",
|
||||||
headerRight: () => <Button onPress={handleLogOut} title="Logout" />,
|
headerRight: () => <Button onPress={logOut} title="Logout" />,
|
||||||
});
|
});
|
||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 313 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
@@ -2,5 +2,6 @@ module.exports = function (api) {
|
|||||||
api.cache(true);
|
api.cache(true);
|
||||||
return {
|
return {
|
||||||
presets: ["babel-preset-expo"],
|
presets: ["babel-preset-expo"],
|
||||||
|
plugins: ["nativewind/babel"],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
// Learn more https://docs.expo.dev/guides/monorepos
|
// Learn more https://docs.expo.dev/guides/monorepos
|
||||||
const { getDefaultConfig } = require("expo/metro-config");
|
const { getDefaultConfig } = require("expo/metro-config");
|
||||||
const { withNativeWind } = require("nativewind/metro");
|
|
||||||
const { FileStore } = require("metro-cache");
|
const { FileStore } = require("metro-cache");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
@@ -19,6 +18,7 @@ config.resolver.nodeModulesPaths = [
|
|||||||
path.resolve(workspaceRoot, "node_modules"),
|
path.resolve(workspaceRoot, "node_modules"),
|
||||||
];
|
];
|
||||||
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
|
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
|
||||||
|
config.resolver.unstable_enablePackageExports = true;
|
||||||
config.resolver.requireCycleIgnorePatterns = [
|
config.resolver.requireCycleIgnorePatterns = [
|
||||||
/(^|\/|\\)node_modules($|\/|\\)/,
|
/(^|\/|\\)node_modules($|\/|\\)/,
|
||||||
/(^|\/|\\)packages($|\/|\\)/,
|
/(^|\/|\\)packages($|\/|\\)/,
|
||||||
@@ -31,5 +31,4 @@ config.cacheStores = [
|
|||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
// module.exports = config;
|
module.exports = config;
|
||||||
module.exports = withNativeWind(config, { input: "./global.css" });
|
|
||||||
77
examples/chat-rn-clerk/package.json
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"name": "chat-rn-clerk",
|
||||||
|
"main": "index.js",
|
||||||
|
"version": "1.0.29",
|
||||||
|
"scripts": {
|
||||||
|
"build": "expo export -p ios",
|
||||||
|
"start": "expo start",
|
||||||
|
"format-and-lint": "biome check .",
|
||||||
|
"format-and-lint:fix": "biome check . --write",
|
||||||
|
"android": "expo run:android",
|
||||||
|
"ios": "expo run:ios",
|
||||||
|
"web": "expo start --web",
|
||||||
|
"test": "jest --watchAll"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "jest-expo"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||||
|
"@bam.tech/react-native-image-resizer": "^3.0.11",
|
||||||
|
"@clerk/clerk-expo": "^2.2.21",
|
||||||
|
"@expo/vector-icons": "^14.0.2",
|
||||||
|
"@react-native-community/netinfo": "^11.4.1",
|
||||||
|
"@react-navigation/native": "^7.0.13",
|
||||||
|
"@react-navigation/native-stack": "^7.1.14",
|
||||||
|
"base-64": "^1.0.0",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"expo": "^52.0.0",
|
||||||
|
"expo-build-properties": "~0.13.1",
|
||||||
|
"expo-clipboard": "~7.0.0",
|
||||||
|
"expo-constants": "~17.0.3",
|
||||||
|
"expo-crypto": "~14.0.1",
|
||||||
|
"expo-dev-client": "~5.0.5",
|
||||||
|
"expo-file-system": "^18.0.4",
|
||||||
|
"expo-font": "~13.0.1",
|
||||||
|
"expo-linking": "~7.0.3",
|
||||||
|
"expo-router": "~4.0.11",
|
||||||
|
"expo-secure-store": "~14.0.0",
|
||||||
|
"expo-splash-screen": "~0.29.16",
|
||||||
|
"expo-status-bar": "~2.0.0",
|
||||||
|
"expo-system-ui": "~4.0.5",
|
||||||
|
"expo-web-browser": "~14.0.1",
|
||||||
|
"jazz-react-native": "workspace:*",
|
||||||
|
"jazz-react-native-auth-clerk": "workspace:*",
|
||||||
|
"jazz-react-native-media-images": "workspace:*",
|
||||||
|
"jazz-tools": "workspace:*",
|
||||||
|
"nativewind": "^2.0.11",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-native": "~0.76.3",
|
||||||
|
"react-native-fetch-api": "^3.0.0",
|
||||||
|
"react-native-gesture-handler": "~2.20.2",
|
||||||
|
"react-native-get-random-values": "^1.11.0",
|
||||||
|
"react-native-polyfill-globals": "^3.1.0",
|
||||||
|
"react-native-quick-base64": "^2.1.2",
|
||||||
|
"react-native-reanimated": "~3.16.3",
|
||||||
|
"react-native-safe-area-context": "4.12.0",
|
||||||
|
"react-native-screens": "4.1.0",
|
||||||
|
"react-native-url-polyfill": "^2.0.0",
|
||||||
|
"react-native-web": "~0.19.13",
|
||||||
|
"text-encoding": "^0.7.0",
|
||||||
|
"web-streams-polyfill": "^3.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.20.0",
|
||||||
|
"@types/jest": "^29.5.3",
|
||||||
|
"@types/react": "^18.3.12",
|
||||||
|
"@types/react-test-renderer": "^18.0.7",
|
||||||
|
"jest": "^29.2.1",
|
||||||
|
"jest-expo": "~52.0.2",
|
||||||
|
"react-test-renderer": "18.2.0",
|
||||||
|
"tailwindcss": "3.3.2",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
8
examples/chat-rn-clerk/polyfills.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import "react-native-polyfill-globals/auto";
|
||||||
|
import "@azure/core-asynciterator-polyfill";
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
import { polyfillGlobal } from "react-native/Libraries/Utilities/PolyfillFunctions";
|
||||||
|
import { ReadableStream } from "web-streams-polyfill/ponyfill/es6";
|
||||||
|
|
||||||
|
polyfillGlobal("Buffer", () => Buffer);
|
||||||
|
polyfillGlobal("ReadableStream", () => ReadableStream);
|
||||||
62
examples/chat-rn-clerk/src/auth-context.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { useClerk, useUser } from "@clerk/clerk-expo";
|
||||||
|
import { useJazzClerkAuth } from "jazz-react-native-auth-clerk";
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
PropsWithChildren,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { Text, View } from "react-native";
|
||||||
|
import { Jazz, kvStore } from "./jazz";
|
||||||
|
|
||||||
|
const AuthContext = createContext<{
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
}>({
|
||||||
|
isAuthenticated: false,
|
||||||
|
isLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
return useContext(AuthContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JazzAndAuth({ children }: PropsWithChildren) {
|
||||||
|
const { isSignedIn, isLoaded: isClerkLoaded } = useUser();
|
||||||
|
const clerk = useClerk();
|
||||||
|
const [auth, state] = useJazzClerkAuth(clerk, kvStore);
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSignedIn && isClerkLoaded && auth) {
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
} else {
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
}
|
||||||
|
}, [isSignedIn, isClerkLoaded, auth]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider
|
||||||
|
value={{ isAuthenticated, isLoading: !isClerkLoaded || !auth }}
|
||||||
|
>
|
||||||
|
{state?.errors?.length > 0 &&
|
||||||
|
state.errors.map((error) => (
|
||||||
|
<View key={error}>
|
||||||
|
<Text style={{ color: "red" }}>{error}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
{auth && clerk.user ? (
|
||||||
|
<Jazz.Provider
|
||||||
|
auth={auth}
|
||||||
|
peer="wss://cloud.jazz.tools/?key=chat-rn-clerk-example-jazz@garden.co"
|
||||||
|
storage={undefined}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Jazz.Provider>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
4
examples/chat-rn-clerk/src/jazz.tsx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { createJazzRNApp } from "jazz-react-native";
|
||||||
|
|
||||||
|
export const Jazz = createJazzRNApp();
|
||||||
|
export const { useAccount, useCoState, useAcceptInvite, kvStore } = Jazz;
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// NOTE: Update this to include the paths to all of your component files.
|
|
||||||
content: [
|
content: [
|
||||||
"./app/**/*.{js,jsx,ts,tsx}",
|
"./app/**/*.{js,jsx,ts,tsx}",
|
||||||
"./components/**/*.{js,jsx,ts,tsx}",
|
"./components/**/*.{js,jsx,ts,tsx}",
|
||||||
"./src/**/*.{js,jsx,ts,tsx}",
|
"./src/**/*.{js,jsx,ts,tsx}",
|
||||||
],
|
],
|
||||||
presets: [require("nativewind/preset")],
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||