Compare commits

...

56 Commits

Author SHA1 Message Date
Guido D'Orsi
9356ffbd4e Merge pull request #1975 from garden-co/changeset-release/main
Version Packages
2025-04-23 10:17:41 +02:00
github-actions[bot]
328227316c Version Packages 2025-04-23 08:06:44 +00:00
Guido D'Orsi
dd5fe12dfe Merge pull request #1972 from garden-co/fix/fatal-error-coValue-creation
fix: do not crash when loading a CoValue core without the group in the sync server
2025-04-23 10:04:38 +02:00
Guido D'Orsi
f837cfe994 fix: do not crash when loading a CoValue core without the group in the sync server 2025-04-23 01:48:38 +02:00
Guido D'Orsi
978c30a7ca Merge pull request #1971 from garden-co/biome-ignore-package-json
fix(biome): ignore package.json files
2025-04-22 22:37:24 +02:00
Guido D'Orsi
e2c02824e7 fix(biome): ignore package.json files 2025-04-22 22:36:09 +02:00
Guido D'Orsi
6dac7c7ce5 Merge pull request #1968 from garden-co/changeset-release/main
Version Packages
2025-04-22 20:09:29 +02:00
github-actions[bot]
d0724a2c13 Version Packages 2025-04-22 18:04:57 +00:00
Guido D'Orsi
4c632e14a4 Merge pull request #1967 from garden-co/fix/colist-null-values
fix(coList): handle null values when using $each: true
2025-04-22 20:02:58 +02:00
Guido D'Orsi
d7238267d9 fix(passkey-svelte): add jazz-tools as dependency 2025-04-22 20:00:59 +02:00
Guido D'Orsi
a6cf01f867 fix(coList): handle null values when using : true 2025-04-22 19:52:34 +02:00
Guido D'Orsi
17d148b1d2 Merge pull request #1803 from garden-co/jazz-paper-scissors
feat: Jazz paper scissors example
2025-04-22 18:44:52 +02:00
Guido D'Orsi
2544782988 feat(dx): simplify dev setup using npm-run-all 2025-04-22 18:44:12 +02:00
Margaret Culotta
8f42c7a749 merge main, handle conflict in pnpm-lock 2025-04-22 18:40:31 +02:00
Guido D'Orsi
62aa4146af Merge pull request #1966 from garden-co/changeset-release/main
Version Packages
2025-04-22 18:27:14 +02:00
github-actions[bot]
d423e3c4b3 Version Packages 2025-04-22 16:24:54 +00:00
Guido D'Orsi
bdac4d1188 Merge pull request #1965 from garden-co/clerk-rn-signup
fix(clerk-expo): correctly catch updates when doing a signup
2025-04-22 18:23:10 +02:00
Guido D'Orsi
47c9004d5f chore: format 2025-04-22 18:19:42 +02:00
Guido D'Orsi
de19a6db37 fix(clerk-expo): correctly catch updates when doing a signup 2025-04-22 18:18:42 +02:00
Guido D'Orsi
cdc885994c test: cover server restarts 2025-04-22 17:39:01 +02:00
Sammii
6e1b27b0c9 Merge pull request #1934 from garden-co/updating-jazz-blue-color-sitewide
Updating jazz blue color site-wide
2025-04-22 14:27:58 +01:00
Sammii
6ac8bebf90 more svg tweaks for dark mode to bring the two more inline 2025-04-22 10:30:55 +01:00
Sammii
29d0b6fa03 svg tweaks 2025-04-22 10:18:42 +01:00
Margaret Culotta
49784a7e65 remove additional icons from manifest.json 2025-04-18 08:44:57 -05:00
Guido D'Orsi
935cd745ba chore: align deps 2025-04-18 13:46:47 +02:00
Guido D'Orsi
608d06d46f chore: throw an error when the .env is missing 2025-04-18 13:43:26 +02:00
Guido D'Orsi
7690e19014 docs: update readme 2025-04-18 13:41:22 +02:00
Guido D'Orsi
4626c79c46 chore: clean lockfile 2025-04-18 13:34:48 +02:00
Guido D'Orsi
aad0bd60f4 fix: remove postinstall 2025-04-18 13:29:34 +02:00
Guido D'Orsi
1fbe8d9651 Merge remote-tracking branch 'origin/main' into jazz-paper-scissors 2025-04-18 13:25:45 +02:00
Sammii
6a9baa304f updating images.tests 2025-04-17 10:37:53 +01:00
Sammii
99b9b10d2d more updates 2025-04-17 10:28:19 +01:00
Sammii
f42b73b09b updating all pngs 2025-04-17 09:57:55 +01:00
Sammii
89ff69f005 more favicon updates for example apps 2025-04-16 16:25:08 +01:00
Sammii
698d3b0a81 resolving class casing on diagram components 2025-04-16 16:14:55 +01:00
Sammii
cb5e55d18f amending favicon images 2025-04-16 16:14:41 +01:00
Sammii
cdab27084c Merge branch 'main' into updating-jazz-blue-color-sitewide 2025-04-16 15:48:06 +01:00
Sammii
17b499e68a amending favicons & title on form & password manager example
+ title on chat vue example app
2025-04-16 15:38:34 +01:00
Sammii
f3f2344948 updating logos and default colours to use new blue 2025-04-16 15:12:12 +01:00
Sammii
ce4064f6a7 Merge branch 'main' into updating-jazz-blue-color-sitewide 2025-04-16 15:08:24 +01:00
Sammii
d66bd6ee1f adding favicons to all examples 2025-04-16 15:01:28 +01:00
Sammii
02c27ababc updating diagrams 2025-04-16 14:45:14 +01:00
Sammii
0ab6a5c739 update all favicons 2025-04-16 10:49:01 +01:00
Margaret Culotta
6e28ac946b update readme 2025-04-15 21:19:22 -05:00
Margaret Culotta
cfce22fc63 Merge branch 'main' into jazz-paper-scissors 2025-04-15 15:40:14 -05:00
Guido D'Orsi
ecfc883419 feat: fix build and add new game button 2025-04-11 13:57:39 +02:00
Guido D'Orsi
4aded04223 Merge remote-tracking branch 'origin/main' into jazz-paper-scissors 2025-04-11 12:18:27 +02:00
Guido D'Orsi
3167ff16cd Merge remote-tracking branch 'origin/main' into jazz-paper-scissors 2025-04-07 11:22:52 +02:00
Margaret Culotta
2e70b2b295 remove unnecessary file, adjust prettierignore 2025-04-04 13:41:20 -05:00
Margaret Culotta
9a5e6eed16 clean up console.logs and unused types 2025-04-04 11:48:11 -05:00
Guido D'Orsi
28d74d73f8 fix: add resolve 2025-04-04 16:40:38 +02:00
Margaret Culotta
6c9d90449a clean up styles, simplify 2025-04-03 22:05:43 -05:00
Margaret Culotta
5398978d74 game play is working. Timing issue with waiting room adding game? 2025-04-03 16:15:35 -05:00
Margaret Culotta
53148db482 clean up 2025-04-03 11:28:12 -05:00
Margaret Culotta
44a0b54026 add game page for first and second player 2025-04-03 08:30:45 -05:00
Giordano Ricci
c877d377d2 first commit 2025-03-31 15:40:04 +01:00
183 changed files with 5706 additions and 1246 deletions

View File

@@ -13,7 +13,9 @@
"**/android/**",
"packages/jazz-svelte/**",
"examples/*svelte*/**",
"homepage/homepage/**"
"examples/jazz-paper-scissors/src/routeTree.gen.ts",
"homepage/homepage/**",
"**/package.json"
]
},
"formatter": {

View File

@@ -1,5 +1,28 @@
# chat-rn-expo-clerk
## 1.0.102
### Patch Changes
- jazz-expo@0.13.10
- jazz-tools@0.13.10
- jazz-react-native-media-images@0.13.10
## 1.0.101
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-expo@0.13.9
- jazz-react-native-media-images@0.13.9
## 1.0.100
### Patch Changes
- jazz-expo@0.13.8
## 1.0.99
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "chat-rn-expo-clerk",
"main": "index.js",
"version": "1.0.99",
"version": "1.0.102",
"scripts": {
"build": "expo export -p ios",
"start": "expo start",

View File

@@ -1,5 +1,26 @@
# chat-rn-expo
## 1.0.89
### Patch Changes
- jazz-expo@0.13.10
- jazz-tools@0.13.10
## 1.0.88
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-expo@0.13.9
## 1.0.87
### Patch Changes
- jazz-expo@0.13.8
## 1.0.86
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn-expo",
"version": "1.0.86",
"version": "1.0.89",
"main": "index.js",
"scripts": {
"build": "expo export -p ios",

View File

@@ -1,5 +1,23 @@
# chat-rn
## 1.0.97
### Patch Changes
- Updated dependencies [f837cfe]
- cojson@0.13.10
- cojson-transport-ws@0.13.10
- jazz-react-native@0.13.10
- jazz-tools@0.13.10
## 1.0.96
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react-native@0.13.9
## 1.0.95
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "chat-rn",
"version": "1.0.95",
"version": "1.0.97",
"main": "index.js",
"scripts": {
"android": "react-native run-android",

View File

@@ -1,5 +1,22 @@
# chat-vue
## 0.0.81
### Patch Changes
- jazz-browser@0.13.10
- jazz-tools@0.13.10
- jazz-vue@0.13.10
## 0.0.80
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-browser@0.13.9
- jazz-vue@0.13.9
## 0.0.79
### Patch Changes

View File

@@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<link rel="icon" href="./public/favicon.ico" type="image/png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<title>Jazz Chat Vue Example</title>
</head>
<body>
<div id="app"></div>

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,5 +1,22 @@
# jazz-example-chat
## 0.0.179
### Patch Changes
- jazz-inspector@0.13.10
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.178
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-inspector@0.13.9
- jazz-react@0.13.9
## 0.0.177
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-chat",
"private": true,
"version": "0.0.177",
"version": "0.0.179",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -36,7 +36,7 @@ const config: Config = {
blue: {
...colors.indigo,
500: "#5870F1",
DEFAULT: "#3313F7",
DEFAULT: "#146AFF",
},
},
container: {

View File

@@ -1,5 +1,28 @@
# minimal-auth-clerk
## 0.0.78
### Patch Changes
- jazz-react@0.13.10
- jazz-react-auth-clerk@0.13.10
- jazz-tools@0.13.10
## 0.0.77
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
- jazz-react-auth-clerk@0.13.9
## 0.0.76
### Patch Changes
- jazz-react-auth-clerk@0.13.8
## 0.0.75
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "clerk",
"private": true,
"version": "0.0.75",
"version": "0.0.78",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,20 @@
# file-share-svelte
## 0.0.61
### Patch Changes
- jazz-svelte@0.13.10
- jazz-tools@0.13.10
## 0.0.60
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-svelte@0.13.9
## 0.0.59
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "file-share-svelte",
"version": "0.0.59",
"version": "0.0.61",
"private": true,
"type": "module",
"scripts": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -1,5 +1,22 @@
# jazz-tailwind-demo-auth-starter
## 0.0.18
### Patch Changes
- jazz-inspector@0.13.10
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.17
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-inspector@0.13.9
- jazz-react@0.13.9
## 0.0.16
### Patch Changes

View File

@@ -2,7 +2,7 @@
<html lang="en" class="h-full">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/favicon.ico" />
<link rel="icon" type="image/png" href="./public/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | React + Demo Auth + Tailwind</title>
</head>

View File

@@ -1,7 +1,7 @@
{
"name": "filestream",
"private": true,
"version": "0.0.16",
"version": "0.0.18",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -14,7 +14,7 @@ export function Logo() {
fillRule="evenodd"
clipRule="evenodd"
d="M136.179 44.8277C136.179 44.8277 136.179 44.8277 136.179 44.8276V21.168C117.931 28.5527 97.9854 32.6192 77.0897 32.6192C65.1466 32.6192 53.5138 31.2908 42.331 28.7737V51.4076C42.331 51.4076 42.331 51.4076 42.331 51.4076V81.1508C41.2955 80.4385 40.1568 79.8458 38.9405 79.3915C36.1732 78.358 33.128 78.0876 30.1902 78.6145C27.2524 79.1414 24.5539 80.4419 22.4358 82.3516C20.3178 84.2613 18.8754 86.6944 18.291 89.3433C17.7066 91.9921 18.0066 94.7377 19.1528 97.2329C20.2991 99.728 22.2403 101.861 24.7308 103.361C27.2214 104.862 30.1495 105.662 33.1448 105.662H33.1455C33.6061 105.662 33.8365 105.662 34.0314 105.659C44.5583 105.449 53.042 96.9656 53.2513 86.4386C53.2534 86.3306 53.2544 86.2116 53.2548 86.0486H53.2552V85.7149L53.2552 85.5521V82.0762L53.2552 53.1993C61.0533 54.2324 69.0092 54.7656 77.0897 54.7656C77.6696 54.7656 78.2489 54.7629 78.8276 54.7574V110.696C77.792 109.983 76.6533 109.391 75.437 108.936C72.6697 107.903 69.6246 107.632 66.6867 108.159C63.7489 108.686 61.0504 109.987 58.9323 111.896C56.8143 113.806 55.3719 116.239 54.7875 118.888C54.2032 121.537 54.5031 124.283 55.6494 126.778C56.7956 129.273 58.7368 131.405 61.2273 132.906C63.7179 134.406 66.646 135.207 69.6414 135.207C70.1024 135.207 70.3329 135.207 70.5279 135.203C81.0548 134.994 89.5385 126.51 89.7478 115.983C89.7517 115.788 89.7517 115.558 89.7517 115.097V111.621L89.7517 54.3266C101.962 53.4768 113.837 51.4075 125.255 48.2397V80.9017C124.219 80.1894 123.081 79.5966 121.864 79.1424C119.097 78.1089 116.052 77.8384 113.114 78.3653C110.176 78.8922 107.478 80.1927 105.36 82.1025C103.242 84.0122 101.799 86.4453 101.215 89.0941C100.631 91.743 100.931 94.4886 102.077 96.9837C103.223 99.4789 105.164 101.612 107.655 103.112C110.145 104.612 113.073 105.413 116.069 105.413C116.53 105.413 116.76 105.413 116.955 105.409C127.482 105.2 135.966 96.7164 136.175 86.1895C136.179 85.9945 136.179 85.764 136.179 85.3029V81.8271L136.179 44.8277Z"
fill="#3313F7"
fill="#146AFF"
/>
</svg>
);

View File

@@ -1,5 +1,20 @@
# form
## 0.1.19
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.1.18
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.1.17
### Patch Changes

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Form example</title>
<link rel="icon" type="image/png" href="./public/favicon.ico">
</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>

View File

@@ -1,7 +1,7 @@
{
"name": "form",
"private": true,
"version": "0.1.17",
"version": "0.1.19",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,5 +1,20 @@
# image-upload
## 0.0.75
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.74
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.0.73
### Patch Changes

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Image upload example</title>
<link rel="icon" href="./public/favicon.ico" type="image/png">
</head>
<body>
<div id="root"></div>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,5 +1,20 @@
# jazz-example-inspector
## 0.0.129
### Patch Changes
- Updated dependencies [f837cfe]
- cojson@0.13.10
- cojson-transport-ws@0.13.10
- jazz-inspector@0.13.10
## 0.0.128
### Patch Changes
- jazz-inspector@0.13.9
## 0.0.127
### Patch Changes

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-inspector-app",
"private": true,
"version": "0.0.127",
"version": "0.0.129",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -57,7 +57,7 @@ const config: Config = {
800: "#2A12BE",
900: "#12046A",
950: "#1e1b4b",
DEFAULT: "#3313F7",
DEFAULT: "#146AFF",
},
},
},

View File

@@ -0,0 +1,2 @@
VITE_JAZZ_WORKER_ACCOUNT=
JAZZ_WORKER_SECRET=

View File

@@ -0,0 +1,3 @@
dist
.env

View File

@@ -0,0 +1,8 @@
# Ignore artifacts:
build
coverage
**/.git
**/.svn
**/.hg
**/node_modules

View File

@@ -0,0 +1,24 @@
# Jazz Paper Scissors
## Setup
First of we need to create a new account for the dealer:
```bash
pnpm generate-env
```
This will generate a .env file like this one
```
VITE_JAZZ_WORKER_ACCOUNT=co_zn95yzQd1z24DJCgayN53ShyuMR
JAZZ_WORKER_SECRET=sealerSecret_z3Tcq41gtELJRHk3SzQutR2DhkpvEScQQP8DG8yeSh7zJ/signerSecret_zDsLhoNRSxjXrX6oSGzGH3XQQHDyp8QS292p28RToANYq
```
This should be enough the setup everything
Then run pnpm dev to start both the local build and the worker
```bash
pnpm dev
```

View File

@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@@ -0,0 +1,21 @@
import * as fs from "fs";
import { createWorkerAccount } from "jazz-run/createWorkerAccount";
if (fs.existsSync(".env")) {
process.exit(0);
}
const account = await createWorkerAccount({
name: "jazz-paper-scissors-worker",
peer: "wss://cloud.jazz.tools/?key=jazz-paper-scissors@garden.co",
});
fs.writeFileSync(
".env",
`
VITE_JAZZ_WORKER_ACCOUNT=${account.accountID}
JAZZ_WORKER_SECRET=${account.agentSecret}
`,
);
process.exit(0);

View File

@@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-tsrouter-app"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>Jazz example - Jazz Paper Scissors</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,44 @@
{
"name": "jazz-paper-scissors",
"private": true,
"type": "module",
"scripts": {
"dev": "npm-run-all --parallel dev:web dev:worker",
"dev:web": "vite",
"dev:worker": "tsx --watch --env-file=.env ./src/worker.ts",
"build": "vite build && tsc",
"serve": "vite preview",
"generate-env": "tsx generate-env.ts"
},
"dependencies": {
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@tailwindcss/vite": "^4.0.17",
"@tanstack/react-router": "^1.115.0",
"@tanstack/react-router-devtools": "^1.114.29",
"@tanstack/router-plugin": "^1.114.30",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"jazz-inspector": "workspace:*",
"jazz-nodejs": "workspace:*",
"jazz-react": "workspace:*",
"jazz-tools": "workspace:*",
"lucide-react": "^0.485.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.17",
"tw-animate-css": "^1.2.5"
},
"devDependencies": {
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"jazz-run": "workspace:*",
"npm-run-all": "^4.1.5",
"tsx": "^4.19.3",
"typescript": "~5.6.2",
"vite": "6.0.11"
}
}

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,15 @@
{
"short_name": "TanStack App",
"name": "Create TanStack App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -0,0 +1,33 @@
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { useAccount } from "jazz-react";
import { routeTree } from "./routeTree.gen";
// Create a new router instance
const router = createRouter({
routeTree,
context: {
// @ts-expect-error - just a placeholder - me is set in the App component down below
me: undefined,
},
defaultPreload: "intent",
scrollRestoration: true,
defaultStructuralSharing: true,
defaultPreloadStaleTime: 0,
});
// Register the router instance for type safety
declare module "@tanstack/react-router" {
interface Register {
router: typeof router;
}
}
export function App() {
const { me } = useAccount();
if (!me) {
return <div>Loading...</div>;
}
return <RouterProvider router={router} context={{ me }} />;
}

View File

@@ -0,0 +1,79 @@
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
import { Loader2Icon } from "lucide-react";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
interface Props
extends React.ComponentProps<"button">,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
loadingText?: string;
}
function Button({
className,
variant,
size,
asChild = false,
loading = false,
disabled,
loadingText = "Loading...",
children,
...props
}: Props) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
disabled={loading || disabled}
{...props}
>
{loading ? (
<>
<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
{loadingText}
</>
) : (
children
)}
</Comp>
);
}
export { Button, buttonVariants };

View File

@@ -0,0 +1,109 @@
import type * as React from "react";
import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className,
)}
{...props}
/>
);
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className,
)}
{...props}
/>
);
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
);
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
);
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className,
)}
{...props}
/>
);
}
function CardSmall({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-small"
className={cn(
"sm-card grid grid-flow-col justify-items-center gap-6 rounded-xl border py-6 shadow-sm m-4",
className,
)}
{...props}
/>
);
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
);
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center px-6 [.border-t]:pt-6 border-t",
className,
)}
{...props}
/>
);
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
CardSmall,
};

View File

@@ -0,0 +1,21 @@
import * as React from "react";
import { cn } from "@/lib/utils";
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
return (
<input
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className,
)}
{...props}
/>
);
}
export { Input };

View File

@@ -0,0 +1,13 @@
import { JazzProvider } from "jazz-react";
export function JazzAndAuth({ children }: { children: React.ReactNode }) {
return (
<JazzProvider
sync={{
peer: "wss://cloud.jazz.tools/?key=jazz-paper-scissors@garden.co",
}}
>
{children}
</JazzProvider>
);
}

View File

@@ -0,0 +1,22 @@
import * as LabelPrimitive from "@radix-ui/react-label";
import * as React from "react";
import { cn } from "@/lib/utils";
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className,
)}
{...props}
/>
);
}
export { Label };

View File

@@ -0,0 +1,28 @@
"use client";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import * as React from "react";
import { cn } from "@/lib/utils";
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator-root"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className,
)}
{...props}
/>
);
}
export { Separator };

View File

@@ -0,0 +1,5 @@
if (!import.meta.env.VITE_JAZZ_WORKER_ACCOUNT) {
throw new Error(".env missing, run `pnpm generate-env`");
}
export const WORKER_ID = import.meta.env.VITE_JAZZ_WORKER_ACCOUNT;

View File

@@ -0,0 +1,120 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -0,0 +1,23 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/**
* Given a player selections, returns the winner of the current game.
*/
export function determineWinner(player1Choice: string, player2Choice: string) {
if (player1Choice === player2Choice) {
return "draw";
} else if (
(player1Choice === "rock" && player2Choice === "scissors") ||
(player1Choice === "paper" && player2Choice === "rock") ||
(player1Choice === "scissors" && player2Choice === "paper")
) {
return "player1";
} else {
return "player2";
}
}

View File

@@ -0,0 +1,24 @@
import { JazzInspector } from "jazz-inspector";
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { JazzProvider } from "jazz-react";
import { App } from "./app";
const rootElement = document.getElementById("app");
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<JazzProvider
sync={{
peer: "wss://cloud.jazz.tools/?key=jazz-paper-scissors@garden.co",
}}
>
<JazzInspector />
<App />
</JazzProvider>
</StrictMode>,
);
}

View File

@@ -0,0 +1,177 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
// Import Routes
import { Route as rootRoute } from "./routes/__root";
import { Route as AuthenticatedImport } from "./routes/_authenticated";
import { Route as IndexImport } from "./routes/index";
import { Route as AuthenticatedWaitingRoomWaitingRoomIdImport } from "./routes/_authenticated/waiting-room.$waitingRoomId";
import { Route as AuthenticatedGameGameIdImport } from "./routes/_authenticated/game.$gameId";
// Create/Update Routes
const AuthenticatedRoute = AuthenticatedImport.update({
id: "/_authenticated",
getParentRoute: () => rootRoute,
} as any);
const IndexRoute = IndexImport.update({
id: "/",
path: "/",
getParentRoute: () => rootRoute,
} as any);
const AuthenticatedWaitingRoomWaitingRoomIdRoute =
AuthenticatedWaitingRoomWaitingRoomIdImport.update({
id: "/waiting-room/$waitingRoomId",
path: "/waiting-room/$waitingRoomId",
getParentRoute: () => AuthenticatedRoute,
} as any);
const AuthenticatedGameGameIdRoute = AuthenticatedGameGameIdImport.update({
id: "/game/$gameId",
path: "/game/$gameId",
getParentRoute: () => AuthenticatedRoute,
} as any);
// Populate the FileRoutesByPath interface
declare module "@tanstack/react-router" {
interface FileRoutesByPath {
"/": {
id: "/";
path: "/";
fullPath: "/";
preLoaderRoute: typeof IndexImport;
parentRoute: typeof rootRoute;
};
"/_authenticated": {
id: "/_authenticated";
path: "";
fullPath: "";
preLoaderRoute: typeof AuthenticatedImport;
parentRoute: typeof rootRoute;
};
"/_authenticated/game/$gameId": {
id: "/_authenticated/game/$gameId";
path: "/game/$gameId";
fullPath: "/game/$gameId";
preLoaderRoute: typeof AuthenticatedGameGameIdImport;
parentRoute: typeof AuthenticatedImport;
};
"/_authenticated/waiting-room/$waitingRoomId": {
id: "/_authenticated/waiting-room/$waitingRoomId";
path: "/waiting-room/$waitingRoomId";
fullPath: "/waiting-room/$waitingRoomId";
preLoaderRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdImport;
parentRoute: typeof AuthenticatedImport;
};
}
}
// Create and export the route tree
interface AuthenticatedRouteChildren {
AuthenticatedGameGameIdRoute: typeof AuthenticatedGameGameIdRoute;
AuthenticatedWaitingRoomWaitingRoomIdRoute: typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
}
const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedGameGameIdRoute: AuthenticatedGameGameIdRoute,
AuthenticatedWaitingRoomWaitingRoomIdRoute:
AuthenticatedWaitingRoomWaitingRoomIdRoute,
};
const AuthenticatedRouteWithChildren = AuthenticatedRoute._addFileChildren(
AuthenticatedRouteChildren,
);
export interface FileRoutesByFullPath {
"/": typeof IndexRoute;
"": typeof AuthenticatedRouteWithChildren;
"/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
}
export interface FileRoutesByTo {
"/": typeof IndexRoute;
"": typeof AuthenticatedRouteWithChildren;
"/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
}
export interface FileRoutesById {
__root__: typeof rootRoute;
"/": typeof IndexRoute;
"/_authenticated": typeof AuthenticatedRouteWithChildren;
"/_authenticated/game/$gameId": typeof AuthenticatedGameGameIdRoute;
"/_authenticated/waiting-room/$waitingRoomId": typeof AuthenticatedWaitingRoomWaitingRoomIdRoute;
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath;
fullPaths: "/" | "" | "/game/$gameId" | "/waiting-room/$waitingRoomId";
fileRoutesByTo: FileRoutesByTo;
to: "/" | "" | "/game/$gameId" | "/waiting-room/$waitingRoomId";
id:
| "__root__"
| "/"
| "/_authenticated"
| "/_authenticated/game/$gameId"
| "/_authenticated/waiting-room/$waitingRoomId";
fileRoutesById: FileRoutesById;
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute;
AuthenticatedRoute: typeof AuthenticatedRouteWithChildren;
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AuthenticatedRoute: AuthenticatedRouteWithChildren,
};
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>();
/* ROUTE_MANIFEST_START
{
"routes": {
"__root__": {
"filePath": "__root.tsx",
"children": [
"/",
"/_authenticated"
]
},
"/": {
"filePath": "index.tsx"
},
"/_authenticated": {
"filePath": "_authenticated.tsx",
"children": [
"/_authenticated/game/$gameId",
"/_authenticated/waiting-room/$waitingRoomId"
]
},
"/_authenticated/game/$gameId": {
"filePath": "_authenticated/game.$gameId.tsx",
"parent": "/_authenticated"
},
"/_authenticated/waiting-room/$waitingRoomId": {
"filePath": "_authenticated/waiting-room.$waitingRoomId.tsx",
"parent": "/_authenticated"
}
}
}
ROUTE_MANIFEST_END */

View File

@@ -0,0 +1,19 @@
import { Outlet, createRootRouteWithContext } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import type { Account } from "jazz-tools";
import { JazzAndAuth } from "../components/ui/jazz-and-auth";
interface RouterContext {
me: Account;
}
export const Route = createRootRouteWithContext<RouterContext>()({
component: () => (
<>
<JazzAndAuth>
<Outlet />
<TanStackRouterDevtools />
</JazzAndAuth>
</>
),
});

View File

@@ -0,0 +1,12 @@
import { Outlet, createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/_authenticated")({
component: RouteComponent,
beforeLoad: ({ context }) => {
return context;
},
});
function RouteComponent() {
return <Outlet />;
}

View File

@@ -0,0 +1,167 @@
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardSmall,
CardTitle,
} from "@/components/ui/card";
import { WORKER_ID } from "@/constants";
import { Game, NewGameIntent, PlayIntent } from "@/schema";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { experimental_useInboxSender, useCoState } from "jazz-react";
import { type ID } from "jazz-tools";
import { Badge, CircleHelp, Scissors, ScrollText } from "lucide-react";
import { useEffect, useState } from "react";
const playIcon = (selection: string | undefined) => {
switch (selection) {
case "rock":
return <Badge className="w-5 h-5" />;
case "paper":
return <ScrollText className="w-5 h-5" />;
case "scissors":
return <Scissors className="w-5 h-5" />;
default:
return <CircleHelp className="w-5 h-5" />;
}
};
export const Route = createFileRoute("/_authenticated/game/$gameId")({
component: RouteComponent,
loader: async ({ context: { me }, params: { gameId } }) => {
const game = await Game.load(gameId as ID<Game>, {
resolve: {
player1: true,
player2: true,
},
});
if (!game) {
throw redirect({ to: "/" });
}
return { gameId, me, loaderGame: game };
},
});
function RouteComponent() {
const { gameId, me, loaderGame } = Route.useLoaderData();
const isPlayer1 = loaderGame.player1?.account?.isMe;
const player = isPlayer1 ? "player1" : "player2";
const [playSelection, setPlaySelection] = useState(
loaderGame[player]?.playSelection ?? "",
);
const sendInboxMessage = experimental_useInboxSender(WORKER_ID);
const game = useCoState(Game, gameId as ID<Game>);
useEffect(() => {
let gameCompleted = Boolean(loaderGame.outcome);
return loaderGame.subscribe((game) => {
if (gameCompleted && !game.outcome) {
setPlaySelection(""); // Reset play selection when one player clicks on "Start a new game"
}
gameCompleted = Boolean(game.outcome);
});
}, []);
if (!game) {
return null;
}
const gameComplete = game.outcome !== undefined;
const opponent = isPlayer1 ? "player2" : "player1";
const currentPlayer = game[player];
const opponentPlayer = game[opponent];
const opponentSelection = opponentPlayer?.playSelection;
const onSubmit = async (playSelection: string) => {
sendInboxMessage(
PlayIntent.create({ type: "play", gameId, player, playSelection }),
);
};
const onNewGame = async () => {
sendInboxMessage(NewGameIntent.create({ type: "newGame", gameId }));
};
return (
<Card className="mx-auto max-w-5xl">
<div className="mx-auto text-center">
<CardHeader>
<CardTitle>Jazz, Paper, Scissors!</CardTitle>
<span>Welcome {isPlayer1 ? "Player 1" : "Player 2"}</span>
<span>
{game?.player1Score ?? 0} - {game?.player2Score ?? 0}
</span>
</CardHeader>
{gameComplete ? (
<>
<div className="border">
Game Over,{" "}
{game?.outcome === player
? "You Win!"
: game?.outcome === "draw"
? "It's a Draw!"
: "You Lose!"}
</div>
<Button onClick={onNewGame}>Start a new game</Button>
</>
) : null}
<CardContent>
<div>
{playSelection === "" ? "Make Your Selection" : "Your Selection: "}
</div>
<CardSmall>{playIcon(playSelection)}</CardSmall>
{gameComplete ? null : (
<>
<dl className="grid grid-cols-3 gap-x-8 gap-y-16 text-center">
<Button
variant={"outline"}
size={"icon"}
onClick={() => setPlaySelection("rock")}
>
<Badge className="w-5 h-5" />
</Button>
<Button
variant={"outline"}
size={"icon"}
onClick={() => setPlaySelection("paper")}
>
<ScrollText className="w-5 h-5" />
</Button>
<Button
variant={"outline"}
size={"icon"}
onClick={() => setPlaySelection("scissors")}
>
<Scissors className="w-5 h-5" />
</Button>
</dl>
<div className="m-4">
<Button
disabled={
playSelection === "" ||
Boolean(currentPlayer?.playSelection)
}
onClick={() => onSubmit(playSelection)}
>
Go!
</Button>
</div>
</>
)}
<div>Your Opponent Selected:</div>
<CardSmall>{playIcon(opponentSelection)}</CardSmall>
</CardContent>
</div>
</Card>
);
}

View File

@@ -0,0 +1,120 @@
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { WORKER_ID } from "@/constants";
import { JoinGameRequest, WaitingRoom } from "@/schema";
import { createFileRoute, redirect } from "@tanstack/react-router";
import { Group, type ID, InboxSender } from "jazz-tools";
import { ClipboardCopyIcon, Loader2Icon } from "lucide-react";
import { useEffect, useState } from "react";
export const Route = createFileRoute(
"/_authenticated/waiting-room/$waitingRoomId",
)({
component: RouteComponent,
loader: async ({ context: { me }, params: { waitingRoomId } }) => {
const waitingRoom = await WaitingRoom.load(
waitingRoomId as ID<WaitingRoom>,
{
resolve: {
game: true,
},
},
);
if (!waitingRoom) {
throw redirect({ to: "/" });
}
if (!waitingRoom?.account1?.isMe) {
const sender = await InboxSender.load<JoinGameRequest, WaitingRoom>(
WORKER_ID,
me,
// { account1: {}, account2: {}, me, game: {} },
);
sender.sendMessage(
JoinGameRequest.create(
{ type: "joinGame", waitingRoom },
{ owner: Group.create({ owner: me }) },
),
);
// If the waiting room already has a game, redirect to the game
if (waitingRoom?.game) {
throw redirect({ to: `/game/${waitingRoom.game.id}` as string });
}
}
return { waitingRoom };
},
});
function RouteComponent() {
const { waitingRoom } = Route.useLoaderData();
const navigate = Route.useNavigate();
const [copied, setCopied] = useState(false);
useEffect(() => {
if (!waitingRoom) {
return;
}
return waitingRoom.subscribe(
{
resolve: {
game: true,
},
},
async () => {
if (waitingRoom.game) {
navigate({ to: `/game/${waitingRoom.game.id}` });
}
},
);
}, [waitingRoom]);
const onCopyClick = () => {
navigator.clipboard.writeText(window.location.toString());
setCopied(true);
};
return (
<div className="h-screen flex flex-col w-full place-items-center justify-center p-2">
<Card className="w-[500px]">
<CardHeader>
<CardTitle className="flex items-center">
<Loader2Icon className="animate-spin inline h-8 w-8 mr-2" />
Waiting for opponent to join the game
</CardTitle>
<CardDescription>
Share this link with your friend to join the game. The game will
automatically start once they join.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex">
<Input
className="w-full border bg-muted rounded-e-none"
readOnly
value={`${window.location}`}
/>
<Button onClick={onCopyClick} className="rounded-s-none w-25">
{copied ? (
"Copied!"
) : (
<>
<ClipboardCopyIcon className="w-5 h-5" />
Copy
</>
)}
</Button>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,54 @@
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { WORKER_ID } from "@/constants";
import { CreateGameRequest } from "@/schema";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { experimental_useInboxSender as useInboxSender } from "jazz-react";
import { useState } from "react";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
function HomeComponent() {
const createGame = useInboxSender(WORKER_ID);
const navigate = useNavigate({ from: "/" });
const [isLoading, setIsLoading] = useState(false);
const onNewGameClick = async () => {
setIsLoading(true);
const waitingRoomId = await createGame(
CreateGameRequest.create({
type: "createGame",
}),
);
if (!waitingRoomId) {
setIsLoading(false);
return;
}
navigate({ to: `/waiting-room/$waitingRoomId`, params: { waitingRoomId } });
};
return (
<div className="h-screen flex flex-col w-full place-items-center justify-center p-2">
<Card className="w-[500px]">
<CardHeader>
<CardTitle>Welcome to Jazz, Paper, Scissors!</CardTitle>
</CardHeader>
<CardContent className="p-0">
<div className="flex items-center p-4">
<Button
onClick={onNewGameClick}
loading={isLoading}
loadingText="Creating game..."
className="w-full"
>
New Game
</Button>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@@ -0,0 +1,65 @@
import { Account, CoMap, SchemaUnion, co } from "jazz-tools";
export class Game extends CoMap {
player1 = co.ref(Player);
player2? = co.ref(Player);
outcome? = co.literal("player1", "player2", "draw");
player1Score = co.number;
player2Score = co.number;
/**
* Given a player, returns the opponent in the current game.
*/
getOpponent(player: Player) {
// TODO: player may be unrelated to this game
const opponent =
player.account?.id === this.player1?.account?.id
? this.player2
: this.player1;
if (!opponent) {
throw new Error("Opponent not found");
}
return opponent.ensureLoaded({
// account: {},
resolve: {},
});
}
}
export class Player extends CoMap {
account = co.ref(Account);
playSelection? = co.string;
}
export class WaitingRoom extends CoMap {
account1 = co.ref(Account);
account2 = co.optional.ref(Account);
game = co.optional.ref(Game);
}
export class InboxMessage extends CoMap {
type = co.literal("play", "createGame", "joinGame", "newGame");
}
export class PlayIntent extends InboxMessage {
type = co.literal("play");
gameId = co.string;
player = co.literal("player1", "player2");
playSelection = co.string;
}
export class NewGameIntent extends InboxMessage {
type = co.literal("newGame");
gameId = co.string;
}
export class CreateGameRequest extends InboxMessage {
type = co.literal("createGame");
}
export class JoinGameRequest extends InboxMessage {
type = co.literal("joinGame");
waitingRoom = co.ref(WaitingRoom);
}

View File

@@ -0,0 +1,201 @@
import {
Game,
InboxMessage,
JoinGameRequest,
NewGameIntent,
PlayIntent,
Player,
WaitingRoom,
} from "@/schema";
import { startWorker } from "jazz-nodejs";
import { Account, Group, type ID } from "jazz-tools";
import { determineWinner } from "./lib/utils";
if (!process.env.VITE_JAZZ_WORKER_ACCOUNT || !process.env.JAZZ_WORKER_SECRET) {
throw new Error(".env missing, run `pnpm generate-env`");
}
const {
worker,
experimental: { inbox },
} = await startWorker({
accountID: process.env.VITE_JAZZ_WORKER_ACCOUNT,
syncServer: "wss://cloud.jazz.tools/?key=jazz-paper-scissors@garden.co ",
});
inbox.subscribe(
InboxMessage,
async (message, senderID) => {
const playerAccount = await Account.load(senderID, { loadAs: worker });
if (!playerAccount) {
return;
}
switch (message.type) {
case "play":
handlePlayIntent(senderID, message.castAs(PlayIntent));
break;
case "newGame":
handleNewGameIntent(senderID, message.castAs(NewGameIntent));
break;
case "createGame":
const waitingRoomGroup = Group.create({ owner: worker });
waitingRoomGroup.addMember("everyone", "reader");
const waitingRoom = WaitingRoom.create(
{ account1: playerAccount },
{ owner: waitingRoomGroup },
);
console.log("waiting room created with id:", waitingRoom.id);
return waitingRoom;
case "joinGame":
const joinGameRequest = message.castAs(JoinGameRequest);
if (
!joinGameRequest.waitingRoom ||
!joinGameRequest.waitingRoom.account1
) {
console.error("No waiting room in join game request");
return;
}
joinGameRequest.waitingRoom.account2 = playerAccount;
const game = await createGame({
account1: joinGameRequest.waitingRoom.account1,
account2: joinGameRequest.waitingRoom.account2,
});
console.log("game created with id:", game.id);
joinGameRequest.waitingRoom.game = game;
return joinGameRequest.waitingRoom.game;
}
},
{ retries: 3 },
);
console.log("worker", worker.id, "started");
interface CreateGameParams {
account1: Account;
account2: Account;
}
async function createGame({ account1, account2 }: CreateGameParams) {
const publicReadOnly = Group.create({ owner: worker });
publicReadOnly.addMember(account1, "reader");
publicReadOnly.addMember(account2, "reader");
const player1 = createPlayer({ account: account1 });
const player2 = createPlayer({ account: account2 });
const game = Game.create(
{
player1: player1,
player2: player2,
player1Score: 0,
player2Score: 0,
},
{ owner: publicReadOnly },
);
await game.waitForSync();
return game;
}
interface CreatePlayerParams {
account: Account;
}
function createPlayer({ account }: CreatePlayerParams) {
const publicRead = Group.create({ owner: worker });
publicRead.addMember("everyone", "reader");
const player = Player.create(
{
account,
},
{ owner: publicRead },
);
return player;
}
async function handleNewGameIntent(_: ID<Account>, message: NewGameIntent) {
const gameId = message.gameId;
const game = await Game.load(gameId as ID<Game>, {
loadAs: worker,
resolve: {
player1: true,
player2: true,
},
});
if (!game) {
throw new Error("Game not found");
}
if (game.outcome) {
game.outcome = undefined;
game.player1.playSelection = undefined;
if (game.player2) {
game.player2.playSelection = undefined;
}
}
}
async function handlePlayIntent(_: ID<Account>, message: PlayIntent) {
// determine current player, update game with outcome
const gameId = message.gameId;
if (!gameId) {
console.error("Game not found");
return;
}
const game = await Game.load(gameId as ID<Game>, {
loadAs: worker,
resolve: {
player1: true,
player2: true,
},
});
if (!game) {
throw new Error("Game not found");
}
const currentPlayer = game[message.player as "player1" | "player2"];
if (!currentPlayer) {
throw new Error("Player not found");
}
if (currentPlayer.playSelection) {
throw new Error("Player already made a selection");
}
currentPlayer.playSelection = message.playSelection;
const player1Selection = game?.player1.playSelection;
const player2Selection = game?.player2?.playSelection;
// once both players have a selection, determine the winner
if (
!!player1Selection &&
player1Selection !== "" &&
!!player2Selection &&
player2Selection !== ""
) {
const outcome = determineWinner(player1Selection, player2Selection);
game.outcome = outcome;
if (outcome === "player1") {
game.player1Score += 1;
} else if (outcome === "player2") {
game.player2Score += 1;
}
}
}

View File

@@ -0,0 +1,28 @@
{
"include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": {
"target": "ES2022",
"jsx": "react-jsx",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
/* Linting */
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}

View File

@@ -0,0 +1,18 @@
import { resolve } from "node:path";
import tailwindcss from "@tailwindcss/vite";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
import viteReact from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
TanStackRouterVite({ autoCodeSplitting: true }),
viteReact(),
tailwindcss(),
],
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
},
},
});

View File

@@ -1,5 +1,20 @@
# multi-cursors
## 0.0.71
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.70
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.0.69
### Patch Changes

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "multi-cursors",
"private": true,
"version": "0.0.69",
"version": "0.0.71",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -14,7 +14,7 @@ export function Logo() {
fillRule="evenodd"
clipRule="evenodd"
d="M136.179 44.8277C136.179 44.8277 136.179 44.8277 136.179 44.8276V21.168C117.931 28.5527 97.9854 32.6192 77.0897 32.6192C65.1466 32.6192 53.5138 31.2908 42.331 28.7737V51.4076C42.331 51.4076 42.331 51.4076 42.331 51.4076V81.1508C41.2955 80.4385 40.1568 79.8458 38.9405 79.3915C36.1732 78.358 33.128 78.0876 30.1902 78.6145C27.2524 79.1414 24.5539 80.4419 22.4358 82.3516C20.3178 84.2613 18.8754 86.6944 18.291 89.3433C17.7066 91.9921 18.0066 94.7377 19.1528 97.2329C20.2991 99.728 22.2403 101.861 24.7308 103.361C27.2214 104.862 30.1495 105.662 33.1448 105.662H33.1455C33.6061 105.662 33.8365 105.662 34.0314 105.659C44.5583 105.449 53.042 96.9656 53.2513 86.4386C53.2534 86.3306 53.2544 86.2116 53.2548 86.0486H53.2552V85.7149L53.2552 85.5521V82.0762L53.2552 53.1993C61.0533 54.2324 69.0092 54.7656 77.0897 54.7656C77.6696 54.7656 78.2489 54.7629 78.8276 54.7574V110.696C77.792 109.983 76.6533 109.391 75.437 108.936C72.6697 107.903 69.6246 107.632 66.6867 108.159C63.7489 108.686 61.0504 109.987 58.9323 111.896C56.8143 113.806 55.3719 116.239 54.7875 118.888C54.2032 121.537 54.5031 124.283 55.6494 126.778C56.7956 129.273 58.7368 131.405 61.2273 132.906C63.7179 134.406 66.646 135.207 69.6414 135.207C70.1024 135.207 70.3329 135.207 70.5279 135.203C81.0548 134.994 89.5385 126.51 89.7478 115.983C89.7517 115.788 89.7517 115.558 89.7517 115.097V111.621L89.7517 54.3266C101.962 53.4768 113.837 51.4075 125.255 48.2397V80.9017C124.219 80.1894 123.081 79.5966 121.864 79.1424C119.097 78.1089 116.052 77.8384 113.114 78.3653C110.176 78.8922 107.478 80.1927 105.36 82.1025C103.242 84.0122 101.799 86.4453 101.215 89.0941C100.631 91.743 100.931 94.4886 102.077 96.9837C103.223 99.4789 105.164 101.612 107.655 103.112C110.145 104.612 113.073 105.413 116.069 105.413C116.53 105.413 116.76 105.413 116.955 105.409C127.482 105.2 135.966 96.7164 136.175 86.1895C136.179 85.9945 136.179 85.764 136.179 85.3029V81.8271L136.179 44.8277Z"
fill="#3313F7"
fill="#146AFF"
/>
</svg>
);

View File

@@ -1,5 +1,28 @@
# multiauth
## 0.0.19
### Patch Changes
- jazz-react@0.13.10
- jazz-react-auth-clerk@0.13.10
- jazz-tools@0.13.10
## 0.0.18
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
- jazz-react-auth-clerk@0.13.9
## 0.0.17
### Patch Changes
- jazz-react-auth-clerk@0.13.8
## 0.0.16
### Patch Changes

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "multiauth",
"private": true,
"version": "0.0.16",
"version": "0.0.19",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,22 @@
# jazz-example-musicplayer
## 0.0.100
### Patch Changes
- jazz-inspector@0.13.10
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.99
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-inspector@0.13.9
- jazz-react@0.13.9
## 0.0.98
### Patch Changes

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-example-music-player",
"private": true,
"version": "0.0.98",
"version": "0.0.100",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -1,5 +1,20 @@
# organization
## 0.0.71
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.70
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.0.69
### Patch Changes

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "organization",
"private": true,
"version": "0.0.69",
"version": "0.0.71",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,20 @@
# passkey-svelte
## 0.0.65
### Patch Changes
- jazz-svelte@0.13.10
- jazz-tools@0.13.10
## 0.0.64
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-svelte@0.13.9
## 0.0.63
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "passkey-svelte",
"version": "0.0.63",
"version": "0.0.65",
"type": "module",
"private": true,
"scripts": {
@@ -32,6 +32,7 @@
"vite": "6.0.11"
},
"dependencies": {
"jazz-svelte": "workspace:*"
"jazz-svelte": "workspace:*",
"jazz-tools": "workspace:*"
}
}

View File

@@ -1,5 +1,20 @@
# minimal-auth-passkey
## 0.0.76
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.75
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.0.74
### Patch Changes

View File

@@ -5,6 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jazz | Minimal Auth Passkey Example</title>
<link rel="icon" href="./public/favicon.ico" type="image/png">
</head>
<body>
<div id="root"></div>

View File

@@ -1,7 +1,7 @@
{
"name": "passkey",
"private": true,
"version": "0.0.74",
"version": "0.0.76",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,5 +1,20 @@
# passphrase
## 0.0.73
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.72
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.0.71
### Patch Changes

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "passphrase",
"private": true,
"version": "0.0.71",
"version": "0.0.73",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,5 +1,20 @@
# jazz-password-manager
## 0.0.97
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.96
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.0.95
### Patch Changes

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS + React + Tailwind</title>
<title>Jazz Password Manager Example</title>
<link rel="icon" type="image/png" href="./public/favicon.ico">
</head>
<body>
<div id="root"></div>

View File

@@ -1,7 +1,7 @@
{
"name": "jazz-password-manager",
"private": true,
"version": "0.0.95",
"version": "0.0.97",
"type": "module",
"scripts": {
"dev": "vite",

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,5 +1,20 @@
# jazz-example-pets
## 0.0.195
### Patch Changes
- jazz-react@0.13.10
- jazz-tools@0.13.10
## 0.0.194
### Patch Changes
- Updated dependencies [a6cf01f]
- jazz-tools@0.13.9
- jazz-react@0.13.9
## 0.0.193
### Patch Changes

BIN
examples/pets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

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