Compare commits

..

43 Commits

Author SHA1 Message Date
Marina Orlova
0589bf275f Add config for cojson 2025-01-08 10:37:12 +01:00
Marina Orlova
2f6ca4cdf4 Sync if not found in peer 2025-01-07 00:58:39 +01:00
Marina Orlova
cfbe745de3 add todo 2025-01-06 13:42:23 +01:00
Marina Orlova
7b96fd719b Fix peers bug 2025-01-05 02:14:56 +01:00
Marina Orlova
c391180093 Add queue runner 2025-01-05 01:12:32 +01:00
Marina Orlova
8ea0858571 Fix build errors 2025-01-04 12:18:37 +01:00
Marina Orlova
d1c0981024 add anaware peers 2025-01-04 11:52:06 +01:00
Marina Orlova
bb0158ec51 Fix dependencies not loaing from another peer 2025-01-03 15:53:22 +01:00
Marina Orlova
8a40c02c52 Fix unloading dependencies 2025-01-02 23:58:43 +01:00
Marina Orlova
b95937511f Send content fix 2025-01-01 22:21:09 +01:00
Marina Orlova
1967c736c5 Add dependency service 2024-12-31 21:46:43 +01:00
Marina Orlova
b84edecb50 Fix data handler for dependencies 2024-12-31 14:34:08 +01:00
Marina Orlova
5631d53e3f Order initial sync messages by dependencies 2024-12-31 01:07:59 +01:00
Marina Orlova
244fd84a88 Send dependencies when unknown covalue requested 2024-12-30 21:55:36 +01:00
Marina Orlova
d4e93afdf9 dont sync from the data action 2024-12-29 23:04:55 +01:00
Marina Orlova
26d4fa985c SQLite selects protocol conditionally 2024-12-29 22:41:57 +01:00
Marina Orlova
cefc6e27de Copy entry upload state when entry is copied 2024-12-29 21:04:29 +01:00
Marina Orlova
452c284a01 Fix sync bugs 2024-12-28 23:10:06 +01:00
Marina Orlova
191a7f33b1 Refactor storage and storage-sqlite 2024-12-27 22:19:15 +01:00
Marina Orlova
d7be246f75 tiny fixes 2024-12-27 18:20:13 +01:00
Marina Orlova
5c8717543c tweaks 2024-12-26 23:11:59 +01:00
Marina Orlova
02cd6fe4b7 Fix Imports 2024-12-26 22:17:30 +01:00
Marina Orlova
526a26a39d Delete subscriptionManager 2024-12-26 21:34:00 +01:00
Marina Orlova
60adbffc26 Refactor sync.ts 2024-12-26 21:30:34 +01:00
Marina Orlova
d8cabe3fa6 Introduce message handlers 2024-12-26 15:14:44 +01:00
Marina Orlova
928ac67a06 Tweaks 2024-12-23 23:11:24 +01:00
Marina Orlova
0458e12721 Refactor handle pull 2024-12-23 20:56:57 +01:00
Marina Orlova
df59b53000 Move all response logic into PeerOperations 2024-12-23 17:54:08 +01:00
Marina Orlova
891baf2053 Move load logic into sync 2024-12-23 11:52:02 +01:00
Marina Orlova
3a55c8a627 Move getServerAndStorage to Peers 2024-12-22 20:59:26 +01:00
Marina Orlova
5a9770242f Create Peers class 2024-12-22 20:48:50 +01:00
Marina Orlova
f6bbe18a53 Rearrange logic between local node and sync 2024-12-21 15:15:52 +01:00
Marina Orlova
0712546277 Move peers tracking into node 2024-12-20 23:28:56 +01:00
Marina Orlova
14b70aa445 add peer ops and refactor sync to make use of them 2024-12-20 22:48:13 +01:00
Marina Orlova
47275a1340 Track upload state in coValueState 2024-12-19 20:03:39 +01:00
Marina Orlova
ca54b4c1a8 tweaks 2024-12-15 22:42:03 +01:00
Marina Orlova
86a2c914d3 implement all actions 2024-12-15 09:05:53 +01:00
Marina Orlova
d9c250386e replace content with push 2024-12-15 02:20:03 +01:00
Marina Orlova
73d5f18cb8 handlePush instead handleLoad 2024-12-15 01:56:53 +01:00
Marina Orlova
84e17a9189 load from peers with pull 2024-12-14 23:57:11 +01:00
Marina Orlova
0e8b04579a rebase storage on new actions 2024-12-14 23:16:52 +01:00
Marina Orlova
56a967cce6 Get rid of peer states 2024-12-14 22:06:13 +01:00
Marina Orlova
f823b2a307 get rid of statefullness 2024-12-14 21:18:51 +01:00
728 changed files with 15689 additions and 37153 deletions

View File

@@ -1,7 +0,0 @@
---
"chat-rn-clerk": patch
"jazz-react-native": patch
"jazz-example-chat": patch
---
Fix image handling in react-native

View File

@@ -0,0 +1,5 @@
---
"cojson": patch
---
Apply time travel when checking the roles on a parent group

View File

@@ -1,5 +0,0 @@
---
"cojson": patch
---
Optimize queue management

View File

@@ -1,52 +0,0 @@
name: Build Starters
on:
push:
branches: [ "main" ]
jobs:
build-starters:
runs-on: ubuntu-latest
strategy:
matrix:
starter: [
"react-demo-auth-tailwind",
]
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Enable corepack
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
run: |
pnpm install
pnpm turbo build;
working-directory: ./starters/${{ matrix.starter }}

View File

@@ -13,7 +13,7 @@ jobs:
continue-on-error: true
strategy:
matrix:
project: ["tests/e2e", "examples/chat", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "examples/onboarding", "starters/react-demo-auth-tailwind"]
project: ["tests/e2e", "examples/chat", "examples/music-player", "examples/pets", "examples/onboarding"]
steps:
- uses: actions/checkout@v3
@@ -45,6 +45,10 @@ jobs:
- 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
run: pnpm turbo build
working-directory: ./${{ matrix.project }}

View File

@@ -4,13 +4,6 @@ on:
push:
branches:
- main
workflow_dispatch:
inputs:
debug_enabled:
type: boolean
description: "Run tmate session for debugging"
required: false
default: false
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -55,11 +48,4 @@ jobs:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_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
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -41,7 +41,7 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Pnpm Build
run: pnpm turbo build --filter="./packages/*"
run: pnpm turbo build
- name: Unit Tests
run: pnpm test:ci

1
.gitignore vendored
View File

@@ -5,7 +5,6 @@ docsTmp
.DS_Store
.turbo
coverage
.direnv
# Next.js
**/.next

View File

@@ -1 +1 @@
22
20

1
.nvmrc
View File

@@ -1 +0,0 @@
22

View File

@@ -25,53 +25,7 @@
"linter": {
"enabled": false,
"rules": {
"recommended": true,
"correctness": {
"useImportExtensions": {
"level": "error",
"options": {
"suggestedExtensions": {
"ts": {
"module": "js",
"component": "jsx"
}
}
}
}
}
"recommended": true
}
},
"overrides": [
{
"include": ["packages/**/src/**"],
"linter": {
"enabled": true,
"rules": {
"recommended": false
}
}
},
{
"include": ["packages/**/src/tests/**", "packages/**/src/test/**"],
"linter": {
"rules": {
"correctness": {
"useImportExtensions": "off"
}
}
}
},
{
"include": ["packages/cojson-storage-indexeddb/**"],
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "info"
}
}
}
}
]
}
}

36
examples/book-shelf/.gitignore vendored Normal file
View 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

View 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

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

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

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

View File

@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;

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

View File

@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@@ -3,27 +3,26 @@
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"allowJs": true,
"skipLibCheck": true,
/* Bundler mode */
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View File

@@ -1,201 +1,5 @@
# chat-rn-clerk
## 1.0.50
### Patch Changes
- Updated dependencies [8eb9247]
- jazz-tools@0.9.9
- jazz-react-native@0.9.9
- jazz-react-native-auth-clerk@0.9.9
- jazz-react-native-media-images@0.9.9
## 1.0.49
### Patch Changes
- Updated dependencies [d1d773b]
- jazz-tools@0.9.8
- jazz-react-native@0.9.8
- jazz-react-native-auth-clerk@0.9.8
- jazz-react-native-media-images@0.9.8
## 1.0.48
### Patch Changes
- Updated dependencies [8a390d2]
- jazz-react-native@0.9.6
- jazz-react-native-auth-clerk@0.9.6
## 1.0.47
### Patch Changes
- Updated dependencies [c871912]
- jazz-react-native@0.9.5
- jazz-react-native-auth-clerk@0.9.5
## 1.0.46
### Patch Changes
- jazz-react-native@0.9.4
- jazz-react-native-auth-clerk@0.9.4
## 1.0.45
### Patch Changes
- Updated dependencies [7cd691f]
- jazz-react-native@0.9.3
- jazz-react-native-auth-clerk@0.9.3
## 1.0.44
### Patch Changes
- Updated dependencies [80fd3e9]
- jazz-react-native@0.9.2
- jazz-react-native-auth-clerk@0.9.2
## 1.0.43
### Patch Changes
- Updated dependencies [1b71969]
- jazz-tools@0.9.1
- jazz-react-native@0.9.1
- jazz-react-native-auth-clerk@0.9.1
- jazz-react-native-media-images@0.9.1
## 1.0.42
### Patch Changes
- Updated dependencies [1da4d55]
- Updated dependencies [8eda792]
- Updated dependencies [1e5e3a1]
- jazz-react-native@0.9.0
- jazz-tools@0.9.0
- jazz-react-native-auth-clerk@0.9.0
- jazz-react-native-media-images@0.9.0
## 1.0.41
### Patch Changes
- Updated dependencies [dc62b95]
- Updated dependencies [1de26f8]
- jazz-tools@0.8.51
- jazz-react-native@0.8.51
- jazz-react-native-auth-clerk@0.8.51
- jazz-react-native-media-images@0.8.51
## 1.0.40
### Patch Changes
- jazz-react-native@0.8.50
- jazz-react-native-auth-clerk@0.8.50
- jazz-tools@0.8.50
- jazz-react-native-media-images@0.8.50
## 1.0.39
### Patch Changes
- jazz-react-native@0.8.49
- jazz-react-native-auth-clerk@0.8.49
- jazz-tools@0.8.49
- jazz-react-native-media-images@0.8.49
## 1.0.38
### Patch Changes
- Updated dependencies [635e824]
- Updated dependencies [0a85982]
- jazz-tools@0.8.48
- jazz-react-native@0.8.48
- jazz-react-native-auth-clerk@0.8.48
- jazz-react-native-media-images@0.8.48
## 1.0.37
### Patch Changes
- Updated dependencies [33ef9c4]
- jazz-react-native@0.8.47
- jazz-react-native-auth-clerk@0.8.47
## 1.0.36
### Patch Changes
- Updated dependencies [ab4ffbd]
- jazz-react-native@0.8.46
- jazz-react-native-auth-clerk@0.8.46
## 1.0.35
### Patch Changes
- Updated dependencies [7701307]
- Updated dependencies [fa41f8e]
- Updated dependencies [88d7d9a]
- Updated dependencies [60e35ea]
- jazz-react-native@0.8.45
- jazz-tools@0.8.45
- jazz-react-native-auth-clerk@0.8.45
- jazz-react-native-media-images@0.8.45
## 1.0.34
### Patch Changes
- jazz-react-native@0.8.44
- jazz-react-native-auth-clerk@0.8.44
- jazz-tools@0.8.44
- jazz-react-native-media-images@0.8.44
## 1.0.33
### Patch Changes
- cdc7f9f: Fixing react-native examples
- Updated dependencies [cdc7f9f]
- jazz-react-native-auth-clerk@0.8.43
## 1.0.32
### Patch Changes
- jazz-react-native@0.8.41
- jazz-react-native-auth-clerk@0.8.41
- jazz-tools@0.8.41
- jazz-react-native-media-images@0.8.41
## 1.0.31
### Patch Changes
- Updated dependencies [0c6b0f3]
- Updated dependencies [249eecb]
- jazz-react-native@0.8.39
- jazz-tools@0.8.39
- jazz-react-native-auth-clerk@0.8.39
- jazz-react-native-media-images@0.8.39
## 1.0.30
### Patch Changes
- jazz-react-native@0.8.38
- jazz-react-native-auth-clerk@0.8.38
- jazz-tools@0.8.38
- jazz-react-native-media-images@0.8.38
## 1.0.29
### Patch Changes

View File

@@ -37,13 +37,7 @@
],
"expo-secure-store",
"expo-font",
"expo-router",
[
"expo-image-picker",
{
"photosPermission": "The app accesses your photos to let you share them with your friends."
}
]
"expo-router"
],
"extra": {
"eas": {

View File

@@ -1,4 +1,3 @@
import "../global.css";
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
import { useFonts } from "expo-font";
import { Slot } from "expo-router";

View File

@@ -1,12 +1,9 @@
import { useAccount, useCoState } from "@/src/jazz";
import { Chat, Message } from "@/src/schema";
import { useNavigation } from "@react-navigation/native";
import { useFocusEffect, useNavigation } from "@react-navigation/native";
import clsx from "clsx";
import * as Clipboard from "expo-clipboard";
import * as ImagePicker from "expo-image-picker";
import { useLocalSearchParams } from "expo-router";
import { useAccount, useCoState } from "jazz-react-native";
import { ProgressiveImg } from "jazz-react-native";
import { createImage } from "jazz-react-native-media-images";
import { Group, ID } from "jazz-tools";
import { useEffect, useLayoutEffect, useState } from "react";
import React, {
@@ -19,8 +16,6 @@ import React, {
KeyboardAvoidingView,
TextInput,
Button,
Image,
ActivityIndicator,
} from "react-native";
export default function Conversation() {
@@ -30,7 +25,6 @@ export default function Conversation() {
const [message, setMessage] = useState("");
const loadedChat = useCoState(Chat, chat?.id, [{}]);
const navigation = useNavigation();
const [isUploading, setIsUploading] = useState(false);
useEffect(() => {
if (chat) return;
@@ -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 isMe = item._edits.text.by?.isMe;
return (
@@ -138,27 +106,14 @@ export default function Conversation() {
isMe ? "flex-row" : "flex-row",
)}
>
{item.image && (
<ProgressiveImg image={item.image} maxWidth={1024}>
{({ src, res, originalSize }) => (
<Image
source={{ uri: src }}
className="w-48 h-48 rounded-lg mb-2"
resizeMode="cover"
/>
)}
</ProgressiveImg>
)}
{item.text && (
<Text
className={clsx(
!isMe ? "text-black" : "text-gray-200",
`text-md max-w-[85%]`,
)}
>
{item.text}
</Text>
)}
<Text
className={clsx(
!isMe ? "text-black" : "text-gray-200",
`text-md max-w-[85%]`,
)}
>
{item.text}
</Text>
<Text
className={clsx(
"text-[10px] text-right ml-2",
@@ -192,17 +147,6 @@ export default function Conversation() {
className="p-3 bg-white border-t border-gray-300"
>
<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
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}

View File

@@ -11,7 +11,7 @@ import React, {
} from "react-native";
import { useUser } from "@clerk/clerk-expo";
import { useAccount } from "jazz-react-native";
import { useAccount } from "../../src/jazz";
import { Chat } from "../../src/schema";
export default function ChatScreen() {

View File

@@ -1,9 +1,7 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
presets: ["babel-preset-expo"],
plugins: ["nativewind/babel"],
};
};

View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,6 +1,5 @@
// Learn more https://docs.expo.dev/guides/monorepos
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const { FileStore } = require("metro-cache");
const path = require("path");
@@ -32,5 +31,4 @@ config.cacheStores = [
}),
];
// module.exports = config;
module.exports = withNativeWind(config, { input: "./global.css" });
module.exports = config;

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-clerk",
"main": "index.js",
"version": "1.0.50",
"version": "1.0.29",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",
@@ -20,7 +20,6 @@
"@bam.tech/react-native-image-resizer": "^3.0.11",
"@clerk/clerk-expo": "^2.2.21",
"@expo/vector-icons": "^14.0.2",
"@op-engineering/op-sqlite": "^11.2.12",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/native": "^7.0.13",
"@react-navigation/native-stack": "^7.1.14",
@@ -35,7 +34,6 @@
"expo-dev-client": "~5.0.5",
"expo-file-system": "^18.0.4",
"expo-font": "~13.0.1",
"expo-image-picker": "~16.0.4",
"expo-linking": "~7.0.3",
"expo-router": "~4.0.11",
"expo-secure-store": "~14.0.0",
@@ -47,9 +45,9 @@
"jazz-react-native-auth-clerk": "workspace:*",
"jazz-react-native-media-images": "workspace:*",
"jazz-tools": "workspace:*",
"nativewind": "^4.1.21",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"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",
@@ -72,8 +70,8 @@
"jest": "^29.2.1",
"jest-expo": "~52.0.2",
"react-test-renderer": "18.2.0",
"tailwindcss": "^3.4.15",
"typescript": "~5.6.2"
"tailwindcss": "3.3.2",
"typescript": "^5.3.3"
},
"private": true
}

View File

@@ -1,5 +1,4 @@
import { useClerk, useUser } from "@clerk/clerk-expo";
import { JazzProvider, setupKvStore } from "jazz-react-native";
import { useJazzClerkAuth } from "jazz-react-native-auth-clerk";
import React, {
createContext,
@@ -9,6 +8,8 @@ import React, {
useState,
} from "react";
import { Text, View } from "react-native";
import { Jazz, kvStore } from "./jazz";
const AuthContext = createContext<{
isAuthenticated: boolean;
isLoading: boolean;
@@ -21,8 +22,6 @@ export function useAuth() {
return useContext(AuthContext);
}
const kvStore = setupKvStore();
export function JazzAndAuth({ children }: PropsWithChildren) {
const { isSignedIn, isLoaded: isClerkLoaded } = useUser();
const clerk = useClerk();
@@ -48,13 +47,13 @@ export function JazzAndAuth({ children }: PropsWithChildren) {
</View>
))}
{auth && clerk.user ? (
<JazzProvider
<Jazz.Provider
auth={auth}
storage="sqlite"
peer="wss://cloud.jazz.tools/?key=chat-rn-clerk-example-jazz@garden.co"
storage={undefined}
>
{children}
</JazzProvider>
</Jazz.Provider>
) : (
children
)}

View File

@@ -0,0 +1,4 @@
import { createJazzRNApp } from "jazz-react-native";
export const Jazz = createJazzRNApp();
export const { useAccount, useCoState, useAcceptInvite, kvStore } = Jazz;

View File

@@ -1,8 +1,7 @@
import { CoList, CoMap, ImageDefinition, co } from "jazz-tools";
import { CoList, CoMap, co } from "jazz-tools";
export class Message extends CoMap {
text = co.string;
image = co.optional.ref(ImageDefinition);
}
export class Chat extends CoList.Of(co.ref(Message)) {}

View File

@@ -1,12 +1,10 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all of your component files.
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},

View File

@@ -7,5 +7,5 @@
"@/*": ["./*"]
}
},
"include": ["**/*.ts", "**/*.tsx", "nativewind-env.d.ts"]
"include": ["**/*.ts", "**/*.tsx"]
}

View File

@@ -1,160 +1,5 @@
# chat-rn
## 1.0.47
### Patch Changes
- Updated dependencies [8eb9247]
- jazz-tools@0.9.9
- jazz-react-native@0.9.9
## 1.0.46
### Patch Changes
- Updated dependencies [d1d773b]
- jazz-tools@0.9.8
- jazz-react-native@0.9.8
## 1.0.45
### Patch Changes
- Updated dependencies [8a390d2]
- jazz-react-native@0.9.6
## 1.0.44
### Patch Changes
- Updated dependencies [c871912]
- jazz-react-native@0.9.5
## 1.0.43
### Patch Changes
- jazz-react-native@0.9.4
## 1.0.42
### Patch Changes
- Updated dependencies [7cd691f]
- jazz-react-native@0.9.3
## 1.0.41
### Patch Changes
- Updated dependencies [80fd3e9]
- jazz-react-native@0.9.2
## 1.0.40
### Patch Changes
- Updated dependencies [1b71969]
- jazz-tools@0.9.1
- jazz-react-native@0.9.1
## 1.0.39
### Patch Changes
- Updated dependencies [1da4d55]
- Updated dependencies [8eda792]
- Updated dependencies [1e5e3a1]
- jazz-react-native@0.9.0
- jazz-tools@0.9.0
## 1.0.38
### Patch Changes
- Updated dependencies [dc62b95]
- Updated dependencies [1de26f8]
- jazz-tools@0.8.51
- jazz-react-native@0.8.51
## 1.0.37
### Patch Changes
- jazz-react-native@0.8.50
- jazz-tools@0.8.50
## 1.0.36
### Patch Changes
- jazz-react-native@0.8.49
- jazz-tools@0.8.49
## 1.0.35
### Patch Changes
- Updated dependencies [635e824]
- Updated dependencies [0a85982]
- jazz-tools@0.8.48
- jazz-react-native@0.8.48
## 1.0.34
### Patch Changes
- Updated dependencies [33ef9c4]
- jazz-react-native@0.8.47
## 1.0.33
### Patch Changes
- Updated dependencies [ab4ffbd]
- jazz-react-native@0.8.46
## 1.0.32
### Patch Changes
- Updated dependencies [7701307]
- Updated dependencies [fa41f8e]
- Updated dependencies [88d7d9a]
- Updated dependencies [60e35ea]
- jazz-react-native@0.8.45
- jazz-tools@0.8.45
## 1.0.31
### Patch Changes
- jazz-react-native@0.8.44
- jazz-tools@0.8.44
## 1.0.30
### Patch Changes
- jazz-react-native@0.8.41
- jazz-tools@0.8.41
## 1.0.29
### Patch Changes
- Updated dependencies [0c6b0f3]
- Updated dependencies [249eecb]
- jazz-react-native@0.8.39
- jazz-tools@0.8.39
## 1.0.28
### Patch Changes
- jazz-react-native@0.8.38
- jazz-tools@0.8.38
## 1.0.27
### Patch Changes

View File

@@ -1,9 +1,7 @@
module.exports = function (api) {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
presets: ["babel-preset-expo"],
plugins: ["nativewind/babel"],
};
};

View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,6 +1,5 @@
// Learn more https://docs.expo.dev/guides/monorepos
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const { FileStore } = require("metro-cache");
const path = require("path");
@@ -8,7 +7,7 @@ const path = require("path");
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
const config = getDefaultConfig(projectRoot, { isCSSEnabled: true });
const config = getDefaultConfig(projectRoot);
// Since we are using pnpm, we have to setup the monorepo manually for Metro
// #1 - Watch all files in the monorepo
@@ -20,10 +19,7 @@ config.resolver.nodeModulesPaths = [
];
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.unstable_enablePackageExports = true;
config.resolver.requireCycleIgnorePatterns = [
/(^|\/|\\)node_modules($|\/|\\)/,
/(^|\/|\\)packages($|\/|\\)/,
];
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
// Use turborepo to restore the cache when possible
config.cacheStores = [
@@ -32,5 +28,4 @@ config.cacheStores = [
}),
];
// module.exports = config;
module.exports = withNativeWind(config, { input: "./global.css" });
module.exports = config;

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.47",
"version": "1.0.27",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",
@@ -13,10 +13,9 @@
},
"dependencies": {
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@op-engineering/op-sqlite": "^11.2.12",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/native": "^7.0.13",
"@react-navigation/native-stack": "^7.1.14",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"base-64": "^1.0.0",
"clsx": "^2.0.0",
"expo": "^52.0.0",
@@ -31,8 +30,8 @@
"expo-web-browser": "~14.0.1",
"jazz-react-native": "workspace:*",
"jazz-tools": "workspace:*",
"nativewind": "^4.1.21",
"react": "^18.3.1",
"nativewind": "^2.0.11",
"react": "18.2.0",
"react-native": "~0.76.3",
"react-native-fetch-api": "^3.0.0",
"react-native-get-random-values": "^1.11.0",
@@ -46,8 +45,8 @@
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "^18.3.12",
"tailwindcss": "^3.4.15",
"typescript": "~5.6.2"
"tailwindcss": "3.3.2",
"typescript": "^5.3.3"
},
"private": true
}

View File

@@ -1,5 +1,3 @@
import "../global.css";
import {
NavigationContainer,
useNavigationContainerRef,
@@ -9,8 +7,9 @@ import * as Linking from "expo-linking";
import React, { StrictMode, useEffect, useState } from "react";
import HandleInviteScreen from "./invite";
import { DemoAuthBasicUI, JazzProvider, useDemoAuth } from "jazz-react-native";
import { DemoAuthBasicUI, useDemoAuth } from "jazz-react-native";
import ChatScreen from "./chat";
import { Jazz } from "./jazz";
const Stack = createNativeStackNavigator();
@@ -33,6 +32,7 @@ function App() {
"ChatScreen" | "HandleInviteScreen"
>("ChatScreen");
const navigationRef = useNavigationContainerRef();
useEffect(() => {
Linking.getInitialURL().then((url) => {
if (url) {
@@ -49,10 +49,10 @@ function App() {
return (
<StrictMode>
<JazzProvider
<Jazz.Provider
auth={auth}
storage="sqlite"
peer="wss://cloud.jazz.tools/?key=chat-rn-example-jazz@garden.co"
storage={undefined}
>
<NavigationContainer linking={linking} ref={navigationRef}>
<Stack.Navigator initialRouteName={initialRoute}>
@@ -68,7 +68,7 @@ function App() {
/>
</Stack.Navigator>
</NavigationContainer>
</JazzProvider>
</Jazz.Provider>
{state.state !== "signedIn" ? (
<DemoAuthBasicUI appName="Jazz Chat" state={state} />
) : null}

View File

@@ -14,40 +14,50 @@ import React, {
Alert,
} from "react-native";
import { useAccount, useCoState } from "jazz-react-native";
import { useAccount, useCoState } from "./jazz";
import { Chat, Message } from "./schema";
export default function ChatScreen({ navigation }: { navigation: any }) {
const { me, logOut } = useAccount();
const [chatId, setChatId] = useState<ID<Chat>>();
const loadedChat = useCoState(Chat, chatId, [{}]);
const [chat, setChat] = useState<Chat>();
const [message, setMessage] = useState("");
const loadedChat = useCoState(Chat, chat?.id, [{}]);
useEffect(() => {
navigation.setOptions({
headerRight: () => <Button onPress={logOut} title="Logout" />,
headerLeft: () =>
loadedChat ? (
chat ? (
<Button
onPress={() => {
if (loadedChat?.id) {
if (chat?.id) {
Clipboard.setStringAsync(
`https://chat.jazz.tools/#/chat/${loadedChat.id}`,
`https://chat.jazz.tools/#/chat/${chat.id}`,
);
Alert.alert("Copied to clipboard", `Chat ID: ${loadedChat.id}`);
Alert.alert("Copied to clipboard", `Chat ID: ${chat.id}`);
}
}}
title="Share"
/>
) : null,
});
}, [navigation, loadedChat]);
}, [navigation, chat]);
const createChat = () => {
const group = Group.create({ owner: me });
group.addMember("everyone", "writer");
const chat = Chat.create([], { owner: group });
setChatId(chat.id);
setChat(chat);
};
const loadChat = async (chatId: ID<Chat>) => {
try {
const chat = await Chat.load(chatId, me, []);
setChat(chat);
} catch (error) {
console.log("Error loading chat", error);
Alert.alert("Error", `Error loading chat: ${error}`);
}
};
const joinChat = () => {
@@ -63,7 +73,7 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
text: "Join",
onPress: (chatId) => {
if (chatId) {
setChatId(chatId as ID<Chat>);
loadChat(chatId as ID<Chat>);
} else {
Alert.alert("Error", "Chat ID cannot be empty.");
}
@@ -75,11 +85,9 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
};
const sendMessage = () => {
if (!loadedChat) return;
if (!chat) return;
if (message.trim()) {
loadedChat.push(
Message.create({ text: message }, { owner: loadedChat?._owner }),
);
chat.push(Message.create({ text: message }, { owner: chat._owner }));
setMessage("");
}
};
@@ -129,7 +137,7 @@ export default function ChatScreen({ navigation }: { navigation: any }) {
return (
<View className="flex flex-col h-full">
{!loadedChat ? (
{!chat ? (
<View className="flex flex-col h-full items-center justify-center">
<TouchableOpacity
onPress={createChat}

View File

@@ -1,7 +1,13 @@
import { useAcceptInvite } from "jazz-react-native";
import React, { Text } from "react-native";
import { useAcceptInvite } from "./jazz";
import { Chat } from "./schema";
type ChatScreenParams = {
valueHint?: string;
valueID?: string;
inviteSecret?: string;
};
export default function HandleInviteScreen({
navigation,
}: {

View File

@@ -0,0 +1,4 @@
import { createJazzRNApp } from "jazz-react-native";
export const Jazz = createJazzRNApp();
export const { useAccount, useCoState, useAcceptInvite } = Jazz;

View File

@@ -1,12 +1,10 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
// NOTE: Update this to include the paths to all of your component files.
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {},
},

View File

@@ -1,133 +1,5 @@
# chat-vue
## 0.0.34
### Patch Changes
- Updated dependencies [8eb9247]
- jazz-tools@0.9.9
- jazz-browser@0.9.9
- jazz-vue@0.9.9
## 0.0.33
### Patch Changes
- Updated dependencies [d1d773b]
- jazz-tools@0.9.8
- jazz-browser@0.9.8
- jazz-vue@0.9.8
## 0.0.32
### Patch Changes
- Updated dependencies [1b71969]
- Updated dependencies [5d98189]
- jazz-browser@0.9.1
- jazz-tools@0.9.1
- jazz-vue@0.9.1
## 0.0.31
### Patch Changes
- Updated dependencies [9dd8d95]
- Updated dependencies [8eda792]
- jazz-vue@0.9.0
- jazz-tools@0.9.0
- jazz-browser@0.9.0
## 0.0.30
### Patch Changes
- Updated dependencies [dc62b95]
- Updated dependencies [1de26f8]
- jazz-tools@0.8.51
- jazz-browser@0.8.51
- jazz-vue@0.8.51
## 0.0.29
### Patch Changes
- jazz-browser@0.8.50
- jazz-tools@0.8.50
- jazz-vue@0.8.50
## 0.0.28
### Patch Changes
- jazz-browser@0.8.49
- jazz-tools@0.8.49
- jazz-vue@0.8.49
## 0.0.27
### Patch Changes
- Updated dependencies [635e824]
- Updated dependencies [0a85982]
- jazz-tools@0.8.48
- jazz-browser@0.8.48
- jazz-vue@0.8.48
## 0.0.26
### Patch Changes
- Updated dependencies [6f0bd7f]
- Updated dependencies [fa41f8e]
- Updated dependencies [88d7d9a]
- Updated dependencies [60e35ea]
- jazz-browser@0.8.45
- jazz-tools@0.8.45
- jazz-vue@0.8.45
## 0.0.25
### Patch Changes
- jazz-browser@0.8.44
- jazz-tools@0.8.44
- jazz-vue@0.8.44
## 0.0.24
### Patch Changes
- Updated dependencies [ac216b9]
- jazz-browser@0.8.41
- jazz-tools@0.8.41
- jazz-vue@0.8.41
## 0.0.23
### Patch Changes
- jazz-browser@0.8.40
- jazz-vue@0.8.40
## 0.0.22
### Patch Changes
- Updated dependencies [e386f2b]
- Updated dependencies [249eecb]
- jazz-browser@0.8.39
- jazz-tools@0.8.39
- jazz-vue@0.8.39
## 0.0.21
### Patch Changes
- jazz-browser@0.8.38
- jazz-tools@0.8.38
- jazz-vue@0.8.38
## 0.0.20
### Patch Changes

View File

@@ -1,59 +1,29 @@
# Chat example with Jazz and Vue
## Getting started
## Installing & running the example locally
You can either
1. Clone the jazz repository, and run the app within the monorepo.
2. Or create a new Jazz project using this example as a template.
(This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
### Using the example as a template
Create a new Jazz project, and use this example as a template.
Start by downloading the [jazz repository](https://github.com/garden-co/jazz):
```bash
npm create jazz-app@latest --example chat-vue --project-name chat-vue
```
or
```bash
npx create-jazz-app@latest --example chat-vue --project-name chat-vue
npx degit gardencmp/jazz jazz
```
Go to the new project directory.
Go to the todo-vue example directory:
```bash
cd chat-vue
cd jazz/examples/chat-vue
```
Run the dev server.
```bash
npm run dev
```
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
Install and build dependencies:
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/chat-vue/
```
Start the dev server.
Start the dev server:
```bash
pnpm dev
```
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
## 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.

View File

@@ -1,6 +1,6 @@
{
"name": "chat-vue",
"version": "0.0.34",
"version": "0.0.20",
"private": true,
"type": "module",
"scripts": {
@@ -25,14 +25,14 @@
"@vitejs/plugin-vue": "^5.1.4",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"@vue/tsconfig": "^0.5.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.7.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.46.0",
"eslint-plugin-vue": "^9.28.0",
"npm-run-all2": "^6.2.3",
"postcss": "^8.4.27",
"tailwindcss": "^3.4.15",
"typescript": "~5.6.2",
"vite": "^5.4.10",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",
"vite": "^5.0.10",
"vite-plugin-vue-devtools": "^7.4.6",
"vue-tsc": "^2.1.6"
}

View File

@@ -9,9 +9,9 @@
</template>
<script setup lang="ts">
import { useAccount } from "jazz-vue";
import AppContainer from "./components/AppContainer.vue";
import TopBar from "./components/TopBar.vue";
import { useAccount } from "./main";
const { me, logOut } = useAccount();
</script>

View File

@@ -1,9 +1,13 @@
import { DemoAuthBasicUI, JazzProvider, useDemoAuth } from "jazz-vue";
import { DemoAuthBasicUI, createJazzVueApp, useDemoAuth } from "jazz-vue";
import { createApp, defineComponent, h } from "vue";
import App from "./App.vue";
import "./index.css";
import router from "./router";
const Jazz = createJazzVueApp();
export const { useAccount, useCoState } = Jazz;
const { JazzProvider } = Jazz;
const RootComponent = defineComponent({
name: "RootComponent",
setup() {

View File

@@ -26,12 +26,12 @@
<script lang="ts">
import type { ID } from "jazz-tools";
import { useCoState } from "jazz-vue";
import { type PropType, computed, defineComponent, ref } from "vue";
import ChatBody from "../components/ChatBody.vue";
import ChatBubble from "../components/ChatBubble.vue";
import ChatInput from "../components/ChatInput.vue";
import EmptyChatMessage from "../components/EmptyChatMessage.vue";
import { useCoState } from "../main";
import { Chat, Message } from "../schema";
export default defineComponent({

View File

@@ -4,8 +4,8 @@
<script setup lang="ts">
import { Group } from "jazz-tools";
import { useAccount } from "jazz-vue";
import { useRouter } from "vue-router";
import { useAccount } from "../main";
import { Chat } from "../schema";
const router = useRouter();

View File

@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
export default {
purge: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -1,11 +0,0 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
export default config;

View File

@@ -1,148 +1,5 @@
# jazz-example-chat
## 0.0.130
### Patch Changes
- Updated dependencies [8eb9247]
- jazz-tools@0.9.9
- jazz-browser-media-images@0.9.9
- jazz-react@0.9.9
## 0.0.129
### Patch Changes
- Updated dependencies [d1d773b]
- jazz-tools@0.9.8
- jazz-react@0.9.8
- jazz-browser-media-images@0.9.8
## 0.0.128
### Patch Changes
- jazz-react@0.9.4
## 0.0.127
### Patch Changes
- Updated dependencies [1b71969]
- jazz-react@0.9.1
- jazz-tools@0.9.1
- jazz-browser-media-images@0.9.1
## 0.0.126
### Patch Changes
- Updated dependencies [956a4d1]
- Updated dependencies [8eda792]
- jazz-react@0.9.0
- jazz-tools@0.9.0
- jazz-browser-media-images@0.9.0
## 0.0.125
### Patch Changes
- Updated dependencies [dc62b95]
- Updated dependencies [1de26f8]
- jazz-tools@0.8.51
- jazz-browser-media-images@0.8.51
- jazz-react@0.8.51
## 0.0.124
### Patch Changes
- jazz-react@0.8.50
- jazz-tools@0.8.50
- jazz-browser-media-images@0.8.50
## 0.0.123
### Patch Changes
- jazz-react@0.8.49
- jazz-tools@0.8.49
- jazz-browser-media-images@0.8.49
## 0.0.122
### Patch Changes
- Updated dependencies [635e824]
- Updated dependencies [0a85982]
- jazz-tools@0.8.48
- jazz-browser-media-images@0.8.48
- jazz-react@0.8.48
## 0.0.121
### Patch Changes
- Updated dependencies [6f0bd7f]
- Updated dependencies [fca6a0b]
- Updated dependencies [fa41f8e]
- Updated dependencies [88d7d9a]
- Updated dependencies [60e35ea]
- cojson@0.8.45
- jazz-tools@0.8.45
- jazz-react@0.8.45
- jazz-browser-media-images@0.8.45
## 0.0.120
### Patch Changes
- Updated dependencies [5d20c81]
- cojson@0.8.44
- jazz-react@0.8.44
- jazz-tools@0.8.44
- jazz-browser-media-images@0.8.44
## 0.0.119
### Patch Changes
- Updated dependencies [3252502]
- Updated dependencies [6370348]
- Updated dependencies [ac216b9]
- cojson@0.8.41
- jazz-react@0.8.41
- jazz-tools@0.8.41
- jazz-browser-media-images@0.8.41
## 0.0.118
### Patch Changes
- jazz-browser-media-images@0.8.40
- jazz-react@0.8.40
## 0.0.117
### Patch Changes
- Updated dependencies [249eecb]
- Updated dependencies [3121551]
- jazz-tools@0.8.39
- cojson@0.8.39
- jazz-browser-media-images@0.8.39
- jazz-react@0.8.39
## 0.0.116
### Patch Changes
- Updated dependencies [b00ee91]
- Updated dependencies [f488c09]
- cojson@0.8.38
- jazz-react@0.8.38
- jazz-tools@0.8.38
## 0.0.115
### Patch Changes

View File

@@ -2,60 +2,30 @@
Live version: [https://chat.jazz.tools](https://chat.jazz.tools)
## Getting started
## Installing & running the example locally
You can either
1. Clone the jazz repository, and run the app within the monorepo.
2. Or create a new Jazz project using this example as a template.
(This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
### Using the example as a template
Create a new Jazz project, and use this example as a template.
Start by downloading the [jazz repository](https://github.com/garden-co/jazz):
```bash
npm create jazz-app@latest --example chat --project-name chat
```
or
```bash
npx create-jazz-app@latest --example chat --project-name chat
npx degit gardencmp/jazz jazz
```
Go to the new project directory.
Go to the chat example directory:
```bash
cd chat
cd jazz/examples/chat
```
Run the dev server.
```bash
npm run dev
```
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
Install and build dependencies:
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/chat/
```
Start the dev server.
Start the dev server:
```bash
pnpm dev
```
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
## 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.
@@ -64,4 +34,4 @@ If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or
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 `<JazzProvider>` provider component in [./src/main.tsx](./src/main.tsx).
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/main.tsx](./src/main.tsx).

View File

@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz Chat Example</title>
</head>
<body class="h-full bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-925">
<body class="h-full">
<div id="root" class="h-full"></div>
<script type="module" src="/src/app.tsx"></script>
</body>

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.130",
"version": "0.0.115",
"type": "module",
"scripts": {
"dev": "vite",
@@ -13,25 +13,37 @@
"test:ui": "playwright test --ui"
},
"dependencies": {
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"hash-slash": "workspace:*",
"jazz-browser-media-images": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"cojson": "workspace:0.8.37",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.37",
"jazz-tools": "workspace:0.8.37",
"lucide-react": "^0.274.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"react-use": "^17.4.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uniqolor": "^1.1.0"
},
"devDependencies": {
"@playwright/test": "^1.46.1",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@types/qrcode": "^1.5.1",
"@types/react": "^18.2.19",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.20",
"autoprefixer": "^10.4.14",
"is-ci": "^3.0.1",
"postcss": "^8.4.27",
"tailwindcss": "^3.4.15",
"typescript": "~5.6.2",
"vite": "^5.4.10"
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",
"vite": "^5.0.10"
}
}

View File

@@ -1,13 +1,9 @@
import { inIframe, onChatLoad } from "@/util.ts";
import { useIframeHashRouter } from "hash-slash";
import { useAccount } from "jazz-react";
import { Group, ID } from "jazz-tools";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { ChatScreen } from "./chatScreen.tsx";
import { JazzAndAuth } from "./jazz.tsx";
import { useAccount } from "./main.tsx";
import { Chat } from "./schema.ts";
import { ThemeProvider } from "./themeProvider.tsx";
import { AppContainer, TopBar } from "./ui.tsx";
export function App() {
@@ -16,13 +12,13 @@ export function App() {
const createChat = () => {
if (!me) return;
const group = Group.create();
const group = Group.create({ owner: me });
group.addMember("everyone", "writer");
const chat = Chat.create([], group);
const chat = Chat.create([], { owner: group });
router.navigate("/#/chat/" + chat.id);
// for https://jazz.tools marketing site demo only
onChatLoad(chat);
onChatLoad(chat, me);
};
return (
@@ -38,13 +34,3 @@ export function App() {
</AppContainer>
);
}
createRoot(document.getElementById("root")!).render(
<ThemeProvider>
<StrictMode>
<JazzAndAuth>
<App />
</JazzAndAuth>
</StrictMode>
</ThemeProvider>,
);

View File

@@ -1,46 +1,21 @@
import { createImage } from "jazz-browser-media-images";
import { useCoState } from "jazz-react";
import { ID } from "jazz-tools";
import { useState } from "react";
import { useCoState } from "./main.tsx";
import { Chat, Message } from "./schema.ts";
import {
BubbleBody,
BubbleContainer,
BubbleImage,
BubbleInfo,
BubbleText,
ChatBody,
ChatInput,
EmptyChatMessage,
ImageInput,
InputBar,
TextInput,
} from "./ui.tsx";
export function ChatScreen(props: { chatID: ID<Chat> }) {
const chat = useCoState(Chat, props.chatID, [{}]);
const [showNLastMessages, setShowNLastMessages] = useState(30);
if (!chat)
return (
<div className="flex-1 flex justify-center items-center">Loading...</div>
);
const sendImage = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.currentTarget.files?.[0];
if (!file) return;
if (file.size > 5000000) {
alert("Please upload an image less than 5MB.");
return;
}
createImage(file, { owner: chat._owner }).then((image) => {
chat.push(Message.create({ text: file.name, image: image }, chat._owner));
});
};
return (
return chat ? (
<>
<ChatBody>
{chat.length > 0 ? (
@@ -60,31 +35,24 @@ export function ChatScreen(props: { chatID: ID<Chat> }) {
</button>
)}
</ChatBody>
<InputBar>
<ImageInput onImageChange={sendImage} />
<TextInput
onSubmit={(text) => {
chat.push(Message.create({ text }, { owner: chat._owner }));
}}
/>
</InputBar>
<ChatInput
onSubmit={(text) => {
chat.push(Message.create({ text }, { owner: chat._owner }));
}}
/>
</>
) : (
<div className="flex-1 flex justify-center items-center">Loading...</div>
);
}
function ChatBubble(props: { msg: Message }) {
const lastEdit = props.msg._edits.text;
const fromMe = lastEdit.by?.isMe;
const { text, image } = props.msg;
return (
<BubbleContainer fromMe={fromMe}>
<BubbleBody fromMe={fromMe}>
{image && <BubbleImage image={image} />}
<BubbleText text={text} />
</BubbleBody>
<BubbleBody fromMe={fromMe}>{props.msg.text}</BubbleBody>
<BubbleInfo by={lastEdit.by?.profile?.name} madeAt={lastEdit.madeAt} />
</BubbleContainer>
);

View File

@@ -1,3 +1,78 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 24 9.8% 10%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 60 9.1% 97.8%;
--primary-foreground: 24 9.8% 10%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 24 5.7% 82.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
margin: 0;
padding: 0;
}
}

View File

@@ -1,19 +0,0 @@
import { DemoAuthBasicUI, JazzProvider, useDemoAuth } from "jazz-react";
export function JazzAndAuth({ children }: { children: React.ReactNode }) {
const [auth, state] = useDemoAuth();
return (
<>
<JazzProvider
auth={auth}
peer="wss://cloud.jazz.tools/?key=chat-example-jazz@garden.co"
>
{children}
</JazzProvider>
{state.state !== "signedIn" && (
<DemoAuthBasicUI appName="Jazz Chat" state={state} />
)}
</>
);
}

View File

@@ -0,0 +1,33 @@
import { DemoAuthBasicUI, createJazzReactApp, useDemoAuth } from "jazz-react";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./app.tsx";
const Jazz = createJazzReactApp();
export const { useAccount, useCoState } = Jazz;
function JazzAndAuth({ children }: { children: React.ReactNode }) {
const [auth, state] = useDemoAuth();
return (
<>
<Jazz.Provider
auth={auth}
peer="wss://cloud.jazz.tools/?key=chat-example-jazz@garden.co"
>
{children}
</Jazz.Provider>
{state.state !== "signedIn" && (
<DemoAuthBasicUI appName="Jazz Chat" state={state} />
)}
</>
);
}
createRoot(document.getElementById("root")!).render(
<StrictMode>
<JazzAndAuth>
<App />
</JazzAndAuth>
</StrictMode>,
);

View File

@@ -1,8 +1,7 @@
import { CoList, CoMap, ImageDefinition, co } from "jazz-tools";
import { CoList, CoMap, co } from "jazz-tools";
export class Message extends CoMap {
text = co.string;
image = co.optional.ref(ImageDefinition);
}
export class Chat extends CoList.Of(co.ref(Message)) {}

View File

@@ -1,72 +0,0 @@
import { createContext, useContext, useEffect, useState } from "react";
type ThemeProviderProps = {
children: React.ReactNode;
defaultTheme?: string;
storageKey?: string;
};
type ThemeProviderState = {
theme: string;
setTheme: (theme: string) => void;
};
const initialState = {
theme: "system",
setTheme: () => null,
};
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
export function ThemeProvider({
children,
defaultTheme = "system",
storageKey = "vite-ui-theme",
...props
}: ThemeProviderProps) {
const [theme, setTheme] = useState(
() => localStorage.getItem(storageKey) || defaultTheme,
);
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove("light", "dark");
if (theme === "system") {
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
root.classList.add(systemTheme);
return;
}
root.classList.add(theme);
}, [theme]);
const value = {
theme,
setTheme: (theme: string) => {
localStorage.setItem(storageKey, theme);
setTheme(theme);
},
};
return (
<ThemeProviderContext.Provider {...props} value={value}>
{children}
</ThemeProviderContext.Provider>
);
}
// eslint-disable-next-line react-refresh/only-export-components
export const useTheme = () => {
const context = useContext(ThemeProviderContext);
if (context === undefined)
throw new Error("useTheme must be used within a ThemeProvider");
return context;
};

View File

@@ -1,8 +1,5 @@
import clsx from "clsx";
import { ProgressiveImg } from "jazz-react";
import { ImageDefinition } from "jazz-tools";
import { ImageIcon } from "lucide-react";
import { useId, useRef } from "react";
import { useId } from "react";
export function AppContainer(props: { children: React.ReactNode }) {
return (
@@ -59,7 +56,7 @@ export function BubbleBody(props: {
<div
className={clsx(
"line-clamp-10 text-ellipsis whitespace-pre-wrap",
"rounded-2xl overflow-hidden max-w-[calc(100%-5rem)] shadow-sm p-1",
"rounded-2xl max-w-full py-1 px-3 shadow-sm",
props.fromMe
? "bg-white dark:bg-stone-700 dark:text-white"
: "bg-blue text-white",
@@ -70,23 +67,6 @@ export function BubbleBody(props: {
);
}
export function BubbleText(props: { text: string }) {
return <p className="px-2 leading-relaxed">{props.text}</p>;
}
export function BubbleImage(props: { image: ImageDefinition }) {
return (
<ProgressiveImg image={props.image}>
{({ src }) => (
<img
className="h-auto max-h-[20rem] max-w-full rounded-t-xl mb-1"
src={src}
/>
)}
</ProgressiveImg>
);
}
export function BubbleInfo(props: { by: string | undefined; madeAt: Date }) {
return (
<div className="text-xs text-neutral-500 mt-1.5">
@@ -95,59 +75,17 @@ export function BubbleInfo(props: { by: string | undefined; madeAt: Date }) {
);
}
export function InputBar(props: { children: React.ReactNode }) {
return (
<div className="p-3 bg-white border-t shadow-2xl mt-auto flex gap-1 dark:bg-transparent dark:border-stone-800">
{props.children}
</div>
);
}
export function ImageInput({
onImageChange,
}: { onImageChange?: (event: React.ChangeEvent<HTMLInputElement>) => void }) {
const inputRef = useRef<HTMLInputElement>(null);
const onUploadClick = () => {
inputRef.current?.click();
};
return (
<>
<button
type="button"
aria-label="Send image"
title="Send image"
onClick={onUploadClick}
className="text-stone-500 p-1.5 rounded-full hover:bg-stone-100 hover:text-stone-800 dark:hover:bg-stone-800 dark:hover:text-stone-200 transition-colors"
>
<ImageIcon size={24} strokeWidth={1.5} />
</button>
<label className="sr-only">
Image
<input
ref={inputRef}
type="file"
accept="image/png, image/jpeg, image/gif"
onChange={onImageChange}
/>
</label>
</>
);
}
export function TextInput(props: { onSubmit: (text: string) => void }) {
export function ChatInput(props: { onSubmit: (text: string) => void }) {
const inputId = useId();
return (
<div className="flex-1">
<div className="p-3 bg-white border-t shadow-2xl mt-auto dark:bg-transparent dark:border-stone-800">
<label className="sr-only" htmlFor={inputId}>
Type a message and press Enter
</label>
<input
id={inputId}
className="rounded-full py-1 px-3 border block w-full placeholder:text-stone-500 dark:bg-black dark:text-white dark:border-stone-700"
className="rounded-full py-2 px-4 border block w-full dark:bg-black dark:text-white dark:border-stone-700"
placeholder="Type a message and press Enter"
maxLength={2048}
onKeyDown={({ key, currentTarget: input }) => {

View File

@@ -2,10 +2,20 @@
// This is NOT needed to make the chat work
import { Chat } from "@/schema.ts";
import { Account, CoValue, ID } from "jazz-tools";
export function onChatLoad(chat: Chat) {
export function waitForUpload(id: ID<CoValue>, me: Account) {
const syncManager = me._raw.core.node.syncManager;
const peers = me._raw.core.node.peers.getAll();
return Promise.all(
peers.map((peer) => syncManager.waitForUploadIntoPeer(peer.id, id)),
);
}
export function onChatLoad(chat: Chat, me: Account) {
if (window.parent) {
chat.waitForSync().then(() => {
waitForUpload(chat.id, me).then(() => {
window.parent.postMessage(
{ type: "chat-load", id: "/chat/" + chat.id },
"*",

View File

@@ -0,0 +1,82 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
blue: {
700: "#3313F7",
DEFAULT: "#3313F7",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
lineClamp: {
10: "10",
},
},
},
plugins: [require("tailwindcss-animate")],
};

View File

@@ -1,79 +0,0 @@
import type { Config } from "tailwindcss";
import plugin from "tailwindcss/plugin";
const colors = require("tailwindcss/colors");
const stonePalette = {
50: "oklch(0.988281 0.002 75)",
75: "oklch(0.980563 0.002 75)",
100: "oklch(0.964844 0.002 75)",
200: "oklch(0.917969 0.002 75)",
300: "oklch(0.853516 0.002 75)",
400: "oklch(0.789063 0.002 75)",
500: "oklch(0.726563 0.002 75)",
600: "oklch(0.613281 0.002 75)",
700: "oklch(0.523438 0.002 75)",
800: "oklch(0.412109 0.002 75)",
900: "oklch(0.302734 0.002 75)",
925: "oklch(0.220000 0.002 75)",
950: "oklch(0.193359 0.002 75)",
} as const;
const stonePaletteWithAlpha = { ...stonePalette };
Object.keys(stonePalette).forEach((key) => {
stonePaletteWithAlpha[key] = stonePaletteWithAlpha[key].replace(
")",
"/ <alpha-value>)",
);
});
const config: Config = {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {
stone: stonePaletteWithAlpha,
blue: {
...colors.indigo,
500: "#5870F1",
DEFAULT: "#3313F7",
},
},
container: {
center: true,
padding: {
DEFAULT: "0.75rem",
sm: "1rem",
},
screens: {
md: "500px",
lg: "500px",
xl: "500px",
},
},
},
},
plugins: [
plugin(({ addBase }) =>
addBase({
":root": {
"--gcmp-border-color": stonePalette[200],
"--gcmp-invert-border-color": stonePalette[900],
},
"*": {
borderColor: "var(--gcmp-border-color)",
},
"@media (prefers-color-scheme: dark)": {
"*": {
borderColor: "var(--gcmp-invert-border-color)",
},
},
"*:focus": {
outline: "none",
},
}),
),
],
} as const;
export default config;

View File

@@ -38,11 +38,14 @@ test("chat between two users", async ({ page }) => {
await chatPage.sendMessage(message2ByLuigi);
await chatPage.expectMessageRow(message2ByLuigi);
await chatPage.logout();
await loginPage.loginAs(mario);
// This breaks with (Error: Error reading from peer storage, handling msg)
// await chatPage.logout();
// await loginPage.loginAs(mario);
await page.goto(roomURL);
// await chatPage.expectLoaded();
await chatPage.expectMessageRow(message1ByMario);
await chatPage.expectMessageRow(message2ByLuigi);
// await page.goto(roomURL);
// await chatPage.expectMessageRow(message1ByMario);
// await chatPage.expectMessageRow(message2ByLuigi);
});

View File

@@ -25,7 +25,7 @@ export class LoginPage {
async loginAs(value: string) {
await this.page
.getByRole("button", {
name: value,
name: `Log in as "${value}"`,
})
.click();
}

View File

@@ -1,8 +0,0 @@
{
"build": {
"env": {
"APP_NAME": "jazz-chat"
}
},
"ignoreCommand": "node ../../ignore-vercel-build.js"
}

View File

@@ -1,136 +1,5 @@
# minimal-auth-clerk
## 0.0.29
### Patch Changes
- Updated dependencies [8eb9247]
- jazz-tools@0.9.9
- jazz-react@0.9.9
- jazz-react-auth-clerk@0.9.9
## 0.0.28
### Patch Changes
- Updated dependencies [d1d773b]
- jazz-tools@0.9.8
- jazz-react@0.9.8
- jazz-react-auth-clerk@0.9.8
## 0.0.27
### Patch Changes
- jazz-react@0.9.4
- jazz-react-auth-clerk@0.9.4
## 0.0.26
### Patch Changes
- Updated dependencies [1b71969]
- jazz-react@0.9.1
- jazz-tools@0.9.1
- jazz-react-auth-clerk@0.9.1
## 0.0.25
### Patch Changes
- Updated dependencies [956a4d1]
- Updated dependencies [8eda792]
- jazz-react@0.9.0
- jazz-tools@0.9.0
- jazz-react-auth-clerk@0.9.0
## 0.0.24
### Patch Changes
- Updated dependencies [dc62b95]
- Updated dependencies [1de26f8]
- jazz-tools@0.8.51
- jazz-react@0.8.51
- jazz-react-auth-clerk@0.8.51
## 0.0.23
### Patch Changes
- jazz-react@0.8.50
- jazz-react-auth-clerk@0.8.50
- jazz-tools@0.8.50
## 0.0.22
### Patch Changes
- jazz-react@0.8.49
- jazz-react-auth-clerk@0.8.49
- jazz-tools@0.8.49
## 0.0.21
### Patch Changes
- Updated dependencies [635e824]
- Updated dependencies [0a85982]
- jazz-tools@0.8.48
- jazz-react@0.8.48
- jazz-react-auth-clerk@0.8.48
## 0.0.20
### Patch Changes
- Updated dependencies [fa41f8e]
- Updated dependencies [88d7d9a]
- Updated dependencies [60e35ea]
- jazz-tools@0.8.45
- jazz-react@0.8.45
- jazz-react-auth-clerk@0.8.45
## 0.0.19
### Patch Changes
- jazz-react@0.8.44
- jazz-react-auth-clerk@0.8.44
- jazz-tools@0.8.44
## 0.0.18
### Patch Changes
- jazz-react@0.8.41
- jazz-react-auth-clerk@0.8.41
- jazz-tools@0.8.41
## 0.0.17
### Patch Changes
- jazz-react@0.8.40
- jazz-react-auth-clerk@0.8.40
## 0.0.16
### Patch Changes
- Updated dependencies [249eecb]
- jazz-tools@0.8.39
- jazz-react@0.8.39
- jazz-react-auth-clerk@0.8.39
## 0.0.15
### Patch Changes
- jazz-react@0.8.38
- jazz-react-auth-clerk@0.8.38
- jazz-tools@0.8.38
## 0.0.14
### Patch Changes

View File

@@ -4,60 +4,30 @@ This is an example of how to use clerk authentication with Jazz.
Live version: https://clerk-demo.jazz.tools
## Getting started
## Installing & running the example locally
You can either
1. Clone the jazz repository, and run the app within the monorepo.
2. Or create a new Jazz project using this example as a template.
(This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
### Using the example as a template
Create a new Jazz project, and use this example as a template.
Start by downloading the [jazz repository](https://github.com/garden-co/jazz):
```bash
npm create jazz-app@latest --start clerk --project-name clerk
```
or
```bash
npx create-jazz-app@latest --start clerk --project-name clerk
npx degit gardencmp/jazz jazz
```
Go to the new project directory.
Go to the clerk example directory:
```bash
cd clerk
cd jazz/examples/clerk
```
Run the dev server.
```bash
npm run dev
```
### Using the monorepo
This requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation).
Clone the jazz repository.
```bash
git clone https://github.com/garden-co/jazz.git
```
Install and build dependencies.
Install and build dependencies:
```bash
pnpm i && npx turbo build
```
Go to the example directory.
```bash
cd jazz/examples/clerk/
```
Start the dev server.
Start the dev server:
```bash
pnpm dev
```
Open [http://localhost:5173](http://localhost:5173) with your browser to see the result.
## 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.

View File

@@ -1,7 +1,7 @@
{
"name": "clerk",
"private": true,
"version": "0.0.29",
"version": "0.0.14",
"type": "module",
"scripts": {
"dev": "vite",
@@ -12,9 +12,9 @@
},
"dependencies": {
"@clerk/clerk-react": "^5.4.1",
"jazz-react": "workspace:*",
"jazz-react-auth-clerk": "workspace:0.9.9",
"jazz-tools": "workspace:*",
"jazz-react": "workspace:*",
"jazz-react-auth-clerk": "workspace:0.8.37",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},

View File

@@ -1,4 +1,4 @@
import { useAccount } from "jazz-react";
import { useAccount } from "./main";
function App() {
const { me, logOut } = useAccount();

View File

@@ -1,10 +1,10 @@
import { ClerkProvider, SignInButton, useClerk } from "@clerk/clerk-react";
import { createJazzReactApp } from "jazz-react";
import { useJazzClerkAuth } from "jazz-react-auth-clerk";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { JazzProvider } from "jazz-react";
// Import your publishable key
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
@@ -13,6 +13,9 @@ if (!PUBLISHABLE_KEY) {
throw new Error("Add your Clerk publishable key to the .env.local file");
}
const Jazz = createJazzReactApp();
export const { useAccount, useCoState } = Jazz;
function JazzAndAuth({ children }: { children: React.ReactNode }) {
const clerk = useClerk();
const [auth, state] = useJazzClerkAuth(clerk);
@@ -23,12 +26,12 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
<div key={error}>{error}</div>
))}
{clerk.user && auth ? (
<JazzProvider
<Jazz.Provider
auth={auth}
peer="wss://cloud.jazz.tools/?key=minimal-auth-clerk-example@garden.co"
>
{children}
</JazzProvider>
</Jazz.Provider>
) : (
<SignInButton />
)}

View File

@@ -1,8 +0,0 @@
{
"build": {
"env": {
"APP_NAME": "clerk-demo"
}
},
"ignoreCommand": "node ../../ignore-vercel-build.js"
}

Some files were not shown because too many files have changed in this diff Show More