let claude create the monorepo layout
This commit is contained in:
38
web/build/404.html
Normal file
38
web/build/404.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<link href="/_app/immutable/entry/start.Pj34kLt-.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/YzYuob9f.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/CUCwB180.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/DNqN6DmX.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/entry/app.H3SWXino.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/Db9w--lA.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/pTMRHjpX.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/Bzak7iHL.js" rel="modulepreload">
|
||||
<link href="/_app/immutable/chunks/C5YqYP7P.js" rel="modulepreload">
|
||||
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">
|
||||
<script>
|
||||
{
|
||||
__sveltekit_1smak34 = {
|
||||
base: ""
|
||||
};
|
||||
|
||||
const element = document.currentScript.parentElement;
|
||||
|
||||
Promise.all([
|
||||
import("/_app/immutable/entry/start.Pj34kLt-.js"),
|
||||
import("/_app/immutable/entry/app.H3SWXino.js")
|
||||
]).then(([kit, app]) => {
|
||||
kit.start(app, element);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1
web/build/_app/env.js
Normal file
1
web/build/_app/env.js
Normal file
@@ -0,0 +1 @@
|
||||
export const env={}
|
||||
1
web/build/_app/immutable/chunks/Bzak7iHL.js
Normal file
1
web/build/_app/immutable/chunks/Bzak7iHL.js
Normal file
@@ -0,0 +1 @@
|
||||
var e;typeof window<"u"&&((e=window.__svelte??(window.__svelte={})).v??(e.v=new Set)).add("5");
|
||||
1
web/build/_app/immutable/chunks/C5YqYP7P.js
Normal file
1
web/build/_app/immutable/chunks/C5YqYP7P.js
Normal file
@@ -0,0 +1 @@
|
||||
import{s as c,g as l}from"./DNqN6DmX.js";import{b as o,d as b,n as a,m as d,g as p,e as g}from"./CUCwB180.js";let s=!1,i=Symbol();function m(e,u,r){const n=r[u]??(r[u]={store:null,source:d(void 0),unsubscribe:a});if(n.store!==e&&!(i in r))if(n.unsubscribe(),n.store=e??null,e==null)n.source.v=void 0,n.unsubscribe=a;else{var t=!0;n.unsubscribe=c(e,f=>{t?n.source.v=f:g(n.source,f)}),t=!1}return e&&i in r?l(e):p(n.source)}function y(){const e={};function u(){o(()=>{for(var r in e)e[r].unsubscribe();b(e,i,{enumerable:!1,value:!0})})}return[e,u]}function N(e){var u=s;try{return s=!1,[e(),s]}finally{s=u}}export{m as a,N as c,y as s};
|
||||
1
web/build/_app/immutable/chunks/CAYgxOZ1.js
Normal file
1
web/build/_app/immutable/chunks/CAYgxOZ1.js
Normal file
@@ -0,0 +1 @@
|
||||
import{w as a}from"./CUCwB180.js";a();
|
||||
1
web/build/_app/immutable/chunks/CGowzvwH.js
Normal file
1
web/build/_app/immutable/chunks/CGowzvwH.js
Normal file
@@ -0,0 +1 @@
|
||||
import{s as e}from"./YzYuob9f.js";const r=()=>{const s=e;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},b={subscribe(s){return r().page.subscribe(s)}};export{b as p};
|
||||
1
web/build/_app/immutable/chunks/CUCwB180.js
Normal file
1
web/build/_app/immutable/chunks/CUCwB180.js
Normal file
File diff suppressed because one or more lines are too long
1
web/build/_app/immutable/chunks/DNqN6DmX.js
Normal file
1
web/build/_app/immutable/chunks/DNqN6DmX.js
Normal file
@@ -0,0 +1 @@
|
||||
import{n as c,j as a,J as p,i as d,h as l,K as m}from"./CUCwB180.js";function h(e){throw new Error("https://svelte.dev/e/lifecycle_outside_component")}function g(e,n,s){if(e==null)return n(void 0),c;const u=a(()=>e.subscribe(n,s));return u.unsubscribe?()=>u.unsubscribe():u}const i=[];function q(e,n=c){let s=null;const u=new Set;function r(o){if(p(e,o)&&(e=o,s)){const f=!i.length;for(const t of u)t[1](),i.push(t,e);if(f){for(let t=0;t<i.length;t+=2)i[t][0](i[t+1]);i.length=0}}}function b(o){r(o(e))}function _(o,f=c){const t=[o,f];return u.add(t),u.size===1&&(s=n(r,b)||c),o(e),()=>{u.delete(t),u.size===0&&s&&(s(),s=null)}}return{set:r,update:b,subscribe:_}}function k(e){let n;return g(e,s=>n=s)(),n}function x(e){l===null&&h(),m&&l.l!==null?w(l).m.push(e):d(()=>{const n=a(e);if(typeof n=="function")return n})}function w(e){var n=e.l;return n.u??(n.u={a:[],b:[],m:[]})}export{k as g,x as o,g as s,q as w};
|
||||
2
web/build/_app/immutable/chunks/Db9w--lA.js
Normal file
2
web/build/_app/immutable/chunks/Db9w--lA.js
Normal file
File diff suppressed because one or more lines are too long
1
web/build/_app/immutable/chunks/YzYuob9f.js
Normal file
1
web/build/_app/immutable/chunks/YzYuob9f.js
Normal file
File diff suppressed because one or more lines are too long
1
web/build/_app/immutable/chunks/eiK12uJk.js
Normal file
1
web/build/_app/immutable/chunks/eiK12uJk.js
Normal file
@@ -0,0 +1 @@
|
||||
import{h as d,u as g,i as c,j as m,k as l,l as b,g as p,o as h,q as k}from"./CUCwB180.js";function x(n=!1){const s=d,e=s.l.u;if(!e)return;let r=()=>h(s.s);if(n){let o=0,t={};const _=k(()=>{let i=!1;const a=s.s;for(const f in a)a[f]!==t[f]&&(t[f]=a[f],i=!0);return i&&o++,o});r=()=>p(_)}e.b.length&&g(()=>{u(s,r),l(e.b)}),c(()=>{const o=m(()=>e.m.map(b));return()=>{for(const t of o)typeof t=="function"&&t()}}),e.a.length&&c(()=>{u(s,r),l(e.a)})}function u(n,s){if(n.l.s)for(const e of n.l.s)p(e);s()}export{x as i};
|
||||
1
web/build/_app/immutable/chunks/pTMRHjpX.js
Normal file
1
web/build/_app/immutable/chunks/pTMRHjpX.js
Normal file
@@ -0,0 +1 @@
|
||||
import{x as h,y as d,z as _,A as l,B as p,T as E,C as g,D as u,E as s,R as y,F as x,G as A,H as M,I as N}from"./CUCwB180.js";var f;const i=((f=globalThis==null?void 0:globalThis.window)==null?void 0:f.trustedTypes)&&globalThis.window.trustedTypes.createPolicy("svelte-trusted-html",{createHTML:t=>t});function b(t){return(i==null?void 0:i.createHTML(t))??t}function L(t){var r=h("template");return r.innerHTML=b(t.replaceAll("<!>","<!---->")),r.content}function n(t,r){var e=_;e.nodes===null&&(e.nodes={start:t,end:r,a:null,t:null})}function w(t,r){var e=(r&E)!==0,c=(r&g)!==0,a,m=!t.startsWith("<!>");return()=>{if(u)return n(s,null),s;a===void 0&&(a=L(m?t:"<!>"+t),e||(a=l(a)));var o=c||p?document.importNode(a,!0):a.cloneNode(!0);if(e){var T=l(o),v=o.lastChild;n(T,v)}else n(o,o);return o}}function C(t=""){if(!u){var r=d(t+"");return n(r,r),r}var e=s;return e.nodeType!==A?(e.before(e=d()),M(e)):N(e),n(e,e),e}function D(){if(u)return n(s,null),s;var t=document.createDocumentFragment(),r=document.createComment(""),e=d();return t.append(r,e),n(r,e),t}function H(t,r){if(u){var e=_;((e.f&y)===0||e.nodes.end===null)&&(e.nodes.end=s),x();return}t!==null&&t.before(r)}export{H as a,n as b,D as c,w as f,C as t};
|
||||
2
web/build/_app/immutable/entry/app.H3SWXino.js
Normal file
2
web/build/_app/immutable/entry/app.H3SWXino.js
Normal file
File diff suppressed because one or more lines are too long
1
web/build/_app/immutable/entry/start.Pj34kLt-.js
Normal file
1
web/build/_app/immutable/entry/start.Pj34kLt-.js
Normal file
@@ -0,0 +1 @@
|
||||
import{l as o,a as r}from"../chunks/YzYuob9f.js";export{o as load_css,r as start};
|
||||
1
web/build/_app/immutable/nodes/0.D7-FTC_1.js
Normal file
1
web/build/_app/immutable/nodes/0.D7-FTC_1.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{o as h}from"../chunks/DNqN6DmX.js";import{D as m,F as g,p as y,f as T,a as $}from"../chunks/CUCwB180.js";import{s as _,a as k}from"../chunks/C5YqYP7P.js";import{c as v,a as w}from"../chunks/pTMRHjpX.js";import{i as A}from"../chunks/eiK12uJk.js";import{g as u}from"../chunks/YzYuob9f.js";import{p as O}from"../chunks/CGowzvwH.js";function S(t,e,a,n,o){var f;m&&g();var s=(f=e.$$slots)==null?void 0:f[a],r=!1;s===!0&&(s=e.children,r=!0),s===void 0||s(t,r?()=>n:n)}const d="https://api.campaign-manager.example.com/v1";let i=null;function E(t){i=t}async function P(){const t=await fetch(`${d}/auth/refresh`,{method:"POST",credentials:"include"});return t.ok?(i=(await t.json()).accessToken,!0):(i=null,!1)}async function c(t,e={}){const{skipAuth:a=!1,...n}=e,o=new Headers(n.headers);!a&&i&&o.set("Authorization",`Bearer ${i}`),n.body&&!o.has("Content-Type")&&o.set("Content-Type","application/json");let s=await fetch(`${d}${t}`,{...n,headers:o,credentials:"include"});return s.status===401&&!a&&await P()&&i&&(o.set("Authorization",`Bearer ${i}`),s=await fetch(`${d}${t}`,{...n,headers:o,credentials:"include"})),s}const B={get:(t,e)=>c(t,{...e,method:"GET"}),post:(t,e,a)=>c(t,{...a,method:"POST",body:e?JSON.stringify(e):void 0}),put:(t,e,a)=>c(t,{...a,method:"PUT",body:e?JSON.stringify(e):void 0}),patch:(t,e,a)=>c(t,{...a,method:"PATCH",body:e?JSON.stringify(e):void 0}),delete:(t,e)=>c(t,{...e,method:"DELETE"})};function z(t,e){y(e,!1);const a=()=>k(O,"$page",n),[n,o]=_(),s=["/login"];h(async()=>{if(!s.includes(a().url.pathname))try{const l=await B.post("/auth/refresh",void 0,{skipAuth:!0});if(l.ok){const p=await l.json();E(p.accessToken)}else u("/login")}catch{u("/login")}}),A();var r=v(),f=T(r);S(f,e,"default",{}),w(t,r),$(),o()}export{z as component};
|
||||
1
web/build/_app/immutable/nodes/1.BZR9od8C.js
Normal file
1
web/build/_app/immutable/nodes/1.BZR9od8C.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{p as h,f as g,t as l,a as v,c as e,r as o,s as d}from"../chunks/CUCwB180.js";import{s as p}from"../chunks/Db9w--lA.js";import{a as _,f as x}from"../chunks/pTMRHjpX.js";import{i as $}from"../chunks/eiK12uJk.js";import{s as k,p as m}from"../chunks/YzYuob9f.js";const b={get error(){return m.error},get status(){return m.status}};k.updated.check;const i=b;var E=x("<h1> </h1> <p> </p>",1);function C(f,c){h(c,!1),$();var t=E(),r=g(t),n=e(r,!0);o(r);var s=d(r,2),u=e(s,!0);o(s),l(()=>{var a;p(n,i.status),p(u,(a=i.error)==null?void 0:a.message)}),_(f,t),v()}export{C as component};
|
||||
1
web/build/_app/immutable/nodes/2.Zg4cVoEY.js
Normal file
1
web/build/_app/immutable/nodes/2.Zg4cVoEY.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{o as p}from"../chunks/DNqN6DmX.js";import{p as r,a as t}from"../chunks/CUCwB180.js";import{i as m}from"../chunks/eiK12uJk.js";import{g as a}from"../chunks/YzYuob9f.js";function c(i,o){r(o,!1),p(()=>a("/groups")),m(),t()}export{c as component};
|
||||
1
web/build/_app/immutable/nodes/3.BjNotmmr.js
Normal file
1
web/build/_app/immutable/nodes/3.BjNotmmr.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{a,f as t}from"../chunks/pTMRHjpX.js";var p=t("<h1>Your Characters</h1>");function n(o){var r=p();a(o,r)}export{n as component};
|
||||
1
web/build/_app/immutable/nodes/4.D0WiVz9B.js
Normal file
1
web/build/_app/immutable/nodes/4.D0WiVz9B.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{p as i,s as f,f as c,t as n,a as h,c as g,r as l}from"../chunks/CUCwB180.js";import{s as _,a as $}from"../chunks/C5YqYP7P.js";import{s as d}from"../chunks/Db9w--lA.js";import{a as u,f as v}from"../chunks/pTMRHjpX.js";import{i as x}from"../chunks/eiK12uJk.js";import{p as C}from"../chunks/CGowzvwH.js";var b=v("<h1>Character Sheet</h1> <p> </p>",1);function z(r,s){i(s,!1);const e=()=>$(C,"$page",p),[p,o]=_();x();var a=b(),t=f(c(a),2),m=g(t);l(t),n(()=>d(m,`Character ID: ${e().params.id??""}`)),u(r,a),h(),o()}export{z as component};
|
||||
1
web/build/_app/immutable/nodes/5.B-zruxZU.js
Normal file
1
web/build/_app/immutable/nodes/5.B-zruxZU.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{a as p,f as a}from"../chunks/pTMRHjpX.js";var t=a("<h1>Your Groups</h1>");function f(o){var r=t();p(o,r)}export{f as component};
|
||||
1
web/build/_app/immutable/nodes/6.Cniq5yLG.js
Normal file
1
web/build/_app/immutable/nodes/6.Cniq5yLG.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{p as i,s as f,f as n,t as c,a as l,c as g,r as _}from"../chunks/CUCwB180.js";import{s as $,a as h}from"../chunks/C5YqYP7P.js";import{s as u}from"../chunks/Db9w--lA.js";import{a as d,f as v}from"../chunks/pTMRHjpX.js";import{i as x}from"../chunks/eiK12uJk.js";import{p as D}from"../chunks/CGowzvwH.js";var G=v("<h1>Group Detail</h1> <p> </p>",1);function A(s,r){i(r,!1);const p=()=>h(D,"$page",o),[o,e]=$();x();var a=G(),t=f(n(a),2),m=g(t);_(t),c(()=>u(m,`Group ID: ${p().params.id??""}`)),d(s,a),l(),e()}export{A as component};
|
||||
1
web/build/_app/immutable/nodes/7.BjdHyPf8.js
Normal file
1
web/build/_app/immutable/nodes/7.BjdHyPf8.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{p as i,s as f,f as n,t as c,a as g,c as l,r as _}from"../chunks/CUCwB180.js";import{s as $,a as h}from"../chunks/C5YqYP7P.js";import{s as u}from"../chunks/Db9w--lA.js";import{a as d,f as v}from"../chunks/pTMRHjpX.js";import{i as x}from"../chunks/eiK12uJk.js";import{p as G}from"../chunks/CGowzvwH.js";var b=v("<h1>Group Settings</h1> <p> </p>",1);function z(a,r){i(r,!1);const p=()=>h(G,"$page",o),[o,e]=$();x();var t=b(),s=f(n(t),2),m=l(s);_(s),c(()=>u(m,`Group ID: ${p().params.id??""}`)),d(a,t),g(),e()}export{z as component};
|
||||
1
web/build/_app/immutable/nodes/8.Chqc1uyl.js
Normal file
1
web/build/_app/immutable/nodes/8.Chqc1uyl.js
Normal file
@@ -0,0 +1 @@
|
||||
import"../chunks/Bzak7iHL.js";import"../chunks/CAYgxOZ1.js";import{v as r}from"../chunks/CUCwB180.js";import{a as m,f as n}from"../chunks/pTMRHjpX.js";var p=n("<h1>Campaign Manager</h1> <p>Login — coming soon</p>",1);function g(o){var a=p();r(2),m(o,a)}export{g as component};
|
||||
1
web/build/_app/version.json
Normal file
1
web/build/_app/version.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version":"1773071061170"}
|
||||
22
web/package.json
Normal file
22
web/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@campaign-manager/web",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"svelte": "^5.0.0",
|
||||
"@campaign-manager/rulesets": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-static": "^3.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^6.0.0"
|
||||
}
|
||||
}
|
||||
12
web/src/app.html
Normal file
12
web/src/app.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
79
web/src/lib/api.ts
Normal file
79
web/src/lib/api.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
export const BASE_URL = 'https://api.campaign-manager.example.com/v1';
|
||||
|
||||
// Access token held in memory only — never persisted to localStorage
|
||||
let _accessToken: string | null = null;
|
||||
|
||||
export function setAccessToken(token: string | null): void {
|
||||
_accessToken = token;
|
||||
}
|
||||
|
||||
export function getAccessToken(): string | null {
|
||||
return _accessToken;
|
||||
}
|
||||
|
||||
interface RequestOptions extends RequestInit {
|
||||
skipAuth?: boolean;
|
||||
}
|
||||
|
||||
async function silentRefresh(): Promise<boolean> {
|
||||
// Refresh token is in an HttpOnly cookie; no manual credential needed
|
||||
const res = await fetch(`${BASE_URL}/auth/refresh`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
_accessToken = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = await res.json() as { accessToken: string };
|
||||
_accessToken = data.accessToken;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function apiFetch(path: string, options: RequestOptions = {}): Promise<Response> {
|
||||
const { skipAuth = false, ...fetchOptions } = options;
|
||||
const headers = new Headers(fetchOptions.headers);
|
||||
|
||||
if (!skipAuth && _accessToken) {
|
||||
headers.set('Authorization', `Bearer ${_accessToken}`);
|
||||
}
|
||||
|
||||
if (fetchOptions.body && !headers.has('Content-Type')) {
|
||||
headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
let res = await fetch(`${BASE_URL}${path}`, {
|
||||
...fetchOptions,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (res.status === 401 && !skipAuth) {
|
||||
const refreshed = await silentRefresh();
|
||||
if (refreshed && _accessToken) {
|
||||
headers.set('Authorization', `Bearer ${_accessToken}`);
|
||||
res = await fetch(`${BASE_URL}${path}`, { ...fetchOptions, headers, credentials: 'include' });
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: (path: string, options?: RequestOptions) =>
|
||||
apiFetch(path, { ...options, method: 'GET' }),
|
||||
|
||||
post: (path: string, body?: unknown, options?: RequestOptions) =>
|
||||
apiFetch(path, { ...options, method: 'POST', body: body ? JSON.stringify(body) : undefined }),
|
||||
|
||||
put: (path: string, body?: unknown, options?: RequestOptions) =>
|
||||
apiFetch(path, { ...options, method: 'PUT', body: body ? JSON.stringify(body) : undefined }),
|
||||
|
||||
patch: (path: string, body?: unknown, options?: RequestOptions) =>
|
||||
apiFetch(path, { ...options, method: 'PATCH', body: body ? JSON.stringify(body) : undefined }),
|
||||
|
||||
delete: (path: string, options?: RequestOptions) =>
|
||||
apiFetch(path, { ...options, method: 'DELETE' }),
|
||||
};
|
||||
27
web/src/routes/+layout.svelte
Normal file
27
web/src/routes/+layout.svelte
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { api, setAccessToken } from '$lib/api';
|
||||
|
||||
const PUBLIC_ROUTES = ['/login'];
|
||||
|
||||
onMount(async () => {
|
||||
if (PUBLIC_ROUTES.includes($page.url.pathname)) return;
|
||||
|
||||
// Attempt silent token refresh via HttpOnly cookie before rendering
|
||||
try {
|
||||
const res = await api.post('/auth/refresh', undefined, { skipAuth: true });
|
||||
if (res.ok) {
|
||||
const data = await res.json() as { accessToken: string };
|
||||
setAccessToken(data.accessToken);
|
||||
} else {
|
||||
goto('/login');
|
||||
}
|
||||
} catch {
|
||||
goto('/login');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
6
web/src/routes/+page.svelte
Normal file
6
web/src/routes/+page.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
onMount(() => goto('/groups'));
|
||||
</script>
|
||||
6
web/src/routes/characters/+page.svelte
Normal file
6
web/src/routes/characters/+page.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
// TODO: fetch and display owner's characters
|
||||
</script>
|
||||
|
||||
<!-- TODO: Character list -->
|
||||
<h1>Your Characters</h1>
|
||||
8
web/src/routes/characters/[id]/+page.svelte
Normal file
8
web/src/routes/characters/[id]/+page.svelte
Normal file
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
// TODO: fetch character by $page.params.id and render via ruleset plugin
|
||||
</script>
|
||||
|
||||
<!-- TODO: Character sheet (web) -->
|
||||
<h1>Character Sheet</h1>
|
||||
<p>Character ID: {$page.params.id}</p>
|
||||
6
web/src/routes/groups/+page.svelte
Normal file
6
web/src/routes/groups/+page.svelte
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
// TODO: fetch and display user's groups
|
||||
</script>
|
||||
|
||||
<!-- TODO: Group list -->
|
||||
<h1>Your Groups</h1>
|
||||
8
web/src/routes/groups/[id]/+page.svelte
Normal file
8
web/src/routes/groups/[id]/+page.svelte
Normal file
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
// TODO: fetch group by $page.params.id
|
||||
</script>
|
||||
|
||||
<!-- TODO: Group detail -->
|
||||
<h1>Group Detail</h1>
|
||||
<p>Group ID: {$page.params.id}</p>
|
||||
8
web/src/routes/groups/[id]/settings/+page.svelte
Normal file
8
web/src/routes/groups/[id]/settings/+page.svelte
Normal file
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
// TODO: DM-only group settings; enforce authorization in layout or server load
|
||||
</script>
|
||||
|
||||
<!-- TODO: Group settings (DM only) -->
|
||||
<h1>Group Settings</h1>
|
||||
<p>Group ID: {$page.params.id}</p>
|
||||
7
web/src/routes/login/+page.svelte
Normal file
7
web/src/routes/login/+page.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
// TODO: Login/Register form
|
||||
</script>
|
||||
|
||||
<!-- TODO: Login / Register form -->
|
||||
<h1>Campaign Manager</h1>
|
||||
<p>Login — coming soon</p>
|
||||
8
web/svelte.config.js
Normal file
8
web/svelte.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
export default {
|
||||
kit: {
|
||||
adapter: adapter({ fallback: '404.html' }),
|
||||
},
|
||||
};
|
||||
6
web/tsconfig.json
Normal file
6
web/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
6
web/vite.config.ts
Normal file
6
web/vite.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
});
|
||||
Reference in New Issue
Block a user