Compare commits

...

223 Commits

Author SHA1 Message Date
Trisha Lim
b097c38617 Test draft indicator 2024-12-13 18:20:59 +00:00
Trisha Lim
385dfc89ff Add test to CI 2024-12-13 18:08:31 +00:00
Trisha Lim
d89e4c3412 Test order editing 2024-12-13 18:05:12 +00:00
Trisha Lim
bbeee086ce Test order creation 2024-12-13 17:59:05 +00:00
Trisha Lim
718e9418e2 Set up playwright on form example 2024-12-13 17:34:48 +00:00
Guido D'Orsi
507a00fe6a Merge pull request #1016 from garden-co/fix-depencencies
fix: run sherif to align all the packages/examples dependencies
2024-12-13 15:13:09 +01:00
Guido D'Orsi
8e940130d2 fix: run sherif to aling all the packages/examples dependencies 2024-12-13 15:05:23 +01:00
Trisha Lim
f3581d1086 Fix form example build 2024-12-13 12:17:25 +00:00
Guido D'Orsi
4ac2b75194 Merge pull request #1014 from garden-co/migrate-taiwind-config
fix: migrate tailwind configs to typescript
2024-12-13 13:17:15 +01:00
Guido D'Orsi
cfa78ed628 fix(form): restore palette and add ts-ignore to the vite config 2024-12-13 13:12:49 +01:00
Guido D'Orsi
94095aff28 fix(ci): build only packages on the unit test workflow 2024-12-13 13:09:57 +01:00
Guido D'Orsi
a70f7a4c49 fix: migrate tailwind configs to typescript 2024-12-13 13:09:20 +01:00
Trisha Lim
2270ae113e Fix link typo 2024-12-13 11:54:32 +00:00
Benjamin S. Leveritt
92b64e05ca Add comment about public visibility on profile content 2024-12-13 11:54:32 +00:00
Benjamin S. Leveritt
cb6b9af57d Improve guide 2024-12-13 11:54:32 +00:00
Benjamin S. Leveritt
fc00369d87 Add initial project guide 2024-12-13 11:54:32 +00:00
Anselm Eickhoff
0b260f339d Merge pull request #993 from garden-co/jazz-549-add-forms-example
Add form example for creating and updating
2024-12-13 11:54:11 +00:00
Trisha Lim
8f50dd48f6 Show errors on page 2024-12-13 11:50:43 +00:00
Anselm Eickhoff
d3af4fecd1 Merge pull request #1012 from garden-co/jazz-582-officially-support-svelte
Officially support svelte
2024-12-13 11:32:07 +00:00
Guido D'Orsi
4ec13a9515 Merge pull request #999 from garden-co/changeset-release/main
Version Packages
2024-12-13 12:22:40 +01:00
Trisha Lim
79262351fa Icon stroke width 2024-12-13 11:05:43 +00:00
Trisha Lim
01292230e8 Add validation 2024-12-13 10:49:26 +00:00
github-actions[bot]
9b31d3241d Version Packages 2024-12-12 17:37:39 +00:00
Anselm Eickhoff
77dfd4f883 Merge pull request #1011 from garden-co/fix/import-extensions-linting
fix: enable linting for import extensions on packages
2024-12-12 17:36:16 +00:00
Trisha Lim
9150c238e4 Add feature tags to file share example 2024-12-12 17:33:35 +00:00
Trisha Lim
67bd5ab07f Update file share icon 2024-12-12 17:25:04 +00:00
Trisha Lim
2678c83a99 Add Svelte passkey example app to examples page 2024-12-12 17:22:03 +00:00
Trisha Lim
4c1364a5cb Add Svelte file share example app to examples page 2024-12-12 17:20:16 +00:00
Trisha Lim
f546811a23 Add Svelte under supported environments 2024-12-12 17:10:32 +00:00
Guido D'Orsi
eb9164633c fix: enable linting for import extensions on packages 2024-12-12 18:02:02 +01:00
Trisha Lim
db5e5d8392 Add form example to examples page 2024-12-12 16:26:56 +00:00
Trisha Lim
a18d870faa Update lock file 2024-12-12 15:16:02 +00:00
Trisha Lim
d434b23a34 Layout fix 2024-12-12 15:14:18 +00:00
Trisha Lim
dd363d613f Add readme description 2024-12-12 15:14:18 +00:00
Trisha Lim
9d41f7a16a Orders empty screen 2024-12-12 15:14:18 +00:00
Trisha Lim
dfddc3603a Set default values 2024-12-12 15:14:18 +00:00
Trisha Lim
5c09f0f41a Style strong tag 2024-12-12 15:12:59 +00:00
Trisha Lim
139fcbe172 Add draft indicator 2024-12-12 15:12:59 +00:00
Trisha Lim
507b3b890a Form styling 2024-12-12 15:11:08 +00:00
Trisha Lim
06ad39c418 Order thumbnail styling 2024-12-12 15:10:59 +00:00
Trisha Lim
7077a41c2d Make addons checkbox work 2024-12-12 15:10:59 +00:00
Trisha Lim
9572729cc9 Remove unused 2024-12-12 15:10:59 +00:00
Trisha Lim
9f08ca8cd8 Save draft as an order 2024-12-12 15:10:59 +00:00
Trisha Lim
8e0bf8a8d8 Replace drafts array with a single draft 2024-12-12 15:10:59 +00:00
Trisha Lim
5b72d1f89b Show drafts in home 2024-12-12 15:10:59 +00:00
Trisha Lim
e10d09e6ab Add order draft to schema 2024-12-12 15:10:59 +00:00
Trisha Lim
2007815745 Do not use react-hook-form 2024-12-12 15:10:59 +00:00
Trisha Lim
1f826734d4 Create Order form using react-form-hook 2024-12-12 15:10:59 +00:00
Trisha Lim
80383dac3b Create form example project 2024-12-12 15:09:20 +00:00
Guido D'Orsi
b9ed9e3f13 Merge pull request #994 from garden-co/jazz-493-wait-for-stuff-to-sync-in-ephemeral-contexts-before-quitting
fix(jazz-nodejs): wait for sync before closing and fix the reconnection
2024-12-12 14:52:19 +01:00
Guido D'Orsi
96f844af5e chore: changeset 2024-12-12 11:07:32 +01:00
Guido D'Orsi
994fbf3524 test: add tests on startWorker 2024-12-12 11:04:28 +01:00
Guido D'Orsi
6eba301599 fix: wait for all coValues in sync before closing the Workers node 2024-12-12 10:16:50 +01:00
Guido D'Orsi
322438f361 fix(jazz-nodejs): fix the reconnection system 2024-12-12 10:16:50 +01:00
Guido D'Orsi
f2f3481773 Merge pull request #935 from garden-co/jazz-550-match-account-load-depth-fallback
Add depth fallback
2024-12-12 10:15:12 +01:00
Guido D'Orsi
b96bab59d0 Merge pull request #955 from garden-co/jzi-29-track-the-amount-of-connected-peers-in-cojson-syncmanager
chore: add an up-down counter to track the amount of connected peers
2024-12-12 10:13:34 +01:00
Benjamin S. Leveritt
aa21072ceb Chore: Add changeset 2024-12-11 23:32:39 +00:00
Benjamin S. Leveritt
01442948d0 Exclude Svelte 5 from coverage instrumentation (not supported) 2024-12-11 23:23:43 +00:00
Benjamin S. Leveritt
6c26902a73 Fix coverage picking up wrong files in Jazz-Svelte 2024-12-11 22:53:24 +00:00
Benjamin S. Leveritt
b12f9dc675 Fix downgrade vitest syntax 2024-12-11 22:28:19 +00:00
Benjamin S. Leveritt
bb6d9791dc Lower vitest version 2024-12-11 22:28:19 +00:00
Benjamin S. Leveritt
024b24f5fa Add tests for jazz.svelte 2024-12-11 22:28:19 +00:00
Benjamin S. Leveritt
facfc51e84 Add tests for passkey auth 2024-12-11 22:28:19 +00:00
Benjamin S. Leveritt
6e4a7411dd Add config 2024-12-11 22:28:19 +00:00
Benjamin S. Leveritt
a02b53cbe3 Add vitest coverage 2024-12-11 22:28:19 +00:00
Benjamin S. Leveritt
a0548ecb25 Add vitest 2024-12-11 22:28:19 +00:00
Benjamin S. Leveritt
970414c8c3 Add depth fallback 2024-12-11 22:27:28 +00:00
Anselm Eickhoff
d62a5c74f9 Merge pull request #1004 from garden-co/jazz-579-external-links-to-expo-are-broken
Fix broken links in docs
2024-12-11 18:20:12 +00:00
Guido D'Orsi
5d35469b91 fix: fix jazz-browser-media-images dependency on chat example 2024-12-11 18:49:04 +01:00
Trisha Lim
6cd79a0631 Fix broken links in docs 2024-12-11 16:52:13 +00:00
Trisha Lim
5122cd027b Fix external links in docs 2024-12-11 16:50:06 +00:00
Giordano Ricci
9d9bb97da3 add tests 2024-12-11 11:59:24 +00:00
Giordano Ricci
9db3f2120b add test metric reader utilities 2024-12-11 11:58:50 +00:00
Giordano Ricci
cb4ccd5d33 chore: add .direnv to ignore file 2024-12-11 11:54:27 +00:00
Giordano Ricci
3121551e8a chore: add an up-down counter to track the amount of connected peers 2024-12-11 11:54:27 +00:00
Anselm Eickhoff
1a65ffd913 Merge pull request #950 from garden-co/jazz-188-add-method-to-wait-until-a-covalue-is-synced
feat: add the APIs to wait for a sync state
2024-12-11 10:42:59 +00:00
Guido D'Orsi
80301e51bb Merge pull request #979 from garden-co/jazz-530-send-images-in-chat-example
Send image in chat example
2024-12-11 11:18:54 +01:00
Guido D'Orsi
5686db9b89 Merge pull request #996 from garden-co/jazz-576-fix-homepage-build
Move to use turbo build
2024-12-11 11:18:26 +01:00
Guido D'Orsi
21a56fc96b Merge remote-tracking branch 'origin/main' into jazz-530-send-images-in-chat-example 2024-12-11 11:17:02 +01:00
Guido D'Orsi
eb22a24f17 Merge pull request #990 from garden-co/jazz-574-bump-repo-to-node-22
Chore: Bump node version to 22
2024-12-11 10:56:02 +01:00
Guido D'Orsi
4f3de09154 Merge remote-tracking branch 'origin/main' into jazz-574-bump-repo-to-node-22 2024-12-11 10:53:39 +01:00
Guido D'Orsi
f2f865da50 Merge pull request #920 from garden-co/add-file-share-example-app
Add file share example app
2024-12-11 10:52:30 +01:00
Guido D'Orsi
c0d12dba26 Merge remote-tracking branch 'origin/main' into add-file-share-example-app 2024-12-11 10:51:26 +01:00
Guido D'Orsi
cd0717f35d Merge pull request #983 from garden-co/jazz-571-bug-the-ws-connection-isnt-restored-when-dropped-by-the
fix(react-native): reconnect automatically when the WebSocket is closed by the server
2024-12-11 10:49:23 +01:00
Guido D'Orsi
4515395bb9 Merge pull request #980 from garden-co/jazz-558-set-up-cryptography-tests-for-react-native
feat(react-native): setup a basic environment to test the crypto
2024-12-11 10:49:05 +01:00
Guido D'Orsi
7cee49f2b2 chore: fix typo 2024-12-11 10:32:18 +01:00
Guido D'Orsi
9414fbfb23 fix: fix e2e tests and initial account migration 2024-12-11 10:25:11 +01:00
Guido D'Orsi
9c6ea58ef5 feat(file-share-svelter): add the e2e test to the CI 2024-12-11 09:44:50 +01:00
Guido D'Orsi
0685436b0c Merge pull request #842 from garden-co/JAZZ-238/very-quick-way-to-open-up-inspector-as-current-user-in-an-app
feat: pressing cmd+j in a jazz app exports the jazz account to inspector
2024-12-11 09:33:48 +01:00
Guido D'Orsi
daa11aa915 chore: remove console.log 2024-12-11 09:33:05 +01:00
Benjamin S. Leveritt
bbdb1ef429 Improve build instructions in readme 2024-12-10 23:37:07 +00:00
Benjamin S. Leveritt
508537928f Update cloud key 2024-12-10 23:30:59 +00:00
Benjamin S. Leveritt
eaeeedc3a4 Update readmes 2024-12-10 23:30:59 +00:00
Benjamin S. Leveritt
15cdfd406c Add svelte output to turbo for better caching 2024-12-10 23:30:59 +00:00
Benjamin S. Leveritt
76262c1a03 Replace svelte auto adapter with vercel 2024-12-10 23:30:59 +00:00
Benjamin S. Leveritt
fc43a2384b Remove the special example exception 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
d1698eed6f Add Readmes 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
6d68dc1bbf Rename file-share app 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
1fb49b266a Remove ownerId from url
As it's not used
2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
49f0ab1545 Exclude file-share from biome checking for now 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
641bbddd64 Add file name to page title 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
7e22f53718 Chore: fix formatting 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
4c44468129 Fix Ownership to be determined by admin role 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
b0c5ecd7b6 Add guest access to files 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
5bf9d69e9f Add loading check 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
3ecf11ded9 Replace messages with toasts 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
709d60b365 Add download error handler 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
f135babcb8 Add page title 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
36a85d4313 Add file detail page for sharing 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
7316379247 Add lucide icons 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
0cf59b6549 Tweak UI layout 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
bcaff2f24a Improve layout of uploader 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
d2a5087e65 Animate uploading files 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
8627d47afc Show uploading files 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
47f625cbd4 Add file size 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
34b65ef67a Add shared files page 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
51209d5314 Fix root re-initialising on reload 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
309dffcc67 Wrap auth and provider around app 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
716b87da0a Initialise Jazz app 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
3c58879a10 Add schema 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
ed16031f12 Add jazz deps 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
90d9e1ca91 Format files 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
95828596f8 Add sv created file share app 2024-12-10 23:28:56 +00:00
Benjamin S. Leveritt
d794a555dc Merge pull request #942 from garden-co/jazz-560-fix-linting-in-cojson-storage-indexeddb
Add linting to cojson-storage-indexeddb
2024-12-10 23:27:15 +00:00
Benjamin S. Leveritt
a6c143f33f Add build instructions 2024-12-10 23:02:06 +00:00
Benjamin S. Leveritt
1db615d1b1 Formatting 2024-12-10 21:28:44 +00:00
Benjamin S. Leveritt
9b45c44b29 Move to use turbo build 2024-12-10 21:27:42 +00:00
Benjamin S. Leveritt
ecc7c9672d Chore: Add changeset 2024-12-10 20:24:59 +00:00
Benjamin S. Leveritt
d645970cb0 Bump dockerfile node version 2024-12-10 20:24:59 +00:00
Benjamin S. Leveritt
e9864cda9d Chore: Bump better-sqlite3 version 2024-12-10 20:24:59 +00:00
Benjamin S. Leveritt
d4e9afc4e2 Chore: Bump node version to 22 2024-12-10 20:21:36 +00:00
pax
4fc4f029a2 Merge pull request #977 from garden-co/changeset-release/main
Version Packages
2024-12-10 20:39:58 +02:00
Guido D'Orsi
fb06319dae chore: simplify createAccount relying on waitForAllCoValuesSync 2024-12-10 18:29:54 +01:00
Guido D'Orsi
a4886fede8 test: fix test after merge 2024-12-10 18:16:30 +01:00
Trisha Lim
b2ca4ec52e Change owner of image 2024-12-10 16:59:56 +00:00
Trisha Lim
b2c8a1a27d Add hover styling to Upload Image button 2024-12-10 16:58:29 +00:00
github-actions[bot]
5469c1f347 Version Packages 2024-12-10 14:55:06 +00:00
Anselm Eickhoff
d36e7cd55a Merge pull request #987 from garden-co/jazz-573-improve-turbo-caching-on-homepage
Adds caching to homepage
2024-12-10 14:53:43 +00:00
Giordano Ricci
03ccdb93af Merge pull request #988 from garden-co/gio/add-queue-metrics-tests
chore: adds message queue size metrics tests
2024-12-10 13:36:00 +00:00
Giordano Ricci
eb8bd20931 teardown meter provider 2024-12-10 13:28:29 +00:00
Giordano Ricci
9819811c91 chore: adds message queue size metrics tests 2024-12-10 12:35:24 +00:00
Benjamin S. Leveritt
350e59b187 Adds caching to homepage 2024-12-10 11:42:57 +00:00
Guido D'Orsi
c2cad4b68c Merge remote-tracking branch 'origin/main' into jazz-188-add-method-to-wait-until-a-covalue-is-synced 2024-12-10 11:23:29 +01:00
Guido D'Orsi
0c6b0f37ed fix(react-native): reconnect automatically when the WebSocket is closed by the server 2024-12-10 11:17:56 +01:00
Anselm Eickhoff
a3541f24ee Merge pull request #975 from garden-co/jzi-28-track-the-message-queue-size-of-each-peerstate-on-cojson
chore: Add message queue size metric
2024-12-10 09:59:17 +00:00
Anselm Eickhoff
f8be4e8bc0 Merge pull request #978 from garden-co/fix/sqlite-scheduling
fix(sqlite): improve the work scheduling when under pressure
2024-12-10 09:52:40 +00:00
Guido D'Orsi
c8015ddee1 feat(react-native): setup a basic environment to test the crypto 2024-12-10 10:24:30 +01:00
Guido D'Orsi
44227bd6e8 Merge pull request #970 from garden-co/fix/sync-server-console
fix(jazz-run): fix console output to log the server address when running the local sync
2024-12-10 10:02:10 +01:00
Trisha Lim
d582429488 Set file size limit to 5MB 2024-12-09 18:45:00 +00:00
Trisha Lim
3db0639636 Update demo section copy 2024-12-09 18:29:10 +00:00
Trisha Lim
c75cd9da37 Send image in chat example 2024-12-09 16:12:05 +00:00
Guido D'Orsi
da13ecac5c fix(sqlite): improve the work scheduling when under pressure 2024-12-09 16:44:34 +01:00
Giordano Ricci
c4af72e1ca mark @opentelemetry/api as a peer dependency 2024-12-09 14:48:10 +00:00
Trisha Lim
e43ca2bc98 Fix broken links 2024-12-09 11:46:28 +00:00
Trisha Lim
bcdd8e9125 Change "updating" to "writing" 2024-12-09 11:46:28 +00:00
Trisha Lim
2d6352a2dc Match side nav links with content links 2024-12-09 11:46:28 +00:00
Trisha Lim
749350cb59 Docs: return coming soon page on routes with no content 2024-12-09 11:46:28 +00:00
Trisha Lim
07e9a73c52 Fix clerk example readme title 2024-12-09 11:28:47 +00:00
Anselm Eickhoff
1ef9998b23 Merge pull request #973 from garden-co/fix/parent-group-role-resolution
fix(role): apply time traveling when resolving role from parents
2024-12-09 11:12:12 +00:00
Guido D'Orsi
b00ee914c3 fix(role): apply time traveling when resolving role from parents 2024-12-09 12:03:18 +01:00
Giordano Ricci
f488c09545 add changeset 2024-12-09 10:17:31 +00:00
Giordano Ricci
0d6ea25e56 chore: Add message queue size metric 2024-12-09 10:09:20 +00:00
Guido D'Orsi
9ffe6d0c63 test(e2e): use waitForSync to improve the test stability 2024-12-08 12:53:23 +01:00
Guido D'Orsi
249eecb68f chore: changeset 2024-12-08 12:46:05 +01:00
Guido D'Orsi
cdbde2970d fix(PeerState): keep optimistic and knownState the same when the role is storage 2024-12-08 12:46:05 +01:00
Guido D'Orsi
24a1b045d3 test(e2e): move synced check up 2024-12-08 12:46:05 +01:00
Guido D'Orsi
a21c3068cd feat(waitForSync): add API docs and some more tests 2024-12-08 12:46:05 +01:00
Guido D'Orsi
55ab4844d6 feat: add the APIs to wait for a sync state 2024-12-08 12:46:05 +01:00
Guido D'Orsi
6e131eb0dc Merge pull request #971 from garden-co/fix/e2e-test-sharing
test(e2e): use the local server to reduce the flakyiness
2024-12-08 11:47:00 +01:00
Guido D'Orsi
8a0cb69c72 test(e2e): fix flakyiness on the Sharing test suit 2024-12-08 11:40:56 +01:00
Guido D'Orsi
d01286b723 test(e2e): use the local server to reduce the flakyiness 2024-12-08 11:32:49 +01:00
Guido D'Orsi
0b636d26f3 Merge pull request #968 from garden-co/playwright-cache
chore(ci): optimize playwright browsers install
2024-12-08 11:22:25 +01:00
Guido D'Orsi
f41a1b0d1d Merge pull request #969 from garden-co/upgrade-pnpm-changeset
chore: update pnpm and changeset to fix release
2024-12-08 11:18:03 +01:00
Guido D'Orsi
28930ee1d5 fix(jazz-run): fix console output 2024-12-08 11:09:53 +01:00
Guido D'Orsi
0a8753dfe0 chore: align pnpm-lock with main 2024-12-08 11:06:39 +01:00
Guido D'Orsi
3ca8b13f6c fix: fix type error on test file 2024-12-08 11:01:40 +01:00
Guido D'Orsi
e06c18150a chore(ci): remove --with-deps 2024-12-08 10:57:35 +01:00
Guido D'Orsi
4c4499ce86 Merge remote-tracking branch 'origin/main' into upgrade-pnpm-changeset 2024-12-08 10:56:11 +01:00
Guido D'Orsi
afa4c825ca chore: update pnpm and changeset to fix release 2024-12-08 10:53:04 +01:00
Guido D'Orsi
60e04cb193 chore(ci): cache playwright browsers 2024-12-08 10:51:30 +01:00
Guido D'Orsi
843a012f33 Merge pull request #967 from garden-co/update-changeset-config
fix(changeset): update config
2024-12-08 10:39:17 +01:00
Guido D'Orsi
bd510a13ce fix(changeset): update config 2024-12-08 10:38:36 +01:00
Benjamin S. Leveritt
0d6853bbdd Fix imports 2024-12-08 07:36:35 +00:00
Benjamin S. Leveritt
a238dde78a Fix test 2024-12-08 07:36:35 +00:00
Benjamin S. Leveritt
59997fbf71 Add linting 2024-12-08 07:36:35 +00:00
Anselm
b56d7e3e7c Make passkey-svelte example private on npm 2024-12-07 19:17:06 +00:00
Anselm Eickhoff
2f5bd74206 Merge pull request #966 from garden-co/changeset-release/main
Version Packages
2024-12-07 19:09:20 +00:00
github-actions[bot]
a4864d93d2 Version Packages 2024-12-07 19:04:33 +00:00
Anselm Eickhoff
19376e6abd Merge pull request #965 from garden-co/perf/coMapAtTime
perf(CoMap): optimize the atTime processing
2024-12-07 19:02:56 +00:00
Guido D'Orsi
22f6db8141 test: cover CoMap public methods with tests 2024-12-07 19:40:44 +01:00
Guido D'Orsi
3d9f12e9ea chore: changeset 2024-12-07 19:28:12 +01:00
Guido D'Orsi
68620a3df9 perf(CoMap): optimize the atTime processing 2024-12-07 19:07:52 +01:00
Anselm Eickhoff
1767f024d9 Merge pull request #964 from garden-co/jazz-567-speed-up-latency-map-rendering
Fix caching for latency map
2024-12-07 14:06:46 +00:00
Anselm
c55924a04a Fix caching 2024-12-07 13:56:48 +00:00
Anselm Eickhoff
31e00a96ae Merge pull request #963 from garden-co/jazz-567-speed-up-latency-map-rendering
Speed up latency map rendering
2024-12-07 12:59:44 +00:00
Anselm
7bb834f399 Lint & format 2024-12-07 12:54:37 +00:00
pax
667f36e1cf Merge pull request #957 from garden-co/rn-examples-new-arch
fix: upgraded RN examples to Expo SDK 52 with new arch
2024-12-07 14:50:50 +02:00
pax-k
07669923ad Merge branch 'main' into rn-examples-new-arch 2024-12-07 14:48:28 +02:00
Anselm
9082a099ee Merge branch 'main' into jazz-567-speed-up-latency-map-rendering 2024-12-07 12:31:58 +00:00
Anselm
aba059db28 Try strongarming vercel into using the right pnpm version 2024-12-07 12:23:15 +00:00
Anselm
ae6b9c8dd2 Speed up latency map rendering 2024-12-06 18:59:21 +00:00
pax-k
7b9f96bf1a chore: pnpm lock 2024-12-06 18:52:25 +02:00
pax-k
8df49546fe Merge branch 'main' into rn-examples-new-arch 2024-12-06 18:52:08 +02:00
pax-k
bb4460f422 fix: upgraded RN examples to Expo SDK 52 with new arch 2024-12-06 18:51:35 +02:00
Anselm Eickhoff
2099099afc Merge pull request #956 from nikitavoloboev/patch-1
fix 404 urls in docs
2024-12-06 16:47:19 +00:00
Nikita
06b0758d7c fix urls 2024-12-06 17:43:06 +01:00
Nikita
6135250e57 fix 404 url 2024-12-06 17:38:32 +01:00
Anselm Eickhoff
aacd03bbdd Merge pull request #945 from garden-co/changeset-release/main
Version Packages
2024-12-06 15:39:02 +00:00
github-actions[bot]
ca7f250d47 Version Packages 2024-12-06 14:46:52 +00:00
pax
83ad506b94 Merge pull request #954 from garden-co/jazz-react-native-auth-clerk
jazz-react-native-auth-clerk package
2024-12-06 16:45:42 +02:00
pax-k
e9751f5b69 Merge branch 'main' into jazz-react-native-auth-clerk 2024-12-06 16:36:14 +02:00
pax-k
c84764acd5 chore: changeset 2024-12-06 16:30:35 +02:00
pax-k
c2a805bffa chore: added expo-font 2024-12-06 16:27:27 +02:00
pax-k
8728dde42b feat: added package jazz-react-native-auth-clerk 2024-12-06 15:13:45 +02:00
pax-k
ab7c899a9e Merge branch 'main' into JAZZ-238/very-quick-way-to-open-up-inspector-as-current-user-in-an-app 2024-12-04 12:18:36 +02:00
pax-k
81c470e9fa fix: comment out createCoValueObservable.test.ts until cirular imports are fixed 2024-12-03 14:31:50 +02:00
pax-k
52d5a14fdf fix: comment out createCoValueObservable.test.ts until cirular imports are fixed 2024-12-03 14:24:49 +02:00
pax-k
fca6be8da0 Merge branch 'main' into JAZZ-238/very-quick-way-to-open-up-inspector-as-current-user-in-an-app 2024-12-03 14:07:17 +02:00
pax-k
e386f2ba8d chore: changeset 2024-12-03 13:56:41 +02:00
pax-k
c2d292effc chore: moved export-account-inspector.ts to jazz-browser 2024-12-03 13:52:09 +02:00
pax-k
a107e17c44 fix: cmd+j now registered only if allowJazzInspector is found in window.location.href 2024-11-25 17:28:46 +02:00
pax
d2d2801537 Update packages/jazz-tools/src/utils/export-account-inspector.ts
Co-authored-by: Anselm Eickhoff <anselm.eickhoff@gmail.com>
2024-11-25 17:04:45 +02:00
pax-k
d5af8df158 feat: pressing cmd+j in a jazz app exports the jazz account to inspector 2024-11-21 17:30:50 +02:00
290 changed files with 10942 additions and 5278 deletions

View File

@@ -6,18 +6,23 @@
"linked": [
[
"cojson",
"jazz-tools",
"jazz-browser",
"jazz-browser-media-images",
"jazz-browser-auth-clerk",
"jazz-react-auth-clerk",
"jazz-react",
"jazz-react-native",
"jazz-nodejs",
"jazz-run",
"cojson-transport-ws",
"cojson-storage",
"cojson-storage-indexeddb",
"cojson-storage-sqlite"
"cojson-storage-sqlite",
"cojson-transport-ws",
"jazz-browser",
"jazz-browser-auth-clerk",
"jazz-browser-media-images",
"jazz-nodejs",
"jazz-react",
"jazz-react-auth-clerk",
"jazz-react-native",
"jazz-react-native-auth-clerk",
"jazz-react-native-media-images",
"jazz-run",
"jazz-svelte",
"jazz-tools",
"jazz-vue"
]
],
"access": "public",

View File

@@ -1,6 +0,0 @@
---
"cojson": patch
"jazz-tools": patch
---
Optimise large record-like CoMaps for access of latest value

View File

@@ -1,7 +0,0 @@
---
"cojson-storage-indexeddb": patch
"cojson-storage-sqlite": patch
"cojson-storage": patch
---
Refactor the SQLite and IndexedDB storage packages to extract common synchronization functionality into newly created cojson-storage package.

View File

@@ -13,7 +13,7 @@ jobs:
continue-on-error: true
strategy:
matrix:
project: ["tests/e2e", "examples/chat", "examples/music-player", "examples/pets", "examples/onboarding"]
project: ["tests/e2e", "examples/chat", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "examples/onboarding"]
steps:
- uses: actions/checkout@v3
@@ -41,20 +41,20 @@ jobs:
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Setup .env
run: echo "VITE_WS_PEER=ws://localhost:4200/" >> .env
working-directory: ./${{ matrix.project }}
- name: Pnpm Build
run: pnpm turbo build
working-directory: ./${{ matrix.project }}
- name: Build jazz-run
run: pnpm exec turbo build && chmod +x dist/index.js;
working-directory: ./packages/jazz-run
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
run: pnpm exec playwright install
working-directory: ./${{ matrix.project }}
- name: Run Playwright tests

View File

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

1
.gitignore vendored
View File

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

View File

@@ -1 +1 @@
20
22

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
22

View File

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

View File

@@ -1,5 +1,39 @@
# jazz-example-book-shelf
## 0.1.31
### Patch Changes
- Updated dependencies [249eecb]
- jazz-tools@0.8.39
- jazz-browser-media-images@0.8.39
- jazz-react@0.8.39
## 0.1.30
### Patch Changes
- jazz-react@0.8.38
- jazz-tools@0.8.38
- jazz-browser-media-images@0.8.38
## 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

View File

@@ -1,6 +1,6 @@
{
"name": "jazz-example-book-shelf",
"version": "0.1.27",
"version": "0.1.31",
"private": true,
"scripts": {
"dev": "next dev",
@@ -11,19 +11,19 @@
},
"dependencies": {
"clsx": "^2.0.0",
"jazz-browser-media-images": "workspace:0.8.35",
"jazz-react": "workspace:0.8.35",
"jazz-tools": "workspace:0.8.35",
"jazz-browser-media-images": "workspace:0.8.39",
"jazz-react": "workspace:0.8.39",
"jazz-tools": "workspace:0.8.39",
"next": "14.2.5",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/node": "^22.5.1",
"@types/react": "^18.2.19",
"@types/react-dom": "^18.2.7",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"postcss": "^8.4.27",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3"
"tailwindcss": "^3.4.9",
"typescript": "~5.6.2"
}
}

View File

@@ -1 +1 @@
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_ZXZpZGVudC1kYW5lLTg5LmNsZXJrLmFjY291bnRzLmRldiQ

View File

@@ -1,5 +1,46 @@
# chat-rn-clerk
## 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
- jazz-react-native@0.8.37
- jazz-react-native-auth-clerk@0.8.37
- jazz-tools@0.8.37
- jazz-react-native-media-images@0.8.28
## 1.0.28
### Patch Changes
- c84764a: feat: added jazz-react-native-auth-clerk package
- Updated dependencies [c84764a]
- Updated dependencies [441fe27]
- jazz-react-native-auth-clerk@0.8.36
- jazz-react-native@0.8.36
- jazz-tools@0.8.36
- jazz-react-native-media-images@0.8.27
## 1.0.27
### Patch Changes

View File

@@ -35,7 +35,9 @@
}
}
],
"expo-secure-store"
"expo-secure-store",
"expo-font",
"expo-router"
],
"extra": {
"eas": {

View File

@@ -4,7 +4,7 @@ import { Platform } from "react-native";
export interface TokenCache {
getToken: (key: string) => Promise<string | undefined | null>;
saveToken: (key: string, token: string) => Promise<void>;
clearToken?: (key: string) => void;
clearToken: (key: string) => void;
}
const createTokenCache = (): TokenCache => {
@@ -27,6 +27,9 @@ const createTokenCache = (): TokenCache => {
saveToken: (key: string, token: string) => {
return SecureStore.setItemAsync(key, token);
},
clearToken: (key: string) => {
return SecureStore.deleteItemAsync(key);
},
};
};

View File

@@ -19,7 +19,10 @@ config.resolver.nodeModulesPaths = [
];
config.resolver.sourceExts = ["mjs", "js", "json", "ts", "tsx"];
config.resolver.unstable_enablePackageExports = true;
config.resolver.requireCycleIgnorePatterns = [/(^|\/|\\)node_modules($|\/|\\)/];
config.resolver.requireCycleIgnorePatterns = [
/(^|\/|\\)node_modules($|\/|\\)/,
/(^|\/|\\)packages($|\/|\\)/,
];
// Use turborepo to restore the cache when possible
config.cacheStores = [

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-clerk",
"main": "index.js",
"version": "1.0.27",
"version": "1.0.31",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",
@@ -17,62 +17,61 @@
},
"dependencies": {
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bam.tech/react-native-image-resizer": "^3.0.10",
"@bam.tech/react-native-image-resizer": "^3.0.11",
"@clerk/clerk-expo": "^2.2.21",
"@expo/vector-icons": "^14.0.2",
"@react-native-community/netinfo": "^11.3.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/native": "^7.0.13",
"@react-navigation/native-stack": "^7.1.14",
"base-64": "^1.0.0",
"buffer": "^6.0.3",
"clsx": "^2.0.0",
"expo": "~51.0.37",
"expo-build-properties": "~0.12.5",
"expo-clipboard": "~6.0.3",
"expo-constants": "~16.0.2",
"expo-crypto": "~13.0.2",
"expo-dev-client": "~4.0.28",
"expo-file-system": "^17.0.1",
"expo-font": "~12.0.4",
"expo-linking": "~6.3.1",
"expo-router": "~3.5.23",
"expo-secure-store": "~13.0.2",
"expo-splash-screen": "~0.27.5",
"expo-status-bar": "~1.12.1",
"expo-system-ui": "~3.0.7",
"expo-web-browser": "~13.0.3",
"jazz-react-auth-clerk": "workspace:*",
"expo": "^52.0.0",
"expo-build-properties": "~0.13.1",
"expo-clipboard": "~7.0.0",
"expo-constants": "~17.0.3",
"expo-crypto": "~14.0.1",
"expo-dev-client": "~5.0.5",
"expo-file-system": "^18.0.4",
"expo-font": "~13.0.1",
"expo-linking": "~7.0.3",
"expo-router": "~4.0.11",
"expo-secure-store": "~14.0.0",
"expo-splash-screen": "~0.29.16",
"expo-status-bar": "~2.0.0",
"expo-system-ui": "~4.0.5",
"expo-web-browser": "~14.0.1",
"jazz-react-native": "workspace:*",
"jazz-react-native-auth-clerk": "workspace:*",
"jazz-react-native-media-images": "workspace:*",
"jazz-tools": "workspace:*",
"nativewind": "^2.0.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-native": "~0.74.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-native": "~0.76.3",
"react-native-fetch-api": "^3.0.0",
"react-native-gesture-handler": "~2.16.1",
"react-native-gesture-handler": "~2.20.2",
"react-native-get-random-values": "^1.11.0",
"react-native-mmkv": "3.0.1",
"react-native-polyfill-globals": "^3.1.0",
"react-native-quick-base64": "^2.1.2",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1",
"react-native-reanimated": "~3.16.3",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "4.1.0",
"react-native-url-polyfill": "^2.0.0",
"react-native-web": "~0.19.10",
"react-native-web": "~0.19.13",
"text-encoding": "^0.7.0",
"web-streams-polyfill": "^3.2.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/jest": "^29.5.3",
"@types/react": "^18.2.19",
"@types/react": "^18.3.12",
"@types/react-test-renderer": "^18.0.7",
"jest": "^29.2.1",
"jest-expo": "~51.0.3",
"jest-expo": "~52.0.2",
"react-test-renderer": "18.2.0",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3"
"tailwindcss": "^3.4.9",
"typescript": "~5.6.2"
},
"private": true
}

View File

@@ -1,5 +1,5 @@
import { useClerk, useUser } from "@clerk/clerk-expo";
import { useJazzClerkAuth } from "jazz-react-auth-clerk";
import { useJazzClerkAuth } from "jazz-react-native-auth-clerk";
import React, {
createContext,
PropsWithChildren,
@@ -8,7 +8,7 @@ import React, {
useState,
} from "react";
import { Text, View } from "react-native";
import { Jazz } from "./jazz";
import { Jazz, kvStore } from "./jazz";
const AuthContext = createContext<{
isAuthenticated: boolean;
@@ -25,7 +25,7 @@ export function useAuth() {
export function JazzAndAuth({ children }: PropsWithChildren) {
const { isSignedIn, isLoaded: isClerkLoaded } = useUser();
const clerk = useClerk();
const [auth, state] = useJazzClerkAuth(clerk);
const [auth, state] = useJazzClerkAuth(clerk, kvStore);
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
@@ -46,7 +46,7 @@ export function JazzAndAuth({ children }: PropsWithChildren) {
<Text style={{ color: "red" }}>{error}</Text>
</View>
))}
{auth ? (
{auth && clerk.user ? (
<Jazz.Provider
auth={auth}
peer="wss://cloud.jazz.tools/?key=chat-rn-clerk-example-jazz@garden.co"

View File

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

View File

@@ -1,5 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
@@ -10,3 +11,5 @@ module.exports = {
},
plugins: [],
};
export default config;

View File

@@ -1,5 +1,37 @@
# chat-rn
## 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
- jazz-react-native@0.8.37
- jazz-tools@0.8.37
## 1.0.26
### Patch Changes
- Updated dependencies [c84764a]
- Updated dependencies [441fe27]
- jazz-react-native@0.8.36
- jazz-tools@0.8.36
## 1.0.25
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.25",
"version": "1.0.29",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",
@@ -13,41 +13,40 @@
},
"dependencies": {
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@react-native-community/netinfo": "^11.3.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/native-stack": "^6.11.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-navigation/native": "^7.0.13",
"@react-navigation/native-stack": "^7.1.14",
"base-64": "^1.0.0",
"clsx": "^2.0.0",
"expo": "~51.0.37",
"expo-build-properties": "~0.12.5",
"expo-clipboard": "~6.0.3",
"expo-constants": "~16.0.2",
"expo-crypto": "~13.0.2",
"expo-dev-client": "~4.0.28",
"expo-linking": "~6.3.1",
"expo-secure-store": "~13.0.2",
"expo-status-bar": "~1.12.1",
"expo-web-browser": "~13.0.3",
"expo": "^52.0.0",
"expo-build-properties": "~0.13.1",
"expo-clipboard": "~7.0.0",
"expo-constants": "~17.0.3",
"expo-crypto": "~14.0.1",
"expo-dev-client": "~5.0.5",
"expo-linking": "~7.0.3",
"expo-secure-store": "~14.0.0",
"expo-status-bar": "~2.0.0",
"expo-web-browser": "~14.0.1",
"jazz-react-native": "workspace:*",
"jazz-tools": "workspace:*",
"nativewind": "^2.0.11",
"react": "^18.2.0",
"react-native": "~0.74.5",
"react": "^18.3.1",
"react-native": "~0.76.3",
"react-native-fetch-api": "^3.0.0",
"react-native-get-random-values": "^1.11.0",
"react-native-mmkv": "3.0.1",
"react-native-polyfill-globals": "^3.1.0",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "4.1.0",
"react-native-url-polyfill": "^2.0.0",
"text-encoding": "^0.7.0",
"web-streams-polyfill": "^3.2.1"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/react": "^18.2.19",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3"
"@types/react": "^18.3.12",
"tailwindcss": "^3.4.9",
"typescript": "~5.6.2"
},
"private": true
}

View File

@@ -1,12 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -1,5 +1,40 @@
# chat-vue
## 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
- jazz-browser@0.8.37
- jazz-tools@0.8.37
- jazz-vue@0.8.25
## 0.0.19
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-browser@0.8.36
- jazz-vue@0.8.24
## 0.0.18
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-vue",
"version": "0.0.18",
"version": "0.0.22",
"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.14",
"eslint": "^8.46.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.7.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",
"tailwindcss": "^3.4.9",
"typescript": "~5.6.2",
"vite": "^5.4.10",
"vite-plugin-vue-devtools": "^7.4.6",
"vue-tsc": "^2.1.6"
}

View File

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

View File

@@ -0,0 +1,11 @@
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,5 +1,44 @@
# jazz-example-chat
## 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
- Updated dependencies [3d9f12e]
- cojson@0.8.37
- jazz-react@0.8.37
- jazz-tools@0.8.37
## 0.0.114
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
- jazz-tools@0.8.36
- jazz-react@0.8.36
## 0.0.113
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.113",
"version": "0.0.117",
"type": "module",
"scripts": {
"dev": "vite",
@@ -18,14 +18,15 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.35",
"cojson": "workspace:0.8.39",
"hash-slash": "workspace:0.2.1",
"jazz-react": "workspace:0.8.35",
"jazz-tools": "workspace:0.8.35",
"jazz-browser-media-images": "workspace:0.8.39",
"jazz-react": "workspace:0.8.39",
"jazz-tools": "workspace:0.8.39",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"react-use": "^17.4.0",
@@ -36,14 +37,14 @@
"devDependencies": {
"@playwright/test": "^1.46.1",
"@types/qrcode": "^1.5.1",
"@types/react": "^18.2.19",
"@types/react-dom": "^18.2.7",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"autoprefixer": "^10.4.20",
"is-ci": "^3.0.1",
"postcss": "^8.4.27",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",
"vite": "^5.0.10"
"tailwindcss": "^3.4.9",
"typescript": "~5.6.2",
"vite": "^5.4.10"
}
}

View File

@@ -18,7 +18,7 @@ export function App() {
router.navigate("/#/chat/" + chat.id);
// for https://jazz.tools marketing site demo only
onChatLoad(chat, me);
onChatLoad(chat);
};
return (

View File

@@ -1,21 +1,54 @@
import { createImage } from "jazz-browser-media-images";
import { ID } from "jazz-tools";
import { useState } from "react";
import { useCoState } from "./main.tsx";
import { useAccount, 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 { me } = useAccount();
const [showNLastMessages, setShowNLastMessages] = useState(30);
return chat ? (
if (!chat)
return (
<div className="flex-1 flex justify-center items-center">Loading...</div>
);
const sendImage = (event: React.ChangeEvent<HTMLInputElement>) => {
if (!me?.profile) return;
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 },
{ owner: chat._owner },
),
);
});
};
return (
<>
<ChatBody>
{chat.length > 0 ? (
@@ -35,24 +68,31 @@ export function ChatScreen(props: { chatID: ID<Chat> }) {
</button>
)}
</ChatBody>
<ChatInput
onSubmit={(text) => {
chat.push(Message.create({ text }, { owner: chat._owner }));
}}
/>
<InputBar>
<ImageInput onImageChange={sendImage} />
<TextInput
onSubmit={(text) => {
chat.push(Message.create({ text }, { owner: chat._owner }));
}}
/>
</InputBar>
</>
) : (
<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}>{props.msg.text}</BubbleBody>
<BubbleBody fromMe={fromMe}>
{image && <BubbleImage image={image} />}
<BubbleText text={text} />
</BubbleBody>
<BubbleInfo by={lastEdit.by?.profile?.name} madeAt={lastEdit.madeAt} />
</BubbleContainer>
);

View File

@@ -1,7 +1,8 @@
import { CoList, CoMap, co } from "jazz-tools";
import { CoList, CoMap, ImageDefinition, 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,5 +1,8 @@
import clsx from "clsx";
import { useId } from "react";
import { ProgressiveImg } from "jazz-react";
import { ImageDefinition } from "jazz-tools";
import { ImageIcon } from "lucide-react";
import { useId, useRef } from "react";
export function AppContainer(props: { children: React.ReactNode }) {
return (
@@ -56,7 +59,7 @@ export function BubbleBody(props: {
<div
className={clsx(
"line-clamp-10 text-ellipsis whitespace-pre-wrap",
"rounded-2xl max-w-full py-1 px-3 shadow-sm",
"rounded-2xl overflow-hidden max-w-[calc(100%-5rem)] shadow-sm p-1",
props.fromMe
? "bg-white dark:bg-stone-700 dark:text-white"
: "bg-blue text-white",
@@ -67,6 +70,23 @@ 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">
@@ -75,17 +95,59 @@ export function BubbleInfo(props: { by: string | undefined; madeAt: Date }) {
);
}
export function ChatInput(props: { onSubmit: (text: string) => void }) {
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 }) {
const inputId = useId();
return (
<div className="p-3 bg-white border-t shadow-2xl mt-auto dark:bg-transparent dark:border-stone-800">
<div className="flex-1">
<label className="sr-only" htmlFor={inputId}>
Type a message and press Enter
</label>
<input
id={inputId}
className="rounded-full py-2 px-4 border block w-full dark:bg-black dark:text-white dark:border-stone-700"
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"
placeholder="Type a message and press Enter"
maxLength={2048}
onKeyDown={({ key, currentTarget: input }) => {

View File

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

View File

@@ -1,82 +0,0 @@
/** @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,5 +1,7 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
import type { Config } from "tailwindcss";
import animate from "tailwindcss-animate";
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
@@ -58,12 +60,12 @@ module.exports = {
},
keyframes: {
"accordion-down": {
from: { height: 0 },
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
to: { height: "0" },
},
},
animation: {
@@ -72,5 +74,7 @@ module.exports = {
},
},
},
plugins: [require("tailwindcss-animate")],
plugins: [animate],
};
export default config;

View File

@@ -1,5 +1,39 @@
# minimal-auth-clerk
## 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
- jazz-react@0.8.37
- jazz-react-auth-clerk@0.8.37
- jazz-tools@0.8.37
## 0.0.13
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
- jazz-react-auth-clerk@0.8.36
## 0.0.12
### Patch Changes

View File

@@ -1,4 +1,4 @@
# Passkey authentication example with Jazz and React
# Clerk authentication example with Jazz and React
This is an example of how to use clerk authentication with Jazz.

View File

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

23
examples/file-share-svelte/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
node_modules
# Output
.output
.vercel
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
playwright-report

View File

@@ -0,0 +1 @@
engine-strict=true

View File

@@ -0,0 +1,4 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

View File

@@ -0,0 +1,16 @@
{
"useTabs": false,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

View File

@@ -0,0 +1,10 @@
# file-share-svelte
## 0.0.2
### Patch Changes
- Updated dependencies [249eecb]
- Updated dependencies [aa21072]
- jazz-tools@0.8.39
- jazz-svelte@0.8.39

View File

@@ -0,0 +1,56 @@
# File share example with Jazz and Svelte
This example app demonstrates how to implement secure file sharing in a Svelte application using Jazz.
## Features
This example showcases how to:
- Upload files securely with end-to-end encryption
- Generate and manage sharing links
- Handle file downloads with decryption
- Manage file access permissions
- Authenticate users using passkeys
## Getting Started
1. Clone the repository:
```sh
git clone https://github.com/garden-co/jazz.git
```
2. Navigate to the example directory:
```sh
cd examples/file-share-svelte
```
3. Install dependencies:
```sh
pnpm install
```
4. Run the development server:
```sh
turbo dev
```
5. Open your browser and visit [http://localhost:5173](http://localhost:5173)
---
Alternatively, you can build and preview the app:
```sh
turbo build
pnpm preview
```
## Learn More
- [Jazz Documentation](https://jazz.tools/docs/svelte)
- [Svelte Documentation](https://svelte.dev)
- [WebAuthn API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)

View File

@@ -0,0 +1,33 @@
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
import svelte from 'eslint-plugin-svelte';
import globals from 'globals';
import ts from 'typescript-eslint';
export default ts.config(
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs['flat/recommended'],
prettier,
...svelte.configs['flat/prettier'],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
}
},
{
files: ['**/*.svelte'],
languageOptions: {
parserOptions: {
parser: ts.parser
}
}
},
{
ignores: ['build/', '.svelte-kit/', 'dist/']
}
);

View File

@@ -0,0 +1,47 @@
{
"name": "file-share-svelte",
"version": "0.0.2",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
"lint": "prettier --check . && eslint .",
"format-and-lint": "pnpm run format && pnpm run lint",
"format-and-lint:fix": "pnpm run format --write && pnpm run lint --fix",
"test": "playwright test",
"test:ui": "playwright test --ui"
},
"devDependencies": {
"@sveltejs/adapter-vercel": "^5.5.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.1",
"@types/is-ci": "^3.0.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
"globals": "^15.11.0",
"is-ci": "^3.0.1",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.5",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"tailwindcss": "^3.4.9",
"typescript": "~5.6.2",
"typescript-eslint": "^8.0.0",
"vite": "^5.4.10"
},
"dependencies": {
"@tailwindcss/typography": "^0.5.15",
"jazz-svelte": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-svelte": "^0.463.0",
"svelte-sonner": "^0.3.28"
}
}

View File

@@ -0,0 +1,53 @@
import { defineConfig, devices } from "@playwright/test";
import isCI from "is-ci";
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* 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: {}
}
};

View File

@@ -0,0 +1,3 @@
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

13
examples/file-share-svelte/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@@ -0,0 +1,117 @@
<script lang="ts">
import { slide } from 'svelte/transition';
import { SharedFile } from '$lib/schema';
import { FileStream } from 'jazz-tools';
import { File, FileDown, Trash2, Link2 } from 'lucide-svelte';
import { useAccount } from '$lib/jazz';
import { toast } from 'svelte-sonner';
import { formatFileSize } from '$lib/utils';
const {
file,
loading = false,
onDelete
}: {
file: SharedFile;
loading?: boolean;
onDelete: (file: SharedFile) => void;
} = $props();
const { me } = useAccount();
const isAdmin = $derived(me && file._owner?.myRole() === 'admin');
async function downloadFile() {
if (!file._refs.file?.id || !me) {
toast.error('Failed to download file');
return;
}
try {
const fileId = file._refs.file.id;
// Load the file as a blob, can take a while
const blob = await FileStream.loadAsBlob(fileId, me, {});
if (!blob) {
toast.error('Failed to download file');
return;
}
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file.name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast.success('File downloaded successfully');
} catch (error) {
console.error('Error downloading file:', error);
toast.error('Failed to download file');
}
}
async function shareFile() {
try {
const fileUrl = `${window.location.origin}/file/${file.id}`;
await navigator.clipboard.writeText(fileUrl);
toast.success('Share link copied to clipboard');
} catch (error) {
console.error('Error sharing file:', error);
toast.error('Failed to create share link');
}
}
</script>
<div
class="flex items-center justify-between rounded-lg border border-gray-200 bg-white p-4"
transition:slide={{ duration: 200 }}
>
<div class="flex items-center space-x-4">
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600">
<File class="h-6 w-6" />
</div>
<div>
<a href="/file/{file.id}" class="hover:text-blue-600 hover:underline">
<h3 class="font-medium text-gray-900">{file.name}</h3>
</a>
<p class="text-sm text-gray-500">
{isAdmin ? 'Owned by you' : ''} • Uploaded {new Date(
file.createdAt || 0
).toLocaleDateString()}
{formatFileSize(file.size || 0)}
</p>
</div>
</div>
<div class="flex items-center space-x-2">
{#if loading}
<div class="text-sm text-gray-500">Uploading...</div>
{:else}
<button
onclick={downloadFile}
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700"
aria-label="Download file"
>
<FileDown class="h-5 w-5" />
</button>
{#if isAdmin}
<button
onclick={shareFile}
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700"
aria-label="Share file"
>
<Link2 class="h-5 w-5" />
</button>
<button
onclick={() => onDelete(file)}
class="rounded-lg p-2 text-gray-500 transition-colors hover:bg-gray-100 hover:text-red-600"
aria-label="Delete file"
>
<Trash2 class="h-5 w-5" />
</button>
{/if}
{/if}
</div>
</div>

View File

@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View File

@@ -0,0 +1,7 @@
import { createJazzApp } from 'jazz-svelte';
import { FileShareAccount } from './schema';
export const { useAccount, useCoState, useAcceptInvite, useAccountOrGuest, Provider } =
createJazzApp({
AccountSchema: FileShareAccount
});

View File

@@ -0,0 +1,51 @@
import { Account, CoList, CoMap, FileStream, Profile, co, Group } from 'jazz-tools';
export class SharedFile extends CoMap {
name = co.string;
file = co.ref(FileStream);
createdAt = co.Date;
uploadedAt = co.Date;
size = co.number;
}
export class FileShareProfile extends Profile {
name = co.string;
}
export class ListOfSharedFiles extends CoList.Of(co.ref(SharedFile)) {}
export class FileShareAccountRoot extends CoMap {
type = co.string;
sharedFiles = co.ref(ListOfSharedFiles);
publicGroup = co.ref(Group);
}
export class FileShareAccount extends Account {
profile = co.ref(FileShareProfile);
root = co.ref(FileShareAccountRoot);
/** 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.
*/
async migrate(creationProps?: { name: string }) {
super.migrate(creationProps);
await this._refs.root?.load();
// Initialize root if it doesn't exist
if (!this.root || this.root.type !== 'file-share-account') {
// Create a group that will own all shared files
const publicGroup = Group.create({ owner: this });
publicGroup.addMember('everyone', 'reader');
this.root = FileShareAccountRoot.create(
{
type: 'file-share-account',
sharedFiles: ListOfSharedFiles.create([], { owner: publicGroup }),
publicGroup
},
{ owner: this }
);
}
}
}

View File

@@ -0,0 +1,22 @@
/**
* Formats a file size in bytes to a human readable string
* @param bytes The size in bytes
* @returns A formatted string like "1.5 MB"
*/
export function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
}
/**
* Generates a temporary file ID based on the file name and creation time
* @param fileName The name of the file
* @param createdAt The creation date
* @returns A unique file ID string
*/
export function generateTempFileId(fileName: string, createdAt: Date): string {
return `file-${fileName}-${createdAt.getTime()}`;
}

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import { Provider } from '$lib/jazz';
import { PasskeyAuthBasicUI, usePasskeyAuth } from 'jazz-svelte';
import { Toaster } from 'svelte-sonner';
import '../app.css';
let { children } = $props();
const auth = usePasskeyAuth({
appName: 'File Share'
});
</script>
<svelte:head>
<title>File Share</title>
</svelte:head>
<Toaster richColors />
{#if auth.state.state === 'ready'}
<div class="fixed inset-0 flex items-center justify-center bg-gray-50/80">
<div class="rounded-lg bg-white p-8 shadow-lg">
<PasskeyAuthBasicUI state={auth.state} />
</div>
</div>
{/if}
{#if auth.current}
<Provider auth={auth.current} peer="wss://cloud.jazz.tools/?key=file-share-svelte@garden.co">
<div class="min-h-screen bg-gray-100">
{@render children()}
</div>
</Provider>
{/if}

View File

@@ -0,0 +1,145 @@
<script lang="ts">
import { useAccount, useCoState } from '$lib/jazz';
import { SharedFile, ListOfSharedFiles } from '$lib/schema';
import { createInviteLink } from 'jazz-svelte';
import { FileStream } from 'jazz-tools';
import FileItem from '$lib/components/FileItem.svelte';
import { SvelteMap } from 'svelte/reactivity';
import { generateTempFileId } from '$lib/utils';
import { CloudUpload } from 'lucide-svelte';
const { me, logOut } = useAccount();
const mySharedFilesId = me?.root?._refs.sharedFiles.id;
const sharedFiles = $derived(useCoState(ListOfSharedFiles, mySharedFilesId, [{}]));
let fileInput: HTMLInputElement;
type PendingSharedFile = {
name: string;
id: string;
createdAt: Date;
};
// Track files that are currently uploading
const uploadingFiles = new SvelteMap<string, PendingSharedFile>();
async function handleFileUpload(event: Event) {
const input = event.target as HTMLInputElement;
const files = input.files;
if (!files || !files.length || !me?.root?.sharedFiles || !me.root.publicGroup) return;
const file = files[0];
const fileName = file.name;
const createdAt = new Date();
const fileId = generateTempFileId(fileName, createdAt);
const tempFile: PendingSharedFile = {
name: fileName,
id: fileId,
createdAt
};
// Add to uploading files
uploadingFiles.set(fileId, tempFile);
try {
const ownership = { owner: me.root.publicGroup };
// Create a FileStream from the uploaded file
const fileStream = await FileStream.createFromBlob(file, ownership);
// Create the shared file entry
const sharedFile = SharedFile.create(
{
name: fileName,
file: fileStream,
createdAt,
uploadedAt: new Date(),
size: file.size
},
ownership
);
// Add the file to the user's files list
me.root.sharedFiles.push(sharedFile);
} finally {
uploadingFiles.delete(fileId);
fileInput.value = ''; // reset input
}
}
async function shareFile(file: SharedFile) {
const inviteLink = createInviteLink(file, 'reader');
await navigator.clipboard.writeText(inviteLink);
alert('Share link copied to clipboard!');
}
async function deleteFile(file: SharedFile) {
if (!me?.root?.sharedFiles || !sharedFiles.current) return;
const index = sharedFiles.current.indexOf(file);
if (index > -1) {
me.root.sharedFiles.splice(index, 1);
}
}
</script>
<div class="min-h-screen bg-gray-50">
<div class="container mx-auto max-w-4xl px-4 py-8">
<div class="mb-12 flex items-center justify-between">
<div>
<h1 class="mb-2 text-4xl font-bold text-gray-900">File Share</h1>
<h2 class="text-xl text-gray-600">Welcome back, {me?.profile?.name}</h2>
</div>
<button
onclick={logOut}
class="rounded-lg bg-red-500 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
>
Log Out
</button>
</div>
<!-- Upload Section -->
<div class="mb-8 rounded-xl bg-white p-6 shadow-sm">
<div
class="group relative flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-gray-300 bg-white p-12 text-center hover:border-blue-500 hover:bg-blue-50"
onclick={() => fileInput.click()}
onkeydown={(e) => e.key === 'Enter' && fileInput.click()}
role="button"
tabindex="0"
>
<CloudUpload class="mb-2 h-8 w-8 text-gray-400 group-hover:text-blue-600" />
<h3 class="mb-1 text-lg font-medium text-gray-900">Upload a new file</h3>
<p class="text-sm text-gray-500">Click to select a file from your computer</p>
<input
type="file"
bind:this={fileInput}
onchange={handleFileUpload}
class="hidden"
accept="*/*"
/>
</div>
</div>
<!-- Files List -->
<div class="space-y-4">
{#if sharedFiles.current}
{#if !(sharedFiles.current.length === 0 && uploadingFiles.size === 0)}
{#each [...sharedFiles.current, ...uploadingFiles.values()] as file (generateTempFileId(file?.name, file?.createdAt))}
<FileItem
{file}
loading={uploadingFiles.has(generateTempFileId(file?.name, file?.createdAt))}
onShare={shareFile}
onDelete={deleteFile}
/>
{/each}
{:else}
<p class="text-center text-gray-500">No files yet</p>
{/if}
{/if}
</div>
</div>
</div>

View File

@@ -0,0 +1,103 @@
<script lang="ts">
import { page } from '$app/stores';
import { useAccount, useCoState } from '$lib/jazz';
import { SharedFile } from '$lib/schema';
import { File, FileDown, Link2 } from 'lucide-svelte';
import type { ID } from 'jazz-tools';
import { FileStream } from 'jazz-tools';
import { toast } from 'svelte-sonner';
const { me } = useAccount();
const fileId = $page.params.fileId;
const file = $state(useCoState(SharedFile, fileId as ID<SharedFile>, {}));
const isAdmin = $derived(me && file.current?._owner?.myRole() === 'admin');
async function downloadFile() {
if (!file.current?._refs.file?.id || !me) {
toast.error('Failed to download file');
return;
}
try {
const fileId = file.current._refs.file.id;
const blob = await FileStream.loadAsBlob(fileId, me, {});
if (!blob) {
toast.error('Failed to download file');
return;
}
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file.current.name;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
toast.success('File downloaded successfully');
} catch (error) {
console.error('Error downloading file:', error);
toast.error('Failed to download file');
}
}
async function shareFile() {
try {
const fileUrl = `${window.location.origin}/file/${file.current?.id}`;
await navigator.clipboard.writeText(fileUrl);
toast.success('Share link copied to clipboard');
} catch (error) {
console.error('Error sharing file:', error);
toast.error('Failed to copy share link');
}
}
</script>
<svelte:head>
<title>{file.current?.name} | File Share</title>
</svelte:head>
{#if file.current}
<div class="container mx-auto max-w-3xl p-4">
<div class="rounded-lg bg-white p-6 shadow-md">
<div class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-3">
<File class="h-6 w-6" />
<h1 class="text-2xl font-semibold">{file.current.name}</h1>
</div>
<div class="flex gap-2">
{#if isAdmin}
<button
onclick={shareFile}
class="flex items-center gap-2 rounded-md bg-gray-100 px-4 py-2 text-gray-700 hover:bg-gray-200"
>
<Link2 class="h-4 w-4" />
Share
</button>
{/if}
{#if file.current._refs.file}
<button
onclick={downloadFile}
class="flex items-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
>
<FileDown class="h-4 w-4" />
Download
</button>
{/if}
</div>
</div>
<p class="text-gray-600">
{isAdmin ? 'You own this file' : 'Shared with you'} • Uploaded {new Date(
file.current.createdAt || 0
).toLocaleDateString()}
</p>
</div>
</div>
{:else}
<div class="container mx-auto max-w-3xl p-4">
<div class="rounded-lg bg-white p-6 shadow-md">
<p class="text-gray-600">Loading file...</p>
</div>
</div>
{/if}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,15 @@
import adapter from '@sveltejs/adapter-vercel';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
adapter: adapter()
}
};
export default config;

View File

@@ -0,0 +1,12 @@
import typography from '@tailwindcss/typography';
import type { Config } from 'tailwindcss';
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: [typography]
} satisfies Config;

View File

@@ -0,0 +1 @@
This is a test file for e2e testing

View File

@@ -0,0 +1,82 @@
import { expect, test } from '@playwright/test';
import type { BrowserContext } from 'playwright-core';
import path from 'path';
async function mockAuthenticator(context: BrowserContext) {
await context.addInitScript(() => {
Object.defineProperty(window.navigator, 'credentials', {
value: {
...window.navigator.credentials,
create: async () => ({
type: 'public-key',
id: new Uint8Array([1, 2, 3, 4]),
rawId: new Uint8Array([1, 2, 3, 4]),
response: {
clientDataJSON: new Uint8Array([1]),
attestationObject: new Uint8Array([2])
}
}),
get: async () => ({
type: 'public-key',
id: new Uint8Array([1, 2, 3, 4]),
rawId: new Uint8Array([1, 2, 3, 4]),
response: {
authenticatorData: new Uint8Array([1]),
clientDataJSON: new Uint8Array([2]),
signature: new Uint8Array([3])
}
})
},
configurable: true
});
});
}
// Configure the authenticator
test.beforeEach(async ({ context }) => {
// Enable virtual authenticator environment
await mockAuthenticator(context);
});
test('can login with passkey and upload file', async ({ page, browser }) => {
// Navigate to the home page
await page.goto('/');
// Click login and handle the passkey authentication
await page.getByRole('textbox').fill('Capitan Hook');
await page.getByRole('button', { name: "Sign up" }).click();
// Verify successful login by checking for user-specific element
await expect(page.getByText("File Share")).toBeVisible();
// Prepare file upload
const filePath = path.join(import.meta.dirname, 'fixtures/test-file.txt');
// Click upload button and handle file selection
const fileChooserPromise = page.waitForEvent('filechooser');
await page.getByRole('button', { name: /upload|add file/i }).click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(filePath);
// Verify the uploaded file appears in the list
await expect(page.getByText('test-file.txt')).toBeVisible();
await page.getByRole('button', { name: 'Share file' }).click();
const inviteLink = await page.evaluate(() => navigator.clipboard.readText());
// Create a new incognito instance and try to load the shared file
const newContext = await browser.newContext();
await mockAuthenticator(newContext);
const newUserPage = await newContext.newPage();
await newUserPage.goto(`/`);
await newUserPage.getByRole('textbox').fill('Mr. Smee');
await newUserPage.getByRole('button', { name: "Sign up" }).click();
await expect(newUserPage.getByText("File Share")).toBeVisible();
await newUserPage.goto(inviteLink);
await expect(newUserPage.getByText("test-file.txt")).toBeVisible();
});

View File

@@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [sveltekit()],
});

27
examples/form/.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# 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?
/test-results/
/playwright-report/

50
examples/form/README.md Normal file
View File

@@ -0,0 +1,50 @@
# Form example with Jazz and React
Live version: [https://form-demo.jazz.tools](https://form-demo.jazz.tools)
This is a simple form example that shows you how to make a form for creating and editing a `CoValue`,
called `BubbleTeaOrder`, with fields of different types such
as single-select, multi-select, date, text, and boolean.
To create a new `BubbleTeaOrder`, we create an empty order. Because `BubbleTeaOrder` has some
required fields, we can't create an empty `BubbleTeaOrder` directly.
Instead, we create a `DraftBubbleTeaOrder`,
which has the same structure as `BubbleTeaOrder`, but with all fields set to `optional`.
When the user is ready to submit the order, we treat `DraftBubbleTeaOrder` as a "real order" by
converting it into a `BubbleTeaOrder`.
## 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 form example directory:
```bash
cd jazz/examples/form
```
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/main.tsx](./src/main.tsx).

12
examples/form/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Form example</title>
</head>
<body class="h-full flex flex-col bg-white text-stone-700 dark:text-stone-400 dark:bg-stone-925">
<div id="root" class="align-self-center flex-1"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,37 @@
{
"name": "form",
"private": true,
"version": "0.0.12",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"format-and-lint": "biome check .",
"format-and-lint:fix": "biome check . --write"
},
"dependencies": {
"hash-slash": "workspace:*",
"jazz-browser-media-images": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.41.5"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@playwright/test": "^1.46.1",
"@tailwindcss/forms": "^0.5.9",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"is-ci": "^3.0.1",
"globals": "^15.11.0",
"postcss": "^8.4.27",
"tailwindcss": "^3.4.9",
"typescript": "~5.6.2",
"vite": "^5.4.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: {},
},
};

40
examples/form/src/App.tsx Normal file
View File

@@ -0,0 +1,40 @@
import { useIframeHashRouter } from "hash-slash";
import { ID } from "jazz-tools";
import { CreateOrder } from "./CreateOrder.tsx";
import { EditOrder } from "./EditOrder.tsx";
import { Orders } from "./Orders.tsx";
import { useAccount } from "./main";
import { BubbleTeaOrder } from "./schema.ts";
function App() {
const { me, logOut } = useAccount();
const router = useIframeHashRouter();
return (
<>
<header>
<nav className="container py-2 border-b flex items-center justify-between">
<span>
You're logged in as <strong>{me?.profile?.name}</strong>
</span>
<button
className="bg-stone-100 py-1.5 px-3 text-sm rounded-md dark:bg-stone-900 dark:text-white"
onClick={() => logOut()}
>
Log out
</button>
</nav>
</header>
<main className="container py-8 space-y-8">
{router.route({
"/": () => <Orders />,
"/order": () => <CreateOrder />,
"/order/:id": (id) => <EditOrder id={id as ID<BubbleTeaOrder>} />,
})}
</main>
</>
);
}
export default App;

View File

@@ -0,0 +1,77 @@
import { useIframeHashRouter } from "hash-slash";
import { ID } from "jazz-tools";
import { useState } from "react";
import { Errors } from "./Errors.tsx";
import { LinkToHome } from "./LinkToHome.tsx";
import { OrderForm } from "./OrderForm.tsx";
import { useAccount, useCoState } from "./main.tsx";
import {
BubbleTeaOrder,
DraftBubbleTeaOrder,
ListOfBubbleTeaAddOns,
} from "./schema.ts";
export function CreateOrder() {
const { me } = useAccount({ profile: { draft: {}, orders: [] } });
const router = useIframeHashRouter();
const [errors, setErrors] = useState<string[]>([]);
if (!me?.profile) return;
const onSave = (draft: DraftBubbleTeaOrder) => {
// validate if the draft is a valid order
const validation = draft.validate();
setErrors(validation.errors);
if (validation.errors.length > 0) {
return;
}
// turn the draft into a real order
me.profile.orders.push(draft as BubbleTeaOrder);
// reset the draft
me.profile.draft = DraftBubbleTeaOrder.create(
{
addOns: ListOfBubbleTeaAddOns.create([], { owner: me.profile._owner }),
},
{ owner: me.profile._owner },
);
router.navigate("/");
};
return (
<>
<LinkToHome />
<h1 className="text-lg">
<strong>Make a new bubble tea order 🧋</strong>
</h1>
<Errors errors={errors} />
<CreateOrderForm id={me?.profile?.draft.id} onSave={onSave} />
</>
);
}
function CreateOrderForm({
id,
onSave,
}: {
id: ID<DraftBubbleTeaOrder>;
onSave: (draft: DraftBubbleTeaOrder) => void;
}) {
const draft = useCoState(DraftBubbleTeaOrder, id, {
addOns: [],
});
if (!draft) return;
const addOrder = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSave(draft);
};
return <OrderForm order={draft} onSave={addOrder} />;
}

View File

@@ -0,0 +1,15 @@
import { useAccount } from "./main.tsx";
export function DraftIndicator() {
const { me } = useAccount({
profile: { draft: {} },
});
if (me?.profile.draft?.hasChanges) {
return (
<div className="absolute -top-1 -right-1 bg-blue-500 border-2 border-white w-3 h-3 rounded-full dark:border-stone-925">
<span className="sr-only">You have a draft</span>
</div>
);
}
}

View File

@@ -0,0 +1,26 @@
import { ID } from "jazz-tools";
import { LinkToHome } from "./LinkToHome.tsx";
import { OrderForm } from "./OrderForm.tsx";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
import { useCoState } from "./main.tsx";
import { BubbleTeaOrder } from "./schema.ts";
export function EditOrder(props: { id: ID<BubbleTeaOrder> }) {
const order = useCoState(BubbleTeaOrder, props.id, []);
if (!order) return;
return (
<>
<LinkToHome />
<OrderThumbnail order={order} />
<h1 className="text-lg">
<strong>Edit your bubble tea order 🧋</strong>
</h1>
<OrderForm order={order} />
</>
);
}

View File

@@ -0,0 +1,13 @@
export function Errors({ errors }: { errors: string[] }) {
if (errors.length === 0) return null;
return (
<div className="rounded-md bg-red-50 border border-red-50 p-4 text-sm text-red-600 dark:bg-transparent dark:border-red-500 dark:text-red-500">
<ul role="list" className="list-disc space-y-1 pl-5">
{errors.map((error, i) => (
<li key={i}>{error}</li>
))}
</ul>
</div>
);
}

View File

@@ -0,0 +1,7 @@
export function LinkToHome() {
return (
<a href="/#" className="text-sm text-stone-600">
&lt; Back to all orders
</a>
);
}

View File

@@ -0,0 +1,107 @@
import {
BubbleTeaAddOnTypes,
BubbleTeaBaseTeaTypes,
BubbleTeaOrder,
DraftBubbleTeaOrder,
} from "./schema.ts";
export function OrderForm({
order,
onSave,
}: {
order: BubbleTeaOrder | DraftBubbleTeaOrder;
onSave?: (e: React.FormEvent<HTMLFormElement>) => void;
}) {
return (
<form onSubmit={onSave} className="grid gap-5">
<div className="flex flex-col gap-2">
<label htmlFor="baseTea">Base tea</label>
<select
name="baseTea"
id="baseTea"
value={order.baseTea || ""}
className="dark:bg-transparent"
onChange={(e) => (order.baseTea = e.target.value as any)}
required
>
<option value="" disabled>
Please select your preferred base tea
</option>
{BubbleTeaBaseTeaTypes.map((teaType) => (
<option key={teaType} value={teaType}>
{teaType}
</option>
))}
</select>
</div>
<fieldset>
<legend className="mb-2">Add-ons</legend>
{BubbleTeaAddOnTypes.map((addOn) => (
<div key={addOn} className="flex items-center gap-2">
<input
type="checkbox"
value={addOn}
name={addOn}
id={addOn}
checked={order.addOns?.includes(addOn) || false}
onChange={(e) => {
if (e.target.checked) {
order.addOns?.push(addOn);
} else {
order.addOns?.splice(order.addOns?.indexOf(addOn), 1);
}
}}
/>
<label htmlFor={addOn}>{addOn}</label>
</div>
))}
</fieldset>
<div className="flex flex-col gap-2">
<label htmlFor="deliveryDate">Delivery date</label>
<input
type="date"
name="deliveryDate"
id="deliveryDate"
className="dark:bg-transparent"
value={order.deliveryDate?.toISOString().split("T")[0] || ""}
onChange={(e) => (order.deliveryDate = new Date(e.target.value))}
required
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
name="withMilk"
id="withMilk"
checked={order.withMilk}
onChange={(e) => (order.withMilk = e.target.checked)}
/>
<label htmlFor="withMilk">With milk?</label>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="instructions">Special instructions</label>
<textarea
name="instructions"
id="instructions"
value={order.instructions}
className="dark:bg-transparent"
onChange={(e) => (order.instructions = e.target.value)}
></textarea>
</div>
{onSave && (
<button
type="submit"
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Submit
</button>
)}
</form>
);
}

View File

@@ -0,0 +1,28 @@
import { BubbleTeaOrder } from "./schema.ts";
export function OrderThumbnail({ order }: { order: BubbleTeaOrder }) {
const { id, baseTea, addOns, instructions, deliveryDate, withMilk } = order;
const date = deliveryDate.toLocaleDateString();
return (
<a
href={`/#/order/${id}`}
className="border p-3 flex justify-between items-start gap-3"
>
<div>
<strong>
{baseTea} {withMilk ? "milk " : ""} tea
</strong>
{addOns && addOns?.length > 0 && (
<p className="text-sm text-stone-600">
with {addOns?.join(", ").toLowerCase()}
</p>
)}
{instructions && (
<p className="text-sm text-stone-600 italic">{instructions}</p>
)}
</div>
<div className="text-sm text-stone-600">{date}</div>
</a>
);
}

View File

@@ -0,0 +1,37 @@
import { DraftIndicator } from "./DraftIndicator.tsx";
import { OrderThumbnail } from "./OrderThumbnail.tsx";
import { useAccount } from "./main.tsx";
export function Orders() {
const { me } = useAccount({
profile: { orders: [] },
});
return (
<>
<section className="space-y-5">
<a
href={`/#/order`}
className="block relative p-3 bg-white border text-center rounded-md dark:bg-stone-900"
>
<strong>Add new order</strong>
<DraftIndicator />
</a>
<div className="space-y-3">
<h1 className="text-lg pb-2 border-b mb-3">
<strong>Your orders 🧋</strong>
</h1>
{me?.profile?.orders?.length ? (
me?.profile?.orders.map((order) =>
order ? <OrderThumbnail key={order.id} order={order} /> : null,
)
) : (
<p>You have no orders yet.</p>
)}
</div>
</section>
</>
);
}

View File

@@ -0,0 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
strong {
@apply font-semibold text-stone-900 dark:text-white;
}
}

View File

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

View File

@@ -0,0 +1,86 @@
import { Account, CoList, CoMap, Profile, co } from "jazz-tools";
export const BubbleTeaAddOnTypes = [
"Pearl",
"Lychee jelly",
"Red bean",
"Brown sugar",
"Taro",
] as const;
export const BubbleTeaBaseTeaTypes = [
"Black",
"Oolong",
"Jasmine",
"Thai",
] as const;
export class ListOfBubbleTeaAddOns extends CoList.Of(
co.literal(...BubbleTeaAddOnTypes),
) {
get hasChanges() {
return Object.entries(this._raw.insertions).length > 0;
}
}
export class BubbleTeaOrder extends CoMap {
baseTea = co.literal(...BubbleTeaBaseTeaTypes);
addOns = co.ref(ListOfBubbleTeaAddOns);
deliveryDate = co.Date;
withMilk = co.boolean;
instructions = co.optional.string;
}
export class DraftBubbleTeaOrder extends CoMap {
baseTea = co.optional.literal(...BubbleTeaBaseTeaTypes);
addOns = co.optional.ref(ListOfBubbleTeaAddOns);
deliveryDate = co.optional.Date;
withMilk = co.optional.boolean;
instructions = co.optional.string;
get hasChanges() {
return Object.keys(this._edits).length > 1 || this.addOns?.hasChanges;
}
// validate if the draft is a valid order
validate() {
const errors: string[] = [];
if (!this.baseTea) {
errors.push("Please select your preferred base tea.");
}
if (!this.deliveryDate) {
errors.push("Plese select a delivery date.");
}
return { errors };
}
}
export class ListOfBubbleTeaOrders extends CoList.Of(co.ref(BubbleTeaOrder)) {}
/** 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 {
draft = co.ref(DraftBubbleTeaOrder);
orders = co.ref(ListOfBubbleTeaOrders);
}
export class JazzAccount extends Account {
profile = co.ref(JazzProfile)!;
migrate(this: JazzAccount, creationProps?: { name: string }) {
super.migrate(creationProps);
if (!this.profile._refs.orders) {
const owner = this.profile._owner;
this.profile.orders = ListOfBubbleTeaOrders.create([], { owner });
this.profile.draft = DraftBubbleTeaOrder.create(
{
addOns: ListOfBubbleTeaAddOns.create([], { owner }),
},
{ owner },
);
}
}
}

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

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

View File

@@ -0,0 +1,75 @@
import formsPlugin from "@tailwindcss/forms";
import type { Config } from "tailwindcss";
import plugin from "tailwindcss/plugin";
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,
},
container: {
center: true,
padding: {
DEFAULT: "0.75rem",
sm: "1rem",
},
screens: {
md: "500px",
lg: "500px",
xl: "500px",
},
},
},
},
plugins: [
formsPlugin,
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

@@ -0,0 +1,56 @@
import { expect, test } from "@playwright/test";
import { LoginPage } from "./pages/LoginPage";
test("create and edit an order", async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.fillUsername("Alice");
await loginPage.signup();
// start an order
await page.getByRole("link", { name: "Add new order" }).click();
await page.getByLabel("Base tea").selectOption("Oolong");
// test draft indicator
await page.getByRole("link", { name: /Back to all orders/ }).click();
await expect(page.getByText("You have a draft")).toBeVisible();
// fill out the rest of order form
await page.getByRole("link", { name: "Add new order" }).click();
await page.getByLabel("Pearl").check();
await page.getByLabel("Taro").check();
await page.getByLabel("Delivery date").fill("2024-12-21");
await page.getByLabel("With milk?").check();
await page.getByLabel("Special instructions").fill("25% sugar");
await page.getByRole("button", { name: "Submit" }).click();
await page.waitForURL("/");
// the draft indicator should be gone because the order was submitted
await expect(page.getByText("You have a draft")).toHaveCount(0);
// check if order was created correctly
const firstOrder = page.getByRole("link", { name: "Oolong milk tea" });
await expect(firstOrder).toHaveText(/25% sugar/);
await expect(firstOrder).toHaveText(/12\/21\/2024/);
await expect(firstOrder).toHaveText(/with pearl, taro/);
// edit order
await firstOrder.click();
await page.getByLabel("Base tea").selectOption("Jasmine");
await page.getByLabel("Red bean").check();
await page.getByLabel("Brown sugar").check();
await page.getByLabel("Delivery date").fill("2024-12-25");
await page.getByLabel("With milk?").uncheck();
await page.getByLabel("Special instructions").fill("10% sugar");
await page.getByRole("link", { name: /Back to all orders/ }).click();
// check if order was edited correctly
const editedOrder = page.getByRole("link", { name: "Jasmine tea" });
await expect(editedOrder).toHaveText(/10% sugar/);
await expect(editedOrder).toHaveText(/12\/25\/2024/);
await expect(editedOrder).toHaveText(
/with pearl, taro, red bean, brown sugar/,
);
});

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() {
this.page.goto("/");
}
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 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
});

View File

@@ -1,5 +1,39 @@
# image-upload
## 0.0.14
### Patch Changes
- Updated dependencies [249eecb]
- jazz-tools@0.8.39
- jazz-browser-media-images@0.8.39
- jazz-react@0.8.39
## 0.0.13
### Patch Changes
- jazz-react@0.8.38
- jazz-tools@0.8.38
- jazz-browser-media-images@0.8.38
## 0.0.12
### Patch Changes
- jazz-react@0.8.37
- jazz-tools@0.8.37
- jazz-browser-media-images@0.8.37
## 0.0.11
### Patch Changes
- Updated dependencies [441fe27]
- jazz-tools@0.8.36
- jazz-react@0.8.36
- jazz-browser-media-images@0.8.36
## 0.0.10
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "image-upload",
"private": true,
"version": "0.0.10",
"version": "0.0.14",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,40 @@
# jazz-example-inspector
## 0.0.86
### Patch Changes
- e386f2b: Automatically set up the Cmd+J listener if 'allowJazzInspector' is present in the URL. Cmd+J opens inspector.jazz.tools with the current user's account.
- Updated dependencies [249eecb]
- Updated dependencies [3121551]
- cojson@0.8.39
- cojson-transport-ws@0.8.39
## 0.0.85
### Patch Changes
- Updated dependencies [b00ee91]
- Updated dependencies [f488c09]
- cojson@0.8.38
- cojson-transport-ws@0.8.38
## 0.0.84
### Patch Changes
- Updated dependencies [3d9f12e]
- cojson@0.8.37
- cojson-transport-ws@0.8.37
## 0.0.83
### Patch Changes
- Updated dependencies [441fe27]
- cojson@0.8.36
- cojson-transport-ws@0.8.36
## 0.0.82
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector",
"private": true,
"version": "0.0.82",
"version": "0.0.86",
"type": "module",
"scripts": {
"dev": "vite",
@@ -16,13 +16,13 @@
"@radix-ui/react-toast": "^1.1.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cojson": "workspace:0.8.35",
"cojson-transport-ws": "workspace:0.8.35",
"cojson": "workspace:0.8.39",
"cojson-transport-ws": "workspace:0.8.39",
"hash-slash": "workspace:0.2.1",
"lucide-react": "^0.274.0",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^6.16.0",
"react-router-dom": "^6.16.0",
"react-use": "^17.4.0",
@@ -32,13 +32,13 @@
},
"devDependencies": {
"@types/qrcode": "^1.5.1",
"@types/react": "^18.2.19",
"@types/react-dom": "^18.2.7",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.14",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.27",
"tailwindcss": "3.3.2",
"typescript": "^5.3.3",
"vite": "^5.0.10"
"tailwindcss": "^3.4.9",
"typescript": "~5.6.2",
"vite": "^5.4.10"
}
}

View File

@@ -53,12 +53,14 @@ export default function CoJsonViewerApp() {
}, [currentAccount]);
useEffect(() => {
if (!currentAccount) {
if (!currentAccount && path.length > 0) {
setLocalNode(null);
goToIndex(-1);
return;
}
if (!currentAccount) return;
WasmCrypto.create().then(async (crypto) => {
const wsPeer = createWebSocketPeer({
id: "cloud",
@@ -77,7 +79,7 @@ export default function CoJsonViewerApp() {
});
setLocalNode(node);
});
}, [currentAccount, goToIndex]);
}, [currentAccount, goToIndex, path]);
const addAccount = (id: RawAccountID, secret: AgentSecret) => {
const newAccount = { id, secret };
@@ -102,6 +104,18 @@ export default function CoJsonViewerApp() {
}
};
if (
path?.[0]?.coId.toString() === "import" &&
path?.[1]?.coId !== undefined &&
path?.[2]?.coId !== undefined
) {
addAccount(
path?.[1]?.coId as RawAccountID,
atob(path?.[2]?.coId as string) as AgentSecret,
);
goToIndex(-1);
}
return (
<div className="w-full h-screen bg-gray-100 p-4 overflow-hidden flex flex-col">
<div className="flex justify-between items-center mb-4">

View File

@@ -1,5 +1,7 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
import type { Config } from "tailwindcss";
import animate from "tailwindcss-animate";
const config: Config = {
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
@@ -57,12 +59,12 @@ module.exports = {
},
keyframes: {
"accordion-down": {
from: { height: 0 },
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
to: { height: "0" },
},
},
animation: {
@@ -71,5 +73,7 @@ module.exports = {
},
},
},
plugins: [require("tailwindcss-animate")],
plugins: [animate],
};
export default config;

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