diff --git a/CHANGELOG.md b/CHANGELOG.md index 432fe48..0a8c744 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- "Request a feature" link in the admin sidebar footer — opens a pre-filled GitHub issue (enhancement label, a short title/body template) so store owners can suggest features without any API token or account setup on the store's side. - Visual page builder with live preview. Every page (Admin → Pages) is built from **reorderable sections** with a **live preview** pane beside the editor — an iframe of your real storefront that re-renders as you add/reorder sections, change colours or pick a collection, with **Desktop/Mobile** width toggles and manual refresh. The editor is a two-pane layout — a collapsible left panel holds the page title, SEO, publish controls **and** the sections; the preview fills the right-hand side and can be expanded to full width. Sections **reorder by drag-and-drop** (or keyboard up/down buttons). - **Product & collection page templates**: shared **Product page** and **Collection page** templates (Admin → Page templates) render builder sections on every product/collection page below the core content, **plus** each individual product and collection can add its own sections ("Edit page sections" on the product/collection editor) — the global template renders first, then the item's own. - **Reusable sections** (Admin → Reusable sections): build a set of sections once and drop it onto any page with the **Reusable block** section — edit the block and every page using it updates. Blocks are edited in the same builder (with live preview); a missing/deleted block simply renders nothing. diff --git a/admin/partials/layout.hbs b/admin/partials/layout.hbs index 432dbc2..c304ccb 100644 --- a/admin/partials/layout.hbs +++ b/admin/partials/layout.hbs @@ -106,6 +106,11 @@ {{admin.name}} + + + Request a feature + {{#if cloudMode}} diff --git a/src/admin/render.ts b/src/admin/render.ts index fcdafc2..a145e41 100644 --- a/src/admin/render.ts +++ b/src/admin/render.ts @@ -7,6 +7,18 @@ import config from '../config'; const ADMIN_VIEWS = path.resolve(process.cwd(), 'admin'); +// A deep link to GitHub's "new issue" form, pre-filled as a feature request. +// No API token to manage per store — the owner files it under their own GitHub +// account, so the link works the same for a self-hosted or a managed store. +const FEATURE_REQUEST_URL = + 'https://github.com/Squaark/squaark/issues/new?labels=enhancement' + + '&title=' + encodeURIComponent('Feature request: ') + + '&body=' + encodeURIComponent( + '### What would you like to add or change?\n\n\n' + + '### Why would it help your store?\n\n\n' + + '---\nSent from the Squaark admin.', + ); + const hbs = Handlebars.create(); function loadPartials() { @@ -192,6 +204,7 @@ export async function render(template: string, context: Record, ...context, csrfToken, cloudMode: config.cloudMode, + featureRequestUrl: FEATURE_REQUEST_URL, update: context.update ?? getCachedUpdateStatus(), }; diff --git a/tests/admin/feature-request.test.ts b/tests/admin/feature-request.test.ts new file mode 100644 index 0000000..52bc5d6 --- /dev/null +++ b/tests/admin/feature-request.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest'; +import { render } from '../../src/admin/render'; + +/** The admin layout footer carries a "Request a feature" link that deep-links to + * GitHub's new-issue form, pre-filled — no API token to manage per store. */ +describe('feature-request link', () => { + const reply = { generateCsrf: async () => 'tok' } as never; + + async function layoutHtml(): Promise { + // Any template renders inside the shared layout; 404 needs the least context. + return render('404', { admin: { name: 'Jane', role: 'admin' }, settings: {} }, reply); + } + + it('appears in the sidebar footer, opening the repo issue form in a new tab', async () => { + const html = await layoutHtml(); + expect(html).toContain('Request a feature'); + const m = html.match(/href="([^"]*issues\/new[^"]*)"[^>]*target="_blank"[^>]*rel="noopener"/); + expect(m).not.toBeNull(); + expect(m![1]).toContain('github.com/Squaark/squaark/issues/new'); + }); + + it('pre-fills the enhancement label, a title and a body once entities decode', async () => { + const html = await layoutHtml(); + const raw = html.match(/href="([^"]*issues\/new[^"]*)"/)![1]; + // Browsers decode HTML entities in the attribute before navigating. + const url = new URL(raw.replace(/=/g, '=').replace(/&/g, '&')); + expect(url.searchParams.get('labels')).toBe('enhancement'); + expect(url.searchParams.get('title')).toBe('Feature request: '); + expect(url.searchParams.get('body')).toContain('What would you like to add or change?'); + }); +});