Initial commit
This commit is contained in:
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
import type { Access, FieldAccess } from 'payload';
|
||||
import type { Role } from './types.js';
|
||||
/**
|
||||
* Collection-level access (returns boolean | Where).
|
||||
* Use in collection `access.read/create/update/delete`.
|
||||
*/
|
||||
export declare const adminOnly: Access;
|
||||
export declare const adminOrEditor: Access;
|
||||
export declare const authenticated: Access;
|
||||
/** Requires at least the given role. */
|
||||
export declare const requireRole: (minimum: Role) => Access;
|
||||
/** Admins see all; others are constrained to their own document. */
|
||||
export declare const adminOrSelf: Access;
|
||||
/**
|
||||
* Field-level access (returns boolean only — no Where support).
|
||||
* Use in field `access.read/update`.
|
||||
*/
|
||||
export declare const adminOnlyField: FieldAccess;
|
||||
export declare const adminOrEditorField: FieldAccess;
|
||||
/** Requires at least the given role, for field-level access. */
|
||||
export declare const requireRoleField: (minimum: Role) => FieldAccess;
|
||||
Vendored
-29
@@ -1,29 +0,0 @@
|
||||
import { hasMinimumRole, isAdmin } from './predicates.js';
|
||||
/**
|
||||
* Collection-level access (returns boolean | Where).
|
||||
* Use in collection `access.read/create/update/delete`.
|
||||
*/ export const adminOnly = ({ req: { user } })=>isAdmin(user);
|
||||
export const adminOrEditor = ({ req: { user } })=>hasMinimumRole(user, 'editor');
|
||||
export const authenticated = ({ req: { user } })=>Boolean(user);
|
||||
/** Requires at least the given role. */ export const requireRole = (minimum)=>({ req: { user } })=>hasMinimumRole(user, minimum);
|
||||
/** Admins see all; others are constrained to their own document. */ export const adminOrSelf = ({ req: { user } })=>{
|
||||
if (isAdmin(user)) {
|
||||
return true;
|
||||
}
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
id: {
|
||||
equals: user.id
|
||||
}
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Field-level access (returns boolean only — no Where support).
|
||||
* Use in field `access.read/update`.
|
||||
*/ export const adminOnlyField = ({ req: { user } })=>isAdmin(user);
|
||||
export const adminOrEditorField = ({ req: { user } })=>hasMinimumRole(user, 'editor');
|
||||
/** Requires at least the given role, for field-level access. */ export const requireRoleField = (minimum)=>({ req: { user } })=>hasMinimumRole(user, minimum);
|
||||
|
||||
//# sourceMappingURL=access.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/access/access.ts"],"sourcesContent":["import type { Access, FieldAccess } from 'payload'\n\nimport type { Role } from './types.js'\n\nimport { hasMinimumRole, isAdmin } from './predicates.js'\n\n/**\n * Collection-level access (returns boolean | Where).\n * Use in collection `access.read/create/update/delete`.\n */\nexport const adminOnly: Access = ({ req: { user } }) => isAdmin(user)\n\nexport const adminOrEditor: Access = ({ req: { user } }) => hasMinimumRole(user, 'editor')\n\nexport const authenticated: Access = ({ req: { user } }) => Boolean(user)\n\n/** Requires at least the given role. */\nexport const requireRole =\n (minimum: Role): Access =>\n ({ req: { user } }) =>\n hasMinimumRole(user, minimum)\n\n/** Admins see all; others are constrained to their own document. */\nexport const adminOrSelf: Access = ({ req: { user } }) => {\n if (isAdmin(user)) {return true}\n if (!user) {return false}\n return { id: { equals: user.id } }\n}\n\n/**\n * Field-level access (returns boolean only — no Where support).\n * Use in field `access.read/update`.\n */\nexport const adminOnlyField: FieldAccess = ({ req: { user } }) => isAdmin(user)\n\nexport const adminOrEditorField: FieldAccess = ({ req: { user } }) => hasMinimumRole(user, 'editor')\n\n/** Requires at least the given role, for field-level access. */\nexport const requireRoleField =\n (minimum: Role): FieldAccess =>\n ({ req: { user } }) =>\n hasMinimumRole(user, minimum)\n"],"names":["hasMinimumRole","isAdmin","adminOnly","req","user","adminOrEditor","authenticated","Boolean","requireRole","minimum","adminOrSelf","id","equals","adminOnlyField","adminOrEditorField","requireRoleField"],"mappings":"AAIA,SAASA,cAAc,EAAEC,OAAO,QAAQ,kBAAiB;AAEzD;;;CAGC,GACD,OAAO,MAAMC,YAAoB,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKH,QAAQG,MAAK;AAErE,OAAO,MAAMC,gBAAwB,CAAC,EAAEF,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKJ,eAAeI,MAAM,UAAS;AAE1F,OAAO,MAAME,gBAAwB,CAAC,EAAEH,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKG,QAAQH,MAAK;AAEzE,sCAAsC,GACtC,OAAO,MAAMI,cACX,CAACC,UACD,CAAC,EAAEN,KAAK,EAAEC,IAAI,EAAE,EAAE,GAChBJ,eAAeI,MAAMK,SAAQ;AAEjC,kEAAkE,GAClE,OAAO,MAAMC,cAAsB,CAAC,EAAEP,KAAK,EAAEC,IAAI,EAAE,EAAE;IACnD,IAAIH,QAAQG,OAAO;QAAC,OAAO;IAAI;IAC/B,IAAI,CAACA,MAAM;QAAC,OAAO;IAAK;IACxB,OAAO;QAAEO,IAAI;YAAEC,QAAQR,KAAKO,EAAE;QAAC;IAAE;AACnC,EAAC;AAED;;;CAGC,GACD,OAAO,MAAME,iBAA8B,CAAC,EAAEV,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKH,QAAQG,MAAK;AAE/E,OAAO,MAAMU,qBAAkC,CAAC,EAAEX,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKJ,eAAeI,MAAM,UAAS;AAEpG,8DAA8D,GAC9D,OAAO,MAAMW,mBACX,CAACN,UACD,CAAC,EAAEN,KAAK,EAAEC,IAAI,EAAE,EAAE,GAChBJ,eAAeI,MAAMK,SAAQ"}
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
export { adminOnly, adminOnlyField, adminOrEditor, adminOrEditorField, adminOrSelf, authenticated, requireRole, requireRoleField, } from './access.js';
|
||||
export { injectRoles } from './injectRoles.js';
|
||||
export { hasMinimumRole, isAdmin, isEditor } from './predicates.js';
|
||||
export { buildRolesField } from './rolesField.js';
|
||||
export type { AccessOption, Role } from './types.js';
|
||||
export { ROLE_HIERARCHY } from './types.js';
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
export { adminOnly, adminOnlyField, adminOrEditor, adminOrEditorField, adminOrSelf, authenticated, requireRole, requireRoleField } from './access.js';
|
||||
export { injectRoles } from './injectRoles.js';
|
||||
export { hasMinimumRole, isAdmin, isEditor } from './predicates.js';
|
||||
export { buildRolesField } from './rolesField.js';
|
||||
export { ROLE_HIERARCHY } from './types.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/access/index.ts"],"sourcesContent":["export {\n adminOnly,\n adminOnlyField,\n adminOrEditor,\n adminOrEditorField,\n adminOrSelf,\n authenticated,\n requireRole,\n requireRoleField,\n} from './access.js'\nexport { injectRoles } from './injectRoles.js'\nexport { hasMinimumRole, isAdmin, isEditor } from './predicates.js'\nexport { buildRolesField } from './rolesField.js'\nexport type { AccessOption, Role } from './types.js'\nexport { ROLE_HIERARCHY } from './types.js'\n"],"names":["adminOnly","adminOnlyField","adminOrEditor","adminOrEditorField","adminOrSelf","authenticated","requireRole","requireRoleField","injectRoles","hasMinimumRole","isAdmin","isEditor","buildRolesField","ROLE_HIERARCHY"],"mappings":"AAAA,SACEA,SAAS,EACTC,cAAc,EACdC,aAAa,EACbC,kBAAkB,EAClBC,WAAW,EACXC,aAAa,EACbC,WAAW,EACXC,gBAAgB,QACX,cAAa;AACpB,SAASC,WAAW,QAAQ,mBAAkB;AAC9C,SAASC,cAAc,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,kBAAiB;AACnE,SAASC,eAAe,QAAQ,kBAAiB;AAEjD,SAASC,cAAc,QAAQ,aAAY"}
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
import type { Config } from 'payload';
|
||||
import type { AccessOption } from './types.js';
|
||||
/**
|
||||
* Injects the fixed `roles` field into the client's auth collection.
|
||||
*
|
||||
* The plugin owns the role definition; the client owns the collection. This
|
||||
* finds the collection by slug and appends the field. We control the whole
|
||||
* stack, so no conflict handling is needed — the field is simply added.
|
||||
*/
|
||||
export declare function injectRoles(config: Config, access: AccessOption): Config;
|
||||
Vendored
-27
@@ -1,27 +0,0 @@
|
||||
import { buildRolesField } from './rolesField.js';
|
||||
/**
|
||||
* Injects the fixed `roles` field into the client's auth collection.
|
||||
*
|
||||
* The plugin owns the role definition; the client owns the collection. This
|
||||
* finds the collection by slug and appends the field. We control the whole
|
||||
* stack, so no conflict handling is needed — the field is simply added.
|
||||
*/ export function injectRoles(config, access) {
|
||||
const rolesField = buildRolesField(access.defaultRole);
|
||||
return {
|
||||
...config,
|
||||
collections: (config.collections ?? []).map((collection)=>{
|
||||
if (collection.slug !== access.authCollection) {
|
||||
return collection;
|
||||
}
|
||||
return {
|
||||
...collection,
|
||||
fields: [
|
||||
...collection.fields,
|
||||
rolesField
|
||||
]
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=injectRoles.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/access/injectRoles.ts"],"sourcesContent":["import type { Config } from 'payload'\n\nimport type { AccessOption } from './types.js'\n\nimport { buildRolesField } from './rolesField.js'\n\n/**\n * Injects the fixed `roles` field into the client's auth collection.\n *\n * The plugin owns the role definition; the client owns the collection. This\n * finds the collection by slug and appends the field. We control the whole\n * stack, so no conflict handling is needed — the field is simply added.\n */\nexport function injectRoles(config: Config, access: AccessOption): Config {\n const rolesField = buildRolesField(access.defaultRole)\n\n return {\n ...config,\n collections: (config.collections ?? []).map((collection) => {\n if (collection.slug !== access.authCollection) {\n return collection\n }\n return {\n ...collection,\n fields: [...collection.fields, rolesField],\n }\n }),\n }\n}\n"],"names":["buildRolesField","injectRoles","config","access","rolesField","defaultRole","collections","map","collection","slug","authCollection","fields"],"mappings":"AAIA,SAASA,eAAe,QAAQ,kBAAiB;AAEjD;;;;;;CAMC,GACD,OAAO,SAASC,YAAYC,MAAc,EAAEC,MAAoB;IAC9D,MAAMC,aAAaJ,gBAAgBG,OAAOE,WAAW;IAErD,OAAO;QACL,GAAGH,MAAM;QACTI,aAAa,AAACJ,CAAAA,OAAOI,WAAW,IAAI,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC;YAC3C,IAAIA,WAAWC,IAAI,KAAKN,OAAOO,cAAc,EAAE;gBAC7C,OAAOF;YACT;YACA,OAAO;gBACL,GAAGA,UAAU;gBACbG,QAAQ;uBAAIH,WAAWG,MAAM;oBAAEP;iBAAW;YAC5C;QACF;IACF;AACF"}
|
||||
Vendored
-20
@@ -1,20 +0,0 @@
|
||||
import type { Role } from './types.js';
|
||||
/**
|
||||
* The plugin can't know the client's generated User type, and Payload types
|
||||
* `req.user` loosely (UntypedUser | null). Predicates therefore accept an
|
||||
* unknown-ish user and read `roles` defensively — no assumptions about shape
|
||||
* beyond an optional roles array.
|
||||
*/
|
||||
type MaybeUser = {
|
||||
roles?: null | Role[];
|
||||
} | null | Record<string, unknown> | undefined;
|
||||
/**
|
||||
* True if the user holds at least the given role in the hierarchy.
|
||||
* admin satisfies 'editor' and 'user'; editor satisfies 'user'.
|
||||
*/
|
||||
export declare function hasMinimumRole(user: MaybeUser, minimum: Role): boolean;
|
||||
/** True if the user is an admin. */
|
||||
export declare function isAdmin(user: MaybeUser): boolean;
|
||||
/** True if the user is an editor or higher (editor, admin). */
|
||||
export declare function isEditor(user: MaybeUser): boolean;
|
||||
export {};
|
||||
Vendored
-32
@@ -1,32 +0,0 @@
|
||||
import { ROLE_HIERARCHY } from './types.js';
|
||||
/** Safely extracts the roles array from a loosely-typed user. */ function getRoles(user) {
|
||||
if (!user || typeof user !== 'object') {
|
||||
return [];
|
||||
}
|
||||
const roles = user.roles;
|
||||
if (!Array.isArray(roles)) {
|
||||
return [];
|
||||
}
|
||||
return roles.filter((role)=>ROLE_HIERARCHY.includes(role));
|
||||
}
|
||||
/** Highest-privilege role index the user holds, or -1 if none. */ function highestRoleIndex(user) {
|
||||
const roles = getRoles(user);
|
||||
if (!roles.length) {
|
||||
return -1;
|
||||
}
|
||||
return Math.max(...roles.map((role)=>ROLE_HIERARCHY.indexOf(role)));
|
||||
}
|
||||
/**
|
||||
* True if the user holds at least the given role in the hierarchy.
|
||||
* admin satisfies 'editor' and 'user'; editor satisfies 'user'.
|
||||
*/ export function hasMinimumRole(user, minimum) {
|
||||
return highestRoleIndex(user) >= ROLE_HIERARCHY.indexOf(minimum);
|
||||
}
|
||||
/** True if the user is an admin. */ export function isAdmin(user) {
|
||||
return hasMinimumRole(user, 'admin');
|
||||
}
|
||||
/** True if the user is an editor or higher (editor, admin). */ export function isEditor(user) {
|
||||
return hasMinimumRole(user, 'editor');
|
||||
}
|
||||
|
||||
//# sourceMappingURL=predicates.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/access/predicates.ts"],"sourcesContent":["import type { Role } from './types.js'\n\nimport { ROLE_HIERARCHY } from './types.js'\n\n/**\n * The plugin can't know the client's generated User type, and Payload types\n * `req.user` loosely (UntypedUser | null). Predicates therefore accept an\n * unknown-ish user and read `roles` defensively — no assumptions about shape\n * beyond an optional roles array.\n */\ntype MaybeUser = { roles?: null | Role[] } | null | Record<string, unknown> | undefined\n\n/** Safely extracts the roles array from a loosely-typed user. */\nfunction getRoles(user: MaybeUser): Role[] {\n if (!user || typeof user !== 'object') {return []}\n const roles = (user as { roles?: unknown }).roles\n if (!Array.isArray(roles)) {return []}\n return roles.filter((role): role is Role => ROLE_HIERARCHY.includes(role as Role))\n}\n\n/** Highest-privilege role index the user holds, or -1 if none. */\nfunction highestRoleIndex(user: MaybeUser): number {\n const roles = getRoles(user)\n if (!roles.length) {return -1}\n return Math.max(...roles.map((role) => ROLE_HIERARCHY.indexOf(role)))\n}\n\n/**\n * True if the user holds at least the given role in the hierarchy.\n * admin satisfies 'editor' and 'user'; editor satisfies 'user'.\n */\nexport function hasMinimumRole(user: MaybeUser, minimum: Role): boolean {\n return highestRoleIndex(user) >= ROLE_HIERARCHY.indexOf(minimum)\n}\n\n/** True if the user is an admin. */\nexport function isAdmin(user: MaybeUser): boolean {\n return hasMinimumRole(user, 'admin')\n}\n\n/** True if the user is an editor or higher (editor, admin). */\nexport function isEditor(user: MaybeUser): boolean {\n return hasMinimumRole(user, 'editor')\n}\n"],"names":["ROLE_HIERARCHY","getRoles","user","roles","Array","isArray","filter","role","includes","highestRoleIndex","length","Math","max","map","indexOf","hasMinimumRole","minimum","isAdmin","isEditor"],"mappings":"AAEA,SAASA,cAAc,QAAQ,aAAY;AAU3C,+DAA+D,GAC/D,SAASC,SAASC,IAAe;IAC/B,IAAI,CAACA,QAAQ,OAAOA,SAAS,UAAU;QAAC,OAAO,EAAE;IAAA;IACjD,MAAMC,QAAQ,AAACD,KAA6BC,KAAK;IACjD,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ;QAAC,OAAO,EAAE;IAAA;IACrC,OAAOA,MAAMG,MAAM,CAAC,CAACC,OAAuBP,eAAeQ,QAAQ,CAACD;AACtE;AAEA,gEAAgE,GAChE,SAASE,iBAAiBP,IAAe;IACvC,MAAMC,QAAQF,SAASC;IACvB,IAAI,CAACC,MAAMO,MAAM,EAAE;QAAC,OAAO,CAAC;IAAC;IAC7B,OAAOC,KAAKC,GAAG,IAAIT,MAAMU,GAAG,CAAC,CAACN,OAASP,eAAec,OAAO,CAACP;AAChE;AAEA;;;CAGC,GACD,OAAO,SAASQ,eAAeb,IAAe,EAAEc,OAAa;IAC3D,OAAOP,iBAAiBP,SAASF,eAAec,OAAO,CAACE;AAC1D;AAEA,kCAAkC,GAClC,OAAO,SAASC,QAAQf,IAAe;IACrC,OAAOa,eAAeb,MAAM;AAC9B;AAEA,6DAA6D,GAC7D,OAAO,SAASgB,SAAShB,IAAe;IACtC,OAAOa,eAAeb,MAAM;AAC9B"}
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
import type { Field } from 'payload';
|
||||
import type { Role } from './types.js';
|
||||
/**
|
||||
* Builds the fixed `roles` field the plugin injects into the auth collection.
|
||||
*
|
||||
* Saved to the JWT so role checks avoid a database lookup. Only admins can
|
||||
* change roles, preventing privilege escalation by lower-privilege users.
|
||||
*/
|
||||
export declare function buildRolesField(defaultRole?: Role): Field;
|
||||
Vendored
-32
@@ -1,32 +0,0 @@
|
||||
import { isAdmin } from './predicates.js';
|
||||
import { ROLE_HIERARCHY } from './types.js';
|
||||
/**
|
||||
* Builds the fixed `roles` field the plugin injects into the auth collection.
|
||||
*
|
||||
* Saved to the JWT so role checks avoid a database lookup. Only admins can
|
||||
* change roles, preventing privilege escalation by lower-privilege users.
|
||||
*/ export function buildRolesField(defaultRole = 'user') {
|
||||
return {
|
||||
name: 'roles',
|
||||
type: 'select',
|
||||
access: {
|
||||
// Only admins may assign or change roles
|
||||
update: ({ req: { user } })=>isAdmin(user)
|
||||
},
|
||||
admin: {
|
||||
description: 'Role hierarchy: admin > editor > user.'
|
||||
},
|
||||
defaultValue: [
|
||||
defaultRole
|
||||
],
|
||||
hasMany: true,
|
||||
options: ROLE_HIERARCHY.map((role)=>({
|
||||
label: role.charAt(0).toUpperCase() + role.slice(1),
|
||||
value: role
|
||||
})),
|
||||
required: true,
|
||||
saveToJWT: true
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=rolesField.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/access/rolesField.ts"],"sourcesContent":["import type { Field } from 'payload'\n\nimport type { Role } from './types.js'\n\nimport { isAdmin } from './predicates.js'\nimport { ROLE_HIERARCHY } from './types.js'\n\n/**\n * Builds the fixed `roles` field the plugin injects into the auth collection.\n *\n * Saved to the JWT so role checks avoid a database lookup. Only admins can\n * change roles, preventing privilege escalation by lower-privilege users.\n */\nexport function buildRolesField(defaultRole: Role = 'user'): Field {\n return {\n name: 'roles',\n type: 'select',\n access: {\n // Only admins may assign or change roles\n update: ({ req: { user } }) => isAdmin(user),\n },\n admin: {\n description: 'Role hierarchy: admin > editor > user.',\n },\n defaultValue: [defaultRole],\n hasMany: true,\n options: ROLE_HIERARCHY.map((role) => ({\n label: role.charAt(0).toUpperCase() + role.slice(1),\n value: role,\n })),\n required: true,\n saveToJWT: true,\n }\n}\n"],"names":["isAdmin","ROLE_HIERARCHY","buildRolesField","defaultRole","name","type","access","update","req","user","admin","description","defaultValue","hasMany","options","map","role","label","charAt","toUpperCase","slice","value","required","saveToJWT"],"mappings":"AAIA,SAASA,OAAO,QAAQ,kBAAiB;AACzC,SAASC,cAAc,QAAQ,aAAY;AAE3C;;;;;CAKC,GACD,OAAO,SAASC,gBAAgBC,cAAoB,MAAM;IACxD,OAAO;QACLC,MAAM;QACNC,MAAM;QACNC,QAAQ;YACN,yCAAyC;YACzCC,QAAQ,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE,EAAE,GAAKT,QAAQS;QACzC;QACAC,OAAO;YACLC,aAAa;QACf;QACAC,cAAc;YAACT;SAAY;QAC3BU,SAAS;QACTC,SAASb,eAAec,GAAG,CAAC,CAACC,OAAU,CAAA;gBACrCC,OAAOD,KAAKE,MAAM,CAAC,GAAGC,WAAW,KAAKH,KAAKI,KAAK,CAAC;gBACjDC,OAAOL;YACT,CAAA;QACAM,UAAU;QACVC,WAAW;IACb;AACF"}
|
||||
Vendored
-19
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* Role hierarchy, lowest to highest privilege.
|
||||
* A higher role satisfies any requirement met by a lower one.
|
||||
*/
|
||||
export declare const ROLE_HIERARCHY: readonly ["user", "editor", "admin"];
|
||||
export type Role = (typeof ROLE_HIERARCHY)[number];
|
||||
/**
|
||||
* Access-control options.
|
||||
*
|
||||
* The plugin injects a fixed `roles` field into the client's auth collection
|
||||
* — the collection itself belongs to the client (create-payload-app), the
|
||||
* role definition belongs to the plugin.
|
||||
*/
|
||||
export type AccessOption = {
|
||||
/** Slug of the client's auth collection, e.g. 'users'. */
|
||||
authCollection: string;
|
||||
/** Role assigned to new users. Defaults to 'user'. */
|
||||
defaultRole?: Role;
|
||||
};
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Role hierarchy, lowest to highest privilege.
|
||||
* A higher role satisfies any requirement met by a lower one.
|
||||
*/ export const ROLE_HIERARCHY = [
|
||||
'user',
|
||||
'editor',
|
||||
'admin'
|
||||
];
|
||||
|
||||
//# sourceMappingURL=types.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/access/types.ts"],"sourcesContent":["/**\n * Role hierarchy, lowest to highest privilege.\n * A higher role satisfies any requirement met by a lower one.\n */\nexport const ROLE_HIERARCHY = ['user', 'editor', 'admin'] as const\n\nexport type Role = (typeof ROLE_HIERARCHY)[number]\n\n/**\n * Access-control options.\n *\n * The plugin injects a fixed `roles` field into the client's auth collection\n * — the collection itself belongs to the client (create-payload-app), the\n * role definition belongs to the plugin.\n */\nexport type AccessOption = {\n /** Slug of the client's auth collection, e.g. 'users'. */\n authCollection: string\n /** Role assigned to new users. Defaults to 'user'. */\n defaultRole?: Role\n}\n"],"names":["ROLE_HIERARCHY"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,iBAAiB;IAAC;IAAQ;IAAU;CAAQ,CAAS"}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import type { AnalyticsConfig } from './types.js';
|
||||
declare global {
|
||||
interface Window {
|
||||
dataLayer?: unknown[];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Loads Google Analytics — GTM if a container ID is set, otherwise GA4 gtag.
|
||||
*
|
||||
* Consent Mode is initialised to the visitor's stored consent BEFORE the tag
|
||||
* loads (via setDefaultConsent from the consent module), so tags respect the
|
||||
* choice from the first hit; useConsent sends an update the moment the visitor
|
||||
* decides. IDs are public and come from SiteIntegrations, passed in as props
|
||||
* (the client project reads them server-side).
|
||||
*
|
||||
* Renders nothing — it only injects the scripts. Mount once, high in the tree
|
||||
* (e.g. root layout), inside ConsentProvider.
|
||||
*/
|
||||
export declare function Analytics({ ga4MeasurementId, gtmContainerId }: AnalyticsConfig): null;
|
||||
Vendored
-63
@@ -1,63 +0,0 @@
|
||||
'use client';
|
||||
import { useEffect } from 'react';
|
||||
import { gtag } from '../consent/googleConsent.js';
|
||||
import { CONSENT_COOKIE, DEFAULT_CONSENT, parseConsent, setDefaultConsent } from '../consent/index.js';
|
||||
const GTM_SCRIPT = (id)=>`https://www.googletagmanager.com/gtm.js?id=${id}`;
|
||||
const GA4_SCRIPT = (id)=>`https://www.googletagmanager.com/gtag/js?id=${id}`;
|
||||
/** Reads the current consent state from cookie, or defaults if none. */ function readConsent() {
|
||||
if (typeof document === 'undefined') {
|
||||
return DEFAULT_CONSENT;
|
||||
}
|
||||
const raw = document.cookie.split('; ').find((c)=>c.startsWith(`${CONSENT_COOKIE}=`))?.split('=')[1];
|
||||
return parseConsent(raw) ?? DEFAULT_CONSENT;
|
||||
}
|
||||
function injectScript(src) {
|
||||
if (document.querySelector(`script[src="${src}"]`)) {
|
||||
return;
|
||||
}
|
||||
const s = document.createElement('script');
|
||||
s.src = src;
|
||||
s.async = true;
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
/**
|
||||
* Loads Google Analytics — GTM if a container ID is set, otherwise GA4 gtag.
|
||||
*
|
||||
* Consent Mode is initialised to the visitor's stored consent BEFORE the tag
|
||||
* loads (via setDefaultConsent from the consent module), so tags respect the
|
||||
* choice from the first hit; useConsent sends an update the moment the visitor
|
||||
* decides. IDs are public and come from SiteIntegrations, passed in as props
|
||||
* (the client project reads them server-side).
|
||||
*
|
||||
* Renders nothing — it only injects the scripts. Mount once, high in the tree
|
||||
* (e.g. root layout), inside ConsentProvider.
|
||||
*/ export function Analytics({ ga4MeasurementId, gtmContainerId }) {
|
||||
useEffect(()=>{
|
||||
if (!ga4MeasurementId && !gtmContainerId) {
|
||||
return;
|
||||
}
|
||||
// 1. Consent Mode default = the visitor's stored choice, before tags load.
|
||||
setDefaultConsent(readConsent());
|
||||
// 2. Load the tag.
|
||||
if (gtmContainerId) {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({
|
||||
event: 'gtm.js',
|
||||
'gtm.start': Date.now()
|
||||
});
|
||||
injectScript(GTM_SCRIPT(gtmContainerId));
|
||||
} else if (ga4MeasurementId) {
|
||||
injectScript(GA4_SCRIPT(ga4MeasurementId));
|
||||
// Uses the shared gtag (pushes `arguments`, as Google's tags expect);
|
||||
// real gtag.js wires itself up once the script loads.
|
||||
gtag('js', new Date());
|
||||
gtag('config', ga4MeasurementId);
|
||||
}
|
||||
}, [
|
||||
ga4MeasurementId,
|
||||
gtmContainerId
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=Analytics.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/analytics/Analytics.tsx"],"sourcesContent":["'use client'\nimport { useEffect } from 'react'\n\nimport type { AnalyticsConfig } from './types.js'\n\nimport { gtag } from '../consent/googleConsent.js'\nimport { CONSENT_COOKIE, DEFAULT_CONSENT, parseConsent, setDefaultConsent } from '../consent/index.js'\n\ndeclare global {\n interface Window {\n dataLayer?: unknown[]\n }\n}\n\nconst GTM_SCRIPT = (id: string) => `https://www.googletagmanager.com/gtm.js?id=${id}`\nconst GA4_SCRIPT = (id: string) => `https://www.googletagmanager.com/gtag/js?id=${id}`\n\n/** Reads the current consent state from cookie, or defaults if none. */\nfunction readConsent() {\n if (typeof document === 'undefined') {return DEFAULT_CONSENT}\n const raw = document.cookie\n .split('; ')\n .find((c) => c.startsWith(`${CONSENT_COOKIE}=`))\n ?.split('=')[1]\n return parseConsent(raw) ?? DEFAULT_CONSENT\n}\n\nfunction injectScript(src: string) {\n if (document.querySelector(`script[src=\"${src}\"]`)) {return}\n const s = document.createElement('script')\n s.src = src\n s.async = true\n document.head.appendChild(s)\n}\n\n/**\n * Loads Google Analytics — GTM if a container ID is set, otherwise GA4 gtag.\n *\n * Consent Mode is initialised to the visitor's stored consent BEFORE the tag\n * loads (via setDefaultConsent from the consent module), so tags respect the\n * choice from the first hit; useConsent sends an update the moment the visitor\n * decides. IDs are public and come from SiteIntegrations, passed in as props\n * (the client project reads them server-side).\n *\n * Renders nothing — it only injects the scripts. Mount once, high in the tree\n * (e.g. root layout), inside ConsentProvider.\n */\nexport function Analytics({ ga4MeasurementId, gtmContainerId }: AnalyticsConfig) {\n useEffect(() => {\n if (!ga4MeasurementId && !gtmContainerId) {return}\n\n // 1. Consent Mode default = the visitor's stored choice, before tags load.\n setDefaultConsent(readConsent())\n\n // 2. Load the tag.\n if (gtmContainerId) {\n window.dataLayer = window.dataLayer || []\n window.dataLayer.push({ event: 'gtm.js', 'gtm.start': Date.now() })\n injectScript(GTM_SCRIPT(gtmContainerId))\n } else if (ga4MeasurementId) {\n injectScript(GA4_SCRIPT(ga4MeasurementId))\n // Uses the shared gtag (pushes `arguments`, as Google's tags expect);\n // real gtag.js wires itself up once the script loads.\n gtag('js', new Date())\n gtag('config', ga4MeasurementId)\n }\n }, [ga4MeasurementId, gtmContainerId])\n\n return null\n}\n"],"names":["useEffect","gtag","CONSENT_COOKIE","DEFAULT_CONSENT","parseConsent","setDefaultConsent","GTM_SCRIPT","id","GA4_SCRIPT","readConsent","document","raw","cookie","split","find","c","startsWith","injectScript","src","querySelector","s","createElement","async","head","appendChild","Analytics","ga4MeasurementId","gtmContainerId","window","dataLayer","push","event","Date","now"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,QAAO;AAIjC,SAASC,IAAI,QAAQ,8BAA6B;AAClD,SAASC,cAAc,EAAEC,eAAe,EAAEC,YAAY,EAAEC,iBAAiB,QAAS,sBAAqB;AAQvG,MAAMC,aAAa,CAACC,KAAe,CAAC,2CAA2C,EAAEA,IAAI;AACrF,MAAMC,aAAa,CAACD,KAAe,CAAC,4CAA4C,EAAEA,IAAI;AAEtF,sEAAsE,GACtE,SAASE;IACP,IAAI,OAAOC,aAAa,aAAa;QAAC,OAAOP;IAAe;IAC5D,MAAMQ,MAAMD,SAASE,MAAM,CACxBC,KAAK,CAAC,MACNC,IAAI,CAAC,CAACC,IAAMA,EAAEC,UAAU,CAAC,GAAGd,eAAe,CAAC,CAAC,IAC5CW,MAAM,IAAI,CAAC,EAAE;IACjB,OAAOT,aAAaO,QAAQR;AAC9B;AAEA,SAASc,aAAaC,GAAW;IAC/B,IAAIR,SAASS,aAAa,CAAC,CAAC,YAAY,EAAED,IAAI,EAAE,CAAC,GAAG;QAAC;IAAM;IAC3D,MAAME,IAAIV,SAASW,aAAa,CAAC;IACjCD,EAAEF,GAAG,GAAGA;IACRE,EAAEE,KAAK,GAAG;IACVZ,SAASa,IAAI,CAACC,WAAW,CAACJ;AAC5B;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASK,UAAU,EAAEC,gBAAgB,EAAEC,cAAc,EAAmB;IAC7E3B,UAAU;QACR,IAAI,CAAC0B,oBAAoB,CAACC,gBAAgB;YAAC;QAAM;QAEjD,2EAA2E;QAC3EtB,kBAAkBI;QAElB,mBAAmB;QACnB,IAAIkB,gBAAgB;YAClBC,OAAOC,SAAS,GAAGD,OAAOC,SAAS,IAAI,EAAE;YACzCD,OAAOC,SAAS,CAACC,IAAI,CAAC;gBAAEC,OAAO;gBAAU,aAAaC,KAAKC,GAAG;YAAG;YACjEhB,aAAaX,WAAWqB;QAC1B,OAAO,IAAID,kBAAkB;YAC3BT,aAAaT,WAAWkB;YACxB,sEAAsE;YACtE,sDAAsD;YACtDzB,KAAK,MAAM,IAAI+B;YACf/B,KAAK,UAAUyB;QACjB;IACF,GAAG;QAACA;QAAkBC;KAAe;IAErC,OAAO;AACT"}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export { Analytics } from './Analytics.js';
|
||||
Vendored
-4
@@ -1,4 +0,0 @@
|
||||
'use client';
|
||||
export { Analytics } from './Analytics.js';
|
||||
|
||||
//# sourceMappingURL=client.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/analytics/client.ts"],"sourcesContent":["'use client'\nexport { Analytics } from './Analytics.js'\n"],"names":["Analytics"],"mappings":"AAAA;AACA,SAASA,SAAS,QAAQ,iBAAgB"}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
import type { AnalyticsConfig } from './types.js';
|
||||
/**
|
||||
* Reads the public analytics IDs from SiteIntegrations, server-side.
|
||||
*
|
||||
* Returns only the two public IDs (GA4 / GTM) — safe to pass to the client
|
||||
* <Analytics> component. Does NOT expose any secret from SiteIntegrations.
|
||||
*/
|
||||
export declare function getAnalyticsConfig(payload: BasePayload): Promise<AnalyticsConfig>;
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { getSiteIntegrations } from '../payload/index.js';
|
||||
/**
|
||||
* Reads the public analytics IDs from SiteIntegrations, server-side.
|
||||
*
|
||||
* Returns only the two public IDs (GA4 / GTM) — safe to pass to the client
|
||||
* <Analytics> component. Does NOT expose any secret from SiteIntegrations.
|
||||
*/ export async function getAnalyticsConfig(payload) {
|
||||
const integrations = await getSiteIntegrations(payload);
|
||||
return {
|
||||
ga4MeasurementId: integrations.ga4MeasurementId ?? null,
|
||||
gtmContainerId: integrations.gtmContainerId ?? null
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=getAnalyticsConfig.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/analytics/getAnalyticsConfig.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nimport type { AnalyticsConfig } from './types.js'\n\nimport { getSiteIntegrations } from '../payload/index.js'\n\n/**\n * Reads the public analytics IDs from SiteIntegrations, server-side.\n *\n * Returns only the two public IDs (GA4 / GTM) — safe to pass to the client\n * <Analytics> component. Does NOT expose any secret from SiteIntegrations.\n */\nexport async function getAnalyticsConfig(payload: BasePayload): Promise<AnalyticsConfig> {\n const integrations = await getSiteIntegrations<AnalyticsConfig>(payload)\n return {\n ga4MeasurementId: integrations.ga4MeasurementId ?? null,\n gtmContainerId: integrations.gtmContainerId ?? null,\n }\n}\n"],"names":["getSiteIntegrations","getAnalyticsConfig","payload","integrations","ga4MeasurementId","gtmContainerId"],"mappings":"AAIA,SAASA,mBAAmB,QAAQ,sBAAqB;AAEzD;;;;;CAKC,GACD,OAAO,eAAeC,mBAAmBC,OAAoB;IAC3D,MAAMC,eAAe,MAAMH,oBAAqCE;IAChE,OAAO;QACLE,kBAAkBD,aAAaC,gBAAgB,IAAI;QACnDC,gBAAgBF,aAAaE,cAAc,IAAI;IACjD;AACF"}
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
export type { AnalyticsConfig } from './types.js';
|
||||
export { getAnalyticsConfig } from './getAnalyticsConfig.js';
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
// Server-safe: config type + the helper that reads IDs from SiteIntegrations.
|
||||
// The <Analytics> component is client-side — exported via ./client.
|
||||
export { getAnalyticsConfig } from './getAnalyticsConfig.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/analytics/index.ts"],"sourcesContent":["// Server-safe: config type + the helper that reads IDs from SiteIntegrations.\n// The <Analytics> component is client-side — exported via ./client.\nexport type { AnalyticsConfig } from './types.js'\nexport { getAnalyticsConfig } from './getAnalyticsConfig.js'\n"],"names":["getAnalyticsConfig"],"mappings":"AAAA,8EAA8E;AAC9E,oEAAoE;AAEpE,SAASA,kBAAkB,QAAQ,0BAAyB"}
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Analytics IDs, read from SiteIntegrations (public — safe on the client).
|
||||
*/
|
||||
export type AnalyticsConfig = {
|
||||
/** GA4 Measurement ID, e.g. 'G-XXXXXXXXXX'. */
|
||||
ga4MeasurementId?: null | string;
|
||||
/** GTM Container ID, e.g. 'GTM-XXXXXXX'. */
|
||||
gtmContainerId?: null | string;
|
||||
};
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Analytics IDs, read from SiteIntegrations (public — safe on the client).
|
||||
*/ export { };
|
||||
|
||||
//# sourceMappingURL=types.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/analytics/types.ts"],"sourcesContent":["/**\n * Analytics IDs, read from SiteIntegrations (public — safe on the client).\n */\nexport type AnalyticsConfig = {\n /** GA4 Measurement ID, e.g. 'G-XXXXXXXXXX'. */\n ga4MeasurementId?: null | string\n /** GTM Container ID, e.g. 'GTM-XXXXXXX'. */\n gtmContainerId?: null | string\n}\n"],"names":[],"mappings":"AAAA;;CAEC,GACD,WAKC"}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
import type { BlockComponentMap, BlockData, EnhanceProps } from './types.js';
|
||||
export type RenderBlocksProps = {
|
||||
/** Block data array from a Payload document (e.g. page.layout). */
|
||||
blocks: BlockData[] | null | undefined;
|
||||
/** Client-provided map of blockType → component. */
|
||||
components: BlockComponentMap;
|
||||
/**
|
||||
* Optional client hook to inject block-specific props (nav anchors, etc.).
|
||||
* Keeps the engine generic — the plugin knows no concrete block types.
|
||||
*/
|
||||
enhanceProps?: EnhanceProps;
|
||||
};
|
||||
/**
|
||||
* Generic, server-side block renderer.
|
||||
*
|
||||
* Iterates a document's blocks and renders each via the client's component
|
||||
* map. Unknown blocks are skipped (null), never thrown. Block-specific logic is
|
||||
* delegated to the client's optional `enhanceProps` — the plugin itself is
|
||||
* block-agnostic.
|
||||
*
|
||||
* The client owns components and block schemas (they pass `components` and
|
||||
* define blocks in their config); the plugin owns only the iteration and
|
||||
* wiring. No wrapper markup is added — spacing/layout belong to the client's
|
||||
* components.
|
||||
*
|
||||
* Nested blocks: a block that needs to render child blocks should render its
|
||||
* own <RenderBlocks> and import the registry itself, rather than receiving the
|
||||
* component map as a prop. Passing the map as a prop breaks React Server
|
||||
* Components — functions (client components) can't cross the server→client
|
||||
* boundary via props.
|
||||
*/
|
||||
export declare function RenderBlocks({ blocks, components, enhanceProps }: RenderBlocksProps): import("react/jsx-runtime").JSX.Element | null;
|
||||
Vendored
-44
@@ -1,44 +0,0 @@
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { Fragment } from 'react';
|
||||
/**
|
||||
* Generic, server-side block renderer.
|
||||
*
|
||||
* Iterates a document's blocks and renders each via the client's component
|
||||
* map. Unknown blocks are skipped (null), never thrown. Block-specific logic is
|
||||
* delegated to the client's optional `enhanceProps` — the plugin itself is
|
||||
* block-agnostic.
|
||||
*
|
||||
* The client owns components and block schemas (they pass `components` and
|
||||
* define blocks in their config); the plugin owns only the iteration and
|
||||
* wiring. No wrapper markup is added — spacing/layout belong to the client's
|
||||
* components.
|
||||
*
|
||||
* Nested blocks: a block that needs to render child blocks should render its
|
||||
* own <RenderBlocks> and import the registry itself, rather than receiving the
|
||||
* component map as a prop. Passing the map as a prop breaks React Server
|
||||
* Components — functions (client components) can't cross the server→client
|
||||
* boundary via props.
|
||||
*/ export function RenderBlocks({ blocks, components, enhanceProps }) {
|
||||
if (!blocks || !Array.isArray(blocks) || blocks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return /*#__PURE__*/ _jsx(Fragment, {
|
||||
children: blocks.map((block, index)=>{
|
||||
const Component = components[block.blockType];
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
const extra = enhanceProps ? enhanceProps({
|
||||
allBlocks: blocks,
|
||||
block,
|
||||
index
|
||||
}) : {};
|
||||
return /*#__PURE__*/ _jsx(Component, {
|
||||
...block,
|
||||
...extra
|
||||
}, index);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=RenderBlocks.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/blocks/RenderBlocks.tsx"],"sourcesContent":["import { Fragment } from 'react'\n\nimport type { BlockComponentMap, BlockData, EnhanceProps } from './types.js'\n\nexport type RenderBlocksProps = {\n /** Block data array from a Payload document (e.g. page.layout). */\n blocks: BlockData[] | null | undefined\n /** Client-provided map of blockType → component. */\n components: BlockComponentMap\n /**\n * Optional client hook to inject block-specific props (nav anchors, etc.).\n * Keeps the engine generic — the plugin knows no concrete block types.\n */\n enhanceProps?: EnhanceProps\n}\n\n/**\n * Generic, server-side block renderer.\n *\n * Iterates a document's blocks and renders each via the client's component\n * map. Unknown blocks are skipped (null), never thrown. Block-specific logic is\n * delegated to the client's optional `enhanceProps` — the plugin itself is\n * block-agnostic.\n *\n * The client owns components and block schemas (they pass `components` and\n * define blocks in their config); the plugin owns only the iteration and\n * wiring. No wrapper markup is added — spacing/layout belong to the client's\n * components.\n *\n * Nested blocks: a block that needs to render child blocks should render its\n * own <RenderBlocks> and import the registry itself, rather than receiving the\n * component map as a prop. Passing the map as a prop breaks React Server\n * Components — functions (client components) can't cross the server→client\n * boundary via props.\n */\nexport function RenderBlocks({ blocks, components, enhanceProps }: RenderBlocksProps) {\n if (!blocks || !Array.isArray(blocks) || blocks.length === 0) {\n return null\n }\n\n return (\n <Fragment>\n {blocks.map((block, index) => {\n const Component = components[block.blockType]\n if (!Component) {return null}\n\n const extra = enhanceProps ? enhanceProps({ allBlocks: blocks, block, index }) : {}\n\n return <Component key={index} {...block} {...extra} />\n })}\n </Fragment>\n )\n}\n"],"names":["Fragment","RenderBlocks","blocks","components","enhanceProps","Array","isArray","length","map","block","index","Component","blockType","extra","allBlocks"],"mappings":";AAAA,SAASA,QAAQ,QAAQ,QAAO;AAgBhC;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,aAAa,EAAEC,MAAM,EAAEC,UAAU,EAAEC,YAAY,EAAqB;IAClF,IAAI,CAACF,UAAU,CAACG,MAAMC,OAAO,CAACJ,WAAWA,OAAOK,MAAM,KAAK,GAAG;QAC5D,OAAO;IACT;IAEA,qBACE,KAACP;kBACEE,OAAOM,GAAG,CAAC,CAACC,OAAOC;YAClB,MAAMC,YAAYR,UAAU,CAACM,MAAMG,SAAS,CAAC;YAC7C,IAAI,CAACD,WAAW;gBAAC,OAAO;YAAI;YAE5B,MAAME,QAAQT,eAAeA,aAAa;gBAAEU,WAAWZ;gBAAQO;gBAAOC;YAAM,KAAK,CAAC;YAElF,qBAAO,KAACC;gBAAuB,GAAGF,KAAK;gBAAG,GAAGI,KAAK;eAA3BH;QACzB;;AAGN"}
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
export { RenderBlocks } from './RenderBlocks.js';
|
||||
export type { RenderBlocksProps } from './RenderBlocks.js';
|
||||
export type { BlockComponentMap, BlockData, EnhanceProps } from './types.js';
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
export { RenderBlocks } from './RenderBlocks.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/blocks/index.ts"],"sourcesContent":["export { RenderBlocks } from './RenderBlocks.js'\nexport type { RenderBlocksProps } from './RenderBlocks.js'\nexport type { BlockComponentMap, BlockData, EnhanceProps } from './types.js'\n"],"names":["RenderBlocks"],"mappings":"AAAA,SAASA,YAAY,QAAQ,oBAAmB"}
|
||||
Vendored
-26
@@ -1,26 +0,0 @@
|
||||
import type { ComponentType } from 'react';
|
||||
/**
|
||||
* A single block's data as stored by Payload — always has a blockType, plus
|
||||
* arbitrary block-specific fields. The plugin stays generic over the shape.
|
||||
*/
|
||||
export type BlockData = {
|
||||
[key: string]: unknown;
|
||||
blockType: string;
|
||||
};
|
||||
/**
|
||||
* Maps a blockType to the client's component for it.
|
||||
* The client owns the components; the plugin only receives this map.
|
||||
*/
|
||||
export type BlockComponentMap = Record<string, ComponentType<any>>;
|
||||
/**
|
||||
* Optional per-block prop enhancer supplied by the client.
|
||||
*
|
||||
* Lets the client inject block-specific logic (e.g. collect anchors for a nav
|
||||
* block) without the plugin knowing any concrete block types. Returns extra
|
||||
* props merged into the rendered block.
|
||||
*/
|
||||
export type EnhanceProps = (args: {
|
||||
allBlocks: BlockData[];
|
||||
block: BlockData;
|
||||
index: number;
|
||||
}) => Record<string, unknown>;
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Optional per-block prop enhancer supplied by the client.
|
||||
*
|
||||
* Lets the client inject block-specific logic (e.g. collect anchors for a nav
|
||||
* block) without the plugin knowing any concrete block types. Returns extra
|
||||
* props merged into the rendered block.
|
||||
*/ export { };
|
||||
|
||||
//# sourceMappingURL=types.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/blocks/types.ts"],"sourcesContent":["import type { ComponentType } from 'react'\n\n/**\n * A single block's data as stored by Payload — always has a blockType, plus\n * arbitrary block-specific fields. The plugin stays generic over the shape.\n */\nexport type BlockData = {\n [key: string]: unknown\n blockType: string\n}\n\n/**\n * Maps a blockType to the client's component for it.\n * The client owns the components; the plugin only receives this map.\n */\nexport type BlockComponentMap = Record<string, ComponentType<any>>\n\n/**\n * Optional per-block prop enhancer supplied by the client.\n *\n * Lets the client inject block-specific logic (e.g. collect anchors for a nav\n * block) without the plugin knowing any concrete block types. Returns extra\n * props merged into the rendered block.\n */\nexport type EnhanceProps = (args: {\n allBlocks: BlockData[]\n block: BlockData\n index: number\n}) => Record<string, unknown>\n"],"names":[],"mappings":"AAiBA;;;;;;CAMC,GACD,WAI6B"}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { type ReactNode } from 'react';
|
||||
import type { ConsentTexts } from './texts.js';
|
||||
import { useConsent } from './useConsent.js';
|
||||
type ConsentContextValue = {
|
||||
texts: ConsentTexts;
|
||||
} & ReturnType<typeof useConsent>;
|
||||
export declare function ConsentProvider({ children, texts }: {
|
||||
children: ReactNode;
|
||||
texts: ConsentTexts;
|
||||
}): import("react/jsx-runtime").JSX.Element;
|
||||
export declare function useConsentContext(): ConsentContextValue;
|
||||
export {};
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
'use client';
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { createContext, use } from 'react';
|
||||
import { useConsent } from './useConsent.js';
|
||||
const ConsentContext = /*#__PURE__*/ createContext(null);
|
||||
export function ConsentProvider({ children, texts }) {
|
||||
const consent = useConsent();
|
||||
return /*#__PURE__*/ _jsx(ConsentContext, {
|
||||
value: {
|
||||
...consent,
|
||||
texts
|
||||
},
|
||||
children: children
|
||||
});
|
||||
}
|
||||
export function useConsentContext() {
|
||||
const ctx = use(ConsentContext);
|
||||
if (!ctx) {
|
||||
throw new Error('useConsentContext must be used within ConsentProvider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=ConsentContext.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/ConsentContext.tsx"],"sourcesContent":["'use client'\nimport { createContext, type ReactNode, use } from 'react'\n\nimport type { ConsentTexts } from './texts.js'\n\nimport { useConsent } from './useConsent.js'\n\ntype ConsentContextValue = { texts: ConsentTexts } & ReturnType<typeof useConsent>\n\nconst ConsentContext = createContext<ConsentContextValue | null>(null)\n\nexport function ConsentProvider({ children, texts }: { children: ReactNode; texts: ConsentTexts }) {\n const consent = useConsent()\n return <ConsentContext value={{ ...consent, texts }}>{children}</ConsentContext>\n}\n\nexport function useConsentContext(): ConsentContextValue {\n const ctx = use(ConsentContext)\n if (!ctx) {throw new Error('useConsentContext must be used within ConsentProvider')}\n return ctx\n}\n"],"names":["createContext","use","useConsent","ConsentContext","ConsentProvider","children","texts","consent","value","useConsentContext","ctx","Error"],"mappings":"AAAA;;AACA,SAASA,aAAa,EAAkBC,GAAG,QAAQ,QAAO;AAI1D,SAASC,UAAU,QAAQ,kBAAiB;AAI5C,MAAMC,+BAAiBH,cAA0C;AAEjE,OAAO,SAASI,gBAAgB,EAAEC,QAAQ,EAAEC,KAAK,EAAgD;IAC/F,MAAMC,UAAUL;IAChB,qBAAO,KAACC;QAAeK,OAAO;YAAE,GAAGD,OAAO;YAAED;QAAM;kBAAID;;AACxD;AAEA,OAAO,SAASI;IACd,MAAMC,MAAMT,IAAIE;IAChB,IAAI,CAACO,KAAK;QAAC,MAAM,IAAIC,MAAM;IAAwD;IACnF,OAAOD;AACT"}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Optional class overrides — for when tokens aren't enough, e.g. turning the
|
||||
* bottom bar into a centred modal. Replaces the slot's classes outright.
|
||||
*/
|
||||
export type CookieBannerClassNames = {
|
||||
primaryButton?: string;
|
||||
root?: string;
|
||||
secondaryButton?: string;
|
||||
};
|
||||
export declare function CookieBanner({ classNames }?: {
|
||||
classNames?: CookieBannerClassNames;
|
||||
}): import("react/jsx-runtime").JSX.Element | null;
|
||||
Vendored
-179
@@ -1,179 +0,0 @@
|
||||
'use client';
|
||||
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
||||
import { Cookie } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CONSENT_CATEGORIES } from './categories.js';
|
||||
import { useConsentContext } from './ConsentContext.js';
|
||||
/**
|
||||
* Colours and radius come from CSS custom properties with built-in fallbacks,
|
||||
* so the banner looks right with no setup, and re-skinning per client means
|
||||
* declaring a few variables — no imports, no props, no specificity fights:
|
||||
*
|
||||
* ```css
|
||||
* :root {
|
||||
* --ipal-primary: #16a34a;
|
||||
* --ipal-radius: 1rem;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Available: --ipal-surface, --ipal-border, --ipal-text, --ipal-text-strong,
|
||||
* --ipal-text-muted, --ipal-primary, --ipal-primary-hover, --ipal-primary-text,
|
||||
* --ipal-hover, --ipal-radius. Each has a `-dark` counterpart used under
|
||||
* Tailwind's `dark:` variant (e.g. --ipal-surface-dark).
|
||||
*
|
||||
* Layout stays in Tailwind: spacing and flow aren't things a brand changes, and
|
||||
* exposing them as tokens would mean re-inventing CSS one variable at a time.
|
||||
*/ const surface = 'bg-[var(--ipal-surface,#fff)] dark:bg-[var(--ipal-surface-dark,#171717)]';
|
||||
const border = 'border-[var(--ipal-border,#e5e5e5)] dark:border-[var(--ipal-border-dark,#262626)]';
|
||||
const radius = 'rounded-[var(--ipal-radius,0.375rem)]';
|
||||
const accent = 'text-[var(--ipal-primary,#2563eb)]';
|
||||
const defaults = {
|
||||
primaryButton: `${radius} px-3 py-1.5 text-sm font-medium bg-[var(--ipal-primary,#2563eb)] text-[var(--ipal-primary-text,#fff)] hover:bg-[var(--ipal-primary-hover,#1d4ed8)]`,
|
||||
root: `fixed inset-x-0 bottom-0 z-50 border-t p-4 shadow-lg ${surface} ${border}`,
|
||||
secondaryButton: `${radius} px-3 py-1.5 text-sm font-medium text-[var(--ipal-text,#404040)] hover:bg-[var(--ipal-hover,#f5f5f5)] dark:text-[var(--ipal-text-dark,#e5e5e5)] dark:hover:bg-[var(--ipal-hover-dark,#262626)]`
|
||||
};
|
||||
const bodyText = 'text-[var(--ipal-text,#404040)] dark:text-[var(--ipal-text-dark,#d4d4d4)]';
|
||||
const strongText = 'text-[var(--ipal-text-strong,#171717)] dark:text-[var(--ipal-text-strong-dark,#f5f5f5)]';
|
||||
const mutedText = 'text-[var(--ipal-text-muted,#737373)] dark:text-[var(--ipal-text-muted-dark,#a3a3a3)]';
|
||||
export function CookieBanner({ classNames } = {}) {
|
||||
const { acceptAll, decided, rejectAll, savePreferences, state, texts } = useConsentContext();
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [choices, setChoices] = useState(state);
|
||||
useEffect(()=>{
|
||||
setChoices(state);
|
||||
}, [
|
||||
state
|
||||
]);
|
||||
if (decided) {
|
||||
return null;
|
||||
}
|
||||
const toggle = (cat)=>{
|
||||
if (cat === 'necessary') {
|
||||
return;
|
||||
}
|
||||
setChoices((prev)=>({
|
||||
...prev,
|
||||
[cat]: !prev[cat]
|
||||
}));
|
||||
};
|
||||
const rootClass = classNames?.root ?? defaults.root;
|
||||
const primaryClass = classNames?.primaryButton ?? defaults.primaryButton;
|
||||
const secondaryClass = classNames?.secondaryButton ?? defaults.secondaryButton;
|
||||
return /*#__PURE__*/ _jsx("div", {
|
||||
"aria-label": "Cookie consent",
|
||||
className: rootClass,
|
||||
"data-ipal": "banner",
|
||||
role: "dialog",
|
||||
children: /*#__PURE__*/ _jsx("div", {
|
||||
className: "mx-auto max-w-4xl",
|
||||
children: !showSettings ? /*#__PURE__*/ _jsxs("div", {
|
||||
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
||||
children: [
|
||||
/*#__PURE__*/ _jsxs("div", {
|
||||
className: "flex items-start gap-3",
|
||||
children: [
|
||||
/*#__PURE__*/ _jsx(Cookie, {
|
||||
"aria-hidden": "true",
|
||||
className: `h-6 w-6 shrink-0 ${accent}`
|
||||
}),
|
||||
/*#__PURE__*/ _jsxs("p", {
|
||||
className: `text-justify text-sm ${bodyText}`,
|
||||
children: [
|
||||
texts.message,
|
||||
texts.privacyLink && /*#__PURE__*/ _jsxs(_Fragment, {
|
||||
children: [
|
||||
' ',
|
||||
/*#__PURE__*/ _jsx("a", {
|
||||
className: `${accent} underline hover:no-underline`,
|
||||
href: texts.privacyLink.href,
|
||||
children: texts.privacyLink.label
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
}),
|
||||
/*#__PURE__*/ _jsxs("div", {
|
||||
className: "flex shrink-0 gap-2",
|
||||
children: [
|
||||
/*#__PURE__*/ _jsx("button", {
|
||||
className: secondaryClass,
|
||||
onClick: ()=>setShowSettings(true),
|
||||
children: texts.buttons.settings
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("button", {
|
||||
className: secondaryClass,
|
||||
onClick: rejectAll,
|
||||
children: texts.buttons.reject
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("button", {
|
||||
className: primaryClass,
|
||||
onClick: acceptAll,
|
||||
children: texts.buttons.acceptAll
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
}) : /*#__PURE__*/ _jsxs("div", {
|
||||
className: "flex flex-col gap-4",
|
||||
children: [
|
||||
/*#__PURE__*/ _jsxs("h2", {
|
||||
className: `flex items-center gap-2 text-base font-semibold ${strongText}`,
|
||||
children: [
|
||||
/*#__PURE__*/ _jsx(Cookie, {
|
||||
"aria-hidden": "true",
|
||||
className: `h-5 w-5 ${accent}`
|
||||
}),
|
||||
texts.settingsTitle
|
||||
]
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("ul", {
|
||||
className: "flex flex-col gap-3",
|
||||
children: CONSENT_CATEGORIES.map((cat)=>/*#__PURE__*/ _jsxs("li", {
|
||||
className: "flex items-start justify-between gap-4",
|
||||
children: [
|
||||
/*#__PURE__*/ _jsxs("div", {
|
||||
children: [
|
||||
/*#__PURE__*/ _jsx("p", {
|
||||
className: `text-sm font-medium ${strongText}`,
|
||||
children: texts.categories[cat].title
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("p", {
|
||||
className: `text-xs ${mutedText}`,
|
||||
children: texts.categories[cat].description
|
||||
})
|
||||
]
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("input", {
|
||||
checked: cat === 'necessary' ? true : choices[cat],
|
||||
className: "mt-1 h-4 w-4 shrink-0 accent-[var(--ipal-primary,#2563eb)]",
|
||||
disabled: cat === 'necessary',
|
||||
onChange: ()=>toggle(cat),
|
||||
type: "checkbox"
|
||||
})
|
||||
]
|
||||
}, cat))
|
||||
}),
|
||||
/*#__PURE__*/ _jsxs("div", {
|
||||
className: "flex justify-end gap-2",
|
||||
children: [
|
||||
/*#__PURE__*/ _jsx("button", {
|
||||
className: secondaryClass,
|
||||
onClick: ()=>setShowSettings(false),
|
||||
children: texts.buttons.back
|
||||
}),
|
||||
/*#__PURE__*/ _jsx("button", {
|
||||
className: primaryClass,
|
||||
onClick: ()=>savePreferences(choices),
|
||||
children: texts.buttons.save
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=CookieBanner.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-13
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Floating "cookie settings" button — lets visitors reopen the consent panel
|
||||
* after deciding (GDPR: withdrawing consent must be as easy as giving it).
|
||||
* Hidden while the banner is showing.
|
||||
*
|
||||
* Colours follow the same CSS custom properties as CookieBanner
|
||||
* (--ipal-surface, --ipal-border, --ipal-hover, --ipal-text), so a client's
|
||||
* palette applies to both without touching either component. Pass className to
|
||||
* replace the styling outright.
|
||||
*/
|
||||
export declare function CookieButton({ className }?: {
|
||||
className?: string;
|
||||
}): import("react/jsx-runtime").JSX.Element | null;
|
||||
Vendored
-32
@@ -1,32 +0,0 @@
|
||||
'use client';
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import { Cookie } from 'lucide-react';
|
||||
import { useConsentContext } from './ConsentContext.js';
|
||||
/**
|
||||
* Floating "cookie settings" button — lets visitors reopen the consent panel
|
||||
* after deciding (GDPR: withdrawing consent must be as easy as giving it).
|
||||
* Hidden while the banner is showing.
|
||||
*
|
||||
* Colours follow the same CSS custom properties as CookieBanner
|
||||
* (--ipal-surface, --ipal-border, --ipal-hover, --ipal-text), so a client's
|
||||
* palette applies to both without touching either component. Pass className to
|
||||
* replace the styling outright.
|
||||
*/ export function CookieButton({ className } = {}) {
|
||||
const { decided, reopen } = useConsentContext();
|
||||
if (!decided) {
|
||||
return null;
|
||||
}
|
||||
const cls = className ?? 'fixed bottom-4 left-4 z-40 flex h-11 w-11 items-center justify-center rounded-full border shadow-md transition-colors ' + 'bg-[var(--ipal-surface,#fff)] border-[var(--ipal-border,#e5e5e5)] hover:bg-[var(--ipal-hover,#f5f5f5)] ' + 'dark:bg-[var(--ipal-surface-dark,#171717)] dark:border-[var(--ipal-border-dark,#262626)] dark:hover:bg-[var(--ipal-hover-dark,#262626)]';
|
||||
return /*#__PURE__*/ _jsx("button", {
|
||||
"aria-label": "Cookie settings",
|
||||
className: cls,
|
||||
"data-ipal": "cookie-button",
|
||||
onClick: reopen,
|
||||
children: /*#__PURE__*/ _jsx(Cookie, {
|
||||
"aria-hidden": "true",
|
||||
className: "h-5 w-5 text-[var(--ipal-text,#525252)] dark:text-[var(--ipal-text-dark,#d4d4d4)]"
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=CookieButton.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/CookieButton.tsx"],"sourcesContent":["'use client'\nimport { Cookie } from 'lucide-react'\n\nimport { useConsentContext } from './ConsentContext.js'\n\n/**\n * Floating \"cookie settings\" button — lets visitors reopen the consent panel\n * after deciding (GDPR: withdrawing consent must be as easy as giving it).\n * Hidden while the banner is showing.\n *\n * Colours follow the same CSS custom properties as CookieBanner\n * (--ipal-surface, --ipal-border, --ipal-hover, --ipal-text), so a client's\n * palette applies to both without touching either component. Pass className to\n * replace the styling outright.\n */\nexport function CookieButton({ className }: { className?: string } = {}) {\n const { decided, reopen } = useConsentContext()\n\n if (!decided) {return null}\n\n const cls =\n className ??\n 'fixed bottom-4 left-4 z-40 flex h-11 w-11 items-center justify-center rounded-full border shadow-md transition-colors ' +\n 'bg-[var(--ipal-surface,#fff)] border-[var(--ipal-border,#e5e5e5)] hover:bg-[var(--ipal-hover,#f5f5f5)] ' +\n 'dark:bg-[var(--ipal-surface-dark,#171717)] dark:border-[var(--ipal-border-dark,#262626)] dark:hover:bg-[var(--ipal-hover-dark,#262626)]'\n\n return (\n <button aria-label=\"Cookie settings\" className={cls} data-ipal=\"cookie-button\" onClick={reopen}>\n <Cookie\n aria-hidden=\"true\"\n className=\"h-5 w-5 text-[var(--ipal-text,#525252)] dark:text-[var(--ipal-text-dark,#d4d4d4)]\"\n />\n </button>\n )\n}\n"],"names":["Cookie","useConsentContext","CookieButton","className","decided","reopen","cls","button","aria-label","data-ipal","onClick","aria-hidden"],"mappings":"AAAA;;AACA,SAASA,MAAM,QAAQ,eAAc;AAErC,SAASC,iBAAiB,QAAQ,sBAAqB;AAEvD;;;;;;;;;CASC,GACD,OAAO,SAASC,aAAa,EAAEC,SAAS,EAA0B,GAAG,CAAC,CAAC;IACrE,MAAM,EAAEC,OAAO,EAAEC,MAAM,EAAE,GAAGJ;IAE5B,IAAI,CAACG,SAAS;QAAC,OAAO;IAAI;IAE1B,MAAME,MACJH,aACA,2HACE,4GACA;IAEJ,qBACE,KAACI;QAAOC,cAAW;QAAkBL,WAAWG;QAAKG,aAAU;QAAgBC,SAASL;kBACtF,cAAA,KAACL;YACCW,eAAY;YACZR,WAAU;;;AAIlB"}
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* Cookie consent categories (GDPR). 'necessary' is always granted and cannot be
|
||||
* disabled. The rest default to false until the user opts in.
|
||||
*/
|
||||
export declare const CONSENT_CATEGORIES: readonly ["necessary", "functional", "analytics", "marketing"];
|
||||
export type ConsentCategory = (typeof CONSENT_CATEGORIES)[number];
|
||||
export type ConsentState = Record<ConsentCategory, boolean>;
|
||||
export declare const DEFAULT_CONSENT: ConsentState;
|
||||
export declare const ACCEPT_ALL_CONSENT: ConsentState;
|
||||
export declare const REJECT_ALL_CONSENT: ConsentState;
|
||||
Vendored
-26
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* Cookie consent categories (GDPR). 'necessary' is always granted and cannot be
|
||||
* disabled. The rest default to false until the user opts in.
|
||||
*/ export const CONSENT_CATEGORIES = [
|
||||
'necessary',
|
||||
'functional',
|
||||
'analytics',
|
||||
'marketing'
|
||||
];
|
||||
export const DEFAULT_CONSENT = {
|
||||
necessary: true,
|
||||
functional: false,
|
||||
analytics: false,
|
||||
marketing: false
|
||||
};
|
||||
export const ACCEPT_ALL_CONSENT = {
|
||||
necessary: true,
|
||||
functional: true,
|
||||
analytics: true,
|
||||
marketing: true
|
||||
};
|
||||
export const REJECT_ALL_CONSENT = {
|
||||
...DEFAULT_CONSENT
|
||||
};
|
||||
|
||||
//# sourceMappingURL=categories.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/categories.ts"],"sourcesContent":["/**\n * Cookie consent categories (GDPR). 'necessary' is always granted and cannot be\n * disabled. The rest default to false until the user opts in.\n */\nexport const CONSENT_CATEGORIES = ['necessary', 'functional', 'analytics', 'marketing'] as const\n\nexport type ConsentCategory = (typeof CONSENT_CATEGORIES)[number]\n\nexport type ConsentState = Record<ConsentCategory, boolean>\n\nexport const DEFAULT_CONSENT: ConsentState = {\n necessary: true,\n functional: false,\n analytics: false,\n marketing: false,\n}\n\nexport const ACCEPT_ALL_CONSENT: ConsentState = {\n necessary: true,\n functional: true,\n analytics: true,\n marketing: true,\n}\n\nexport const REJECT_ALL_CONSENT: ConsentState = { ...DEFAULT_CONSENT }\n"],"names":["CONSENT_CATEGORIES","DEFAULT_CONSENT","necessary","functional","analytics","marketing","ACCEPT_ALL_CONSENT","REJECT_ALL_CONSENT"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,qBAAqB;IAAC;IAAa;IAAc;IAAa;CAAY,CAAS;AAMhG,OAAO,MAAMC,kBAAgC;IAC3CC,WAAW;IACXC,YAAY;IACZC,WAAW;IACXC,WAAW;AACb,EAAC;AAED,OAAO,MAAMC,qBAAmC;IAC9CJ,WAAW;IACXC,YAAY;IACZC,WAAW;IACXC,WAAW;AACb,EAAC;AAED,OAAO,MAAME,qBAAmC;IAAE,GAAGN,eAAe;AAAC,EAAC"}
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
export { ConsentProvider, useConsentContext } from './ConsentContext.js';
|
||||
export { CookieBanner } from './CookieBanner.js';
|
||||
export type { CookieBannerClassNames } from './CookieBanner.js';
|
||||
export { CookieButton } from './CookieButton.js';
|
||||
export { useConsent } from './useConsent.js';
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
'use client';
|
||||
export { ConsentProvider, useConsentContext } from './ConsentContext.js';
|
||||
export { CookieBanner } from './CookieBanner.js';
|
||||
export { CookieButton } from './CookieButton.js';
|
||||
// Client-only consent exports — hook, provider, and UI components.
|
||||
export { useConsent } from './useConsent.js';
|
||||
|
||||
//# sourceMappingURL=client.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/client.ts"],"sourcesContent":["'use client'\nexport { ConsentProvider, useConsentContext } from './ConsentContext.js'\nexport { CookieBanner } from './CookieBanner.js'\nexport type { CookieBannerClassNames } from './CookieBanner.js'\nexport { CookieButton } from './CookieButton.js'\n// Client-only consent exports — hook, provider, and UI components.\nexport { useConsent } from './useConsent.js'\n"],"names":["ConsentProvider","useConsentContext","CookieBanner","CookieButton","useConsent"],"mappings":"AAAA;AACA,SAASA,eAAe,EAAEC,iBAAiB,QAAQ,sBAAqB;AACxE,SAASC,YAAY,QAAQ,oBAAmB;AAEhD,SAASC,YAAY,QAAQ,oBAAmB;AAChD,mEAAmE;AACnE,SAASC,UAAU,QAAQ,kBAAiB"}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
import type { I18nConfig } from '../i18n/index.js';
|
||||
import type { ConsentTexts } from './texts.js';
|
||||
type GetConsentTextsArgs = {
|
||||
config: I18nConfig;
|
||||
locale?: string;
|
||||
payload: BasePayload;
|
||||
/**
|
||||
* Privacy-policy label + the system page (from SiteSettings.privacyPolicy,
|
||||
* read with locale:'all') to link to. Optional — omit for no link.
|
||||
*/
|
||||
privacyPolicy?: {
|
||||
label: string;
|
||||
page: {
|
||||
slug?: null | Record<string, unknown>;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Resolves consent banner texts from the CookieSettings global, falling back to
|
||||
* English defaults per field. The privacy-policy link is built from the pages
|
||||
* module (system page role), not stored in CookieSettings — one source of truth.
|
||||
*/
|
||||
export declare function getConsentTexts({ config, locale, payload, privacyPolicy, }: GetConsentTextsArgs): Promise<ConsentTexts>;
|
||||
export {};
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
import { getSystemPagePath } from '../pages/index.js';
|
||||
import { getGlobal } from '../payload/index.js';
|
||||
import { CONSENT_CATEGORIES } from './categories.js';
|
||||
/** English fallback used when the global has no value for a field. */ const FALLBACK = {
|
||||
buttons: {
|
||||
acceptAll: 'Accept all',
|
||||
back: 'Back',
|
||||
reject: 'Reject',
|
||||
save: 'Save preferences',
|
||||
settings: 'Settings'
|
||||
},
|
||||
categories: {
|
||||
analytics: {
|
||||
description: 'Help us understand usage.',
|
||||
title: 'Analytics'
|
||||
},
|
||||
functional: {
|
||||
description: 'Remember preferences.',
|
||||
title: 'Functional'
|
||||
},
|
||||
marketing: {
|
||||
description: 'Used to show relevant ads.',
|
||||
title: 'Marketing'
|
||||
},
|
||||
necessary: {
|
||||
description: 'Required for the site to work, including your language and theme preferences. Always on.',
|
||||
title: 'Necessary'
|
||||
}
|
||||
},
|
||||
message: 'We use cookies to run the site, analyze traffic, and improve your experience. You can accept all, reject non-essential, or choose which to allow.',
|
||||
privacyLink: null,
|
||||
settingsTitle: 'Cookie settings'
|
||||
};
|
||||
/**
|
||||
* Resolves consent banner texts from the CookieSettings global, falling back to
|
||||
* English defaults per field. The privacy-policy link is built from the pages
|
||||
* module (system page role), not stored in CookieSettings — one source of truth.
|
||||
*/ export async function getConsentTexts({ config, locale, payload, privacyPolicy }) {
|
||||
const g = await getGlobal(payload, 'cookie-settings', {
|
||||
locale
|
||||
});
|
||||
const categories = {};
|
||||
for (const cat of CONSENT_CATEGORIES){
|
||||
const entry = g.categories?.find((c)=>c.key === cat);
|
||||
categories[cat] = {
|
||||
description: entry?.description || FALLBACK.categories[cat].description,
|
||||
title: entry?.title || FALLBACK.categories[cat].title
|
||||
};
|
||||
}
|
||||
let privacyLink = null;
|
||||
if (privacyPolicy?.page && locale) {
|
||||
const href = getSystemPagePath({
|
||||
config,
|
||||
locale,
|
||||
page: privacyPolicy.page
|
||||
});
|
||||
if (href) {
|
||||
privacyLink = {
|
||||
href,
|
||||
label: privacyPolicy.label
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
buttons: {
|
||||
acceptAll: g.buttons?.acceptAll || FALLBACK.buttons.acceptAll,
|
||||
back: g.buttons?.back || FALLBACK.buttons.back,
|
||||
reject: g.buttons?.reject || FALLBACK.buttons.reject,
|
||||
save: g.buttons?.save || FALLBACK.buttons.save,
|
||||
settings: g.buttons?.settings || FALLBACK.buttons.settings
|
||||
},
|
||||
categories,
|
||||
message: g.message || FALLBACK.message,
|
||||
privacyLink,
|
||||
settingsTitle: g.settingsTitle || FALLBACK.settingsTitle
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=getConsentTexts.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
-23
@@ -1,23 +0,0 @@
|
||||
import type { ConsentState } from './categories.js';
|
||||
declare global {
|
||||
interface Window {
|
||||
dataLayer?: unknown[];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Mirrors Google's canonical snippet: `function gtag(){dataLayer.push(arguments)}`.
|
||||
*
|
||||
* Pushing `arguments` rather than a plain array is not a stylistic detail.
|
||||
* Google's tags recognise a consent command by the Arguments object it arrives
|
||||
* in; a real array lands in the dataLayer as ordinary data and the command is
|
||||
* silently ignored — the tag loads, but consent never updates and analytics
|
||||
* stays denied. The rest parameter exists only to type the call sites.
|
||||
*
|
||||
* Exported for the analytics module (which needs `js`/`config` commands) —
|
||||
* intentionally not re-exported from the package's public API.
|
||||
*/
|
||||
export declare function gtag(..._args: unknown[]): void;
|
||||
/** Sets the default consent state (call before Google tags load). */
|
||||
export declare function setDefaultConsent(state: ConsentState): void;
|
||||
/** Updates consent after the user decides. */
|
||||
export declare function updateConsent(state: ConsentState): void;
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
function toSignals(state) {
|
||||
const g = (b)=>b ? 'granted' : 'denied';
|
||||
return {
|
||||
ad_personalization: g(state.marketing),
|
||||
ad_storage: g(state.marketing),
|
||||
ad_user_data: g(state.marketing),
|
||||
analytics_storage: g(state.analytics),
|
||||
functionality_storage: g(state.functional),
|
||||
personalization_storage: g(state.functional),
|
||||
security_storage: 'granted'
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Mirrors Google's canonical snippet: `function gtag(){dataLayer.push(arguments)}`.
|
||||
*
|
||||
* Pushing `arguments` rather than a plain array is not a stylistic detail.
|
||||
* Google's tags recognise a consent command by the Arguments object it arrives
|
||||
* in; a real array lands in the dataLayer as ordinary data and the command is
|
||||
* silently ignored — the tag loads, but consent never updates and analytics
|
||||
* stays denied. The rest parameter exists only to type the call sites.
|
||||
*
|
||||
* Exported for the analytics module (which needs `js`/`config` commands) —
|
||||
* intentionally not re-exported from the package's public API.
|
||||
*/ export function gtag(..._args) {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
window.dataLayer.push(arguments);
|
||||
}
|
||||
/** Sets the default consent state (call before Google tags load). */ export function setDefaultConsent(state) {
|
||||
gtag('consent', 'default', {
|
||||
...toSignals(state),
|
||||
wait_for_update: 500
|
||||
});
|
||||
}
|
||||
/** Updates consent after the user decides. */ export function updateConsent(state) {
|
||||
gtag('consent', 'update', toSignals(state));
|
||||
}
|
||||
|
||||
//# sourceMappingURL=googleConsent.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/googleConsent.ts"],"sourcesContent":["import type { ConsentState } from './categories.js'\n\ntype ConsentValue = 'denied' | 'granted'\ntype GoogleConsentSignals = Record<string, ConsentValue>\n\nfunction toSignals(state: ConsentState): GoogleConsentSignals {\n const g = (b: boolean): ConsentValue => (b ? 'granted' : 'denied')\n return {\n ad_personalization: g(state.marketing),\n ad_storage: g(state.marketing),\n ad_user_data: g(state.marketing),\n analytics_storage: g(state.analytics),\n functionality_storage: g(state.functional),\n personalization_storage: g(state.functional),\n security_storage: 'granted', // necessary — always on\n }\n}\n\ndeclare global {\n interface Window {\n dataLayer?: unknown[]\n }\n}\n\n/**\n * Mirrors Google's canonical snippet: `function gtag(){dataLayer.push(arguments)}`.\n *\n * Pushing `arguments` rather than a plain array is not a stylistic detail.\n * Google's tags recognise a consent command by the Arguments object it arrives\n * in; a real array lands in the dataLayer as ordinary data and the command is\n * silently ignored — the tag loads, but consent never updates and analytics\n * stays denied. The rest parameter exists only to type the call sites.\n *\n * Exported for the analytics module (which needs `js`/`config` commands) —\n * intentionally not re-exported from the package's public API.\n */\nexport function gtag(..._args: unknown[]) {\n if (typeof window === 'undefined') {return}\n window.dataLayer = window.dataLayer || []\n // eslint-disable-next-line prefer-rest-params\n window.dataLayer.push(arguments)\n}\n\n/** Sets the default consent state (call before Google tags load). */\nexport function setDefaultConsent(state: ConsentState) {\n gtag('consent', 'default', {\n ...toSignals(state),\n wait_for_update: 500, // ms to wait for an update before firing\n })\n}\n\n/** Updates consent after the user decides. */\nexport function updateConsent(state: ConsentState) {\n gtag('consent', 'update', toSignals(state))\n}\n"],"names":["toSignals","state","g","b","ad_personalization","marketing","ad_storage","ad_user_data","analytics_storage","analytics","functionality_storage","functional","personalization_storage","security_storage","gtag","_args","window","dataLayer","push","arguments","setDefaultConsent","wait_for_update","updateConsent"],"mappings":"AAKA,SAASA,UAAUC,KAAmB;IACpC,MAAMC,IAAI,CAACC,IAA8BA,IAAI,YAAY;IACzD,OAAO;QACLC,oBAAoBF,EAAED,MAAMI,SAAS;QACrCC,YAAYJ,EAAED,MAAMI,SAAS;QAC7BE,cAAcL,EAAED,MAAMI,SAAS;QAC/BG,mBAAmBN,EAAED,MAAMQ,SAAS;QACpCC,uBAAuBR,EAAED,MAAMU,UAAU;QACzCC,yBAAyBV,EAAED,MAAMU,UAAU;QAC3CE,kBAAkB;IACpB;AACF;AAQA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,KAAK,GAAGC,KAAgB;IACtC,IAAI,OAAOC,WAAW,aAAa;QAAC;IAAM;IAC1CA,OAAOC,SAAS,GAAGD,OAAOC,SAAS,IAAI,EAAE;IACzC,8CAA8C;IAC9CD,OAAOC,SAAS,CAACC,IAAI,CAACC;AACxB;AAEA,mEAAmE,GACnE,OAAO,SAASC,kBAAkBnB,KAAmB;IACnDa,KAAK,WAAW,WAAW;QACzB,GAAGd,UAAUC,MAAM;QACnBoB,iBAAiB;IACnB;AACF;AAEA,4CAA4C,GAC5C,OAAO,SAASC,cAAcrB,KAAmB;IAC/Ca,KAAK,WAAW,UAAUd,UAAUC;AACtC"}
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
export { ACCEPT_ALL_CONSENT, CONSENT_CATEGORIES, DEFAULT_CONSENT, REJECT_ALL_CONSENT, } from './categories.js';
|
||||
export type { ConsentCategory, ConsentState } from './categories.js';
|
||||
export { getConsentTexts } from './getConsentTexts.js';
|
||||
export { setDefaultConsent, updateConsent } from './googleConsent.js';
|
||||
export { CONSENT_COOKIE, CONSENT_MAX_AGE, CONSENT_VERSION, parseConsent, serializeConsent, } from './storage.js';
|
||||
export type { ConsentTexts } from './texts.js';
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
// Server-safe exports (logic, types, helper). Client components (banner,
|
||||
// provider, hook) are exported separately via ./client to keep the RSC/client
|
||||
// boundary clean.
|
||||
export { ACCEPT_ALL_CONSENT, CONSENT_CATEGORIES, DEFAULT_CONSENT, REJECT_ALL_CONSENT } from './categories.js';
|
||||
export { getConsentTexts } from './getConsentTexts.js';
|
||||
export { setDefaultConsent, updateConsent } from './googleConsent.js';
|
||||
export { CONSENT_COOKIE, CONSENT_MAX_AGE, CONSENT_VERSION, parseConsent, serializeConsent } from './storage.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/index.ts"],"sourcesContent":["// Server-safe exports (logic, types, helper). Client components (banner,\n// provider, hook) are exported separately via ./client to keep the RSC/client\n// boundary clean.\nexport {\n ACCEPT_ALL_CONSENT,\n CONSENT_CATEGORIES,\n DEFAULT_CONSENT,\n REJECT_ALL_CONSENT,\n} from './categories.js'\nexport type { ConsentCategory, ConsentState } from './categories.js'\nexport { getConsentTexts } from './getConsentTexts.js'\nexport { setDefaultConsent, updateConsent } from './googleConsent.js'\nexport {\n CONSENT_COOKIE,\n CONSENT_MAX_AGE,\n CONSENT_VERSION,\n parseConsent,\n serializeConsent,\n} from './storage.js'\nexport type { ConsentTexts } from './texts.js'\n"],"names":["ACCEPT_ALL_CONSENT","CONSENT_CATEGORIES","DEFAULT_CONSENT","REJECT_ALL_CONSENT","getConsentTexts","setDefaultConsent","updateConsent","CONSENT_COOKIE","CONSENT_MAX_AGE","CONSENT_VERSION","parseConsent","serializeConsent"],"mappings":"AAAA,yEAAyE;AACzE,8EAA8E;AAC9E,kBAAkB;AAClB,SACEA,kBAAkB,EAClBC,kBAAkB,EAClBC,eAAe,EACfC,kBAAkB,QACb,kBAAiB;AAExB,SAASC,eAAe,QAAQ,uBAAsB;AACtD,SAASC,iBAAiB,EAAEC,aAAa,QAAQ,qBAAoB;AACrE,SACEC,cAAc,EACdC,eAAe,EACfC,eAAe,EACfC,YAAY,EACZC,gBAAgB,QACX,eAAc"}
|
||||
Vendored
-11
@@ -1,11 +0,0 @@
|
||||
import type { ConsentState } from './categories.js';
|
||||
/**
|
||||
* Consent persistence in a cookie. Stored with a version so we can invalidate
|
||||
* old consents when the policy changes, and a timestamp (GDPR: consent must be
|
||||
* dated). Readable both client-side (banner) and server-side (script gating).
|
||||
*/
|
||||
export declare const CONSENT_COOKIE = "cookie-consent";
|
||||
export declare const CONSENT_VERSION = 1;
|
||||
export declare const CONSENT_MAX_AGE: number;
|
||||
export declare function serializeConsent(state: ConsentState): string;
|
||||
export declare function parseConsent(raw: string | undefined): ConsentState | null;
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
import { CONSENT_CATEGORIES, DEFAULT_CONSENT } from './categories.js';
|
||||
/**
|
||||
* Consent persistence in a cookie. Stored with a version so we can invalidate
|
||||
* old consents when the policy changes, and a timestamp (GDPR: consent must be
|
||||
* dated). Readable both client-side (banner) and server-side (script gating).
|
||||
*/ export const CONSENT_COOKIE = 'cookie-consent';
|
||||
export const CONSENT_VERSION = 1;
|
||||
export const CONSENT_MAX_AGE = 60 * 60 * 24 * 180 // 180 days
|
||||
;
|
||||
export function serializeConsent(state) {
|
||||
const payload = {
|
||||
state,
|
||||
timestamp: new Date().toISOString(),
|
||||
version: CONSENT_VERSION
|
||||
};
|
||||
return encodeURIComponent(JSON.stringify(payload));
|
||||
}
|
||||
export function parseConsent(raw) {
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(decodeURIComponent(raw));
|
||||
if (parsed.version !== CONSENT_VERSION) {
|
||||
return null;
|
||||
}
|
||||
const state = {
|
||||
...DEFAULT_CONSENT
|
||||
};
|
||||
for (const cat of CONSENT_CATEGORIES){
|
||||
if (typeof parsed.state?.[cat] === 'boolean') {
|
||||
state[cat] = parsed.state[cat];
|
||||
}
|
||||
}
|
||||
state.necessary = true;
|
||||
return state;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//# sourceMappingURL=storage.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/storage.ts"],"sourcesContent":["import type { ConsentState } from './categories.js'\n\nimport { CONSENT_CATEGORIES, DEFAULT_CONSENT } from './categories.js'\n\n/**\n * Consent persistence in a cookie. Stored with a version so we can invalidate\n * old consents when the policy changes, and a timestamp (GDPR: consent must be\n * dated). Readable both client-side (banner) and server-side (script gating).\n */\nexport const CONSENT_COOKIE = 'cookie-consent'\nexport const CONSENT_VERSION = 1\nexport const CONSENT_MAX_AGE = 60 * 60 * 24 * 180 // 180 days\n\ntype StoredConsent = {\n state: ConsentState\n timestamp: string\n version: number\n}\n\nexport function serializeConsent(state: ConsentState): string {\n const payload: StoredConsent = {\n state,\n timestamp: new Date().toISOString(),\n version: CONSENT_VERSION,\n }\n return encodeURIComponent(JSON.stringify(payload))\n}\n\nexport function parseConsent(raw: string | undefined): ConsentState | null {\n if (!raw) {return null}\n try {\n const parsed = JSON.parse(decodeURIComponent(raw)) as StoredConsent\n if (parsed.version !== CONSENT_VERSION) {return null}\n const state = { ...DEFAULT_CONSENT }\n for (const cat of CONSENT_CATEGORIES) {\n if (typeof parsed.state?.[cat] === 'boolean') {state[cat] = parsed.state[cat]}\n }\n state.necessary = true\n return state\n } catch {\n return null\n }\n}\n"],"names":["CONSENT_CATEGORIES","DEFAULT_CONSENT","CONSENT_COOKIE","CONSENT_VERSION","CONSENT_MAX_AGE","serializeConsent","state","payload","timestamp","Date","toISOString","version","encodeURIComponent","JSON","stringify","parseConsent","raw","parsed","parse","decodeURIComponent","cat","necessary"],"mappings":"AAEA,SAASA,kBAAkB,EAAEC,eAAe,QAAQ,kBAAiB;AAErE;;;;CAIC,GACD,OAAO,MAAMC,iBAAiB,iBAAgB;AAC9C,OAAO,MAAMC,kBAAkB,EAAC;AAChC,OAAO,MAAMC,kBAAkB,KAAK,KAAK,KAAK,IAAI,WAAW;CAAZ;AAQjD,OAAO,SAASC,iBAAiBC,KAAmB;IAClD,MAAMC,UAAyB;QAC7BD;QACAE,WAAW,IAAIC,OAAOC,WAAW;QACjCC,SAASR;IACX;IACA,OAAOS,mBAAmBC,KAAKC,SAAS,CAACP;AAC3C;AAEA,OAAO,SAASQ,aAAaC,GAAuB;IAClD,IAAI,CAACA,KAAK;QAAC,OAAO;IAAI;IACtB,IAAI;QACF,MAAMC,SAASJ,KAAKK,KAAK,CAACC,mBAAmBH;QAC7C,IAAIC,OAAON,OAAO,KAAKR,iBAAiB;YAAC,OAAO;QAAI;QACpD,MAAMG,QAAQ;YAAE,GAAGL,eAAe;QAAC;QACnC,KAAK,MAAMmB,OAAOpB,mBAAoB;YACpC,IAAI,OAAOiB,OAAOX,KAAK,EAAE,CAACc,IAAI,KAAK,WAAW;gBAACd,KAAK,CAACc,IAAI,GAAGH,OAAOX,KAAK,CAACc,IAAI;YAAA;QAC/E;QACAd,MAAMe,SAAS,GAAG;QAClB,OAAOf;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF"}
|
||||
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
import type { ConsentCategory } from './categories.js';
|
||||
export type ConsentTexts = {
|
||||
buttons: {
|
||||
acceptAll: string;
|
||||
back: string;
|
||||
reject: string;
|
||||
save: string;
|
||||
settings: string;
|
||||
};
|
||||
categories: Record<ConsentCategory, {
|
||||
description: string;
|
||||
title: string;
|
||||
}>;
|
||||
message: string;
|
||||
/** null = no privacy-policy link shown */
|
||||
privacyLink: {
|
||||
href: string;
|
||||
label: string;
|
||||
} | null;
|
||||
settingsTitle: string;
|
||||
};
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
export { };
|
||||
|
||||
//# sourceMappingURL=texts.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/texts.ts"],"sourcesContent":["import type { ConsentCategory } from './categories.js'\n\nexport type ConsentTexts = {\n buttons: {\n acceptAll: string\n back: string\n reject: string\n save: string\n settings: string\n }\n categories: Record<ConsentCategory, { description: string; title: string }>\n message: string\n /** null = no privacy-policy link shown */\n privacyLink: { href: string; label: string } | null\n settingsTitle: string\n}\n"],"names":[],"mappings":"AAEA,WAaC"}
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
import type { ConsentCategory, ConsentState } from './categories.js';
|
||||
export declare function useConsent(): {
|
||||
acceptAll: () => void;
|
||||
decided: boolean;
|
||||
rejectAll: () => void;
|
||||
reopen: () => void;
|
||||
savePreferences: (choices: Partial<Record<ConsentCategory, boolean>>) => void;
|
||||
state: ConsentState;
|
||||
};
|
||||
Vendored
-51
@@ -1,51 +0,0 @@
|
||||
'use client';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ACCEPT_ALL_CONSENT, DEFAULT_CONSENT, REJECT_ALL_CONSENT } from './categories.js';
|
||||
import { updateConsent } from './googleConsent.js';
|
||||
import { CONSENT_COOKIE, CONSENT_MAX_AGE, parseConsent, serializeConsent } from './storage.js';
|
||||
export function useConsent() {
|
||||
const [state, setState] = useState(DEFAULT_CONSENT);
|
||||
const [decided, setDecided] = useState(false);
|
||||
useEffect(()=>{
|
||||
const raw = document.cookie.split('; ').find((c)=>c.startsWith(`${CONSENT_COOKIE}=`))?.split('=')[1];
|
||||
const parsed = parseConsent(raw);
|
||||
if (parsed) {
|
||||
setState(parsed);
|
||||
setDecided(true);
|
||||
}
|
||||
}, []);
|
||||
const persist = useCallback((next)=>{
|
||||
document.cookie = `${CONSENT_COOKIE}=${serializeConsent(next)};path=/;max-age=${CONSENT_MAX_AGE};samesite=lax`;
|
||||
setState(next);
|
||||
setDecided(true);
|
||||
// Tell Google right away. Tags are already on the page holding whatever
|
||||
// defaults Analytics set at load; without this update they keep them for
|
||||
// the rest of the session and never write their cookies — the banner would
|
||||
// look like it worked while nothing changed.
|
||||
updateConsent(next);
|
||||
}, []);
|
||||
const acceptAll = useCallback(()=>persist(ACCEPT_ALL_CONSENT), [
|
||||
persist
|
||||
]);
|
||||
const rejectAll = useCallback(()=>persist(REJECT_ALL_CONSENT), [
|
||||
persist
|
||||
]);
|
||||
const savePreferences = useCallback((choices)=>persist({
|
||||
...DEFAULT_CONSENT,
|
||||
...choices,
|
||||
necessary: true
|
||||
}), [
|
||||
persist
|
||||
]);
|
||||
const reopen = useCallback(()=>setDecided(false), []);
|
||||
return {
|
||||
acceptAll,
|
||||
decided,
|
||||
rejectAll,
|
||||
reopen,
|
||||
savePreferences,
|
||||
state
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=useConsent.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/consent/useConsent.ts"],"sourcesContent":["'use client'\nimport { useCallback, useEffect, useState } from 'react'\n\nimport type { ConsentCategory, ConsentState } from './categories.js'\n\nimport { ACCEPT_ALL_CONSENT, DEFAULT_CONSENT, REJECT_ALL_CONSENT } from './categories.js'\nimport { updateConsent } from './googleConsent.js'\nimport { CONSENT_COOKIE, CONSENT_MAX_AGE, parseConsent, serializeConsent } from './storage.js'\n\nexport function useConsent() {\n const [state, setState] = useState<ConsentState>(DEFAULT_CONSENT)\n const [decided, setDecided] = useState(false)\n\n useEffect(() => {\n const raw = document.cookie\n .split('; ')\n .find((c) => c.startsWith(`${CONSENT_COOKIE}=`))\n ?.split('=')[1]\n const parsed = parseConsent(raw)\n if (parsed) {\n setState(parsed)\n setDecided(true)\n }\n }, [])\n\n const persist = useCallback((next: ConsentState) => {\n document.cookie = `${CONSENT_COOKIE}=${serializeConsent(next)};path=/;max-age=${CONSENT_MAX_AGE};samesite=lax`\n setState(next)\n setDecided(true)\n // Tell Google right away. Tags are already on the page holding whatever\n // defaults Analytics set at load; without this update they keep them for\n // the rest of the session and never write their cookies — the banner would\n // look like it worked while nothing changed.\n updateConsent(next)\n }, [])\n\n const acceptAll = useCallback(() => persist(ACCEPT_ALL_CONSENT), [persist])\n const rejectAll = useCallback(() => persist(REJECT_ALL_CONSENT), [persist])\n const savePreferences = useCallback(\n (choices: Partial<Record<ConsentCategory, boolean>>) =>\n persist({ ...DEFAULT_CONSENT, ...choices, necessary: true }),\n [persist],\n )\n const reopen = useCallback(() => setDecided(false), [])\n\n return { acceptAll, decided, rejectAll, reopen, savePreferences, state }\n}\n"],"names":["useCallback","useEffect","useState","ACCEPT_ALL_CONSENT","DEFAULT_CONSENT","REJECT_ALL_CONSENT","updateConsent","CONSENT_COOKIE","CONSENT_MAX_AGE","parseConsent","serializeConsent","useConsent","state","setState","decided","setDecided","raw","document","cookie","split","find","c","startsWith","parsed","persist","next","acceptAll","rejectAll","savePreferences","choices","necessary","reopen"],"mappings":"AAAA;AACA,SAASA,WAAW,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAIxD,SAASC,kBAAkB,EAAEC,eAAe,EAAEC,kBAAkB,QAAQ,kBAAiB;AACzF,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,cAAc,EAAEC,eAAe,EAAEC,YAAY,EAAEC,gBAAgB,QAAQ,eAAc;AAE9F,OAAO,SAASC;IACd,MAAM,CAACC,OAAOC,SAAS,GAAGX,SAAuBE;IACjD,MAAM,CAACU,SAASC,WAAW,GAAGb,SAAS;IAEvCD,UAAU;QACR,MAAMe,MAAMC,SAASC,MAAM,CACxBC,KAAK,CAAC,MACNC,IAAI,CAAC,CAACC,IAAMA,EAAEC,UAAU,CAAC,GAAGf,eAAe,CAAC,CAAC,IAC5CY,MAAM,IAAI,CAAC,EAAE;QACjB,MAAMI,SAASd,aAAaO;QAC5B,IAAIO,QAAQ;YACVV,SAASU;YACTR,WAAW;QACb;IACF,GAAG,EAAE;IAEL,MAAMS,UAAUxB,YAAY,CAACyB;QAC3BR,SAASC,MAAM,GAAG,GAAGX,eAAe,CAAC,EAAEG,iBAAiBe,MAAM,gBAAgB,EAAEjB,gBAAgB,aAAa,CAAC;QAC9GK,SAASY;QACTV,WAAW;QACX,wEAAwE;QACxE,yEAAyE;QACzE,2EAA2E;QAC3E,6CAA6C;QAC7CT,cAAcmB;IAChB,GAAG,EAAE;IAEL,MAAMC,YAAY1B,YAAY,IAAMwB,QAAQrB,qBAAqB;QAACqB;KAAQ;IAC1E,MAAMG,YAAY3B,YAAY,IAAMwB,QAAQnB,qBAAqB;QAACmB;KAAQ;IAC1E,MAAMI,kBAAkB5B,YACtB,CAAC6B,UACCL,QAAQ;YAAE,GAAGpB,eAAe;YAAE,GAAGyB,OAAO;YAAEC,WAAW;QAAK,IAC5D;QAACN;KAAQ;IAEX,MAAMO,SAAS/B,YAAY,IAAMe,WAAW,QAAQ,EAAE;IAEtD,OAAO;QAAEW;QAAWZ;QAASa;QAAWI;QAAQH;QAAiBhB;IAAM;AACzE"}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import type { Field } from 'payload';
|
||||
import type { ContentOption } from './types.js';
|
||||
/**
|
||||
* Builds one relationship field per content collection, pointing at the
|
||||
* client's Pages collection.
|
||||
*
|
||||
* Sits alongside the system-page roles (homepage, privacy policy) because it's
|
||||
* the same idea: a page referenced by function rather than by slug. The
|
||||
* assignment is locale-agnostic — one page document — while the resulting path
|
||||
* is locale-aware, since the page's slug is localized. Assign the "Artykuły"
|
||||
* page once and you get /pl/artykuly and /en/articles from its own slugs.
|
||||
*/
|
||||
export declare function buildArchiveFields(content: ContentOption, pagesSlug: string): Field[];
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
import { archiveFieldName } from './types.js';
|
||||
/**
|
||||
* Builds one relationship field per content collection, pointing at the
|
||||
* client's Pages collection.
|
||||
*
|
||||
* Sits alongside the system-page roles (homepage, privacy policy) because it's
|
||||
* the same idea: a page referenced by function rather than by slug. The
|
||||
* assignment is locale-agnostic — one page document — while the resulting path
|
||||
* is locale-aware, since the page's slug is localized. Assign the "Artykuły"
|
||||
* page once and you get /pl/artykuly and /en/articles from its own slugs.
|
||||
*/ export function buildArchiveFields(content, pagesSlug) {
|
||||
return content.collections.map((collection)=>{
|
||||
const label = collection.label ?? collection.slug;
|
||||
return {
|
||||
name: archiveFieldName(collection.slug),
|
||||
type: 'relationship',
|
||||
admin: {
|
||||
description: `Page listing ${label}. Its slug becomes the URL segment for entries (e.g. /pl/artykuly/moj-wpis).`
|
||||
},
|
||||
label: `${label} — archive page`,
|
||||
maxDepth: 1,
|
||||
relationTo: pagesSlug
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
//# sourceMappingURL=archiveFields.js.map
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/content/archiveFields.ts"],"sourcesContent":["import type { Field, RelationshipField } from 'payload'\n\nimport type { ContentOption } from './types.js'\n\nimport { archiveFieldName } from './types.js'\n\n/**\n * Builds one relationship field per content collection, pointing at the\n * client's Pages collection.\n *\n * Sits alongside the system-page roles (homepage, privacy policy) because it's\n * the same idea: a page referenced by function rather than by slug. The\n * assignment is locale-agnostic — one page document — while the resulting path\n * is locale-aware, since the page's slug is localized. Assign the \"Artykuły\"\n * page once and you get /pl/artykuly and /en/articles from its own slugs.\n */\nexport function buildArchiveFields(content: ContentOption, pagesSlug: string): Field[] {\n return content.collections.map((collection): RelationshipField => {\n const label = collection.label ?? collection.slug\n\n return {\n name: archiveFieldName(collection.slug),\n type: 'relationship',\n admin: {\n description: `Page listing ${label}. Its slug becomes the URL segment for entries (e.g. /pl/artykuly/moj-wpis).`,\n },\n label: `${label} — archive page`,\n maxDepth: 1,\n relationTo: pagesSlug,\n }\n })\n}\n"],"names":["archiveFieldName","buildArchiveFields","content","pagesSlug","collections","map","collection","label","slug","name","type","admin","description","maxDepth","relationTo"],"mappings":"AAIA,SAASA,gBAAgB,QAAQ,aAAY;AAE7C;;;;;;;;;CASC,GACD,OAAO,SAASC,mBAAmBC,OAAsB,EAAEC,SAAiB;IAC1E,OAAOD,QAAQE,WAAW,CAACC,GAAG,CAAC,CAACC;QAC9B,MAAMC,QAAQD,WAAWC,KAAK,IAAID,WAAWE,IAAI;QAEjD,OAAO;YACLC,MAAMT,iBAAiBM,WAAWE,IAAI;YACtCE,MAAM;YACNC,OAAO;gBACLC,aAAa,CAAC,aAAa,EAAEL,MAAM,4EAA4E,CAAC;YAClH;YACAA,OAAO,GAAGA,MAAM,eAAe,CAAC;YAChCM,UAAU;YACVC,YAAYX;QACd;IACF;AACF"}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
export type ArchiveEntries<T = Record<string, unknown>> = {
|
||||
docs: T[];
|
||||
hasNextPage: boolean;
|
||||
hasPrevPage: boolean;
|
||||
/** 1-based. */
|
||||
page: number;
|
||||
totalDocs: number;
|
||||
totalPages: number;
|
||||
};
|
||||
type GetArchiveEntriesArgs = {
|
||||
/** Collection to list, e.g. 'posts'. */
|
||||
collection: string;
|
||||
/** Relationship depth. Defaults to 1 — enough for a cover image. */
|
||||
depth?: number;
|
||||
locale: string;
|
||||
/** 1-based. Values below 1 are clamped. */
|
||||
page?: number;
|
||||
payload: BasePayload;
|
||||
/** Defaults to 10. */
|
||||
perPage?: number;
|
||||
/** Payload sort string. Defaults to newest first. */
|
||||
sort?: string;
|
||||
/** Extra constraints merged into the query, e.g. { category: { equals: id } }. */
|
||||
where?: Record<string, unknown>;
|
||||
};
|
||||
/**
|
||||
* Fetches one page of a collection's entries for an archive listing.
|
||||
*
|
||||
* Only published documents are returned when the collection has drafts enabled;
|
||||
* Payload's `where` on `_status` is left to the caller, since a collection
|
||||
* without drafts has no such field.
|
||||
*
|
||||
* Returns pagination facts rather than markup — the listing itself belongs to
|
||||
* the client (as a block on the archive page, most likely), because how a list
|
||||
* of posts should look isn't something a plugin can decide.
|
||||
*/
|
||||
export declare function getArchiveEntries<T = Record<string, unknown>>({ collection, depth, locale, page, payload, perPage, sort, where, }: GetArchiveEntriesArgs): Promise<ArchiveEntries<T>>;
|
||||
export {};
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* Fetches one page of a collection's entries for an archive listing.
|
||||
*
|
||||
* Only published documents are returned when the collection has drafts enabled;
|
||||
* Payload's `where` on `_status` is left to the caller, since a collection
|
||||
* without drafts has no such field.
|
||||
*
|
||||
* Returns pagination facts rather than markup — the listing itself belongs to
|
||||
* the client (as a block on the archive page, most likely), because how a list
|
||||
* of posts should look isn't something a plugin can decide.
|
||||
*/ export async function getArchiveEntries({ collection, depth = 1, locale, page = 1, payload, perPage = 10, sort = '-createdAt', where }) {
|
||||
const result = await payload.find({
|
||||
collection: collection,
|
||||
depth,
|
||||
limit: perPage,
|
||||
locale: locale,
|
||||
page: Math.max(1, page),
|
||||
sort,
|
||||
...where ? {
|
||||
where: where
|
||||
} : {}
|
||||
});
|
||||
return {
|
||||
docs: result.docs,
|
||||
hasNextPage: result.hasNextPage,
|
||||
hasPrevPage: result.hasPrevPage,
|
||||
page: result.page ?? 1,
|
||||
totalDocs: result.totalDocs,
|
||||
totalPages: result.totalPages
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=getArchiveEntries.js.map
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/content/getArchiveEntries.ts"],"sourcesContent":["import type { BasePayload } from 'payload'\n\nexport type ArchiveEntries<T = Record<string, unknown>> = {\n docs: T[]\n hasNextPage: boolean\n hasPrevPage: boolean\n /** 1-based. */\n page: number\n totalDocs: number\n totalPages: number\n}\n\ntype GetArchiveEntriesArgs = {\n /** Collection to list, e.g. 'posts'. */\n collection: string\n /** Relationship depth. Defaults to 1 — enough for a cover image. */\n depth?: number\n locale: string\n /** 1-based. Values below 1 are clamped. */\n page?: number\n payload: BasePayload\n /** Defaults to 10. */\n perPage?: number\n /** Payload sort string. Defaults to newest first. */\n sort?: string\n /** Extra constraints merged into the query, e.g. { category: { equals: id } }. */\n where?: Record<string, unknown>\n}\n\n/**\n * Fetches one page of a collection's entries for an archive listing.\n *\n * Only published documents are returned when the collection has drafts enabled;\n * Payload's `where` on `_status` is left to the caller, since a collection\n * without drafts has no such field.\n *\n * Returns pagination facts rather than markup — the listing itself belongs to\n * the client (as a block on the archive page, most likely), because how a list\n * of posts should look isn't something a plugin can decide.\n */\nexport async function getArchiveEntries<T = Record<string, unknown>>({\n collection,\n depth = 1,\n locale,\n page = 1,\n payload,\n perPage = 10,\n sort = '-createdAt',\n where,\n}: GetArchiveEntriesArgs): Promise<ArchiveEntries<T>> {\n const result = await payload.find({\n collection: collection as never,\n depth,\n limit: perPage,\n locale: locale as never,\n page: Math.max(1, page),\n sort,\n ...(where ? { where: where as never } : {}),\n })\n\n return {\n docs: result.docs as T[],\n hasNextPage: result.hasNextPage,\n hasPrevPage: result.hasPrevPage,\n page: result.page ?? 1,\n totalDocs: result.totalDocs,\n totalPages: result.totalPages,\n }\n}\n"],"names":["getArchiveEntries","collection","depth","locale","page","payload","perPage","sort","where","result","find","limit","Math","max","docs","hasNextPage","hasPrevPage","totalDocs","totalPages"],"mappings":"AA6BA;;;;;;;;;;CAUC,GACD,OAAO,eAAeA,kBAA+C,EACnEC,UAAU,EACVC,QAAQ,CAAC,EACTC,MAAM,EACNC,OAAO,CAAC,EACRC,OAAO,EACPC,UAAU,EAAE,EACZC,OAAO,YAAY,EACnBC,KAAK,EACiB;IACtB,MAAMC,SAAS,MAAMJ,QAAQK,IAAI,CAAC;QAChCT,YAAYA;QACZC;QACAS,OAAOL;QACPH,QAAQA;QACRC,MAAMQ,KAAKC,GAAG,CAAC,GAAGT;QAClBG;QACA,GAAIC,QAAQ;YAAEA,OAAOA;QAAe,IAAI,CAAC,CAAC;IAC5C;IAEA,OAAO;QACLM,MAAML,OAAOK,IAAI;QACjBC,aAAaN,OAAOM,WAAW;QAC/BC,aAAaP,OAAOO,WAAW;QAC/BZ,MAAMK,OAAOL,IAAI,IAAI;QACrBa,WAAWR,OAAOQ,SAAS;QAC3BC,YAAYT,OAAOS,UAAU;IAC/B;AACF"}
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
export { buildArchiveFields } from './archiveFields.js';
|
||||
export { getArchiveEntries } from './getArchiveEntries.js';
|
||||
export type { ArchiveEntries } from './getArchiveEntries.js';
|
||||
export { buildArchivePath, buildEntryPath, parsePageParam } from './paths.js';
|
||||
export { resolveRoute } from './resolveRoute.js';
|
||||
export type { ResolvedRoute } from './resolveRoute.js';
|
||||
export type { ContentCollectionOption, ContentOption } from './types.js';
|
||||
export { archiveFieldName } from './types.js';
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
export { buildArchiveFields } from './archiveFields.js';
|
||||
export { getArchiveEntries } from './getArchiveEntries.js';
|
||||
export { buildArchivePath, buildEntryPath, parsePageParam } from './paths.js';
|
||||
export { resolveRoute } from './resolveRoute.js';
|
||||
export { archiveFieldName } from './types.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/content/index.ts"],"sourcesContent":["export { buildArchiveFields } from './archiveFields.js'\nexport { getArchiveEntries } from './getArchiveEntries.js'\nexport type { ArchiveEntries } from './getArchiveEntries.js'\nexport { buildArchivePath, buildEntryPath, parsePageParam } from './paths.js'\nexport { resolveRoute } from './resolveRoute.js'\nexport type { ResolvedRoute } from './resolveRoute.js'\nexport type { ContentCollectionOption, ContentOption } from './types.js'\nexport { archiveFieldName } from './types.js'\n"],"names":["buildArchiveFields","getArchiveEntries","buildArchivePath","buildEntryPath","parsePageParam","resolveRoute","archiveFieldName"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,qBAAoB;AACvD,SAASC,iBAAiB,QAAQ,yBAAwB;AAE1D,SAASC,gBAAgB,EAAEC,cAAc,EAAEC,cAAc,QAAQ,aAAY;AAC7E,SAASC,YAAY,QAAQ,oBAAmB;AAGhD,SAASC,gBAAgB,QAAQ,aAAY"}
|
||||
Vendored
-35
@@ -1,35 +0,0 @@
|
||||
type ArchivePathArgs = {
|
||||
/** Archive page's slug in this locale, e.g. 'artykuly'. */
|
||||
archiveSlug: string;
|
||||
locale: string;
|
||||
/** 1-based. Page 1 is omitted from the URL. */
|
||||
page?: number;
|
||||
};
|
||||
/**
|
||||
* Path to an archive listing: /pl/artykuly, /pl/artykuly?page=2.
|
||||
*
|
||||
* Pagination goes in the query string rather than the path. A path segment
|
||||
* (/pl/artykuly/2) would be ambiguous with an entry slugged "2", and the
|
||||
* alternative (/pl/artykuly/strona/2) drags in yet another localized segment to
|
||||
* configure and translate. Google has understood ?page= for years, and
|
||||
* rel=next/prev — the reason people used to prefer path segments — was retired.
|
||||
*/
|
||||
export declare function buildArchivePath({ archiveSlug, locale, page }: ArchivePathArgs): string;
|
||||
type EntryPathArgs = {
|
||||
/** Archive page's slug in this locale, e.g. 'artykuly'. */
|
||||
archiveSlug: string;
|
||||
/** Entry's slug in this locale, e.g. 'moj-post'. */
|
||||
entrySlug: string;
|
||||
locale: string;
|
||||
};
|
||||
/** Path to a single entry: /pl/artykuly/moj-post. */
|
||||
export declare function buildEntryPath({ archiveSlug, entrySlug, locale }: EntryPathArgs): string;
|
||||
/**
|
||||
* Reads a page number out of a query parameter.
|
||||
*
|
||||
* Anything that isn't a positive integer is page 1 — `?page=abc`, `?page=-5`,
|
||||
* `?page=1.5` and a repeated `?page=1&page=2` all have to resolve to something,
|
||||
* and silently showing the first page beats a 500 on a URL a crawler invented.
|
||||
*/
|
||||
export declare function parsePageParam(value: string | string[] | undefined): number;
|
||||
export {};
|
||||
Vendored
-28
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* Path to an archive listing: /pl/artykuly, /pl/artykuly?page=2.
|
||||
*
|
||||
* Pagination goes in the query string rather than the path. A path segment
|
||||
* (/pl/artykuly/2) would be ambiguous with an entry slugged "2", and the
|
||||
* alternative (/pl/artykuly/strona/2) drags in yet another localized segment to
|
||||
* configure and translate. Google has understood ?page= for years, and
|
||||
* rel=next/prev — the reason people used to prefer path segments — was retired.
|
||||
*/ export function buildArchivePath({ archiveSlug, locale, page = 1 }) {
|
||||
const base = `/${locale}/${archiveSlug}`;
|
||||
return page > 1 ? `${base}?page=${page}` : base;
|
||||
}
|
||||
/** Path to a single entry: /pl/artykuly/moj-post. */ export function buildEntryPath({ archiveSlug, entrySlug, locale }) {
|
||||
return `/${locale}/${archiveSlug}/${entrySlug}`;
|
||||
}
|
||||
/**
|
||||
* Reads a page number out of a query parameter.
|
||||
*
|
||||
* Anything that isn't a positive integer is page 1 — `?page=abc`, `?page=-5`,
|
||||
* `?page=1.5` and a repeated `?page=1&page=2` all have to resolve to something,
|
||||
* and silently showing the first page beats a 500 on a URL a crawler invented.
|
||||
*/ export function parsePageParam(value) {
|
||||
const raw = Array.isArray(value) ? value[0] : value;
|
||||
const n = Number(raw);
|
||||
return Number.isInteger(n) && n > 0 ? n : 1;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=paths.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/content/paths.ts"],"sourcesContent":["type ArchivePathArgs = {\n /** Archive page's slug in this locale, e.g. 'artykuly'. */\n archiveSlug: string\n locale: string\n /** 1-based. Page 1 is omitted from the URL. */\n page?: number\n}\n\n/**\n * Path to an archive listing: /pl/artykuly, /pl/artykuly?page=2.\n *\n * Pagination goes in the query string rather than the path. A path segment\n * (/pl/artykuly/2) would be ambiguous with an entry slugged \"2\", and the\n * alternative (/pl/artykuly/strona/2) drags in yet another localized segment to\n * configure and translate. Google has understood ?page= for years, and\n * rel=next/prev — the reason people used to prefer path segments — was retired.\n */\nexport function buildArchivePath({ archiveSlug, locale, page = 1 }: ArchivePathArgs): string {\n const base = `/${locale}/${archiveSlug}`\n return page > 1 ? `${base}?page=${page}` : base\n}\n\ntype EntryPathArgs = {\n /** Archive page's slug in this locale, e.g. 'artykuly'. */\n archiveSlug: string\n /** Entry's slug in this locale, e.g. 'moj-post'. */\n entrySlug: string\n locale: string\n}\n\n/** Path to a single entry: /pl/artykuly/moj-post. */\nexport function buildEntryPath({ archiveSlug, entrySlug, locale }: EntryPathArgs): string {\n return `/${locale}/${archiveSlug}/${entrySlug}`\n}\n\n/**\n * Reads a page number out of a query parameter.\n *\n * Anything that isn't a positive integer is page 1 — `?page=abc`, `?page=-5`,\n * `?page=1.5` and a repeated `?page=1&page=2` all have to resolve to something,\n * and silently showing the first page beats a 500 on a URL a crawler invented.\n */\nexport function parsePageParam(value: string | string[] | undefined): number {\n const raw = Array.isArray(value) ? value[0] : value\n const n = Number(raw)\n return Number.isInteger(n) && n > 0 ? n : 1\n}\n"],"names":["buildArchivePath","archiveSlug","locale","page","base","buildEntryPath","entrySlug","parsePageParam","value","raw","Array","isArray","n","Number","isInteger"],"mappings":"AAQA;;;;;;;;CAQC,GACD,OAAO,SAASA,iBAAiB,EAAEC,WAAW,EAAEC,MAAM,EAAEC,OAAO,CAAC,EAAmB;IACjF,MAAMC,OAAO,CAAC,CAAC,EAAEF,OAAO,CAAC,EAAED,aAAa;IACxC,OAAOE,OAAO,IAAI,GAAGC,KAAK,MAAM,EAAED,MAAM,GAAGC;AAC7C;AAUA,mDAAmD,GACnD,OAAO,SAASC,eAAe,EAAEJ,WAAW,EAAEK,SAAS,EAAEJ,MAAM,EAAiB;IAC9E,OAAO,CAAC,CAAC,EAAEA,OAAO,CAAC,EAAED,YAAY,CAAC,EAAEK,WAAW;AACjD;AAEA;;;;;;CAMC,GACD,OAAO,SAASC,eAAeC,KAAoC;IACjE,MAAMC,MAAMC,MAAMC,OAAO,CAACH,SAASA,KAAK,CAAC,EAAE,GAAGA;IAC9C,MAAMI,IAAIC,OAAOJ;IACjB,OAAOI,OAAOC,SAAS,CAACF,MAAMA,IAAI,IAAIA,IAAI;AAC5C"}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
import type { BasePayload } from 'payload';
|
||||
import type { ContentOption } from './types.js';
|
||||
export type ResolvedRoute =
|
||||
/** Locale root — the page assigned as Homepage in System Pages. */
|
||||
{
|
||||
/** The archive page this entry lives under — its slug is the URL prefix. */
|
||||
archive: Record<string, unknown>;
|
||||
collection: string;
|
||||
doc: Record<string, unknown>;
|
||||
type: 'entry';
|
||||
}
|
||||
/** An ordinary page. */
|
||||
| {
|
||||
collection: string;
|
||||
doc: Record<string, unknown>;
|
||||
/** 1-based, from ?page=. */
|
||||
page: number;
|
||||
perPage: number;
|
||||
type: 'archive';
|
||||
}
|
||||
/** A collection's archive page, e.g. /pl/artykuly. */
|
||||
| {
|
||||
doc: Record<string, unknown>;
|
||||
type: 'home';
|
||||
}
|
||||
/** A single entry, e.g. /pl/artykuly/moj-post. */
|
||||
| {
|
||||
doc: Record<string, unknown>;
|
||||
type: 'page';
|
||||
};
|
||||
type ResolveRouteArgs = {
|
||||
content?: ContentOption;
|
||||
locale: string;
|
||||
/** Page number from the query string (?page=2). Defaults to 1. */
|
||||
page?: number;
|
||||
/** Client's Pages collection slug. Defaults to 'pages'. */
|
||||
pagesSlug?: string;
|
||||
payload: BasePayload;
|
||||
/** Route segments after the locale, e.g. ['artykuly', 'moj-post']. */
|
||||
segments?: string[];
|
||||
/** SiteSettings global slug. Defaults to 'site-settings'. */
|
||||
settingsSlug?: string;
|
||||
};
|
||||
/**
|
||||
* Works out what a URL points at: the home page, an ordinary page, a
|
||||
* collection's archive, or a single entry.
|
||||
*
|
||||
* Routing only. An archive result says which collection to list and on which
|
||||
* page, but doesn't fetch the entries — that's `getArchiveEntries`, called by
|
||||
* whoever actually needs them. Keeping the two apart means listing changes
|
||||
* (sorting, filtering, pagination) never touch routing rules, and metadata
|
||||
* generation doesn't pay for a query it would discard.
|
||||
*
|
||||
* The trick is that archive prefixes aren't configured anywhere — they're the
|
||||
* slug of whichever page an editor assigned as that collection's archive. So
|
||||
* /pl/artykuly and /en/articles come from one assignment, and renaming the page
|
||||
* moves the whole section.
|
||||
*
|
||||
* Resolution order matters: a first segment matching an archive's slug wins
|
||||
* over an ordinary page of the same name, because an archive *is* a page and
|
||||
* would otherwise shadow its own entries.
|
||||
*
|
||||
* Depth beyond {archive}/{entry} isn't supported — categories in the path would
|
||||
* make canonical and hreflang ambiguous (the same entry reachable under several
|
||||
* URLs).
|
||||
*/
|
||||
export declare function resolveRoute({ content, locale, page, pagesSlug, payload, segments, settingsSlug, }: ResolveRouteArgs): Promise<null | ResolvedRoute>;
|
||||
export {};
|
||||
Vendored
-111
@@ -1,111 +0,0 @@
|
||||
import { archiveFieldName } from './types.js';
|
||||
/**
|
||||
* Works out what a URL points at: the home page, an ordinary page, a
|
||||
* collection's archive, or a single entry.
|
||||
*
|
||||
* Routing only. An archive result says which collection to list and on which
|
||||
* page, but doesn't fetch the entries — that's `getArchiveEntries`, called by
|
||||
* whoever actually needs them. Keeping the two apart means listing changes
|
||||
* (sorting, filtering, pagination) never touch routing rules, and metadata
|
||||
* generation doesn't pay for a query it would discard.
|
||||
*
|
||||
* The trick is that archive prefixes aren't configured anywhere — they're the
|
||||
* slug of whichever page an editor assigned as that collection's archive. So
|
||||
* /pl/artykuly and /en/articles come from one assignment, and renaming the page
|
||||
* moves the whole section.
|
||||
*
|
||||
* Resolution order matters: a first segment matching an archive's slug wins
|
||||
* over an ordinary page of the same name, because an archive *is* a page and
|
||||
* would otherwise shadow its own entries.
|
||||
*
|
||||
* Depth beyond {archive}/{entry} isn't supported — categories in the path would
|
||||
* make canonical and hreflang ambiguous (the same entry reachable under several
|
||||
* URLs).
|
||||
*/ export async function resolveRoute({ content, locale, page = 1, pagesSlug = 'pages', payload, segments, settingsSlug = 'site-settings' }) {
|
||||
const settings = await payload.findGlobal({
|
||||
slug: settingsSlug,
|
||||
depth: 1,
|
||||
locale: locale
|
||||
});
|
||||
// Locale root → Homepage from System Pages.
|
||||
if (!segments?.length) {
|
||||
const homepage = settings.homepage;
|
||||
if (homepage && typeof homepage === 'object') {
|
||||
return {
|
||||
type: 'home',
|
||||
doc: homepage
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const [first, ...rest] = segments;
|
||||
// Does the first segment name an archive page?
|
||||
for (const collection of content?.collections ?? []){
|
||||
const archive = settings[archiveFieldName(collection.slug)];
|
||||
if (!archive || typeof archive !== 'object') {
|
||||
continue;
|
||||
}
|
||||
const archiveDoc = archive;
|
||||
if (archiveDoc.slug !== first) {
|
||||
continue;
|
||||
}
|
||||
// /pl/artykuly → the archive page itself.
|
||||
if (rest.length === 0) {
|
||||
return {
|
||||
type: 'archive',
|
||||
collection: collection.slug,
|
||||
doc: archiveDoc,
|
||||
page,
|
||||
perPage: collection.perPage ?? 10
|
||||
};
|
||||
}
|
||||
// /pl/artykuly/moj-post → an entry.
|
||||
if (rest.length === 1) {
|
||||
const found = await payload.find({
|
||||
collection: collection.slug,
|
||||
depth: 2,
|
||||
limit: 1,
|
||||
locale: locale,
|
||||
where: {
|
||||
slug: {
|
||||
equals: rest[0]
|
||||
}
|
||||
}
|
||||
});
|
||||
const entry = found.docs[0];
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: 'entry',
|
||||
archive: archiveDoc,
|
||||
collection: collection.slug,
|
||||
doc: entry
|
||||
};
|
||||
}
|
||||
// Deeper than {archive}/{entry}.
|
||||
return null;
|
||||
}
|
||||
// An ordinary page. Joined, so nested slugs ('a/b') keep working.
|
||||
const found = await payload.find({
|
||||
collection: pagesSlug,
|
||||
depth: 2,
|
||||
limit: 1,
|
||||
locale: locale,
|
||||
where: {
|
||||
slug: {
|
||||
equals: segments.join('/')
|
||||
}
|
||||
}
|
||||
});
|
||||
const doc = found.docs[0];
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: 'page',
|
||||
doc: doc
|
||||
};
|
||||
}
|
||||
|
||||
//# sourceMappingURL=resolveRoute.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
Vendored
-29
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* A collection whose entries live under an archive page, e.g. blog posts at
|
||||
* /pl/artykuly/moj-post.
|
||||
*
|
||||
* The collection itself belongs to the client — Payload keeps its schema in
|
||||
* code, so adding one is always a code change. What this option adds is the
|
||||
* routing: the plugin exposes an "archive page" assignment in SiteSettings, and
|
||||
* whichever page an editor picks becomes the URL segment. Rename that page from
|
||||
* "Artykuły" to "Wpisy" and the path follows, per locale, with no deploy.
|
||||
*/
|
||||
export type ContentCollectionOption = {
|
||||
/** Slug of the client's collection, e.g. 'posts'. */
|
||||
slug: string;
|
||||
/** Admin label for the archive assignment. Defaults to the slug. */
|
||||
label?: string;
|
||||
/** Entries per page in listings. Defaults to 10. */
|
||||
perPage?: number;
|
||||
};
|
||||
/**
|
||||
* Plugin option wiring archive-backed collections into routing.
|
||||
*/
|
||||
export type ContentOption = {
|
||||
collections: ContentCollectionOption[];
|
||||
};
|
||||
/**
|
||||
* Field name holding a collection's archive page in SiteSettings.
|
||||
* `posts` → `postsArchive`.
|
||||
*/
|
||||
export declare function archiveFieldName(collectionSlug: string): string;
|
||||
Vendored
-17
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* A collection whose entries live under an archive page, e.g. blog posts at
|
||||
* /pl/artykuly/moj-post.
|
||||
*
|
||||
* The collection itself belongs to the client — Payload keeps its schema in
|
||||
* code, so adding one is always a code change. What this option adds is the
|
||||
* routing: the plugin exposes an "archive page" assignment in SiteSettings, and
|
||||
* whichever page an editor picks becomes the URL segment. Rename that page from
|
||||
* "Artykuły" to "Wpisy" and the path follows, per locale, with no deploy.
|
||||
*/ /**
|
||||
* Field name holding a collection's archive page in SiteSettings.
|
||||
* `posts` → `postsArchive`.
|
||||
*/ export function archiveFieldName(collectionSlug) {
|
||||
return `${collectionSlug}Archive`;
|
||||
}
|
||||
|
||||
//# sourceMappingURL=types.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/content/types.ts"],"sourcesContent":["/**\n * A collection whose entries live under an archive page, e.g. blog posts at\n * /pl/artykuly/moj-post.\n *\n * The collection itself belongs to the client — Payload keeps its schema in\n * code, so adding one is always a code change. What this option adds is the\n * routing: the plugin exposes an \"archive page\" assignment in SiteSettings, and\n * whichever page an editor picks becomes the URL segment. Rename that page from\n * \"Artykuły\" to \"Wpisy\" and the path follows, per locale, with no deploy.\n */\nexport type ContentCollectionOption = {\n /** Slug of the client's collection, e.g. 'posts'. */\n slug: string\n /** Admin label for the archive assignment. Defaults to the slug. */\n label?: string\n /** Entries per page in listings. Defaults to 10. */\n perPage?: number\n}\n\n/**\n * Plugin option wiring archive-backed collections into routing.\n */\nexport type ContentOption = {\n collections: ContentCollectionOption[]\n}\n\n/**\n * Field name holding a collection's archive page in SiteSettings.\n * `posts` → `postsArchive`.\n */\nexport function archiveFieldName(collectionSlug: string): string {\n return `${collectionSlug}Archive`\n}\n"],"names":["archiveFieldName","collectionSlug"],"mappings":"AAAA;;;;;;;;;CASC,GAiBD;;;CAGC,GACD,OAAO,SAASA,iBAAiBC,cAAsB;IACrD,OAAO,GAAGA,eAAe,OAAO,CAAC;AACnC"}
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
export { sendEmail } from './sendEmail.js';
|
||||
export type { SendEmailArgs, SendEmailResult } from './sendEmail.js';
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
// Server-only exports. sendEmail imports 'server-only' (SMTP password, nodemailer)
|
||||
// so this must never be imported from a client component.
|
||||
export { sendEmail } from './sendEmail.js';
|
||||
|
||||
//# sourceMappingURL=index.js.map
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["../../../src/modules/email/index.ts"],"sourcesContent":["// Server-only exports. sendEmail imports 'server-only' (SMTP password, nodemailer)\n// so this must never be imported from a client component.\nexport { sendEmail } from './sendEmail.js'\nexport type { SendEmailArgs, SendEmailResult } from './sendEmail.js'\n"],"names":["sendEmail"],"mappings":"AAAA,mFAAmF;AACnF,0DAA0D;AAC1D,SAASA,SAAS,QAAQ,iBAAgB"}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
import type { PayloadEmailAdapter } from 'payload';
|
||||
export type PanelSmtpAdapterArgs = {
|
||||
/**
|
||||
* Used only until the panel is filled in — Payload requires a from-address
|
||||
* synchronously at boot, before any global can be read. Once SiteIntegrations
|
||||
* has an address, it wins.
|
||||
*/
|
||||
fallbackFromAddress?: string;
|
||||
fallbackFromName?: string;
|
||||
};
|
||||
/**
|
||||
* Payload email adapter backed by the SMTP settings in the SiteIntegrations
|
||||
* global.
|
||||
*
|
||||
* Why this exists: Payload builds its email adapter once, at boot, from the
|
||||
* config — which would normally mean SMTP credentials living in env vars and a
|
||||
* redeploy to change them. This adapter instead resolves SMTP on every send, so
|
||||
* an editor can change the mailbox in the admin panel and the next email uses
|
||||
* it.
|
||||
*
|
||||
* Wiring it into the config means `payload.sendEmail` works everywhere — which
|
||||
* includes the form-builder's own submission emails (the ones an editor
|
||||
* configures per form under "Emails"). Those go out over the panel's SMTP with
|
||||
* no extra code.
|
||||
*
|
||||
* Boot-time constraints shape two details:
|
||||
* - `defaultFromAddress` / `defaultFromName` must be returned synchronously, so
|
||||
* they're placeholders; the real from-address is applied per message below.
|
||||
* - nodemailer is imported dynamically inside sendEmail, so merely loading the
|
||||
* Payload config (or running `generate:importmap`) doesn't pull it in.
|
||||
*/
|
||||
export declare const panelSmtpAdapter: (args?: PanelSmtpAdapterArgs) => PayloadEmailAdapter;
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
import { getSiteIntegrations } from '../payload/index.js';
|
||||
/**
|
||||
* Payload email adapter backed by the SMTP settings in the SiteIntegrations
|
||||
* global.
|
||||
*
|
||||
* Why this exists: Payload builds its email adapter once, at boot, from the
|
||||
* config — which would normally mean SMTP credentials living in env vars and a
|
||||
* redeploy to change them. This adapter instead resolves SMTP on every send, so
|
||||
* an editor can change the mailbox in the admin panel and the next email uses
|
||||
* it.
|
||||
*
|
||||
* Wiring it into the config means `payload.sendEmail` works everywhere — which
|
||||
* includes the form-builder's own submission emails (the ones an editor
|
||||
* configures per form under "Emails"). Those go out over the panel's SMTP with
|
||||
* no extra code.
|
||||
*
|
||||
* Boot-time constraints shape two details:
|
||||
* - `defaultFromAddress` / `defaultFromName` must be returned synchronously, so
|
||||
* they're placeholders; the real from-address is applied per message below.
|
||||
* - nodemailer is imported dynamically inside sendEmail, so merely loading the
|
||||
* Payload config (or running `generate:importmap`) doesn't pull it in.
|
||||
*/ export const panelSmtpAdapter = (args = {})=>({ payload })=>({
|
||||
name: 'ipal-panel-smtp',
|
||||
defaultFromAddress: args.fallbackFromAddress ?? 'noreply@localhost',
|
||||
defaultFromName: args.fallbackFromName ?? 'Website',
|
||||
sendEmail: async (message)=>{
|
||||
const smtp = await getSiteIntegrations(payload);
|
||||
const host = smtp.smtpHost;
|
||||
const port = smtp.smtpPort ?? 587;
|
||||
const user = smtp.smtpUser;
|
||||
const pass = smtp.smtpPassword;
|
||||
if (!host || !user || !pass) {
|
||||
payload.logger.error('[ipal] Email not sent: SMTP is not configured in Site Integrations.');
|
||||
return {
|
||||
error: 'SMTP is not configured in Site Integrations.',
|
||||
sent: false
|
||||
};
|
||||
}
|
||||
// The panel is the source of truth for the sender; fall back to whatever
|
||||
// the caller set (Payload fills in defaultFromAddress when unset).
|
||||
const fromAddress = smtp.smtpFromAddress;
|
||||
const fromName = smtp.smtpFromName;
|
||||
const from = fromAddress ? fromName ? `${fromName} <${fromAddress}>` : fromAddress : message.from;
|
||||
// Defence in depth against header injection. Modern nodemailer already
|
||||
// normalises CRLF in standard headers, but the form-builder interpolates
|
||||
// user data into the subject via {{field}} placeholders, so strip any
|
||||
// newlines from header-bound values before they reach the transport.
|
||||
const stripCRLF = (v)=>String(v ?? '').replace(/[\r\n]+/g, ' ').trim();
|
||||
const { default: nodemailer } = await import('nodemailer');
|
||||
const transporter = nodemailer.createTransport({
|
||||
auth: {
|
||||
pass,
|
||||
user
|
||||
},
|
||||
host,
|
||||
port,
|
||||
secure: port === 465
|
||||
});
|
||||
try {
|
||||
const info = await transporter.sendMail({
|
||||
...message,
|
||||
from,
|
||||
...message.subject ? {
|
||||
subject: stripCRLF(message.subject)
|
||||
} : {}
|
||||
});
|
||||
return {
|
||||
messageId: info.messageId,
|
||||
sent: true
|
||||
};
|
||||
} catch (err) {
|
||||
// Never surface SMTP internals to the caller — a form submission
|
||||
// shouldn't fail loudly because the mailbox is misconfigured.
|
||||
payload.logger.error(`[ipal] Email send failed: ${err.message}`);
|
||||
return {
|
||||
error: 'Failed to send email.',
|
||||
sent: false
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//# sourceMappingURL=panelSmtpAdapter.js.map
|
||||
-1
File diff suppressed because one or more lines are too long
Vendored
-34
@@ -1,34 +0,0 @@
|
||||
import 'server-only';
|
||||
import type { BasePayload } from 'payload';
|
||||
export type SendEmailArgs = {
|
||||
/** Override the configured from-address for this message. */
|
||||
from?: string;
|
||||
/** HTML body. */
|
||||
html: string;
|
||||
/** Payload instance — used to read SMTP config from SiteIntegrations. */
|
||||
payload: BasePayload;
|
||||
replyTo?: string;
|
||||
subject: string;
|
||||
/** Optional plain-text fallback. */
|
||||
text?: string;
|
||||
to: string | string[];
|
||||
};
|
||||
export type SendEmailResult = {
|
||||
error: string;
|
||||
sent: false;
|
||||
} | {
|
||||
messageId: string;
|
||||
sent: true;
|
||||
};
|
||||
/**
|
||||
* Sends an email using SMTP settings from the SiteIntegrations global.
|
||||
*
|
||||
* Reads config at call time (not at boot) so editors can change SMTP in the
|
||||
* admin panel without a restart — consistent with the plugin's panel-managed
|
||||
* model. `server-only` keeps the SMTP password out of any client bundle.
|
||||
*
|
||||
* Returns a result object rather than throwing, so callers (e.g. form
|
||||
* submission) can handle failure without a try/catch and never leak SMTP
|
||||
* details to the client.
|
||||
*/
|
||||
export declare function sendEmail({ from, html, payload, replyTo, subject, text, to, }: SendEmailArgs): Promise<SendEmailResult>;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user