Compare commits
104 Commits
gio/queue-
...
jazz-react
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eee2330325 | ||
|
|
b83ec05ccc | ||
|
|
386525db48 | ||
|
|
a8809d840c | ||
|
|
005fc1f8c9 | ||
|
|
3129982582 | ||
|
|
8da6f3a897 | ||
|
|
dca9293ae7 | ||
|
|
2b362fd331 | ||
|
|
f03f1b6de8 | ||
|
|
bd57177586 | ||
|
|
9e0e2709a5 | ||
|
|
01b2ab7148 | ||
|
|
44b7d39467 | ||
|
|
5373ee2858 | ||
|
|
b19cab78d3 | ||
|
|
3f86dfce4f | ||
|
|
12f8bfa28f | ||
|
|
53211a4fca | ||
|
|
0a6cd4e9b2 | ||
|
|
69954caee6 | ||
|
|
6a5bd28d07 | ||
|
|
ba58bc3ace | ||
|
|
c3dd099ee1 | ||
|
|
882fd55d69 | ||
|
|
b2fdb8b9e5 | ||
|
|
e879ec981e | ||
|
|
980609ca87 | ||
|
|
71cd7396b7 | ||
|
|
dedc3e277c | ||
|
|
656866729d | ||
|
|
f93dd1f779 | ||
|
|
80d499f002 | ||
|
|
c9a87e52f3 | ||
|
|
3f98d9ab73 | ||
|
|
abb0e8fada | ||
|
|
be2e1f3c61 | ||
|
|
75f20d8176 | ||
|
|
e6bef5275b | ||
|
|
9813db1603 | ||
|
|
a3143f20a9 | ||
|
|
f54beb2d88 | ||
|
|
b7ce1e2da0 | ||
|
|
6dba138ec7 | ||
|
|
32f59a618f | ||
|
|
9b2de387ed | ||
|
|
b612258c5e | ||
|
|
09b59ed18b | ||
|
|
b7b186b67e | ||
|
|
f2ba925db6 | ||
|
|
bc9488241f | ||
|
|
4fc36779dd | ||
|
|
bc008aeb23 | ||
|
|
8ad45a421e | ||
|
|
197317efbf | ||
|
|
775ad975f3 | ||
|
|
eaedf455d4 | ||
|
|
017f6c8074 | ||
|
|
48dd922712 | ||
|
|
202b320ad1 | ||
|
|
ea094ae64b | ||
|
|
9412aeb938 | ||
|
|
4f5e471667 | ||
|
|
fc6b20d370 | ||
|
|
fe82631db6 | ||
|
|
e4886d1b03 | ||
|
|
dfb2b19209 | ||
|
|
1a92d6b1e5 | ||
|
|
d5c1f49cc5 | ||
|
|
7d6ce843de | ||
|
|
ae4be2be7a | ||
|
|
0298f0eb29 | ||
|
|
794c56dfac | ||
|
|
6b9382b5e9 | ||
|
|
917e8a21d8 | ||
|
|
abdf4a3577 | ||
|
|
595e3c89df | ||
|
|
6b0e772c9f | ||
|
|
b9afa42662 | ||
|
|
5246a54118 | ||
|
|
a036391f69 | ||
|
|
039b1151bf | ||
|
|
6c79cfb109 | ||
|
|
b9f42bb733 | ||
|
|
542d839ed6 | ||
|
|
6ad24315bb | ||
|
|
3c1a4f124c | ||
|
|
48a83c356d | ||
|
|
756d52d106 | ||
|
|
83876a3523 | ||
|
|
b77c6d4edc | ||
|
|
c74fc11b25 | ||
|
|
7a636bd8c2 | ||
|
|
f6cc06b8dc | ||
|
|
6202436c9a | ||
|
|
65929a8e7e | ||
|
|
20e5786fc0 | ||
|
|
91bc3c432d | ||
|
|
1ff11cebf6 | ||
|
|
25cd967207 | ||
|
|
bcdc468a5d | ||
|
|
72d5112bac | ||
|
|
0b11a5f567 | ||
|
|
fac2d4a3b1 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"cojson": patch
|
||||
---
|
||||
|
||||
Add jazz.messagequeue.pushed/pulled counters, remove jazz.messagequeue.size gauge
|
||||
2
.github/workflows/playwright.yml
vendored
2
.github/workflows/playwright.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
project: ["tests/e2e", "examples/chat", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "starters/react-passkey-auth"]
|
||||
project: ["tests/e2e", "examples/chat", "examples/clerk", "examples/file-share-svelte", "examples/form", "examples/music-player", "examples/pets", "starters/react-passkey-auth"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -23,11 +23,9 @@ test-results
|
||||
.husky
|
||||
|
||||
.vscode/*
|
||||
.idea/*
|
||||
|
||||
.svelte-kit
|
||||
|
||||
.cursorrules
|
||||
.windsurfrules
|
||||
|
||||
|
||||
.idea
|
||||
|
||||
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
||||
8
.idea/jazz.iml
generated
8
.idea/jazz.iml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/jazz.iml" filepath="$PROJECT_DIR$/.idea/jazz.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
19
.idea/php.xml
generated
19
.idea/php.xml
generated
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/prettier.xml
generated
6
.idea/prettier.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PrettierConfiguration">
|
||||
<option name="myConfigurationMode" value="AUTOMATIC" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,5 +1,33 @@
|
||||
# chat-rn-expo-clerk
|
||||
|
||||
## 1.0.97
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-expo@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-react-native-media-images@0.13.4
|
||||
|
||||
## 1.0.96
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-expo@0.13.3
|
||||
- jazz-react-native-media-images@0.13.3
|
||||
|
||||
## 1.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-expo@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
- jazz-react-native-media-images@0.13.2
|
||||
|
||||
## 1.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,28 +2,39 @@ import "../global.css";
|
||||
import { ClerkLoaded, ClerkProvider } from "@clerk/clerk-expo";
|
||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
||||
import { useFonts } from "expo-font";
|
||||
import { Slot } from "expo-router";
|
||||
import { Slot, useRouter, useSegments } from "expo-router";
|
||||
import * as SplashScreen from "expo-splash-screen";
|
||||
import { useIsAuthenticated, useJazzContext } from "jazz-expo";
|
||||
import React, { useEffect } from "react";
|
||||
import { tokenCache } from "../cache";
|
||||
import { JazzAndAuth } from "../src/auth-context";
|
||||
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
|
||||
export default function RootLayout() {
|
||||
const [loaded] = useFonts({
|
||||
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||
});
|
||||
function InitialLayout() {
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const segments = useSegments();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync();
|
||||
}
|
||||
}, [loaded]);
|
||||
const inAuthGroup = segments[0] === "(auth)";
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
if (isAuthenticated && inAuthGroup) {
|
||||
router.replace("/chat");
|
||||
} else if (!isAuthenticated && !inAuthGroup) {
|
||||
router.replace("/");
|
||||
}
|
||||
|
||||
SplashScreen.hideAsync();
|
||||
}, [isAuthenticated, segments, router]);
|
||||
|
||||
return <Slot />;
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const [fontsLoaded] = useFonts({
|
||||
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
|
||||
});
|
||||
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
@@ -33,6 +44,17 @@ export default function RootLayout() {
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (fontsLoaded) {
|
||||
} else {
|
||||
SplashScreen.preventAutoHideAsync();
|
||||
}
|
||||
}, [fontsLoaded]);
|
||||
|
||||
if (!fontsLoaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider
|
||||
tokenCache={tokenCache}
|
||||
@@ -41,7 +63,7 @@ export default function RootLayout() {
|
||||
>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
<InitialLayout />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "chat-rn-expo-clerk",
|
||||
"main": "index.js",
|
||||
"version": "1.0.94",
|
||||
"version": "1.0.97",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
"start": "expo start",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# chat-rn-expo
|
||||
|
||||
## 1.0.84
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-expo@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 1.0.83
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-expo@0.13.3
|
||||
|
||||
## 1.0.82
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-expo@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 1.0.81
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn-expo",
|
||||
"version": "1.0.81",
|
||||
"version": "1.0.84",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "expo export -p ios",
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# chat-rn
|
||||
|
||||
## 1.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-react-native@0.13.4
|
||||
|
||||
## 1.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [b19cab7]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- cojson-transport-ws@0.13.3
|
||||
- jazz-react-native@0.13.3
|
||||
|
||||
## 1.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [c551839]
|
||||
- cojson@0.13.2
|
||||
- cojson-transport-ws@0.13.2
|
||||
- jazz-react-native@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 1.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-rn",
|
||||
"version": "1.0.90",
|
||||
"version": "1.0.93",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"android": "react-native run-android",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# chat-vue
|
||||
|
||||
## 0.0.77
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-browser@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-vue@0.13.4
|
||||
|
||||
## 0.0.76
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-browser@0.13.3
|
||||
- jazz-vue@0.13.3
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
- jazz-vue@0.13.2
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "chat-vue",
|
||||
"version": "0.0.74",
|
||||
"version": "0.0.77",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.174
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.173
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- Updated dependencies [017f6c8]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.172
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ae4be2b]
|
||||
- jazz-inspector@0.13.2
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.171
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-chat",
|
||||
"private": true,
|
||||
"version": "0.0.171",
|
||||
"version": "0.0.174",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
2
examples/clerk/.gitignore
vendored
2
examples/clerk/.gitignore
vendored
@@ -22,3 +22,5 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
playwright-report
|
||||
@@ -1,5 +1,33 @@
|
||||
# minimal-auth-clerk
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react-auth-clerk@0.13.4
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
- jazz-react-auth-clerk@0.13.3
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-react-auth-clerk@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"name": "clerk",
|
||||
"private": true,
|
||||
"version": "0.0.70",
|
||||
"version": "0.0.73",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"format-and-lint": "biome check .",
|
||||
"format-and-lint:fix": "biome check . --write"
|
||||
"format-and-lint:fix": "biome check . --write",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.4.1",
|
||||
@@ -19,6 +21,7 @@
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.50.1",
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
|
||||
53
examples/clerk/playwright.config.ts
Normal file
53
examples/clerk/playwright.config.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
import isCI from "is-ci";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// import dotenv from 'dotenv';
|
||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: isCI,
|
||||
/* Retry on CI only */
|
||||
retries: isCI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: isCI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: "http://localhost:5173/",
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
permissions: ["clipboard-read", "clipboard-write"],
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: [
|
||||
{
|
||||
command: "pnpm preview --port 5173",
|
||||
url: "http://localhost:5173/",
|
||||
reuseExistingServer: !isCI,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,8 +1,8 @@
|
||||
import { SignInButton } from "@clerk/clerk-react";
|
||||
import { SignInButton, SignOutButton } from "@clerk/clerk-react";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
function App() {
|
||||
const { me, logOut } = useAccount();
|
||||
const { me } = useAccount();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
@@ -11,7 +11,7 @@ function App() {
|
||||
<div className="container">
|
||||
<h1>You're logged in</h1>
|
||||
<p>Welcome back, {me?.profile?.name}</p>
|
||||
<button onClick={() => logOut()}>Logout</button>
|
||||
<SignOutButton>Logout</SignOutButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ClerkProvider, useClerk } from "@clerk/clerk-react";
|
||||
import { ClerkProvider, SignOutButton, useClerk } from "@clerk/clerk-react";
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
@@ -28,12 +28,23 @@ function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
// Route to test that when the Clerk user expires, the app is logged out
|
||||
if (location.search.includes("expirationTest")) {
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<SignOutButton>Simulate expiration</SignOutButton>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
} else {
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
}
|
||||
|
||||
36
examples/clerk/tests/expiration.spec.ts
Normal file
36
examples/clerk/tests/expiration.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("login & expiration", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
expect(page.getByText("You're not logged in")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Email address" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Password" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page.waitForURL("/");
|
||||
|
||||
await page.getByText("You're logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
|
||||
await page.goto("/?expirationTest");
|
||||
|
||||
// Simulate expiration by logging out outside of Jazz
|
||||
await page.getByRole("button", { name: "Simulate expiration" }).click();
|
||||
|
||||
await page.goto("/");
|
||||
|
||||
await page.getByText("You're not logged in").waitFor({ state: "visible" });
|
||||
});
|
||||
33
examples/clerk/tests/logout.spec.ts
Normal file
33
examples/clerk/tests/logout.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("login & logout", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
expect(page.getByText("You're not logged in")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Email address" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Password" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page.waitForURL("/");
|
||||
|
||||
await page.getByText("You're logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Logout" }).click();
|
||||
|
||||
await page.getByText("You're not logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're not logged in")).toBeVisible();
|
||||
});
|
||||
31
examples/clerk/tests/reload.spec.ts
Normal file
31
examples/clerk/tests/reload.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("login & reload", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
expect(page.getByText("You're not logged in")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Email address" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page
|
||||
.getByRole("textbox", { name: "Password" })
|
||||
.fill("guido+clerk-test@garden.co");
|
||||
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await page.waitForURL("/");
|
||||
|
||||
await page.getByText("You're logged in").waitFor({ state: "visible" });
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
|
||||
expect(page.getByText("You're logged in")).toBeVisible();
|
||||
});
|
||||
@@ -1,5 +1,29 @@
|
||||
# file-share-svelte
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-svelte@0.13.4
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-svelte@0.13.3
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "file-share-svelte",
|
||||
"version": "0.0.54",
|
||||
"version": "0.0.57",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# jazz-tailwind-demo-auth-starter
|
||||
|
||||
## 0.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- Updated dependencies [017f6c8]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ae4be2b]
|
||||
- jazz-inspector@0.13.2
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "filestream",
|
||||
"private": true,
|
||||
"version": "0.0.10",
|
||||
"version": "0.0.13",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# form
|
||||
|
||||
## 0.1.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.1.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.1.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.1.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "form",
|
||||
"private": true,
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.15",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# image-upload
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "image-upload",
|
||||
"private": true,
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.71",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# jazz-example-inspector
|
||||
|
||||
## 0.0.124
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.123
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [b19cab7]
|
||||
- Updated dependencies [017f6c8]
|
||||
- cojson-transport-ws@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
|
||||
## 0.0.122
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ae4be2b]
|
||||
- Updated dependencies [c551839]
|
||||
- jazz-inspector@0.13.2
|
||||
- cojson@0.13.2
|
||||
- cojson-transport-ws@0.13.2
|
||||
|
||||
## 0.0.121
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-inspector-app",
|
||||
"private": true,
|
||||
"version": "0.0.121",
|
||||
"version": "0.0.124",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# multi-cursors
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multi-cursors",
|
||||
"private": true,
|
||||
"version": "0.0.64",
|
||||
"version": "0.0.67",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -30,6 +30,6 @@
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.11",
|
||||
"vitest": "3.0.5"
|
||||
"vitest": "3.1.1"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,33 @@
|
||||
# multiauth
|
||||
|
||||
## 0.0.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react-auth-clerk@0.13.4
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
- jazz-react-auth-clerk@0.13.3
|
||||
|
||||
## 0.0.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-react-auth-clerk@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "multiauth",
|
||||
"private": true,
|
||||
"version": "0.0.11",
|
||||
"version": "0.0.14",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# jazz-example-musicplayer
|
||||
|
||||
## 0.0.95
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.94
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- Updated dependencies [017f6c8]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ae4be2b]
|
||||
- jazz-inspector@0.13.2
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-music-player",
|
||||
"private": true,
|
||||
"version": "0.0.92",
|
||||
"version": "0.0.95",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
54
examples/music-player/tests/auth.spec.ts
Normal file
54
examples/music-player/tests/auth.spec.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { BrowserContext, test } from "@playwright/test";
|
||||
import { HomePage } from "./pages/HomePage";
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
async function mockAuthenticator(context: BrowserContext) {
|
||||
await context.addInitScript(() => {
|
||||
Object.defineProperty(window.navigator, "credentials", {
|
||||
value: {
|
||||
...window.navigator.credentials,
|
||||
create: async () => ({
|
||||
type: "public-key",
|
||||
id: new Uint8Array([1, 2, 3, 4]),
|
||||
rawId: new Uint8Array([1, 2, 3, 4]),
|
||||
response: {
|
||||
clientDataJSON: new Uint8Array([1]),
|
||||
attestationObject: new Uint8Array([2]),
|
||||
},
|
||||
}),
|
||||
get: async () => ({
|
||||
type: "public-key",
|
||||
id: new Uint8Array([1, 2, 3, 4]),
|
||||
rawId: new Uint8Array([1, 2, 3, 4]),
|
||||
response: {
|
||||
authenticatorData: new Uint8Array([1]),
|
||||
clientDataJSON: new Uint8Array([2]),
|
||||
signature: new Uint8Array([3]),
|
||||
},
|
||||
}),
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Configure the authenticator
|
||||
test.beforeEach(async ({ context }) => {
|
||||
// Enable virtual authenticator environment
|
||||
await mockAuthenticator(context);
|
||||
});
|
||||
|
||||
test("sign up and log out", async ({ page: marioPage }) => {
|
||||
await marioPage.goto("/");
|
||||
|
||||
const marioHome = new HomePage(marioPage);
|
||||
|
||||
await marioHome.signUp("Mario");
|
||||
|
||||
await marioHome.logoutButton.waitFor({
|
||||
state: "visible",
|
||||
});
|
||||
|
||||
await marioHome.logOut();
|
||||
});
|
||||
@@ -11,8 +11,12 @@ export class HomePage {
|
||||
name: "Playlist title",
|
||||
});
|
||||
|
||||
loginButton = this.page.getByRole("button", {
|
||||
name: "Sign in",
|
||||
});
|
||||
|
||||
logoutButton = this.page.getByRole("button", {
|
||||
name: "Logout",
|
||||
name: "Sign out",
|
||||
});
|
||||
|
||||
async expectActiveTrackPlaying() {
|
||||
@@ -131,12 +135,12 @@ export class HomePage {
|
||||
await this.page
|
||||
.getByRole("button", { name: "Sign up with passkey" })
|
||||
.click();
|
||||
await expect(
|
||||
this.page.getByRole("button", { name: "Sign out" }),
|
||||
).toBeVisible();
|
||||
await expect(this.logoutButton).toBeVisible();
|
||||
}
|
||||
|
||||
async logout() {
|
||||
async logOut() {
|
||||
await this.logoutButton.click();
|
||||
|
||||
await expect(this.loginButton).toBeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# organization
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.64
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "organization",
|
||||
"private": true,
|
||||
"version": "0.0.64",
|
||||
"version": "0.0.67",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# passkey-svelte
|
||||
|
||||
## 0.0.61
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.4
|
||||
|
||||
## 0.0.60
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.3
|
||||
|
||||
## 0.0.59
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-svelte@0.13.2
|
||||
|
||||
## 0.0.58
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "passkey-svelte",
|
||||
"version": "0.0.58",
|
||||
"version": "0.0.61",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# minimal-auth-passkey
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passkey",
|
||||
"private": true,
|
||||
"version": "0.0.69",
|
||||
"version": "0.0.72",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# passphrase
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "passphrase",
|
||||
"private": true,
|
||||
"version": "0.0.66",
|
||||
"version": "0.0.69",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# jazz-password-manager
|
||||
|
||||
## 0.0.93
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.92
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.91
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.90
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-password-manager",
|
||||
"private": true,
|
||||
"version": "0.0.90",
|
||||
"version": "0.0.93",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# jazz-example-pets
|
||||
|
||||
## 0.0.191
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.190
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.189
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.188
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-pets",
|
||||
"private": true,
|
||||
"version": "0.0.188",
|
||||
"version": "0.0.191",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# reactions
|
||||
|
||||
## 0.0.71
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.70
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.69
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "reactions",
|
||||
"private": true,
|
||||
"version": "0.0.68",
|
||||
"version": "0.0.71",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# todo-vue
|
||||
|
||||
## 0.0.75
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-browser@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-vue@0.13.4
|
||||
|
||||
## 0.0.74
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-browser@0.13.3
|
||||
- jazz-vue@0.13.3
|
||||
|
||||
## 0.0.73
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-browser@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
- jazz-vue@0.13.2
|
||||
|
||||
## 0.0.72
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "todo-vue",
|
||||
"version": "0.0.72",
|
||||
"version": "0.0.75",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# jazz-example-todo
|
||||
|
||||
## 0.0.190
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
|
||||
## 0.0.189
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.188
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.187
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jazz-example-todo",
|
||||
"private": true,
|
||||
"version": "0.0.187",
|
||||
"version": "0.0.190",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# version-history
|
||||
|
||||
## 0.0.68
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [3129982]
|
||||
- jazz-react@0.13.4
|
||||
- jazz-tools@0.13.4
|
||||
- jazz-inspector@0.13.4
|
||||
|
||||
## 0.0.67
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [12f8bfa]
|
||||
- Updated dependencies [bd57177]
|
||||
- Updated dependencies [017f6c8]
|
||||
- jazz-tools@0.13.3
|
||||
- jazz-inspector@0.13.3
|
||||
- jazz-react@0.13.3
|
||||
|
||||
## 0.0.66
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [ae4be2b]
|
||||
- jazz-inspector@0.13.2
|
||||
- jazz-react@0.13.2
|
||||
- jazz-tools@0.13.2
|
||||
|
||||
## 0.0.65
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "version-history",
|
||||
"private": true,
|
||||
"version": "0.0.65",
|
||||
"version": "0.0.68",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1,36 +1,20 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
# Jazz Design System
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
First, install packages
|
||||
|
||||
```bash
|
||||
pnpm i
|
||||
```
|
||||
|
||||
Then run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import clsx from "clsx";
|
||||
|
||||
export function Icon404({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 556 517"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={clsx(className)}
|
||||
>
|
||||
<path
|
||||
d="M449.244 300.893C406.744 291.059 313.398 281.691 246.244 308.893C167.244 340.893 125.077 395.393 108.244 417.893"
|
||||
stroke="currentColor"
|
||||
strokeWidth="20"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M137.404 194H118.997V295.03C117.252 293.83 115.333 292.831 113.284 292.066C108.621 290.324 103.49 289.869 98.54 290.756C93.5899 291.644 89.043 293.836 85.4742 297.053C81.9054 300.271 79.475 304.371 78.4903 308.834C77.5057 313.297 78.0111 317.924 79.9425 322.128C81.8739 326.332 85.1447 329.925 89.3411 332.454C93.5376 334.982 98.4713 336.331 103.518 336.331C122.233 336.331 137.403 321.161 137.403 302.446L137.404 301.191V194Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M468.404 100H449.997V201.03C448.252 199.83 446.333 198.831 444.284 198.066C439.621 196.324 434.49 195.869 429.54 196.756C424.59 197.644 420.043 199.836 416.474 203.053C412.905 206.271 410.475 210.371 409.49 214.834C408.506 219.297 409.011 223.924 410.942 228.128C412.874 232.332 416.145 235.925 420.341 238.454C424.538 240.982 429.471 242.331 434.518 242.331C453.233 242.331 468.403 227.161 468.403 208.446L468.404 207.191V100Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
import { clsx } from "clsx";
|
||||
|
||||
export function JazzLogo({ className }: { className?: string }) {
|
||||
export function JazzLogo({
|
||||
className,
|
||||
width = undefined,
|
||||
height = undefined,
|
||||
}: { className?: string; width?: number; height?: number }) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 386 146"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={clsx(className, "text-black dark:text-white")}
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<path
|
||||
d="M176.725 33.865H188.275V22.7H176.725V33.865ZM164.9 129.4H172.875C182.72 129.4 188.275 123.9 188.275 114.22V43.6H176.725V109.545C176.725 115.65 173.975 118.51 167.925 118.51H164.9V129.4ZM245.298 53.28C241.613 45.47 233.363 41.95 222.748 41.95C208.998 41.95 200.748 48.44 197.888 58.615L208.613 61.915C210.648 55.315 216.368 52.565 222.638 52.565C231.933 52.565 235.673 56.415 236.058 64.61C226.433 65.93 216.643 67.195 209.768 69.23C200.583 72.145 195.743 77.865 195.743 86.83C195.743 96.51 202.673 104.65 215.818 104.65C225.443 104.65 232.318 101.35 237.213 94.365V103H247.388V66.425C247.388 61.475 247.168 57.185 245.298 53.28ZM217.853 95.245C210.483 95.245 207.128 91.34 207.128 86.72C207.128 82.045 210.593 79.515 215.323 77.92C220.328 76.435 226.983 75.5 235.948 74.18C235.893 76.93 235.673 80.725 234.738 83.475C233.418 89.25 227.643 95.245 217.853 95.245ZM251.22 103H301.545V92.715H269.535L303.195 45.47V43.6H254.3V53.885H284.935L251.22 101.185V103ZM304.815 103H355.14V92.715H323.13L356.79 45.47V43.6H307.895V53.885H338.53L304.815 101.185V103Z"
|
||||
|
||||
@@ -57,7 +57,9 @@ export function Footer({
|
||||
key={index}
|
||||
className="flex flex-col gap-2 text-sm col-span-6 md:col-span-2"
|
||||
>
|
||||
<h2 className="font-medium">{section.title}</h2>
|
||||
<h2 className="font-medium dark:text-stone-700 cursor-default">
|
||||
{section.title}
|
||||
</h2>
|
||||
{section.links.map((link, linkIndex) => (
|
||||
<FooterLink
|
||||
key={linkIndex}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { ImageResponse } from "next/og";
|
||||
import { JazzLogo } from "../atoms/logos/JazzLogo";
|
||||
|
||||
export const imageSize = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export const imageContentType = "image/png";
|
||||
|
||||
export default async function OpenGraphImage({ title }: { title: string }) {
|
||||
const manropeSemiBold = await readFile(
|
||||
join(process.cwd(), "public/fonts/Manrope-SemiBold.ttf"),
|
||||
);
|
||||
|
||||
return new ImageResponse(
|
||||
<div
|
||||
style={{
|
||||
fontSize: "7em",
|
||||
background: "white",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
padding: "77px",
|
||||
letterSpacing: "-0.05em",
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
<div
|
||||
style={{ display: "flex", position: "absolute", bottom: 35, right: 45 }}
|
||||
>
|
||||
<JazzLogo width={193} height={73} />
|
||||
</div>
|
||||
</div>,
|
||||
{
|
||||
...imageSize,
|
||||
fonts: [
|
||||
{
|
||||
name: "Manrope",
|
||||
data: manropeSemiBold,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -34,8 +34,8 @@ const commitMono = localFont({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "Jazz Design System by Garden Computing, Inc",
|
||||
description: "Jazz Design System by Garden Computing, Inc",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
export const metadata = { title: "Authentication States" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Authentication States
|
||||
|
||||
Jazz provides three distinct authentication states that determine how users interact with your app: **Anonymous Authentication**, **Guest Mode**, and **Authenticated Account**.
|
||||
|
||||
## Anonymous Authentication
|
||||
|
||||
When a user loads a Jazz application for the first time, we create a new Account by generating keys and storing them locally:
|
||||
|
||||
- Users have full accounts with unique IDs
|
||||
- Data persists between sessions on the same device
|
||||
- Can be upgraded to a full account (passkey, passphrase, etc.)
|
||||
- Data syncs across the network (if enabled)
|
||||
|
||||
## Authenticated Account
|
||||
|
||||
**Authenticated Account** provides full multi-device functionality:
|
||||
|
||||
- Persistent identity across multiple devices
|
||||
- Full access to all application features
|
||||
- Data can sync across all user devices
|
||||
- Multiple authentication methods available
|
||||
|
||||
## Guest Mode
|
||||
|
||||
**Guest Mode** provides a completely accountless context:
|
||||
|
||||
- No persistent identity or account
|
||||
- Only provides access to publicly readable content
|
||||
- Cannot save or sync user-specific data
|
||||
- Suitable for read-only access to public resources
|
||||
|
||||
## Detecting Authentication State
|
||||
|
||||
You can detect the current authentication state using `useAccountOrGuest` and `useIsAuthenticated`.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
// ---cut---
|
||||
import { useAccountOrGuest, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
function AuthStateIndicator() {
|
||||
const { me } = useAccountOrGuest();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
// Check if guest mode is enabled in JazzProvider
|
||||
const isGuest = me._type !== "Account"
|
||||
|
||||
// Anonymous authentication: has an account but not fully authenticated
|
||||
const isAnonymous = me._type === "Account" && !isAuthenticated;
|
||||
return (
|
||||
<div>
|
||||
{isGuest && <span>Guest Mode</span>}
|
||||
{isAnonymous && <span>Anonymous Account</span>}
|
||||
{isAuthenticated && <span>Authenticated</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Migrating data from anonymous to authenticated account
|
||||
|
||||
When a user signs up, their anonymous account is transparently upgraded to an authenticated account, preserving all their data.
|
||||
|
||||
However, if a user has been using your app anonymously and later logs in with an existing account, their anonymous account data would normally be discarded. To prevent data loss, you can use the `onAnonymousAccountDiscarded` handler.
|
||||
|
||||
This example from our [music player example app](https://github.com/garden-co/jazz/tree/main/examples/music-player) shows how to migrate data:
|
||||
|
||||
<CodeGroup>
|
||||
```ts twoslash
|
||||
import { Account, Group, CoMap, co, CoList } from "jazz-tools";
|
||||
|
||||
class MusicTrack extends CoMap {
|
||||
title = co.string;
|
||||
duration = co.number;
|
||||
isExampleTrack = co.optional.boolean;
|
||||
}
|
||||
class ListOfTracks extends CoList.Of(co.ref(MusicTrack)) {}
|
||||
class Playlist extends CoMap {
|
||||
title = co.string;
|
||||
tracks = co.ref(ListOfTracks);
|
||||
}
|
||||
class MusicaAccountRoot extends CoMap {
|
||||
rootPlaylist = co.ref(Playlist);
|
||||
}
|
||||
class MusicaAccount extends Account {
|
||||
root = co.ref(MusicaAccountRoot);
|
||||
}
|
||||
|
||||
// ---cut---
|
||||
export async function onAnonymousAccountDiscarded(
|
||||
anonymousAccount: MusicaAccount,
|
||||
) {
|
||||
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
||||
resolve: {
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: {
|
||||
$each: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
resolve: {
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const track of anonymousAccountRoot.rootPlaylist.tracks) {
|
||||
if (track.isExampleTrack) continue;
|
||||
|
||||
const trackGroup = track._owner.castAs(Group);
|
||||
trackGroup.addMember(me, "admin");
|
||||
|
||||
me.root.rootPlaylist.tracks.push(track);
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To see how this works, try uploading a song in the [music player demo](https://music-demo.jazz.tools/) and then log in with an existing account.
|
||||
|
||||
## Controlling sync for different authentication states
|
||||
|
||||
You can control network sync with [Providers](/docs/project-setup/providers) based on authentication state:
|
||||
|
||||
- `when: "always"`: Sync is enabled for both Anonymous Authentication and Authenticated Account
|
||||
- `when: "signedUp"`: Sync is enabled when the user is authenticated
|
||||
- `when: "never"`: Sync is disabled, content stays local
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
const apiKey = "you@example.com";
|
||||
function App() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
// ---cut---
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// Controls when sync is enabled for
|
||||
// both Anonymous Authentication and Authenticated Account
|
||||
when: "always", // or "signedUp" or "never"
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
### Disable sync for Anonymous Authentication
|
||||
|
||||
You can disable network sync to make your app local-only under specific circumstances.
|
||||
|
||||
For example, you may want to give users with Anonymous Authentication the opportunity to try your app locally-only (incurring no sync traffic), then enable network sync only when the user is fully authenticated.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
const apiKey = "you@example.com";
|
||||
function App() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
// ---cut---
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// This makes the app work in local mode when using Anonymous Authentication
|
||||
when: "signedUp",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
### Configuring Guest Mode Access
|
||||
|
||||
You can configure Guest Mode access with the `guestMode` prop for [Providers](/docs/project-setup/providers).
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { JazzProvider } from "jazz-react";
|
||||
const apiKey = "you@example.com";
|
||||
function App() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
// ---cut---
|
||||
<JazzProvider
|
||||
// Enable Guest Mode for public content
|
||||
guestMode={true}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// Only sync for authenticated users
|
||||
when: "signedUp",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
For more complex behaviours, you can manually control sync by statefully switching when between `"always"` and `"never"`.
|
||||
@@ -0,0 +1,227 @@
|
||||
export const metadata = { title: "Clerk Authentication" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Clerk Authentication
|
||||
|
||||
Jazz can be integrated with [Clerk](https://clerk.com/) to authenticate users. This method combines Clerk's comprehensive authentication services with Jazz's local-first capabilities.
|
||||
|
||||
<ContentByFramework framework="react-native">
|
||||
We do not currently support Clerk in React Native, but we do have support for [React Native Expo](/docs/react-native-expo/authentication/clerk).
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework={["react", "react-native-expo", "svelte", "vue", "vanilla"]}>
|
||||
## How it works
|
||||
|
||||
When using Clerk authentication:
|
||||
1. Users sign up or sign in through Clerk's authentication system
|
||||
2. Jazz securely stores the user's account keys with Clerk
|
||||
3. When logging in, Jazz retrieves these keys from Clerk
|
||||
4. Once authenticated, users can work offline with full Jazz functionality
|
||||
|
||||
This authentication method is not fully local-first, as login and signup need to be done online, but once authenticated, users can use all of Jazz's features without needing to be online.
|
||||
|
||||
## Key benefits
|
||||
|
||||
- **Rich auth options**: Email/password, social logins, multi-factor authentication
|
||||
- **User management**: Complete user administration dashboard
|
||||
- **Familiar sign-in**: Standard auth flows users already know
|
||||
- **OAuth providers**: Google, GitHub, and other popular providers
|
||||
- **Enterprise features**: SSO, SAML, and other advanced options
|
||||
|
||||
## Implementation
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
We offer Clerk integration through our package: [`jazz-react-auth-clerk`](https://npmjs.com/package/jazz-react-auth-clerk).
|
||||
|
||||
After installing, use `<JazzProviderWithClerk />` to wrap your app:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
const apiKey = "you@example.com";
|
||||
const PUBLISHABLE_KEY = "fake_key";
|
||||
function App() {
|
||||
return <div>Hello World</div>;
|
||||
}
|
||||
// ---cut---
|
||||
import { useClerk, ClerkProvider } from '@clerk/clerk-react';
|
||||
import { JazzProviderWithClerk } from "jazz-react-auth-clerk";
|
||||
|
||||
function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
);
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
We offer Clerk integration through our package: [`jazz-expo`](https://npmjs.com/package/jazz-expo).
|
||||
|
||||
After installing, use `<JazzProviderWithClerk />` to wrap your app.
|
||||
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { Slot } from "expo-router";
|
||||
const apiKey = "you@example.com";
|
||||
const tokenCache = {
|
||||
getToken: async (key: string) => {
|
||||
return null;
|
||||
},
|
||||
saveToken: async (key: string, token: string) => {},
|
||||
clearToken: async (key: string) => {},
|
||||
};
|
||||
// ---cut---
|
||||
import { useClerk, ClerkProvider, ClerkLoaded } from '@clerk/clerk-expo';
|
||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
||||
import { JazzProviderWithClerk } from "jazz-expo/auth/clerk";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
if (!publishableKey) {
|
||||
throw new Error(
|
||||
"Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env",
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider
|
||||
tokenCache={tokenCache}
|
||||
publishableKey={publishableKey}
|
||||
__experimental_resourceCache={secureStore}
|
||||
>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
Once set up, you can use Clerk's auth methods for login and signup:
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
// ---cut---
|
||||
import { SignInButton } from "@clerk/clerk-react";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <button onClick={() => logOut()}>Logout</button>;
|
||||
}
|
||||
|
||||
return <SignInButton />;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
// ---cut---
|
||||
import { useSignIn } from "@clerk/clerk-expo";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-expo";
|
||||
import { Button, Text } from 'react-native';
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <Button title="Logout" onPress={() => logOut()} />;
|
||||
}
|
||||
|
||||
const onSignInPress = async () => {
|
||||
if (!isLoaded) return;
|
||||
const signInAttempt = await signIn.create({
|
||||
identifier: "you@example.com",
|
||||
password: "password",
|
||||
});
|
||||
if (signInAttempt.status === "complete") {
|
||||
await setActive({ session: signInAttempt.createdSessionId });
|
||||
}
|
||||
};
|
||||
|
||||
return <Button title="Sign In" onPress={onSignInPress} />;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Examples
|
||||
|
||||
You can explore Jazz with Clerk integration in our [example projects](/docs/examples). For more Clerk-specific demos, visit [Clerk's documentation](https://clerk.com/docs).
|
||||
|
||||
## When to use Clerk
|
||||
|
||||
Clerk authentication is ideal when:
|
||||
|
||||
- You need an existing user management system
|
||||
- You want to integrate with other Clerk features (roles, permissions)
|
||||
- You require email/password authentication with verification
|
||||
- You need OAuth providers (Google, GitHub, etc.)
|
||||
- You want to avoid users having to manage passphrases
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
- **Online requirement**: Initial signup/login requires internet connectivity
|
||||
- **Third-party dependency**: Relies on Clerk's services for authentication
|
||||
- **Not fully local-first**: Initial authentication requires a server
|
||||
- **Platform support**: Not available on all platforms
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [Clerk Documentation](https://clerk.com/docs)
|
||||
- [Jazz React Auth Clerk Package](https://npmjs.com/package/jazz-react-auth-clerk)
|
||||
</ContentByFramework>
|
||||
@@ -6,408 +6,32 @@ import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
Jazz authentication is based on cryptographic keys ("Account keys"). Their public part represents a user's identity, their secret part lets you act as that user.
|
||||
|
||||
When a user loads a Jazz application for the first time, we create a new Account by generating keys and storing them locally.
|
||||
## Authentication Flow
|
||||
|
||||
Without any additional steps the user can use Jazz normally, but they would be limited to use on only one device.
|
||||
When a user first opens your app, they'll be in one of these states:
|
||||
|
||||
To make Accounts work across devices, you can store/retrieve the account keys from an authentication method by using the corresponding hooks and providers.
|
||||
- **Anonymous Authentication**: Default starting point where Jazz automatically creates a local account on first visit. Data persists on one device and can be upgraded to a full account.
|
||||
|
||||
<ContentByFramework framework={["react", "vue", "svelte"]}>
|
||||
## Authentication with passkeys
|
||||
- **Authenticated Account**: Full account accessible across multiple devices using [passkeys](./passkey), [passphrases](./passphrase), or third-party authentications, such as [Clerk](./clerk).
|
||||
|
||||
Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides (because keys are managed by the device/operating system itself).
|
||||
- **Guest Mode**: No account, read-only access to public content. Users can browse but can't save data or sync.
|
||||
|
||||
It is based on the [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) and is both very easy to use (using familiar FaceID/TouchID flows) and widely supported.
|
||||
Learn more about these states in the [Authentication States](./authentication-states) documentation.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Using passkeys in Jazz is as easy as this:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
Without authentication, users are limited to using the application on only one device.
|
||||
|
||||
const auth = usePasskeyAuth({ // Must be inside the JazzProvider!
|
||||
appName: "My super-cool web app",
|
||||
});
|
||||
When a user logs out of an Authenticated Account, they return to the Anonymous Authentication state with a new local account.
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
Here's what happens during registration and login:
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp(username);
|
||||
onOpenChange(false);
|
||||
};
|
||||
- **Register**: When a user registers with an authentication provider, their Anonymous account credentials are stored in the auth provider, and the account is marked as Authenticated. The user keeps all their existing data.
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn();
|
||||
onOpenChange(false);
|
||||
};
|
||||
- **Login**: When a user logs in with an authentication provider, their Anonymous account is discarded and the credentials are loaded from the auth provider. Data from the Anonymous account can be transferred using the [onAnonymousAccountDiscarded handler](./authentication-states#migrating-data-from-anonymous-to-authenticated-account).
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
<button onClick={handleSignUp}>Sign up</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
## Available Authentication Methods
|
||||
|
||||
Jazz provides several ways to authenticate users:
|
||||
|
||||
You can try our passkey authentication using our [passkey example](https://passkey-demo.jazz.tools/) or the [music player demo](https://music-demo.jazz.tools/).
|
||||
|
||||
</ContentByFramework>
|
||||
|
||||
## Passphrase-based authentication
|
||||
|
||||
Passphrase authentication lets users log into any device using a Bitcoin-style passphrase. This means users are themselves responsible for storing the passphrase safely.
|
||||
|
||||
The passphrase is generated from the local account certificate using a wordlist of your choice.
|
||||
|
||||
You can find a set of ready-to-use wordlists in the [bip39](https://github.com/bitcoinjs/bip39/tree/a7ecbfe2e60d0214ce17163d610cad9f7b23140c/src/wordlists) repository.
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
For example:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { englishWordlist } from "./wordlist"
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({ // Must be inside the JazzProvider!
|
||||
wordlist: englishWordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>
|
||||
Your current passphrase
|
||||
<textarea
|
||||
readOnly
|
||||
value={auth.passphrase}
|
||||
rows={5}
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleSignUp}>I have stored my passphrase</button>
|
||||
<label>
|
||||
Log in with your passphrase
|
||||
<textarea
|
||||
value={loginPassphrase}
|
||||
onChange={(e) => setLoginPassphrase(e.target.value)}
|
||||
placeholder="Enter your passphrase"
|
||||
rows={5}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework={["react-native", "react-native-expo"]}>
|
||||
For example:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { View, TextInput, Button, Text } from 'react-native';
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({
|
||||
wordlist: englishWordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") {
|
||||
return <Text>You are already signed in</Text>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Text>Your current passphrase</Text>
|
||||
<TextInput
|
||||
editable={false}
|
||||
value={auth.passphrase}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="I have stored my passphrase"
|
||||
onPress={handleSignUp}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<Text>Log in with your passphrase</Text>
|
||||
<TextInput
|
||||
value={loginPassphrase}
|
||||
onChangeText={setLoginPassphrase}
|
||||
placeholder="Enter your passphrase"
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
required
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="Log in"
|
||||
onPress={handleLogIn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
You can try our passphrase authentication using our [passphrase example](https://passphrase-demo.jazz.tools/) or the [todo list demo](https://todo-demo.jazz.tools/).
|
||||
|
||||
<ContentByFramework framework={["react", "react-native-expo"]}>
|
||||
## Integration with Clerk
|
||||
|
||||
Jazz can be used with [Clerk](https://clerk.com/) to authenticate users.
|
||||
|
||||
This authentication method is not fully local-first, because the login and signup need to be done while online. Clerk and anyone who is an admin in the app's Clerk org are trusted with the user's key secret and could impersonate them.
|
||||
|
||||
However, once authenticated, your users won't need to interact with Clerk anymore, and are able to use all of Jazz's features without needing to be online.
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
The clerk provider is not built into `jazz-react` and needs the `jazz-react-auth-clerk` package to be installed.
|
||||
|
||||
After installing the package you can use the `JazzProviderWithClerk` component to wrap your app:
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
You can use the `JazzProviderWithClerk` component to wrap your app. Note the `__experimental_resourceCache` option. This helps render Clerk components when offline.
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import { JazzProviderWithClerk } from "jazz-react-auth-clerk";
|
||||
|
||||
function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
|
||||
<JazzProvider>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
</ClerkProvider>
|
||||
);
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
import { JazzProviderWithClerk } from "jazz-expo/auth/clerk";
|
||||
import { secureStore } from "@clerk/clerk-expo/secure-store";
|
||||
|
||||
function JazzAndAuth({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk();
|
||||
|
||||
return (
|
||||
<JazzProviderWithClerk
|
||||
clerk={clerk}
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</JazzProviderWithClerk>
|
||||
);
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
|
||||
|
||||
if (!publishableKey) {
|
||||
throw new Error(
|
||||
"Missing Publishable Key. Please set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env",
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider
|
||||
tokenCache={tokenCache}
|
||||
publishableKey={publishableKey}
|
||||
__experimental_resourceCache={secureStore}
|
||||
>
|
||||
<ClerkLoaded>
|
||||
<JazzAndAuth>
|
||||
<Slot />
|
||||
</JazzAndAuth>
|
||||
</ClerkLoaded>
|
||||
</ClerkProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Then you can use the [Clerk auth methods](https://clerk.com/docs/references/react/overview) to log in and sign up:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { SignInButton } from "@clerk/clerk-react";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-react";
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <button onClick={() => logOut()}>Logout</button>;
|
||||
}
|
||||
|
||||
return <SignInButton />;
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework="react-native-expo">
|
||||
Then you can use the [Clerk auth methods](https://clerk.com/docs/references/expo/overview) to log in and sign up:
|
||||
<CodeGroup>
|
||||
{/* prettier-ignore */}
|
||||
```tsx
|
||||
import { SignInButton } from "@clerk/clerk-expo";
|
||||
import { useAccount, useIsAuthenticated } from "jazz-expo";
|
||||
|
||||
export function AuthButton() {
|
||||
const { logOut } = useAccount();
|
||||
const { signIn, setActive, isLoaded } = useSignIn();
|
||||
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
|
||||
if (isAuthenticated) {
|
||||
return <button onClick={() => logOut()}>Logout</button>;
|
||||
}
|
||||
|
||||
// Login code with Clerk Expo
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Migrating data from anonymous to authenticated account
|
||||
|
||||
You may want allow your users to use your app without authenticating (a poll response for example). When *signing up* their anonymous account is transparently upgraded using the provided auth method, keeping the data stored in the account intact.
|
||||
|
||||
However, a user may realise that they already have an existing account *after using the app anonymously and having already stored data in the anonymous account*.
|
||||
|
||||
When they now *log in*, by default the anonymous account will be discarded and this could lead to unexpected data loss.
|
||||
|
||||
To avoid this situation we provide the `onAnonymousAccountDiscarded` handler to migrate the data from the anonymous account to the existing authenticated one.
|
||||
|
||||
This is an example from our [music player](https://github.com/garden-co/jazz/tree/main/examples/music-player):
|
||||
<CodeGroup>
|
||||
```ts
|
||||
export async function onAnonymousAccountDiscarded(
|
||||
anonymousAccount: MusicaAccount,
|
||||
) {
|
||||
const { root: anonymousAccountRoot } = await anonymousAccount.ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [{}],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const me = await MusicaAccount.getMe().ensureLoaded({
|
||||
root: {
|
||||
rootPlaylist: {
|
||||
tracks: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const track of anonymousAccountRoot.rootPlaylist.tracks) {
|
||||
if (track.isExampleTrack) continue;
|
||||
|
||||
const trackGroup = track._owner.castAs(Group);
|
||||
trackGroup.addMember(me, "admin");
|
||||
|
||||
me.root.rootPlaylist.tracks.push(track);
|
||||
}
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
To see how this works in reality we suggest you to try
|
||||
to upload a song in the [music player demo](https://music-demo.jazz.tools/) and then
|
||||
try to log in with an existing account.
|
||||
|
||||
## Disable network sync for anonymous users
|
||||
|
||||
You can disable network sync to make your app local-only under specific circumstances.
|
||||
|
||||
For example, you may want to give the opportunity to non-authenticated users to try your app locally-only (incurring no sync traffic), then enable the network sync only when the user is authenticated:
|
||||
<CodeGroup>
|
||||
```tsx
|
||||
<JazzProvider
|
||||
sync={{
|
||||
peer: `wss://cloud.jazz.tools/?key=${apiKey}`,
|
||||
// This makes the app work in local mode when the user is anonymous
|
||||
when: "signedUp",
|
||||
}}
|
||||
>
|
||||
<App />
|
||||
</JazzProvider>
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
For more complex behaviours, you can manually control sync by statefully switching when between `"always"` and `"never"`.
|
||||
- [**Passkeys**](./passkey): Secure, biometric authentication using WebAuthn
|
||||
- [**Passphrases**](./passphrase): Bitcoin-style word phrases that users store
|
||||
- [**Clerk Integration**](./clerk): Third-party authentication service with OAuth support
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
export const metadata = { title: "Passkey Authentication" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Passkey Authentication
|
||||
|
||||
Passkey authentication is fully local-first and the most secure of the auth methods that Jazz provides because keys are managed by the device/operating system itself.
|
||||
|
||||
## How it works
|
||||
|
||||
Passkey authentication is based on the [Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) and uses familiar FaceID/TouchID flows that users already know how to use.
|
||||
|
||||
## Key benefits
|
||||
|
||||
- **Most secure**: Keys are managed by the device/OS
|
||||
- **User-friendly**: Uses familiar biometric verification (FaceID/TouchID)
|
||||
- **Cross-device**: Works across devices with the same biometric authentication
|
||||
- **No password management**: Users don't need to remember or store anything
|
||||
- **Wide support**: Available in most modern browsers
|
||||
|
||||
## Implementation
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
Using passkeys in Jazz is as easy as this:
|
||||
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { usePasskeyAuth } from "jazz-react";
|
||||
type AuthModalProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
// ---cut---
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [username, setUsername] = useState("");
|
||||
|
||||
const auth = usePasskeyAuth({ // Must be inside the JazzProvider!
|
||||
appName: "My super-cool web app",
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp(username);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
<button onClick={handleSignUp}>Sign up</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Examples
|
||||
|
||||
You can try passkey authentication using our [passkey example](https://passkey-demo.jazz.tools/) or the [music player demo](https://music-demo.jazz.tools/).
|
||||
|
||||
## When to use Passkeys
|
||||
|
||||
Passkeys are ideal when:
|
||||
- Security is a top priority
|
||||
- You want the most user-friendly authentication experience
|
||||
- You're targeting modern browsers and devices
|
||||
- You want to eliminate the risk of password-based attacks
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
- Requires hardware/OS support for biometric authentication
|
||||
- Not supported in older browsers (see browser support below)
|
||||
- Requires a fallback method for unsupported environments
|
||||
|
||||
### Browser Support
|
||||
|
||||
[Passkeys are supported in most modern browsers](https://caniuse.com/passkeys).
|
||||
|
||||
For older browsers, we recommend using [passphrase authentication](./passphrase) as a fallback.
|
||||
|
||||
## Additional resources
|
||||
|
||||
For more information about the Web Authentication API and passkeys:
|
||||
- [WebAuthn.io](https://webauthn.io/)
|
||||
- [MDN Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)
|
||||
@@ -0,0 +1,194 @@
|
||||
export const metadata = { title: "Passphrase Authentication" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# Passphrase Authentication
|
||||
|
||||
Passphrase authentication lets users log into any device using a recovery phrase consisting of multiple words (similar to cryptocurrency wallets). Users are responsible for storing this passphrase safely.
|
||||
|
||||
## How it works
|
||||
|
||||
When a user creates an account with passphrase authentication:
|
||||
|
||||
1. Jazz generates a unique recovery phrase derived from the user's cryptographic keys
|
||||
2. This phrase consists of words from a wordlist
|
||||
3. Users save this phrase and enter it when logging in on new devices
|
||||
|
||||
You can use one of the ready-to-use wordlists from the [BIP39 repository](https://github.com/bitcoinjs/bip39/tree/a7ecbfe2e60d0214ce17163d610cad9f7b23140c/src/wordlists) or create your own.
|
||||
|
||||
## Key benefits
|
||||
|
||||
- **Portable**: Works across any device, even without browser or OS support
|
||||
- **User-controlled**: User manages their authentication phrase
|
||||
- **Flexible**: Works with any wordlist you choose
|
||||
- **Offline capable**: No external dependencies
|
||||
|
||||
## Implementation
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
// @filename: wordlist.ts
|
||||
export const wordlist = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon", "mango", "orange", "pear", "quince", "raspberry", "strawberry", "tangerine", "uva", "watermelon", "xigua", "yuzu", "zucchini"];
|
||||
// @filename: AuthModal.tsx
|
||||
import * as React from "react";
|
||||
import { useState } from "react";
|
||||
import { usePassphraseAuth } from "jazz-react";
|
||||
type AuthModalProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
// ---cut---
|
||||
import { wordlist } from "./wordlist"
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({ // Must be inside the JazzProvider!
|
||||
wordlist: wordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") { // You can also use `useIsAuthenticated()`
|
||||
return <div>You are already signed in</div>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>
|
||||
Your current passphrase
|
||||
<textarea
|
||||
readOnly
|
||||
value={auth.passphrase}
|
||||
rows={5}
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleSignUp}>I have stored my passphrase</button>
|
||||
<label>
|
||||
Log in with your passphrase
|
||||
<textarea
|
||||
value={loginPassphrase}
|
||||
onChange={(e) => setLoginPassphrase(e.target.value)}
|
||||
placeholder="Enter your passphrase"
|
||||
rows={5}
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<button onClick={handleLogIn}>Log in</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
<ContentByFramework framework={["react-native", "react-native-expo"]}>
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
// @filename: wordlist.ts
|
||||
export const wordlist = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape", "honeydew", "kiwi", "lemon", "mango", "orange", "pear", "quince", "raspberry", "strawberry", "tangerine", "uva", "watermelon", "xigua", "yuzu", "zucchini"];
|
||||
// @filename: AuthModal.tsx
|
||||
import * as React from "react";
|
||||
import { View, TextInput, Button, Text } from 'react-native';
|
||||
import { useState } from "react";
|
||||
import { usePassphraseAuth } from "jazz-react-native";
|
||||
|
||||
type AuthModalProps = {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
// ---cut---
|
||||
import { wordlist } from "./wordlist"
|
||||
|
||||
export function AuthModal({ open, onOpenChange }: AuthModalProps) {
|
||||
const [loginPassphrase, setLoginPassphrase] = useState("");
|
||||
|
||||
const auth = usePassphraseAuth({
|
||||
wordlist: wordlist,
|
||||
});
|
||||
|
||||
if (auth.state === "signedIn") {
|
||||
return <Text>You are already signed in</Text>;
|
||||
}
|
||||
|
||||
const handleSignUp = async () => {
|
||||
await auth.signUp();
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
const handleLogIn = async () => {
|
||||
await auth.logIn(loginPassphrase);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<Text>Your current passphrase</Text>
|
||||
<TextInput
|
||||
editable={false}
|
||||
value={auth.passphrase}
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="I have stored my passphrase"
|
||||
onPress={handleSignUp}
|
||||
/>
|
||||
|
||||
<View>
|
||||
<Text>Log in with your passphrase</Text>
|
||||
<TextInput
|
||||
value={loginPassphrase}
|
||||
onChangeText={setLoginPassphrase}
|
||||
placeholder="Enter your passphrase"
|
||||
multiline
|
||||
numberOfLines={5}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title="Log in"
|
||||
onPress={handleLogIn}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Examples
|
||||
|
||||
You can see passphrase authentication in our [passphrase example](https://passphrase-demo.jazz.tools/) or the [todo list demo](https://todo-demo.jazz.tools/).
|
||||
|
||||
## When to use Passphrases
|
||||
|
||||
Passphrase authentication is ideal when:
|
||||
|
||||
- You need to support older browsers without WebAuthn capabilities
|
||||
- Your users need to access the app on many different devices
|
||||
- You want a fallback authentication method alongside passkeys
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
- **User responsibility**: Users must securely store their passphrase
|
||||
- **Recovery concerns**: If a user loses their passphrase, they cannot recover their account
|
||||
- **Security risk**: Anyone with the passphrase can access the account
|
||||
- **User experience**: Requires users to enter a potentially long phrase
|
||||
|
||||
Make sure to emphasize to your users:
|
||||
1. Store the passphrase in a secure location (password manager, written down in a safe place)
|
||||
2. The passphrase is the only way to recover their account
|
||||
3. Anyone with the passphrase can access the account
|
||||
@@ -15,11 +15,11 @@ The Jazz docs are currently heavily work in progress, sorry about that!
|
||||
|
||||
## Quickstart
|
||||
|
||||
Run the following command to create a new Jazz project from one of our example apps:
|
||||
You can use [`create-jazz-app`](/docs/tools/create-jazz-app) to create a new Jazz project from one of our starter templates or example apps:
|
||||
|
||||
<CodeGroup>
|
||||
```sh
|
||||
npx create-jazz-app@latest
|
||||
npx create-jazz-app@latest --api-key you@example.com
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -53,10 +53,19 @@ export default async function Page({
|
||||
|
||||
try {
|
||||
const mdxSource = await getMdxSource(framework, slugPath);
|
||||
const { default: Content, tableOfContents } = mdxSource;
|
||||
const {
|
||||
default: Content,
|
||||
tableOfContents,
|
||||
headingsFrameworkVisibility,
|
||||
test,
|
||||
} = mdxSource;
|
||||
|
||||
// Exclude h1 from table of contents
|
||||
const tocItems = (tableOfContents as Toc)?.[0]?.children;
|
||||
// Remove items that should not be shown for the current framework
|
||||
const tocItems = (tableOfContents as Toc).filter(({ id }) =>
|
||||
id && id in headingsFrameworkVisibility
|
||||
? headingsFrameworkVisibility[id]?.includes(framework)
|
||||
: true,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -214,8 +214,8 @@ export class MyAppAccount extends Account {
|
||||
}
|
||||
|
||||
// We need to load the root field to check for the myContacts field
|
||||
const { root } = await this.ensureLoaded({ // [!code ++:3]
|
||||
root: {},
|
||||
const { root } = await this.ensureLoaded({
|
||||
resolve: { root: true }
|
||||
});
|
||||
|
||||
// we specifically need to check for undefined,
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
export const metadata = {
|
||||
title: "create-jazz-app",
|
||||
};
|
||||
|
||||
import { CodeGroup } from "@/components/forMdx";
|
||||
|
||||
# create-jazz-app
|
||||
|
||||
Jazz comes with a CLI tool that helps you quickly scaffold new Jazz applications. There are two main ways to get started:
|
||||
|
||||
1. **Starter templates** - Pre-configured setups to start you off with your preferred framework
|
||||
2. **Example apps** - Extend one of our [example applications](https://jazz.tools/examples) to build your project
|
||||
|
||||
## Quick Start with Starter Templates
|
||||
|
||||
Create a new Jazz app from a starter template in seconds:
|
||||
|
||||
<CodeGroup>
|
||||
```bash
|
||||
npx create-jazz-app@latest --api-key you@example.com
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
This launches an interactive CLI that guides you through selecting:
|
||||
- Pre-configured frameworks and authentication methods (See [Available Starters](#available-starters))
|
||||
- Package manager
|
||||
- Project name
|
||||
- Jazz Cloud API key (optional) - Provides seamless sync and storage for your app
|
||||
|
||||
## Command Line Options
|
||||
|
||||
If you know what you want, you can specify options directly from the command line:
|
||||
|
||||
<CodeGroup>
|
||||
```bash
|
||||
# Basic usage with project name
|
||||
npx create-jazz-app@latest my-app --framework react --api-key you@example.com
|
||||
|
||||
# Specify a starter template
|
||||
npx create-jazz-app@latest my-app --starter react-passkey-auth --api-key you@example.com
|
||||
|
||||
# Specify example app
|
||||
npx create-jazz-app@latest my-app --example chat --api-key you@example.com
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Available Options
|
||||
|
||||
- `directory` - Directory to create the project in (defaults to project name)
|
||||
- `-f, --framework` - Framework to use (React, React Native, Svelte, Vue)
|
||||
- `-s, --starter` - Starter template to use
|
||||
- `-e, --example` - Example project to use
|
||||
- `-p, --package-manager` - Package manager to use (npm, yarn, pnpm, bun, deno)
|
||||
- `-k, --api-key` - Jazz Cloud API key (during our [free public alpha](/docs/react/sync-and-storage#free-public-alpha), you can use your email as the API key)
|
||||
- `-h, --help` - Display help information
|
||||
|
||||
## Start From an Example App
|
||||
|
||||
Want to start from one of [our example apps](https://jazz.tools/examples)? Our example apps include specific examples of features and use cases. They demonstrate real-world patterns for building with Jazz. Use one as your starting point:
|
||||
|
||||
<CodeGroup>
|
||||
```bash
|
||||
npx create-jazz-app@latest --example chat
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Available Starters
|
||||
|
||||
Starter templates are minimal setups that include the basic configuration needed to get started with Jazz. They're perfect when you want a clean slate to build on.
|
||||
|
||||
Choose from these ready-to-use starter templates:
|
||||
|
||||
- `react-passkey-auth` - React with Passkey authentication (easiest to start with)
|
||||
- `react-clerk-auth` - React with Clerk authentication
|
||||
- `vue-demo-auth` - Vue with Demo authentication
|
||||
- `svelte-passkey-auth` - Svelte with Passkey authentication
|
||||
- `rn-clerk-auth` - React Native with Clerk authentication
|
||||
|
||||
Run `npx create-jazz-app --help` to see the latest list of available starters.
|
||||
|
||||
## What Happens Behind the Scenes
|
||||
|
||||
When you run `create-jazz-app`, we'll:
|
||||
|
||||
1. Ask for your preferences (or use your command line arguments)
|
||||
2. Clone the appropriate starter template
|
||||
3. Update dependencies to their latest versions
|
||||
4. Install all required packages
|
||||
5. Set up your project and show next steps
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 14.0.0 or later
|
||||
- Your preferred package manager (npm, yarn, pnpm, bun, or deno)
|
||||
@@ -39,10 +39,10 @@ Update your imports to use the new `jazz-expo` package.
|
||||
```tsx twoslash
|
||||
// @noErrors: 2300 2307
|
||||
// Before
|
||||
import { JazzProvider, useAccount, useCoValue } from "jazz-react-native"; // [!code --]
|
||||
import { JazzProvider, useAccount, useCoState } from "jazz-react-native"; // [!code --]
|
||||
|
||||
// After
|
||||
import { JazzProvider, useAccount, useCoValue } from "jazz-expo"; // [!code ++]
|
||||
import { JazzProvider, useAccount, useCoState } from "jazz-expo"; // [!code ++]
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ import LatencyChart from "@/components/LatencyChart";
|
||||
import { clsx } from "clsx";
|
||||
import { HeroHeader } from "gcmp-design-system/src/app/components/molecules/HeroHeader";
|
||||
import type { Metadata } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Fragment } from "react";
|
||||
|
||||
const title = "Status";
|
||||
|
||||
export const revalidate = 300;
|
||||
export const dynamic = "force-static";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
@@ -41,6 +40,10 @@ interface DataRow {
|
||||
|
||||
const query = async () => {
|
||||
const res = await fetch("https://gcmp.grafana.net/api/ds/query", {
|
||||
cache: "force-cache",
|
||||
next: {
|
||||
revalidate: 300,
|
||||
},
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ThemeProvider } from "@/components/ThemeProvider";
|
||||
import { JazzFooter } from "@/components/footer";
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next";
|
||||
import { marketingCopy } from "@/lib/marketingCopy";
|
||||
|
||||
// If loading a variable font, you don't need to specify the font weight
|
||||
const manrope = Manrope({
|
||||
@@ -40,9 +41,8 @@ const commitMono = localFont({
|
||||
});
|
||||
|
||||
const metaTags = {
|
||||
title: "Jazz - Whip up an app",
|
||||
description:
|
||||
"Jazz gives you data without needing a database — plus auth, permissions, files and multiplayer without needing a backend. Do everything right from the frontend and ship better apps, faster.",
|
||||
title: `Jazz - ${marketingCopy.headline}`,
|
||||
description: marketingCopy.description,
|
||||
url: "https://jazz.tools",
|
||||
};
|
||||
|
||||
@@ -63,13 +63,6 @@ export const metadata: Metadata = {
|
||||
description: metaTags.description,
|
||||
url: metaTags.url,
|
||||
siteName: "Jazz",
|
||||
images: [
|
||||
{
|
||||
url: "/social-image.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
13
homepage/homepage/app/not-found.tsx
Normal file
13
homepage/homepage/app/not-found.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Icon404 } from 'gcmp-design-system/src/app/components/atoms/icons/404'
|
||||
|
||||
export default function NotFound() {
|
||||
const text = "Don't Worry 'Bout Me";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-flex-start justify-center min-h-[95vh] w-auto pb-32">
|
||||
<Icon404 className="w-75" />
|
||||
<h1 className="text-3xl font-semibold my-7">{text}</h1>
|
||||
<p>Page not found.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
11
homepage/homepage/app/opengraph-image.tsx
Normal file
11
homepage/homepage/app/opengraph-image.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { marketingCopy } from '@/lib/marketingCopy';
|
||||
import OpenGraphImage, { imageSize, imageContentType } from 'gcmp-design-system/src/app/components/organisms/OpenGraphImage';
|
||||
|
||||
export const title = marketingCopy.headline;
|
||||
export const size = imageSize;
|
||||
export const contentType = imageContentType;
|
||||
export const alt = marketingCopy.headline;
|
||||
|
||||
export default async function Image() {
|
||||
return OpenGraphImage({ title })
|
||||
}
|
||||
@@ -51,7 +51,7 @@ export default function DocsLayout({
|
||||
</div>
|
||||
<div className={clsx("md:col-span-8 lg:col-span-9 flex gap-12")}>
|
||||
{children}
|
||||
{tocItems?.length && (
|
||||
{!!tocItems?.length && (
|
||||
<>
|
||||
<TableOfContents
|
||||
className="pl-3 py-6 shrink-0 text-sm sticky align-start top-[61px] w-[16rem] h-[calc(100vh-61px)] overflow-y-auto hidden lg:block"
|
||||
|
||||
@@ -20,7 +20,7 @@ const TocList = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className="space-y-2" style={{ paddingLeft: "1rem" }}>
|
||||
<ul className="space-y-2" style={{ paddingLeft: level ? "1rem" : "0" }}>
|
||||
{items.map((item) => (
|
||||
<li key={item.id} className="space-y-2">
|
||||
{item.id && (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CreateJazzApp from "@/components/home/CreateJazzApp.mdx";
|
||||
import { marketingCopy } from "@/lib/marketingCopy";
|
||||
import { H1 } from "gcmp-design-system/src/app/components/atoms/Headings";
|
||||
import {
|
||||
Icon,
|
||||
@@ -54,7 +55,7 @@ export function HeroSection() {
|
||||
<div className="flex flex-col justify-center gap-5 lg:col-span-2 lg:gap-8">
|
||||
<Kicker>Toolkit for backendless apps</Kicker>
|
||||
<H1>
|
||||
<span className="inline-block">Whip up an app.</span>
|
||||
<span className="inline-block">{marketingCopy.headline}</span>
|
||||
</H1>
|
||||
|
||||
<Prose size="lg" className="text-pretty max-w-2xl dark:text-stone-200">
|
||||
|
||||
51
homepage/homepage/docs/authentication-method-template.md
Normal file
51
homepage/homepage/docs/authentication-method-template.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Authentication Method Template
|
||||
|
||||
Use this template structure for all authentication method documentation pages to ensure consistency.
|
||||
|
||||
````markdown
|
||||
export const metadata = { title: "[Method] Authentication" };
|
||||
|
||||
import { CodeGroup, ContentByFramework } from "@/components/forMdx";
|
||||
|
||||
# [Method] Authentication
|
||||
|
||||
Brief introduction explaining what this authentication method is.
|
||||
|
||||
## How it works
|
||||
|
||||
Clear, simple explanation of the mechanism behind this auth method.
|
||||
|
||||
## Key benefits
|
||||
|
||||
- Benefit one
|
||||
- Benefit two
|
||||
- Benefit three
|
||||
- Benefit four
|
||||
|
||||
## Implementation
|
||||
|
||||
<ContentByFramework framework="react">
|
||||
<CodeGroup>
|
||||
```tsx twoslash
|
||||
// Code example here
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
</ContentByFramework>
|
||||
|
||||
## Examples
|
||||
|
||||
Links to live demos using this authentication method.
|
||||
|
||||
## When to use [Method]
|
||||
|
||||
Guidelines about when this method is most appropriate.
|
||||
|
||||
## Limitations and considerations
|
||||
|
||||
Any drawbacks or special considerations to be aware of.
|
||||
|
||||
## Additional resources (optional)
|
||||
|
||||
External links and further reading where applicable.
|
||||
````
|
||||
@@ -67,6 +67,11 @@ export const docNavigationItems = [
|
||||
href: "/docs/ai-tools",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "create-jazz-app",
|
||||
href: "/docs/tools/create-jazz-app",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Inspector",
|
||||
href: "/docs/inspector",
|
||||
@@ -221,12 +226,27 @@ export const docNavigationItems = [
|
||||
{
|
||||
name: "Overview",
|
||||
href: "/docs/authentication/overview",
|
||||
done: {
|
||||
react: 100,
|
||||
vue: 50,
|
||||
"react-native": 100,
|
||||
svelte: 50,
|
||||
},
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Authentication States",
|
||||
href: "/docs/authentication/authentication-states",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Passkey",
|
||||
href: "/docs/authentication/passkey",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Passphrase",
|
||||
href: "/docs/authentication/passphrase",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Clerk",
|
||||
href: "/docs/authentication/clerk",
|
||||
done: 100,
|
||||
},
|
||||
{
|
||||
name: "Writing your own",
|
||||
|
||||
4
homepage/homepage/lib/marketingCopy.ts
Normal file
4
homepage/homepage/lib/marketingCopy.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const marketingCopy = {
|
||||
headline: "Whip up an app",
|
||||
description: "Jazz gives you data without needing a database — plus auth, permissions, files and multiplayer without needing a backend. Do everything right from the frontend and ship better apps, faster.",
|
||||
}
|
||||
@@ -2,8 +2,10 @@ import createMDX from "@next/mdx";
|
||||
import { transformerNotationDiff } from "@shikijs/transformers";
|
||||
import { transformerTwoslash } from "@shikijs/twoslash";
|
||||
import withToc from "@stefanprobst/rehype-extract-toc";
|
||||
import withTocExport from "@stefanprobst/rehype-extract-toc/mdx";
|
||||
import rehypeSlug from "rehype-slug";
|
||||
import { valueToEstree } from "estree-util-value-to-estree";
|
||||
import GithubSlugger from "github-slugger";
|
||||
import { headingRank } from "hast-util-heading-rank";
|
||||
import { toString } from "hast-util-to-string";
|
||||
import { createHighlighter } from "shiki";
|
||||
import { SKIP, visit } from "unist-util-visit";
|
||||
import { jazzDark } from "./themes/jazzDark.mjs";
|
||||
@@ -20,7 +22,18 @@ const withMDX = createMDX({
|
||||
// Add markdown plugins here, as desired
|
||||
options: {
|
||||
remarkPlugins: [highlightPlugin, remarkHtmlToJsx],
|
||||
rehypePlugins: [rehypeSlug, withToc, withTocExport],
|
||||
rehypePlugins: [
|
||||
// Add id to heading elements, and indicate which frameworks to show the heading for
|
||||
// This is a modified version of rehype-slug
|
||||
withSlugAndHeadingsFrameworkVisibility,
|
||||
|
||||
// Create table of contents array
|
||||
withToc,
|
||||
|
||||
// Return the table of contents and framework visibility data when importing a .mdx file
|
||||
// This is a modified version of withTocExport from @stefanprobst/rehype-extract-toc
|
||||
withTocAndFrameworkHeadingsVisibilityExport,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -85,10 +98,6 @@ function highlightPlugin() {
|
||||
};
|
||||
}
|
||||
|
||||
function escape(s) {
|
||||
return s.replace(/[^0-9A-Za-z ]/g, (c) => "&#" + c.charCodeAt(0) + ";");
|
||||
}
|
||||
|
||||
function remarkHtmlToJsx() {
|
||||
async function transform(...args) {
|
||||
// Async import since these packages are all in ESM
|
||||
@@ -116,4 +125,83 @@ function remarkHtmlToJsx() {
|
||||
return transform;
|
||||
}
|
||||
|
||||
const slugs = new GithubSlugger();
|
||||
|
||||
export function withSlugAndHeadingsFrameworkVisibility() {
|
||||
return function (tree, vfile) {
|
||||
slugs.reset();
|
||||
vfile.data.headingsFrameworkVisibility = {};
|
||||
|
||||
visit(tree, "element", function (node) {
|
||||
if (headingRank(node) && !node.properties.id) {
|
||||
const lastChild = node.children?.[node.children.length - 1];
|
||||
if (!lastChild || lastChild.type !== "text") return;
|
||||
|
||||
const match = lastChild.value.match(
|
||||
/\s*\[\!framework=([a-zA-Z0-9,_-]+)\]\s*$/,
|
||||
);
|
||||
if (match) {
|
||||
const frameworks = match[1];
|
||||
|
||||
lastChild.value = lastChild.value.replace(
|
||||
/\s*\[\!framework=[a-zA-Z0-9,_-]+\]\s*$/,
|
||||
"",
|
||||
);
|
||||
|
||||
node.properties.id = slugs.slug(lastChild.value);
|
||||
vfile.data.headingsFrameworkVisibility[node.properties.id] =
|
||||
frameworks.split(",");
|
||||
} else {
|
||||
node.properties.id = slugs.slug(toString(node));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function withTocAndFrameworkHeadingsVisibilityExport() {
|
||||
return function transformer(tree, vfile) {
|
||||
if (vfile.data.toc == null) return;
|
||||
|
||||
tree.children.unshift({
|
||||
type: "mdxjsEsm",
|
||||
data: {
|
||||
estree: {
|
||||
type: "Program",
|
||||
sourceType: "module",
|
||||
body: [
|
||||
{
|
||||
type: "ExportNamedDeclaration",
|
||||
source: null,
|
||||
specifiers: [],
|
||||
declaration: {
|
||||
type: "VariableDeclaration",
|
||||
kind: "const",
|
||||
declarations: [
|
||||
{
|
||||
type: "VariableDeclarator",
|
||||
id: {
|
||||
type: "Identifier",
|
||||
name: "headingsFrameworkVisibility",
|
||||
},
|
||||
init: valueToEstree(vfile.data.headingsFrameworkVisibility),
|
||||
},
|
||||
{
|
||||
type: "VariableDeclarator",
|
||||
id: { type: "Identifier", name: "tableOfContents" },
|
||||
init: valueToEstree(
|
||||
// exclude h1
|
||||
vfile.data.toc.length ? vfile.data.toc[0].children : [],
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -32,11 +32,18 @@
|
||||
"@vercel/analytics": "^1.3.1",
|
||||
"@vercel/speed-insights": "^1.0.12",
|
||||
"clsx": "^2.1.1",
|
||||
"estree-util-value-to-estree": "^3.3.2",
|
||||
"gcmp-design-system": "workspace:*",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hast-util-heading-rank": "^3.0.0",
|
||||
"hast-util-to-string": "^3.0.1",
|
||||
"jazz-browser": "link:../../packages/jazz-browser",
|
||||
"jazz-browser-media-images": "link:../../packages/jazz-browser-media-images",
|
||||
"jazz-expo": "link:../../packages/jazz-expo",
|
||||
"jazz-nodejs": "link:../../packages/jazz-nodejs",
|
||||
"jazz-react": "link:../../packages/jazz-react",
|
||||
"jazz-react-auth-clerk": "link:../../packages/jazz-react-auth-clerk",
|
||||
"jazz-react-native": "link:../../packages/jazz-react-native",
|
||||
"jazz-tools": "link:../../packages/jazz-tools",
|
||||
"lucide-react": "^0.436.0",
|
||||
"mdast-util-from-markdown": "^2.0.0",
|
||||
@@ -47,7 +54,6 @@
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"shiki": "^3.2.1",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
||||
BIN
homepage/homepage/public/fonts/Manrope-SemiBold.ttf
Normal file
BIN
homepage/homepage/public/fonts/Manrope-SemiBold.ttf
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 172 KiB |
@@ -3,7 +3,8 @@
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build", "build:generate-docs"],
|
||||
"outputs": [".next/**", "!.next/cache/**"]
|
||||
"outputs": [".next/**", "!.next/cache/**"],
|
||||
"env": ["GRAFANA_SERVICE_ACCOUNT"]
|
||||
},
|
||||
"build:generate-docs": {
|
||||
"inputs": ["../../../packages/*/src/**"],
|
||||
|
||||
70
homepage/pnpm-lock.yaml
generated
70
homepage/pnpm-lock.yaml
generated
@@ -223,21 +223,42 @@ importers:
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
estree-util-value-to-estree:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
gcmp-design-system:
|
||||
specifier: workspace:*
|
||||
version: link:../design-system
|
||||
github-slugger:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
hast-util-heading-rank:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
hast-util-to-string:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
jazz-browser:
|
||||
specifier: link:../../packages/jazz-browser
|
||||
version: link:../../packages/jazz-browser
|
||||
jazz-browser-media-images:
|
||||
specifier: link:../../packages/jazz-browser-media-images
|
||||
version: link:../../packages/jazz-browser-media-images
|
||||
jazz-expo:
|
||||
specifier: link:../../packages/jazz-expo
|
||||
version: link:../../packages/jazz-expo
|
||||
jazz-nodejs:
|
||||
specifier: link:../../packages/jazz-nodejs
|
||||
version: link:../../packages/jazz-nodejs
|
||||
jazz-react:
|
||||
specifier: link:../../packages/jazz-react
|
||||
version: link:../../packages/jazz-react
|
||||
jazz-react-auth-clerk:
|
||||
specifier: link:../../packages/jazz-react-auth-clerk
|
||||
version: link:../../packages/jazz-react-auth-clerk
|
||||
jazz-react-native:
|
||||
specifier: link:../../packages/jazz-react-native
|
||||
version: link:../../packages/jazz-react-native
|
||||
jazz-tools:
|
||||
specifier: link:../../packages/jazz-tools
|
||||
version: link:../../packages/jazz-tools
|
||||
@@ -268,9 +289,6 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^18
|
||||
version: 18.3.1(react@18.3.1)
|
||||
rehype-slug:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
shiki:
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1
|
||||
@@ -1982,6 +2000,9 @@ packages:
|
||||
resolution: {integrity: sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
estree-util-value-to-estree@3.3.2:
|
||||
resolution: {integrity: sha512-hYH1aSvQI63Cvq3T3loaem6LW4u72F187zW4FHpTrReJSm6W66vYTFNO1vH/chmcOulp1HlAj1pxn8Ag0oXI5Q==}
|
||||
|
||||
estree-util-visit@1.2.1:
|
||||
resolution: {integrity: sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==}
|
||||
|
||||
@@ -2860,9 +2881,6 @@ packages:
|
||||
regex@6.0.1:
|
||||
resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
|
||||
|
||||
rehype-slug@6.0.0:
|
||||
resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==}
|
||||
|
||||
remark-mdx@2.3.0:
|
||||
resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==}
|
||||
|
||||
@@ -5138,7 +5156,7 @@ snapshots:
|
||||
|
||||
'@types/acorn@4.0.6':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
'@types/d3-voronoi@1.1.12': {}
|
||||
|
||||
@@ -5158,7 +5176,7 @@ snapshots:
|
||||
|
||||
'@types/estree-jsx@1.0.5':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
'@types/estree@1.0.5': {}
|
||||
|
||||
@@ -5647,7 +5665,7 @@ snapshots:
|
||||
|
||||
estree-util-attach-comments@2.1.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
estree-util-build-jsx@2.2.2:
|
||||
dependencies:
|
||||
@@ -5669,6 +5687,10 @@ snapshots:
|
||||
dependencies:
|
||||
is-plain-obj: 3.0.0
|
||||
|
||||
estree-util-value-to-estree@3.3.2:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
estree-util-visit@1.2.1:
|
||||
dependencies:
|
||||
'@types/estree-jsx': 1.0.5
|
||||
@@ -5681,7 +5703,7 @@ snapshots:
|
||||
|
||||
estree-walker@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
events@3.3.0: {}
|
||||
|
||||
@@ -5780,7 +5802,7 @@ snapshots:
|
||||
|
||||
hast-util-to-estree@2.3.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
'@types/estree-jsx': 1.0.5
|
||||
'@types/hast': 2.3.10
|
||||
'@types/unist': 2.0.10
|
||||
@@ -5887,7 +5909,7 @@ snapshots:
|
||||
|
||||
is-reference@3.0.2:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
isexe@2.0.0: {}
|
||||
|
||||
@@ -6228,7 +6250,7 @@ snapshots:
|
||||
|
||||
micromark-extension-mdx-expression@1.0.8:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
micromark-factory-mdx-expression: 1.0.9
|
||||
micromark-factory-space: 1.1.0
|
||||
micromark-util-character: 1.2.0
|
||||
@@ -6251,7 +6273,7 @@ snapshots:
|
||||
micromark-extension-mdx-jsx@1.0.5:
|
||||
dependencies:
|
||||
'@types/acorn': 4.0.6
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
estree-util-is-identifier-name: 2.1.0
|
||||
micromark-factory-mdx-expression: 1.0.9
|
||||
micromark-factory-space: 1.1.0
|
||||
@@ -6284,7 +6306,7 @@ snapshots:
|
||||
|
||||
micromark-extension-mdxjs-esm@1.0.5:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
micromark-core-commonmark: 1.1.0
|
||||
micromark-util-character: 1.2.0
|
||||
micromark-util-events-to-acorn: 1.2.3
|
||||
@@ -6356,7 +6378,7 @@ snapshots:
|
||||
|
||||
micromark-factory-mdx-expression@1.0.9:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
micromark-util-character: 1.2.0
|
||||
micromark-util-events-to-acorn: 1.2.3
|
||||
micromark-util-symbol: 1.1.0
|
||||
@@ -6367,7 +6389,7 @@ snapshots:
|
||||
|
||||
micromark-factory-mdx-expression@2.0.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
devlop: 1.1.0
|
||||
micromark-util-character: 2.1.0
|
||||
micromark-util-events-to-acorn: 2.0.2
|
||||
@@ -6483,7 +6505,7 @@ snapshots:
|
||||
micromark-util-events-to-acorn@1.2.3:
|
||||
dependencies:
|
||||
'@types/acorn': 4.0.6
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
'@types/unist': 2.0.10
|
||||
estree-util-visit: 1.2.1
|
||||
micromark-util-symbol: 1.1.0
|
||||
@@ -6494,7 +6516,7 @@ snapshots:
|
||||
micromark-util-events-to-acorn@2.0.2:
|
||||
dependencies:
|
||||
'@types/acorn': 4.0.6
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
'@types/unist': 3.0.2
|
||||
devlop: 1.1.0
|
||||
estree-util-visit: 2.0.0
|
||||
@@ -6801,7 +6823,7 @@ snapshots:
|
||||
|
||||
periscopic@3.1.0:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
'@types/estree': 1.0.6
|
||||
estree-walker: 3.0.3
|
||||
is-reference: 3.0.2
|
||||
|
||||
@@ -6942,14 +6964,6 @@ snapshots:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
rehype-slug@6.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
github-slugger: 2.0.0
|
||||
hast-util-heading-rank: 3.0.0
|
||||
hast-util-to-string: 3.0.1
|
||||
unist-util-visit: 5.0.0
|
||||
|
||||
remark-mdx@2.3.0:
|
||||
dependencies:
|
||||
mdast-util-mdx: 2.0.1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user