Fullstak-nl / medusa-plugin-postmark

Postmark notification plugin for MedusaJS
https://fullstak-nl.github.io/medusa-plugin-postmark/
MIT License
12 stars 1 forks source link

Dynamic Configuration, Template Editing #7

Open thoward27 opened 8 months ago

thoward27 commented 8 months ago

I am currently working on a Medusa store and one feature I am building out is in-admin email template editing. I've gotten pretty far with my implementation and am at the point now where a user can create templates, edit them, they are synced to postmark, and properly persisted in the database.

The issue I am having is that this plugin cannot be configured dynamically.

I am hoping to essentially allow users to build the templates for the supported events live in their browsers.

If any collaborators on this project were interested, I'd love to get this all figured out then contribute a PR to add template building to the plugin.

Let me know if there is any interest in this line of work.

breakerh commented 8 months ago

@thoward27 awesome idea! I really like to help with this. We could create a new entity to save all the settings and add an extra settings page to edit them.

At the moment we're working on a lightspeed plugin who also does this.

thoward27 commented 8 months ago

@breakerh I have a PR in progress to start this line of work. The first step I want to take is allowing dynamic configuration of the events object. Unfortunately, I am having a hell of a time getting the medusa-config.js file to import a function I have defined.

If you have any advice I will gladly take it.

My current function definition is


import { IsNull, Not } from "typeorm";
import { dataSource } from "@medusajs/medusa/dist/loaders/database";
import EmailTemplate from "../models/email-template";

export async function getTemplateMappings() {
    const templateRepo = dataSource.getRepository(EmailTemplate);
    const templates = await templateRepo.find({ where: { notification_event: Not(IsNull()) } });
    const mappings = {};
    templates.forEach((template) => {
        const [group, action] = template.notification_event.split(".", 2);
        if (!mappings[group]) {
            mappings[group] = {};
        }
        mappings[group][action] = template;
    });
}

I've tried this style, and the CommonJS style, neither have worked for me.

The imports I have tried have been some variation of

const getTemplateMappings = require("./src/utils/get-template-mappings");

But I have had no luck.

Worst case I think I need to expand the scope of my first PR to include the entity definition. That would allow me to shift the event function into the plugin and setup something like

...
events: "dynamic"

But, I don't like that approach as it would lead to a much larger initial PR....

breakerh commented 8 months ago

@thoward27 I could be wrong but why making it this hard? If we create a simple settings page and save settings to a separate entity all problems are solved, right?

You can collect these settings and don't need to handle any workarounds for the plugin options?

Edit: I'm also working on a lightspeed => medusa sync where the plugins options are moved to a separate settings page. Saving them in the shop metadata (a few simple settings, still in doubt to create a separate entity).. image image

thoward27 commented 8 months ago

@breakerh this approach sounds good, I even think I can embed the settings into the email template editing flow, if I'm careful. But, for the life of me, I am having a very hard time getting a page to render from the current plugin.

I defined this page

// src/admin/routes/email-templates/page.jsx
import { useAdminCustomQuery } from "medusa-react"
import { Envelope } from "@medusajs/icons"

const EmailTemplates = () => {
    const { data, isLoading } = useAdminCustomQuery(
        "/admin/email-templates",
        ["email-templates"],
        {}
    )

    return (
        <div>
            <div className="flex justify-between items-center mb-4">
                <h1 className="text-3xl font-bold">Email Templates</h1>
                <a href="email-templates/new" className="inline-block px-4 py-2 bg-green-500 text-white rounded hover:bg-green-700">Create New Template</a>
            </div>
            {isLoading && <span>Loading...</span>}
            {data?.templates && !data.templates.length && (
                <span className="text-lg text-red-500">No Email Templates Found</span>
            )}
            {data?.templates && data.templates?.length > 0 && (
                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
                    {data.templates.map((template) => (
                        <div key={template.alias} className="border rounded-lg p-4 shadow-sm">
                            <div className="flex justify-between items-center">
                                <h2 className="text-xl font-bold" id={`template-name-${template.alias}`}>{template.name}</h2>
                                <a href={`email-templates/edit/${template.alias}`} aria-labelledby={`template-name-${template.alias}`} className="mt-2 inline-block px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-700">Edit Template</a>
                            </div>
                            <div className="text-sm text-gray-500">
                                <p>Created: {new Date(template.created_at).toLocaleDateString()}</p>
                                <p>Last Updated: {new Date(template.updated_at).toLocaleDateString()}</p>
                            </div>
                        </div>
                    ))}
                </div>
            )}
        </div>
    )
}

export const config = {
    link: {
        label: "Email Templates",
        icon: Envelope,
    },
}

export default EmailTemplates

I updated the babel config to compile the JSX page

{
  "plugins": [
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-transform-instanceof",
    "@babel/plugin-transform-classes"
  ],
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "env": {
    "test": {
      "plugins": ["@babel/plugin-transform-runtime"]
    }
  }
}

And, I confirmed the page itself is being generated, I get admin/routes/email-templates/page.js in the root dir after building.

Apologies if I am missing something here, I am a backend dev and sometimes the configuration for FE tooling gets me turned around.

Since this is my first time making a medusa plugin, I am leaning on medusa-plugin-ultimate for inspiration on how to create the pages, since their admin page does show up.

thoward27 commented 8 months ago

Actually, I think I've got it. Needed to add a call to medusa-admin bundle. That seems to have done the trick for me.

breakerh commented 8 months ago

Since this is my first time making a medusa plugin, I am leaning on medusa-plugin-ultimate for inspiration on how to create the pages, since their admin page does show up.

You know that there's quite decent and extensive documentation from MedusaJS itself, right? https://docs.medusajs.com/

thoward27 commented 8 months ago

@breakerh yes, I have been referencing the docs a bunch, I find it really helps to see examples though, as they can help me cross bridges. I'm a backend/devops engineer by trade, so a good portion of the tooling here is new for me.