medusa-contact-us 0.0.17 → 0.0.20
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/api/admin/contact-requests/[id]/route.js +17 -0
- package/.medusa/server/src/api/admin/contact-requests/[id]/status/route.js +24 -0
- package/.medusa/server/src/api/admin/contact-requests/route.js +67 -0
- package/.medusa/server/src/api/admin/contact-requests/validators.js +29 -0
- package/.medusa/server/src/api/store/contact-requests/route.js +23 -0
- package/.medusa/server/src/api/store/contact-requests/validators.js +11 -0
- package/.medusa/server/src/helpers/contact-request.js +52 -0
- package/.medusa/server/src/helpers/index.js +5 -2
- package/.medusa/server/src/index.js +10 -2
- package/.medusa/server/src/modules/contact-requests/index.js +14 -0
- package/.medusa/server/src/modules/contact-requests/migrations/Migration20241129163317.js +47 -0
- package/.medusa/server/src/modules/contact-requests/models/contact-request.js +15 -0
- package/.medusa/server/src/modules/contact-requests/service.js +236 -0
- package/.medusa/server/src/modules/contact-requests/utils/resolve-options.js +48 -0
- package/.medusa/server/src/types/contact-request.js +3 -0
- package/.medusa/server/src/workflows/create-contact-request-workflow.js +13 -0
- package/.medusa/server/src/workflows/index.js +8 -0
- package/.medusa/server/src/workflows/steps/create-contact-request-step.js +13 -0
- package/.medusa/server/src/workflows/steps/resolve-status-transition-step.js +15 -0
- package/.medusa/server/src/workflows/steps/send-status-notification-step.js +56 -0
- package/.medusa/server/src/workflows/steps/update-contact-request-status-step.js +17 -0
- package/.medusa/server/src/workflows/update-contact-request-status-workflow.js +33 -0
- package/README.md +376 -5
- package/package.json +2 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveContactUsOptions = resolveContactUsOptions;
|
|
4
|
+
exports.getNextAllowedStatuses = getNextAllowedStatuses;
|
|
5
|
+
exports.getStatusTransition = getStatusTransition;
|
|
6
|
+
const DEFAULT_OPTIONS = {
|
|
7
|
+
default_status: "pending",
|
|
8
|
+
payload_fields: [],
|
|
9
|
+
allowed_statuses: ["pending", "in_progress", "resolved", "closed"],
|
|
10
|
+
status_transitions: [
|
|
11
|
+
{ from: null, to: "pending", send_email: false },
|
|
12
|
+
{ from: "pending", to: "in_progress", send_email: true },
|
|
13
|
+
{ from: "in_progress", to: "resolved", send_email: true },
|
|
14
|
+
{ from: "resolved", to: "closed", send_email: false },
|
|
15
|
+
],
|
|
16
|
+
email: {
|
|
17
|
+
enabled: true,
|
|
18
|
+
default_subject: "Contact Request Status Update",
|
|
19
|
+
default_template: null,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
function resolveContactUsOptions(options) {
|
|
23
|
+
if (!options) {
|
|
24
|
+
return DEFAULT_OPTIONS;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
default_status: options.default_status ?? DEFAULT_OPTIONS.default_status,
|
|
28
|
+
payload_fields: options.payload_fields ?? DEFAULT_OPTIONS.payload_fields,
|
|
29
|
+
allowed_statuses: options.allowed_statuses ?? DEFAULT_OPTIONS.allowed_statuses,
|
|
30
|
+
status_transitions: options.status_transitions ?? DEFAULT_OPTIONS.status_transitions,
|
|
31
|
+
email: {
|
|
32
|
+
enabled: options.email?.enabled ?? DEFAULT_OPTIONS.email.enabled,
|
|
33
|
+
default_subject: options.email?.default_subject ??
|
|
34
|
+
DEFAULT_OPTIONS.email.default_subject,
|
|
35
|
+
default_template: options.email?.default_template ??
|
|
36
|
+
DEFAULT_OPTIONS.email.default_template,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function getNextAllowedStatuses(currentStatus, transitions) {
|
|
41
|
+
return transitions
|
|
42
|
+
.filter((transition) => transition.from === currentStatus)
|
|
43
|
+
.map((transition) => transition.to);
|
|
44
|
+
}
|
|
45
|
+
function getStatusTransition(from, to, transitions) {
|
|
46
|
+
return transitions.find((transition) => transition.from === from && transition.to === to);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1vcHRpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvY29udGFjdC1yZXF1ZXN0cy91dGlscy9yZXNvbHZlLW9wdGlvbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFtQ0EsMERBd0JDO0FBRUQsd0RBT0M7QUFFRCxrREFRQztBQTVERCxNQUFNLGVBQWUsR0FBNkI7SUFDaEQsY0FBYyxFQUFFLFNBQVM7SUFDekIsY0FBYyxFQUFFLEVBQUU7SUFDbEIsZ0JBQWdCLEVBQUUsQ0FBQyxTQUFTLEVBQUUsYUFBYSxFQUFFLFVBQVUsRUFBRSxRQUFRLENBQUM7SUFDbEUsa0JBQWtCLEVBQUU7UUFDbEIsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEVBQUUsRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtRQUNoRCxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsRUFBRSxFQUFFLGFBQWEsRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFO1FBQ3hELEVBQUUsSUFBSSxFQUFFLGFBQWEsRUFBRSxFQUFFLEVBQUUsVUFBVSxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUU7UUFDekQsRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLEVBQUUsRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRTtLQUN0RDtJQUNELEtBQUssRUFBRTtRQUNMLE9BQU8sRUFBRSxJQUFJO1FBQ2IsZUFBZSxFQUFFLCtCQUErQjtRQUNoRCxnQkFBZ0IsRUFBRSxJQUFJO0tBQ3ZCO0NBQ0YsQ0FBQTtBQUVELFNBQWdCLHVCQUF1QixDQUNyQyxPQUF1QztJQUV2QyxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDYixPQUFPLGVBQWUsQ0FBQTtJQUN4QixDQUFDO0lBRUQsT0FBTztRQUNMLGNBQWMsRUFBRSxPQUFPLENBQUMsY0FBYyxJQUFJLGVBQWUsQ0FBQyxjQUFjO1FBQ3hFLGNBQWMsRUFBRSxPQUFPLENBQUMsY0FBYyxJQUFJLGVBQWUsQ0FBQyxjQUFjO1FBQ3hFLGdCQUFnQixFQUNkLE9BQU8sQ0FBQyxnQkFBZ0IsSUFBSSxlQUFlLENBQUMsZ0JBQWdCO1FBQzlELGtCQUFrQixFQUNoQixPQUFPLENBQUMsa0JBQWtCLElBQUksZUFBZSxDQUFDLGtCQUFrQjtRQUNsRSxLQUFLLEVBQUU7WUFDTCxPQUFPLEVBQUUsT0FBTyxDQUFDLEtBQUssRUFBRSxPQUFPLElBQUksZUFBZSxDQUFDLEtBQUssQ0FBQyxPQUFPO1lBQ2hFLGVBQWUsRUFDYixPQUFPLENBQUMsS0FBSyxFQUFFLGVBQWU7Z0JBQzlCLGVBQWUsQ0FBQyxLQUFLLENBQUMsZUFBZTtZQUN2QyxnQkFBZ0IsRUFDZCxPQUFPLENBQUMsS0FBSyxFQUFFLGdCQUFnQjtnQkFDL0IsZUFBZSxDQUFDLEtBQUssQ0FBQyxnQkFBZ0I7U0FDekM7S0FDRixDQUFBO0FBQ0gsQ0FBQztBQUVELFNBQWdCLHNCQUFzQixDQUNwQyxhQUFxQixFQUNyQixXQUFxQztJQUVyQyxPQUFPLFdBQVc7U0FDZixNQUFNLENBQUMsQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEtBQUssYUFBYSxDQUFDO1NBQ3pELEdBQUcsQ0FBQyxDQUFDLFVBQVUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQyxDQUFBO0FBQ3ZDLENBQUM7QUFFRCxTQUFnQixtQkFBbUIsQ0FDakMsSUFBbUIsRUFDbkIsRUFBVSxFQUNWLFdBQXFDO0lBRXJDLE9BQU8sV0FBVyxDQUFDLElBQUksQ0FDckIsQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEtBQUssSUFBSSxJQUFJLFVBQVUsQ0FBQyxFQUFFLEtBQUssRUFBRSxDQUNqRSxDQUFBO0FBQ0gsQ0FBQyJ9
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGFjdC1yZXF1ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3R5cGVzL2NvbnRhY3QtcmVxdWVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createContactRequestWorkflow = void 0;
|
|
4
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
5
|
+
const create_contact_request_step_1 = require("./steps/create-contact-request-step");
|
|
6
|
+
exports.createContactRequestWorkflow = (0, workflows_sdk_1.createWorkflow)("create-contact-request", (input) => {
|
|
7
|
+
const { request } = (0, create_contact_request_step_1.createContactRequestStep)(input);
|
|
8
|
+
return new workflows_sdk_1.WorkflowResponse({
|
|
9
|
+
request,
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
exports.default = exports.createContactRequestWorkflow;
|
|
13
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLWNvbnRhY3QtcmVxdWVzdC13b3JrZmxvdy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy93b3JrZmxvd3MvY3JlYXRlLWNvbnRhY3QtcmVxdWVzdC13b3JrZmxvdy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxRUFHMEM7QUFDMUMscUZBQThFO0FBWWpFLFFBQUEsNEJBQTRCLEdBQUcsSUFBQSw4QkFBYyxFQUN4RCx3QkFBd0IsRUFDeEIsQ0FDRSxLQUF3QyxFQUNjLEVBQUU7SUFDeEQsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLElBQUEsc0RBQXdCLEVBQUMsS0FBSyxDQUFDLENBQUE7SUFFbkQsT0FBTyxJQUFJLGdDQUFnQixDQUFDO1FBQzFCLE9BQU87S0FDUixDQUFDLENBQUE7QUFDSixDQUFDLENBQ0YsQ0FBQTtBQUVELGtCQUFlLG9DQUE0QixDQUFBIn0=
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateContactRequestStatusWorkflow = exports.createContactRequestWorkflow = void 0;
|
|
4
|
+
var create_contact_request_workflow_1 = require("./create-contact-request-workflow");
|
|
5
|
+
Object.defineProperty(exports, "createContactRequestWorkflow", { enumerable: true, get: function () { return create_contact_request_workflow_1.createContactRequestWorkflow; } });
|
|
6
|
+
var update_contact_request_status_workflow_1 = require("./update-contact-request-status-workflow");
|
|
7
|
+
Object.defineProperty(exports, "updateContactRequestStatusWorkflow", { enumerable: true, get: function () { return update_contact_request_status_workflow_1.updateContactRequestStatusWorkflow; } });
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFGQUFnRjtBQUF2RSwrSUFBQSw0QkFBNEIsT0FBQTtBQUNyQyxtR0FBNkY7QUFBcEYsNEpBQUEsa0NBQWtDLE9BQUEifQ==
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createContactRequestStep = void 0;
|
|
4
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
5
|
+
const contact_requests_1 = require("../../modules/contact-requests");
|
|
6
|
+
exports.createContactRequestStep = (0, workflows_sdk_1.createStep)("create-contact-request", async (input, { container }) => {
|
|
7
|
+
const service = container.resolve(contact_requests_1.CONTACT_REQUEST_MODULE);
|
|
8
|
+
const request = await service.createRequest(input);
|
|
9
|
+
return new workflows_sdk_1.StepResponse({
|
|
10
|
+
request,
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLWNvbnRhY3QtcmVxdWVzdC1zdGVwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL3dvcmtmbG93cy9zdGVwcy9jcmVhdGUtY29udGFjdC1yZXF1ZXN0LXN0ZXAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEscUVBQTRFO0FBQzVFLHFFQUF1RTtBQVcxRCxRQUFBLHdCQUF3QixHQUFHLElBQUEsMEJBQVUsRUFDaEQsd0JBQXdCLEVBQ3hCLEtBQUssRUFDSCxLQUFnQyxFQUNoQyxFQUFFLFNBQVMsRUFBRSxFQUMwQyxFQUFFO0lBQ3pELE1BQU0sT0FBTyxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQy9CLHlDQUFzQixDQUN2QixDQUFBO0lBRUQsTUFBTSxPQUFPLEdBQUcsTUFBTSxPQUFPLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxDQUFBO0lBRWxELE9BQU8sSUFBSSw0QkFBWSxDQUFpQztRQUN0RCxPQUFPO0tBQ1IsQ0FBQyxDQUFBO0FBQ0osQ0FBQyxDQUNGLENBQUEifQ==
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveStatusTransitionStep = void 0;
|
|
4
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
5
|
+
const contact_requests_1 = require("../../modules/contact-requests");
|
|
6
|
+
exports.resolveStatusTransitionStep = (0, workflows_sdk_1.createStep)("resolve-status-transition", async (input, { container }) => {
|
|
7
|
+
const service = container.resolve(contact_requests_1.CONTACT_REQUEST_MODULE);
|
|
8
|
+
const options = service.getOptions();
|
|
9
|
+
const transition = service.getStatusTransition(input.fromStatus, input.toStatus);
|
|
10
|
+
return new workflows_sdk_1.StepResponse({
|
|
11
|
+
transition,
|
|
12
|
+
options,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS1zdGF0dXMtdHJhbnNpdGlvbi1zdGVwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL3dvcmtmbG93cy9zdGVwcy9yZXNvbHZlLXN0YXR1cy10cmFuc2l0aW9uLXN0ZXAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEscUVBQTRFO0FBQzVFLHFFQUF1RTtBQWUxRCxRQUFBLDJCQUEyQixHQUFHLElBQUEsMEJBQVUsRUFDbkQsMkJBQTJCLEVBQzNCLEtBQUssRUFDSCxLQUFtQyxFQUNuQyxFQUFFLFNBQVMsRUFBRSxFQUN5QyxFQUFFO0lBQ3hELE1BQU0sT0FBTyxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQy9CLHlDQUFzQixDQUN2QixDQUFBO0lBRUQsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLFVBQVUsRUFBRSxDQUFBO0lBQ3BDLE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsQ0FDNUMsS0FBSyxDQUFDLFVBQVUsRUFDaEIsS0FBSyxDQUFDLFFBQVEsQ0FDZixDQUFBO0lBRUQsT0FBTyxJQUFJLDRCQUFZLENBQWdDO1FBQ3JELFVBQVU7UUFDVixPQUFPO0tBQ1IsQ0FBQyxDQUFBO0FBQ0osQ0FBQyxDQUNGLENBQUEifQ==
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendStatusNotificationStep = void 0;
|
|
4
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
5
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
6
|
+
/**
|
|
7
|
+
* Prepare email payload for notification service
|
|
8
|
+
*/
|
|
9
|
+
function prepareEmailPayload(to, subject, template, contactRequest) {
|
|
10
|
+
const payload = {
|
|
11
|
+
to,
|
|
12
|
+
channel: "email",
|
|
13
|
+
data: {
|
|
14
|
+
subject,
|
|
15
|
+
contactRequest,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
if (template) {
|
|
19
|
+
payload.template = template;
|
|
20
|
+
}
|
|
21
|
+
return payload;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Send email notification for status change
|
|
25
|
+
*/
|
|
26
|
+
exports.sendStatusNotificationStep = (0, workflows_sdk_1.createStep)("send-status-notification", async (input, { container }) => {
|
|
27
|
+
// If no email subject provided, skip sending
|
|
28
|
+
if (!input.emailSubject) {
|
|
29
|
+
return new workflows_sdk_1.StepResponse({
|
|
30
|
+
sent: false,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const payload = prepareEmailPayload(input.contactRequest.email, input.emailSubject, input.emailTemplate ?? null, input.contactRequest);
|
|
34
|
+
const notificationService = container.resolve(utils_1.Modules.NOTIFICATION);
|
|
35
|
+
if (!notificationService) {
|
|
36
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Notification service is not configured.");
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
if (typeof notificationService.create === "function") {
|
|
40
|
+
await notificationService.create(payload);
|
|
41
|
+
}
|
|
42
|
+
else if (typeof notificationService.createNotifications === "function") {
|
|
43
|
+
await notificationService.createNotifications([payload]);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, "Notification service does not support sending notifications.");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Failed to send notification: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
return new workflows_sdk_1.StepResponse({
|
|
53
|
+
sent: true,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VuZC1zdGF0dXMtbm90aWZpY2F0aW9uLXN0ZXAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL3N0ZXBzL3NlbmQtc3RhdHVzLW5vdGlmaWNhdGlvbi1zdGVwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFFQUE0RTtBQUM1RSxxREFBZ0U7QUFlaEU7O0dBRUc7QUFDSCxTQUFTLG1CQUFtQixDQUMxQixFQUFVLEVBQ1YsT0FBZSxFQUNmLFFBQXVCLEVBQ3ZCLGNBQWlDO0lBRWpDLE1BQU0sT0FBTyxHQVFUO1FBQ0YsRUFBRTtRQUNGLE9BQU8sRUFBRSxPQUFPO1FBQ2hCLElBQUksRUFBRTtZQUNKLE9BQU87WUFDUCxjQUFjO1NBQ2Y7S0FDRixDQUFBO0lBRUQsSUFBSSxRQUFRLEVBQUUsQ0FBQztRQUNiLE9BQU8sQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFBO0lBQzdCLENBQUM7SUFFRCxPQUFPLE9BQU8sQ0FBQTtBQUNoQixDQUFDO0FBRUQ7O0dBRUc7QUFDVSxRQUFBLDBCQUEwQixHQUFHLElBQUEsMEJBQVUsRUFDbEQsMEJBQTBCLEVBQzFCLEtBQUssRUFBRSxLQUFrQyxFQUFFLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRTtJQUMxRCw2Q0FBNkM7SUFDN0MsSUFBSSxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQUUsQ0FBQztRQUN4QixPQUFPLElBQUksNEJBQVksQ0FBK0I7WUFDcEQsSUFBSSxFQUFFLEtBQUs7U0FDWixDQUFDLENBQUE7SUFDSixDQUFDO0lBRUQsTUFBTSxPQUFPLEdBQUcsbUJBQW1CLENBQ2pDLEtBQUssQ0FBQyxjQUFjLENBQUMsS0FBSyxFQUMxQixLQUFLLENBQUMsWUFBWSxFQUNsQixLQUFLLENBQUMsYUFBYSxJQUFJLElBQUksRUFDM0IsS0FBSyxDQUFDLGNBQWMsQ0FDckIsQ0FBQTtJQUVELE1BQU0sbUJBQW1CLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxlQUFPLENBQUMsWUFBWSxDQUdqRSxDQUFBO0lBRUQsSUFBSSxDQUFDLG1CQUFtQixFQUFFLENBQUM7UUFDekIsTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLFlBQVksRUFDOUIseUNBQXlDLENBQzFDLENBQUE7SUFDSCxDQUFDO0lBRUQsSUFBSSxDQUFDO1FBQ0gsSUFBSSxPQUFPLG1CQUFtQixDQUFDLE1BQU0sS0FBSyxVQUFVLEVBQUUsQ0FBQztZQUNyRCxNQUFNLG1CQUFtQixDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQTtRQUMzQyxDQUFDO2FBQU0sSUFBSSxPQUFPLG1CQUFtQixDQUFDLG1CQUFtQixLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQ3pFLE1BQU0sbUJBQW1CLENBQUMsbUJBQW1CLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFBO1FBQzFELENBQUM7YUFBTSxDQUFDO1lBQ04sTUFBTSxJQUFJLG1CQUFXLENBQ25CLG1CQUFXLENBQUMsS0FBSyxDQUFDLGdCQUFnQixFQUNsQyw4REFBOEQsQ0FDL0QsQ0FBQTtRQUNILENBQUM7SUFDSCxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsRUFDbEMsZ0NBQWdDLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUN6RixDQUFBO0lBQ0gsQ0FBQztJQUVELE9BQU8sSUFBSSw0QkFBWSxDQUErQjtRQUNwRCxJQUFJLEVBQUUsSUFBSTtLQUNYLENBQUMsQ0FBQTtBQUNKLENBQUMsQ0FDRixDQUFBIn0=
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateContactRequestStatusStep = void 0;
|
|
4
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
5
|
+
const contact_requests_1 = require("../../modules/contact-requests");
|
|
6
|
+
exports.updateContactRequestStatusStep = (0, workflows_sdk_1.createStep)("update-contact-request-status", async (input, { container }) => {
|
|
7
|
+
const service = container.resolve(contact_requests_1.CONTACT_REQUEST_MODULE);
|
|
8
|
+
// Get current request to capture previous status
|
|
9
|
+
const currentRequest = await service.getRequest(input.id);
|
|
10
|
+
const previousStatus = currentRequest.status;
|
|
11
|
+
const request = await service.updateStatus(input);
|
|
12
|
+
return new workflows_sdk_1.StepResponse({
|
|
13
|
+
request,
|
|
14
|
+
previousStatus,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBkYXRlLWNvbnRhY3QtcmVxdWVzdC1zdGF0dXMtc3RlcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy93b3JrZmxvd3Mvc3RlcHMvdXBkYXRlLWNvbnRhY3QtcmVxdWVzdC1zdGF0dXMtc3RlcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxxRUFBNEU7QUFDNUUscUVBQXVFO0FBWTFELFFBQUEsOEJBQThCLEdBQUcsSUFBQSwwQkFBVSxFQUN0RCwrQkFBK0IsRUFDL0IsS0FBSyxFQUNILEtBQXNDLEVBQ3RDLEVBQUUsU0FBUyxFQUFFLEVBQ2dELEVBQUU7SUFDL0QsTUFBTSxPQUFPLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FDL0IseUNBQXNCLENBQ3ZCLENBQUE7SUFFRCxpREFBaUQ7SUFDakQsTUFBTSxjQUFjLEdBQUcsTUFBTSxPQUFPLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQTtJQUN6RCxNQUFNLGNBQWMsR0FBRyxjQUFjLENBQUMsTUFBTSxDQUFBO0lBRTVDLE1BQU0sT0FBTyxHQUFHLE1BQU0sT0FBTyxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQTtJQUVqRCxPQUFPLElBQUksNEJBQVksQ0FBdUM7UUFDNUQsT0FBTztRQUNQLGNBQWM7S0FDZixDQUFDLENBQUE7QUFDSixDQUFDLENBQ0YsQ0FBQSJ9
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateContactRequestStatusWorkflow = void 0;
|
|
4
|
+
const workflows_sdk_1 = require("@medusajs/framework/workflows-sdk");
|
|
5
|
+
const update_contact_request_status_step_1 = require("./steps/update-contact-request-status-step");
|
|
6
|
+
const resolve_status_transition_step_1 = require("./steps/resolve-status-transition-step");
|
|
7
|
+
const send_status_notification_step_1 = require("./steps/send-status-notification-step");
|
|
8
|
+
exports.updateContactRequestStatusWorkflow = (0, workflows_sdk_1.createWorkflow)("update-contact-request-status", (input) => {
|
|
9
|
+
// Step 1: Update status (validation happens inside the step)
|
|
10
|
+
const { request, previousStatus } = (0, update_contact_request_status_step_1.updateContactRequestStatusStep)(input);
|
|
11
|
+
// Step 2: Resolve transition config for email notification
|
|
12
|
+
const { transition, options } = (0, resolve_status_transition_step_1.resolveStatusTransitionStep)({
|
|
13
|
+
fromStatus: previousStatus,
|
|
14
|
+
toStatus: input.status,
|
|
15
|
+
});
|
|
16
|
+
// Step 3: Send notification if configured
|
|
17
|
+
(0, send_status_notification_step_1.sendStatusNotificationStep)({
|
|
18
|
+
contactRequest: request,
|
|
19
|
+
fromStatus: previousStatus,
|
|
20
|
+
toStatus: input.status,
|
|
21
|
+
emailSubject: transition?.send_email && options.email.enabled
|
|
22
|
+
? transition.email_subject ?? options.email.default_subject
|
|
23
|
+
: undefined,
|
|
24
|
+
emailTemplate: transition?.send_email && options.email.enabled
|
|
25
|
+
? transition.email_template ?? options.email.default_template
|
|
26
|
+
: undefined,
|
|
27
|
+
});
|
|
28
|
+
return new workflows_sdk_1.WorkflowResponse({
|
|
29
|
+
request,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
exports.default = exports.updateContactRequestStatusWorkflow;
|
|
33
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBkYXRlLWNvbnRhY3QtcmVxdWVzdC1zdGF0dXMtd29ya2Zsb3cuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvd29ya2Zsb3dzL3VwZGF0ZS1jb250YWN0LXJlcXVlc3Qtc3RhdHVzLXdvcmtmbG93LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFFQUcwQztBQUMxQyxtR0FBMkY7QUFDM0YsMkZBQW9GO0FBQ3BGLHlGQUFrRjtBQWFyRSxRQUFBLGtDQUFrQyxHQUFHLElBQUEsOEJBQWMsRUFDOUQsK0JBQStCLEVBQy9CLENBQ0UsS0FBOEMsRUFDYyxFQUFFO0lBQzlELDZEQUE2RDtJQUM3RCxNQUFNLEVBQUUsT0FBTyxFQUFFLGNBQWMsRUFBRSxHQUFHLElBQUEsbUVBQThCLEVBQUMsS0FBSyxDQUFDLENBQUE7SUFFekUsMkRBQTJEO0lBQzNELE1BQU0sRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLEdBQUcsSUFBQSw0REFBMkIsRUFBQztRQUMxRCxVQUFVLEVBQUUsY0FBYztRQUMxQixRQUFRLEVBQUUsS0FBSyxDQUFDLE1BQU07S0FDdkIsQ0FBQyxDQUFBO0lBRUYsMENBQTBDO0lBQzFDLElBQUEsMERBQTBCLEVBQUM7UUFDekIsY0FBYyxFQUFFLE9BQU87UUFDdkIsVUFBVSxFQUFFLGNBQWM7UUFDMUIsUUFBUSxFQUFFLEtBQUssQ0FBQyxNQUFNO1FBQ3RCLFlBQVksRUFDVixVQUFVLEVBQUUsVUFBVSxJQUFJLE9BQU8sQ0FBQyxLQUFLLENBQUMsT0FBTztZQUM3QyxDQUFDLENBQUMsVUFBVSxDQUFDLGFBQWEsSUFBSSxPQUFPLENBQUMsS0FBSyxDQUFDLGVBQWU7WUFDM0QsQ0FBQyxDQUFDLFNBQVM7UUFDZixhQUFhLEVBQ1gsVUFBVSxFQUFFLFVBQVUsSUFBSSxPQUFPLENBQUMsS0FBSyxDQUFDLE9BQU87WUFDN0MsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxjQUFjLElBQUksT0FBTyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0I7WUFDN0QsQ0FBQyxDQUFDLFNBQVM7S0FDaEIsQ0FBQyxDQUFBO0lBRUYsT0FBTyxJQUFJLGdDQUFnQixDQUFDO1FBQzFCLE9BQU87S0FDUixDQUFDLENBQUE7QUFDSixDQUFDLENBQ0YsQ0FBQTtBQUVELGtCQUFlLDBDQUFrQyxDQUFBIn0=
|
package/README.md
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
<h1 align="center">Medusa Contact
|
|
1
|
+
<h1 align="center">Medusa Contact Us Plugin</h1>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A comprehensive Medusa v2 plugin for managing contact requests and email subscriptions. The plugin provides a complete solution: database-backed contact requests with configurable status workflows, email subscriptions, admin APIs, helper utilities, and a polished admin UI.
|
|
4
|
+
|
|
5
|
+
**Features:**
|
|
6
|
+
- 📝 Contact request management with status workflow and email notifications
|
|
7
|
+
- ✉️ Email subscription management (opt-ins and opt-outs)
|
|
8
|
+
- 🔄 Configurable status transitions and validation
|
|
9
|
+
- 📧 Automatic email notifications on status changes
|
|
10
|
+
- 🎯 Payload validation against configurable field definitions
|
|
4
11
|
|
|
5
12
|
## Features
|
|
6
13
|
|
|
14
|
+
### Email Subscriptions
|
|
7
15
|
- ✉️ Storefront opt-in helper + admin list for email subscriptions (subscribed/unsubscribed)
|
|
8
16
|
- 🖥️ React Admin extension with list view, filters, and search
|
|
9
17
|
- 🔌 Frontend helper (`upsertContactSubscription`) to abstract API wiring
|
|
10
18
|
- 🧪 Table-driven tests for helper logic
|
|
11
19
|
|
|
20
|
+
### Contact Requests
|
|
21
|
+
- 📝 Contact request management with configurable status workflow
|
|
22
|
+
- 🔄 Status transition validation with configurable allowed transitions
|
|
23
|
+
- 📧 Email notifications on status changes (configurable per transition)
|
|
24
|
+
- 🎯 Payload validation against configurable field definitions
|
|
25
|
+
- 📊 Admin API for listing, filtering, and managing requests
|
|
26
|
+
- 🔌 Storefront helper (`submitContactRequest`) for easy integration
|
|
27
|
+
- 📈 Status history tracking with timestamps and actor information
|
|
28
|
+
|
|
12
29
|
## Installation
|
|
13
30
|
|
|
14
31
|
```bash
|
|
@@ -25,16 +42,79 @@ Inside `medusa-config.ts` register the plugin:
|
|
|
25
42
|
import type { ConfigModule } from "@medusajs/framework/types"
|
|
26
43
|
import {
|
|
27
44
|
ContactSubscriptionModule,
|
|
45
|
+
ContactRequestModule,
|
|
28
46
|
} from "medusa-contact-us"
|
|
29
47
|
|
|
30
48
|
const plugins = [
|
|
31
49
|
{
|
|
32
50
|
resolve: "medusa-contact-us",
|
|
51
|
+
options: {
|
|
52
|
+
// Contact Request Configuration
|
|
53
|
+
default_status: "pending",
|
|
54
|
+
payload_fields: [
|
|
55
|
+
{
|
|
56
|
+
key: "subject",
|
|
57
|
+
type: "text",
|
|
58
|
+
required: true,
|
|
59
|
+
label: "Subject",
|
|
60
|
+
placeholder: "Enter subject",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: "message",
|
|
64
|
+
type: "textarea",
|
|
65
|
+
required: true,
|
|
66
|
+
label: "Message",
|
|
67
|
+
placeholder: "Enter your message",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: "priority",
|
|
71
|
+
type: "select",
|
|
72
|
+
required: false,
|
|
73
|
+
label: "Priority",
|
|
74
|
+
options: [
|
|
75
|
+
{ value: "low", label: "Low" },
|
|
76
|
+
{ value: "medium", label: "Medium" },
|
|
77
|
+
{ value: "high", label: "High" },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
allowed_statuses: ["pending", "in_progress", "resolved", "closed"],
|
|
82
|
+
status_transitions: [
|
|
83
|
+
{
|
|
84
|
+
from: null,
|
|
85
|
+
to: "pending",
|
|
86
|
+
send_email: false,
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
from: "pending",
|
|
90
|
+
to: "in_progress",
|
|
91
|
+
send_email: true,
|
|
92
|
+
email_subject: "Your request is being processed",
|
|
93
|
+
email_template: null, // Optional: path to email template
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
from: "in_progress",
|
|
97
|
+
to: "resolved",
|
|
98
|
+
send_email: true,
|
|
99
|
+
email_subject: "Your request has been resolved",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
from: "resolved",
|
|
103
|
+
to: "closed",
|
|
104
|
+
send_email: false,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
email: {
|
|
108
|
+
enabled: true,
|
|
109
|
+
default_subject: "Contact Request Status Update",
|
|
110
|
+
default_template: null, // Optional: default email template path
|
|
111
|
+
},
|
|
112
|
+
},
|
|
33
113
|
},
|
|
34
114
|
]
|
|
35
115
|
|
|
36
|
-
// Register the
|
|
37
|
-
const modules = [ContactSubscriptionModule]
|
|
116
|
+
// Register the modules
|
|
117
|
+
const modules = [ContactSubscriptionModule, ContactRequestModule]
|
|
38
118
|
|
|
39
119
|
export default {
|
|
40
120
|
projectConfig: {
|
|
@@ -47,10 +127,91 @@ export default {
|
|
|
47
127
|
} satisfies ConfigModule
|
|
48
128
|
```
|
|
49
129
|
|
|
130
|
+
### Configuration Options
|
|
131
|
+
|
|
132
|
+
#### Contact Request Options
|
|
133
|
+
|
|
134
|
+
- **`default_status`** (string, optional): Default status for new requests. Default: `"pending"`
|
|
135
|
+
- **`payload_fields`** (array, optional): Field definitions for payload validation. Each field supports:
|
|
136
|
+
- `key` (string, required): Field identifier
|
|
137
|
+
- `type` (string, required): Field type - `"text"`, `"textarea"`, `"number"`, `"email"`, `"select"`, `"checkbox"`
|
|
138
|
+
- `required` (boolean, optional): Whether field is required
|
|
139
|
+
- `label` (string, optional): Display label
|
|
140
|
+
- `placeholder` (string, optional): Placeholder text
|
|
141
|
+
- `options` (array, optional): For select type - `[{ value: string, label: string }]`
|
|
142
|
+
- `validation` (object, optional): Validation rules (min, max, pattern)
|
|
143
|
+
- **`allowed_statuses`** (array, optional): List of allowed status values. Default: `["pending", "in_progress", "resolved", "closed"]`
|
|
144
|
+
- **`status_transitions`** (array, optional): Allowed status transitions. Each transition supports:
|
|
145
|
+
- `from` (string | null): Source status (null = initial status)
|
|
146
|
+
- `to` (string, required): Target status
|
|
147
|
+
- `send_email` (boolean, optional): Whether to send email on this transition
|
|
148
|
+
- `email_subject` (string, optional): Custom email subject for this transition
|
|
149
|
+
- `email_template` (string | null, optional): Custom email template path
|
|
150
|
+
- **`email`** (object, optional): Email notification settings
|
|
151
|
+
- `enabled` (boolean, optional): Enable/disable email notifications. Default: `true`
|
|
152
|
+
- `default_subject` (string, optional): Default email subject. Default: `"Contact Request Status Update"`
|
|
153
|
+
- `default_template` (string | null, optional): Default email template path
|
|
154
|
+
|
|
50
155
|
## REST API
|
|
51
156
|
|
|
52
157
|
### Storefront (open)
|
|
53
158
|
|
|
159
|
+
#### Contact Requests
|
|
160
|
+
|
|
161
|
+
Create a contact request:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
curl -X POST https://your-medusa.com/store/contact-requests \
|
|
165
|
+
-H "Content-Type: application/json" \
|
|
166
|
+
-H "x-publishable-api-key: pk_storefront" \
|
|
167
|
+
-d '{
|
|
168
|
+
"email": "customer@example.com",
|
|
169
|
+
"payload": {
|
|
170
|
+
"subject": "Order inquiry",
|
|
171
|
+
"message": "I need help with my order #12345"
|
|
172
|
+
},
|
|
173
|
+
"metadata": {
|
|
174
|
+
"source_page": "contact"
|
|
175
|
+
},
|
|
176
|
+
"source": "storefront"
|
|
177
|
+
}'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Body fields:
|
|
181
|
+
- `email` – required, valid email address
|
|
182
|
+
- `payload` – optional, JSON object with custom fields (validated against configured `payload_fields`)
|
|
183
|
+
- `metadata` – optional, additional metadata
|
|
184
|
+
- `source` – optional, request source identifier (default: `"storefront"`)
|
|
185
|
+
|
|
186
|
+
Response:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"request": {
|
|
191
|
+
"id": "creq_123",
|
|
192
|
+
"email": "customer@example.com",
|
|
193
|
+
"payload": {
|
|
194
|
+
"subject": "Order inquiry",
|
|
195
|
+
"message": "I need help with my order #12345"
|
|
196
|
+
},
|
|
197
|
+
"status": "pending",
|
|
198
|
+
"status_history": [
|
|
199
|
+
{
|
|
200
|
+
"from": null,
|
|
201
|
+
"to": "pending",
|
|
202
|
+
"changed_at": "2024-11-29T16:33:17.000Z"
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
"metadata": {
|
|
206
|
+
"source_page": "contact"
|
|
207
|
+
},
|
|
208
|
+
"source": "storefront",
|
|
209
|
+
"created_at": "2024-11-29T16:33:17.000Z",
|
|
210
|
+
"updated_at": "2024-11-29T16:33:17.000Z"
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
54
215
|
#### Email subscriptions
|
|
55
216
|
|
|
56
217
|
```bash
|
|
@@ -85,6 +246,83 @@ Response:
|
|
|
85
246
|
|
|
86
247
|
### Admin (requires admin auth cookie/token)
|
|
87
248
|
|
|
249
|
+
#### Contact Requests
|
|
250
|
+
|
|
251
|
+
List contact requests:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
curl -X GET "https://your-medusa.com/admin/contact-requests?status=pending&email=customer@example.com&limit=20&offset=0" \
|
|
255
|
+
-H "Authorization: Bearer <token>"
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Query parameters:
|
|
259
|
+
- `email` – optional, filter by email (partial match)
|
|
260
|
+
- `status` – optional, filter by status
|
|
261
|
+
- `source` – optional, filter by source
|
|
262
|
+
- `created_at.gte` – optional, filter by creation date (ISO 8601)
|
|
263
|
+
- `created_at.lte` – optional, filter by creation date (ISO 8601)
|
|
264
|
+
- `limit` – optional, number of results (default: 20, max: 100)
|
|
265
|
+
- `offset` – optional, pagination offset (default: 0)
|
|
266
|
+
- `order` – optional, sort field: `created_at`, `updated_at`, `email` (default: `created_at`)
|
|
267
|
+
- `order_direction` – optional, sort direction: `ASC` or `DESC` (default: `DESC`)
|
|
268
|
+
|
|
269
|
+
Get contact request details:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
curl -X GET "https://your-medusa.com/admin/contact-requests/creq_123" \
|
|
273
|
+
-H "Authorization: Bearer <token>"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Response includes the request and `next_allowed_statuses` array for the admin UI:
|
|
277
|
+
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"request": {
|
|
281
|
+
"id": "creq_123",
|
|
282
|
+
"email": "customer@example.com",
|
|
283
|
+
"payload": { ... },
|
|
284
|
+
"status": "pending",
|
|
285
|
+
"status_history": [ ... ],
|
|
286
|
+
"metadata": { ... },
|
|
287
|
+
"source": "storefront",
|
|
288
|
+
"created_at": "2024-11-29T16:33:17.000Z",
|
|
289
|
+
"updated_at": "2024-11-29T16:33:17.000Z"
|
|
290
|
+
},
|
|
291
|
+
"next_allowed_statuses": ["in_progress"]
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Create contact request (admin):
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
curl -X POST https://your-medusa.com/admin/contact-requests \
|
|
299
|
+
-H "Content-Type: application/json" \
|
|
300
|
+
-H "Authorization: Bearer <token>" \
|
|
301
|
+
-d '{
|
|
302
|
+
"email": "customer@example.com",
|
|
303
|
+
"payload": {
|
|
304
|
+
"subject": "Support request",
|
|
305
|
+
"message": "Need assistance"
|
|
306
|
+
},
|
|
307
|
+
"source": "admin"
|
|
308
|
+
}'
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Update contact request status:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
curl -X POST https://your-medusa.com/admin/contact-requests/creq_123/status \
|
|
315
|
+
-H "Content-Type: application/json" \
|
|
316
|
+
-H "Authorization: Bearer <token>" \
|
|
317
|
+
-d '{
|
|
318
|
+
"status": "in_progress"
|
|
319
|
+
}'
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Note**: Only status transitions defined in `status_transitions` configuration are allowed. The API will return an error if an invalid transition is attempted.
|
|
323
|
+
|
|
324
|
+
#### Email Subscriptions
|
|
325
|
+
|
|
88
326
|
List subscriptions:
|
|
89
327
|
|
|
90
328
|
```bash
|
|
@@ -109,6 +347,84 @@ After running `medusa admin dev --plugins medusa-contact-us`, the sidebar will i
|
|
|
109
347
|
|
|
110
348
|
Skip hand-writing `fetch` calls by importing the provided helpers. **Storefront requests must include a publishable API key** (create one under `Settings → API Keys` in the Medusa admin). The helpers automatically attach the header for you.
|
|
111
349
|
|
|
350
|
+
### Contact Requests
|
|
351
|
+
|
|
352
|
+
Submit a contact request from your storefront:
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { submitContactRequest } from "medusa-contact-us/helpers"
|
|
356
|
+
|
|
357
|
+
const result = await submitContactRequest(
|
|
358
|
+
{
|
|
359
|
+
email: "customer@example.com",
|
|
360
|
+
payload: {
|
|
361
|
+
subject: "Order inquiry",
|
|
362
|
+
message: "I need help with my order #12345",
|
|
363
|
+
priority: "high",
|
|
364
|
+
},
|
|
365
|
+
metadata: {
|
|
366
|
+
source_page: "contact",
|
|
367
|
+
user_agent: navigator.userAgent,
|
|
368
|
+
},
|
|
369
|
+
source: "storefront",
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
baseUrl: "https://store.myshop.com",
|
|
373
|
+
publishableApiKey: "pk_test_storefront",
|
|
374
|
+
}
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
console.log("Request created:", result.request.id)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Using a Medusa JS client:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import Medusa from "@medusajs/medusa-js"
|
|
384
|
+
import { submitContactRequest } from "medusa-contact-us/helpers"
|
|
385
|
+
|
|
386
|
+
const medusa = new Medusa({
|
|
387
|
+
baseUrl: "https://store.myshop.com",
|
|
388
|
+
publishableKey: "pk_live_client",
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
await submitContactRequest(
|
|
392
|
+
{
|
|
393
|
+
email: "customer@example.com",
|
|
394
|
+
payload: {
|
|
395
|
+
subject: "Support request",
|
|
396
|
+
message: "Need assistance",
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
client: medusa,
|
|
401
|
+
publishableApiKey: "pk_live_client",
|
|
402
|
+
}
|
|
403
|
+
)
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
For SSR or edge runtimes, preconfigure the helper:
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import { createSubmitContactRequest } from "medusa-contact-us/helpers"
|
|
410
|
+
|
|
411
|
+
export const submitRequest = createSubmitContactRequest({
|
|
412
|
+
baseUrl: process.env.NEXT_PUBLIC_MEDUSA_URL,
|
|
413
|
+
publishableApiKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
export async function action(formData: FormData) {
|
|
417
|
+
await submitRequest({
|
|
418
|
+
email: formData.get("email") as string,
|
|
419
|
+
payload: {
|
|
420
|
+
subject: formData.get("subject") as string,
|
|
421
|
+
message: formData.get("message") as string,
|
|
422
|
+
},
|
|
423
|
+
source: "contact_form",
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
112
428
|
### Email subscriptions
|
|
113
429
|
|
|
114
430
|
```ts
|
|
@@ -182,6 +498,57 @@ export async function action(formData: FormData) {
|
|
|
182
498
|
|
|
183
499
|
Customer-authenticated endpoints still require the appropriate session cookie or JWT. Provide those via the `headers` option if they're not already managed by the browser fetch call.
|
|
184
500
|
|
|
501
|
+
## Status Workflow
|
|
502
|
+
|
|
503
|
+
Contact requests follow a configurable status workflow:
|
|
504
|
+
|
|
505
|
+
1. **Initial Status**: New requests are created with the `default_status` (typically `"pending"`)
|
|
506
|
+
2. **Status Transitions**: Only transitions defined in `status_transitions` are allowed
|
|
507
|
+
3. **Status History**: All status changes are tracked with timestamps and actor information
|
|
508
|
+
4. **Email Notifications**: Emails can be sent on specific transitions when `send_email: true`
|
|
509
|
+
|
|
510
|
+
### Example Workflow
|
|
511
|
+
|
|
512
|
+
```
|
|
513
|
+
pending → in_progress → resolved → closed
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
In this example:
|
|
517
|
+
- Admin can move requests from `pending` to `in_progress` (email sent)
|
|
518
|
+
- Admin can move requests from `in_progress` to `resolved` (email sent)
|
|
519
|
+
- Admin can move requests from `resolved` to `closed` (no email)
|
|
520
|
+
|
|
521
|
+
### Admin UI Integration
|
|
522
|
+
|
|
523
|
+
When fetching a contact request via the admin API, the response includes `next_allowed_statuses`:
|
|
524
|
+
|
|
525
|
+
```json
|
|
526
|
+
{
|
|
527
|
+
"request": { ... },
|
|
528
|
+
"next_allowed_statuses": ["in_progress"]
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Use this array to populate a status dropdown in your admin UI, ensuring only valid transitions are shown.
|
|
533
|
+
|
|
534
|
+
## Database Migrations
|
|
535
|
+
|
|
536
|
+
After installing the plugin, run migrations to create the required tables:
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
npx medusa db:migrate
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
This will create:
|
|
543
|
+
- `contact_email_subscription` table (for email subscriptions)
|
|
544
|
+
- `contact_request` table (for contact requests)
|
|
545
|
+
|
|
546
|
+
**Note**: If you're developing the plugin locally, generate migrations after model changes:
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
npx medusa plugin:db:generate
|
|
550
|
+
```
|
|
551
|
+
|
|
185
552
|
## Testing & build
|
|
186
553
|
|
|
187
554
|
```bash
|
|
@@ -193,4 +560,8 @@ Always run `yarn build` after development to ensure the bundler succeeds before
|
|
|
193
560
|
|
|
194
561
|
## Troubleshooting
|
|
195
562
|
|
|
196
|
-
- Admin UI blank
|
|
563
|
+
- **Admin UI blank**: Rebuild the plugin (`yarn build`) and restart the Admin app with the plugin registered in `medusa-config.ts`.
|
|
564
|
+
- **Status transition errors**: Ensure the transition is defined in `status_transitions` configuration. Only transitions from the current status to an allowed next status are permitted.
|
|
565
|
+
- **Payload validation errors**: Check that all required fields defined in `payload_fields` are provided and match the expected types.
|
|
566
|
+
- **Email notifications not sending**: Verify that `email.enabled` is `true` in configuration and that the transition has `send_email: true`. Ensure the notification service is properly configured in your Medusa instance.
|
|
567
|
+
- **Service resolution errors**: Make sure both `ContactSubscriptionModule` and `ContactRequestModule` are registered in the `modules` array in `medusa-config.ts`.
|