create-partylayer-app 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +10 -0
- package/dist/templates.js.map +1 -1
- package/package.json +1 -1
- package/templates/next-ssr/README.md +28 -0
- package/templates/next-ssr/_gitignore +10 -0
- package/templates/next-ssr/_package.json +26 -0
- package/templates/next-ssr/app/globals.css +42 -0
- package/templates/next-ssr/app/layout.tsx +18 -0
- package/templates/next-ssr/app/page.tsx +33 -0
- package/templates/next-ssr/app/providers.tsx +19 -0
- package/templates/next-ssr/components/ConnectButton.tsx +8 -0
- package/templates/next-ssr/lib/session.ts +41 -0
- package/templates/next-ssr/next.config.mjs +4 -0
- package/templates/next-ssr/tsconfig.json +23 -0
- package/templates/vue-nuxt-pinia/README.md +27 -0
- package/templates/vue-nuxt-pinia/_gitignore +10 -0
- package/templates/vue-nuxt-pinia/_package.json +23 -0
- package/templates/vue-nuxt-pinia/app.vue +73 -0
- package/templates/vue-nuxt-pinia/lib/session.ts +46 -0
- package/templates/vue-nuxt-pinia/nuxt.config.ts +6 -0
- package/templates/vue-nuxt-pinia/plugins/partylayer.client.ts +19 -0
- package/templates/vue-nuxt-pinia/stores/session.ts +16 -0
- package/templates/vue-nuxt-pinia/tsconfig.json +4 -0
package/dist/templates.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,SAAS,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,SAAS,EAAE,YAAY,EAgBnC,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD"}
|
package/dist/templates.js
CHANGED
|
@@ -4,6 +4,16 @@ export const TEMPLATES = [
|
|
|
4
4
|
label: 'React + Vite',
|
|
5
5
|
hint: 'React 18, Vite, zero-config PartyLayerKit',
|
|
6
6
|
},
|
|
7
|
+
{
|
|
8
|
+
id: 'next-ssr',
|
|
9
|
+
label: 'Next.js (SSR)',
|
|
10
|
+
hint: 'App Router, server-side session via cookieStorage',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: 'vue-nuxt-pinia',
|
|
14
|
+
label: 'Vue + Nuxt + Pinia',
|
|
15
|
+
hint: 'Nuxt 3 SSR session, PartyLayer session in a Pinia store',
|
|
16
|
+
},
|
|
7
17
|
];
|
|
8
18
|
export function isTemplateId(value) {
|
|
9
19
|
return TEMPLATES.some((t) => t.id === value);
|
package/dist/templates.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,2CAA2C;KAClD;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;AAC/C,CAAC"}
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,SAAS,GAAmB;IACvC;QACE,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,cAAc;QACrB,IAAI,EAAE,2CAA2C;KAClD;IACD;QACE,EAAE,EAAE,UAAU;QACd,KAAK,EAAE,eAAe;QACtB,IAAI,EAAE,mDAAmD;KAC1D;IACD;QACE,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,yDAAyD;KAChE;CACF,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;AAC/C,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A [PartyLayer](https://partylayer.xyz) dApp — Next.js App Router with **SSR session**.
|
|
4
|
+
|
|
5
|
+
## Develop
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## How SSR session works
|
|
12
|
+
|
|
13
|
+
- **Server** ([lib/session.ts](lib/session.ts)) reads the session cookie via `next/headers` `cookies()` + `@partylayer/session`'s `createCookieStorage` → the connected party is rendered in the **initial HTML** (no disconnected→connected flash). `app/page.tsx` is a Server Component that gates on `getServerSession()`.
|
|
14
|
+
- **Client** ([app/providers.tsx](app/providers.tsx)) wraps the app in `PartyLayerKit` with `sessionOptions={{ storage: createCookieStorage() }}` — `document.cookie` (synchronous), the **same** cookie the server reads.
|
|
15
|
+
- `next/headers` is imported only in your app (`lib/session.ts`), never in `@partylayer/session` — the library stays framework-agnostic.
|
|
16
|
+
|
|
17
|
+
Switch networks by changing `network` in `app/providers.tsx` and `appName` accordingly.
|
|
18
|
+
|
|
19
|
+
## Build
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run build && npm start
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Docs
|
|
26
|
+
|
|
27
|
+
- [PartyLayer docs](https://partylayer.xyz/docs/introduction)
|
|
28
|
+
- [@partylayer/session — createCookieStorage](https://www.npmjs.com/package/@partylayer/session)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"lint": "next lint"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@partylayer/react": "^0.9.1",
|
|
14
|
+
"@partylayer/sdk": "^0.10.0",
|
|
15
|
+
"@partylayer/session": "^1.1.0",
|
|
16
|
+
"next": "^14.2.5",
|
|
17
|
+
"react": "^18.3.1",
|
|
18
|
+
"react-dom": "^18.3.1"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.11.0",
|
|
22
|
+
"@types/react": "^18.3.3",
|
|
23
|
+
"@types/react-dom": "^18.3.0",
|
|
24
|
+
"typescript": "^5.6.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: light dark;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
* {
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
margin: 0;
|
|
11
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
12
|
+
background: #0b0f1a;
|
|
13
|
+
color: rgba(255, 255, 255, 0.92);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.app {
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
gap: 16px;
|
|
23
|
+
padding: 24px;
|
|
24
|
+
text-align: center;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.app h1 {
|
|
28
|
+
font-size: 2.5rem;
|
|
29
|
+
margin: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.subtitle {
|
|
33
|
+
margin: 0 0 8px;
|
|
34
|
+
color: rgba(255, 255, 255, 0.6);
|
|
35
|
+
max-width: 32rem;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.session code {
|
|
39
|
+
background: rgba(255, 255, 255, 0.08);
|
|
40
|
+
padding: 2px 8px;
|
|
41
|
+
border-radius: 6px;
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Providers } from './providers';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
title: '{{PROJECT_NAME}}',
|
|
7
|
+
description: 'A PartyLayer dApp on Canton (Next.js App Router + SSR session).',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
11
|
+
return (
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<body>
|
|
14
|
+
<Providers>{children}</Providers>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getServerSession, truncateParty } from '@/lib/session';
|
|
2
|
+
import { ConnectButton } from '@/components/ConnectButton';
|
|
3
|
+
|
|
4
|
+
// Reading cookies() opts this route into dynamic (per-request) rendering — which
|
|
5
|
+
// is exactly what SSR session needs: the server reads the session cookie on
|
|
6
|
+
// every request and renders the connected state into the initial HTML.
|
|
7
|
+
export default function Home() {
|
|
8
|
+
const session = getServerSession();
|
|
9
|
+
const party = session?.account?.partyId ?? null;
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<main className="app">
|
|
13
|
+
<h1>{{PROJECT_NAME}}</h1>
|
|
14
|
+
<p className="subtitle">A PartyLayer dApp on Canton — connect any verified wallet.</p>
|
|
15
|
+
|
|
16
|
+
{/* SERVER-RENDERED connected state (Option A: server owns the display). This
|
|
17
|
+
appears in the initial HTML from the cookie, before any client JS, so
|
|
18
|
+
there is no disconnected→connected flash. */}
|
|
19
|
+
{party ? (
|
|
20
|
+
<p className="session">
|
|
21
|
+
Connected as <code data-testid="server-party">{truncateParty(party)}</code>
|
|
22
|
+
</p>
|
|
23
|
+
) : (
|
|
24
|
+
<p className="session" data-testid="server-cta">
|
|
25
|
+
Not connected — connect a wallet to continue.
|
|
26
|
+
</p>
|
|
27
|
+
)}
|
|
28
|
+
|
|
29
|
+
{/* Interactive client island — reconciles silently with the live provider. */}
|
|
30
|
+
<ConnectButton />
|
|
31
|
+
</main>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo, type ReactNode } from 'react';
|
|
4
|
+
import { PartyLayerKit } from '@partylayer/react';
|
|
5
|
+
import { createCookieStorage } from '@partylayer/session';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Client provider boundary. cookieStorage on the client uses document.cookie
|
|
9
|
+
* (synchronous → flash-free), the SAME cookie the server reads in lib/session.ts
|
|
10
|
+
* — so server HTML and client hydration agree.
|
|
11
|
+
*/
|
|
12
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
13
|
+
const storage = useMemo(() => createCookieStorage(), []);
|
|
14
|
+
return (
|
|
15
|
+
<PartyLayerKit network="devnet" appName="{{PROJECT_NAME}}" sessionOptions={{ storage }}>
|
|
16
|
+
{children}
|
|
17
|
+
</PartyLayerKit>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cookies } from 'next/headers';
|
|
2
|
+
import {
|
|
3
|
+
createCookieStorage,
|
|
4
|
+
decodeSessionEnvelope,
|
|
5
|
+
type CookieAdapter,
|
|
6
|
+
type PersistedSessionSnapshot,
|
|
7
|
+
} from '@partylayer/session';
|
|
8
|
+
|
|
9
|
+
// next/headers is imported HERE (the app), never inside @partylayer/session —
|
|
10
|
+
// the library stays framework-agnostic. This adapter wraps Next's read-only
|
|
11
|
+
// request cookies into the CookieAdapter shape createCookieStorage expects.
|
|
12
|
+
function nextCookieAdapter(): CookieAdapter {
|
|
13
|
+
const jar = cookies();
|
|
14
|
+
return {
|
|
15
|
+
get: (name) => jar.get(name)?.value ?? null,
|
|
16
|
+
// Server Components can only READ cookies; writes happen client-side
|
|
17
|
+
// (document.cookie) or in a Route Handler / Server Action.
|
|
18
|
+
set: () => {},
|
|
19
|
+
remove: () => {},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read the persisted session ON THE SERVER (for SSR). Returns the decoded
|
|
25
|
+
* snapshot (party, network, …) or null. This is what lets a Server Component
|
|
26
|
+
* render the connected state in the initial HTML — before any client JS.
|
|
27
|
+
*/
|
|
28
|
+
export function getServerSession(): PersistedSessionSnapshot | null {
|
|
29
|
+
const storage = createCookieStorage({ adapter: nextCookieAdapter() });
|
|
30
|
+
// Cookie reads are synchronous; the SessionStorage interface types getItem as
|
|
31
|
+
// string | null | Promise, so narrow to a string (null/absent → no session).
|
|
32
|
+
const raw = storage.getItem('pl_session');
|
|
33
|
+
if (typeof raw !== 'string') return null;
|
|
34
|
+
return decodeSessionEnvelope(raw);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Pure, server-safe party-id truncation (avoids importing a client module). */
|
|
38
|
+
export function truncateParty(partyId: string): string {
|
|
39
|
+
if (partyId.length <= 16) return partyId;
|
|
40
|
+
return `${partyId.slice(0, 10)}…${partyId.slice(-4)}`;
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
22
|
+
"exclude": ["node_modules"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A [PartyLayer](https://partylayer.xyz) dApp — Nuxt 3 + Vue 3 + Pinia, with **SSR session**.
|
|
4
|
+
|
|
5
|
+
## Develop
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## How it's wired
|
|
12
|
+
|
|
13
|
+
- **Server** ([lib/session.ts](lib/session.ts)) reads the session cookie via Nuxt's SSR-aware `useCookie()`, wrapped as a `CookieAdapter` and fed to `@partylayer/session`'s `createCookieStorage` → the connected party renders in the **initial HTML** (no flash). `app.vue` gates on `getServerSession()`.
|
|
14
|
+
- **Client** ([plugins/partylayer.client.ts](plugins/partylayer.client.ts)) creates the PartyLayer client and provides the session store (`createPartyLayerSession`), using `createCookieStorage()` (the same cookie, `document.cookie`).
|
|
15
|
+
- **Pinia** ([stores/session.ts](stores/session.ts)) surfaces the PartyLayer session (`isConnected` / `party` / `status` + `connect`/`disconnect`) as a store — components read the store. This is the idiomatic "PartyLayer + Pinia" pattern.
|
|
16
|
+
- Nuxt's cookie API is used only in your app (`lib/session.ts`), never in `@partylayer/session` — the library stays framework-agnostic (the same `CookieAdapter` the Next template feeds with `cookies()`).
|
|
17
|
+
|
|
18
|
+
## Build
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm run build && npm run preview
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Docs
|
|
25
|
+
|
|
26
|
+
- [PartyLayer docs](https://partylayer.xyz/docs/introduction)
|
|
27
|
+
- [@partylayer/vue composables](https://www.npmjs.com/package/@partylayer/vue)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "nuxt dev",
|
|
7
|
+
"build": "nuxt build",
|
|
8
|
+
"preview": "nuxt preview",
|
|
9
|
+
"generate": "nuxt generate",
|
|
10
|
+
"postinstall": "nuxt prepare"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@partylayer/sdk": "^0.10.0",
|
|
14
|
+
"@partylayer/session": "^1.1.0",
|
|
15
|
+
"@partylayer/vue": "^0.1.3",
|
|
16
|
+
"@pinia/nuxt": "^0.5.5",
|
|
17
|
+
"pinia": "^2.2.0",
|
|
18
|
+
"vue": "^3.5.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"nuxt": "^3.13.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { getServerSession, truncateParty } from './lib/session';
|
|
3
|
+
import { useSessionStore } from './stores/session';
|
|
4
|
+
|
|
5
|
+
// SSR-aware cookie → server-rendered connected party (Option A: the server owns
|
|
6
|
+
// the display, so there's no disconnected→connected flash in the initial HTML).
|
|
7
|
+
const cookie = useCookie<string | null>('pl_session');
|
|
8
|
+
const session = getServerSession(cookie);
|
|
9
|
+
const serverParty = session?.account?.partyId ?? null;
|
|
10
|
+
|
|
11
|
+
// Pinia store = the reactive client session (the interactive island).
|
|
12
|
+
const sessionStore = useSessionStore();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<main class="app">
|
|
17
|
+
<h1>{{PROJECT_NAME}}</h1>
|
|
18
|
+
<p class="subtitle">A PartyLayer dApp on Canton — Nuxt 3 + Pinia, SSR session.</p>
|
|
19
|
+
|
|
20
|
+
<!-- SERVER-RENDERED party from the cookie (initial HTML, no flash) -->
|
|
21
|
+
<p v-if="serverParty" class="session">
|
|
22
|
+
Connected as <code data-testid="server-party">{{ truncateParty(serverParty) }}</code>
|
|
23
|
+
</p>
|
|
24
|
+
<p v-else class="session" data-testid="server-cta">
|
|
25
|
+
Not connected — connect a wallet to continue.
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
<!-- Pinia-backed interactive session (reads the Pinia store, not the composables) -->
|
|
29
|
+
<button v-if="!sessionStore.isConnected" class="btn" @click="sessionStore.connect()">
|
|
30
|
+
Connect Wallet
|
|
31
|
+
</button>
|
|
32
|
+
<div v-else class="connected">
|
|
33
|
+
<code>{{ truncateParty(sessionStore.party ?? '') }}</code>
|
|
34
|
+
<button class="btn" @click="sessionStore.disconnect()">Disconnect</button>
|
|
35
|
+
</div>
|
|
36
|
+
</main>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<style>
|
|
40
|
+
:root { color-scheme: light dark; }
|
|
41
|
+
body {
|
|
42
|
+
margin: 0;
|
|
43
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
44
|
+
background: #0b0f1a;
|
|
45
|
+
color: rgba(255, 255, 255, 0.92);
|
|
46
|
+
}
|
|
47
|
+
.app {
|
|
48
|
+
min-height: 100vh;
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
gap: 16px;
|
|
54
|
+
padding: 24px;
|
|
55
|
+
text-align: center;
|
|
56
|
+
}
|
|
57
|
+
.app h1 { font-size: 2.5rem; margin: 0; }
|
|
58
|
+
.subtitle { margin: 0 0 8px; color: rgba(255, 255, 255, 0.6); max-width: 32rem; }
|
|
59
|
+
.session code, .connected code {
|
|
60
|
+
background: rgba(255, 255, 255, 0.08);
|
|
61
|
+
padding: 2px 8px;
|
|
62
|
+
border-radius: 6px;
|
|
63
|
+
}
|
|
64
|
+
.btn {
|
|
65
|
+
padding: 10px 18px;
|
|
66
|
+
border-radius: 8px;
|
|
67
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
68
|
+
background: #ffcc00;
|
|
69
|
+
color: #0b0f1a;
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCookieStorage,
|
|
3
|
+
decodeSessionEnvelope,
|
|
4
|
+
type CookieAdapter,
|
|
5
|
+
type PersistedSessionSnapshot,
|
|
6
|
+
} from '@partylayer/session';
|
|
7
|
+
import type { Ref } from 'vue';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wrap a Nuxt `useCookie()` ref as a `CookieAdapter`. `useCookie` is SSR-aware:
|
|
11
|
+
* on the server it reads the request's Cookie header, on the client it reads
|
|
12
|
+
* `document.cookie`. This is the SAME `CookieAdapter` interface next-ssr feeds
|
|
13
|
+
* with `next/headers` `cookies()` — different framework, one primitive. Nuxt's
|
|
14
|
+
* cookie API lives ONLY here (the app), never inside `@partylayer/session`.
|
|
15
|
+
*/
|
|
16
|
+
export function nuxtCookieAdapter(cookieRef: Ref<string | null | undefined>): CookieAdapter {
|
|
17
|
+
return {
|
|
18
|
+
get: () => cookieRef.value ?? null,
|
|
19
|
+
set: (_name, value) => {
|
|
20
|
+
cookieRef.value = value;
|
|
21
|
+
},
|
|
22
|
+
remove: () => {
|
|
23
|
+
cookieRef.value = null;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read the persisted session on the server (for SSR) from the cookie ref, so a
|
|
30
|
+
* page can render the connected party in the initial HTML — before any client JS.
|
|
31
|
+
*/
|
|
32
|
+
export function getServerSession(
|
|
33
|
+
cookieRef: Ref<string | null | undefined>,
|
|
34
|
+
): PersistedSessionSnapshot | null {
|
|
35
|
+
const storage = createCookieStorage({ adapter: nuxtCookieAdapter(cookieRef) });
|
|
36
|
+
// Cookie reads are synchronous; narrow the MaybePromise interface type.
|
|
37
|
+
const raw = storage.getItem('pl_session');
|
|
38
|
+
if (typeof raw !== 'string') return null;
|
|
39
|
+
return decodeSessionEnvelope(raw);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Pure, server-safe party-id truncation. */
|
|
43
|
+
export function truncateParty(partyId: string): string {
|
|
44
|
+
if (partyId.length <= 16) return partyId;
|
|
45
|
+
return `${partyId.slice(0, 10)}…${partyId.slice(-4)}`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createPartyLayer } from '@partylayer/sdk';
|
|
2
|
+
import { createPartyLayerSession } from '@partylayer/vue';
|
|
3
|
+
import { createCookieStorage } from '@partylayer/session';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Client-only plugin: create the PartyLayer client and provide the session store
|
|
7
|
+
* app-wide. cookieStorage on the client uses document.cookie — the SAME cookie
|
|
8
|
+
* the server reads for SSR (lib/session.ts). The connected/wallet UI is
|
|
9
|
+
* client-side; the server-rendered party comes from the cookie directly.
|
|
10
|
+
*/
|
|
11
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
12
|
+
const client = createPartyLayer({ network: 'devnet', app: { name: '{{PROJECT_NAME}}' } });
|
|
13
|
+
nuxtApp.vueApp.use(
|
|
14
|
+
createPartyLayerSession({
|
|
15
|
+
provider: client.asProvider(),
|
|
16
|
+
storage: createCookieStorage(),
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import { useSession, useAccount } from '@partylayer/vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pinia store surfacing the PartyLayer session — the idiomatic
|
|
6
|
+
* "PartyLayer + Pinia" pattern. Components read THIS store instead of the
|
|
7
|
+
* composables directly. The composables are SSR-safe (with no provided store
|
|
8
|
+
* they report a disconnected session), so this store is safe during SSR too;
|
|
9
|
+
* the live state comes in on the client once the plugin provides the store.
|
|
10
|
+
*/
|
|
11
|
+
export const useSessionStore = defineStore('partylayer-session', () => {
|
|
12
|
+
const { status, isConnected, isConnecting, connect, disconnect } = useSession();
|
|
13
|
+
const { party, networkId } = useAccount();
|
|
14
|
+
|
|
15
|
+
return { status, isConnected, isConnecting, party, networkId, connect, disconnect };
|
|
16
|
+
});
|