Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d745a764fe | ||
|
|
6c273118a2 | ||
|
|
a010a7368a | ||
|
|
08bd00f01f |
Vendored
+2
@@ -12,3 +12,5 @@ export { ConsentProvider, CookieBanner, CookieButton, useConsent, useConsentCont
|
|||||||
export type { CookieBannerClassNames } from '../modules/consent/client.js';
|
export type { CookieBannerClassNames } from '../modules/consent/client.js';
|
||||||
export { Turnstile } from '../modules/turnstile/client.js';
|
export { Turnstile } from '../modules/turnstile/client.js';
|
||||||
export type { TurnstileProps } from '../modules/turnstile/client.js';
|
export type { TurnstileProps } from '../modules/turnstile/client.js';
|
||||||
|
export { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js';
|
||||||
|
export type { FormNotificationTexts } from '../modules/notifications/types.js';
|
||||||
|
|||||||
Vendored
+1
@@ -10,5 +10,6 @@ export { Analytics } from '../modules/analytics/client.js';
|
|||||||
* client-only code.
|
* client-only code.
|
||||||
*/ export { ConsentProvider, CookieBanner, CookieButton, useConsent, useConsentContext } from '../modules/consent/client.js';
|
*/ export { ConsentProvider, CookieBanner, CookieButton, useConsent, useConsentContext } from '../modules/consent/client.js';
|
||||||
export { Turnstile } from '../modules/turnstile/client.js';
|
export { Turnstile } from '../modules/turnstile/client.js';
|
||||||
|
export { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js';
|
||||||
|
|
||||||
//# sourceMappingURL=client.js.map
|
//# sourceMappingURL=client.js.map
|
||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["'use client'\nexport { MaskedField } from '../globals/SiteIntegrations/components/MaskedField.js'\nexport { TestEmailButton } from '../globals/SiteIntegrations/components/TestEmailButton.js'\nexport { Analytics } from '../modules/analytics/client.js'\n/**\n * Entry point: ipal-kit/client\n *\n * Client-side ('use client') exports — React hooks, providers, and UI\n * components. Kept separate from the main entry so server bundles don't pull in\n * client-only code.\n */\nexport {\n ConsentProvider,\n CookieBanner,\n CookieButton,\n useConsent,\n useConsentContext,\n} from '../modules/consent/client.js'\nexport type { CookieBannerClassNames } from '../modules/consent/client.js'\nexport { Turnstile } from '../modules/turnstile/client.js'\nexport type { TurnstileProps } from '../modules/turnstile/client.js'\n"],"names":["MaskedField","TestEmailButton","Analytics","ConsentProvider","CookieBanner","CookieButton","useConsent","useConsentContext","Turnstile"],"mappings":"AAAA;AACA,SAASA,WAAW,QAAQ,wDAAuD;AACnF,SAASC,eAAe,QAAQ,4DAA2D;AAC3F,SAASC,SAAS,QAAQ,iCAAgC;AAC1D;;;;;;CAMC,GACD,SACEC,eAAe,EACfC,YAAY,EACZC,YAAY,EACZC,UAAU,EACVC,iBAAiB,QACZ,+BAA8B;AAErC,SAASC,SAAS,QAAQ,iCAAgC"}
|
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["'use client'\nexport { MaskedField } from '../globals/SiteIntegrations/components/MaskedField.js'\nexport { TestEmailButton } from '../globals/SiteIntegrations/components/TestEmailButton.js'\nexport { Analytics } from '../modules/analytics/client.js'\n/**\n * Entry point: ipal-kit/client\n *\n * Client-side ('use client') exports — React hooks, providers, and UI\n * components. Kept separate from the main entry so server bundles don't pull in\n * client-only code.\n */\nexport {\n ConsentProvider,\n CookieBanner,\n CookieButton,\n useConsent,\n useConsentContext,\n} from '../modules/consent/client.js'\nexport type { CookieBannerClassNames } from '../modules/consent/client.js'\nexport { Turnstile } from '../modules/turnstile/client.js'\nexport type { TurnstileProps } from '../modules/turnstile/client.js'\n\nexport { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js'\nexport type { FormNotificationTexts } from '../modules/notifications/types.js'\n"],"names":["MaskedField","TestEmailButton","Analytics","ConsentProvider","CookieBanner","CookieButton","useConsent","useConsentContext","Turnstile","resolveFormMessage"],"mappings":"AAAA;AACA,SAASA,WAAW,QAAQ,wDAAuD;AACnF,SAASC,eAAe,QAAQ,4DAA2D;AAC3F,SAASC,SAAS,QAAQ,iCAAgC;AAC1D;;;;;;CAMC,GACD,SACEC,eAAe,EACfC,YAAY,EACZC,YAAY,EACZC,UAAU,EACVC,iBAAiB,QACZ,+BAA8B;AAErC,SAASC,SAAS,QAAQ,iCAAgC;AAG1D,SAASC,kBAAkB,QAAQ,iDAAgD"}
|
||||||
+17
-8
@@ -1,17 +1,26 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||||
import { useField } from '@payloadcms/ui';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
export const MaskedField = ({ field, path })=>{
|
export const MaskedField = (props)=>{
|
||||||
const { setValue, value } = useField({
|
const { field, path, value: propValue, setValue: propSetValue, onChange: propOnChange } = props || {};
|
||||||
path
|
const [internalValue, setInternalValue] = useState(propValue ?? '');
|
||||||
});
|
|
||||||
const [revealed, setRevealed] = useState(false);
|
const [revealed, setRevealed] = useState(false);
|
||||||
const label = typeof field?.label === 'string' ? field.label : field?.name ?? path;
|
const label = typeof field?.label === 'string' ? field.label : field?.name ?? path;
|
||||||
|
const handleChange = (e)=>{
|
||||||
|
const newVal = e.target.value;
|
||||||
|
setInternalValue(newVal);
|
||||||
|
if (typeof propSetValue === 'function') {
|
||||||
|
propSetValue(newVal);
|
||||||
|
}
|
||||||
|
if (typeof propOnChange === 'function') {
|
||||||
|
propOnChange(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const currentValue = propValue !== undefined ? propValue : internalValue;
|
||||||
return /*#__PURE__*/ _jsxs("div", {
|
return /*#__PURE__*/ _jsxs("div", {
|
||||||
className: "field-type text",
|
className: "field-type text",
|
||||||
children: [
|
children: [
|
||||||
/*#__PURE__*/ _jsx("label", {
|
label && /*#__PURE__*/ _jsx("label", {
|
||||||
className: "field-label",
|
className: "field-label",
|
||||||
children: label
|
children: label
|
||||||
}),
|
}),
|
||||||
@@ -23,12 +32,12 @@ export const MaskedField = ({ field, path })=>{
|
|||||||
children: [
|
children: [
|
||||||
/*#__PURE__*/ _jsx("input", {
|
/*#__PURE__*/ _jsx("input", {
|
||||||
autoComplete: "off",
|
autoComplete: "off",
|
||||||
onChange: (e)=>setValue(e.target.value),
|
onChange: handleChange,
|
||||||
style: {
|
style: {
|
||||||
flex: 1
|
flex: 1
|
||||||
},
|
},
|
||||||
type: revealed ? 'text' : 'password',
|
type: revealed ? 'text' : 'password',
|
||||||
value: value ?? ''
|
value: currentValue ?? ''
|
||||||
}),
|
}),
|
||||||
/*#__PURE__*/ _jsx("button", {
|
/*#__PURE__*/ _jsx("button", {
|
||||||
onClick: ()=>setRevealed((r)=>!r),
|
onClick: ()=>setRevealed((r)=>!r),
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"sources":["../../../../src/globals/SiteIntegrations/components/MaskedField.tsx"],"sourcesContent":["'use client'\nimport type { TextFieldClientComponent } from 'payload'\n\nimport { useField } from '@payloadcms/ui'\nimport { useState } from 'react'\n\nexport const MaskedField: TextFieldClientComponent = ({ field, path }) => {\n const { setValue, value } = useField<string>({ path })\n const [revealed, setRevealed] = useState(false)\n const label = typeof field?.label === 'string' ? field.label : (field?.name ?? path)\n\n return (\n <div className=\"field-type text\">\n <label className=\"field-label\">{label}</label>\n <div style={{ display: 'flex', gap: '.5rem' }}>\n <input\n autoComplete=\"off\"\n onChange={(e) => setValue(e.target.value)}\n style={{ flex: 1 }}\n type={revealed ? 'text' : 'password'}\n value={value ?? ''}\n />\n <button onClick={() => setRevealed((r) => !r)} type=\"button\">\n {revealed ? 'Hide' : 'Reveal'}\n </button>\n </div>\n </div>\n )\n}\nexport default MaskedField\n"],"names":["useField","useState","MaskedField","field","path","setValue","value","revealed","setRevealed","label","name","div","className","style","display","gap","input","autoComplete","onChange","e","target","flex","type","button","onClick","r"],"mappings":"AAAA;;AAGA,SAASA,QAAQ,QAAQ,iBAAgB;AACzC,SAASC,QAAQ,QAAQ,QAAO;AAEhC,OAAO,MAAMC,cAAwC,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAE;IACnE,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAE,GAAGN,SAAiB;QAAEI;IAAK;IACpD,MAAM,CAACG,UAAUC,YAAY,GAAGP,SAAS;IACzC,MAAMQ,QAAQ,OAAON,OAAOM,UAAU,WAAWN,MAAMM,KAAK,GAAIN,OAAOO,QAAQN;IAE/E,qBACE,MAACO;QAAIC,WAAU;;0BACb,KAACH;gBAAMG,WAAU;0BAAeH;;0BAChC,MAACE;gBAAIE,OAAO;oBAAEC,SAAS;oBAAQC,KAAK;gBAAQ;;kCAC1C,KAACC;wBACCC,cAAa;wBACbC,UAAU,CAACC,IAAMd,SAASc,EAAEC,MAAM,CAACd,KAAK;wBACxCO,OAAO;4BAAEQ,MAAM;wBAAE;wBACjBC,MAAMf,WAAW,SAAS;wBAC1BD,OAAOA,SAAS;;kCAElB,KAACiB;wBAAOC,SAAS,IAAMhB,YAAY,CAACiB,IAAM,CAACA;wBAAIH,MAAK;kCACjDf,WAAW,SAAS;;;;;;AAK/B,EAAC;AACD,eAAeL,YAAW"}
|
{"version":3,"sources":["../../../../src/globals/SiteIntegrations/components/MaskedField.tsx"],"sourcesContent":["'use client'\nimport type { TextFieldClientComponent } from 'payload'\nimport { useState } from 'react'\n\nexport const MaskedField: TextFieldClientComponent = (props: any) => {\n const { field, path, value: propValue, setValue: propSetValue, onChange: propOnChange } = props || {}\n const [internalValue, setInternalValue] = useState(propValue ?? '')\n const [revealed, setRevealed] = useState(false)\n const label = typeof field?.label === 'string' ? field.label : (field?.name ?? path)\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const newVal = e.target.value\n setInternalValue(newVal)\n if (typeof propSetValue === 'function') {\n propSetValue(newVal)\n }\n if (typeof propOnChange === 'function') {\n propOnChange(e)\n }\n }\n\n const currentValue = propValue !== undefined ? propValue : internalValue\n\n return (\n <div className=\"field-type text\">\n {label && <label className=\"field-label\">{label}</label>}\n <div style={{ display: 'flex', gap: '.5rem' }}>\n <input\n autoComplete=\"off\"\n onChange={handleChange}\n style={{ flex: 1 }}\n type={revealed ? 'text' : 'password'}\n value={currentValue ?? ''}\n />\n <button onClick={() => setRevealed((r) => !r)} type=\"button\">\n {revealed ? 'Hide' : 'Reveal'}\n </button>\n </div>\n </div>\n )\n}\n\nexport default MaskedField\n"],"names":["useState","MaskedField","props","field","path","value","propValue","setValue","propSetValue","onChange","propOnChange","internalValue","setInternalValue","revealed","setRevealed","label","name","handleChange","e","newVal","target","currentValue","undefined","div","className","style","display","gap","input","autoComplete","flex","type","button","onClick","r"],"mappings":"AAAA;;AAEA,SAASA,QAAQ,QAAQ,QAAO;AAEhC,OAAO,MAAMC,cAAwC,CAACC;IACpD,MAAM,EAAEC,KAAK,EAAEC,IAAI,EAAEC,OAAOC,SAAS,EAAEC,UAAUC,YAAY,EAAEC,UAAUC,YAAY,EAAE,GAAGR,SAAS,CAAC;IACpG,MAAM,CAACS,eAAeC,iBAAiB,GAAGZ,SAASM,aAAa;IAChE,MAAM,CAACO,UAAUC,YAAY,GAAGd,SAAS;IACzC,MAAMe,QAAQ,OAAOZ,OAAOY,UAAU,WAAWZ,MAAMY,KAAK,GAAIZ,OAAOa,QAAQZ;IAE/E,MAAMa,eAAe,CAACC;QACpB,MAAMC,SAASD,EAAEE,MAAM,CAACf,KAAK;QAC7BO,iBAAiBO;QACjB,IAAI,OAAOX,iBAAiB,YAAY;YACtCA,aAAaW;QACf;QACA,IAAI,OAAOT,iBAAiB,YAAY;YACtCA,aAAaQ;QACf;IACF;IAEA,MAAMG,eAAef,cAAcgB,YAAYhB,YAAYK;IAE3D,qBACE,MAACY;QAAIC,WAAU;;YACZT,uBAAS,KAACA;gBAAMS,WAAU;0BAAeT;;0BAC1C,MAACQ;gBAAIE,OAAO;oBAAEC,SAAS;oBAAQC,KAAK;gBAAQ;;kCAC1C,KAACC;wBACCC,cAAa;wBACbpB,UAAUQ;wBACVQ,OAAO;4BAAEK,MAAM;wBAAE;wBACjBC,MAAMlB,WAAW,SAAS;wBAC1BR,OAAOgB,gBAAgB;;kCAEzB,KAACW;wBAAOC,SAAS,IAAMnB,YAAY,CAACoB,IAAM,CAACA;wBAAIH,MAAK;kCACjDlB,WAAW,SAAS;;;;;;AAK/B,EAAC;AAED,eAAeZ,YAAW"}
|
||||||
Vendored
+2
@@ -31,5 +31,7 @@ export { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/
|
|||||||
export type { AutoFillMapping, RobotsRules, SitemapEntry } from './modules/seo/index.js';
|
export type { AutoFillMapping, RobotsRules, SitemapEntry } from './modules/seo/index.js';
|
||||||
export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta, } from './modules/seo/index.js';
|
export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta, } from './modules/seo/index.js';
|
||||||
export { buildSlugField, toSlug } from './modules/slug/index.js';
|
export { buildSlugField, toSlug } from './modules/slug/index.js';
|
||||||
|
export { NOTIFICATION_FALLBACK, getNotificationTexts, resolveFormMessage, } from './modules/notifications/index.js';
|
||||||
|
export type { FormNotificationTexts, NotificationsData, NotificationTexts, } from './modules/notifications/index.js';
|
||||||
export { ipalKit } from './plugin.js';
|
export { ipalKit } from './plugin.js';
|
||||||
export type { IpalOptions } from './types.js';
|
export type { IpalOptions } from './types.js';
|
||||||
|
|||||||
Vendored
+1
@@ -18,6 +18,7 @@ export { buildSecurityHeaders } from './modules/security/index.js';
|
|||||||
export { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/seo/index.js';
|
export { buildHreflangAlternates, buildMetadata, composeTitle } from './modules/seo/index.js';
|
||||||
export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta } from './modules/seo/index.js';
|
export { buildAutoFillMetaHook, buildRobots, buildSitemapEntries, createMetadataGenerator, createPageMetadata, injectAutoFillMeta } from './modules/seo/index.js';
|
||||||
export { buildSlugField, toSlug } from './modules/slug/index.js';
|
export { buildSlugField, toSlug } from './modules/slug/index.js';
|
||||||
|
export { NOTIFICATION_FALLBACK, getNotificationTexts, resolveFormMessage } from './modules/notifications/index.js';
|
||||||
export { ipalKit } from './plugin.js';
|
export { ipalKit } from './plugin.js';
|
||||||
|
|
||||||
//# sourceMappingURL=index.js.map
|
//# sourceMappingURL=index.js.map
|
||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+25
-18
@@ -89,10 +89,18 @@ import { getSiteIntegrations } from '../payload/index.js';
|
|||||||
sent: false
|
sent: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// From-display comes from the panel; falls back to the caller's from.
|
// The panel's from-address is used as Reply-To, NOT as the message From.
|
||||||
|
//
|
||||||
|
// Why: app-only Graph sends from GRAPH_SENDER's mailbox. If we also set a
|
||||||
|
// `from` that differs from that mailbox, Exchange demands "Send As"
|
||||||
|
// permission on it and rejects with ErrorSendAsDenied otherwise. So we
|
||||||
|
// never override `from` — Graph stamps the mail as GRAPH_SENDER (the
|
||||||
|
// mailbox we legitimately own) — and route replies to the panel address
|
||||||
|
// via Reply-To. Recipients see the mail from forms@… but replying reaches
|
||||||
|
// the real destination. No Send-As needed.
|
||||||
const panel = await getSiteIntegrations(payload);
|
const panel = await getSiteIntegrations(payload);
|
||||||
const fromAddress = panel.smtpFromAddress || undefined;
|
const replyToAddress = panel.smtpFromAddress || undefined;
|
||||||
const fromName = panel.smtpFromName || undefined;
|
const replyToName = panel.smtpFromName || undefined;
|
||||||
const to = toRecipients(message.to);
|
const to = toRecipients(message.to);
|
||||||
if (to.length === 0) {
|
if (to.length === 0) {
|
||||||
payload.logger.error('[ipal] Email not sent: no valid recipient.');
|
payload.logger.error('[ipal] Email not sent: no valid recipient.');
|
||||||
@@ -104,6 +112,17 @@ import { getSiteIntegrations } from '../payload/index.js';
|
|||||||
// Graph accepts either HTML or Text; Payload gives us html and/or text.
|
// Graph accepts either HTML or Text; Payload gives us html and/or text.
|
||||||
const isHtml = typeof message.html === 'string' && message.html.length > 0;
|
const isHtml = typeof message.html === 'string' && message.html.length > 0;
|
||||||
const content = isHtml ? String(message.html) : String(message.text ?? '');
|
const content = isHtml ? String(message.html) : String(message.text ?? '');
|
||||||
|
// Reply-To: prefer whatever the caller set; otherwise the panel address.
|
||||||
|
const replyTo = message.replyTo ? toRecipients(message.replyTo) : replyToAddress ? [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: replyToAddress,
|
||||||
|
...replyToName ? {
|
||||||
|
name: replyToName
|
||||||
|
} : {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] : [];
|
||||||
const graphMessage = {
|
const graphMessage = {
|
||||||
body: {
|
body: {
|
||||||
content,
|
content,
|
||||||
@@ -117,21 +136,9 @@ import { getSiteIntegrations } from '../payload/index.js';
|
|||||||
...message.bcc ? {
|
...message.bcc ? {
|
||||||
bccRecipients: toRecipients(message.bcc)
|
bccRecipients: toRecipients(message.bcc)
|
||||||
} : {},
|
} : {},
|
||||||
// from is only honoured if the app has Send-As for that address; when
|
// NO `from` — Graph uses GRAPH_SENDER's own mailbox, so no Send-As.
|
||||||
// it's the shared mailbox itself, omit it and Graph uses the sender.
|
...replyTo.length > 0 ? {
|
||||||
...fromAddress ? {
|
replyTo
|
||||||
from: {
|
|
||||||
emailAddress: {
|
|
||||||
address: fromAddress,
|
|
||||||
...fromName ? {
|
|
||||||
name: fromName
|
|
||||||
} : {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} : {},
|
|
||||||
// replyTo lets the recipient reply to the real submitter if the caller set it.
|
|
||||||
...message.replyTo ? {
|
|
||||||
replyTo: toRecipients(message.replyTo)
|
|
||||||
} : {}
|
} : {}
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+16
-13
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@intecion/ipal-kit",
|
"name": "@intecion/ipal-kit",
|
||||||
"version": "1.0.10",
|
"version": "1.0.12",
|
||||||
"description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.",
|
"description": "Intecion Payload Advanced Library — a Payload CMS 3 plugin: i18n, SEO, forms, consent, analytics, blog/archives.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -65,22 +65,25 @@
|
|||||||
"slugify": "^1.6.6"
|
"slugify": "^1.6.6"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@payloadcms/plugin-form-builder": "^3.84.1",
|
"@payloadcms/next": "^3.88.0",
|
||||||
"@payloadcms/plugin-seo": "^3.84.1",
|
"@payloadcms/plugin-form-builder": "^3.88.0",
|
||||||
|
"@payloadcms/plugin-seo": "^3.88.0",
|
||||||
|
"@payloadcms/ui": "^3.88.0",
|
||||||
"next": ">=15",
|
"next": ">=15",
|
||||||
"payload": "^3.84.1",
|
"payload": "^3.88.0",
|
||||||
"react": "^19.0.0"
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@payloadcms/db-postgres": "3.84.1",
|
"@payloadcms/db-postgres": "3.88.0",
|
||||||
"@payloadcms/db-sqlite": "3.84.1",
|
"@payloadcms/db-sqlite": "3.88.0",
|
||||||
"@payloadcms/eslint-config": "3.28.0",
|
"@payloadcms/eslint-config": "3.28.0",
|
||||||
"@payloadcms/next": "3.84.1",
|
"@payloadcms/next": "3.88.0",
|
||||||
"@payloadcms/plugin-form-builder": "3.84.1",
|
"@payloadcms/plugin-form-builder": "3.88.0",
|
||||||
"@payloadcms/plugin-seo": "3.84.1",
|
"@payloadcms/plugin-seo": "3.88.0",
|
||||||
"@payloadcms/richtext-lexical": "3.84.1",
|
"@payloadcms/richtext-lexical": "3.88.0",
|
||||||
"@payloadcms/ui": "3.84.1",
|
"@payloadcms/ui": "3.88.0",
|
||||||
"@playwright/test": "1.58.2",
|
"@playwright/test": "1.58.2",
|
||||||
"@swc-node/register": "1.10.9",
|
"@swc-node/register": "1.10.9",
|
||||||
"@swc/cli": "0.6.0",
|
"@swc/cli": "0.6.0",
|
||||||
@@ -96,7 +99,7 @@
|
|||||||
"mongodb-memory-server": "10.1.4",
|
"mongodb-memory-server": "10.1.4",
|
||||||
"next": "16.2.6",
|
"next": "16.2.6",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"payload": "3.84.1",
|
"payload": "3.88.0",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"qs-esm": "8.0.1",
|
"qs-esm": "8.0.1",
|
||||||
"react": "19.2.6",
|
"react": "19.2.6",
|
||||||
|
|||||||
Generated
+122
-416
File diff suppressed because it is too large
Load Diff
@@ -19,3 +19,6 @@ export {
|
|||||||
export type { CookieBannerClassNames } from '../modules/consent/client.js'
|
export type { CookieBannerClassNames } from '../modules/consent/client.js'
|
||||||
export { Turnstile } from '../modules/turnstile/client.js'
|
export { Turnstile } from '../modules/turnstile/client.js'
|
||||||
export type { TurnstileProps } from '../modules/turnstile/client.js'
|
export type { TurnstileProps } from '../modules/turnstile/client.js'
|
||||||
|
|
||||||
|
export { resolveFormMessage } from '../modules/notifications/resolveFormMessage.js'
|
||||||
|
export type { FormNotificationTexts } from '../modules/notifications/types.js'
|
||||||
|
|||||||
@@ -1,24 +1,36 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import type { TextFieldClientComponent } from 'payload'
|
import type { TextFieldClientComponent } from 'payload'
|
||||||
|
|
||||||
import { useField } from '@payloadcms/ui'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
export const MaskedField: TextFieldClientComponent = ({ field, path }) => {
|
export const MaskedField: TextFieldClientComponent = (props: any) => {
|
||||||
const { setValue, value } = useField<string>({ path })
|
const { field, path, value: propValue, setValue: propSetValue, onChange: propOnChange } = props || {}
|
||||||
|
const [internalValue, setInternalValue] = useState(propValue ?? '')
|
||||||
const [revealed, setRevealed] = useState(false)
|
const [revealed, setRevealed] = useState(false)
|
||||||
const label = typeof field?.label === 'string' ? field.label : (field?.name ?? path)
|
const label = typeof field?.label === 'string' ? field.label : (field?.name ?? path)
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const newVal = e.target.value
|
||||||
|
setInternalValue(newVal)
|
||||||
|
if (typeof propSetValue === 'function') {
|
||||||
|
propSetValue(newVal)
|
||||||
|
}
|
||||||
|
if (typeof propOnChange === 'function') {
|
||||||
|
propOnChange(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentValue = propValue !== undefined ? propValue : internalValue
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="field-type text">
|
<div className="field-type text">
|
||||||
<label className="field-label">{label}</label>
|
{label && <label className="field-label">{label}</label>}
|
||||||
<div style={{ display: 'flex', gap: '.5rem' }}>
|
<div style={{ display: 'flex', gap: '.5rem' }}>
|
||||||
<input
|
<input
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onChange={(e) => setValue(e.target.value)}
|
onChange={handleChange}
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
type={revealed ? 'text' : 'password'}
|
type={revealed ? 'text' : 'password'}
|
||||||
value={value ?? ''}
|
value={currentValue ?? ''}
|
||||||
/>
|
/>
|
||||||
<button onClick={() => setRevealed((r) => !r)} type="button">
|
<button onClick={() => setRevealed((r) => !r)} type="button">
|
||||||
{revealed ? 'Hide' : 'Reveal'}
|
{revealed ? 'Hide' : 'Reveal'}
|
||||||
@@ -27,4 +39,5 @@ export const MaskedField: TextFieldClientComponent = ({ field, path }) => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MaskedField
|
export default MaskedField
|
||||||
|
|||||||
@@ -99,6 +99,16 @@ export {
|
|||||||
injectAutoFillMeta,
|
injectAutoFillMeta,
|
||||||
} from './modules/seo/index.js'
|
} from './modules/seo/index.js'
|
||||||
export { buildSlugField, toSlug } from './modules/slug/index.js'
|
export { buildSlugField, toSlug } from './modules/slug/index.js'
|
||||||
|
export {
|
||||||
|
NOTIFICATION_FALLBACK,
|
||||||
|
getNotificationTexts,
|
||||||
|
resolveFormMessage,
|
||||||
|
} from './modules/notifications/index.js'
|
||||||
|
export type {
|
||||||
|
FormNotificationTexts,
|
||||||
|
NotificationsData,
|
||||||
|
NotificationTexts,
|
||||||
|
} from './modules/notifications/index.js'
|
||||||
|
|
||||||
export { ipalKit } from './plugin.js'
|
export { ipalKit } from './plugin.js'
|
||||||
export type { IpalOptions } from './types.js'
|
export type { IpalOptions } from './types.js'
|
||||||
|
|||||||
@@ -115,10 +115,18 @@ export const graphAdapter =
|
|||||||
return { error: 'Graph is not configured (missing env vars).', sent: false }
|
return { error: 'Graph is not configured (missing env vars).', sent: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// From-display comes from the panel; falls back to the caller's from.
|
// The panel's from-address is used as Reply-To, NOT as the message From.
|
||||||
|
//
|
||||||
|
// Why: app-only Graph sends from GRAPH_SENDER's mailbox. If we also set a
|
||||||
|
// `from` that differs from that mailbox, Exchange demands "Send As"
|
||||||
|
// permission on it and rejects with ErrorSendAsDenied otherwise. So we
|
||||||
|
// never override `from` — Graph stamps the mail as GRAPH_SENDER (the
|
||||||
|
// mailbox we legitimately own) — and route replies to the panel address
|
||||||
|
// via Reply-To. Recipients see the mail from forms@… but replying reaches
|
||||||
|
// the real destination. No Send-As needed.
|
||||||
const panel = await getSiteIntegrations<GraphIntegrations>(payload)
|
const panel = await getSiteIntegrations<GraphIntegrations>(payload)
|
||||||
const fromAddress = panel.smtpFromAddress || undefined
|
const replyToAddress = panel.smtpFromAddress || undefined
|
||||||
const fromName = panel.smtpFromName || undefined
|
const replyToName = panel.smtpFromName || undefined
|
||||||
|
|
||||||
const to = toRecipients(message.to)
|
const to = toRecipients(message.to)
|
||||||
if (to.length === 0) {
|
if (to.length === 0) {
|
||||||
@@ -130,25 +138,28 @@ export const graphAdapter =
|
|||||||
const isHtml = typeof message.html === 'string' && message.html.length > 0
|
const isHtml = typeof message.html === 'string' && message.html.length > 0
|
||||||
const content = isHtml ? String(message.html) : String(message.text ?? '')
|
const content = isHtml ? String(message.html) : String(message.text ?? '')
|
||||||
|
|
||||||
|
// Reply-To: prefer whatever the caller set; otherwise the panel address.
|
||||||
|
const replyTo = message.replyTo
|
||||||
|
? toRecipients(message.replyTo as SendEmailOptions['to'])
|
||||||
|
: replyToAddress
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: replyToAddress,
|
||||||
|
...(replyToName ? { name: replyToName } : {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
|
||||||
const graphMessage: Record<string, unknown> = {
|
const graphMessage: Record<string, unknown> = {
|
||||||
body: { content, contentType: isHtml ? 'HTML' : 'Text' },
|
body: { content, contentType: isHtml ? 'HTML' : 'Text' },
|
||||||
subject: message.subject ?? '',
|
subject: message.subject ?? '',
|
||||||
toRecipients: to,
|
toRecipients: to,
|
||||||
...(message.cc ? { ccRecipients: toRecipients(message.cc) } : {}),
|
...(message.cc ? { ccRecipients: toRecipients(message.cc) } : {}),
|
||||||
...(message.bcc ? { bccRecipients: toRecipients(message.bcc) } : {}),
|
...(message.bcc ? { bccRecipients: toRecipients(message.bcc) } : {}),
|
||||||
// from is only honoured if the app has Send-As for that address; when
|
// NO `from` — Graph uses GRAPH_SENDER's own mailbox, so no Send-As.
|
||||||
// it's the shared mailbox itself, omit it and Graph uses the sender.
|
...(replyTo.length > 0 ? { replyTo } : {}),
|
||||||
...(fromAddress
|
|
||||||
? {
|
|
||||||
from: {
|
|
||||||
emailAddress: { address: fromAddress, ...(fromName ? { name: fromName } : {}) },
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
// replyTo lets the recipient reply to the real submitter if the caller set it.
|
|
||||||
...(message.replyTo
|
|
||||||
? { replyTo: toRecipients(message.replyTo as SendEmailOptions['to']) }
|
|
||||||
: {}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user