@sevenfold/setto-client 0.3.4 → 0.5.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/README.md CHANGED
@@ -8,29 +8,34 @@ Editors authenticate via Supabase, edit text inline on the live page, pick secti
8
8
 
9
9
  ## Install
10
10
 
11
- ```bash
12
- bun add @setto/client@npm:@sevenfold/setto-client
11
+ Sevenfold sites pin a git tag, not an npm version, so changes ship without an npm round-trip:
12
+
13
+ ```json
14
+ "@setto/client": "github:nitech/setto-client#v0.5.0"
13
15
  ```
14
16
 
15
- Or add directly to `package.json`:
17
+ Bun clones the repo on `bun install`, runs the package's `prepare` script to build `dist/`, and links it as `@setto/client`. Vercel does the same automatically when it installs build deps.
16
18
 
17
- ```json
18
- "@setto/client": "npm:@sevenfold/setto-client@^0.1.0"
19
+ The package is also still published to npm (`@sevenfold/setto-client`) for external consumers:
20
+
21
+ ```bash
22
+ bun add @setto/client@npm:@sevenfold/setto-client
19
23
  ```
20
24
 
21
25
  Peer deps: `react`, `react-dom`, `react-i18next`, `i18next`.
22
26
 
23
- For local monorepo development alongside `carryon.no`, a `file:` link also works — see [Local development](#local-development-sevenfold-monorepo).
27
+ For local monorepo development the consumer's `vite.config.ts` aliases `@setto/client` to setto-client source — see [Local development](#local-development-sevenfold-monorepo).
24
28
 
25
- ### Publishing a new version
29
+ ### Releasing a new version
26
30
 
27
- 1. Bump `version` in `package.json`
28
- 2. Commit and push a tag: `git tag v0.1.1 && git push origin v0.1.1`
29
- 3. GitHub Actions publishes to npm automatically
31
+ 1. Bump `version` in `package.json` following semver (patch / minor / major).
32
+ 2. Commit and tag: `git tag v0.5.1 && git push origin main --follow-tags`.
33
+ 3. In each consumer (`carryon.no`, `setto-site`):
34
+ - Update the tag in `package.json` (e.g. `#v0.5.0` → `#v0.5.1`).
35
+ - Run `bun install` to refresh the lockfile.
36
+ - Push — Vercel pulls the new tag on the next deploy.
30
37
 
31
- Requires an `NPM_TOKEN` secret in the GitHub repo (Granular Access Token from npmjs.com with write access to `@sevenfold`).
32
-
33
- Or trigger manually via **Actions → Publish to npm → Run workflow**.
38
+ GitHub Actions still publishes to npm automatically when `package.json` lands on `main` (requires `NPM_TOKEN` secret), but Sevenfold consumers no longer wait for it.
34
39
 
35
40
  ---
36
41
 
@@ -67,7 +72,9 @@ createRoot(document.getElementById('root')!).render(
67
72
  );
68
73
  ```
69
74
 
70
- ### 2. Mount the admin route
75
+ ### 2. Mount the Setto route
76
+
77
+ Editors sign in once at `sitenavn.no/setto`. After that, every visit to the public site auto-enters edit mode for as long as the Supabase session persists.
71
78
 
72
79
  ```tsx
73
80
  // App.tsx
@@ -75,7 +82,7 @@ import { SettoAdminApp } from '@setto/client';
75
82
 
76
83
  <Routes>
77
84
  <Route path="/" element={<Home />} />
78
- <Route path="/admin/*" element={<SettoAdminApp />} />
85
+ <Route path="/setto/*" element={<SettoAdminApp />} />
79
86
  </Routes>
80
87
  ```
81
88
 
@@ -124,24 +131,32 @@ function ValuesSection() {
124
131
 
125
132
  ## Edit mode
126
133
 
127
- Edit mode activates when **both** are true:
134
+ Edit mode activates whenever **all** of these are true:
128
135
 
129
- 1. Authenticated Supabase session.
130
- 2. URL contains `?setto=edit`.
136
+ 1. The user has an authenticated Supabase session.
137
+ 2. The user has access to `config.siteId` (a row in the `sites` table that they can read).
138
+ 3. The current path is not `/setto` (the dashboard is rendered without inline editing).
131
139
 
132
- The `/admin` dashboard does **not** activate edit mode. Use **Begynn å redigere**, which navigates to `/?setto=edit`.
140
+ Sign-in at `/setto` persists via Supabase, so subsequent visits drop straight into edit mode no URL flag, no extra step.
133
141
 
134
142
  ### What editors see
135
143
 
144
+ A small round Setto button floats in the bottom-right corner. While there are no unpublished changes it stays as a circle showing only the Setto mark. As soon as you edit something it grows into a pill containing **Publiser** and a **⋯** menu with:
145
+
146
+ | Item | Effect |
147
+ |------|--------|
148
+ | **Avbryt** | Discards every unsaved draft (text, section colours, image uploads) |
149
+ | **Logg ut** | Signs out via Supabase and exits edit mode |
150
+ | **Historikk** | Opens the `/setto` dashboard with deployment history |
151
+
136
152
  | Action | How |
137
153
  |--------|-----|
138
154
  | Edit text | Click any `<T>` element — it becomes `contentEditable` |
139
155
  | Edit section colours | Click a section or block background (not text) |
140
156
  | Follow a link | Ctrl/Cmd + click (desktop) · **Naviger ↗** chip when focused (touch) |
141
- | Publish | Top toolbar → **Publiser** |
142
- | Exit | Top toolbar → **Avslutt** (removes `?setto=edit`) |
157
+ | Publish | FAB → **Publiser** (visible once you have drafts) |
143
158
 
144
- A fixed toolbar sits at the top of the viewport. When you click a section or block, a compact colour toolbar appears above it, centred and sized to its contents. Click again or press Escape to dismiss.
159
+ When you click a section or block, a compact colour toolbar appears above it. Click again or press Escape to dismiss.
145
160
 
146
161
  ---
147
162
 
@@ -272,31 +287,41 @@ Click a block's background to edit **that block's** colours. Click the section p
272
287
 
273
288
  ## Server setup (setto-server)
274
289
 
275
- Each site row in Supabase needs:
290
+ Site configuration is split in two:
291
+
292
+ **1. Supabase `sites` row — control plane (where + routing).** Set once when the site is registered (platform admin → **Ny side**):
276
293
 
277
294
  | Column | Example |
278
295
  |--------|---------|
279
- | `id` | `carryon-no` |
296
+ | `id` | `carryon-no` (must match `siteId` in `SettoProvider`) |
280
297
  | `repo_owner` / `repo_name` / `branch` | GitHub target |
281
- | `content_paths` | Whitelist of publishable file paths |
298
+ | `vercel_project_id` | `prj_…` (used to route Vercel webhooks) |
282
299
 
283
- Example `content_paths`:
300
+ **2. `setto.config.json` in the site repo root — content shape.** setto-server reads this from GitHub (cached, with a DB fallback):
284
301
 
285
- ```
286
- src/i18n/locales/no.json
287
- src/i18n/locales/en.json
288
- src/theme/sections.json
302
+ ```json
303
+ {
304
+ "displayName": "Carry On",
305
+ "contentPaths": [
306
+ "src/i18n/locales/no.json",
307
+ "src/i18n/locales/en.json",
308
+ "src/theme/sections.json",
309
+ "public/images/setto/"
310
+ ],
311
+ "allowedOrigins": ["https://carryon.no", "http://localhost:3000"]
312
+ }
289
313
  ```
290
314
 
291
- Only paths in this list can be committed. Add new content files here before publishing them.
315
+ - `contentPaths` is the publish whitelist — only these paths can be committed. Add new content files here before publishing them. `setto.config.json` itself is intentionally not in the list, so editors can never widen their own access.
316
+ - `allowedOrigins` is the CORS allow-list; the first entry is also used as the editor-invite activation domain.
292
317
 
293
- Register allowed origins for CORS (`allowed_origins`).
318
+ Keeping this in the repo means content shape lives with the code that defines it, and no DB change is needed when you add a content file — just commit the config. The legacy `content_paths` / `allowed_origins` / `display_name` DB columns remain as a fallback for sites without the file.
294
319
 
295
320
  ---
296
321
 
297
322
  ## Publish flow
298
323
 
299
- 1. Editor clicks **Publiser** in the top toolbar.
324
+ 1. Editor clicks **Publiser** in the floating FAB.
300
325
  2. Client serialises changed locale bundles + `sections.json` (if theme drafts exist).
301
326
  3. `POST /sites/:siteId/publish` with `{ files: [{ path, content }] }`.
302
327
  4. setto-server validates paths against `content_paths`, commits to GitHub.
@@ -308,9 +333,9 @@ Drafts are cleared after a successful publish.
308
333
 
309
334
  ## Admin app (`SettoAdminApp`)
310
335
 
311
- Route: `/admin/*`
336
+ Route: `/setto/*`
312
337
 
313
- Provides Supabase email/password login (invite-only — no self-service sign-up), password reset, and a dashboard with a link to start editing (`/?setto=edit`). Invite links from Supabase land on `/admin` to set a password. Does not render the inline editor itself.
338
+ Provides Supabase email/password login (invite-only — no self-service sign-up), password reset, and a dashboard. After sign-in the dashboard redirects to the site home, where edit mode auto-activates. Invite links from Supabase land on `/setto` to set a password. Does not render the inline editor itself.
314
339
 
315
340
  ---
316
341
 
@@ -353,7 +378,7 @@ Produces `dist/setto-client.js` and `.d.ts` via Vite library mode. Only needed b
353
378
  | `SettoSection` | Section wrapper + edit selection |
354
379
  | `SettoBlock` | Nested card/panel with its own colour toolbar |
355
380
  | `useSectionTheme` | Read section colour tokens |
356
- | `SettoAdminApp` | `/admin` login + dashboard |
381
+ | `SettoAdminApp` | `/setto` login + dashboard |
357
382
  | `AuthGate` | Standalone login wrapper |
358
383
  | `BrandColor`, `SectionSchema`, `SettoConfig` | Types for host config |
359
384
 
@@ -10,6 +10,7 @@ export interface SettoRepeaterProps {
10
10
  className?: string;
11
11
  }
12
12
  /**
13
- * Renders a dynamic list from i18n. In edit mode, editors can add and remove items.
13
+ * Renders a dynamic list from i18n. In edit mode, editors can add items (button
14
+ * at the end of the list) and remove items (round × revealed on hover).
14
15
  */
15
16
  export declare function SettoRepeater({ itemsKey, defaultItem, itemLabel, children, className, }: SettoRepeaterProps): import("react/jsx-runtime").JSX.Element;
@@ -1,11 +1,10 @@
1
1
  /**
2
- * Drop-in admin SPA. Mount under a route like `<Route path="/admin/*" .../>`.
2
+ * Drop-in admin SPA. Mount under a route like `<Route path="/setto/*" .../>`.
3
3
  *
4
4
  * Behaviour after login:
5
- * - Redirects to `/?setto=edit` on the site home (edit mode active).
5
+ * - Redirects to the site home `/`. Edit mode activates automatically there
6
+ * via `SettoProvider` when the user has access to `config.siteId`.
6
7
  * - Shows the dashboard only when the user lacks access to this site, or on
7
- * `/admin?deployment=…` for publish progress / history.
8
- *
9
- * Editing happens on the public site at `/?setto=edit`.
8
+ * `/setto?deployment=…` for publish progress / history.
10
9
  */
11
10
  export declare function SettoAdminApp(): import("react/jsx-runtime").JSX.Element;
@@ -1,2 +1,4 @@
1
- /** Height of the fixed edit toolbar (px). */
2
- export declare const TOOLBAR_HEIGHT = 44;
1
+ /** Diameter of the collapsed FAB and height of the expanded pill (px). */
2
+ export declare const FAB_SIZE = 52;
3
+ /** Distance from the viewport edges to the FAB (px). */
4
+ export declare const FAB_INSET = 16;
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Reserves space at the top of the viewport for the Setto edit toolbar.
3
- * The host site keeps full width only vertical offset is applied.
2
+ * Applies the document-level CSS for inline edit mode. The fixed top toolbar
3
+ * has been replaced with a floating FAB anchored bottom-right, so the host
4
+ * page no longer needs vertical padding — only edit-affordance hover styles
5
+ * and the portal layers above the page.
4
6
  */
5
7
  export declare function useSettoDocumentLayout(active: boolean): void;
@@ -5,8 +5,8 @@ interface EditModeShellProps {
5
5
  themeStore: ThemeStore | null;
6
6
  }
7
7
  /**
8
- * Edit mode: full-width page with a fixed toolbar at the top. Text is edited
9
- * inline via contentEditable on `<T>` — no sidebar.
8
+ * Edit mode: full-width page with a floating Setto FAB anchored bottom-right.
9
+ * Text is edited inline via contentEditable on `<T>` — no sidebar.
10
10
  */
11
11
  export declare function EditModeShell({ children, themeStore }: EditModeShellProps): import("react/jsx-runtime").JSX.Element;
12
12
  /** @deprecated Use EditModeShell */
@@ -0,0 +1,16 @@
1
+ import type { CSSProperties } from 'react';
2
+ interface SettoMarkProps {
3
+ size?: number;
4
+ /** Color of the "S" letter. Defaults to the Setto crema tone. */
5
+ letterColor?: string;
6
+ /** Color of the accent dot. Defaults to the Setto terracotta. */
7
+ dotColor?: string;
8
+ style?: CSSProperties;
9
+ }
10
+ /**
11
+ * Setto brand mark — the foreground of the setto-site favicon (italic "S" with
12
+ * a terracotta dot) on a transparent background. The FAB renders the dark
13
+ * Blekk background itself so the mark only carries the glyphs.
14
+ */
15
+ export declare function SettoMark({ size, letterColor, dotColor, style, }: SettoMarkProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
@@ -5,9 +5,9 @@ interface GuestEditContextValue {
5
5
  stop: () => void;
6
6
  }
7
7
  /**
8
- * Lets visitors inline-edit copy on a marketing site (e.g. setto.no) without
9
- * auth. Changes are session-only and never published. Real Setto edit mode
10
- * always takes precedence.
8
+ * Lets visitors inline-edit a marketing site (e.g. setto.no) without auth —
9
+ * text, section/block colours, icons and list items. Changes are session-only
10
+ * and never published. Real Setto edit mode always takes precedence.
11
11
  */
12
12
  export declare function GuestEditProvider({ children }: {
13
13
  children: ReactNode;
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export { SettoRepeater } from './SettoRepeater';
11
11
  export type { SettoRepeaterProps } from './SettoRepeater';
12
12
  export { useSectionTheme } from './use-section-theme';
13
13
  export { SettoAdminApp } from './admin/App';
14
+ export { SETTO_BASE } from './lib/urls';
14
15
  export { AuthGate } from './edit-mode/auth-gate';
15
16
  export type { SettoConfig, BrandColor, DeploymentRow, SiteRow, ContentFile, PublishFile, PublishResult, } from './types';
16
17
  export type { SectionSchema, SectionColorField } from './section-schema';
@@ -27,6 +27,8 @@ export declare class ThemeStore {
27
27
  set(sectionId: string, field: string, value: string): void;
28
28
  /** Replace baseline from GitHub without creating drafts. */
29
29
  loadBaseline(next: SectionsTheme): void;
30
+ /** Resets every draft back to its original value. */
31
+ revertAll(): void;
30
32
  commit(): void;
31
33
  size(): number;
32
34
  serialise(): SectionsTheme;
@@ -1,6 +1,16 @@
1
- /** Site home with inline edit mode active. */
2
- export declare function editModeUrl(pathname?: string): string;
1
+ /**
2
+ * Base path where the Setto login + dashboard SPA is mounted on the host site.
3
+ * Editors reach the dashboard by visiting `sitenavn.no/setto`. The host app
4
+ * must mount `<SettoAdminApp>` at this path (e.g. `<Route path="/setto/*" …>`).
5
+ */
6
+ export declare const SETTO_BASE = "/setto";
7
+ /**
8
+ * Site home URL used after sign-in. Edit mode itself is now driven by the
9
+ * Supabase session + site access — no URL flag required — so this just sends
10
+ * the editor to the public site root.
11
+ */
12
+ export declare function siteHomeUrl(pathname?: string): string;
3
13
  export declare function isAdminRoute(): boolean;
4
- /** Admin deployment progress panel (`/admin?deployment=…`). */
14
+ /** Setto deployment progress panel (`/setto?deployment=…`). */
5
15
  export declare function isAdminDeploymentView(): boolean;
6
16
  export declare function adminRedirectUrl(): string;
@@ -0,0 +1,16 @@
1
+ import type { Session, SupabaseClient } from '@supabase/supabase-js';
2
+ import type { SiteRow } from '../types';
3
+ export interface SiteAccessState {
4
+ /** All sites the current user can access. `null` until loaded. */
5
+ sites: SiteRow[] | null;
6
+ /** The site matching `siteId`, or `null` if the user has no access. */
7
+ currentSite: SiteRow | null;
8
+ loading: boolean;
9
+ error: string | null;
10
+ }
11
+ /**
12
+ * Loads the sites table for the current Supabase session and resolves whether
13
+ * the user has access to `siteId`. Returns a stable shape across loading,
14
+ * success, and failure so callers can read `currentSite` directly.
15
+ */
16
+ export declare function useSiteAccess(supabase: SupabaseClient, session: Session | null, siteId: string): SiteAccessState;
@@ -4,7 +4,7 @@ import { type SettoApi } from './lib/api';
4
4
  import { I18nStore } from './lib/i18n-store';
5
5
  import { AssetStore } from './lib/asset-store';
6
6
  import { ThemeStore } from './lib/theme-store';
7
- import type { SettoConfig } from './types';
7
+ import type { SettoConfig, SiteRow } from './types';
8
8
  interface SettoContextValue {
9
9
  config: SettoConfig;
10
10
  supabase: SupabaseClient;
@@ -12,8 +12,12 @@ interface SettoContextValue {
12
12
  session: Session | null;
13
13
  /** True while the initial auth state is being read from local storage. */
14
14
  authLoading: boolean;
15
- /** True when inline edit mode is active (`?setto=edit` + session). */
15
+ /** True when inline edit mode is active (session + site access). */
16
16
  editMode: boolean;
17
+ /** Site row matching `config.siteId`, or `null` if the user has no access. */
18
+ currentSite: SiteRow | null;
19
+ /** All sites the user can access (loaded after sign-in). */
20
+ sites: SiteRow[] | null;
17
21
  /**
18
22
  * Lazily created when i18next is initialised. Components that need it
19
23
  * should `if (!store) return null;` until then.
@@ -32,10 +36,11 @@ export interface SettoProviderProps {
32
36
  *
33
37
  * Edit mode is on when ALL of:
34
38
  * 1. There is an authenticated Supabase session.
35
- * 2. The URL contains `?setto=edit`.
39
+ * 2. The user has access to `config.siteId` in the Setto sites table.
40
+ * 3. The current path is not the `/setto` admin SPA.
36
41
  *
37
- * The /admin dashboard does not activate edit mode use "Begynn å redigere"
38
- * which navigates to `/?setto=edit`.
42
+ * Once an editor signs in via `/setto`, returning visits auto-enter edit mode
43
+ * for as long as the Supabase session persists.
39
44
  */
40
45
  export declare function SettoProvider({ config, children }: SettoProviderProps): import("react/jsx-runtime").JSX.Element;
41
46
  export declare function useSetto(): SettoContextValue;