Compare commits

..

138 Commits

Author SHA1 Message Date
Marina Orlova
2241ad4089 rename folder back to e2e/BinaryCoStream 2024-11-18 20:07:39 +01:00
Marina Orlova
d348c2d021 Add changeset 2024-11-18 19:05:57 +01:00
Marina Orlova
e5f68904ed Rename BinaryCoStream to FileStream 2024-11-18 19:03:42 +01:00
Marina Orlova
6902b5bb91 Add changeset for Rename CoStream to CoFeed 2024-11-18 17:38:17 +01:00
Anselm Eickhoff
756528e18e Merge pull request #790 from gardencmp/trishalim-jazz-474
Fix: page reloads when clicking a link inside mdx
2024-11-18 15:12:11 +00:00
Anselm Eickhoff
466e587fba Merge pull request #794 from gardencmp/benjamin-jazz-502
PassphraseAuth - Move saving credentials into `saveCredentials`
2024-11-18 15:11:35 +00:00
Anselm Eickhoff
22caae870d Merge pull request #789 from gardencmp/docs/getting-started
Rewrite docs introduction
2024-11-18 15:10:59 +00:00
Anselm Eickhoff
435fc0c917 Merge pull request #768 from gardencmp/fuzzyobject-jazz-491
Enhance onboarding test - add page context
2024-11-18 15:05:52 +00:00
Anselm Eickhoff
3dfce294d4 Merge pull request #777 from gardencmp/benjamin-jazz-481
Fix readme for jazz-react-auth-clerk
2024-11-18 15:05:18 +00:00
Anselm Eickhoff
90be185094 Merge pull request #784 from gardencmp/vscode-settings
chore: remove .vscode/settings.json and add it to .gitignore
2024-11-18 15:04:46 +00:00
Anselm Eickhoff
9eac8633b5 Merge pull request #792 from gardencmp/benjamin-jazz-495
Fix PasskeyAuth forgetting authentication on reload
2024-11-18 15:04:30 +00:00
Anselm Eickhoff
007c462362 Merge pull request #783 from gardencmp/benjamin-jazz-497
Tweak 'Docs coming soon' copy
2024-11-18 15:03:41 +00:00
Benjamin S. Leveritt
e442bb820b Add changeset 2024-11-18 14:35:58 +00:00
Benjamin S. Leveritt
3f7235c6f7 Moves saving credentials into saveCredentials
To match other implementations
2024-11-18 14:21:43 +00:00
Benjamin S. Leveritt
56d94bd8cd Add saveCredentials on successful login 2024-11-18 13:55:29 +00:00
Anselm Eickhoff
cb937c4335 Merge pull request #757 from gardencmp/fuzzyobject-jazz-460
Rename CoStream to CoFeed
2024-11-18 13:45:14 +00:00
Trisha Lim
b4e31657a0 Fix typo 2024-11-18 12:36:39 +00:00
Trisha Lim
f6bf75890a fix: page reloads when clicking a link inside mdx 2024-11-18 12:16:37 +00:00
Trisha Lim
5bb1ad4c42 Move vue up the nav 2024-11-18 12:05:45 +00:00
Trisha Lim
3ed74b6b1d Write intro in react guide 2024-11-18 12:00:34 +00:00
Trisha Lim
823a343fe1 Rewrite docs intro page 2024-11-18 11:56:29 +00:00
Guido D'Orsi
5b3c3fff7b chore: remove .vscode/settings.json and add it to .gitignore 2024-11-18 11:46:43 +01:00
Benjamin S. Leveritt
e3f1c49fdd Tweak copy 2024-11-18 10:10:50 +00:00
Benjamin S. Leveritt
6cc7a905f8 Fix readme 2024-11-18 09:06:42 +00:00
Guido D'Orsi
5f12e31a80 test(onboarding): increase the waiting to 3s 2024-11-16 14:52:38 +01:00
Guido D'Orsi
5f96a6fef2 choe(onboarding): remove the local sync logic from the playwright config and support peer override in the app 2024-11-16 14:39:52 +01:00
Guido D'Orsi
08db39ef2f test(onboarding): move the timeout waiting before the invite navigation 2024-11-16 14:38:53 +01:00
Marina Orlova
2b520e938e Take out acceptInvite 2024-11-16 13:16:31 +01:00
Marina Orlova
63568273db Take out acceptInvite 2024-11-16 13:12:29 +01:00
Marina Orlova
16702702c8 fix onboarding test - add context 2024-11-16 13:12:29 +01:00
Guido D'Orsi
6610aea708 Merge pull request #761 from gardencmp/changeset-release/main
Version Packages
2024-11-15 20:30:51 +01:00
github-actions[bot]
f6b6c01852 Version Packages 2024-11-15 18:40:37 +00:00
Trisha Lim
63f9aa6b97 fix: active route styling on docs 2024-11-15 18:39:20 +00:00
pax-k
822eb22da6 fix(docs): fix github URL reference 2024-11-15 20:22:46 +02:00
Anselm Eickhoff
fdf3c6e93d Merge pull request #742 from gardencmp/vue-docs
Vue docs
2024-11-15 18:06:41 +00:00
Guido D'Orsi
de38161023 Merge pull request #770 from gardencmp/gudorsi-jazz-492
fix(useAcceptInvite): trigger the onAccept callback on hashchange
2024-11-15 18:50:15 +01:00
Guido D'Orsi
59cc64d697 fix(useAcceptInvite): trigger the onAccept callback on hashchange 2024-11-15 18:33:27 +01:00
pax-k
55c249834a chore(docs): cleanup 2024-11-15 19:26:16 +02:00
pax-k
dd189abd4a chore: pnpm lock 2024-11-15 19:16:32 +02:00
pax-k
df1a8e9c99 feat: vue docs 2024-11-15 19:13:19 +02:00
pax-k
259467ac3f Merge branch 'main' into vue-docs 2024-11-15 19:09:34 +02:00
Anselm Eickhoff
9a93ab2476 Merge pull request #767 from gardencmp/aeplay-jazz-447
Implement dynamic tables of content
2024-11-15 17:02:39 +00:00
Anselm
337272bbdc Merge branch 'main' into aeplay-jazz-447 2024-11-15 16:56:36 +00:00
pax-k
464ef7d9c3 chore: cleanup 2024-11-15 18:50:27 +02:00
pax-k
8727089654 Merge branch 'aeplay-jazz-447' of https://github.com/gardencmp/jazz into aeplay-jazz-447 2024-11-15 18:45:57 +02:00
pax-k
753bceddb3 fix: SSG for docs 2024-11-15 18:44:47 +02:00
Trisha Lim
8202996108 fix: TOC grows too wide if text is long 2024-11-15 16:31:21 +00:00
Trisha Lim
1385c8a66f Remove custom IDs on headings in mdx files 2024-11-15 15:48:02 +00:00
Anselm
2a72942f8e Implement dynamic ToCs 2024-11-15 15:34:35 +00:00
Anselm Eickhoff
e16836becc Merge pull request #765 from gardencmp/trishalim-jazz-480
Move examples page outside of docs
2024-11-15 15:21:06 +00:00
Trisha Lim
134a101a52 Center align book app thumbnail 2024-11-15 14:11:12 +00:00
Trisha Lim
3fc6030776 Update music player thumbnail 2024-11-15 14:08:40 +00:00
Anselm Eickhoff
8aebe61bf9 Merge pull request #762 from gardencmp/trishalim-jazz-440
Put social icons back to nav
2024-11-15 13:58:08 +00:00
Trisha Lim
8c9e807c19 Move examples page outside of docs 2024-11-15 12:07:42 +00:00
Trisha Lim
e25e4080c7 Reduce large prose font size 2024-11-15 12:04:17 +00:00
Trisha Lim
70a8353fe6 Remove roadmap link from footer 2024-11-15 11:53:54 +00:00
Trisha Lim
185b0d866a Replace Twitter with X 2024-11-15 11:53:41 +00:00
Trisha Lim
717667ac4c Fix color of garden logo 2024-11-15 11:51:24 +00:00
Trisha Lim
a0151caf02 Put social icons back to nav 2024-11-15 11:48:19 +00:00
Guido D'Orsi
c259331f57 Merge pull request #752 from gardencmp/reuse-crypto
fix: reuse the crypto instance when creating the browser context
2024-11-15 12:33:35 +01:00
Guido D'Orsi
f6bc8afa06 chore: changeset 2024-11-15 12:21:47 +01:00
Anselm Eickhoff
cee0aea5e6 Merge pull request #713 from gardencmp/trishalim-jazz-440
Redesign main nav on desktop
2024-11-15 11:20:28 +00:00
Anselm Eickhoff
c0e11f543d Merge pull request #741 from gardencmp/marina-onboarding-example
Onboarding example
2024-11-15 10:57:51 +00:00
Marina Orlova
b38f902d70 Add backward-compatible aliases for CoFeed* 2024-11-14 22:30:58 +01:00
Marina Orlova
a6085094a7 Rename CoStream -> CoFeed in jazz-tools 2024-11-14 18:57:44 +01:00
pax
df359ab6dd Update selfish-wolves-shout.md 2024-11-14 17:01:28 +02:00
Anselm Eickhoff
d89da07d42 Merge pull request #753 from gardencmp/bensleveritt-patch-1
Fix mesh reference
2024-11-14 11:37:46 +00:00
pax
94098c1f1f Merge pull request #755 from gardencmp/JAZZ-485-docs-pin-react-native-deps
Updated react-native docs and removed babel config section
2024-11-14 12:07:25 +02:00
pax-k
e71e83a217 Merge branch 'main' into JAZZ-485-docs-pin-react-native-deps 2024-11-14 11:58:30 +02:00
pax-k
df0b21caed chore: pnpm lock 2024-11-14 11:58:06 +02:00
Anselm Eickhoff
6921e621d7 Merge pull request #749 from gardencmp/changeset-release/main
Version Packages
2024-11-14 09:41:28 +00:00
pax-k
ec9e03c266 fix: updated react-native docs and removed babel config section 2024-11-14 11:40:27 +02:00
github-actions[bot]
1b0ef401fb Version Packages 2024-11-14 09:29:38 +00:00
pax
1833983b8d Merge pull request #754 from gardencmp/jazz-tools-ts-target
Change jazz-tools TS target to ES2021
2024-11-14 11:28:21 +02:00
pax-k
149ca97c48 chore: changeset 2024-11-14 11:15:25 +02:00
pax-k
f01a7621b0 fix: change jazz-tools TS target to ES2021 2024-11-14 11:13:13 +02:00
Benjamin S. Leveritt
e6a1e2f169 Fix mesh reference 2024-11-14 06:32:10 +00:00
Guido D'Orsi
6413059709 fix: assign a unique name to the IndexedDB storage peer 2024-11-13 20:14:22 +01:00
Guido D'Orsi
3c6fd37aba fix: reuse the crypto instance when creating the browser context 2024-11-13 18:39:49 +01:00
Marina Orlova
8dc90a3554 Fixed Jazz workspace versions 2024-11-13 18:26:15 +01:00
Marina Orlova
51b1818de6 Fix group creation 2024-11-13 16:19:48 +01:00
Anselm Eickhoff
ec7c416097 Merge pull request #744 from gardencmp/better-known-state
fix: dispatch more updates to knownState
2024-11-13 14:16:50 +00:00
Guido D'Orsi
0f30eeaec6 chore: changeset 2024-11-13 15:12:18 +01:00
Guido D'Orsi
5a3cf04ba7 fix: dispatch more updates to knownState 2024-11-13 12:32:24 +01:00
pax-k
1fe74be8af fix(jazz-vue): useCoState accepts id as either a ref or a plain value 2024-11-13 12:47:05 +02:00
pax-k
376032f54c chore: cleanup 2024-11-13 11:39:23 +02:00
Anselm Eickhoff
d6e744d948 Merge pull request #738 from gardencmp/changeset-release/main 2024-11-12 22:11:31 +00:00
github-actions[bot]
23b3acb58c Version Packages 2024-11-12 22:10:32 +00:00
Anselm Eickhoff
f32d0c1fad Merge pull request #743 from gardencmp/fix/react-provider-multiple-storage
fix: fixes the react provider intialization when multiple storage options are provided
2024-11-12 22:09:16 +00:00
Guido D'Orsi
a69ed0b7cd chore: changeset 2024-11-12 22:43:19 +01:00
Guido D'Orsi
b4d7024b98 fix: fixes the react provider intialization when multiple storage options are provided 2024-11-12 22:42:27 +01:00
Marina Orlova
222a2ce2e6 Fixed test 2024-11-12 20:41:26 +01:00
Marina Orlova
34a50a9173 Adds the app into Playwright Tests job 2024-11-12 20:28:15 +01:00
Marina Orlova
72c6198ef7 Adds tests 2024-11-12 20:28:15 +01:00
Marina Orlova
e4df1048c8 pnpm-lock 2024-11-12 20:28:15 +01:00
Marina Orlova
e8abd06406 Fix schema 2024-11-12 20:28:15 +01:00
Marina Orlova
968c2bd699 Onboarding app: Make tests run 2024-11-12 20:28:15 +01:00
Marina Orlova
12b6a3b291 Add path alias 2024-11-12 20:28:15 +01:00
Marina Orlova
c9f89e9c32 Adds onboarding example 2024-11-12 20:28:15 +01:00
pax-k
d27cff5c67 feat(docs): added VueJS docs 2024-11-12 20:41:53 +02:00
pax-k
a734530cc3 chore: added changeset 2024-11-12 19:55:58 +02:00
pax-k
cc51926d14 chore: biome fix 2024-11-12 19:53:52 +02:00
Guido D'Orsi
c0395dd0a3 Merge pull request #723 from gardencmp/feature/typescript-as-dev
fix: set up typescript as dev dependency
2024-11-12 18:48:28 +01:00
Guido D'Orsi
51d7ca09d9 Merge remote-tracking branch 'origin/main' into feature/typescript-as-dev 2024-11-12 18:41:56 +01:00
pax-k
fce808cc48 fix(chat-vue): pass computed(id) to useCoState 2024-11-12 19:37:49 +02:00
pax-k
4a9217eb25 fix(jazz-vue): useCoState reactive to id 2024-11-12 19:36:46 +02:00
pax-k
69df98f4fa feat(todo-vue): implemented a Todo demo app for jazz-vue 2024-11-12 19:35:59 +02:00
Anselm Eickhoff
cf0a38d6bf Merge pull request #739 from gardencmp/fix/autologin
Move auto login check to useEffect
2024-11-12 10:56:35 +00:00
Trisha Lim
dd9b13fbaa Add changeset 2024-11-11 20:37:22 +00:00
Trisha Lim
6a982a29cb Move auto login check to useEffect 2024-11-11 20:35:41 +00:00
Anselm Eickhoff
ebc1b03158 Merge pull request #737 from gardencmp/feat/demo-auth-user-prop 2024-11-11 19:41:24 +00:00
Trisha Lim
adaf01f7fa Add bg blur effect on popover 2024-11-11 19:37:41 +00:00
Trisha Lim
c048f4eda9 Increase spacing in popover 2024-11-11 19:37:41 +00:00
Trisha Lim
b06272ff17 Layout fix 2024-11-11 19:37:40 +00:00
Trisha Lim
ac37bfab2d Close popover on click of link 2024-11-11 19:37:40 +00:00
Trisha Lim
d8e50824cb Add description and icon to mega menu items 2024-11-11 19:37:40 +00:00
Trisha Lim
29b27291a3 Links placement in desktop nav 2024-11-11 19:37:40 +00:00
Trisha Lim
efa1b60585 Add mobile nav 2024-11-11 19:37:39 +00:00
Trisha Lim
9d0b39c5cb Add popover menu to main nav using headless 2024-11-11 19:37:18 +00:00
Trisha Lim
2a02a5b212 Remove unused shadcn configs 2024-11-11 19:37:18 +00:00
Trisha Lim
c6931b82a0 Add changeset 2024-11-11 18:58:22 +00:00
Anselm Eickhoff
279e2202ba Merge pull request #710 from gardencmp/trishalim-jazz-455 2024-11-11 18:40:32 +00:00
Anselm Eickhoff
ec2324519e Merge pull request #735 from gardencmp/feat/demo-auth-user-prop 2024-11-11 18:38:46 +00:00
Anselm Eickhoff
5932f50c68 Merge pull request #699 from gardencmp/trishalim-jazz-458 2024-11-11 18:36:30 +00:00
Anselm Eickhoff
00906e5d08 Merge pull request #732 from gardencmp/fix/todo-demo-link 2024-11-11 18:35:59 +00:00
Trisha Lim
7bc5fca440 Add user prop to demo auth to skip login on demos 2024-11-11 16:35:55 +00:00
Trisha Lim
e30eb224ae Update demo links 2024-11-11 16:20:09 +00:00
Trisha Lim
983ea7cf03 Fix link to demo for pets example 2024-11-11 16:15:12 +00:00
Trisha Lim
ad2d453e33 Thumbnail fixes for mobile/tablet 2024-11-11 16:13:40 +00:00
Trisha Lim
15d711f6de Formatting fixes 2024-11-11 16:13:40 +00:00
Trisha Lim
7441a7d3d8 Formatting fixes 2024-11-11 16:13:40 +00:00
Trisha Lim
0d756209e9 Layout for mobile 2024-11-11 16:13:40 +00:00
Trisha Lim
b20c2ca173 Add images, description to examples 2024-11-11 16:13:40 +00:00
Trisha Lim
987a186db3 Add tech and features to example apps 2024-11-11 16:13:40 +00:00
Trisha Lim
f4e0b59fa1 Indicate active page on side nav 2024-11-11 16:13:31 +00:00
Trisha Lim
0d2112d8d0 Fix link to demo for todo example 2024-11-11 12:40:31 +00:00
Guido D'Orsi
3ef3ff3db9 chore: changeset 2024-11-08 12:22:34 +00:00
Guido D'Orsi
549ec2047f fix: set up typescript as dev dependency 2024-11-08 12:21:50 +00:00
213 changed files with 4593 additions and 1280 deletions

View File

@@ -0,0 +1,5 @@
---
"jazz-tools": patch
---
Rename BinaryCoStream to FileStream

View File

@@ -0,0 +1,5 @@
---
"jazz-browser": patch
---
Persist PasskeyAuth credentials on reload

View File

@@ -0,0 +1,5 @@
---
"jazz-tools": patch
---
Rename CoStream to CoFeed

View File

@@ -17,6 +17,7 @@ jobs:
"password-manager",
"pets",
"todo",
"onboarding",
]
steps:
@@ -55,4 +56,4 @@ jobs:
run: |
pnpm install
pnpm turbo build;
working-directory: ./examples/${{ matrix.example }}
working-directory: ./examples/${{ matrix.example }}

View File

@@ -13,7 +13,7 @@ jobs:
continue-on-error: true
strategy:
matrix:
project: ["e2e/BinaryCoStream", "e2e/CoValues", "examples/chat", "examples/music-player", "examples/pets"]
project: ["e2e/BinaryCoStream", "e2e/CoValues", "examples/chat", "examples/music-player", "examples/pets", "examples/onboarding"]
steps:
- uses: actions/checkout@v3

4
.gitignore vendored
View File

@@ -15,4 +15,6 @@ coverage
# Playwright
test-results
.husky
.husky
.vscode/settings.json

View File

@@ -1,3 +0,0 @@
{
"editor.defaultFormatter": "biomejs.biome"
}

View File

@@ -15,6 +15,6 @@ For community and support, please join our [Discord](https://discord.gg/utDMjHYg
- Homepage: [jazz.tools](https://jazz.tools)
- Docs: [jazz.tools/docs](https://jazz.tools/docs)
- Community & support: [Discord](https://discord.gg/utDMjHYg42)
- Updates: [Twitter](https://twitter.com/jazz_tools) & [Email](https://gcmp.io/news)
- Updates: [X](https://x.com/jazz_tools) & [Email](https://gcmp.io/news)
Copyright 2024 — Garden Computing, Inc.
Copyright 2024 — Garden Computing, Inc.

View File

@@ -7,7 +7,7 @@
},
"files": {
"ignoreUnknown": false,
"ignore": ["jazz-tools.json"]
"ignore": ["jazz-tools.json", "**/ios/**", "**/android/**"]
},
"formatter": {
"enabled": true,

View File

@@ -1,5 +1,32 @@
# @jazz-e2e/binarycostream
## 0.0.100
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
## 0.0.99
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.98
### Patch Changes
- Updated dependencies [dd9b13f]
- Updated dependencies [a69ed0b]
- Updated dependencies [3ef3ff3]
- Updated dependencies [c6931b8]
- jazz-react@0.8.20
## 0.0.97
### Patch Changes

View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/png" href="/jazz-logo.png" />
<link rel="stylesheet" href="/src/index.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz BinaryCoStream Tests</title>
<title>Jazz FileStream Tests</title>
</head>
<body>
<div id="root"></div>

View File

@@ -1,7 +1,7 @@
{
"name": "@jazz-e2e/binarycostream",
"name": "@jazz-e2e/filestream",
"private": true,
"version": "0.0.97",
"version": "0.0.100",
"type": "module",
"scripts": {
"dev": "vite",
@@ -13,11 +13,11 @@
"test:ui": "playwright test --ui"
},
"dependencies": {
"cojson": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"hash-slash": "workspace:0.2.1",
"is-ci": "^3.0.1",
"jazz-react": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.22",
"jazz-tools": "workspace:0.8.21",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},

View File

@@ -1,4 +1,4 @@
import { Account, BinaryCoStream, ID } from "jazz-tools";
import { Account, FileStream, ID } from "jazz-tools";
import { useEffect } from "react";
import { useAccount, useCoState } from "./jazz";
import { waitForCoValue } from "./lib/waitForCoValue";
@@ -15,7 +15,7 @@ async function getUploadedFile(me: Account, uploadedFileId: ID<UploadedFile>) {
uploadedFile.coMapDownloaded = true;
await BinaryCoStream.loadAsBlob(uploadedFile._refs.file.id, me);
await FileStream.loadAsBlob(uploadedFile._refs.file.id, me);
return uploadedFile;
}

View File

@@ -1,4 +1,4 @@
import { Account, BinaryCoStream, Group } from "jazz-tools";
import { Account, FileStream, Group } from "jazz-tools";
import { UploadedFile } from "../schema";
export async function generateTestFile(me: Account, bytes: number) {
@@ -8,7 +8,7 @@ export async function generateTestFile(me: Account, bytes: number) {
const ownership = { owner: group };
const testFile = UploadedFile.create(
{
file: await BinaryCoStream.createFromBlob(
file: await FileStream.createFromBlob(
new Blob(["1".repeat(bytes)], { type: "image/png" }),
ownership,
),

View File

@@ -1,7 +1,7 @@
import { BinaryCoStream, CoMap, co } from "jazz-tools";
import { CoMap, FileStream, co } from "jazz-tools";
export class UploadedFile extends CoMap {
file = co.ref(BinaryCoStream);
file = co.ref(FileStream);
syncCompleted = co.boolean;
coMapDownloaded = co.boolean;
}

View File

@@ -1,5 +1,32 @@
# @jazz-e2e/covalues
## 0.0.99
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
## 0.0.98
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.97
### Patch Changes
- Updated dependencies [dd9b13f]
- Updated dependencies [a69ed0b]
- Updated dependencies [3ef3ff3]
- Updated dependencies [c6931b8]
- jazz-react@0.8.20
## 0.0.96
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@jazz-e2e/covalues",
"private": true,
"version": "0.0.96",
"version": "0.0.99",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,33 @@
# jazz-example-book-shelf
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-example-book-shelf",
"version": "0.1.12",
"version": "0.1.15",
"private": true,
"scripts": {
"dev": "next dev",
@@ -11,9 +11,9 @@
},
"dependencies": {
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.19",
"jazz-react": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"jazz-browser-media-images": "workspace:0.8.22",
"jazz-react": "workspace:0.8.22",
"jazz-tools": "workspace:0.8.21",
"next": "14.2.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"

View File

@@ -1,5 +1,35 @@
# jazz-example-chat
## 0.0.99
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
- jazz-react-auth-clerk@0.8.22
## 0.0.98
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-react@0.8.21
- jazz-react-auth-clerk@0.8.21
## 0.0.97
### Patch Changes
- Updated dependencies [dd9b13f]
- Updated dependencies [a69ed0b]
- Updated dependencies [3ef3ff3]
- Updated dependencies [c6931b8]
- jazz-react@0.8.20
- jazz-react-auth-clerk@0.8.20
## 0.0.96
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat-clerk",
"private": true,
"version": "0.0.96",
"version": "0.0.99",
"type": "module",
"scripts": {
"dev": "vite",
@@ -17,11 +17,11 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.19",
"jazz-react-auth-clerk": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.22",
"jazz-react-auth-clerk": "workspace:0.8.22",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,30 @@
# chat-rn-clerk
## 1.0.15
### Patch Changes
- jazz-react-auth-clerk@0.8.22
## 1.0.14
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react-auth-clerk@0.8.21
- jazz-react-native@0.8.21
- jazz-react-native-media-images@0.8.17
## 1.0.13
### Patch Changes
- Updated dependencies [3ef3ff3]
- jazz-react-native-media-images@0.8.16
- jazz-react-native@0.8.20
- jazz-react-auth-clerk@0.8.20
## 1.0.12
### Patch Changes

View File

@@ -2,6 +2,6 @@ module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: ["nativewind/babel", "@babel/plugin-transform-class-static-block"],
plugins: ["nativewind/babel"],
};
};

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-clerk",
"main": "index.js",
"version": "1.0.12",
"version": "1.0.15",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",
@@ -65,7 +65,6 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/plugin-transform-class-static-block": "^7.24.7",
"@types/jest": "^29.5.3",
"@types/react": "^18.2.19",
"@types/react-test-renderer": "^18.0.7",

View File

@@ -1,5 +1,20 @@
# chat-rn
## 1.0.16
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react-native@0.8.21
## 1.0.15
### Patch Changes
- Updated dependencies [3ef3ff3]
- jazz-react-native@0.8.20
## 1.0.14
### Patch Changes

View File

@@ -2,6 +2,6 @@ module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: ["nativewind/babel", "@babel/plugin-transform-class-static-block"],
plugins: ["nativewind/babel"],
};
};

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.14",
"version": "1.0.16",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",
@@ -45,7 +45,6 @@
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/plugin-transform-class-static-block": "^7.24.7",
"@types/react": "^18.2.19",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3"

View File

@@ -1,5 +1,31 @@
# chat-vue
## 0.0.7
### Patch Changes
- a734530: fix useCoState reactivity
- Updated dependencies [f6bc8af]
- Updated dependencies [a734530]
- jazz-browser@0.8.22
- jazz-vue@0.8.12
## 0.0.6
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-browser@0.8.21
- jazz-vue@0.8.11
## 0.0.5
### Patch Changes
- jazz-browser@0.8.20
- jazz-vue@0.8.10
## 0.0.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-vue",
"version": "0.0.4",
"version": "0.0.7",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,5 +1,32 @@
# jazz-example-chat
## 0.0.101
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
## 0.0.100
### Patch Changes
- Updated dependencies [0f30eea]
- Updated dependencies [149ca97]
- cojson@0.8.21
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.99
### Patch Changes
- Updated dependencies [dd9b13f]
- Updated dependencies [a69ed0b]
- Updated dependencies [3ef3ff3]
- Updated dependencies [c6931b8]
- jazz-react@0.8.20
## 0.0.98
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.98",
"version": "0.0.101",
"type": "module",
"scripts": {
"dev": "vite",
@@ -18,10 +18,10 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.22",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

View File

@@ -1,5 +1,13 @@
# jazz-example-inspector
## 0.0.73
### Patch Changes
- Updated dependencies [0f30eea]
- cojson@0.8.21
- cojson-transport-ws@0.8.21
## 0.0.72
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector",
"private": true,
"version": "0.0.72",
"version": "0.0.73",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,8 +16,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.19",
"cojson-transport-ws": "workspace:0.8.19",
"cojson": "workspace:0.8.21",
"cojson-transport-ws": "workspace:0.8.21",
"hash-slash": "workspace:0.2.1",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",

View File

@@ -1,5 +1,30 @@
# jazz-example-musicplayer
## 0.0.21
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
## 0.0.20
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.19
### Patch Changes
- Updated dependencies [dd9b13f]
- Updated dependencies [a69ed0b]
- Updated dependencies [3ef3ff3]
- Updated dependencies [c6931b8]
- jazz-react@0.8.20
## 0.0.18
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.18",
"version": "0.0.21",
"type": "module",
"scripts": {
"dev": "vite",
@@ -18,8 +18,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.22",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -1,11 +1,4 @@
import {
Account,
BinaryCoStream,
CoList,
CoMap,
Profile,
co,
} from "jazz-tools";
import { Account, CoList, CoMap, FileStream, Profile, co } from "jazz-tools";
/** Walkthrough: Defining the data model with CoJSON
*
@@ -36,12 +29,12 @@ export class MusicTrack extends CoMap {
sourceTrack = co.optional.ref(MusicTrack);
/**
* In Jazz you can files using BinaryCoStream.
* In Jazz you can upload files using FileStream.
*
* As for any other coValue the music files we put inside BinaryCoStream
* As for any other coValue the music files we put inside FileStream
* is available offline and end-to-end encrypted 😉
*/
file = co.ref(BinaryCoStream);
file = co.ref(FileStream);
waveform = co.ref(MusicTrackWaveform);
}

View File

@@ -70,7 +70,11 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
return (
<>
<Jazz.Provider auth={auth} peer={peer}>
<Jazz.Provider
storage={["singleTabOPFS", "indexedDB"]}
auth={auth}
peer={peer}
>
{children}
</Jazz.Provider>
<DemoAuthBasicUI appName="Jazz Music Player" state={state} />

View File

@@ -1,5 +1,5 @@
import { getAudioFileData } from "@/lib/audio/getAudioFileData";
import { BinaryCoStream, Group } from "jazz-tools";
import { FileStream, Group } from "jazz-tools";
import {
ListOfTracks,
MusicTrack,
@@ -35,14 +35,14 @@ export async function uploadMusicTracks(
for (const file of files) {
const data = await getAudioFileData(file);
// We transform the file blob into a BinaryCoStream
// We transform the file blob into a FileStream
// making it a collaborative value that is encrypted, easy
// to share across devices and users and available offline!
const binaryCoStream = await BinaryCoStream.createFromBlob(file, ownership);
const fileStream = await FileStream.createFromBlob(file, ownership);
const musicTrack = MusicTrack.create(
{
file: binaryCoStream,
file: fileStream,
duration: data.duration,
waveform: MusicTrackWaveform.create({ data: data.waveform }, ownership),
title: file.name,
@@ -98,7 +98,7 @@ export async function addTrackToPlaylist(
* won't need to clone values to have this kind of sharing granularity
*/
const ownership = { owner: playlist._owner };
const blob = await BinaryCoStream.loadAsBlob(track._refs.file.id, account);
const blob = await FileStream.loadAsBlob(track._refs.file.id, account);
const waveform = await MusicTrackWaveform.load(
track._refs.waveform.id,
account,
@@ -109,7 +109,7 @@ export async function addTrackToPlaylist(
const trackClone = MusicTrack.create(
{
file: await BinaryCoStream.createFromBlob(blob, ownership),
file: await FileStream.createFromBlob(blob, ownership),
duration: track.duration,
waveform: MusicTrackWaveform.create({ data: waveform.data }, ownership),
title: track.title,

View File

@@ -1,7 +1,7 @@
import { MusicTrack, Playlist } from "@/1_schema";
import { usePlayMedia } from "@/lib/audio/usePlayMedia";
import { usePlayState } from "@/lib/audio/usePlayState";
import { BinaryCoStream, ID } from "jazz-tools";
import { FileStream, ID } from "jazz-tools";
import { useRef, useState } from "react";
import { useAccount } from "./2_main";
import { updateActivePlaylist, updateActiveTrack } from "./4_actions";
@@ -27,7 +27,7 @@ export function useMediaPlayer() {
setLoading(track.id);
const file = await BinaryCoStream.loadAsBlob(track._refs.file.id, me);
const file = await FileStream.loadAsBlob(track._refs.file.id, me);
if (!file) {
setLoading(null);

26
examples/onboarding/.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
playwright-report

View File

@@ -0,0 +1,9 @@
# jazz-example-onboarding
## 0.0.2
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
- jazz-browser-media-images@0.8.22

View File

@@ -0,0 +1,4 @@
FROM caddy:2.7.3-alpine
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
COPY ./dist /usr/share/caddy/

View File

@@ -0,0 +1 @@
# vite-ts-react-tailwind

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz onboarding example</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,36 @@
{
"name": "jazz-example-onboarding",
"private": true,
"version": "0.0.2",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write",
"preview": "vite preview",
"test": "playwright test",
"test:ui": "playwright test --ui",
"sync": "jazz-run sync"
},
"dependencies": {
"jazz-browser-media-images": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"jazz-run": "workspace:*",
"@playwright/test": "^1.46.1",
"@types/react": "^18.2.19",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.27",
"tailwindcss": "^3.3.2",
"typescript": "^5.3.3",
"is-ci": "^3.0.1",
"vite": "^5.0.10"
}
}

View File

@@ -0,0 +1,46 @@
import { defineConfig, devices } from "@playwright/test";
import isCI from "is-ci";
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: isCI,
/* Retry on CI only */
retries: isCI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: isCI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: "http://localhost:5173/",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
permissions: ["clipboard-read", "clipboard-write"],
},
/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
/* Run your local dev server before starting the tests */
webServer: [
{
command: "pnpm preview --port 5173",
url: "http://localhost:5173/",
reuseExistingServer: !isCI,
},
],
});

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

View File

@@ -0,0 +1,100 @@
import { Button } from "@/components/Button.tsx";
import { useAcceptInvite, useAccount, useCoState } from "@/main.tsx";
import { EmployeeList } from "@/pages/EmployeeList.tsx";
import { EmployeeOnboading } from "@/pages/EmployeeOnboarding.tsx";
import { NewEmployee } from "@/pages/NewEmployee.tsx";
import { CoEmployee, EmployeeCoList } from "@/schema.ts";
import { ID } from "jazz-tools";
import { useEffect } from "react";
import {
RouterProvider,
createHashRouter,
useNavigate,
useParams,
} from "react-router-dom";
function ImportEmployee({
employeeListCoId,
}: { employeeListCoId: ID<EmployeeCoList> }) {
const { employeeCoId } = useParams();
const navigate = useNavigate();
const employees = useCoState(EmployeeCoList, employeeListCoId, [{}]);
const employee = useCoState(CoEmployee, employeeCoId as ID<CoEmployee>, {});
useEffect(() => {
if (!employee || !employees) return;
const exists = employees.find((employee) => employeeCoId === employee.id);
if (!exists) {
employees.push(employee);
}
navigate("/");
}, [employee, employees, navigate]);
return <div>Importing Employee ${employeeCoId} ...</div>;
}
function AcceptInvite() {
const navigate = useNavigate();
useAcceptInvite({
invitedObjectSchema: CoEmployee,
onAccept: (employeeCoId) => {
navigate(`/import/${employeeCoId}`);
},
});
return <p>Accepting invite...</p>;
}
function App() {
const { me, logOut } = useAccount();
const employeeCoListId = me.profile?._refs.employees.id;
const router = createHashRouter([
{
path: "/",
element: <EmployeeList employeeListCoId={employeeCoListId} />,
},
{
path: "employee/new",
element: <NewEmployee employeeListCoId={employeeCoListId} />,
},
{
path: "/employee/:employeeCoId",
element: <EmployeeOnboading />,
},
{
path: "/import/:employeeCoId",
element: <ImportEmployee employeeListCoId={employeeCoListId} />,
},
{
path: "/invite/*",
element: <AcceptInvite />,
},
]);
return (
<>
<header className="flex flex-wrap space-x-8 max-w-screen-lg m-2">
<h1 className="text-3xl font-extrabold">
Jazz Onboarding Flow example
</h1>
<Button
onClick={() => {
window.location.href = "/";
logOut();
}}
text="Log Out"
/>
</header>
<main className="ml-2">
{employeeCoListId && <RouterProvider router={router} />}
</main>
</>
);
}
export default App;

View File

@@ -0,0 +1,29 @@
const disabledClasses =
"text-white bg-gray-400 dark:bg-gray-500 cursor-not-allowed";
const regularClasses =
"text-white bg-gradient-to-r from-green-400 via-green-500 to-green-600 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-green-300 dark:focus:ring-green-800";
export function Button({
text,
onClick,
disabled,
...props
}: {
text: string;
onClick?: () => void;
disabled?: boolean;
}) {
return (
<button
{...props}
onClick={onClick}
type="button"
disabled={disabled}
className={`${
disabled ? disabledClasses : regularClasses
} text-base font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2`}
>
{text}
</button>
);
}

View File

@@ -0,0 +1,19 @@
import React from "react";
import { Link } from "react-router-dom";
export function ButtonLink({
to,
children,
}: {
to: string;
children: React.ReactNode;
}) {
return (
<Link
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
to={to}
>
{children}
</Link>
);
}

View File

@@ -0,0 +1,19 @@
import React from "react";
import { Link } from "react-router-dom";
export function NavLink({
to,
children,
}: {
to: string;
children: React.ReactNode;
}) {
return (
<Link
className="font-medium text-blue-600 dark:text-blue-500 hover:underline"
to={to}
>
{children}
</Link>
);
}

View File

@@ -0,0 +1,24 @@
import { useNavigate } from "react-router-dom";
import { Button } from "./Button.tsx";
export function NavigateBack() {
const navigate = useNavigate();
const canGoBack = window.history.state.idx !== 0;
if (!canGoBack) return null;
return (
<div>
<Button onClick={() => navigate(-1)} text="< Back" />
</div>
);
}
export function NavigateButton({ text, to }: { text: string; to: string }) {
const navigate = useNavigate();
return (
<div>
<Button onClick={() => navigate(to)} text={text} />
</div>
);
}

View File

@@ -0,0 +1,19 @@
import React from "react";
export function Stack({
children,
horizontal,
}: {
children: React.ReactNode;
horizontal?: boolean;
}) {
return (
<div
className={`container flex ${
horizontal ? "flex-row" : "flex-col"
} col ${horizontal ? "space-x-4 flex-wrap" : "space-y-4"} p-4`}
>
{children}
</div>
);
}

View File

@@ -0,0 +1,36 @@
import { ChangeEvent } from "react";
export function TextInput({
id,
value,
label,
onChange,
disabled,
}: {
id: string;
label: string;
value: string;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
disabled?: boolean;
}) {
return (
<div>
<label
htmlFor={id}
className="block mb-2 font-medium text-gray-900 dark:text-white"
>
{label}
</label>
<input
value={value}
onChange={onChange}
type="text"
id={id}
disabled={disabled}
className="disabled:cursor-not-allowed bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="John"
required
/>
</div>
);
}

View File

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

View File

@@ -0,0 +1,39 @@
import App from "@/App.tsx";
import React from "react";
import ReactDOM from "react-dom/client";
import "@/index.css";
import { HRAccount } from "@/schema.ts";
import { DemoAuthBasicUI, createJazzReactApp, useDemoAuth } from "jazz-react";
const Jazz = createJazzReactApp({
AccountSchema: HRAccount,
});
export const { useAccount, useCoState, useAcceptInvite } = Jazz;
const peer =
(new URL(window.location.href).searchParams.get(
"peer",
) as `ws://${string}`) ??
"wss://cloud.jazz.tools/?key=onboarding-example-jazz@gcmp.io";
function JazzAndAuth({ children }: { children: React.ReactNode }) {
const [auth, authState] = useDemoAuth();
return (
<>
<Jazz.Provider auth={auth} peer={peer}>
{children}
</Jazz.Provider>
{authState.state !== "signedIn" && (
<DemoAuthBasicUI appName="Jazz Onboarding" state={authState} />
)}
</>
);
}
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<JazzAndAuth>
<App />
</JazzAndAuth>
</React.StrictMode>,
);

View File

@@ -0,0 +1,50 @@
import { NavLink } from "@/components/NavLink.tsx";
import { NavigateButton } from "@/components/NavigateBack.tsx";
import { Stack } from "@/components/Stack.tsx";
import { useCoState } from "@/main.tsx";
import { CoEmployee, EmployeeCoList } from "@/schema.ts";
import { ID } from "jazz-tools";
export function EmployeeList({
employeeListCoId,
}: {
employeeListCoId: ID<EmployeeCoList>;
}) {
const employees = useCoState(EmployeeCoList, employeeListCoId, [{}]);
if (!employees) {
return <div>Loading...</div>;
}
return (
<Stack>
<NavigateButton to="/employee/new" text={"Add New Employee"} />
<ul className="max-w-md">
{employees.map((employee: CoEmployee) =>
employee.deleted ? null : (
<li key={employee.id} className="flex flex-row space-x-8 w-full">
<span>{employee._owner.myRole()}</span>
<span className="w-1/3">
<NavLink to={`/employee/${employee.id}`}>
{employee.name}
</NavLink>
</span>
{employee.finalStep?.done && <span></span>}
{employee._owner.myRole() === "admin" &&
!employee.finalStep?.done && (
<span
onClick={() => {
employee.deleted = true;
}}
className="cursor-pointer"
>
🗑
</span>
)}
</li>
),
)}
</ul>
</Stack>
);
}

View File

@@ -0,0 +1,240 @@
import { Button } from "@/components/Button.tsx";
import { NavigateBack } from "@/components/NavigateBack.tsx";
import { Stack } from "@/components/Stack.tsx";
import { TextInput } from "@/components/TextInput.tsx";
import { useCoState } from "@/main.tsx";
import { createImage } from "jazz-browser-media-images";
import { ProgressiveImg, createInviteLink } from "jazz-react";
import { CoMap, ID } from "jazz-tools";
import { ChangeEvent, ReactNode, useCallback } from "react";
import { useParams } from "react-router";
import {
CoDocUploadStep,
CoEmployee,
CoFinalStep,
CoInitialStep,
} from "../schema.ts";
const Card = ({
children,
title,
isDone,
isActive,
}: {
children: ReactNode;
title: string;
isDone: boolean;
isActive?: boolean;
}) => (
<div
className={`w-full p-4 bg-white border border-gray-200 rounded-lg shadow max-w-md ${
isActive ? "border-gray-900 hover:bg-green-50 shadow-xl" : ""
}`}
>
<Stack horizontal={true}>
<h5 className="mb-2 text-2xl text-gray-900">{title}</h5>
<h6 className="mb-2 text-2xl">
{isDone ? "✅" : isActive ? "❓" : "⌛"}
</h6>
</Stack>
{children}
</div>
);
const InfoCard = ({
initialStep,
canWrite,
}: {
initialStep: CoInitialStep;
canWrite: boolean;
}) => {
const isDisabled = !initialStep.isCurrentStep() || !canWrite;
return (
<Card
title="Personal Info"
isDone={initialStep?.done}
isActive={initialStep.isCurrentStep()}
>
<Stack>
<TextInput
disabled={isDisabled}
id="ssn"
label="Social Security Number"
value={initialStep.ssn || ""}
onChange={({ target: { value } }) => (initialStep.ssn = value)}
/>
<TextInput
disabled={isDisabled}
id="address"
label="Address"
value={initialStep.address || ""}
onChange={({ target: { value } }) => (initialStep.address = value)}
/>
{!initialStep.done && (
<Button
text={"Upload step >"}
disabled={!initialStep.ssn || !initialStep.address || isDisabled}
onClick={() => (initialStep.done = true)}
/>
)}
</Stack>
</Card>
);
};
const UploadCard = ({
uploadStep,
canWrite,
}: {
uploadStep: CoDocUploadStep;
canWrite: boolean;
}) => {
const isDisabled = !uploadStep.isCurrentStep() || !canWrite;
const onImageSelected = useCallback(
async (event: ChangeEvent<HTMLInputElement>) => {
if (!event.target.files) return;
const image = await createImage(event.target.files[0], {
owner: uploadStep._owner,
});
uploadStep.photo = image;
},
[uploadStep],
);
return (
<Card
title="Uploads"
isDone={uploadStep?.done}
isActive={uploadStep.isCurrentStep()}
>
<Stack>
{uploadStep.photo && (
<ProgressiveImg image={uploadStep.photo}>
{({ src }) => (
<img
className="max-h-full max-w-full rounded-l-sm rounded-r-md shadow-lg p-2"
src={src}
/>
)}
</ProgressiveImg>
)}
{!uploadStep.done && (
<>
<input
type="file"
disabled={isDisabled}
onChange={onImageSelected}
data-testid="file-upload"
/>
<Button
text={"Confirmation step >"}
disabled={isDisabled || !uploadStep.photo}
onClick={() => (uploadStep.done = true)}
/>
</>
)}
</Stack>
</Card>
);
};
const ConfirmationCard = ({
finalStep,
editable,
}: {
finalStep: CoFinalStep;
editable: boolean;
}) => {
const isDisabled = !finalStep.isCurrentStep() || !editable;
return (
<Card
title="Confirmation by admin"
isDone={finalStep?.done}
isActive={finalStep.isCurrentStep()}
>
<Stack>
{!finalStep.done && (
<Button
text="Confirmation by admin"
disabled={isDisabled}
onClick={() => (finalStep.done = true)}
/>
)}
</Stack>
</Card>
);
};
export function EmployeeOnboading() {
const { employeeCoId } = useParams();
const employee = useCoState(CoEmployee, employeeCoId as ID<CoEmployee>, {});
const handleInviteLinkCreation = useCallback(
(role: "reader" | "writer") => {
if (!employee) return;
const link = createInviteLink(employee, role);
navigator.clipboard.writeText(link);
alert("Invite link copied to clipboard!");
},
[employee],
);
const isMeWriter = (step: CoMap): boolean => {
return ["writer", "admin"].includes(step._owner.myRole() || "");
};
return (
<>
<Stack>
<Stack horizontal={true}>
<NavigateBack />
{employee?._owner.myRole() === "admin" && (
<Button
text={"Invite a co-worker"}
onClick={() => handleInviteLinkCreation("writer")}
/>
)}
</Stack>
<h2 className="mb-2 text-2xl text-gray-900 font-semibold">
{employee ? employee.name : "Loading..."}
</h2>
</Stack>
{employee && (
<Stack>
{employee.initialStep ? (
<InfoCard
initialStep={employee.initialStep}
canWrite={isMeWriter(employee.initialStep)}
/>
) : (
<div>Loading...</div>
)}
{employee.docUploadStep ? (
<UploadCard
uploadStep={employee.docUploadStep}
canWrite={isMeWriter(employee.docUploadStep)}
/>
) : (
<div>Loading...</div>
)}
{employee.finalStep ? (
<ConfirmationCard
finalStep={employee.finalStep}
editable={isMeWriter(employee.finalStep)}
/>
) : (
<div>Loading...</div>
)}
</Stack>
)}
</>
);
}

View File

@@ -0,0 +1,89 @@
import { Button } from "@/components/Button.tsx";
import { NavigateBack } from "@/components/NavigateBack.tsx";
import { Stack } from "@/components/Stack.tsx";
import { TextInput } from "@/components/TextInput.tsx";
import { useAccount, useCoState } from "@/main.tsx";
import { Group, ID } from "jazz-tools";
import { useCallback, useState } from "react";
import { useNavigate } from "react-router-dom";
import {
CoDocUploadStep,
CoEmployee,
CoFinalStep,
CoInitialStep,
EmployeeCoList,
} from "../schema.ts";
export function NewEmployee({
employeeListCoId,
}: {
employeeListCoId: ID<EmployeeCoList>;
}) {
const navigate = useNavigate();
const { me } = useAccount();
const employees = useCoState(EmployeeCoList, employeeListCoId, [{}]);
const [employeeName, setEmployeeName] = useState<string>("");
const createEmployee = useCallback(() => {
if (!employees) return;
const writerGroup = Group.create({ owner: me });
const readerGroup = Group.create({ owner: me });
readerGroup.addMember("everyone", "reader");
const initialStep = CoInitialStep.create(
{ done: false, type: "initial" },
{ owner: writerGroup },
);
const docUploadStep = CoDocUploadStep.create(
{ done: false, prevStep: initialStep, type: "upload" },
{ owner: writerGroup },
);
const finalStep = CoFinalStep.create(
{ done: false, prevStep: docUploadStep, type: "final" },
{ owner: readerGroup },
);
const employee = CoEmployee.create(
{
name: employeeName,
initialStep,
docUploadStep,
finalStep,
},
{ owner: writerGroup },
);
employees.push(employee);
setEmployeeName("");
}, [employeeName, employees]);
return (
<div className="w-96">
<Stack>
<NavigateBack />
<form>
<TextInput
label="Employee name"
id="employee-name"
value={employeeName}
onChange={({ target: { value } }) => setEmployeeName(value)}
/>
</form>
<Button
disabled={!employeeName}
onClick={() => {
createEmployee();
navigate("/");
}}
text="Create Employee"
/>
</Stack>
</div>
);
}

View File

@@ -0,0 +1,78 @@
import {
Account,
CoList,
CoMap,
ImageDefinition,
Profile,
co,
} from "jazz-tools";
type Steps = "initial" | "upload" | "final";
interface Step {
type: Steps;
prevStep: ReturnType<typeof co.ref> | undefined;
done: boolean;
isCurrentStep(): boolean;
}
export class CoInitialStep extends CoMap implements Step {
type = co.literal("initial");
ssn? = co.string;
address? = co.string;
done = co.boolean;
prevStep = co.null;
isCurrentStep() {
return !this.done;
}
}
export class CoDocUploadStep extends CoMap implements Step {
type = co.literal("upload");
prevStep = co.ref(CoInitialStep);
photo = co.ref(ImageDefinition, { optional: true });
done = co.boolean;
isCurrentStep() {
return !!(this.prevStep?.done && !this.done);
}
}
export class CoFinalStep extends CoMap implements Step {
type = co.literal("final");
prevStep = co.ref(CoDocUploadStep);
done = co.boolean;
isCurrentStep() {
return !!(this.prevStep?.done && !this.done);
}
}
export class CoEmployee extends CoMap {
name = co.string;
deleted? = co.boolean;
initialStep = co.ref(CoInitialStep);
docUploadStep = co.ref(CoDocUploadStep);
finalStep = co.ref(CoFinalStep);
}
export class EmployeeCoList extends CoList.Of(co.ref(CoEmployee)) {}
export class HRProfile extends Profile {
employees = co.ref(EmployeeCoList);
}
export class HRAccount extends Account {
profile = co.ref(HRProfile)!;
migrate(this: HRAccount, creationProps?: { name: string }) {
super.migrate(creationProps);
if (!this.profile._refs.employees) {
this.profile.employees = EmployeeCoList.create([], {
owner: this.profile._owner,
});
}
}
}

1
examples/onboarding/src/vite-env.d.ts vendored Normal file
View File

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

View File

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

View File

@@ -0,0 +1,117 @@
import {
Browser,
BrowserContext,
Page,
chromium,
expect,
test,
} from "@playwright/test";
import { EmployeeOnboardingPage } from "./pages/EmployeeOnboardingPage";
import { HomePage } from "./pages/HomePage";
import { LoginPage } from "./pages/LoginPage";
async function scrollToBottom(page: Page) {
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight);
});
}
const login = async ({
page,
userName,
loginAs = false,
}: {
page: Page;
userName: string;
loginAs?: boolean;
}) => {
const loginPage = new LoginPage(page);
await loginPage.goto("/");
if (loginAs) {
await loginPage.loginAs(userName);
} else {
await loginPage.fillUsername(userName);
await loginPage.signup();
}
};
test.describe("Admin onboarding flow", () => {
let browser: Browser;
let adminContext: BrowserContext;
let writerContext: BrowserContext;
test.beforeAll(async () => {
browser = await chromium.launch();
adminContext = await browser.newContext();
writerContext = await browser.newContext();
});
test.afterAll(async () => {
await adminContext.close();
await writerContext.close();
await browser.close();
});
test("Create and delete flow", async () => {
const adminPage = await adminContext.newPage();
await login({ page: adminPage, userName: "HR specialist" });
const adminHomePage = new HomePage(adminPage);
await adminHomePage.createEmployee("Paul");
await adminHomePage.createEmployee("Sean");
await adminHomePage.expectEmployee(["Sean", "admin"]);
await adminHomePage.expectEmployee(["Paul", "admin"]);
await adminHomePage.deleteEmployee("Sean");
await adminHomePage.expectEmployeeDeleted("Sean");
await adminPage.close();
});
test("Onboard flow", async () => {
const adminPage = await adminContext.newPage();
const writerPage = await writerContext.newPage();
const adminUser = "HR specialist";
const writerUser = "Invitee";
await login({ page: adminPage, userName: adminUser });
await login({ page: writerPage, userName: writerUser });
const adminHomePage = new HomePage(adminPage);
await adminHomePage.createEmployee("Paul");
await adminHomePage.expectEmployee(["Paul", "admin"]);
await adminHomePage.navigateToEmployeeOnboardingPage("Paul");
const adminOnboardingPage = new EmployeeOnboardingPage(adminPage);
// create invitation
const invitation = await adminOnboardingPage.getShareLink();
// Wait for the invitation to be synced
await writerPage.waitForTimeout(3000);
//fill out by invitee (writer)
await writerPage.goto(invitation);
const writerHomePage = new HomePage(writerPage);
await writerHomePage.expectEmployee(["Paul", "write"]);
await writerHomePage.navigateToEmployeeOnboardingPage("Paul");
const writerOnboardingPage = new EmployeeOnboardingPage(writerPage);
await writerOnboardingPage.expectEmployeeName("Paul");
await writerOnboardingPage.fillPersonalDetailsCardAndSave(
"123-45-6789",
"123 Elm Street",
);
await writerOnboardingPage.fillUploadCardAndSave(
"./public/jazz-logo-low-res.jpg",
);
// invitee cannot confirm the onboarding completion
expect(
writerOnboardingPage.finalConfirmationButton.isDisabled(),
).toBeTruthy();
// final confirmation step by admin
await scrollToBottom(adminPage);
await adminOnboardingPage.finalConfirmationButton.click();
await adminOnboardingPage.backButton.click();
await adminHomePage.expectOnboardingCompleteForEmployee("Paul");
});
});

View File

@@ -0,0 +1,92 @@
import { Locator, Page, expect } from "@playwright/test";
export class EmployeeOnboardingPage {
readonly page: Page;
readonly shareButton: Locator;
readonly backButton: Locator;
readonly logoutButton: Locator;
readonly finalConfirmationButton: Locator;
readonly fileInput: Locator;
constructor(page: Page) {
this.page = page;
this.shareButton = page.getByRole("button", {
name: /invite a co-worker/i,
});
this.backButton = page.getByRole("button", {
name: /back/i,
});
this.logoutButton = page.getByRole("button", {
name: /log out/i,
});
this.finalConfirmationButton = this.page.getByRole("button", {
name: /confirmation by admin/i,
});
this.fileInput = page.getByTestId("file-upload");
}
async uploadFile(value: string) {
// Start waiting for file chooser before clicking. Note no await.
const fileChooserPromise = this.page.waitForEvent("filechooser");
await this.fileInput.click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(value);
}
async expectEmployeeName(name: string) {
await expect(
this.page.getByRole("heading", {
name: name,
}),
).toBeVisible();
}
async fillPersonalDetailsCardAndSave(ssn: string, address: string) {
const nextStepButton = this.page.getByRole("button", {
name: /upload step >/i,
});
await expect(nextStepButton).toBeDisabled();
const ssnInput = this.page.getByLabel(/Social Security Number/i);
await ssnInput.fill(ssn);
const addressInput = this.page.getByLabel(/Address/i);
await addressInput.fill(address);
// save and hide the button
await expect(nextStepButton).toBeEnabled();
await nextStepButton.click();
await expect(nextStepButton).not.toBeVisible();
}
async fillUploadCardAndSave(file: string) {
const nextStepButton = this.page.getByRole("button", {
name: /confirmation step >/i,
});
await expect(nextStepButton).toBeDisabled();
await this.uploadFile(file);
await expect(nextStepButton).toBeEnabled();
await nextStepButton.click();
await expect(nextStepButton).not.toBeVisible();
}
async getShareLink() {
await this.shareButton.click();
const inviteUrl = await this.page.evaluate(() =>
navigator.clipboard.readText(),
);
expect(inviteUrl).toBeTruthy();
return inviteUrl;
}
async logout() {
await this.logoutButton.click();
}
}

View File

@@ -0,0 +1,66 @@
import { Locator, Page, expect } from "@playwright/test";
import { NewEmployeePage } from "./NewEmployeePage";
export class HomePage {
readonly page: Page;
readonly newEmployeeLink: Locator;
readonly logoutButton: Locator;
constructor(page: Page) {
this.page = page;
this.newEmployeeLink = page.getByRole("button", {
name: "Add New Employee",
});
this.logoutButton = page.getByRole("button", {
name: "Log Out",
});
}
async expectEmployee([name, role]: [string, string]) {
const liElement = this.page.locator(
`li:has-text("${name}"):has-text("${role}")`,
);
await expect(liElement).toBeVisible();
}
async deleteEmployee(name: string) {
const liElement = this.page.locator(`li:has-text("${name}")`);
const deleteIcon = liElement.locator('span:has-text("🗑")');
await deleteIcon.click();
}
async expectOnboardingCompleteForEmployee(name: string) {
const liElement = this.page.locator(`li:has-text("${name}")`);
const completionIcon = liElement.locator('span:has-text("✅")');
await expect(completionIcon).toBeVisible();
}
async expectEmployeeDeleted(name: string) {
const liElement = this.page.locator(`li:has-text("${name}")`);
await expect(liElement).not.toBeVisible();
}
async navigateToEmployeeOnboardingPage(name: string) {
await this.page
.getByRole("link", {
name,
})
.click();
}
async navigateToNewEmployee() {
await this.newEmployeeLink.click();
}
async createEmployee(name: string) {
await this.navigateToNewEmployee();
const newEmployeePage = new NewEmployeePage(this.page);
await newEmployeePage.fillEmployeeName(name);
await newEmployeePage.submit();
}
async logout() {
await this.logoutButton.click();
}
}

View File

@@ -0,0 +1,40 @@
import { Locator, Page, expect } from "@playwright/test";
export class LoginPage {
readonly page: Page;
readonly usernameInput: Locator;
readonly signupButton: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.getByRole("textbox");
this.signupButton = page.getByRole("button", {
name: "Sign up",
});
}
async goto(url: string) {
await this.page.goto(url);
}
async fillUsername(value: string) {
await this.usernameInput.clear();
await this.usernameInput.fill(value);
}
async loginAs(value: string) {
await this.page
.getByRole("button", {
name: value,
})
.click();
}
async signup() {
await this.signupButton.click();
}
async expectLoaded() {
await expect(this.signupButton).toBeVisible();
}
}

View File

@@ -0,0 +1,24 @@
import { Locator, Page } from "@playwright/test";
export class NewEmployeePage {
readonly page: Page;
readonly employeeNameInput: Locator;
readonly submitButton: Locator;
constructor(page: Page) {
this.page = page;
this.employeeNameInput = page.getByLabel(/employee name/i);
this.submitButton = page.getByRole("button", {
name: /create employee/i,
});
}
async fillEmployeeName(value: string) {
await this.employeeNameInput.clear();
await this.employeeNameInput.fill(value);
}
async submit() {
await this.submitButton.click();
}
}

View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,17 @@
import path from "path";
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vite";
import topLevelAwait from "vite-plugin-top-level-await";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react(), topLevelAwait()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
build: {
minify: false,
},
});

View File

@@ -1,5 +1,30 @@
# jazz-password-manager
## 0.0.20
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
## 0.0.19
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.18
### Patch Changes
- Updated dependencies [dd9b13f]
- Updated dependencies [a69ed0b]
- Updated dependencies [3ef3ff3]
- Updated dependencies [c6931b8]
- jazz-react@0.8.20
## 0.0.17
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.17",
"version": "0.0.20",
"type": "module",
"scripts": {
"dev": "vite",
@@ -12,8 +12,8 @@
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
},
"dependencies": {
"jazz-react": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.22",
"jazz-tools": "workspace:0.8.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.41.5",

View File

@@ -1,5 +1,33 @@
# jazz-example-pets
## 0.0.118
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
- jazz-browser-media-images@0.8.22
## 0.0.117
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
- jazz-browser-media-images@0.8.21
## 0.0.116
### 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.0.115
### Patch Changes

View File

@@ -1,6 +1,6 @@
# Jazz Rate-My-Pet List Example
Live version: https://example-pets.jazz.tools
Live version: https://pets-demo.jazz.tools/
## Installing & running the example locally

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-pets",
"private": true,
"version": "0.0.115",
"version": "0.0.118",
"type": "module",
"scripts": {
"dev": "vite",
@@ -19,9 +19,9 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.19",
"jazz-react": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"jazz-browser-media-images": "workspace:0.8.22",
"jazz-react": "workspace:0.8.22",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
@@ -41,7 +41,7 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"is-ci": "^3.0.1",
"jazz-run": "workspace:0.8.19",
"jazz-run": "workspace:0.8.21",
"postcss": "^8.4.27",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",

View File

@@ -1,8 +1,8 @@
import {
Account,
CoFeed,
CoList,
CoMap,
CoStream,
ImageDefinition,
Profile,
co,
@@ -25,7 +25,7 @@ export const ReactionTypes = [
] as const;
export type ReactionType = (typeof ReactionTypes)[number];
export class PetReactions extends CoStream.Of(co.json<ReactionType>()) {}
export class PetReactions extends CoFeed.Of(co.json<ReactionType>()) {}
export class PetPost extends CoMap {
name = co.string;

1
examples/todo-vue/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist

View File

@@ -0,0 +1,11 @@
# todo-vue
## 0.0.5
### Patch Changes
- a734530: fix useCoState reactivity
- Updated dependencies [f6bc8af]
- Updated dependencies [a734530]
- jazz-browser@0.8.22
- jazz-vue@0.8.12

View File

@@ -0,0 +1,39 @@
# todo-vue
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Type-Check, Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

1
examples/todo-vue/env.d.ts vendored Normal file
View File

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

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,39 @@
{
"name": "todo-vue",
"version": "0.0.5",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build-type-check": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build": "vite build",
"type-check": "vue-tsc --build --force",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write"
},
"dependencies": {
"jazz-browser": "workspace:*",
"jazz-tools": "workspace:*",
"jazz-vue": "workspace:*",
"vue": "^3.5.11",
"vue-router": "^4.4.5"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
"@types/node": "^22.5.1",
"@vitejs/plugin-vue": "^5.1.4",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"@vue/tsconfig": "^0.5.1",
"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.3.2",
"typescript": "^5.3.3",
"vite": "^5.0.10",
"vite-plugin-vue-devtools": "^7.4.6",
"vue-tsc": "^2.1.6"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,69 @@
<template>
<div class="app-container">
<header v-if="me" class="app-header">
<h1>Todo App</h1>
<div class="user-section">
<span>{{ me.profile?.name }}</span>
<button class="logout-btn" @click="logOut">Log out</button>
</div>
</header>
<main>
<router-view />
</main>
</div>
</template>
<style scoped>
.app-container {
min-height: 100vh;
background-color: #f5f5f5;
}
.app-header {
background-color: #fff;
padding: 1rem 2rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
h1 {
color: #2c3e50;
margin: 0;
font-size: 1.5rem;
}
.user-section {
display: flex;
align-items: center;
gap: 1rem;
}
.logout-btn {
background-color: transparent;
border: 1px solid #dc3545;
color: #dc3545;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.logout-btn:hover {
background-color: #dc3545;
color: white;
}
main {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
</style>
<script setup lang="ts">
import { useAccount } from "./main";
const { me, logOut } = useAccount();
</script>

View File

@@ -0,0 +1,74 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition: color 0.5s, background-color 0.5s;
line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@@ -0,0 +1 @@
@import "./base.css";

View File

@@ -0,0 +1,42 @@
import "./assets/main.css";
import { DemoAuthBasicUI, createJazzVueApp, useDemoAuth } from "jazz-vue";
import { createApp, defineComponent, h } from "vue";
import App from "./App.vue";
import router from "./router";
import { ToDoAccount } from "./schema";
const Jazz = createJazzVueApp<ToDoAccount>({ AccountSchema: ToDoAccount });
export const { useAccount, useCoState } = Jazz;
const { JazzProvider } = Jazz;
const RootComponent = defineComponent({
name: "RootComponent",
setup() {
const { authMethod, state } = useDemoAuth();
return () => [
h(
JazzProvider,
{
auth: authMethod.value,
peer: "wss://mesh.jazz.tools/?key=vue-todo-example-jazz@gcmp.io",
},
{
default: () => h(App),
},
),
state.state !== "signedIn" &&
h(DemoAuthBasicUI, {
appName: "Jazz Vue Todo",
state,
}),
];
},
});
const app = createApp(RootComponent);
app.use(router);
app.mount("#app");

View File

@@ -0,0 +1,15 @@
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/HomeView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "Home",
component: Home,
},
],
});
export default router;

View File

@@ -0,0 +1,52 @@
import { Account, CoList, CoMap, Group, Profile, co } from "jazz-tools";
export class ToDoItem extends CoMap {
name = co.string;
completed = co.boolean;
}
export class ToDoList extends CoList.Of(co.ref(ToDoItem)) {}
export class Folder extends CoMap {
name = co.string;
items = co.ref(ToDoList);
}
export class FolderList extends CoList.Of(co.ref(Folder)) {}
export class ToDoAccountRoot extends CoMap {
folders = co.ref(FolderList);
}
export class ToDoAccount extends Account {
profile = co.ref(Profile);
root = co.ref(ToDoAccountRoot);
migrate(this: ToDoAccount, creationProps?: { name: string }) {
super.migrate(creationProps);
if (!this._refs.root) {
const group = Group.create({ owner: this });
const exampleTodo = ToDoItem.create(
{ name: "Example todo", completed: false },
{ owner: group },
);
const defaultFolder = Folder.create(
{
name: "Default",
items: ToDoList.create([exampleTodo], { owner: group }),
},
{ owner: group },
);
this.root = ToDoAccountRoot.create(
{
folders: FolderList.create([defaultFolder], {
owner: this,
}),
},
{ owner: this },
);
}
}
}

View File

@@ -0,0 +1,285 @@
<template>
<div class="todo-container">
<div class="folders">
<div class="section-header">
<h2>Folders</h2>
<div class="new-folder">
<input
v-model="newFolderName"
placeholder="New folder name"
class="input"
/>
<button class="btn btn-primary" @click="createFolder">Create</button>
</div>
</div>
<div class="folder-list">
<div
v-for="folder in folders"
:key="folder?.id"
:class="['folder-item', { active: selectedFolder?.id === folder?.id }]"
@click="selectFolder(folder)"
>
<span class="folder-name">{{ folder?.name }}</span>
<button class="btn btn-icon" @click.stop="deleteFolder(folder?.id)">
<span class="material-icons">delete</span>
</button>
</div>
</div>
</div>
<div class="todos" v-if="selectedFolder">
<div class="section-header">
<h2>{{ selectedFolder?.name }}</h2>
<div class="new-todo">
<input
v-model="newTodoTitle"
placeholder="Add a new task"
class="input"
/>
<button class="btn btn-primary" @click="createTodo">Add</button>
</div>
</div>
<div class="todo-list">
<template v-if="selectedFolder?.items?.length">
<div v-for="todo in selectedFolder.items" :key="todo?.id" class="todo-item">
<label class="todo-label">
<input
type="checkbox"
:checked="todo?.completed"
@change="toggleTodo(todo)"
/>
<span :class="{ completed: todo?.completed }">{{ todo?.name }}</span>
</label>
<button class="btn btn-icon" @click="deleteTodo(todo?.id)">
<span class="material-icons">delete</span>
</button>
</div>
</template>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Group, type ID } from "jazz-tools";
import { ref, toRaw, watch } from "vue";
import { computed } from "vue";
import { useAccount, useCoState } from "../main";
import { Folder, FolderList, ToDoItem, ToDoList } from "../schema";
const { me } = useAccount();
const computedFoldersId = computed(() => me.value?.root?.folders?.id);
const folders = useCoState(FolderList, computedFoldersId, [{ items: [{}] }]);
const selectedFolder = ref<Folder>();
const newFolderName = ref("");
const newTodoTitle = ref("");
// Select the first folder if none is selected
watch(folders, (loadedFolders) => {
if (selectedFolder.value) return;
selectedFolder.value = loadedFolders?.[0] || undefined;
});
const selectFolder = (folder: Folder) => {
selectedFolder.value = folder;
};
const createFolder = () => {
if (!newFolderName.value.trim()) return;
// Create a group where the folder will be owned by the current user
const group = Group.create({ owner: me.value });
// Create the folder
const newFolder = Folder.create(
{
name: newFolderName.value,
items: ToDoList.create([], { owner: group }),
},
{ owner: group },
);
// Add the folder to the list of folders. This change will be synced to all connected clients.
folders.value?.push(newFolder);
newFolderName.value = "";
};
const deleteFolder = (folderId: ID<Folder> | undefined) => {
if (!folders.value || !folderId) return;
const index = folders.value.findIndex((f) => f.id === folderId);
if (index !== -1) {
// Remove the folder from the list. This change will be synced to all connected clients.
folders.value.splice(index, 1);
}
if (selectedFolder.value?.id === folderId) {
selectedFolder.value = folders.value[0] || null;
}
};
// Todo handlers
const createTodo = () => {
if (!newTodoTitle.value.trim() || !selectedFolder.value) return;
const group = Group.create({ owner: me.value });
const newTodo = ToDoItem.create(
{
name: newTodoTitle.value,
completed: false,
},
{ owner: group },
);
// Add the todo to the list of todos. This change will be synced to all connected clients.
// toRaw is used to get the plain object from the reactive object, because the plain object is already proxied by Jazz.
// otherwise it will throw an error.
toRaw(selectedFolder.value)?.items?.push(newTodo);
newTodoTitle.value = "";
};
const deleteTodo = (todoId: ID<ToDoItem> | undefined) => {
if (!selectedFolder.value?.items || !todoId) return;
const index = toRaw(selectedFolder.value)?.items?.findIndex(
(t) => t?.id === todoId,
);
if (index !== -1 && index !== undefined) {
toRaw(selectedFolder.value)?.items?.splice(index, 1);
}
};
const toggleTodo = (todo: ToDoItem | null) => {
if (!todo) return;
todo.completed = !todo.completed;
};
</script>
<style scoped>
.todo-container {
display: grid;
grid-template-columns: 300px 1fr;
gap: 2rem;
height: 100%;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.section-header {
padding: 1.5rem;
border-bottom: 1px solid #eee;
}
h2 {
color: #2c3e50;
margin: 0 0 1rem 0;
font-size: 1.25rem;
}
.folders {
border-right: 1px solid #eee;
}
.folder-list,
.todo-list {
padding: 1rem;
}
.folder-item {
padding: 0.75rem 1rem;
margin: 0.25rem 0;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 6px;
transition: all 0.2s;
color: #333;
}
.folder-item:hover {
background-color: #f8f9fa;
}
.folder-item.active {
background-color: #e9ecef;
}
.todo-item {
padding: 0.75rem 1rem;
margin: 0.25rem 0;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 6px;
transition: all 0.2s;
color: #333;
}
.todo-item:hover {
background-color: #f8f9fa;
}
.todo-label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
color: #333;
}
.completed {
text-decoration: line-through;
color: #6c757d;
}
.input {
padding: 0.5rem 1rem;
border: 1px solid #dee2e6;
border-radius: 4px;
font-size: 0.9rem;
width: 100%;
}
.btn {
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
border: none;
font-size: 0.9rem;
transition: all 0.2s;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-icon {
padding: 0.25rem;
background: transparent;
color: #dc3545;
}
.btn-icon:hover {
background-color: #fee2e2;
}
.new-folder,
.new-todo {
display: flex;
gap: 0.5rem;
}
.folder-name {
color: #333;
}
</style>

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

@@ -0,0 +1,14 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

View File

@@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

View File

@@ -0,0 +1,16 @@
import { URL, fileURLToPath } from "node:url";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import { defineConfig } from "vite";
import vueDevTools from "vite-plugin-vue-devtools";
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx(), vueDevTools()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});

View File

@@ -1,5 +1,30 @@
# jazz-example-todo
## 0.0.117
### Patch Changes
- Updated dependencies [59cc64d]
- jazz-react@0.8.22
## 0.0.116
### Patch Changes
- Updated dependencies [149ca97]
- jazz-tools@0.8.21
- jazz-react@0.8.21
## 0.0.115
### Patch Changes
- Updated dependencies [dd9b13f]
- Updated dependencies [a69ed0b]
- Updated dependencies [3ef3ff3]
- Updated dependencies [c6931b8]
- jazz-react@0.8.20
## 0.0.114
### Patch Changes

View File

@@ -1,6 +1,6 @@
# Jazz Todo List Example
Live version: https://example-todo.jazz.tools
Live version: https://todo-demo.jazz.tools/
## Installing & running the example locally

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-todo",
"private": true,
"version": "0.0.114",
"version": "0.0.117",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,8 +16,8 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"jazz-react": "workspace:0.8.19",
"jazz-tools": "workspace:0.8.19",
"jazz-react": "workspace:0.8.22",
"jazz-tools": "workspace:0.8.21",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",

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