Compare commits
113 Commits
jazz-react
...
vscode-set
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b3c3fff7b | ||
|
|
6610aea708 | ||
|
|
f6b6c01852 | ||
|
|
63f9aa6b97 | ||
|
|
822eb22da6 | ||
|
|
fdf3c6e93d | ||
|
|
de38161023 | ||
|
|
59cc64d697 | ||
|
|
55c249834a | ||
|
|
dd189abd4a | ||
|
|
df1a8e9c99 | ||
|
|
259467ac3f | ||
|
|
9a93ab2476 | ||
|
|
337272bbdc | ||
|
|
464ef7d9c3 | ||
|
|
8727089654 | ||
|
|
753bceddb3 | ||
|
|
8202996108 | ||
|
|
1385c8a66f | ||
|
|
2a72942f8e | ||
|
|
e16836becc | ||
|
|
134a101a52 | ||
|
|
3fc6030776 | ||
|
|
8aebe61bf9 | ||
|
|
8c9e807c19 | ||
|
|
e25e4080c7 | ||
|
|
70a8353fe6 | ||
|
|
185b0d866a | ||
|
|
717667ac4c | ||
|
|
a0151caf02 | ||
|
|
c259331f57 | ||
|
|
f6bc8afa06 | ||
|
|
cee0aea5e6 | ||
|
|
c0e11f543d | ||
|
|
df359ab6dd | ||
|
|
d89da07d42 | ||
|
|
94098c1f1f | ||
|
|
e71e83a217 | ||
|
|
df0b21caed | ||
|
|
6921e621d7 | ||
|
|
ec9e03c266 | ||
|
|
1b0ef401fb | ||
|
|
1833983b8d | ||
|
|
149ca97c48 | ||
|
|
f01a7621b0 | ||
|
|
e6a1e2f169 | ||
|
|
6413059709 | ||
|
|
3c6fd37aba | ||
|
|
8dc90a3554 | ||
|
|
51b1818de6 | ||
|
|
ec7c416097 | ||
|
|
0f30eeaec6 | ||
|
|
5a3cf04ba7 | ||
|
|
1fe74be8af | ||
|
|
376032f54c | ||
|
|
d6e744d948 | ||
|
|
23b3acb58c | ||
|
|
f32d0c1fad | ||
|
|
a69ed0b7cd | ||
|
|
b4d7024b98 | ||
|
|
222a2ce2e6 | ||
|
|
34a50a9173 | ||
|
|
72c6198ef7 | ||
|
|
e4df1048c8 | ||
|
|
e8abd06406 | ||
|
|
968c2bd699 | ||
|
|
12b6a3b291 | ||
|
|
c9f89e9c32 | ||
|
|
d27cff5c67 | ||
|
|
a734530cc3 | ||
|
|
cc51926d14 | ||
|
|
c0395dd0a3 | ||
|
|
51d7ca09d9 | ||
|
|
fce808cc48 | ||
|
|
4a9217eb25 | ||
|
|
69df98f4fa | ||
|
|
cf0a38d6bf | ||
|
|
dd9b13fbaa | ||
|
|
6a982a29cb | ||
|
|
ebc1b03158 | ||
|
|
adaf01f7fa | ||
|
|
c048f4eda9 | ||
|
|
b06272ff17 | ||
|
|
ac37bfab2d | ||
|
|
d8e50824cb | ||
|
|
29b27291a3 | ||
|
|
efa1b60585 | ||
|
|
9d0b39c5cb | ||
|
|
2a02a5b212 | ||
|
|
c6931b82a0 | ||
|
|
279e2202ba | ||
|
|
ec2324519e | ||
|
|
5932f50c68 | ||
|
|
00906e5d08 | ||
|
|
7bc5fca440 | ||
|
|
e30eb224ae | ||
|
|
983ea7cf03 | ||
|
|
ad2d453e33 | ||
|
|
15d711f6de | ||
|
|
7441a7d3d8 | ||
|
|
0d756209e9 | ||
|
|
b20c2ca173 | ||
|
|
987a186db3 | ||
|
|
f4e0b59fa1 | ||
|
|
0d2112d8d0 | ||
|
|
6fdab780a9 | ||
|
|
159e2eb7f6 | ||
|
|
9daa50dd7d | ||
|
|
9c2aadb7d5 | ||
|
|
7d0d81b16e | ||
|
|
760a1c10c5 | ||
|
|
3ef3ff3db9 | ||
|
|
549ec2047f |
3
.github/workflows/build-examples.yaml
vendored
3
.github/workflows/build-examples.yaml
vendored
@@ -17,6 +17,7 @@ jobs:
|
||||
"password-manager",
|
||||
"pets",
|
||||
"todo",
|
||||
"onboarding",
|
||||
]
|
||||
|
||||
steps:
|
||||
@@ -55,4 +56,4 @@ jobs:
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm turbo build;
|
||||
working-directory: ./examples/${{ matrix.example }}
|
||||
working-directory: ./examples/${{ matrix.example }}
|
||||
|
||||
2
.github/workflows/playwright.yml
vendored
2
.github/workflows/playwright.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
project: ["e2e/BinaryCoStream", "e2e/CoValues", "examples/chat", "examples/music-player", "examples/pets"]
|
||||
project: ["e2e/BinaryCoStream", "e2e/CoValues", "examples/chat", "examples/music-player", "examples/pets", "examples/onboarding"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -15,4 +15,6 @@ coverage
|
||||
# Playwright
|
||||
test-results
|
||||
|
||||
.husky
|
||||
.husky
|
||||
|
||||
.vscode/settings.json
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
@@ -15,6 +15,6 @@ For community and support, please join our [Discord](https://discord.gg/utDMjHYg
|
||||
- Homepage: [jazz.tools](https://jazz.tools)
|
||||
- Docs: [jazz.tools/docs](https://jazz.tools/docs)
|
||||
- Community & support: [Discord](https://discord.gg/utDMjHYg42)
|
||||
- Updates: [Twitter](https://twitter.com/jazz_tools) & [Email](https://gcmp.io/news)
|
||||
- Updates: [X](https://x.com/jazz_tools) & [Email](https://gcmp.io/news)
|
||||
|
||||
Copyright 2024 — Garden Computing, Inc.
|
||||
Copyright 2024 — Garden Computing, Inc.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": ["jazz-tools.json"]
|
||||
"ignore": ["jazz-tools.json", "**/ios/**", "**/android/**"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# @jazz-e2e/binarycostream
|
||||
|
||||
## 0.0.100
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
|
||||
## 0.0.99
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f30eea]
|
||||
- Updated dependencies [149ca97]
|
||||
- cojson@0.8.21
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9c2aadb]
|
||||
- cojson@0.8.19
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@jazz-e2e/binarycostream",
|
||||
"private": true,
|
||||
"version": "0.0.96",
|
||||
"version": "0.0.100",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -13,11 +13,11 @@
|
||||
"test:ui": "playwright test --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"cojson": "workspace:0.8.18",
|
||||
"cojson": "workspace:0.8.21",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-react": "workspace:0.8.18",
|
||||
"jazz-tools": "workspace:0.8.18",
|
||||
"jazz-react": "workspace:0.8.22",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# @jazz-e2e/covalues
|
||||
|
||||
## 0.0.99
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f30eea]
|
||||
- Updated dependencies [149ca97]
|
||||
- cojson@0.8.21
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9c2aadb]
|
||||
- cojson@0.8.19
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@jazz-e2e/covalues",
|
||||
"private": true,
|
||||
"version": "0.0.95",
|
||||
"version": "0.0.99",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# jazz-example-book-shelf
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
- jazz-browser-media-images@0.8.22
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
- jazz-browser-media-images@0.8.21
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
- jazz-browser-media-images@0.8.20
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
- jazz-browser-media-images@0.8.19
|
||||
|
||||
## 0.1.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jazz-example-book-shelf",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.15",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -11,9 +11,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.8.18",
|
||||
"jazz-react": "workspace:0.8.18",
|
||||
"jazz-tools": "workspace:0.8.18",
|
||||
"jazz-browser-media-images": "workspace:0.8.22",
|
||||
"jazz-react": "workspace:0.8.22",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"next": "14.2.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
|
||||
@@ -1,5 +1,45 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.99
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
- jazz-react-auth-clerk@0.8.22
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f30eea]
|
||||
- Updated dependencies [149ca97]
|
||||
- cojson@0.8.21
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
- jazz-react-auth-clerk@0.8.21
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
- jazz-react-auth-clerk@0.8.20
|
||||
|
||||
## 0.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9c2aadb]
|
||||
- cojson@0.8.19
|
||||
- jazz-react@0.8.19
|
||||
- jazz-react-auth-clerk@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat-clerk",
|
||||
"private": true,
|
||||
"version": "0.0.95",
|
||||
"version": "0.0.99",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -17,11 +17,11 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.8.18",
|
||||
"cojson": "workspace:0.8.21",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"jazz-react": "workspace:0.8.18",
|
||||
"jazz-react-auth-clerk": "workspace:0.8.18",
|
||||
"jazz-tools": "workspace:0.8.18",
|
||||
"jazz-react": "workspace:0.8.22",
|
||||
"jazz-react-auth-clerk": "workspace:0.8.22",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# chat-rn-clerk
|
||||
|
||||
## 1.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.22
|
||||
|
||||
## 1.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react-auth-clerk@0.8.21
|
||||
- jazz-react-native@0.8.21
|
||||
- jazz-react-native-media-images@0.8.17
|
||||
|
||||
## 1.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- jazz-react-native-media-images@0.8.16
|
||||
- jazz-react-native@0.8.20
|
||||
- jazz-react-auth-clerk@0.8.20
|
||||
|
||||
## 1.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-auth-clerk@0.8.19
|
||||
- jazz-react-native@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
- jazz-react-native-media-images@0.8.15
|
||||
|
||||
## 1.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,6 +2,6 @@ module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
plugins: ["nativewind/babel", "@babel/plugin-transform-class-static-block"],
|
||||
plugins: ["nativewind/babel"],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.11",
|
||||
"version": "1.0.15",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
@@ -65,7 +65,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.7",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/react": "^18.2.19",
|
||||
"@types/react-test-renderer": "^18.0.7",
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react-native@0.8.21
|
||||
|
||||
## 1.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- jazz-react-native@0.8.20
|
||||
|
||||
## 1.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react-native@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 1.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,6 +2,6 @@ module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ["babel-preset-expo"],
|
||||
plugins: ["nativewind/babel", "@babel/plugin-transform-class-static-block"],
|
||||
plugins: ["nativewind/babel"],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.16",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
@@ -45,7 +45,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-transform-class-static-block": "^7.24.7",
|
||||
"@types/react": "^18.2.19",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3"
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a734530: fix useCoState reactivity
|
||||
- Updated dependencies [f6bc8af]
|
||||
- Updated dependencies [a734530]
|
||||
- jazz-browser@0.8.22
|
||||
- jazz-vue@0.8.12
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-browser@0.8.21
|
||||
- jazz-vue@0.8.11
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.8.20
|
||||
- jazz-vue@0.8.10
|
||||
|
||||
## 0.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
- jazz-vue@0.8.9
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.7",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.101
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
|
||||
## 0.0.100
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f30eea]
|
||||
- Updated dependencies [149ca97]
|
||||
- cojson@0.8.21
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
|
||||
## 0.0.99
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
|
||||
## 0.0.98
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9c2aadb]
|
||||
- cojson@0.8.19
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.97",
|
||||
"version": "0.0.101",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -18,10 +18,10 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.8.18",
|
||||
"cojson": "workspace:0.8.21",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"jazz-react": "workspace:0.8.18",
|
||||
"jazz-tools": "workspace:0.8.18",
|
||||
"jazz-react": "workspace:0.8.22",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [0f30eea]
|
||||
- cojson@0.8.21
|
||||
- cojson-transport-ws@0.8.21
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9c2aadb]
|
||||
- cojson@0.8.19
|
||||
- cojson-transport-ws@0.8.19
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector",
|
||||
"private": true,
|
||||
"version": "0.0.71",
|
||||
"version": "0.0.73",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,8 +16,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:0.8.18",
|
||||
"cojson-transport-ws": "workspace:0.8.18",
|
||||
"cojson": "workspace:0.8.21",
|
||||
"cojson-transport-ws": "workspace:0.8.21",
|
||||
"hash-slash": "workspace:0.2.1",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
|
||||
## 0.0.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
|
||||
## 0.0.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
|
||||
## 0.0.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.17",
|
||||
"version": "0.0.21",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -18,8 +18,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "workspace:0.8.18",
|
||||
"jazz-tools": "workspace:0.8.18",
|
||||
"jazz-react": "workspace:0.8.22",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"lucide-react": "^0.274.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -70,7 +70,11 @@ function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Jazz.Provider auth={auth} peer={peer}>
|
||||
<Jazz.Provider
|
||||
storage={["singleTabOPFS", "indexedDB"]}
|
||||
auth={auth}
|
||||
peer={peer}
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
<DemoAuthBasicUI appName="Jazz Music Player" state={state} />
|
||||
|
||||
26
examples/onboarding/.gitignore
vendored
Normal file
26
examples/onboarding/.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
playwright-report
|
||||
9
examples/onboarding/CHANGELOG.md
Normal file
9
examples/onboarding/CHANGELOG.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# jazz-example-onboarding
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
- jazz-browser-media-images@0.8.22
|
||||
4
examples/onboarding/Dockerfile
Normal file
4
examples/onboarding/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM caddy:2.7.3-alpine
|
||||
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
|
||||
|
||||
COPY ./dist /usr/share/caddy/
|
||||
1
examples/onboarding/README.md
Normal file
1
examples/onboarding/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# vite-ts-react-tailwind
|
||||
12
examples/onboarding/index.html
Normal file
12
examples/onboarding/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Jazz onboarding example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
36
examples/onboarding/package.json
Normal file
36
examples/onboarding/package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "jazz-example-onboarding",
|
||||
"private": true,
|
||||
"version": "0.0.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"preview": "vite preview",
|
||||
"test": "playwright test",
|
||||
"test:ui": "playwright test --ui",
|
||||
"sync": "jazz-run sync"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-browser-media-images": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jazz-run": "workspace:*",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@types/react": "^18.2.19",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"typescript": "^5.3.3",
|
||||
"is-ci": "^3.0.1",
|
||||
"vite": "^5.0.10"
|
||||
}
|
||||
}
|
||||
51
examples/onboarding/playwright.config.ts
Normal file
51
examples/onboarding/playwright.config.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
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/?peer=ws://localhost:1234",
|
||||
|
||||
/* 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,
|
||||
},
|
||||
{
|
||||
command: "pnpm sync --in-memory --port 1234",
|
||||
url: "http://localhost:1234/health",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
});
|
||||
6
examples/onboarding/postcss.config.js
Normal file
6
examples/onboarding/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
examples/onboarding/public/jazz-logo-low-res.jpg
Normal file
BIN
examples/onboarding/public/jazz-logo-low-res.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 820 B |
83
examples/onboarding/src/App.tsx
Normal file
83
examples/onboarding/src/App.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Button } from "@/components/Button.tsx";
|
||||
import { useAccount, useCoState } from "@/main.tsx";
|
||||
import { EmployeeList } from "@/pages/EmployeeList.tsx";
|
||||
import { EmployeeOnboading } from "@/pages/EmployeeOnboarding.tsx";
|
||||
import { NewEmployee } from "@/pages/NewEmployee.tsx";
|
||||
import { CoEmployee, EmployeeCoList } from "@/schema.ts";
|
||||
import { ID } from "jazz-tools";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
RouterProvider,
|
||||
createBrowserRouter,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
|
||||
function ImportEmployee({
|
||||
employeeListCoId,
|
||||
}: { employeeListCoId: ID<EmployeeCoList> }) {
|
||||
const { employeeCoId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const employees = useCoState(EmployeeCoList, employeeListCoId, [{}]);
|
||||
const employee = useCoState(CoEmployee, employeeCoId as ID<CoEmployee>, {});
|
||||
|
||||
useEffect(() => {
|
||||
if (!employee || !employees) return;
|
||||
|
||||
const exists = employees.find((employee) => employeeCoId === employee.id);
|
||||
|
||||
if (!exists) {
|
||||
employees.push(employee);
|
||||
}
|
||||
navigate("/");
|
||||
}, [employee, employees, navigate]);
|
||||
|
||||
return <div>Importing Employee ${employeeCoId} ...</div>;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const { me, logOut } = useAccount();
|
||||
const employeeCoListId = me.profile?._refs.employees.id;
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <EmployeeList employeeListCoId={employeeCoListId} />,
|
||||
},
|
||||
{
|
||||
path: "employee/new",
|
||||
element: <NewEmployee employeeListCoId={employeeCoListId} />,
|
||||
},
|
||||
{
|
||||
path: "/employee/:employeeCoId",
|
||||
element: <EmployeeOnboading />,
|
||||
},
|
||||
{
|
||||
path: "/import/:employeeCoId",
|
||||
element: <ImportEmployee employeeListCoId={employeeCoListId} />,
|
||||
},
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="flex flex-wrap space-x-8 max-w-screen-lg m-2">
|
||||
<h1 className="text-3xl font-extrabold">
|
||||
Jazz Onboarding Flow example
|
||||
</h1>
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.location.href = "/";
|
||||
logOut();
|
||||
}}
|
||||
text="Log Out"
|
||||
/>
|
||||
</header>
|
||||
<main className="ml-2">
|
||||
{employeeCoListId && <RouterProvider router={router} />}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
29
examples/onboarding/src/components/Button.tsx
Normal file
29
examples/onboarding/src/components/Button.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
const disabledClasses =
|
||||
"text-white bg-gray-400 dark:bg-gray-500 cursor-not-allowed";
|
||||
const regularClasses =
|
||||
"text-white bg-gradient-to-r from-green-400 via-green-500 to-green-600 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-green-300 dark:focus:ring-green-800";
|
||||
|
||||
export function Button({
|
||||
text,
|
||||
onClick,
|
||||
disabled,
|
||||
...props
|
||||
}: {
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
onClick={onClick}
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
className={`${
|
||||
disabled ? disabledClasses : regularClasses
|
||||
} text-base font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2`}
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
19
examples/onboarding/src/components/ButtonLink.tsx
Normal file
19
examples/onboarding/src/components/ButtonLink.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function ButtonLink({
|
||||
to,
|
||||
children,
|
||||
}: {
|
||||
to: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
|
||||
to={to}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
19
examples/onboarding/src/components/NavLink.tsx
Normal file
19
examples/onboarding/src/components/NavLink.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function NavLink({
|
||||
to,
|
||||
children,
|
||||
}: {
|
||||
to: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
className="font-medium text-blue-600 dark:text-blue-500 hover:underline"
|
||||
to={to}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
24
examples/onboarding/src/components/NavigateBack.tsx
Normal file
24
examples/onboarding/src/components/NavigateBack.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "./Button.tsx";
|
||||
|
||||
export function NavigateBack() {
|
||||
const navigate = useNavigate();
|
||||
const canGoBack = window.history.state.idx !== 0;
|
||||
|
||||
if (!canGoBack) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => navigate(-1)} text="< Back" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function NavigateButton({ text, to }: { text: string; to: string }) {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => navigate(to)} text={text} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
19
examples/onboarding/src/components/Stack.tsx
Normal file
19
examples/onboarding/src/components/Stack.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
|
||||
export function Stack({
|
||||
children,
|
||||
horizontal,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
horizontal?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`container flex ${
|
||||
horizontal ? "flex-row" : "flex-col"
|
||||
} col ${horizontal ? "space-x-4 flex-wrap" : "space-y-4"} p-4`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
examples/onboarding/src/components/TextInput.tsx
Normal file
36
examples/onboarding/src/components/TextInput.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { ChangeEvent } from "react";
|
||||
|
||||
export function TextInput({
|
||||
id,
|
||||
value,
|
||||
label,
|
||||
onChange,
|
||||
disabled,
|
||||
}: {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="block mb-2 font-medium text-gray-900 dark:text-white"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
type="text"
|
||||
id={id}
|
||||
disabled={disabled}
|
||||
className="disabled:cursor-not-allowed bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="John"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
examples/onboarding/src/index.css
Normal file
3
examples/onboarding/src/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
37
examples/onboarding/src/main.tsx
Normal file
37
examples/onboarding/src/main.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import App from "@/App.tsx";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "@/index.css";
|
||||
import { HRAccount } from "@/schema.ts";
|
||||
import { DemoAuthBasicUI, createJazzReactApp, useDemoAuth } from "jazz-react";
|
||||
|
||||
const Jazz = createJazzReactApp({
|
||||
AccountSchema: HRAccount,
|
||||
});
|
||||
export const { useAccount, useCoState, useAcceptInvite } = Jazz;
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const [auth, authState] = useDemoAuth();
|
||||
return (
|
||||
<>
|
||||
<Jazz.Provider
|
||||
auth={auth}
|
||||
// replace `you@example.com` with your email as a temporary API key
|
||||
peer="wss://cloud.jazz.tools/?key=you@example.com"
|
||||
>
|
||||
{children}
|
||||
</Jazz.Provider>
|
||||
{authState.state !== "signedIn" && (
|
||||
<DemoAuthBasicUI appName="Jazz Onboarding" state={authState} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<JazzAndAuth>
|
||||
<App />
|
||||
</JazzAndAuth>
|
||||
</React.StrictMode>,
|
||||
);
|
||||
50
examples/onboarding/src/pages/EmployeeList.tsx
Normal file
50
examples/onboarding/src/pages/EmployeeList.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { NavLink } from "@/components/NavLink.tsx";
|
||||
import { NavigateButton } from "@/components/NavigateBack.tsx";
|
||||
import { Stack } from "@/components/Stack.tsx";
|
||||
import { useCoState } from "@/main.tsx";
|
||||
import { CoEmployee, EmployeeCoList } from "@/schema.ts";
|
||||
import { ID } from "jazz-tools";
|
||||
|
||||
export function EmployeeList({
|
||||
employeeListCoId,
|
||||
}: {
|
||||
employeeListCoId: ID<EmployeeCoList>;
|
||||
}) {
|
||||
const employees = useCoState(EmployeeCoList, employeeListCoId, [{}]);
|
||||
|
||||
if (!employees) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<NavigateButton to="/employee/new" text={"Add New Employee"} />
|
||||
<ul className="max-w-md">
|
||||
{employees.map((employee: CoEmployee) =>
|
||||
employee.deleted ? null : (
|
||||
<li key={employee.id} className="flex flex-row space-x-8 w-full">
|
||||
<span>{employee._owner.myRole()}</span>
|
||||
<span className="w-1/3">
|
||||
<NavLink to={`/employee/${employee.id}`}>
|
||||
{employee.name}
|
||||
</NavLink>
|
||||
</span>
|
||||
{employee.finalStep?.done && <span>✅</span>}
|
||||
{employee._owner.myRole() === "admin" &&
|
||||
!employee.finalStep?.done && (
|
||||
<span
|
||||
onClick={() => {
|
||||
employee.deleted = true;
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
🗑
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</ul>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
249
examples/onboarding/src/pages/EmployeeOnboarding.tsx
Normal file
249
examples/onboarding/src/pages/EmployeeOnboarding.tsx
Normal file
@@ -0,0 +1,249 @@
|
||||
import { Button } from "@/components/Button.tsx";
|
||||
import { NavigateBack } from "@/components/NavigateBack.tsx";
|
||||
import { Stack } from "@/components/Stack.tsx";
|
||||
import { TextInput } from "@/components/TextInput.tsx";
|
||||
import { useAcceptInvite, useCoState } from "@/main.tsx";
|
||||
import { createImage } from "jazz-browser-media-images";
|
||||
import { ProgressiveImg, createInviteLink } from "jazz-react";
|
||||
import { CoMap, ID } from "jazz-tools";
|
||||
import { ChangeEvent, ReactNode, useCallback } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
CoDocUploadStep,
|
||||
CoEmployee,
|
||||
CoFinalStep,
|
||||
CoInitialStep,
|
||||
} from "../schema.ts";
|
||||
|
||||
const Card = ({
|
||||
children,
|
||||
title,
|
||||
isDone,
|
||||
isActive,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
title: string;
|
||||
isDone: boolean;
|
||||
isActive?: boolean;
|
||||
}) => (
|
||||
<div
|
||||
className={`w-full p-4 bg-white border border-gray-200 rounded-lg shadow max-w-md ${
|
||||
isActive ? "border-gray-900 hover:bg-green-50 shadow-xl" : ""
|
||||
}`}
|
||||
>
|
||||
<Stack horizontal={true}>
|
||||
<h5 className="mb-2 text-2xl text-gray-900">{title}</h5>
|
||||
<h6 className="mb-2 text-2xl">
|
||||
{isDone ? "✅" : isActive ? "❓" : "⌛"}
|
||||
</h6>
|
||||
</Stack>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
const InfoCard = ({
|
||||
initialStep,
|
||||
canWrite,
|
||||
}: {
|
||||
initialStep: CoInitialStep;
|
||||
canWrite: boolean;
|
||||
}) => {
|
||||
const isDisabled = !initialStep.isCurrentStep() || !canWrite;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="Personal Info"
|
||||
isDone={initialStep?.done}
|
||||
isActive={initialStep.isCurrentStep()}
|
||||
>
|
||||
<Stack>
|
||||
<TextInput
|
||||
disabled={isDisabled}
|
||||
id="ssn"
|
||||
label="Social Security Number"
|
||||
value={initialStep.ssn || ""}
|
||||
onChange={({ target: { value } }) => (initialStep.ssn = value)}
|
||||
/>
|
||||
<TextInput
|
||||
disabled={isDisabled}
|
||||
id="address"
|
||||
label="Address"
|
||||
value={initialStep.address || ""}
|
||||
onChange={({ target: { value } }) => (initialStep.address = value)}
|
||||
/>
|
||||
{!initialStep.done && (
|
||||
<Button
|
||||
text={"Upload step >"}
|
||||
disabled={!initialStep.ssn || !initialStep.address || isDisabled}
|
||||
onClick={() => (initialStep.done = true)}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const UploadCard = ({
|
||||
uploadStep,
|
||||
canWrite,
|
||||
}: {
|
||||
uploadStep: CoDocUploadStep;
|
||||
canWrite: boolean;
|
||||
}) => {
|
||||
const isDisabled = !uploadStep.isCurrentStep() || !canWrite;
|
||||
|
||||
const onImageSelected = useCallback(
|
||||
async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!event.target.files) return;
|
||||
|
||||
const image = await createImage(event.target.files[0], {
|
||||
owner: uploadStep._owner,
|
||||
});
|
||||
|
||||
uploadStep.photo = image;
|
||||
},
|
||||
[uploadStep],
|
||||
);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title="Uploads"
|
||||
isDone={uploadStep?.done}
|
||||
isActive={uploadStep.isCurrentStep()}
|
||||
>
|
||||
<Stack>
|
||||
{uploadStep.photo && (
|
||||
<ProgressiveImg image={uploadStep.photo}>
|
||||
{({ src }) => (
|
||||
<img
|
||||
className="max-h-full max-w-full rounded-l-sm rounded-r-md shadow-lg p-2"
|
||||
src={src}
|
||||
/>
|
||||
)}
|
||||
</ProgressiveImg>
|
||||
)}
|
||||
|
||||
{!uploadStep.done && (
|
||||
<>
|
||||
<input
|
||||
type="file"
|
||||
disabled={isDisabled}
|
||||
onChange={onImageSelected}
|
||||
data-testid="file-upload"
|
||||
/>
|
||||
<Button
|
||||
text={"Confirmation step >"}
|
||||
disabled={isDisabled || !uploadStep.photo}
|
||||
onClick={() => (uploadStep.done = true)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
const ConfirmationCard = ({
|
||||
finalStep,
|
||||
editable,
|
||||
}: {
|
||||
finalStep: CoFinalStep;
|
||||
editable: boolean;
|
||||
}) => {
|
||||
const isDisabled = !finalStep.isCurrentStep() || !editable;
|
||||
return (
|
||||
<Card
|
||||
title="Confirmation by admin"
|
||||
isDone={finalStep?.done}
|
||||
isActive={finalStep.isCurrentStep()}
|
||||
>
|
||||
<Stack>
|
||||
{!finalStep.done && (
|
||||
<Button
|
||||
text="Confirmation by admin"
|
||||
disabled={isDisabled}
|
||||
onClick={() => (finalStep.done = true)}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export function EmployeeOnboading() {
|
||||
const { employeeCoId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const employee = useCoState(CoEmployee, employeeCoId as ID<CoEmployee>, {});
|
||||
|
||||
useAcceptInvite({
|
||||
invitedObjectSchema: CoEmployee,
|
||||
onAccept: (employeeCoId) => {
|
||||
navigate(`/import/${employeeCoId}`);
|
||||
},
|
||||
});
|
||||
|
||||
const handleInviteLinkCreation = useCallback(
|
||||
(role: "reader" | "writer") => {
|
||||
if (!employee) return;
|
||||
|
||||
const link = createInviteLink(employee, role);
|
||||
navigator.clipboard.writeText(link);
|
||||
alert("Invite link copied to clipboard!");
|
||||
},
|
||||
[employee],
|
||||
);
|
||||
|
||||
const isMeWriter = (step: CoMap): boolean => {
|
||||
return ["writer", "admin"].includes(step._owner.myRole() || "");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack>
|
||||
<Stack horizontal={true}>
|
||||
<NavigateBack />
|
||||
{employee?._owner.myRole() === "admin" && (
|
||||
<Button
|
||||
text={"Invite a co-worker"}
|
||||
onClick={() => handleInviteLinkCreation("writer")}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
<h2 className="mb-2 text-2xl text-gray-900 font-semibold">
|
||||
{employee ? employee.name : "Loading..."}
|
||||
</h2>
|
||||
</Stack>
|
||||
|
||||
{employee && (
|
||||
<Stack>
|
||||
{employee.initialStep ? (
|
||||
<InfoCard
|
||||
initialStep={employee.initialStep}
|
||||
canWrite={isMeWriter(employee.initialStep)}
|
||||
/>
|
||||
) : (
|
||||
<div>Loading...</div>
|
||||
)}
|
||||
{employee.docUploadStep ? (
|
||||
<UploadCard
|
||||
uploadStep={employee.docUploadStep}
|
||||
canWrite={isMeWriter(employee.docUploadStep)}
|
||||
/>
|
||||
) : (
|
||||
<div>Loading...</div>
|
||||
)}
|
||||
{employee.finalStep ? (
|
||||
<ConfirmationCard
|
||||
finalStep={employee.finalStep}
|
||||
editable={isMeWriter(employee.finalStep)}
|
||||
/>
|
||||
) : (
|
||||
<div>Loading...</div>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
89
examples/onboarding/src/pages/NewEmployee.tsx
Normal file
89
examples/onboarding/src/pages/NewEmployee.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Button } from "@/components/Button.tsx";
|
||||
import { NavigateBack } from "@/components/NavigateBack.tsx";
|
||||
import { Stack } from "@/components/Stack.tsx";
|
||||
import { TextInput } from "@/components/TextInput.tsx";
|
||||
import { useAccount, useCoState } from "@/main.tsx";
|
||||
import { Group, ID } from "jazz-tools";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
CoDocUploadStep,
|
||||
CoEmployee,
|
||||
CoFinalStep,
|
||||
CoInitialStep,
|
||||
EmployeeCoList,
|
||||
} from "../schema.ts";
|
||||
|
||||
export function NewEmployee({
|
||||
employeeListCoId,
|
||||
}: {
|
||||
employeeListCoId: ID<EmployeeCoList>;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const { me } = useAccount();
|
||||
|
||||
const employees = useCoState(EmployeeCoList, employeeListCoId, [{}]);
|
||||
|
||||
const [employeeName, setEmployeeName] = useState<string>("");
|
||||
|
||||
const createEmployee = useCallback(() => {
|
||||
if (!employees) return;
|
||||
|
||||
const writerGroup = Group.create({ owner: me });
|
||||
const readerGroup = Group.create({ owner: me });
|
||||
readerGroup.addMember("everyone", "reader");
|
||||
|
||||
const initialStep = CoInitialStep.create(
|
||||
{ done: false, type: "initial" },
|
||||
{ owner: writerGroup },
|
||||
);
|
||||
|
||||
const docUploadStep = CoDocUploadStep.create(
|
||||
{ done: false, prevStep: initialStep, type: "upload" },
|
||||
{ owner: writerGroup },
|
||||
);
|
||||
|
||||
const finalStep = CoFinalStep.create(
|
||||
{ done: false, prevStep: docUploadStep, type: "final" },
|
||||
{ owner: readerGroup },
|
||||
);
|
||||
|
||||
const employee = CoEmployee.create(
|
||||
{
|
||||
name: employeeName,
|
||||
initialStep,
|
||||
docUploadStep,
|
||||
finalStep,
|
||||
},
|
||||
{ owner: writerGroup },
|
||||
);
|
||||
|
||||
employees.push(employee);
|
||||
setEmployeeName("");
|
||||
}, [employeeName, employees]);
|
||||
|
||||
return (
|
||||
<div className="w-96">
|
||||
<Stack>
|
||||
<NavigateBack />
|
||||
<form>
|
||||
<TextInput
|
||||
label="Employee name"
|
||||
id="employee-name"
|
||||
value={employeeName}
|
||||
onChange={({ target: { value } }) => setEmployeeName(value)}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<Button
|
||||
disabled={!employeeName}
|
||||
onClick={() => {
|
||||
createEmployee();
|
||||
navigate("/");
|
||||
}}
|
||||
text="Create Employee"
|
||||
/>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
78
examples/onboarding/src/schema.ts
Normal file
78
examples/onboarding/src/schema.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
Account,
|
||||
CoList,
|
||||
CoMap,
|
||||
ImageDefinition,
|
||||
Profile,
|
||||
co,
|
||||
} from "jazz-tools";
|
||||
|
||||
type Steps = "initial" | "upload" | "final";
|
||||
|
||||
interface Step {
|
||||
type: Steps;
|
||||
prevStep: ReturnType<typeof co.ref> | undefined;
|
||||
done: boolean;
|
||||
|
||||
isCurrentStep(): boolean;
|
||||
}
|
||||
|
||||
export class CoInitialStep extends CoMap implements Step {
|
||||
type = co.literal("initial");
|
||||
ssn? = co.string;
|
||||
address? = co.string;
|
||||
done = co.boolean;
|
||||
prevStep = co.null;
|
||||
isCurrentStep() {
|
||||
return !this.done;
|
||||
}
|
||||
}
|
||||
|
||||
export class CoDocUploadStep extends CoMap implements Step {
|
||||
type = co.literal("upload");
|
||||
prevStep = co.ref(CoInitialStep);
|
||||
photo = co.ref(ImageDefinition, { optional: true });
|
||||
done = co.boolean;
|
||||
|
||||
isCurrentStep() {
|
||||
return !!(this.prevStep?.done && !this.done);
|
||||
}
|
||||
}
|
||||
|
||||
export class CoFinalStep extends CoMap implements Step {
|
||||
type = co.literal("final");
|
||||
prevStep = co.ref(CoDocUploadStep);
|
||||
done = co.boolean;
|
||||
|
||||
isCurrentStep() {
|
||||
return !!(this.prevStep?.done && !this.done);
|
||||
}
|
||||
}
|
||||
|
||||
export class CoEmployee extends CoMap {
|
||||
name = co.string;
|
||||
deleted? = co.boolean;
|
||||
initialStep = co.ref(CoInitialStep);
|
||||
docUploadStep = co.ref(CoDocUploadStep);
|
||||
finalStep = co.ref(CoFinalStep);
|
||||
}
|
||||
|
||||
export class EmployeeCoList extends CoList.Of(co.ref(CoEmployee)) {}
|
||||
|
||||
export class HRProfile extends Profile {
|
||||
employees = co.ref(EmployeeCoList);
|
||||
}
|
||||
|
||||
export class HRAccount extends Account {
|
||||
profile = co.ref(HRProfile)!;
|
||||
|
||||
migrate(this: HRAccount, creationProps?: { name: string }) {
|
||||
super.migrate(creationProps);
|
||||
|
||||
if (!this.profile._refs.employees) {
|
||||
this.profile.employees = EmployeeCoList.create([], {
|
||||
owner: this.profile._owner,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
1
examples/onboarding/src/vite-env.d.ts
vendored
Normal file
1
examples/onboarding/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
8
examples/onboarding/tailwind.config.js
Normal file
8
examples/onboarding/tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
88
examples/onboarding/tests/onboardingFlow.spec.ts
Normal file
88
examples/onboarding/tests/onboardingFlow.spec.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { Page, expect, test } from "@playwright/test";
|
||||
import { EmployeeOnboardingPage } from "./pages/EmployeeOnboardingPage";
|
||||
import { HomePage } from "./pages/HomePage";
|
||||
import { LoginPage } from "./pages/LoginPage";
|
||||
|
||||
async function scrollToBottom(page: Page) {
|
||||
await page.evaluate(() => {
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
});
|
||||
}
|
||||
|
||||
const login = async ({
|
||||
page,
|
||||
userName,
|
||||
loginAs = false,
|
||||
}: {
|
||||
page: Page;
|
||||
userName: string;
|
||||
loginAs?: boolean;
|
||||
}) => {
|
||||
const loginPage = new LoginPage(page);
|
||||
await loginPage.goto("/");
|
||||
if (loginAs) {
|
||||
await loginPage.loginAs(userName);
|
||||
} else {
|
||||
await loginPage.fillUsername(userName);
|
||||
await loginPage.signup();
|
||||
}
|
||||
};
|
||||
|
||||
test.describe("Admin onboarding flow", () => {
|
||||
test("Create and delete flow", async ({ page }) => {
|
||||
await login({ page, userName: "HR specialist" });
|
||||
|
||||
const homePage = new HomePage(page);
|
||||
await homePage.createEmployee("Paul");
|
||||
await homePage.createEmployee("Sean");
|
||||
await homePage.expectEmployee(["Sean", "admin"]);
|
||||
await homePage.expectEmployee(["Paul", "admin"]);
|
||||
await homePage.deleteEmployee("Sean");
|
||||
await homePage.expectEmployeeDeleted("Sean");
|
||||
});
|
||||
|
||||
test("Onboard flow", async ({ page }) => {
|
||||
const adminUser = "HR specialist";
|
||||
const writerUser = "Invitee";
|
||||
await login({ page, userName: adminUser });
|
||||
|
||||
const homePage = new HomePage(page);
|
||||
await homePage.createEmployee("Paul");
|
||||
await homePage.expectEmployee(["Paul", "admin"]);
|
||||
await homePage.navigateToEmployeeOnboardingPage("Paul");
|
||||
const onboardingPage = new EmployeeOnboardingPage(page);
|
||||
|
||||
// create invitation
|
||||
const invitation = await onboardingPage.getShareLink();
|
||||
await onboardingPage.logout();
|
||||
|
||||
//fill out by invitee (writer)
|
||||
await login({ page, userName: writerUser });
|
||||
await page.goto(invitation);
|
||||
await page.waitForTimeout(1000);
|
||||
await homePage.expectEmployee(["Paul", "write"]);
|
||||
await homePage.navigateToEmployeeOnboardingPage("Paul");
|
||||
await onboardingPage.expectEmployeeName("Paul");
|
||||
await onboardingPage.fillPersonalDetailsCardAndSave(
|
||||
"123-45-6789",
|
||||
"123 Elm Street",
|
||||
);
|
||||
await onboardingPage.fillUploadCardAndSave(
|
||||
"./public/jazz-logo-low-res.jpg",
|
||||
);
|
||||
|
||||
// invitee cannot confirm the onboarding completion
|
||||
expect(onboardingPage.finalConfirmationButton.isDisabled()).toBeTruthy();
|
||||
|
||||
// final confirmation step by admin
|
||||
await onboardingPage.logout();
|
||||
await login({ page, userName: adminUser, loginAs: true });
|
||||
|
||||
await homePage.expectEmployee(["Paul", "admin"]);
|
||||
await homePage.navigateToEmployeeOnboardingPage("Paul");
|
||||
await scrollToBottom(page);
|
||||
await onboardingPage.finalConfirmationButton.click();
|
||||
await onboardingPage.backButton.click();
|
||||
await homePage.expectOnboardingCompleteForEmployee("Paul");
|
||||
});
|
||||
});
|
||||
92
examples/onboarding/tests/pages/EmployeeOnboardingPage.ts
Normal file
92
examples/onboarding/tests/pages/EmployeeOnboardingPage.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Locator, Page, expect } from "@playwright/test";
|
||||
|
||||
export class EmployeeOnboardingPage {
|
||||
readonly page: Page;
|
||||
readonly shareButton: Locator;
|
||||
readonly backButton: Locator;
|
||||
readonly logoutButton: Locator;
|
||||
readonly finalConfirmationButton: Locator;
|
||||
readonly fileInput: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.shareButton = page.getByRole("button", {
|
||||
name: /invite a co-worker/i,
|
||||
});
|
||||
this.backButton = page.getByRole("button", {
|
||||
name: /back/i,
|
||||
});
|
||||
this.logoutButton = page.getByRole("button", {
|
||||
name: /log out/i,
|
||||
});
|
||||
this.finalConfirmationButton = this.page.getByRole("button", {
|
||||
name: /confirmation by admin/i,
|
||||
});
|
||||
|
||||
this.fileInput = page.getByTestId("file-upload");
|
||||
}
|
||||
|
||||
async uploadFile(value: string) {
|
||||
// Start waiting for file chooser before clicking. Note no await.
|
||||
const fileChooserPromise = this.page.waitForEvent("filechooser");
|
||||
|
||||
await this.fileInput.click();
|
||||
|
||||
const fileChooser = await fileChooserPromise;
|
||||
await fileChooser.setFiles(value);
|
||||
}
|
||||
|
||||
async expectEmployeeName(name: string) {
|
||||
await expect(
|
||||
this.page.getByRole("heading", {
|
||||
name: name,
|
||||
}),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
async fillPersonalDetailsCardAndSave(ssn: string, address: string) {
|
||||
const nextStepButton = this.page.getByRole("button", {
|
||||
name: /upload step >/i,
|
||||
});
|
||||
await expect(nextStepButton).toBeDisabled();
|
||||
|
||||
const ssnInput = this.page.getByLabel(/Social Security Number/i);
|
||||
await ssnInput.fill(ssn);
|
||||
|
||||
const addressInput = this.page.getByLabel(/Address/i);
|
||||
await addressInput.fill(address);
|
||||
|
||||
// save and hide the button
|
||||
await expect(nextStepButton).toBeEnabled();
|
||||
await nextStepButton.click();
|
||||
await expect(nextStepButton).not.toBeVisible();
|
||||
}
|
||||
|
||||
async fillUploadCardAndSave(file: string) {
|
||||
const nextStepButton = this.page.getByRole("button", {
|
||||
name: /confirmation step >/i,
|
||||
});
|
||||
await expect(nextStepButton).toBeDisabled();
|
||||
|
||||
await this.uploadFile(file);
|
||||
await expect(nextStepButton).toBeEnabled();
|
||||
await nextStepButton.click();
|
||||
await expect(nextStepButton).not.toBeVisible();
|
||||
}
|
||||
|
||||
async getShareLink() {
|
||||
await this.shareButton.click();
|
||||
|
||||
const inviteUrl = await this.page.evaluate(() =>
|
||||
navigator.clipboard.readText(),
|
||||
);
|
||||
|
||||
expect(inviteUrl).toBeTruthy();
|
||||
|
||||
return inviteUrl;
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this.logoutButton.click();
|
||||
}
|
||||
}
|
||||
66
examples/onboarding/tests/pages/HomePage.ts
Normal file
66
examples/onboarding/tests/pages/HomePage.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Locator, Page, expect } from "@playwright/test";
|
||||
import { NewEmployeePage } from "./NewEmployeePage";
|
||||
|
||||
export class HomePage {
|
||||
readonly page: Page;
|
||||
readonly newEmployeeLink: Locator;
|
||||
readonly logoutButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.newEmployeeLink = page.getByRole("button", {
|
||||
name: "Add New Employee",
|
||||
});
|
||||
this.logoutButton = page.getByRole("button", {
|
||||
name: "Log Out",
|
||||
});
|
||||
}
|
||||
|
||||
async expectEmployee([name, role]: [string, string]) {
|
||||
const liElement = this.page.locator(
|
||||
`li:has-text("${name}"):has-text("${role}")`,
|
||||
);
|
||||
await expect(liElement).toBeVisible();
|
||||
}
|
||||
|
||||
async deleteEmployee(name: string) {
|
||||
const liElement = this.page.locator(`li:has-text("${name}")`);
|
||||
const deleteIcon = liElement.locator('span:has-text("🗑")');
|
||||
await deleteIcon.click();
|
||||
}
|
||||
|
||||
async expectOnboardingCompleteForEmployee(name: string) {
|
||||
const liElement = this.page.locator(`li:has-text("${name}")`);
|
||||
const completionIcon = liElement.locator('span:has-text("✅")');
|
||||
await expect(completionIcon).toBeVisible();
|
||||
}
|
||||
|
||||
async expectEmployeeDeleted(name: string) {
|
||||
const liElement = this.page.locator(`li:has-text("${name}")`);
|
||||
await expect(liElement).not.toBeVisible();
|
||||
}
|
||||
|
||||
async navigateToEmployeeOnboardingPage(name: string) {
|
||||
await this.page
|
||||
.getByRole("link", {
|
||||
name,
|
||||
})
|
||||
.click();
|
||||
}
|
||||
|
||||
async navigateToNewEmployee() {
|
||||
await this.newEmployeeLink.click();
|
||||
}
|
||||
|
||||
async createEmployee(name: string) {
|
||||
await this.navigateToNewEmployee();
|
||||
const newEmployeePage = new NewEmployeePage(this.page);
|
||||
|
||||
await newEmployeePage.fillEmployeeName(name);
|
||||
await newEmployeePage.submit();
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this.logoutButton.click();
|
||||
}
|
||||
}
|
||||
40
examples/onboarding/tests/pages/LoginPage.ts
Normal file
40
examples/onboarding/tests/pages/LoginPage.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Locator, Page, expect } from "@playwright/test";
|
||||
|
||||
export class LoginPage {
|
||||
readonly page: Page;
|
||||
readonly usernameInput: Locator;
|
||||
readonly signupButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.usernameInput = page.getByRole("textbox");
|
||||
this.signupButton = page.getByRole("button", {
|
||||
name: "Sign up",
|
||||
});
|
||||
}
|
||||
|
||||
async goto(url: string) {
|
||||
await this.page.goto(url);
|
||||
}
|
||||
|
||||
async fillUsername(value: string) {
|
||||
await this.usernameInput.clear();
|
||||
await this.usernameInput.fill(value);
|
||||
}
|
||||
|
||||
async loginAs(value: string) {
|
||||
await this.page
|
||||
.getByRole("button", {
|
||||
name: value,
|
||||
})
|
||||
.click();
|
||||
}
|
||||
|
||||
async signup() {
|
||||
await this.signupButton.click();
|
||||
}
|
||||
|
||||
async expectLoaded() {
|
||||
await expect(this.signupButton).toBeVisible();
|
||||
}
|
||||
}
|
||||
24
examples/onboarding/tests/pages/NewEmployeePage.ts
Normal file
24
examples/onboarding/tests/pages/NewEmployeePage.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Locator, Page } from "@playwright/test";
|
||||
|
||||
export class NewEmployeePage {
|
||||
readonly page: Page;
|
||||
readonly employeeNameInput: Locator;
|
||||
readonly submitButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
this.employeeNameInput = page.getByLabel(/employee name/i);
|
||||
this.submitButton = page.getByRole("button", {
|
||||
name: /create employee/i,
|
||||
});
|
||||
}
|
||||
|
||||
async fillEmployeeName(value: string) {
|
||||
await this.employeeNameInput.clear();
|
||||
await this.employeeNameInput.fill(value);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await this.submitButton.click();
|
||||
}
|
||||
}
|
||||
27
examples/onboarding/tsconfig.json
Normal file
27
examples/onboarding/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
11
examples/onboarding/tsconfig.node.json
Normal file
11
examples/onboarding/tsconfig.node.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
17
examples/onboarding/vite.config.ts
Normal file
17
examples/onboarding/vite.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import path from "path";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import { defineConfig } from "vite";
|
||||
import topLevelAwait from "vite-plugin-top-level-await";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), topLevelAwait()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
minify: false,
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,37 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
|
||||
## 0.0.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
|
||||
## 0.0.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
|
||||
## 0.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.20",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,8 +12,8 @@
|
||||
"clean-install": "rm -rf node_modules pnpm-lock.yaml && pnpm install"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-react": "workspace:0.8.18",
|
||||
"jazz-tools": "workspace:0.8.18",
|
||||
"jazz-react": "workspace:0.8.22",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.41.5",
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.118
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
- jazz-browser-media-images@0.8.22
|
||||
|
||||
## 0.0.117
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
- jazz-browser-media-images@0.8.21
|
||||
|
||||
## 0.0.116
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
- jazz-browser-media-images@0.8.20
|
||||
|
||||
## 0.0.115
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
- jazz-browser-media-images@0.8.19
|
||||
|
||||
## 0.0.114
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Jazz Rate-My-Pet List Example
|
||||
|
||||
Live version: https://example-pets.jazz.tools
|
||||
Live version: https://pets-demo.jazz.tools/
|
||||
|
||||
## Installing & running the example locally
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.114",
|
||||
"version": "0.0.118",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -19,9 +19,9 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-browser-media-images": "workspace:0.8.18",
|
||||
"jazz-react": "workspace:0.8.18",
|
||||
"jazz-tools": "workspace:0.8.18",
|
||||
"jazz-browser-media-images": "workspace:0.8.22",
|
||||
"jazz-react": "workspace:0.8.22",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
@@ -41,7 +41,7 @@
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"is-ci": "^3.0.1",
|
||||
"jazz-run": "workspace:0.8.18",
|
||||
"jazz-run": "workspace:0.8.21",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3",
|
||||
|
||||
1
examples/todo-vue/.gitignore
vendored
Normal file
1
examples/todo-vue/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
dist
|
||||
11
examples/todo-vue/CHANGELOG.md
Normal file
11
examples/todo-vue/CHANGELOG.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a734530: fix useCoState reactivity
|
||||
- Updated dependencies [f6bc8af]
|
||||
- Updated dependencies [a734530]
|
||||
- jazz-browser@0.8.22
|
||||
- jazz-vue@0.8.12
|
||||
39
examples/todo-vue/README.md
Normal file
39
examples/todo-vue/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# todo-vue
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
pnpm lint
|
||||
```
|
||||
1
examples/todo-vue/env.d.ts
vendored
Normal file
1
examples/todo-vue/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
13
examples/todo-vue/index.html
Normal file
13
examples/todo-vue/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
39
examples/todo-vue/package.json
Normal file
39
examples/todo-vue/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build-type-check": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build": "vite build",
|
||||
"type-check": "vue-tsc --build --force",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"jazz-browser": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"jazz-vue": "workspace:*",
|
||||
"vue": "^3.5.11",
|
||||
"vue-router": "^4.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/node": "^22.5.1",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "3.3.2",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.10",
|
||||
"vite-plugin-vue-devtools": "^7.4.6",
|
||||
"vue-tsc": "^2.1.6"
|
||||
}
|
||||
}
|
||||
6
examples/todo-vue/postcss.config.js
Normal file
6
examples/todo-vue/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
examples/todo-vue/public/favicon.ico
Normal file
BIN
examples/todo-vue/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
69
examples/todo-vue/src/App.vue
Normal file
69
examples/todo-vue/src/App.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<header v-if="me" class="app-header">
|
||||
<h1>Todo App</h1>
|
||||
<div class="user-section">
|
||||
<span>{{ me.profile?.name }}</span>
|
||||
<button class="logout-btn" @click="logOut">Log out</button>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<router-view />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
background-color: #fff;
|
||||
padding: 1rem 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.user-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background-color: transparent;
|
||||
border: 1px solid #dc3545;
|
||||
color: #dc3545;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAccount } from "./main";
|
||||
|
||||
const { me, logOut } = useAccount();
|
||||
</script>
|
||||
74
examples/todo-vue/src/assets/base.css
Normal file
74
examples/todo-vue/src/assets/base.css
Normal file
@@ -0,0 +1,74 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
1
examples/todo-vue/src/assets/main.css
Normal file
1
examples/todo-vue/src/assets/main.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "./base.css";
|
||||
42
examples/todo-vue/src/main.ts
Normal file
42
examples/todo-vue/src/main.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import "./assets/main.css";
|
||||
import { DemoAuthBasicUI, createJazzVueApp, useDemoAuth } from "jazz-vue";
|
||||
import { createApp, defineComponent, h } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { ToDoAccount } from "./schema";
|
||||
|
||||
const Jazz = createJazzVueApp<ToDoAccount>({ AccountSchema: ToDoAccount });
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
const { JazzProvider } = Jazz;
|
||||
|
||||
const RootComponent = defineComponent({
|
||||
name: "RootComponent",
|
||||
setup() {
|
||||
const { authMethod, state } = useDemoAuth();
|
||||
|
||||
return () => [
|
||||
h(
|
||||
JazzProvider,
|
||||
{
|
||||
auth: authMethod.value,
|
||||
peer: "wss://mesh.jazz.tools/?key=vue-todo-example-jazz@gcmp.io",
|
||||
},
|
||||
{
|
||||
default: () => h(App),
|
||||
},
|
||||
),
|
||||
|
||||
state.state !== "signedIn" &&
|
||||
h(DemoAuthBasicUI, {
|
||||
appName: "Jazz Vue Todo",
|
||||
state,
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
const app = createApp(RootComponent);
|
||||
|
||||
app.use(router);
|
||||
|
||||
app.mount("#app");
|
||||
15
examples/todo-vue/src/router/index.ts
Normal file
15
examples/todo-vue/src/router/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import Home from "../views/HomeView.vue";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
||||
52
examples/todo-vue/src/schema.ts
Normal file
52
examples/todo-vue/src/schema.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Account, CoList, CoMap, Group, Profile, co } from "jazz-tools";
|
||||
|
||||
export class ToDoItem extends CoMap {
|
||||
name = co.string;
|
||||
completed = co.boolean;
|
||||
}
|
||||
|
||||
export class ToDoList extends CoList.Of(co.ref(ToDoItem)) {}
|
||||
|
||||
export class Folder extends CoMap {
|
||||
name = co.string;
|
||||
items = co.ref(ToDoList);
|
||||
}
|
||||
|
||||
export class FolderList extends CoList.Of(co.ref(Folder)) {}
|
||||
|
||||
export class ToDoAccountRoot extends CoMap {
|
||||
folders = co.ref(FolderList);
|
||||
}
|
||||
|
||||
export class ToDoAccount extends Account {
|
||||
profile = co.ref(Profile);
|
||||
root = co.ref(ToDoAccountRoot);
|
||||
|
||||
migrate(this: ToDoAccount, creationProps?: { name: string }) {
|
||||
super.migrate(creationProps);
|
||||
if (!this._refs.root) {
|
||||
const group = Group.create({ owner: this });
|
||||
const exampleTodo = ToDoItem.create(
|
||||
{ name: "Example todo", completed: false },
|
||||
{ owner: group },
|
||||
);
|
||||
|
||||
const defaultFolder = Folder.create(
|
||||
{
|
||||
name: "Default",
|
||||
items: ToDoList.create([exampleTodo], { owner: group }),
|
||||
},
|
||||
{ owner: group },
|
||||
);
|
||||
|
||||
this.root = ToDoAccountRoot.create(
|
||||
{
|
||||
folders: FolderList.create([defaultFolder], {
|
||||
owner: this,
|
||||
}),
|
||||
},
|
||||
{ owner: this },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
285
examples/todo-vue/src/views/HomeView.vue
Normal file
285
examples/todo-vue/src/views/HomeView.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<div class="todo-container">
|
||||
<div class="folders">
|
||||
<div class="section-header">
|
||||
<h2>Folders</h2>
|
||||
<div class="new-folder">
|
||||
<input
|
||||
v-model="newFolderName"
|
||||
placeholder="New folder name"
|
||||
class="input"
|
||||
/>
|
||||
<button class="btn btn-primary" @click="createFolder">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="folder-list">
|
||||
<div
|
||||
v-for="folder in folders"
|
||||
:key="folder?.id"
|
||||
:class="['folder-item', { active: selectedFolder?.id === folder?.id }]"
|
||||
@click="selectFolder(folder)"
|
||||
>
|
||||
<span class="folder-name">{{ folder?.name }}</span>
|
||||
<button class="btn btn-icon" @click.stop="deleteFolder(folder?.id)">
|
||||
<span class="material-icons">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="todos" v-if="selectedFolder">
|
||||
<div class="section-header">
|
||||
<h2>{{ selectedFolder?.name }}</h2>
|
||||
<div class="new-todo">
|
||||
<input
|
||||
v-model="newTodoTitle"
|
||||
placeholder="Add a new task"
|
||||
class="input"
|
||||
/>
|
||||
<button class="btn btn-primary" @click="createTodo">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="todo-list">
|
||||
<template v-if="selectedFolder?.items?.length">
|
||||
<div v-for="todo in selectedFolder.items" :key="todo?.id" class="todo-item">
|
||||
<label class="todo-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="todo?.completed"
|
||||
@change="toggleTodo(todo)"
|
||||
/>
|
||||
<span :class="{ completed: todo?.completed }">{{ todo?.name }}</span>
|
||||
</label>
|
||||
<button class="btn btn-icon" @click="deleteTodo(todo?.id)">
|
||||
<span class="material-icons">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Group, type ID } from "jazz-tools";
|
||||
import { ref, toRaw, watch } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useAccount, useCoState } from "../main";
|
||||
import { Folder, FolderList, ToDoItem, ToDoList } from "../schema";
|
||||
|
||||
const { me } = useAccount();
|
||||
|
||||
const computedFoldersId = computed(() => me.value?.root?.folders?.id);
|
||||
const folders = useCoState(FolderList, computedFoldersId, [{ items: [{}] }]);
|
||||
|
||||
const selectedFolder = ref<Folder>();
|
||||
const newFolderName = ref("");
|
||||
const newTodoTitle = ref("");
|
||||
|
||||
// Select the first folder if none is selected
|
||||
watch(folders, (loadedFolders) => {
|
||||
if (selectedFolder.value) return;
|
||||
selectedFolder.value = loadedFolders?.[0] || undefined;
|
||||
});
|
||||
|
||||
const selectFolder = (folder: Folder) => {
|
||||
selectedFolder.value = folder;
|
||||
};
|
||||
|
||||
const createFolder = () => {
|
||||
if (!newFolderName.value.trim()) return;
|
||||
|
||||
// Create a group where the folder will be owned by the current user
|
||||
const group = Group.create({ owner: me.value });
|
||||
|
||||
// Create the folder
|
||||
const newFolder = Folder.create(
|
||||
{
|
||||
name: newFolderName.value,
|
||||
items: ToDoList.create([], { owner: group }),
|
||||
},
|
||||
{ owner: group },
|
||||
);
|
||||
|
||||
// Add the folder to the list of folders. This change will be synced to all connected clients.
|
||||
folders.value?.push(newFolder);
|
||||
newFolderName.value = "";
|
||||
};
|
||||
|
||||
const deleteFolder = (folderId: ID<Folder> | undefined) => {
|
||||
if (!folders.value || !folderId) return;
|
||||
|
||||
const index = folders.value.findIndex((f) => f.id === folderId);
|
||||
if (index !== -1) {
|
||||
// Remove the folder from the list. This change will be synced to all connected clients.
|
||||
folders.value.splice(index, 1);
|
||||
}
|
||||
|
||||
if (selectedFolder.value?.id === folderId) {
|
||||
selectedFolder.value = folders.value[0] || null;
|
||||
}
|
||||
};
|
||||
|
||||
// Todo handlers
|
||||
const createTodo = () => {
|
||||
if (!newTodoTitle.value.trim() || !selectedFolder.value) return;
|
||||
const group = Group.create({ owner: me.value });
|
||||
const newTodo = ToDoItem.create(
|
||||
{
|
||||
name: newTodoTitle.value,
|
||||
completed: false,
|
||||
},
|
||||
{ owner: group },
|
||||
);
|
||||
|
||||
// Add the todo to the list of todos. This change will be synced to all connected clients.
|
||||
// toRaw is used to get the plain object from the reactive object, because the plain object is already proxied by Jazz.
|
||||
// otherwise it will throw an error.
|
||||
toRaw(selectedFolder.value)?.items?.push(newTodo);
|
||||
newTodoTitle.value = "";
|
||||
};
|
||||
|
||||
const deleteTodo = (todoId: ID<ToDoItem> | undefined) => {
|
||||
if (!selectedFolder.value?.items || !todoId) return;
|
||||
|
||||
const index = toRaw(selectedFolder.value)?.items?.findIndex(
|
||||
(t) => t?.id === todoId,
|
||||
);
|
||||
if (index !== -1 && index !== undefined) {
|
||||
toRaw(selectedFolder.value)?.items?.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleTodo = (todo: ToDoItem | null) => {
|
||||
if (!todo) return;
|
||||
todo.completed = !todo.completed;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.todo-container {
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1fr;
|
||||
gap: 2rem;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #2c3e50;
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.folders {
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
.folder-list,
|
||||
.todo-list {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.folder-item {
|
||||
padding: 0.75rem 1rem;
|
||||
margin: 0.25rem 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.folder-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.folder-item.active {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.todo-item {
|
||||
padding: 0.75rem 1rem;
|
||||
margin: 0.25rem 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.todo-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.todo-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
cursor: pointer;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.completed {
|
||||
text-decoration: line-through;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.input {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
padding: 0.25rem;
|
||||
background: transparent;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background-color: #fee2e2;
|
||||
}
|
||||
|
||||
.new-folder,
|
||||
.new-todo {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.folder-name {
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
8
examples/todo-vue/tailwind.config.js
Normal file
8
examples/todo-vue/tailwind.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
purge: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
14
examples/todo-vue/tsconfig.app.json
Normal file
14
examples/todo-vue/tsconfig.app.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
11
examples/todo-vue/tsconfig.json
Normal file
11
examples/todo-vue/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
examples/todo-vue/tsconfig.node.json
Normal file
19
examples/todo-vue/tsconfig.node.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
16
examples/todo-vue/vite.config.ts
Normal file
16
examples/todo-vue/vite.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { URL, fileURLToPath } from "node:url";
|
||||
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import { defineConfig } from "vite";
|
||||
import vueDevTools from "vite-plugin-vue-devtools";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx(), vueDevTools()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,37 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.117
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [59cc64d]
|
||||
- jazz-react@0.8.22
|
||||
|
||||
## 0.0.116
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [149ca97]
|
||||
- jazz-tools@0.8.21
|
||||
- jazz-react@0.8.21
|
||||
|
||||
## 0.0.115
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [dd9b13f]
|
||||
- Updated dependencies [a69ed0b]
|
||||
- Updated dependencies [3ef3ff3]
|
||||
- Updated dependencies [c6931b8]
|
||||
- jazz-react@0.8.20
|
||||
|
||||
## 0.0.114
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.8.19
|
||||
- jazz-tools@0.8.19
|
||||
|
||||
## 0.0.113
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Jazz Todo List Example
|
||||
|
||||
Live version: https://example-todo.jazz.tools
|
||||
Live version: https://todo-demo.jazz.tools/
|
||||
|
||||
## Installing & running the example locally
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.113",
|
||||
"version": "0.0.117",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -16,8 +16,8 @@
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"jazz-react": "workspace:0.8.18",
|
||||
"jazz-tools": "workspace:0.8.18",
|
||||
"jazz-react": "workspace:0.8.22",
|
||||
"jazz-tools": "workspace:0.8.21",
|
||||
"lucide-react": "^0.274.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@evilmartians/harmony": "^1.0.0",
|
||||
"@headlessui/react": "^2.2.0",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.436.0",
|
||||
|
||||
@@ -4,7 +4,7 @@ import Link from "next/link";
|
||||
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
variant?: "primary" | "secondary" | "tertiary";
|
||||
size?: "md" | "lg";
|
||||
size?: "sm" | "md" | "lg";
|
||||
href?: string;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ export function Button(props: ButtonProps) {
|
||||
} = props;
|
||||
|
||||
const sizeClasses = {
|
||||
md: "py-2 px-3",
|
||||
sm: "text-sm py-1 px-2",
|
||||
md: "py-1.5 px-3",
|
||||
lg: "md:text-lg py-2 px-3 md:px-8 md:py-3",
|
||||
};
|
||||
|
||||
@@ -34,7 +35,7 @@ export function Button(props: ButtonProps) {
|
||||
|
||||
const classNames = clsx(
|
||||
className,
|
||||
"inline-block rounded-lg text-center transition-colors",
|
||||
"inline-flex items-center gap-2 rounded-lg text-center transition-colors",
|
||||
sizeClasses[size],
|
||||
variantClasses[variant],
|
||||
disabled && "opacity-50 cursor-not-allowed pointer-events-none",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export function GcmpLogo({
|
||||
monochrome,
|
||||
className,
|
||||
@@ -10,7 +12,7 @@ export function GcmpLogo({
|
||||
viewBox="0 0 557 164"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={className}
|
||||
className={clsx(className, "text-black dark:text-white")}
|
||||
>
|
||||
<g clipPath="url(#clip0_12197_1172)">
|
||||
<path
|
||||
|
||||
@@ -13,7 +13,7 @@ export function Prose({
|
||||
const sizeClassName = {
|
||||
sm: "prose-sm",
|
||||
md: "",
|
||||
lg: "prose-xl",
|
||||
lg: "prose-lg lg:prose-xl",
|
||||
}[size];
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,30 +1,115 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
CloseButton,
|
||||
Popover,
|
||||
PopoverButton,
|
||||
PopoverGroup,
|
||||
PopoverPanel,
|
||||
} from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import { MenuIcon, XIcon } from "lucide-react";
|
||||
import { ChevronDownIcon, MenuIcon, XIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { BreadCrumb } from "../molecules/Breadcrumb";
|
||||
import { ThemeToggle } from "../molecules/ThemeToggle";
|
||||
|
||||
export function Nav({
|
||||
mainLogo,
|
||||
items,
|
||||
docNav,
|
||||
cta,
|
||||
}: {
|
||||
type NavItemProps = {
|
||||
href: string;
|
||||
icon?: ReactNode;
|
||||
title: string;
|
||||
firstOnRight?: boolean;
|
||||
newTab?: boolean;
|
||||
items?: NavItemProps[];
|
||||
description?: string;
|
||||
};
|
||||
|
||||
type NavProps = {
|
||||
mainLogo: ReactNode;
|
||||
items: {
|
||||
href: string;
|
||||
icon?: ReactNode;
|
||||
title: string;
|
||||
firstOnRight?: boolean;
|
||||
newTab?: boolean;
|
||||
}[];
|
||||
items: NavItemProps[];
|
||||
docNav?: ReactNode;
|
||||
cta?: ReactNode;
|
||||
};
|
||||
|
||||
function NavItem({
|
||||
item,
|
||||
className,
|
||||
}: {
|
||||
item: NavItemProps;
|
||||
className?: string;
|
||||
}) {
|
||||
const { href, icon, title, items, firstOnRight } = item;
|
||||
|
||||
const path = usePathname();
|
||||
|
||||
if (!items?.length) {
|
||||
if (item.icon) {
|
||||
return (
|
||||
<NavLinkLogo className="px-3" {...item}>
|
||||
{icon}
|
||||
<span className="sr-only">{title}</span>
|
||||
</NavLinkLogo>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
className={clsx(
|
||||
className,
|
||||
"text-sm px-2 lg:px-4 py-3 ",
|
||||
firstOnRight && "ml-auto",
|
||||
path === href ? "text-black dark:text-white" : "",
|
||||
)}
|
||||
{...item}
|
||||
>
|
||||
{title}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover className={clsx("relative", className, firstOnRight && "ml-auto")}>
|
||||
<PopoverButton
|
||||
className={clsx(
|
||||
"flex items-center gap-1.5 text-sm px-2 lg:px-4 py-3 max-sm:w-full text-stone-600 dark:text-stone-400 hover:text-black dark:hover:text-white transition-colors hover:transition-none focus-visible:outline-none",
|
||||
path === href ? "text-black dark:text-white" : "",
|
||||
)}
|
||||
>
|
||||
<span>{title}</span>
|
||||
<ChevronDownIcon aria-hidden="true" className="size-4" />
|
||||
</PopoverButton>
|
||||
|
||||
<PopoverPanel
|
||||
transition
|
||||
className="absolute left-1/2 -translate-x-1/2 z-10 flex w-screen max-w-[24rem] mt-5 transition data-[closed]:translate-y-1 data-[closed]:opacity-0 data-[enter]:duration-200 data-[leave]:duration-150 data-[enter]:ease-out data-[leave]:ease-in"
|
||||
>
|
||||
<div className="flex-auto overflow-hidden rounded-lg ring-1 ring-stone-300/60 bg-white/90 backdrop-blur-lg shadow-lg dark:ring-stone-800/50 dark:bg-stone-925/90">
|
||||
<div className="p-3 grid">
|
||||
{items.map(({ href, title, description, icon }) => (
|
||||
<CloseButton
|
||||
className="p-3 rounded-md flex gap-3 hover:bg-stone-100/80 dark:hover:bg-stone-900/80 transition-colors"
|
||||
href={href}
|
||||
aria-label={title}
|
||||
as={Link}
|
||||
>
|
||||
{icon}
|
||||
<div className="grid gap-1.5 mt-px">
|
||||
<p className="text-sm font-medium text-stone-900 dark:text-white">
|
||||
{title}
|
||||
</p>
|
||||
<p className="text-sm leading-relaxed">{description}</p>
|
||||
</div>
|
||||
</CloseButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export function MobileNav({ mainLogo, items, docNav, cta }: NavProps) {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
@@ -41,44 +126,6 @@ export function Nav({
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
className={[
|
||||
clsx(
|
||||
"hidden md:flex sticky left-0 right-0 top-0 w-full justify-center",
|
||||
"bg-white dark:bg-stone-950 border-b",
|
||||
"z-50",
|
||||
),
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="flex flex-wrap items-center max-sm:justify-between md:gap-2 container w-full">
|
||||
<div className="flex items-center flex-shrink">
|
||||
<NavLinkLogo prominent href="/" className="-ml-2">
|
||||
{mainLogo}
|
||||
</NavLinkLogo>
|
||||
</div>
|
||||
{items.map((item, i) =>
|
||||
"icon" in item ? (
|
||||
<NavLinkLogo key={i} href={item.href} newTab={item.newTab}>
|
||||
{item.icon}
|
||||
</NavLinkLogo>
|
||||
) : (
|
||||
<NavLink
|
||||
key={i}
|
||||
href={item.href}
|
||||
newTab={item.newTab}
|
||||
className={clsx(
|
||||
"max-sm:w-full",
|
||||
item.firstOnRight ? "md:ml-auto" : "",
|
||||
)}
|
||||
>
|
||||
{item.title}
|
||||
</NavLink>
|
||||
),
|
||||
)}
|
||||
|
||||
{cta}
|
||||
</div>
|
||||
</nav>
|
||||
<div className="md:hidden px-4 flex items-center self-stretch dark:text-white">
|
||||
<NavLinkLogo prominent href="/" className="mr-auto">
|
||||
{mainLogo}
|
||||
@@ -137,12 +184,12 @@ export function Nav({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-4 justify-end -mb-2">
|
||||
{items
|
||||
<div className="flex flex-wrap justify-end py-2 gap-x-3 gap-y-1 border-b">
|
||||
{[{ title: "Home", href: "/" }, ...items]
|
||||
.filter((item) => !("icon" in item))
|
||||
.slice(0, 3)
|
||||
.map((item, i) => (
|
||||
<NavLink
|
||||
className="p-1 text-sm"
|
||||
key={i}
|
||||
href={item.href}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
@@ -152,47 +199,8 @@ export function Nav({
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 justify-end border-b">
|
||||
{items
|
||||
.filter((item) => !("icon" in item))
|
||||
.slice(3)
|
||||
.map((item, i) => (
|
||||
<NavLink
|
||||
key={i}
|
||||
href={item.href}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
newTab={item.newTab}
|
||||
className={clsx("")}
|
||||
>
|
||||
{item.title}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center self-stretch justify-between">
|
||||
{/* <input
|
||||
type="text"
|
||||
className={clsx(
|
||||
menuOpen || searchOpen ? "" : "hidden",
|
||||
"ml-2 border px-2 py-1 rounded w-full"
|
||||
)}
|
||||
placeholder="Search docs..."
|
||||
ref={searchRef}
|
||||
/> */}
|
||||
{/* <button
|
||||
className="flex p-3 rounded-xl"
|
||||
onClick={() => {
|
||||
setSearchOpen(true);
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
if (!e.currentTarget.value) {
|
||||
setSearchOpen(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SearchIcon className="" />
|
||||
</button> */}
|
||||
{(menuOpen || searchOpen) && <ThemeToggle className="p-3" />}
|
||||
<button
|
||||
className="flex gap-2 p-3 rounded-xl items-center"
|
||||
@@ -236,11 +244,8 @@ function NavLink({
|
||||
<Link
|
||||
href={href}
|
||||
className={clsx(
|
||||
"px-2 lg:px-4 py-3 text-sm",
|
||||
"text-stone-600 dark:text-stone-400 hover:text-black dark:hover:text-white transition-colors hover:transition-none",
|
||||
className,
|
||||
path === href
|
||||
? "font-medium text-black dark:text-white cursor-default"
|
||||
: "text-stone-600 dark:text-stone-400 hover:text-black dark:hover:text-white transition-colors hover:transition-none",
|
||||
)}
|
||||
onClick={onClick}
|
||||
target={newTab ? "_blank" : undefined}
|
||||
@@ -261,7 +266,6 @@ function NavLinkLogo({
|
||||
href,
|
||||
className,
|
||||
children,
|
||||
prominent,
|
||||
onClick,
|
||||
newTab,
|
||||
}: {
|
||||
@@ -272,19 +276,11 @@ function NavLinkLogo({
|
||||
onClick?: () => void;
|
||||
newTab?: boolean;
|
||||
}) {
|
||||
const path = usePathname();
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className={clsx(
|
||||
"max-sm:px-4 px-2 lg:px-3 py-3 transition-opacity hover:transition-none",
|
||||
path === href
|
||||
? "cursor-default"
|
||||
: prominent
|
||||
? "hover:opacity-50"
|
||||
: "opacity-60 hover:opacity-100",
|
||||
"text-black dark:text-white",
|
||||
"py-3 hover:text-stone-900 dark:hover:text-white",
|
||||
className,
|
||||
)}
|
||||
onClick={onClick}
|
||||
@@ -294,3 +290,29 @@ function NavLinkLogo({
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function Nav(props: NavProps) {
|
||||
const { mainLogo, items, docNav, cta } = props;
|
||||
return (
|
||||
<>
|
||||
<div className="w-full border-b py-2 sticky top-0 z-50 bg-white dark:bg-stone-950 hidden md:block">
|
||||
<PopoverGroup className="flex flex-wrap items-center max-sm:justify-between md:gap-2 container w-full">
|
||||
<Link href="/" className="flex items-center">
|
||||
{mainLogo}
|
||||
</Link>
|
||||
|
||||
{items.map((item, i) => (
|
||||
<NavItem
|
||||
key={i}
|
||||
item={item}
|
||||
className={i == items.length - 1 ? "mr-3" : ""}
|
||||
/>
|
||||
))}
|
||||
|
||||
{cta}
|
||||
</PopoverGroup>
|
||||
</div>
|
||||
<MobileNav {...props} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,56 +60,6 @@ const config = {
|
||||
fontSize: {
|
||||
"2xs": ["0.75rem", { lineHeight: "1.25rem" }],
|
||||
},
|
||||
// shadcn-ui
|
||||
colors: {
|
||||
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))",
|
||||
},
|
||||
},
|
||||
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",
|
||||
},
|
||||
container: {
|
||||
center: true,
|
||||
padding: {
|
||||
@@ -228,6 +178,9 @@ const config = {
|
||||
".dark *": {
|
||||
borderColor: "var(--gcmp-invert-border-color)",
|
||||
},
|
||||
"*:focus": {
|
||||
outline: "none",
|
||||
},
|
||||
}),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export function GardenLogo(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="175"
|
||||
viewBox="0 0 540 164"
|
||||
fill="none"
|
||||
{...props}
|
||||
className={clsx(props.className, "text-black dark:text-white")}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_3069_1)">
|
||||
<path
|
||||
d="M226.005 55.6V60.935C221.77 56.535 215.885 53.95 208.515 53.95C191.795 53.95 181.4 67.315 181.4 85.245C181.4 103.065 191.74 116.65 208.295 116.65C215.005 116.65 220.45 114.56 224.63 110.875V116.485C224.795 126.715 219.075 131.995 209.285 131.995C204.005 131.995 198.835 129.74 196.25 124.68L185.635 130.07C190.475 138.54 199.605 143.05 209.175 143.05C223.035 143.05 232.935 137.44 235.41 125.395C236.015 122.59 236.18 119.785 236.18 116.595V55.6H226.005ZM209.89 106.255C198.835 106.255 193.61 97.015 193.61 85.245C193.61 73.585 198.835 64.345 210.275 64.345C221.11 64.345 226.005 72.925 226.005 85.245C226.005 97.565 221.22 106.255 209.89 106.255ZM291.829 65.28C288.144 57.47 279.894 53.95 269.279 53.95C255.529 53.95 247.279 60.44 244.419 70.615L255.144 73.915C257.179 67.315 262.899 64.565 269.169 64.565C278.464 64.565 282.204 68.415 282.589 76.61C272.964 77.93 263.174 79.195 256.299 81.23C247.114 84.145 242.274 89.865 242.274 98.83C242.274 108.51 249.204 116.65 262.349 116.65C271.974 116.65 278.849 113.35 283.744 106.365V115H293.919V78.425C293.919 73.475 293.699 69.185 291.829 65.28ZM264.384 107.245C257.014 107.245 253.659 103.34 253.659 98.72C253.659 94.045 257.124 91.515 261.854 89.92C266.859 88.435 273.514 87.5 282.479 86.18C282.424 88.93 282.204 92.725 281.269 95.475C279.949 101.25 274.174 107.245 264.384 107.245ZM317.881 58.955C315.626 60.44 313.756 62.585 312.381 65.06V55.6H302.151V115H313.756V85.025C313.756 78.04 315.626 71.77 321.236 68.195C325.196 65.665 330.366 65.335 334.216 66.38V55.6C328.881 54.61 322.336 55.655 317.881 58.955ZM377.551 35.8V59.67C373.426 56.04 368.036 53.95 361.381 53.95C344.661 53.95 334.266 67.315 334.266 85.245C334.266 103.065 344.606 116.65 361.161 116.65C368.641 116.65 374.581 114.01 378.871 109.555V115H389.101V35.8H377.551ZM362.756 106.255C351.701 106.255 346.476 97.015 346.476 85.245C346.476 73.585 351.701 64.345 363.141 64.345C373.976 64.345 378.871 72.925 378.871 85.245C378.871 97.565 374.086 106.255 362.756 106.255ZM407.459 88.545H451.734C452.944 67.26 442.384 53.95 424.234 53.95C406.854 53.95 395.139 66.325 395.139 85.795C395.139 104.165 407.019 116.65 424.784 116.65C436.279 116.65 446.344 110.545 450.964 99.93L439.689 96.355C436.774 102.46 431.384 105.815 424.234 105.815C414.224 105.815 408.394 99.6 407.459 88.545ZM424.674 64.125C433.804 64.125 438.754 69.075 439.964 79.58H407.734C409.219 69.515 414.884 64.125 424.674 64.125ZM486.278 54.005C477.698 54.005 470.988 57.195 466.643 62.695V55.6H456.358V115H468.018V84.09C468.018 70.01 474.838 64.895 483.088 64.895C495.738 64.895 498.103 76.555 498.103 85.795V115H509.763V82C509.763 72.815 506.738 54.005 486.278 54.005Z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M129.988 44.3845C130.172 44.2449 130.355 44.1054 130.539 43.9661L130.658 44.0735C130.434 44.1767 130.211 44.2804 129.988 44.3845ZM39.0525 139.076L45.3628 139.076C49.6432 131.896 54.1741 124.911 58.9404 118.13L57.3623 112.133H52.1219C46.8244 120.321 42.1253 128.999 38.102 138.123L39.0525 139.076Z"
|
||||
fill="#42BB69"
|
||||
></path>
|
||||
<path
|
||||
d="M82.5315 57.3805C79.3982 51.5075 76.846 45.4544 74.8578 39.275C74.5484 39.4079 74.2394 39.5426 73.931 39.679C65.031 43.6161 56.744 48.9482 49.5431 55.3708L49.543 55.3707C49.4471 55.4562 49.3516 55.542 49.2565 55.6281C49.4618 64.8598 51.8486 73.8528 56.4599 81.9511C51.0264 76.0801 46.3449 69.8102 42.4097 63.2312C41.3378 64.7087 40.3643 66.2329 39.4971 67.7939C36.9797 72.3255 35.4076 77.0787 34.8708 81.7821C34.3339 86.4854 34.8428 91.0468 36.3683 95.2058C37.8937 99.3648 40.406 103.04 43.7616 106.022C45.4394 107.512 47.506 108.627 49.8434 109.302C51.024 109.643 52.262 109.868 53.5385 109.977C60.6749 99.2754 68.8435 89.4366 77.8657 80.5635L77.8805 80.5489C93.1057 65.5787 110.761 53.3583 129.988 44.3845C130.172 44.2449 130.355 44.1054 130.539 43.9661L130.658 44.0735C130.434 44.1767 130.211 44.2804 129.988 44.3845C113.587 56.8641 98.2957 70.8841 84.357 86.3032L84.343 86.3187C75.6615 95.9234 67.5049 106.071 59.9322 116.727C60.0716 117.428 60.2633 118.113 60.5069 118.777C61.2696 120.856 62.5258 122.694 64.2036 124.184C67.5592 127.166 71.6924 129.395 76.3672 130.745C81.042 132.095 86.1669 132.54 91.4492 132.053C96.7316 131.566 102.068 130.157 107.154 127.907C110.193 126.563 113.106 124.934 115.837 123.058C104.785 118.874 94.3576 113.318 84.8544 106.375C97.965 112.264 113.15 114.043 128.211 111.832C132.295 107.26 135.812 102.344 138.67 97.2002C139.416 95.8575 140.114 94.5037 140.764 93.141C129.699 91.2029 118.905 88.0557 108.612 83.6429C120.293 86.137 132.678 85.8577 144.642 83.0796C145.66 79.6346 146.372 76.17 146.766 72.7208C147.146 69.3887 147.227 66.0974 147.01 62.877C139.63 61.9724 132.319 60.5435 125.138 58.5674C132.194 59.1483 139.343 58.7882 146.374 57.5667C145.876 54.7067 145.132 51.9198 144.145 49.2292C143.791 48.2641 143.407 47.3138 142.993 46.3792L142.272 44.3173L141.717 36.8508L133.329 36.371L131.011 35.7334C129.961 35.3664 128.892 35.026 127.807 34.7127C123.483 33.4639 118.939 32.6579 114.25 32.3015C113.594 37.0369 113.498 41.8046 113.999 46.5239C112.338 41.7743 111.017 36.9598 110.025 32.1028C107.192 32.0504 104.317 32.1572 101.414 32.4248C94.593 33.0536 87.7207 34.5604 81.0112 36.8888C80.2968 43.7957 80.7762 50.7136 82.5315 57.3805Z"
|
||||
fill="#42BB69"
|
||||
></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3069_1">
|
||||
<rect width="540" height="164" fill="white"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { GardenLogo } from "@/components/GardenLogo";
|
||||
import { TilescapeLogo } from "@/components/TilescapeLogo";
|
||||
import { Button } from "gcmp-design-system/src/app/components/atoms/Button";
|
||||
import { GridCard } from "gcmp-design-system/src/app/components/atoms/GridCard";
|
||||
import { H2, H3 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import { P } from "gcmp-design-system/src/app/components/atoms/Paragraph";
|
||||
import { GcmpLogo } from "gcmp-design-system/src/app/components/atoms/logos/GcmpLogo";
|
||||
import { JazzLogo } from "gcmp-design-system/src/app/components/atoms/logos/JazzLogo";
|
||||
import { GappedGrid } from "gcmp-design-system/src/app/components/molecules/GappedGrid";
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function Products() {
|
||||
|
||||
<div className="mt-8 space-y-3">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<GardenLogo className="h-10 w-auto" />
|
||||
<GcmpLogo className="h-10 w-auto" />
|
||||
<div>
|
||||
<ComingSoon>Coming 2024</ComingSoon>
|
||||
</div>
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
export default function Page() {
|
||||
const examples = [
|
||||
{
|
||||
name: "Chat",
|
||||
slug: "chat",
|
||||
},
|
||||
{
|
||||
name: "Chat (with Clerk for auth)",
|
||||
slug: "chat-clerk",
|
||||
},
|
||||
{
|
||||
name: "Music player",
|
||||
slug: "music-player",
|
||||
},
|
||||
{
|
||||
name: "Pets",
|
||||
slug: "pets",
|
||||
},
|
||||
{
|
||||
name: "Todo",
|
||||
slug: "todo",
|
||||
},
|
||||
{
|
||||
name: "Password manager",
|
||||
slug: "password-manager",
|
||||
},
|
||||
{
|
||||
name: "Book shelf",
|
||||
slug: "book-shelf",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<h1>Example Apps</h1>
|
||||
<ul>
|
||||
{examples.map(({ name, slug }) => (
|
||||
<li key={name}>
|
||||
<a
|
||||
href={`https://github.com/gardencmp/jazz/tree/main/examples/${slug}`}
|
||||
>
|
||||
{name}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user