Compare commits

...

10 Commits

Author SHA1 Message Date
Anselm
6571a6fb31 update start command 2024-09-23 16:14:28 -07:00
Anselm
1139605a7d update build command 2024-09-23 16:14:06 -07:00
Anselm
f40c80394d Add router refresh 2024-09-23 16:08:22 -07:00
Anselm
2bfb42dd4a Everything mostly working 2024-09-23 15:58:37 -07:00
Anselm
3ebdaa55c7 changes in jazz-react 2024-09-23 15:38:21 -07:00
Anselm
50dbd46c6c move and implement more 2024-09-23 15:38:08 -07:00
Anselm
37fbe504e3 Merge branch 'main' into lotion 2024-09-23 14:48:09 -07:00
Anselm
dc43f9a6b0 SSR start 2024-09-23 14:48:04 -07:00
Anselm
61535c80e3 create doc 2024-09-23 14:35:18 -07:00
Anselm
285b7609d6 skeleton 2024-09-23 14:15:58 -07:00
25 changed files with 1534 additions and 36 deletions

View File

@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}

36
examples/next-ssr/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@@ -0,0 +1,10 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 80
}

View File

@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, 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/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## 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/app/building-your-application/deploying) for more details.

View File

@@ -0,0 +1,41 @@
"use client";
import { useAccount } from "../jazz-client";
import { Doc } from "../schema";
import { useRouter } from "next/navigation";
export function NewDocButton() {
const { me } = useAccount();
const router = useRouter();
function createDoc() {
if (!me) return;
const document = Doc.create(
{
title: "Untitled",
text: "Lorem ipsum...",
tweet: "",
},
{ owner: me }
);
setTimeout(() => {
router.push(`/doc/${document.id}`);
router.refresh();
}, 200);
}
return (
<button
className={
(me ? "bg-blue-500" : "bg-gray-500") +
" text-white px-4 py-2 rounded-md"
}
onClick={createDoc}
disabled={!me}
>
Create new document
</button>
);
}

View File

@@ -0,0 +1,13 @@
import { NewDocButton } from "./newDocButton";
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<h1 className="text-4xl font-bold">Lotion</h1>
<NewDocButton />
</main>
</div>
);
}

View File

@@ -0,0 +1,17 @@
"use client";
import { ID } from "jazz-tools";
import { useCoState } from "../../jazz-client";
import { Doc } from "../../schema";
import { RenderDoc } from "./renderDoc";
export function ClientDoc({ docId, ssrDoc }: { docId: ID<Doc>; ssrDoc: Doc }) {
const doc = useCoState(Doc, docId);
return (
<>
<RenderDoc doc={doc || ssrDoc} />
<div>{doc ? "client" : <span className="bg-red">ssr</span>}</div>
</>
);
}

View File

@@ -0,0 +1,45 @@
import { Doc } from "@/app/schema";
import { JazzSSRPromise } from "@/app/jazz-server";
import { ClientDoc } from "./clientDoc";
import { ID } from "jazz-tools";
export default async function DocPage({
params,
}: {
params: { docId: string };
}) {
const JazzSSR = await JazzSSRPromise;
console.log("Loaded ssrJazz");
const ssrDoc = await new Promise((resolve) => {
let latestDoc: Doc | undefined = undefined;
Doc.subscribe(params.docId as ID<Doc>, JazzSSR.worker, {}, (doc) => {
latestDoc = doc;
});
setTimeout(() => {
if (latestDoc) {
resolve(latestDoc);
}
}, 100);
});
console.log("Loaded ssrDoc", ssrDoc);
if (!ssrDoc) {
return <div>Doc not found</div>;
}
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<div>Doc {params.docId}</div>
<ClientDoc
docId={params.docId as ID<Doc>}
ssrDoc={{ ...ssrDoc } as Doc}
/>
</main>
</div>
);
}

View File

@@ -0,0 +1,33 @@
import { Doc } from "../../schema";
import { Tweet } from 'react-tweet'
export function RenderDoc({ doc }: { doc: Doc }) {
return (
<>
<input
type="text"
value={doc.title}
className="border rounded p-2 text-2xl font-bold"
onChange={(e) => {
doc.title = e.target.value;
}}
/>
<textarea
value={doc.text}
className="border rounded p-2 text-lg"
onChange={(e) => {
doc.text = e.target.value;
}}
/>
<input
type="text"
value={doc.tweet}
className="border rounded p-2 text-lg"
onChange={(e) => {
doc.tweet = e.target.value;
}}
/>
{doc.tweet && <Tweet id={doc.tweet} />}
</>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,27 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}

View File

@@ -0,0 +1,6 @@
import { ID, Account } from "jazz-tools";
export const hardcodedUserCredentials = {
accountID: "co_z9vEwRPyv91S1DjgK7YZrU3L2qQ" as ID<Account>,
secret: "sealerSecret_z2EYHmgJdUo4eDzYAQV8mz4PFTwnQrZTJCpBSiBiofEgT/signerSecret_z9k8kci8cskPBuRQmQUDKszEH6UJJCHvMzG9rx6Wxvpgh" as const
};

View File

@@ -0,0 +1,37 @@
"use client";
import { createJazzReactApp } from "jazz-react";
import {
Account,
fixedCredentialsAuth,
ID,
} from "jazz-tools";
const JazzClient = createJazzReactApp();
export const { useCoState, useAccount } = JazzClient;
export function JazzClientContext({
children,
credentials,
}: {
children: React.ReactNode;
credentials: {
accountID: ID<Account>;
secret: `sealerSecret_z${string}/signerSecret_z${string}`;
};
}) {
return (
<JazzClient.Provider
auth={{
async start(crypto) {
await new Promise((resolve) => setTimeout(resolve, 3000));
return fixedCredentialsAuth(credentials).start(crypto)
}
}}
peer="wss://mesh.jazz.tools/?key=you@example.com"
>
{children}
</JazzClient.Provider>
);
}

View File

@@ -0,0 +1,9 @@
import "next/server";
import { hardcodedUserCredentials } from "./hardcodedUserCredentials";
import { startWorker } from "jazz-nodejs";
export const JazzSSRPromise = startWorker({
accountID: hardcodedUserCredentials.accountID,
accountSecret: hardcodedUserCredentials.secret,
syncServer: "wss://mesh.jazz.tools/?key=you@example.com",
});

View File

@@ -0,0 +1,39 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
import { JazzClientContext } from "./jazz-client";
import { hardcodedUserCredentials } from "./hardcodedUserCredentials";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<JazzClientContext credentials={hardcodedUserCredentials}>
{children}
</JazzClientContext>
</body>
</html>
);
}

View File

@@ -0,0 +1,7 @@
import { co, CoMap } from "jazz-tools";
export class Doc extends CoMap {
title = co.string;
text = co.string;
tweet = co.string;
}

View File

@@ -0,0 +1,13 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ["jazz-tools", "jazz-nodejs", "cojson-storage-indexeddb"],
webpack: config => {
config.resolve.extensionAlias = {
".js": [".ts", ".tsx", ".js"],
};
return config;
},
};
export default nextConfig;

View File

@@ -0,0 +1,30 @@
{
"name": "jazz-example-next-ssr",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "WS_NO_BUFFER_UTIL=true next build",
"start": "WS_NO_BUFFER_UTIL=true next start",
"lint": "next lint"
},
"dependencies": {
"jazz-nodejs": "workspace:^0.8.1",
"jazz-react": "workspace:^0.8.1",
"jazz-tools": "workspace:^0.8.1",
"next": "14.2.13",
"react": "^18",
"react-dom": "^18",
"react-tweet": "^3.2.1"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.13",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

View File

@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

View File

@@ -0,0 +1,19 @@
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
},
},
},
plugins: [],
};
export default config;

View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"target": "es2020",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View File

@@ -75,12 +75,12 @@ export function createJazzReactApp<Acc extends Account>({
return (
<JazzContext.Provider value={ctx}>
{ctx && children}
{children}
</JazzContext.Provider>
);
}
function useAccount(): { me: Acc; logOut: () => void };
function useAccount(): { me: Acc | undefined; logOut: () => void };
function useAccount<D extends DepthsIn<Acc>>(
depth: D,
): { me: DeeplyLoaded<Acc, D> | undefined; logOut: () => void };
@@ -89,25 +89,27 @@ export function createJazzReactApp<Acc extends Account>({
): { me: Acc | DeeplyLoaded<Acc, D> | undefined; logOut: () => void } {
const context = React.useContext(JazzContext);
if (!context) {
throw new Error("useAccount must be used within a JazzProvider");
}
// if (!context) {
// throw new Error("useAccount must be used within a JazzProvider");
// }
if (!("me" in context)) {
throw new Error(
"useAccount can't be used in a JazzProvider with auth === 'guest' - consider using useAccountOrGuest()",
);
}
// if (!("me" in context)) {
// throw new Error(
// "useAccount can't be used in a JazzProvider with auth === 'guest' - consider using useAccountOrGuest()",
// );
// }
const meInContext = context && "me" in context ? context.me : undefined;
const me = useCoState<Acc, D>(
context?.me.constructor as CoValueClass<Acc>,
context?.me.id,
meInContext?.constructor as CoValueClass<Acc>,
meInContext?.id,
depth,
);
return {
me: depth === undefined ? me || context.me : me,
logOut: context.logOut,
me: depth === undefined ? me || meInContext : me,
logOut: context?.logOut || (() => {}),
};
}
@@ -154,12 +156,8 @@ export function createJazzReactApp<Acc extends Account>({
}>({ value: undefined });
const context = React.useContext(JazzContext);
if (!context) {
throw new Error("useCoState must be used within a JazzProvider");
}
useEffect(() => {
if (!id) return;
if (!id || !context) return;
return subscribeToCoValue(
Schema,
@@ -172,6 +170,10 @@ export function createJazzReactApp<Acc extends Account>({
);
}, [Schema, id, context]);
if (!context) {
return undefined;
}
return state.value;
}
@@ -236,7 +238,7 @@ export interface JazzReactApp<Acc extends Account> {
/** @category Hooks */
useAccount(): {
me: Acc;
me: Acc | undefined;
logOut: () => void;
};
/** @category Hooks */

1072
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff