create-partylayer-app 0.2.0 → 0.4.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/vanilla/README.md +36 -0
- package/templates/vanilla/_gitignore +7 -0
- package/templates/vanilla/_package.json +18 -0
- package/templates/vanilla/index.html +12 -0
- package/templates/vanilla/src/main.ts +77 -0
- package/templates/vanilla/src/style.css +45 -0
- package/templates/vanilla/tsconfig.json +19 -0
- package/templates/vanilla/vite.config.ts +4 -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,EAqBnC,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD"}
|
package/dist/templates.js
CHANGED
|
@@ -9,6 +9,16 @@ export const TEMPLATES = [
|
|
|
9
9
|
label: 'Next.js (SSR)',
|
|
10
10
|
hint: 'App Router, server-side session via cookieStorage',
|
|
11
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
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'vanilla',
|
|
19
|
+
label: 'Vanilla TypeScript',
|
|
20
|
+
hint: 'No framework — Vite + the @partylayer/sdk client API',
|
|
21
|
+
},
|
|
12
22
|
];
|
|
13
23
|
export function isTemplateId(value) {
|
|
14
24
|
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;IACD;QACE,EAAE,EAAE,UAAU;QACd,KAAK,EAAE,eAAe;QACtB,IAAI,EAAE,mDAAmD;KAC1D;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;IACD;QACE,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,sDAAsD;KAC7D;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,36 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A [PartyLayer](https://partylayer.xyz) dApp — vanilla TypeScript + Vite, no framework.
|
|
4
|
+
|
|
5
|
+
## Develop
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## How it's wired
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createPartyLayer } from '@partylayer/sdk';
|
|
15
|
+
|
|
16
|
+
const client = createPartyLayer({ network: 'devnet', app: { name: '{{PROJECT_NAME}}' } });
|
|
17
|
+
|
|
18
|
+
const wallets = await client.listWallets(); // verified wallets from the registry
|
|
19
|
+
const session = await client.connect({ walletId }); // connect to one
|
|
20
|
+
client.on('session:connected', () => { /* … */ }); // react to session changes
|
|
21
|
+
client.on('session:disconnected', () => { /* … */ });
|
|
22
|
+
const active = await client.getActiveSession(); // restore on load
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`createPartyLayer` is the dApp connect API — no framework bindings needed. See `src/main.ts` for the full hand-rolled connect UI. Switch networks by changing `network` to `"testnet"` or `"mainnet"`.
|
|
26
|
+
|
|
27
|
+
## Build
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm run build && npm run preview
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Docs
|
|
34
|
+
|
|
35
|
+
- [PartyLayer docs](https://partylayer.xyz/docs/introduction)
|
|
36
|
+
- [@partylayer/sdk](https://www.npmjs.com/package/@partylayer/sdk)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@partylayer/sdk": "^0.10.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"typescript": "^5.6.2",
|
|
16
|
+
"vite": "^5.4.2"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{PROJECT_NAME}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createPartyLayer, type Session } from '@partylayer/sdk';
|
|
2
|
+
import './style.css';
|
|
3
|
+
|
|
4
|
+
// The PartyLayer client — the dApp connect API (listWallets / connect / session).
|
|
5
|
+
// All built-in wallet adapters are auto-registered.
|
|
6
|
+
const client = createPartyLayer({ network: 'devnet', app: { name: '{{PROJECT_NAME}}' } });
|
|
7
|
+
|
|
8
|
+
const app = document.querySelector<HTMLDivElement>('#app')!;
|
|
9
|
+
app.innerHTML = `
|
|
10
|
+
<main class="app">
|
|
11
|
+
<h1>{{PROJECT_NAME}}</h1>
|
|
12
|
+
<p class="subtitle">A PartyLayer dApp on Canton — vanilla TypeScript, no framework.</p>
|
|
13
|
+
<div id="status" class="status"></div>
|
|
14
|
+
<button id="connect" class="btn">Connect Wallet</button>
|
|
15
|
+
<ul id="wallets" class="wallets" hidden></ul>
|
|
16
|
+
</main>
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const statusEl = document.querySelector<HTMLDivElement>('#status')!;
|
|
20
|
+
const connectBtn = document.querySelector<HTMLButtonElement>('#connect')!;
|
|
21
|
+
const walletsEl = document.querySelector<HTMLUListElement>('#wallets')!;
|
|
22
|
+
|
|
23
|
+
const truncateParty = (p: string) => (p.length <= 16 ? p : `${p.slice(0, 10)}…${p.slice(-4)}`);
|
|
24
|
+
|
|
25
|
+
function renderConnected(session: Session) {
|
|
26
|
+
walletsEl.hidden = true;
|
|
27
|
+
connectBtn.hidden = true;
|
|
28
|
+
statusEl.innerHTML = `Connected as <code>${truncateParty(session.partyId)}</code>`;
|
|
29
|
+
const disconnect = document.createElement('button');
|
|
30
|
+
disconnect.className = 'btn';
|
|
31
|
+
disconnect.textContent = 'Disconnect';
|
|
32
|
+
disconnect.addEventListener('click', () => void client.disconnect());
|
|
33
|
+
statusEl.appendChild(disconnect);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function renderDisconnected() {
|
|
37
|
+
statusEl.textContent = 'Not connected — connect a wallet to continue.';
|
|
38
|
+
connectBtn.hidden = false;
|
|
39
|
+
walletsEl.hidden = true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Connect → list the verified wallets from the registry → pick one → connect.
|
|
43
|
+
connectBtn.addEventListener('click', async () => {
|
|
44
|
+
const wallets = await client.listWallets();
|
|
45
|
+
walletsEl.innerHTML = wallets
|
|
46
|
+
.map(
|
|
47
|
+
(w, i) => `<li>
|
|
48
|
+
<button class="wallet" data-idx="${i}">
|
|
49
|
+
${w.icons?.md ? `<img src="${w.icons.md}" alt="" width="24" height="24" />` : ''}
|
|
50
|
+
<span>${w.name}</span>
|
|
51
|
+
</button>
|
|
52
|
+
</li>`,
|
|
53
|
+
)
|
|
54
|
+
.join('');
|
|
55
|
+
walletsEl.hidden = false;
|
|
56
|
+
walletsEl.querySelectorAll<HTMLButtonElement>('.wallet').forEach((btn) => {
|
|
57
|
+
btn.addEventListener('click', async () => {
|
|
58
|
+
// Pass the WalletInfo's walletId (a branded WalletId), not a raw string.
|
|
59
|
+
const wallet = wallets[Number(btn.dataset.idx)];
|
|
60
|
+
try {
|
|
61
|
+
const session = await client.connect({ walletId: wallet.walletId });
|
|
62
|
+
renderConnected(session);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
statusEl.textContent = `Connect failed: ${(err as Error).message}`;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Keep the UI in sync with external session changes (multi-tab, expiry, restore).
|
|
71
|
+
client.on('session:connected', () => {
|
|
72
|
+
void client.getActiveSession().then((s) => s && renderConnected(s));
|
|
73
|
+
});
|
|
74
|
+
client.on('session:disconnected', () => renderDisconnected());
|
|
75
|
+
|
|
76
|
+
// Initial state — restore an existing session if there is one.
|
|
77
|
+
void client.getActiveSession().then((s) => (s ? renderConnected(s) : renderDisconnected()));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
3
|
+
color-scheme: light dark;
|
|
4
|
+
background: #0b0f1a;
|
|
5
|
+
color: rgba(255, 255, 255, 0.92);
|
|
6
|
+
}
|
|
7
|
+
* { box-sizing: border-box; }
|
|
8
|
+
body { margin: 0; }
|
|
9
|
+
.app {
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
align-items: center;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
gap: 16px;
|
|
16
|
+
padding: 24px;
|
|
17
|
+
text-align: center;
|
|
18
|
+
}
|
|
19
|
+
.app h1 { font-size: 2.5rem; margin: 0; }
|
|
20
|
+
.subtitle { margin: 0 0 8px; color: rgba(255, 255, 255, 0.6); max-width: 32rem; }
|
|
21
|
+
.status { display: flex; align-items: center; gap: 12px; }
|
|
22
|
+
.status code { background: rgba(255, 255, 255, 0.08); padding: 2px 8px; border-radius: 6px; }
|
|
23
|
+
.btn {
|
|
24
|
+
padding: 10px 18px;
|
|
25
|
+
border-radius: 8px;
|
|
26
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
27
|
+
background: #ffcc00;
|
|
28
|
+
color: #0b0f1a;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
}
|
|
32
|
+
.wallets { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; width: 100%; max-width: 20rem; }
|
|
33
|
+
.wallet {
|
|
34
|
+
width: 100%;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: 10px;
|
|
38
|
+
padding: 10px 14px;
|
|
39
|
+
border-radius: 8px;
|
|
40
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
41
|
+
background: rgba(255, 255, 255, 0.04);
|
|
42
|
+
color: inherit;
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
.wallet img { border-radius: 6px; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"]
|
|
19
|
+
}
|
|
@@ -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
|
+
});
|