@hartl-services/medusa-base 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +117 -1
- package/.medusa/server/src/admin/index.mjs +118 -2
- package/.medusa/server/src/documents/plugin-features.js +15 -1
- package/.medusa/server/src/documents/templates/financial-document.js +69 -51
- package/.medusa/server/src/documents/templates/format.js +44 -2
- package/.medusa/server/src/documents/templates/html-factory.js +7 -5
- package/.medusa/server/src/documents/templates/html-utils.js +12 -2
- package/.medusa/server/src/documents/templates/layout.js +115 -70
- package/.medusa/server/src/documents/templates/packing-list.js +10 -8
- package/.medusa/server/src/documents/utils/document-render-settings.js +59 -0
- package/.medusa/server/src/email/application/handle-invite-event.js +8 -2
- package/.medusa/server/src/email/application/handle-password-reset-event.js +16 -4
- package/.medusa/server/src/email/domain/order-summary.js +103 -0
- package/.medusa/server/src/email/domain/refund-summary.js +43 -0
- package/.medusa/server/src/email/domain/return-summary.js +118 -0
- package/.medusa/server/src/email/domain/shipment-summary.js +74 -0
- package/.medusa/server/src/email/plugin-features.js +9 -1
- package/.medusa/server/src/email/rendering/default-templates.js +693 -65
- package/.medusa/server/src/email/rendering/load-email-shop-details.js +36 -0
- package/.medusa/server/src/email/rendering/render-email.js +16 -3
- package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +20 -1
- package/.medusa/server/src/email/types/template-data.js +155 -2
- package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +4 -3
- package/.medusa/server/src/public/descriptor.js +3 -1
- package/.medusa/server/src/public/shop-master-data.js +2 -2
- package/.medusa/server/src/shop-config/build-food-shop-config.js +5 -1
- package/.medusa/server/src/shop-config/options.js +5 -1
- package/.medusa/server/src/workflows/documents/issue-document.js +4 -2
- package/.medusa/server/src/workflows/documents/issue-packing-list.js +8 -2
- package/.medusa/server/src/workflows/email/dispatch-email-message.js +4 -2
- package/.medusa/server/src/workflows/email/prepare-order-canceled-email.js +42 -4
- package/.medusa/server/src/workflows/email/prepare-order-received-email.js +31 -5
- package/.medusa/server/src/workflows/email/prepare-refund-email.js +25 -1
- package/.medusa/server/src/workflows/email/prepare-return-email.js +64 -3
- package/.medusa/server/src/workflows/email/prepare-shipment-email.js +36 -3
- package/README.md +166 -9
- package/dist/admin-components/features/plugin-features/components/ImageSettingInput.d.ts +15 -0
- package/dist/admin-components/features/plugin-features/components/ImageSettingInput.js +42 -0
- package/dist/admin-components/features/plugin-features/components/PluginSettingsForm.js +4 -2
- package/dist/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +1 -1
- package/dist/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +4 -4
- package/dist/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +1 -1
- package/dist/admin-components/features/plugin-features/lib/image-upload.d.ts +6 -0
- package/dist/admin-components/features/plugin-features/lib/image-upload.js +17 -0
- package/dist/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +4 -4
- package/dist/admin-components/i18n/texts.d.ts +6 -0
- package/dist/admin-components/i18n/texts.js +6 -0
- package/dist/documents/templates/financial-document.d.ts +3 -3
- package/dist/documents/templates/financial-document.js +68 -50
- package/dist/documents/templates/format.d.ts +14 -0
- package/dist/documents/templates/format.js +43 -1
- package/dist/documents/templates/html-factory.d.ts +6 -2
- package/dist/documents/templates/html-factory.js +6 -4
- package/dist/documents/templates/html-utils.d.ts +6 -0
- package/dist/documents/templates/html-utils.js +11 -1
- package/dist/documents/templates/layout.d.ts +21 -3
- package/dist/documents/templates/layout.js +114 -69
- package/dist/documents/templates/packing-list.js +9 -7
- package/dist/documents/types/documents.d.ts +17 -0
- package/dist/documents/types/html-factory.d.ts +1 -0
- package/dist/email/types/shop-configuration.d.ts +25 -0
- package/dist/email/types/template-data.d.ts +509 -0
- package/dist/email/types/template-data.js +154 -1
- package/dist/public/descriptor.js +2 -0
- package/dist/public/shop-master-data.d.ts +1 -1
- package/dist/public/shop-master-data.js +1 -1
- package/dist/public/types.d.ts +2 -1
- package/dist/shop-config/build-food-shop-config.js +4 -0
- package/dist/shop-config/options.d.ts +10 -3
- package/dist/shop-config/options.js +4 -0
- package/package.json +2 -2
|
@@ -4,8 +4,61 @@ exports.prepareReturnEmailWorkflow = exports.prepareReturnEmail = void 0;
|
|
|
4
4
|
const utils_1 = require("@medusajs/framework/utils");
|
|
5
5
|
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
6
6
|
const business_keys_1 = require("../../email/domain/business-keys");
|
|
7
|
+
const return_summary_1 = require("../../email/domain/return-summary");
|
|
8
|
+
const load_email_shop_details_1 = require("../../email/rendering/load-email-shop-details");
|
|
7
9
|
const email_1 = require("../../modules/email");
|
|
8
10
|
const dispatch_email_message_1 = require("./dispatch-email-message");
|
|
11
|
+
/**
|
|
12
|
+
* The snapshot must never block the return confirmation: a return that
|
|
13
|
+
* cannot be read or mapped is confirmed without it.
|
|
14
|
+
*/
|
|
15
|
+
const returnSummaryData = async (container, orderReturn) => {
|
|
16
|
+
try {
|
|
17
|
+
const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
18
|
+
const { data: returns } = await query.graph({
|
|
19
|
+
entity: "return",
|
|
20
|
+
fields: [...return_summary_1.RETURN_SUMMARY_FIELDS],
|
|
21
|
+
filters: { id: orderReturn.id },
|
|
22
|
+
});
|
|
23
|
+
const { data: locations } = orderReturn.location_id
|
|
24
|
+
? await query.graph({
|
|
25
|
+
entity: "stock_location",
|
|
26
|
+
fields: [...return_summary_1.RETURN_LOCATION_FIELDS],
|
|
27
|
+
filters: { id: orderReturn.location_id },
|
|
28
|
+
})
|
|
29
|
+
: { data: [] };
|
|
30
|
+
return {
|
|
31
|
+
summary: (0, return_summary_1.toReturnSummary)(returns[0], locations[0] ?? null, await (0, load_email_shop_details_1.loadEmailShopDetails)(container)),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
container
|
|
36
|
+
.resolve(utils_1.ContainerRegistrationKeys.LOGGER)
|
|
37
|
+
.warn("Email: return summary unavailable; confirming without it.");
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The snapshot must never block the return receipt: a return that cannot be
|
|
43
|
+
* read or mapped is confirmed without it.
|
|
44
|
+
*/
|
|
45
|
+
const returnReceivedSummaryData = async (container, returnId) => {
|
|
46
|
+
try {
|
|
47
|
+
const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
48
|
+
const { data } = await query.graph({
|
|
49
|
+
entity: "return",
|
|
50
|
+
fields: [...return_summary_1.RETURN_RECEIVED_SUMMARY_FIELDS],
|
|
51
|
+
filters: { id: returnId },
|
|
52
|
+
});
|
|
53
|
+
return { summary: (0, return_summary_1.toReturnReceivedSummary)(data[0]) };
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
container
|
|
57
|
+
.resolve(utils_1.ContainerRegistrationKeys.LOGGER)
|
|
58
|
+
.warn("Email: received return summary unavailable; confirming without it.");
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
};
|
|
9
62
|
const prepareReturnEmail = async (container, input) => {
|
|
10
63
|
const orders = container.resolve(utils_1.Modules.ORDER);
|
|
11
64
|
const orderReturn = (await orders.retrieveReturn(input.return_id));
|
|
@@ -32,7 +85,11 @@ const prepareReturnEmail = async (container, input) => {
|
|
|
32
85
|
...requestedIdentity,
|
|
33
86
|
recipient: order.email ?? null,
|
|
34
87
|
fact: "requested",
|
|
35
|
-
template_data: {
|
|
88
|
+
template_data: {
|
|
89
|
+
order_id: order.id,
|
|
90
|
+
return_id: orderReturn.id,
|
|
91
|
+
...(await returnSummaryData(container, orderReturn)),
|
|
92
|
+
},
|
|
36
93
|
});
|
|
37
94
|
}
|
|
38
95
|
const receivedIdentity = {
|
|
@@ -53,7 +110,11 @@ const prepareReturnEmail = async (container, input) => {
|
|
|
53
110
|
...receivedIdentity,
|
|
54
111
|
recipient: order.email ?? null,
|
|
55
112
|
fact: "received",
|
|
56
|
-
template_data: {
|
|
113
|
+
template_data: {
|
|
114
|
+
order_id: order.id,
|
|
115
|
+
return_id: orderReturn.id,
|
|
116
|
+
...(await returnReceivedSummaryData(container, orderReturn.id)),
|
|
117
|
+
},
|
|
57
118
|
});
|
|
58
119
|
}
|
|
59
120
|
await (0, dispatch_email_message_1.dispatchEmailMessageWorkflow)(container).run({
|
|
@@ -72,4 +133,4 @@ const prepareReturnEmail = async (container, input) => {
|
|
|
72
133
|
exports.prepareReturnEmail = prepareReturnEmail;
|
|
73
134
|
const prepareReturnEmailStep = (0, workflows_sdk_1.createStep)("email-prepare-return", async (input, { container }) => new workflows_sdk_1.StepResponse(await (0, exports.prepareReturnEmail)(container, input)));
|
|
74
135
|
exports.prepareReturnEmailWorkflow = (0, workflows_sdk_1.createWorkflow)("email-prepare-return", (input) => new workflows_sdk_1.WorkflowResponse(prepareReturnEmailStep(input)));
|
|
75
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
136
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlcGFyZS1yZXR1cm4tZW1haWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL2VtYWlsL3ByZXBhcmUtcmV0dXJuLWVtYWlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUtBLHFEQUltQztBQUNuQyxxRUFLMkM7QUFFM0Msb0VBRzBDO0FBQzFDLHNFQU0yQztBQUMzQywyRkFBcUY7QUFFckYsK0NBQW1EO0FBRW5ELHFFQUF3RTtBQXNCeEU7OztHQUdHO0FBQ0gsTUFBTSxpQkFBaUIsR0FBRyxLQUFLLEVBQzdCLFNBQTBCLEVBQzFCLFdBQXlCLEVBQ2EsRUFBRTtJQUN4QyxJQUFJLENBQUM7UUFDSCxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2pFLE1BQU0sRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLEdBQUcsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDO1lBQzFDLE1BQU0sRUFBRSxRQUFRO1lBQ2hCLE1BQU0sRUFBRSxDQUFDLEdBQUcsc0NBQXFCLENBQUM7WUFDbEMsT0FBTyxFQUFFLEVBQUUsRUFBRSxFQUFFLFdBQVcsQ0FBQyxFQUFFLEVBQUU7U0FDaEMsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsR0FBRyxXQUFXLENBQUMsV0FBVztZQUNqRCxDQUFDLENBQUMsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDO2dCQUNoQixNQUFNLEVBQUUsZ0JBQWdCO2dCQUN4QixNQUFNLEVBQUUsQ0FBQyxHQUFHLHVDQUFzQixDQUFDO2dCQUNuQyxPQUFPLEVBQUUsRUFBRSxFQUFFLEVBQUUsV0FBVyxDQUFDLFdBQVcsRUFBRTthQUN6QyxDQUFDO1lBQ0osQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLEVBQUUsRUFBRSxDQUFDO1FBQ2pCLE9BQU87WUFDTCxPQUFPLEVBQUUsSUFBQSxn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
|
|
@@ -3,8 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.prepareShipmentEmailWorkflow = exports.prepareShipmentEmail = void 0;
|
|
4
4
|
const utils_1 = require("@medusajs/framework/utils");
|
|
5
5
|
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
6
|
+
const core_flows_1 = require("@medusajs/medusa/core-flows");
|
|
6
7
|
const business_keys_1 = require("../../email/domain/business-keys");
|
|
7
8
|
const documents_fact_fingerprint_1 = require("../../email/domain/documents-fact-fingerprint");
|
|
9
|
+
const shipment_summary_1 = require("../../email/domain/shipment-summary");
|
|
8
10
|
const email_1 = require("../../modules/email");
|
|
9
11
|
const dispatch_email_message_1 = require("./dispatch-email-message");
|
|
10
12
|
const documents_required_facts_1 = require("./documents-required-facts");
|
|
@@ -17,6 +19,27 @@ const findOrderIdForFulfillment = async (container, fulfillmentId) => {
|
|
|
17
19
|
}
|
|
18
20
|
return records[0].order_id;
|
|
19
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* The shipment snapshot must never block the shipping confirmation: a
|
|
24
|
+
* fulfillment that cannot be read or mapped is confirmed without it.
|
|
25
|
+
*/
|
|
26
|
+
const shipmentSummaryData = async (container, order, fulfillmentId) => {
|
|
27
|
+
try {
|
|
28
|
+
const query = container.resolve(utils_1.ContainerRegistrationKeys.QUERY);
|
|
29
|
+
const { data } = await query.graph({
|
|
30
|
+
entity: "fulfillment",
|
|
31
|
+
fields: [...shipment_summary_1.SHIPMENT_FULFILLMENT_FIELDS],
|
|
32
|
+
filters: { id: fulfillmentId },
|
|
33
|
+
});
|
|
34
|
+
return { shipment: (0, shipment_summary_1.toShipmentSummary)(order, data[0]) };
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
container
|
|
38
|
+
.resolve(utils_1.ContainerRegistrationKeys.LOGGER)
|
|
39
|
+
.warn("Email: shipment summary unavailable; confirming without it.");
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
20
43
|
const prepareShipmentEmail = async (container, input) => {
|
|
21
44
|
const email = container.resolve(email_1.EMAIL_MODULE);
|
|
22
45
|
const businessKey = (0, business_keys_1.orderShippedBusinessKey)(input.fulfillment_id);
|
|
@@ -39,6 +62,7 @@ const prepareShipmentEmail = async (container, input) => {
|
|
|
39
62
|
template_data: {
|
|
40
63
|
order_id: input.order_id,
|
|
41
64
|
fulfillment_id: input.fulfillment_id,
|
|
65
|
+
invoice_attached: input.decision === "carrier",
|
|
42
66
|
documents_fact_fingerprint: (0, documents_fact_fingerprint_1.fingerprintDocumentsFact)({
|
|
43
67
|
schema_version: input.schema_version,
|
|
44
68
|
order_id: input.order_id,
|
|
@@ -54,8 +78,16 @@ const prepareShipmentEmail = async (container, input) => {
|
|
|
54
78
|
})
|
|
55
79
|
: await (async () => {
|
|
56
80
|
const orderId = await findOrderIdForFulfillment(container, input.fulfillment_id);
|
|
57
|
-
const
|
|
58
|
-
|
|
81
|
+
const { result } = await (0, core_flows_1.getOrderDetailWorkflow)(container).run({
|
|
82
|
+
input: {
|
|
83
|
+
order_id: orderId,
|
|
84
|
+
fields: ["id", "email", ...shipment_summary_1.SHIPMENT_ORDER_FIELDS],
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
if (!result) {
|
|
88
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, "The shipped fulfillment's order was not found.");
|
|
89
|
+
}
|
|
90
|
+
const order = result;
|
|
59
91
|
return await email.upsertMessageFact({
|
|
60
92
|
...identity,
|
|
61
93
|
fact: "shipment",
|
|
@@ -63,6 +95,7 @@ const prepareShipmentEmail = async (container, input) => {
|
|
|
63
95
|
template_data: {
|
|
64
96
|
order_id: order.id,
|
|
65
97
|
fulfillment_id: input.fulfillment_id,
|
|
98
|
+
...(await shipmentSummaryData(container, order, input.fulfillment_id)),
|
|
66
99
|
},
|
|
67
100
|
suppression_code: input.no_notification === true ? "notifications_disabled" : null,
|
|
68
101
|
});
|
|
@@ -79,4 +112,4 @@ const prepareShipmentEmail = async (container, input) => {
|
|
|
79
112
|
exports.prepareShipmentEmail = prepareShipmentEmail;
|
|
80
113
|
const prepareShipmentEmailStep = (0, workflows_sdk_1.createStep)("email-prepare-shipment", async (input, { container }) => new workflows_sdk_1.StepResponse(await (0, exports.prepareShipmentEmail)(container, input)));
|
|
81
114
|
exports.prepareShipmentEmailWorkflow = (0, workflows_sdk_1.createWorkflow)("email-prepare-shipment", (input) => new workflows_sdk_1.WorkflowResponse(prepareShipmentEmailStep(input)));
|
|
82
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
115
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlcGFyZS1zaGlwbWVudC1lbWFpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy93b3JrZmxvd3MvZW1haWwvcHJlcGFyZS1zaGlwbWVudC1lbWFpbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFDQSxxREFJbUM7QUFDbkMscUVBSzJDO0FBQzNDLDREQUFxRTtBQUdyRSxvRUFBMkU7QUFDM0UsOEZBQXlGO0FBQ3pGLDBFQUk2QztBQUU3QywrQ0FBbUQ7QUFFbkQscUVBQXdFO0FBQ3hFLHlFQUEyRTtBQXdDM0UsTUFBTSx5QkFBeUIsR0FBRyxLQUFLLEVBQ3JDLFNBQTBCLEVBQzFCLGFBQXFCLEVBQ0osRUFBRTtJQUNuQixNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsT0FBTyxDQUM3QixpQ0FBeUIsQ0FBQyxJQUFJLENBQ0wsQ0FBQztJQUM1QixNQUFNLG9CQUFvQixHQUFHLEtBQUssQ0FBQyxhQUFhLENBQzlDLGVBQU8sQ0FBQyxLQUFLLEVBQ2IsVUFBVSxFQUNWLGVBQU8sQ0FBQyxXQUFXLEVBQ25CLGdCQUFnQixDQUNqQixDQUFDO0lBQ0YsTUFBTSxPQUFPLEdBQUcsTUFBTSxvQkFBb0IsQ0FBQyxJQUFJLENBQzdDLEVBQUUsY0FBYyxFQUFFLGFBQWEsRUFBRSxFQUNqQyxFQUFFLE1BQU0sRUFBRSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQ3pCLENBQUM7SUFDRixJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDO1FBQ2xELE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxTQUFTLEVBQzNCLG1EQUFtRCxDQUNwRCxDQUFDO0lBQ0osQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQztBQUM3QixDQUFDLENBQUM7QUFFRjs7O0dBR0c7QUFD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
|
package/README.md
CHANGED
|
@@ -318,7 +318,10 @@ class DemoModuleService extends MedusaService({ /* models */ }) {
|
|
|
318
318
|
|
|
319
319
|
- Types: `string`, `url` (http/https), `number` (finite, optional inclusive
|
|
320
320
|
`min`/`max`), `boolean`, `secret`, `select` (`options: [{ value, label }]`,
|
|
321
|
-
non-empty, unique values; the stored value must be one of them)
|
|
321
|
+
non-empty, unique values; the stored value must be one of them), `image`
|
|
322
|
+
(the http(s) URL of a PNG/JPEG of at most 2 MiB that the Admin uploads
|
|
323
|
+
through Medusa's `/admin/uploads` into the public File Module storage and
|
|
324
|
+
previews; replacing or removing it leaves the old file in storage). Keys
|
|
322
325
|
follow the feature key format and are unique. A `secret` has no default;
|
|
323
326
|
any other default must match its type, options and bounds. `options` exist
|
|
324
327
|
only on `select`, `min`/`max` only on `number`. `required` defaults to
|
|
@@ -891,6 +894,10 @@ meantime, such as a password-reset link, is not sent). A toggle read
|
|
|
891
894
|
failure fails the subscriber with its fixed sentinel (the Event Bus retries
|
|
892
895
|
it) or fails the job run.
|
|
893
896
|
|
|
897
|
+
The plugin declares one setting, edited in the Einstellungen section of its
|
|
898
|
+
Admin page: `logo` (type `image`, optional), the PNG/JPG shown on top of every
|
|
899
|
+
mail. It is not a Shop-Stammdaten field; Documents has its own `logo`.
|
|
900
|
+
|
|
894
901
|
Admin surfaces of a disabled feature are hidden. The E-Mail settings page
|
|
895
902
|
keeps `PluginFeatureSections` visible (so the plugin can be switched back on)
|
|
896
903
|
and gates the Terms PDF versions and mail log sections below it on the
|
|
@@ -1060,20 +1067,130 @@ has at most 35 characters. Email runtime-validates every contract before use.
|
|
|
1060
1067
|
- Password-reset messages cannot be retried by the generic retry job or resent
|
|
1061
1068
|
by an Admin.
|
|
1062
1069
|
- Unknown transport outcomes are never automatically resent.
|
|
1063
|
-
- Shop branding is
|
|
1064
|
-
|
|
1065
|
-
plugin.
|
|
1070
|
+
- Shop branding is the configured `displayName` plus the `logo` setting of
|
|
1071
|
+
the E-Mail plugin and the company details of the Shop-Stammdaten; a shop overrides the default layout or
|
|
1072
|
+
single renderers in its own configuration, not in the plugin.
|
|
1066
1073
|
|
|
1067
1074
|
Read this section and the central design before changing Email domain behavior.
|
|
1068
1075
|
|
|
1069
1076
|
### Shop configuration and default templates
|
|
1070
1077
|
|
|
1071
|
-
The E-Mail module ships a neutral German layout
|
|
1072
|
-
|
|
1073
|
-
|
|
1078
|
+
The E-Mail module ships a neutral German layout in grey tones (table-based,
|
|
1079
|
+
inline styles) and one renderer per accepted kind. The layout shows, from top
|
|
1080
|
+
to bottom: the `logo` centered (an `image` setting of the E-Mail plugin,
|
|
1081
|
+
edited on its Admin page; without one, the configured `displayName`), a white card with the subject as heading and the
|
|
1082
|
+
renderer's body, a contact line ("Fragen? Dann schreib uns an … oder ruf uns
|
|
1083
|
+
an unter …", only with `email`/`phone`), and a footer with the company details
|
|
1084
|
+
(`legal_name` or `displayName`, address, UID, Firmenbuchnummer and
|
|
1085
|
+
Firmenbuchgericht, website) plus "Diese E-Mail wurde automatisch versendet.".
|
|
1086
|
+
Missing master data fields are left out. Email reads the logo and the
|
|
1087
|
+
Shop-Stammdaten when it sends a mail, so a change applies to the next mail; if
|
|
1088
|
+
a read fails the mail is sent without that part (logo or company details)
|
|
1089
|
+
instead of failing. Besides
|
|
1090
|
+
the layout, `displayName` appears in the password-reset and invite subjects.
|
|
1091
|
+
A custom `layout` receives the same data as `shop` (`EmailShopDetails`, every
|
|
1092
|
+
field optional) next to `bodyHtml`, `preheader` and `subject`. A shop
|
|
1074
1093
|
therefore needs only `displayName`, `affiliatePortalUrl`, `inviteUrl`,
|
|
1075
1094
|
`passwordResetUrl` and `orderNotificationEmail`.
|
|
1076
1095
|
|
|
1096
|
+
The order confirmation (`order.received`) receives an `OrderSummary` as
|
|
1097
|
+
`summary`: the display ID, the items with quantity and gross amount, the
|
|
1098
|
+
shipping method names, subtotal, shipping, discount, included tax and total,
|
|
1099
|
+
plus the billing and shipping address. Email freezes it when the order is
|
|
1100
|
+
placed, so a resend shows the order as it was placed. `summary` is absent for
|
|
1101
|
+
messages stored before the summary existed; a custom renderer must handle
|
|
1102
|
+
that case.
|
|
1103
|
+
|
|
1104
|
+
The shop's order notification (`order.received.admin`) receives the same
|
|
1105
|
+
`summary` (with the order's `email` and `placed_at`). Its subject names the
|
|
1106
|
+
order number and the customer ("Neue Bestellung #1042 von Ada Beispiel"),
|
|
1107
|
+
an info box lists number, customer, e-mail and order time, followed by an
|
|
1108
|
+
"Im Admin öffnen" button and the items, totals and addresses. Two optional
|
|
1109
|
+
`ShopEmailConfiguration` fields drive it: `adminOrderUrl({ orderId })` (the
|
|
1110
|
+
button is left out without it) and `timeZone` (IANA; the order time is left
|
|
1111
|
+
out without it). `buildFoodShopConfig` sets both (`<MEDUSA_BACKEND_URL>/app/
|
|
1112
|
+
orders/<id>` and its `timeZone` option). The layout receives `audience`
|
|
1113
|
+
(`"shop"` for this kind and `user.invite`, `"customer"` for all others) and
|
|
1114
|
+
shows the contact line only to customers.
|
|
1115
|
+
|
|
1116
|
+
The shipping confirmation (`order.shipped`, one per fulfillment) receives a
|
|
1117
|
+
`ShipmentSummary` as `shipment`, frozen when the shipment fact arrives: the
|
|
1118
|
+
display ID, the shipping option name, the tracking numbers and URLs entered
|
|
1119
|
+
at shipping, the parcel's items (title and variant of the order line,
|
|
1120
|
+
shipped quantity), the delivery address and `more_to_follow` (some ordered
|
|
1121
|
+
quantity is not shipped yet). `invoice_attached` is `true` when Documents chose
|
|
1122
|
+
this shipment for the invoice PDF. Its subject is "Deine Bestellung bei
|
|
1123
|
+
<displayName> ist unterwegs"; a single http(s) tracking URL becomes a
|
|
1124
|
+
"Sendung verfolgen" button, several become one link each. A fulfillment that
|
|
1125
|
+
cannot be read is confirmed without `shipment`, like a message stored before
|
|
1126
|
+
the snapshot existed.
|
|
1127
|
+
|
|
1128
|
+
The cancellation confirmation (`order.canceled`) receives the same
|
|
1129
|
+
`OrderSummary` as `summary` plus `canceled_at`, frozen when the cancellation
|
|
1130
|
+
fact arrives, and `cancellation_attached` (`true` when Documents attached at
|
|
1131
|
+
least one cancellation document). Its subject is "Deine Bestellung #1042 bei
|
|
1132
|
+
<displayName> wurde storniert"; an info box lists the order number and the
|
|
1133
|
+
cancellation date (left out without `timeZone`), followed by a fixed refund
|
|
1134
|
+
note and the canceled items and totals without addresses. An order that
|
|
1135
|
+
cannot be summarized is confirmed without `summary` and without its internal
|
|
1136
|
+
ID.
|
|
1137
|
+
|
|
1138
|
+
The return confirmation (`order.return_requested`) receives a `ReturnSummary`
|
|
1139
|
+
as `summary`, frozen when the return is requested: return and order numbers,
|
|
1140
|
+
the return shipping method, the items to send back (title and variant of the
|
|
1141
|
+
order line, quantity, return reason label) and the return address. It is
|
|
1142
|
+
the stock location's address, addressed to the location's company or else
|
|
1143
|
+
the master data's `legal_name`; without a location address it falls back to
|
|
1144
|
+
the master data's company address. Its subject is "Deine Rücksendung zur
|
|
1145
|
+
Bestellung #1042 bei <displayName>"; the mail asks the customer to enclose
|
|
1146
|
+
the return number (`#R5`). A return that cannot be read is confirmed without
|
|
1147
|
+
`summary` and without internal IDs.
|
|
1148
|
+
|
|
1149
|
+
The return receipt (`order.return_received`, sent once the return is
|
|
1150
|
+
completely received) receives a `ReturnReceivedSummary` as `summary`: return
|
|
1151
|
+
and order numbers, `received_at` and the received items (title and variant
|
|
1152
|
+
of the order line, received quantity and the damaged part of it). Its subject
|
|
1153
|
+
is "Deine Rücksendung zur Bestellung #1042 bei <displayName> ist
|
|
1154
|
+
eingetroffen"; the arrival date needs `timeZone`, damaged units are noted per
|
|
1155
|
+
item, and a fixed sentence says that an eventual refund follows on the
|
|
1156
|
+
original payment method, without promising a separate mail (`order.refunded`
|
|
1157
|
+
only exists with Documents).
|
|
1158
|
+
|
|
1159
|
+
The refund confirmation (`order.refunded`, one per non-cancellation refund,
|
|
1160
|
+
Documents only) receives a `RefundSummary` as `summary`, frozen when the
|
|
1161
|
+
Documents refund fact arrives and read through the refund's payment: order
|
|
1162
|
+
number, refunded gross amount and currency, `refunded_at` and the Admin's
|
|
1163
|
+
refund reason label. `cancellation_attached` is `true` when Documents attached
|
|
1164
|
+
a cancellation document. Its subject is "Deine Erstattung zur Bestellung #1042
|
|
1165
|
+
bei <displayName>"; the info box shows number, amount, date (needs
|
|
1166
|
+
`timeZone`) and reason, followed by a fixed note on bank processing times.
|
|
1167
|
+
Without `summary` the mail keeps the intro and the attachment note.
|
|
1168
|
+
|
|
1169
|
+
The password reset (`auth.password_reset`) has the subject "Neues Passwort
|
|
1170
|
+
für dein Kundenkonto bei <displayName>", a "Neues Passwort festlegen" button,
|
|
1171
|
+
the link as copyable text below it, the link's lifetime ("Der Link ist 15
|
|
1172
|
+
Minuten gültig.", computed from the token's `exp` − `iat` and left out
|
|
1173
|
+
without `iat`) and a note that the mail can be ignored. The lifetime is
|
|
1174
|
+
passed as the ephemeral `valid_minutes` next to `reset_url` and is never
|
|
1175
|
+
persisted.
|
|
1176
|
+
|
|
1177
|
+
The affiliate reminder (`affiliate.consignment_report_due`) greets the
|
|
1178
|
+
affiliate by `affiliate_display_name`, names the report month ("September
|
|
1179
|
+
2026", Austrian month names) and the due date ("10.10.2026") as calendar
|
|
1180
|
+
values formatted in UTC so no time zone shifts them, explains that the counted
|
|
1181
|
+
stock determines the billed sales, links `affiliatePortalUrl` as a "Zum
|
|
1182
|
+
Partnerbereich" button and says the mail can be ignored once the report is
|
|
1183
|
+
submitted. The button leads to a 404 while the affiliate plugin's
|
|
1184
|
+
`partner_portal` toggle is off.
|
|
1185
|
+
|
|
1186
|
+
The Admin invite (`user.invite`) has the subject "Einladung ins Admin-Team von
|
|
1187
|
+
<displayName>", an "Einladung annehmen" button with the link as copyable text,
|
|
1188
|
+
the invite's current expiry ("Die Einladung ist gültig bis 02.10.2026, 01:40
|
|
1189
|
+
Uhr.", needs `timeZone`; read from the invite at send time, so a resend shows
|
|
1190
|
+
its new expiry) and a note that the mail can be ignored. The expiry is passed
|
|
1191
|
+
as the ephemeral `expires_at` next to `invite_url`. The invite goes to the
|
|
1192
|
+
shop's team, so its layout audience is `"shop"` (no contact line).
|
|
1193
|
+
|
|
1077
1194
|
- `layout` (optional) replaces the default layout completely.
|
|
1078
1195
|
- `renderers` (optional, `Partial<EmailRendererMap>`) replaces only the listed
|
|
1079
1196
|
kinds; every other kind keeps its default renderer.
|
|
@@ -1384,8 +1501,9 @@ below. `medusa db:migrate` creates its tables.
|
|
|
1384
1501
|
`@hartl-services/medusa-base/config` exports, next to the environment helpers,
|
|
1385
1502
|
build-time values for `medusa-config.ts` and tests:
|
|
1386
1503
|
|
|
1387
|
-
- `createDefaultDocumentHtmlFactory({ timeZone })` — the built-in
|
|
1388
|
-
use as it is
|
|
1504
|
+
- `createDefaultDocumentHtmlFactory({ timeZone, theme? })` — the built-in
|
|
1505
|
+
factory, to use as it is, with a shop theme (see "Layout, logo, payment
|
|
1506
|
+
terms and shop theme" below) or to wrap.
|
|
1389
1507
|
- `DOCUMENTS_BUILD_PLACEHOLDERS` — `{ DOCUMENT_PDF_EXECUTABLE: "google-chrome" }`.
|
|
1390
1508
|
`medusa build` evaluates `medusa-config.ts` without the runtime-only
|
|
1391
1509
|
environment, so spread it under the real environment while building.
|
|
@@ -1393,6 +1511,44 @@ build-time values for `medusa-config.ts` and tests:
|
|
|
1393
1511
|
The document types (`DocumentMerchantOption`, `DocumentHtmlFactory`,
|
|
1394
1512
|
`DocumentRenderInput`, ...) come from `@hartl-services/medusa-base/types`.
|
|
1395
1513
|
|
|
1514
|
+
### Layout, logo, payment terms and shop theme
|
|
1515
|
+
|
|
1516
|
+
The default markup prints an A4 letterhead: logo top left, document title and
|
|
1517
|
+
key data (number, order, date, customer UID for exempt B2B supplies) on the
|
|
1518
|
+
right, the billing address on the left, the item table with batch and
|
|
1519
|
+
best-before date per line, the totals below the price columns and a
|
|
1520
|
+
three-column footer (sender with UID and company register, contact, bank) on
|
|
1521
|
+
every page. Packing lists show only the sender in the footer. Every page
|
|
1522
|
+
carries "Seite x von y" bottom right in an 8 mm bottom page margin
|
|
1523
|
+
(`@page { @bottom-right }`; the page count exists only in page margin boxes).
|
|
1524
|
+
|
|
1525
|
+
Settings under Settings → "Hartl Services Plugins" → **Dokumente**, read on
|
|
1526
|
+
every render and passed as `input.settings` (`DocumentRenderSettings`). Like
|
|
1527
|
+
the merchant data they are not part of the snapshot: a regenerated document
|
|
1528
|
+
shows the current values.
|
|
1529
|
+
|
|
1530
|
+
- **Logo** (`logo`, PNG or JPG): each render downloads it (10 s timeout, at
|
|
1531
|
+
most 2 MiB) and passes it inline as `settings.logo_src` (`data:` URL), so
|
|
1532
|
+
Chromium never waits on the network. Without a logo, or when the download
|
|
1533
|
+
fails (logged as a warning), the document shows the merchant's legal name;
|
|
1534
|
+
a logo never blocks issuance.
|
|
1535
|
+
- **Zahlungskonditionen** (`payment_terms`, text): printed below the totals
|
|
1536
|
+
of invoices only (not on cancellations or credit notes); blank means none.
|
|
1537
|
+
Line breaks in the text are kept.
|
|
1538
|
+
|
|
1539
|
+
**Theme:** `createDefaultDocumentHtmlFactory({ timeZone, theme })` keeps the
|
|
1540
|
+
content and markup and changes only the look. `theme.stylesheet` is CSS
|
|
1541
|
+
appended after the default stylesheet (later rules win; the defaults use the
|
|
1542
|
+
custom properties `--accent`, `--text`, `--muted`, `--line`, `--table-head`,
|
|
1543
|
+
`--page-x`, `--page-top`, `--footer-height`). Put a watermark or other
|
|
1544
|
+
full-page image into `@page { background: ... / 210mm 297mm }`: it covers the
|
|
1545
|
+
whole sheet on every page, whereas Chromium clips fixed elements at the page
|
|
1546
|
+
margin and sizes an inline `<svg>` in them wrongly from the second page on.
|
|
1547
|
+
Restyle the page number with `@page { @bottom-right { ... } }`. Embed web
|
|
1548
|
+
fonts as `data:` URLs; the container's Chromium has only its system fonts. A
|
|
1549
|
+
shop passes its factory through the preset option `documentHtmlFactory` (see
|
|
1550
|
+
"Shop-Config-Preset").
|
|
1551
|
+
|
|
1396
1552
|
### Merchant data
|
|
1397
1553
|
|
|
1398
1554
|
The legal sender printed on every document comes from the base plugin's shop
|
|
@@ -2207,6 +2363,7 @@ validate the environment; a missing variable fails with the variable's name.
|
|
|
2207
2363
|
| `orderNotificationEmail` | Receives order notification e-mails; must look like an e-mail address. |
|
|
2208
2364
|
| `timeZone` | IANA time zone (validated with `Intl`). Chooses the annual document-number period, the printed dates on documents and the affiliate consignment reminder time zone. |
|
|
2209
2365
|
| `storefrontCountryCode` | 2-letter lowercase country code of the storefront route. Links: `<STOREFRONT_URL>/<cc>/partnerportal` (affiliate portal), `<STOREFRONT_URL>/<cc>/reset-password` (the e-mail plugin appends `#token=`), referral base `<STOREFRONT_URL>/<cc>`. The Admin invite link is `<MEDUSA_BACKEND_URL>/app/invite?token=`. |
|
|
2366
|
+
| `documentHtmlFactory` | Optional `DocumentHtmlFactory` that replaces the default document markup, typically `createDefaultDocumentHtmlFactory({ timeZone, theme })` from `/config` with the shop's CSS and watermark (see "Layout, logo, payment terms and shop theme"). Must be a function. |
|
|
2210
2367
|
| `loadEnvFile` | Default `true`: calls `loadEnv(NODE_ENV \|\| "development", process.cwd())`. Pass `false` when the environment is already complete (tests, isolated child processes). |
|
|
2211
2368
|
|
|
2212
2369
|
### Environment variables
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type ImageSettingInputProps = {
|
|
2
|
+
id: string;
|
|
3
|
+
hintId: string;
|
|
4
|
+
label: string;
|
|
5
|
+
/** Public URL of the current image; `""` = none. */
|
|
6
|
+
value: string;
|
|
7
|
+
disabled: boolean;
|
|
8
|
+
onChange: (url: string) => void;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Uploads an image to the public File Module storage and hands its URL to
|
|
12
|
+
* the settings draft; the form stores it on save like any other value.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ImageSettingInput: ({ id, hintId, label, value, disabled, onChange, }: ImageSettingInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Hint, Text } from "@medusajs/ui";
|
|
3
|
+
import { useMutation } from "@tanstack/react-query";
|
|
4
|
+
import { useRef, useState } from "react";
|
|
5
|
+
import { texts } from "../../../i18n/texts";
|
|
6
|
+
import { sdk } from "../../../lib/sdk";
|
|
7
|
+
import { validateSettingImage } from "../lib/image-upload";
|
|
8
|
+
/**
|
|
9
|
+
* Uploads an image to the public File Module storage and hands its URL to
|
|
10
|
+
* the settings draft; the form stores it on save like any other value.
|
|
11
|
+
*/
|
|
12
|
+
export const ImageSettingInput = ({ id, hintId, label, value, disabled, onChange, }) => {
|
|
13
|
+
const fileInput = useRef(null);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const upload = useMutation({
|
|
16
|
+
mutationFn: async (file) => {
|
|
17
|
+
const { files } = await sdk.admin.upload.create({ files: [file] });
|
|
18
|
+
const url = files[0]?.url;
|
|
19
|
+
if (!url) {
|
|
20
|
+
throw new Error("upload returned no file");
|
|
21
|
+
}
|
|
22
|
+
return url;
|
|
23
|
+
},
|
|
24
|
+
onSuccess: (url) => onChange(url),
|
|
25
|
+
onError: () => setError(texts.settings.imageUploadFailed),
|
|
26
|
+
});
|
|
27
|
+
const busy = disabled || upload.isPending;
|
|
28
|
+
const selectFile = (file) => {
|
|
29
|
+
if (!file) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const invalid = validateSettingImage(file);
|
|
33
|
+
setError(invalid);
|
|
34
|
+
if (!invalid) {
|
|
35
|
+
upload.mutate(file);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return (_jsxs("div", { className: "grid gap-2", children: [value && (_jsx("div", { className: "bg-ui-bg-subtle flex items-center justify-center rounded-md border p-4", children: _jsx("img", { src: value, alt: texts.settings.imagePreviewAlt(label), className: "max-h-24 max-w-full object-contain" }) })), _jsx("input", { ref: fileInput, id: id, "aria-describedby": hintId, type: "file", accept: "image/png,image/jpeg", className: "hidden", onChange: (event) => {
|
|
39
|
+
selectFile(event.target.files?.[0]);
|
|
40
|
+
event.target.value = "";
|
|
41
|
+
} }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", size: "small", variant: "secondary", isLoading: upload.isPending, disabled: busy, onClick: () => fileInput.current?.click(), children: value ? texts.settings.imageReplace : texts.settings.imageUpload }), value && (_jsx(Button, { type: "button", size: "small", variant: "transparent", disabled: busy, onClick: () => onChange(""), children: texts.settings.imageRemove })), !value && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: texts.settings.imageNone }))] }), error && _jsx(Hint, { variant: "error", children: error })] }));
|
|
42
|
+
};
|
|
@@ -2,6 +2,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { Alert, Button, Container, Heading, Hint, Input, Label, Select, Switch, Text, } from "@medusajs/ui";
|
|
3
3
|
import { texts } from "../../../i18n/texts";
|
|
4
4
|
import { usePluginSettingsForm } from "../hooks/usePluginSettingsForm";
|
|
5
|
+
import { ImageSettingInput } from "./ImageSettingInput";
|
|
5
6
|
const fieldId = (pluginKey, key) => `plugin-setting-${pluginKey}-${key}`;
|
|
6
7
|
export const missingLabels = (plugin) => plugin.missing
|
|
7
8
|
.map((key) => plugin.settings.find((setting) => setting.key === key)?.label ?? key)
|
|
@@ -27,13 +28,14 @@ const SettingField = ({ pluginKey, setting, invalidatesLabels, value, invalid, d
|
|
|
27
28
|
? texts.settings.secretClearUndoLabel(setting.label)
|
|
28
29
|
: texts.settings.secretClearLabel(setting.label), onClick: () => onChange(clearPending ? "" : null), children: clearPending
|
|
29
30
|
? texts.settings.secretClearUndo
|
|
30
|
-
: texts.settings.secretClear }))] })) : setting.type === "select" ? (_jsxs(Select, { value: typeof value === "string" ? value : "", disabled: disabled, onValueChange: (next) => onChange(next), children: [_jsx(Select.Trigger, { id: id, "aria-describedby": hintId, children: _jsx(Select.Value, { placeholder: texts.settings.selectPlaceholder }) }), _jsx(Select.Content, { children: setting.options.map((option) => (_jsx(Select.Item, { value: option.value, children: option.label }, option.value))) })] })) : setting.type === "number" ? (_jsx(Input, { id: id, "aria-describedby": hintId, "aria-invalid": invalid, type: "number", step: "any", min: setting.min ?? undefined, max: setting.max ?? undefined, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value, event.target.validity.badInput) })) : (_jsx(Input, { id: id, "aria-describedby": hintId, "aria-invalid": invalid, type: setting.type === "url" ? "url" : "text", value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value) })), _jsxs("div", { id: hintId, className: "grid gap-1", children: [setting.description && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: setting.description })), clearPending && (_jsx(Hint, { variant: "info", children: texts.settings.secretClearPending })), invalidatesLabels && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: texts.settings.invalidates(invalidatesLabels) })), bounds && !invalid && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: bounds })), invalid && (_jsx(Hint, { variant: "error", children: bounds ?? texts.settings.invalidNumber }))] })] }));
|
|
31
|
+
: texts.settings.secretClear }))] })) : setting.type === "select" ? (_jsxs(Select, { value: typeof value === "string" ? value : "", disabled: disabled, onValueChange: (next) => onChange(next), children: [_jsx(Select.Trigger, { id: id, "aria-describedby": hintId, children: _jsx(Select.Value, { placeholder: texts.settings.selectPlaceholder }) }), _jsx(Select.Content, { children: setting.options.map((option) => (_jsx(Select.Item, { value: option.value, children: option.label }, option.value))) })] })) : setting.type === "image" ? (_jsx(ImageSettingInput, { id: id, hintId: hintId, label: setting.label, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (url) => onChange(url) })) : setting.type === "number" ? (_jsx(Input, { id: id, "aria-describedby": hintId, "aria-invalid": invalid, type: "number", step: "any", min: setting.min ?? undefined, max: setting.max ?? undefined, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value, event.target.validity.badInput) })) : (_jsx(Input, { id: id, "aria-describedby": hintId, "aria-invalid": invalid, type: setting.type === "url" ? "url" : "text", value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value) })), _jsxs("div", { id: hintId, className: "grid gap-1", children: [setting.description && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: setting.description })), clearPending && (_jsx(Hint, { variant: "info", children: texts.settings.secretClearPending })), invalidatesLabels && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: texts.settings.invalidates(invalidatesLabels) })), bounds && !invalid && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: bounds })), invalid && (_jsx(Hint, { variant: "error", children: bounds ?? texts.settings.invalidNumber }))] })] }));
|
|
31
32
|
};
|
|
32
33
|
export const PluginSettingsForm = ({ plugin }) => {
|
|
33
34
|
const form = usePluginSettingsForm(plugin);
|
|
34
35
|
const pending = form.mutation.isPending;
|
|
35
36
|
const hasTextFields = plugin.settings.some((setting) => setting.type !== "boolean" &&
|
|
36
37
|
setting.type !== "secret" &&
|
|
37
|
-
setting.type !== "select"
|
|
38
|
+
setting.type !== "select" &&
|
|
39
|
+
setting.type !== "image");
|
|
38
40
|
return (_jsxs(Container, { className: "divide-y p-0", "aria-busy": pending, children: [_jsxs("div", { className: "px-6 py-4", children: [_jsx(Heading, { level: "h2", children: texts.sections.settings }), hasTextFields && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.clearHint }))] }), _jsxs("form", { onSubmit: form.submit, className: "grid gap-5 px-6 py-5", children: [!plugin.configured && (_jsx(Alert, { variant: "warning", children: texts.settings.missing(missingLabels(plugin)) })), plugin.settings.map((setting) => (_jsx(SettingField, { pluginKey: plugin.plugin_key, setting: setting, invalidatesLabels: invalidatesLabels(plugin, setting), value: form.draft[setting.key], invalid: form.invalid.includes(setting.key), disabled: pending, onChange: (value, badInput) => form.setValue(setting.key, value, badInput) }, setting.key))), _jsx("div", { className: "flex justify-end", children: _jsx(Button, { type: "submit", size: "small", isLoading: pending, disabled: !form.dirty || form.invalid.length > 0 || pending, children: texts.settings.save }) })] })] }));
|
|
39
41
|
};
|
|
@@ -74,7 +74,7 @@ export declare const pluginFeatureEnabledQueryOptions: (pluginKey: string, featu
|
|
|
74
74
|
source: "default" | "stored" | "config" | "none";
|
|
75
75
|
invalidates: string[];
|
|
76
76
|
} | {
|
|
77
|
-
type: "string" | "boolean" | "url";
|
|
77
|
+
type: "string" | "boolean" | "url" | "image";
|
|
78
78
|
default: string | number | boolean | null;
|
|
79
79
|
value: string | number | boolean | null;
|
|
80
80
|
key: string;
|
|
@@ -54,7 +54,7 @@ export declare function usePluginFeatures(): import("@tanstack/react-query").Use
|
|
|
54
54
|
source: "default" | "stored" | "config" | "none";
|
|
55
55
|
invalidates: string[];
|
|
56
56
|
} | {
|
|
57
|
-
type: "string" | "boolean" | "url";
|
|
57
|
+
type: "string" | "boolean" | "url" | "image";
|
|
58
58
|
default: string | number | boolean | null;
|
|
59
59
|
value: string | number | boolean | null;
|
|
60
60
|
key: string;
|
|
@@ -141,7 +141,7 @@ export declare function usePluginFeature(pluginKey: string | undefined): import(
|
|
|
141
141
|
source: "default" | "stored" | "config" | "none";
|
|
142
142
|
invalidates: string[];
|
|
143
143
|
} | {
|
|
144
|
-
type: "string" | "boolean" | "url";
|
|
144
|
+
type: "string" | "boolean" | "url" | "image";
|
|
145
145
|
default: string | number | boolean | null;
|
|
146
146
|
value: string | number | boolean | null;
|
|
147
147
|
key: string;
|
|
@@ -222,7 +222,7 @@ export declare function useUpdatePluginFeatures(): import("@tanstack/react-query
|
|
|
222
222
|
source: "default" | "stored" | "config" | "none";
|
|
223
223
|
invalidates: string[];
|
|
224
224
|
} | {
|
|
225
|
-
type: "string" | "boolean" | "url";
|
|
225
|
+
type: "string" | "boolean" | "url" | "image";
|
|
226
226
|
default: string | number | boolean | null;
|
|
227
227
|
value: string | number | boolean | null;
|
|
228
228
|
key: string;
|
|
@@ -307,7 +307,7 @@ export declare function useUpdatePluginSettings(pluginKey: string): import("@tan
|
|
|
307
307
|
source: "default" | "stored" | "config" | "none";
|
|
308
308
|
invalidates: string[];
|
|
309
309
|
} | {
|
|
310
|
-
type: "string" | "boolean" | "url";
|
|
310
|
+
type: "string" | "boolean" | "url" | "image";
|
|
311
311
|
default: string | number | boolean | null;
|
|
312
312
|
value: string | number | boolean | null;
|
|
313
313
|
key: string;
|
|
@@ -63,7 +63,7 @@ export declare function usePluginSettingsForm(plugin: AdminPluginFeatures): {
|
|
|
63
63
|
source: "default" | "stored" | "config" | "none";
|
|
64
64
|
invalidates: string[];
|
|
65
65
|
} | {
|
|
66
|
-
type: "string" | "boolean" | "url";
|
|
66
|
+
type: "string" | "boolean" | "url" | "image";
|
|
67
67
|
default: string | number | boolean | null;
|
|
68
68
|
value: string | number | boolean | null;
|
|
69
69
|
key: string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Largest image an `image` setting accepts in the Admin. */
|
|
2
|
+
export declare const SETTING_IMAGE_MAX_BYTES: number;
|
|
3
|
+
type ImageSelection = Pick<File, "size" | "type">;
|
|
4
|
+
/** Why `file` cannot be uploaded for an `image` setting; `null` = valid. */
|
|
5
|
+
export declare const validateSettingImage: (file: ImageSelection) => string | null;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Largest image an `image` setting accepts in the Admin. */
|
|
2
|
+
export const SETTING_IMAGE_MAX_BYTES = 2 * 1024 * 1024;
|
|
3
|
+
// Gmail and Outlook do not display SVG or WebP in e-mails.
|
|
4
|
+
const IMAGE_TYPES = new Set(["image/png", "image/jpeg"]);
|
|
5
|
+
/** Why `file` cannot be uploaded for an `image` setting; `null` = valid. */
|
|
6
|
+
export const validateSettingImage = (file) => {
|
|
7
|
+
if (!IMAGE_TYPES.has(file.type)) {
|
|
8
|
+
return "Bitte eine PNG- oder JPG-Datei auswählen.";
|
|
9
|
+
}
|
|
10
|
+
if (file.size === 0) {
|
|
11
|
+
return "Die Bilddatei ist leer.";
|
|
12
|
+
}
|
|
13
|
+
if (file.size > SETTING_IMAGE_MAX_BYTES) {
|
|
14
|
+
return "Die Bilddatei darf maximal 2 MiB groß sein.";
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
};
|