diff --git a/.changeset/major-release-builder-ui.md b/.changeset/major-release-builder-ui.md new file mode 100644 index 0000000..a603617 --- /dev/null +++ b/.changeset/major-release-builder-ui.md @@ -0,0 +1,142 @@ +--- +"@codee-sh/medusa-plugin-notification-emails": major +--- + +## Breaking Changes + +### API Routes Changed +All API endpoints moved from `/api/admin/notification-plugin/` to `/api/admin/mpn/`. Update all API integrations. + +**Changed endpoints:** +- `/api/admin/notification-plugin/events` → `/api/admin/mpn/events` +- `/api/admin/notification-plugin/notifications` → `/api/admin/mpn/notifications` +- `/api/admin/notification-plugin/render-template` → `/api/admin/mpn/render-template` + +**New endpoints:** +- `/api/admin/mpn/available-templates` +- `/api/admin/mpn/templates` +- `/api/admin/mpn/templates/[id]/blocks` +- `/api/admin/mpn/templates/types` +- `/api/admin/mpn/templates/types/[type]/services` +- `/api/admin/mpn/templates/types/[type]/services/[service]/templates` + +### Admin UI Routes Changed +Admin UI routes changed from `/notifications-emails/` to `/mpn/notifications/`. Update all Admin UI links/bookmarks. + +**Changed routes:** +- `/notifications-emails/list` → `/mpn/notifications/list` +- `/notifications-emails/render` → `/mpn/notifications/render` + +**New routes:** +- `/mpn/templates` - templates list +- `/mpn/templates/[id]/blocks` - template blocks editor + +### Module Restructure +Modules moved from `templates/` to `modules/mpn-builder/`. Update all direct imports from `templates/`. + +**Removed locations:** +- `src/templates/emails/email-template-service.ts` +- `src/templates/slack/slack-template-service.ts` +- `src/templates/emails/types.ts` +- `src/templates/slack/types.ts` + +**New locations:** +- `src/modules/mpn-builder/services-local/email-template-service.ts` +- `src/modules/mpn-builder/services-local/slack-template-service.ts` +- `src/modules/mpn-builder/types/` + +### Old Workflows Removed +Removed old workflows (`email-service`, `mpn-templates/*`). Migrate to new `mpn-builder/*` workflows. + +### Email and Slack Service Import Changed +Changed import and execution method for `emailService` and `slackService`: + +**Before:** +```typescript +import { emailService } from "../templates/emails" +await emailService.render(...) +``` + +**After:** +```typescript +import { emailServiceWorkflow } from "../workflows/mpn-builder-services/email-service" +await emailServiceWorkflow(container).run({...}) +``` + +## New Features + +### MPN Builder Module +Added complete `mpn-builder` module with template management (services, models, 3 migrations). + +### UI Builder for Templates +Added full UI builder with drag & drop, inline editing, and template preview. Supports multiple block types (heading, text, row, separator, group, repeater, product-item). + +### Slack Block Kit Support +Added full Slack Block Kit format support with block transformation and data interpolation. Supports all Slack block types (header, section, divider, etc.) and nested structures. + +### External Templates Support +Added support for external templates with type filtering. Configure via `medusa-config.ts`: + +```typescript +{ + resolve: "@codee-sh/medusa-plugin-notification-emails", + options: { + extend: { + services: [ + { + id: "slack", // Service ID (e.g., "slack", "email") + templates: [ + { + name: "external_template-name", + path: path.resolve(require.resolve("@package/templates/slack/template")) + } + ] + } + ] + } + } +} +``` + +**Template Types:** +- `system` - Built-in system templates (default) +- `db` - Templates stored in database (created via UI or API) +- `external` - External templates registered programmatically or via configuration + +### New Block Types +Added support for new block types (group, repeater, product-item) with recursive rendering, data interpolation, and nested structures. + +### New Workflows and Steps +Added 10+ workflows for template management: +- `create-template` - create template +- `edit-template` - edit template +- `delete-template` - delete template +- `edit-template-blocks` - edit template blocks +- `get-template` - get single template +- `get-blocks-by-template-id` - get template blocks +- `get-templates-services` - get template services +- `get-templates-types` - get template types +- `get-templates-by-type-service` - get templates by type +- `get-services-types-templates` - get templates with filtering + +All workflows include full JSDoc documentation. + +### Helper Components +Added helper components (ManagerFields, TwoColumnPage, form fields: TextField, TextAreaField, SelectField, NumberField, CheckboxField). + +### Store and Region Workflows +Added workflows for retrieving Store and Region data (`get-store-by-id`, `get-region-by-id`) for template preview context. + +### API Middleware +Added middleware for API routing (error handling, request validation). + +## Improvements + +- Code refactoring: standardized naming (`settings` → `options`), renamed `renderTemplate` → `renderAction`, removed unused methods, consolidated similar methods +- Template ID handling fixes: unified template ID handling (removed `system_` prefix in UI, kebab-case format, fixed recognition logic) +- Removed unused files (old workflows, types, interfaces, console.logs, placeholder text) +- Fixed block data structure (added `parent_id` support, fixed `children` structure, added `arrayPath` for repeaters) +- Added JSDoc documentation for all workflows and steps +- UI improvements: button sizes, field descriptions, two-column layout, better error messages +- Improved data handling: added `region` to order queries, improved template filtering, fixed Slack Block Kit transformation +- Added new UI dependencies (`@dnd-kit/core`, `@lexical/*`, `@react-email/*`, `react-email`) diff --git a/.github/labeler.yml b/.github/labeler.yml index 53b4282..4a872e8 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -21,3 +21,11 @@ dependencies: - title: ["deps:", "dependencies:", "bump "] - head: ["deps/", "dependencies/"] +"type: chore": + - title: ["chore:"] + - head: ["chore/"] + +breaking: + - title: ["breaking:", "BREAKING:"] + - head: ["breaking/"] + diff --git a/.github/workflows/changeset-version.yml b/.github/workflows/changeset-version.yml deleted file mode 100644 index 71dcec7..0000000 --- a/.github/workflows/changeset-version.yml +++ /dev/null @@ -1,50 +0,0 @@ -# name: Version Bump - -# on: -# push: -# branches: -# - main - -# jobs: -# version: -# runs-on: ubuntu-latest -# permissions: -# contents: write -# pull-requests: write -# steps: -# - name: Checkout -# uses: actions/checkout@v4 -# with: -# fetch-depth: 0 - -# - name: Setup Node.js -# uses: actions/setup-node@v4 -# with: -# node-version: 20 - -# - name: Install dependencies -# run: npm ci - -# - name: Configure Git for tag creation -# run: | -# git config user.name "github-actions[bot]" -# git config user.email "github-actions[bot]@users.noreply.github.com" - -# - name: Create Release Pull Request or Publish -# id: changesets -# uses: changesets/action@v1 -# with: -# # changeset publish automatically creates git tags in format: package-name@version -# # For single package repo, it creates: v1.0.0 format -# publish: | -# npm run build -# npm run release -# # Push tags created by changeset publish -# git push --follow-tags -# version: npm run version -# commit: "chore: version packages" -# title: "chore: version packages" -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# NPM_TOKEN: ${{ secrets.npm_token }} -# NODE_AUTH_TOKEN: ${{ secrets.npm_token }} diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml deleted file mode 100644 index 305c3a8..0000000 --- a/.github/workflows/npm-publish.yml +++ /dev/null @@ -1,48 +0,0 @@ -# ⚠️ THIS WORKFLOW IS DISABLED ⚠️ -# -# Versioning and publishing is now handled automatically by changeset-version.yml -# Changesets workflow automatically: -# 1. Creates version bump PR when changesets are merged to master -# 2. Publishes to npm when version bump PR is merged -# -# This workflow is kept for reference but will NOT run automatically. -# If you need manual release via GitHub Release, you can: -# 1. Delete changeset-version.yml -# 2. Uncomment the "on:" section below -# 3. Remove workflow_dispatch - -name: Node.js Package (Legacy - Disabled) - -# DISABLED - uncomment to enable manual release via GitHub Release -# on: -# release: -# types: [created] - -# Only allows manual trigger, prevents automatic execution -on: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - - run: npm ci - - run: npm test - - publish-npm: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - registry-url: https://registry.npmjs.org/ - - run: npm ci - - run: npm publish - env: - NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml deleted file mode 100644 index 1d15336..0000000 --- a/.github/workflows/release-notes.yml +++ /dev/null @@ -1,124 +0,0 @@ -# Backup: Generate Release Notes from commits -# This workflow generates release notes from commits when PRs are not available. -# For PR-based releases, use GitHub's built-in "Generate release notes" button -# which uses .github/release.yml configuration. - -name: Generate Release Notes (Backup) - -# DISABLED - Using GitHub's built-in "Generate release notes" instead -# This workflow is kept for reference but will NOT run automatically. -# To enable: uncomment the "on:" section below and remove workflow_dispatch - -# on: -# release: -# types: [created] - -# Only allows manual trigger, prevents automatic execution -on: - workflow_dispatch: - -jobs: - generate-release-notes: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Generate Release Notes from Commits - id: generate_notes - uses: actions/github-script@v7 - with: - script: | - const { data: release } = await github.rest.repos.getRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: context.payload.release.id, - }); - - const tagName = release.tag_name; - const previousTag = await github.rest.repos.listTags({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 2, - }); - - let previousTagName = ''; - if (previousTag.data.length > 1) { - previousTagName = previousTag.data[1].name; - } - - // Get commits between tags - const { data: commits } = await github.rest.repos.compareCommits({ - owner: context.repo.owner, - repo: context.repo.repo, - base: previousTagName || 'HEAD~50', - head: tagName, - }); - - // Categorize commits - const breaking = []; - const features = []; - const fixes = []; - const improvements = []; - const docs = []; - const other = []; - - commits.commits.forEach(commit => { - const message = commit.commit.message; - const firstLine = message.split('\n')[0]; - - if (firstLine.toLowerCase().includes('breaking') || firstLine.includes('!:')) { - breaking.push(`- ${firstLine}`); - } else if (firstLine.toLowerCase().startsWith('feat') || firstLine.toLowerCase().includes('feature')) { - features.push(`- ${firstLine}`); - } else if (firstLine.toLowerCase().startsWith('fix') || firstLine.toLowerCase().includes('bug')) { - fixes.push(`- ${firstLine}`); - } else if (firstLine.toLowerCase().startsWith('refactor') || firstLine.toLowerCase().includes('improve')) { - improvements.push(`- ${firstLine}`); - } else if (firstLine.toLowerCase().startsWith('docs') || firstLine.toLowerCase().includes('doc')) { - docs.push(`- ${firstLine}`); - } else if (!firstLine.toLowerCase().startsWith('chore') && !firstLine.toLowerCase().startsWith('ci') && !firstLine.toLowerCase().startsWith('build')) { - other.push(`- ${firstLine}`); - } - }); - - // Build release notes - let releaseNotes = `## Release ${tagName}\n\n`; - - if (breaking.length > 0) { - releaseNotes += `### Breaking Changes\n${breaking.join('\n')}\n\n`; - } - if (features.length > 0) { - releaseNotes += `### New Features\n${features.join('\n')}\n\n`; - } - if (fixes.length > 0) { - releaseNotes += `### Bug Fixes\n${fixes.join('\n')}\n\n`; - } - if (improvements.length > 0) { - releaseNotes += `### Improvements\n${improvements.join('\n')}\n\n`; - } - if (docs.length > 0) { - releaseNotes += `### Documentation\n${docs.join('\n')}\n\n`; - } - if (other.length > 0) { - releaseNotes += `### Other Changes\n${other.join('\n')}\n\n`; - } - - if (previousTagName) { - releaseNotes += `\n**Full Changelog**: ${previousTagName}...${tagName}`; - } - - // Update release - await github.rest.repos.updateRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: context.payload.release.id, - body: releaseNotes - }); - - console.log('Release notes generated successfully!'); - diff --git a/.github/workflows/release-on-merge.yml b/.github/workflows/release-on-merge.yml new file mode 100644 index 0000000..7d3638a --- /dev/null +++ b/.github/workflows/release-on-merge.yml @@ -0,0 +1,303 @@ +name: Create Tag and Release on Merge + +# This workflow creates a tag and GitHub Release when a release PR is merged to main +# Triggered when PR with title starting with "release:" is merged to main + +on: + pull_request: + types: [closed] + branches: + - main + +jobs: + create-tag-and-release: + if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.title, 'release:') + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: main + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Enable Corepack + run: corepack enable + + - name: Install dependencies + run: yarn install --immutable + + # - name: Debug - Check NPM_TOKEN secret + # run: | + # if [ -z "${{ secrets.NPM_TOKEN }}" ]; then + # echo "❌ NPM_TOKEN secret is not set or empty" + # echo "Please add NPM_TOKEN secret in: Settings → Secrets and variables → Actions" + # exit 1 + # else + # echo "✅ NPM_TOKEN secret exists (value is hidden for security)" + # echo "Secret length: ${#NPM_TOKEN} characters" + # fi + # env: + # NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Extract version from package.json + id: version + run: | + VERSION=$(node -p "require('./package.json').version") + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "tag=v$VERSION" >> $GITHUB_OUTPUT + echo "✅ Detected version: $VERSION" + + - name: Check if tag already exists + id: check-tag + run: | + TAG_NAME="${{ steps.version.outputs.tag }}" + if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "⚠️ Tag $TAG_NAME already exists locally" + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "✅ Tag $TAG_NAME does not exist, will create" + fi + + - name: Create Git Tag + if: steps.check-tag.outputs.exists == 'false' + run: | + TAG_NAME="${{ steps.version.outputs.tag }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "$TAG_NAME" -m "Release $TAG_NAME" + git push origin "$TAG_NAME" + echo "✅ Tag $TAG_NAME created and pushed" + + # - name: Publish to npm + # if: steps.check-tag.outputs.exists == 'false' + # run: | + # echo "🔐 Configuring npm authentication..." + # # npm automatically detects NODE_AUTH_TOKEN for authentication + # # Also set NPM_TOKEN for .npmrc compatibility (if needed) + + # echo "🔍 Verifying npm authentication..." + # npm whoami --registry=https://registry.npmjs.org/ || { + # echo "❌ npm authentication failed!" + # echo "Check if NPM_TOKEN secret is correct in GitHub Secrets" + # exit 1 + # } + + # echo "📦 Publishing package..." + # npm publish --access restricted + # env: + # NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Generate and Create GitHub Release + uses: actions/github-script@v7 + with: + script: | + const { execSync } = require('child_process'); + const fs = require('fs'); + + // Get version from package.json + const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8')); + const version = packageJson.version; + const tagName = `v${version}`; + + console.log(`📦 Creating release for tag: ${tagName}`); + + // Check if release already exists + try { + const existingRelease = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: tagName + }); + console.log(`⚠️ Release ${tagName} already exists!`); + console.log(`Release URL: ${existingRelease.data.html_url}`); + return; + } catch (error) { + if (error.status !== 404) { + throw error; + } + console.log(`✅ No existing release found for ${tagName}, proceeding...`); + } + + // Get previous tag + const { data: tags } = await github.rest.repos.listTags({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 10, + }); + + let previousTagName = ''; + for (const tag of tags) { + if (tag.name !== tagName) { + previousTagName = tag.name; + break; + } + } + + console.log(`Generating release notes from ${previousTagName || 'start'} to ${tagName}`); + + // Get commits between tags + let commits = []; + try { + const { data: comparison } = await github.rest.repos.compareCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + base: previousTagName || 'HEAD~50', + head: tagName, + }); + commits = comparison.commits; + console.log(`Found ${commits.length} commits between ${previousTagName || 'start'} and ${tagName}`); + } catch (error) { + console.log(`Could not compare commits: ${error.message}`); + commits = []; + } + + // Get PRs for commits + const prMap = new Map(); + for (const commit of commits) { + try { + const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + commit_sha: commit.sha, + }); + + for (const pr of prs) { + if (pr.merged_at && + !prMap.has(pr.number) && + !pr.title.toLowerCase().includes('release:')) { + prMap.set(pr.number, pr); + } + } + } catch (error) { + console.log(`Could not get PRs for commit ${commit.sha.substring(0, 7)}:`, error.message); + } + } + + const prs = Array.from(prMap.values()); + console.log(`Found ${prs.length} merged PRs between tags`); + + // Categorize PRs by labels + const breaking = []; + const features = []; + const bugs = []; + const improvements = []; + const docs = []; + const dependencies = []; + const chores = []; + const other = []; + + prs.forEach(pr => { + const labels = pr.labels.map(l => l.name); + const prLine = `- ${pr.title} (#${pr.number}) @${pr.user.login}`; + + if (labels.includes('breaking')) { + breaking.push(prLine); + } else if (labels.includes('type: feature')) { + features.push(prLine); + } else if (labels.includes('type: bug')) { + bugs.push(prLine); + } else if (labels.includes('type: improvement')) { + improvements.push(prLine); + } else if (labels.includes('type: docs')) { + docs.push(prLine); + } else if (labels.includes('dependencies')) { + dependencies.push(prLine); + } else if (labels.includes('type: chore')) { + chores.push(prLine); + } else { + other.push(prLine); + } + }); + + // Build release notes + let releaseNotes = ``; + + // Get CHANGELOG.md content for this version + let changelogContent = ''; + try { + const changelogPath = './CHANGELOG.md'; + if (fs.existsSync(changelogPath)) { + const changelogText = fs.readFileSync(changelogPath, 'utf8'); + const versionNumber = version; + const escapedVersion = versionNumber.replace(/\./g, '\\.'); + const versionRegex = new RegExp(`##\\s+${escapedVersion}\\s*\\n([\\s\\S]*?)(?=\\n##|$)`); + const match = changelogText.match(versionRegex); + + if (match && match[1]) { + changelogContent = match[1].trim(); + console.log(`✅ Found CHANGELOG.md content for version ${versionNumber}`); + } + } + } catch (error) { + console.log(`Could not read CHANGELOG.md: ${error.message}`); + } + + // Use CHANGELOG.md if available + if (changelogContent) { + releaseNotes += changelogContent + '\n\n'; + releaseNotes += `---\n\n`; + releaseNotes += `## Pull Requests\n\n`; + } else { + if (prs.length > 0) { + const totalPRs = prs.length; + releaseNotes += `This release includes ${totalPRs} ${totalPRs === 1 ? 'change' : 'changes'}.\n\n`; + } + } + + if (breaking.length > 0) { + releaseNotes += `### Breaking Changes\n${breaking.join('\n')}\n\n`; + } + if (features.length > 0) { + releaseNotes += `### New Features\n${features.join('\n')}\n\n`; + } + if (bugs.length > 0) { + releaseNotes += `### Bug Fixes\n${bugs.join('\n')}\n\n`; + } + if (improvements.length > 0) { + releaseNotes += `### Improvements\n${improvements.join('\n')}\n\n`; + } + if (docs.length > 0) { + releaseNotes += `### Documentation\n${docs.join('\n')}\n\n`; + } + if (dependencies.length > 0) { + releaseNotes += `### Dependencies\n${dependencies.join('\n')}\n\n`; + } + if (chores.length > 0) { + releaseNotes += `### Chores\n${chores.join('\n')}\n\n`; + } + if (other.length > 0) { + releaseNotes += `### Other Changes\n${other.join('\n')}\n\n`; + } + + if (prs.length === 0) { + releaseNotes += `No merged pull requests found for this release.\n\n`; + } + + if (previousTagName) { + releaseNotes += `\n---\n**Full Changelog**: https://github.com/${context.repo.owner}/${context.repo.repo}/compare/${previousTagName}...${tagName}`; + } + + // Create GitHub Release + const release = await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: tagName, + name: `Release ${tagName}`, + body: releaseNotes, + draft: false, + prerelease: false, + make_latest: 'true' + }); + + console.log(`✅ Release ${tagName} created successfully!`); + console.log(`Release URL: ${release.data.html_url}`); diff --git a/.gitignore b/.gitignore index 9145ace..97d8bcf 100755 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ build/ *.swp *.swo *~ +.github-workflow # OS .DS_Store @@ -38,4 +39,4 @@ yarn-error.log* # Notes and internal docs things/ - +builder-lexical/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a589cf..c15971d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,437 +1,132 @@ -# Contributing - -Thank you for your interest in contributing to this project! This document describes the workflow for working on the repository and the conventions we should follow. If you have any questions, we encourage you to reach out via issues or directly with the team. - -## Important Information - -- All changes should be made through Pull Requests (PR) -- Before starting work on larger changes, consult with the team -- All PRs should include appropriate tests -- Code should follow the accepted style conventions -- **The project uses Changesets** for version management - every PR with code changes requires a changeset - -## Prerequisites - -- Knowledge of Git and GitHub (Issues, Pull Requests) -- Local development environment configured -- Node.js >= 20 -- Yarn 3.2.3+ -- Knowledge of technologies used in the project (Medusa, TypeScript, React) - -## Workflow - -### 1. Issues before PR - -1. **Check existing issues** - before starting work, make sure there isn't already an issue for what you want to work on -2. **Create an issue** - if there's no appropriate issue, create a new one, describing: - - What you want to implement/change - - Why this change is needed - - What are the expected results -3. **Wait for approval** - for larger changes, wait for team approval before starting work - -### 2. Branches - -All changes should be made in separate branches and submitted as Pull Requests. - -**Branch naming:** -- `fix/` - for bug fixes (e.g., `fix/login-error`) -- `feat/` - for new features (e.g., `feat/user-profile`) -- `docs/` - for documentation changes (e.g., `docs/api-update`) -- `refactor/` - for code refactoring (e.g., `refactor/auth-module`) -- `test/` - for adding or improving tests (e.g., `test/integration-tests`) -- `chore/` - for maintenance tasks (e.g., `chore/dependencies-update`) - -**Base branch:** -- Use `develop` as the base branch for your PRs by default -- `master` is only used by admins for releases - -### 3. Commits - -- Try to create small, isolated commits - this makes review and understanding changes easier -- Use conventional commit messages: - - `fix: fixed login error` - - `feat: added password reset functionality` - - `docs: updated API documentation` - - `refactor: improved authorization module structure` - - `test: added unit tests for payment module` - - `chore: updated dependencies` - -### 4. Pull Requests - -**Process (for developers):** -1. Make sure your branch is up to date with `develop` -2. **Add a changeset** (if you're making code changes) - see the [Changesets](#changesets) section -3. Create a Pull Request with a clear description of changes **to the `develop` branch** -4. Add appropriate labels and assign reviewers -5. Respond to comments and make corrections -6. After approval, the PR will be merged into `develop` - -**Release process (admins only):** - -1. After merging changes to `develop`, admin creates a PR from `develop` to `master` -2. This PR contains all changes ready for release -3. After merging to `master`, admin performs release locally (see the [Release Process](#release-process-locally-by-admin) section) - -**How to create a PR from `develop` to `master`:** - -1. **Make sure `develop` is up to date:** -```bash -git checkout develop -git pull origin develop -``` - -2. **Create PR on GitHub:** - - Base branch: `master` - - Compare branch: `develop` - - PR title: `chore: release [date]` or `chore: prepare release` (e.g., `chore: release 2024-01-15`) - -3. **In the PR description you can:** - - List the main changes in this release - - Add links to merged PRs from `develop` - - Optionally: add a list of changesets that will be processed - -4. **After creating the PR:** - - Merge the PR to `master` (squash merge or merge commit) - - After merge, perform the release process locally (see the [Release Process](#release-process-locally-by-admin) section) - -**PR description structure:** -- **What** - what was changed in this PR -- **Why** - why these changes are needed -- **How** - how the changes were implemented -- **Testing** - how the changes were tested or how the reviewer can test them -- **Related issue** - link to the related issue (use keywords: `closes #123`, `fixes #456`) - -**Self-review:** -We encourage self-review of code before requesting review. Check: -- Is the code readable and follows conventions -- Do all tests pass -- Are there no unused imports or comments -- Has documentation been updated (if applicable) -- Has a changeset been added (if applicable) - -**Merge Style:** -- Pull Requests are merged via squash and merge -- Make sure the commit message is clear and descriptive - -## Local Development - -### Environment Setup - -1. **Clone the repository:** -```bash -git clone -cd -``` - -2. **Install dependencies:** -```bash -yarn install -``` - -3. **Start the development server:** -```bash -yarn dev -``` - -This will start the plugin in watch mode, automatically rebuilding on changes. - -### Project Structure - -``` -medusa-plugin-automations/ -├── src/ -│ ├── admin/ # Admin UI (React components) -│ ├── api/ # API routes (admin/store) -│ ├── modules/ # Medusa modules -│ ├── workflows/ # Medusa workflows -│ ├── subscribers/ # Event subscribers -│ ├── providers/ # Action providers (Slack, etc.) -│ └── utils/ # Utility functions -├── docs/ # Documentation -├── .changeset/ # Changesets for versioning -├── .github/ # GitHub workflows -└── package.json -``` - -### Working with the plugin locally (yalc) - -If you're working on the plugin and want to test it in a Medusa application: - -1. **In the plugin directory, build and publish locally:** -```bash -yarn build -yalc publish -``` - -2. **In the Medusa application, link the local plugin:** -```bash -cd /path/to/medusa-app -yalc link @codee-sh/medusa-plugin-automations -yarn install -``` - -3. **While working on the plugin:** -```bash -# In the plugin directory (watch mode) -yarn dev - -# In the Medusa application (in a separate terminal) -yarn dev -``` - -4. **After finishing work, remove local links:** -```bash -cd /path/to/medusa-app -yalc remove @codee-sh/medusa-plugin-automations -yarn install -``` - -**Note:** `.yalc` and `yalc.lock` files are ignored by git - don't commit them. - -## Testing - -### Types of Tests - -- **Unit tests** - unit tests for individual functions/modules -- **Integration tests** - integration tests for larger components -- **E2E tests** - end-to-end tests for entire flows - -### Running Tests - -```bash -# All tests -yarn test - -# Tests in watch mode -yarn test:watch - -# Tests with coverage -yarn test:coverage - -# Integration tests -yarn test:integration -``` - -### Test Requirements - -- All PRs should include appropriate tests for the changes made -- New features require new tests -- Bug fixes should include tests that reproduce and verify the fix -- Aim for code coverage >= 80% - -## Documentation - -### Updating Documentation - -- If you change user-facing API, update documentation in `docs/` -- Add usage examples for new features -- Update README.md if you change the setup or installation process -- Document breaking changes through changesets (CHANGELOG.md is generated automatically) - -### Documentation Conventions - -- Use [TSDoc](https://tsdoc.org/) for TypeScript documentation -- Use JSDoc for JavaScript documentation -- Write documentation in English (or according to project convention) -- Add code examples where possible - -## Code Style - -### Formatting - -- We use Prettier for code formatting -- Run `yarn format` before committing (formats files) -- Check formatting before PR: `yarn format:check` (checks without formatting) - -### Linting - -- All files should pass linting without errors -- Run `yarn format:check` before committing (checks formatting) -- Fix all warnings before PR - -### TypeScript - -- Use TypeScript for all new files -- Avoid `any` - use appropriate types -- Add types for all public APIs -- Use interfaces for objects, type for union types -- Export types from `src/utils/types/` for public API - -## Changesets - -The project uses [Changesets](https://github.com/changesets/changesets) for version and changelog management. - -### Changesets Workflow - -The release process consists of **three stages**: - -1. **Feature PR** (you create) - contains code changes + changeset file → merge to `develop` -2. **Release PR** (admin creates) - PR from `develop` to `master` with ready changes -3. **Manual release** (admin executes locally) - after merge to `master`, admin locally runs `yarn version` and `yarn release` - -**Flow diagram:** -``` -Developer: - feature-branch → PR → develop (with changeset) - -Admin: - develop → PR "chore: release [date]" → master - -Admin (locally, after merge to master): - 1. git checkout master && git pull - 2. yarn version (updates version and CHANGELOG) - 3. git commit && git push - 4. yarn release (builds and publishes to npm) -``` - -### How to Add a Changeset - -1. **After making code changes, before creating PR:** -```bash -yarn changeset -``` - -2. **Select the type of change:** - - `major` - breaking changes - - `minor` - new features (backward compatible) - - `patch` - bug fixes (backward compatible) - -3. **Describe the changes** - write a brief description of what was changed - -4. **Commit the changeset file:** -```bash -git add .changeset/ -git commit -m "feat: add changeset for my feature" -``` - -5. **Create PR to `develop`** - make sure the changeset file is included in the PR - -### Release Process (locally by admin) - -After merging PR from `develop` to `master`: - -```bash -# 1. Switch to master and pull changes -git checkout master && git pull origin master - -# 2. Update version and CHANGELOG -yarn version - -# 3. Commit and push changes -git add package.json CHANGELOG.md .changeset/ -git commit -m "chore: version packages" -git push origin master - -# 4. Build and publish to npm -yarn release - -# 5. Push tag -git push origin --tags -``` - -**Important:** -- Changesets must be in PR to `develop` - without them `yarn version` won't find changes -- Make sure you're logged in to npm (`npm login`) before `yarn release` - -### Changesets Commands - -#### `yarn changeset` - -**What it does:** Creates a new changeset file describing code changes. - -**When to use:** -- After making code changes, **before creating PR** -- For every code change that should be included in the release -- **Don't use** for documentation-only changes (unless it's a breaking change in documentation) - -**How to use:** -```bash -yarn changeset -``` - -**Process:** Select the type of change (major/minor/patch) and describe the changes. A file will be created in `.changeset/`. - ---- - -#### `yarn version` - -**What it does:** -- Reads all changesets in `.changeset/` -- Updates version in `package.json` according to changeset types -- Updates `CHANGELOG.md` with change descriptions -- Removes processed changeset files - -**When to use:** -- **Admins only** after merging PR `develop` → `master`, on `master` branch -- Before `yarn release` - -**How to use:** -```bash -yarn version -``` - -**Process:** Checks changesets, updates version in `package.json` and `CHANGELOG.md`, removes processed changeset files. - -**After running:** Commit the changes (`package.json`, `CHANGELOG.md`, `.changeset/`). - ---- - -#### `yarn release` - -**What it does:** -- Builds the package (`yarn build`) -- Publishes the package to npm (`npm publish`) -- Creates a git tag with the version - -**When to use:** -- **Admins only** after `yarn version` and committing changes -- On `master` branch with updated version - -**How to use:** -```bash -yarn release -``` - -**Process:** Builds the package (`yarn build`), publishes to npm (`yarn publish-package`), creates a git tag. - -**Requirements:** Log in to npm (`npm login`), changes must be committed and pushed. - ---- - -**Versioning:** The project uses [Semantic Versioning](https://semver.org/): -- `major` - breaking changes -- `minor` - new features (backward compatible) -- `patch` - bug fixes (backward compatible) - -CHANGELOG.md is automatically generated by Changesets. - -## Code Review - -### For PR Authors - -- Be open to feedback -- Respond to all comments -- Make corrections according to suggestions -- If you disagree with a suggestion, explain why - -### For Reviewers - -- Be constructive and polite -- Explain your suggestions -- Check not only code, but also tests and documentation -- Check if a changeset has been added (if applicable) -- Approve PR only if you're sure it's ready - -## Questions and Help - -- **Issues** - for bugs and feature requests -- **Discussions** - for questions and discussions -- **Team** - direct contact with team members - -## License - -By contributing to this project, you agree that your changes will be licensed under the same terms as the project. - ---- - -Thank you for your contribution! 🎉 +# Contributing Guide + +This guide explains how we organize releases, structure branches, and prepare pull requests so changes land smoothly. + +## Branch Model + +- **`main`** – Release-ready code. Every commit is tagged and deployable. Keep PRs targeting `main` limited to hotfixes or release preparation approved by maintainers. +- **`develop`** – Nightly builds and upcoming release work. Base regular feature work off `develop` so it can soak in automation and shared testing. +- **Topic branches** – Create a dedicated branch per change using the format `feat/` (for example `feat/customer-export`). Use other prefixes when appropriate (`fix/`, `chore/`, `docs/`). + +## Working on Features + +1. **Branch from `develop`**: + ```bash + git checkout develop + git pull origin develop + git checkout -b feat/your-feature-name + ``` + +2. **Keep commits scoped and descriptive**: + ```bash + git commit -m "feat: add customer export feature" + ``` + +3. **Keep your branch up to date**: + ```bash + git pull --rebase origin develop + ``` + +4. **Open PR targeting `develop`**: + - Describe the user impact, architectural notes, and testing performed + - Ensure the branch merges cleanly and CI is green before requesting review + - Reference related issues or discussions + +## Release Process + +### Normal Release Flow + +1. **Prepare release on `develop`**: + ```bash + git checkout develop + git pull origin develop + yarn changeset version # Updates package.json and CHANGELOG.md + git add . + git commit -m "chore: version packages" + git push origin develop + ``` + +2. **Create release branch**: + ```bash + git checkout -b release/v1.1.X + git push origin release/v1.1.X + ``` + +3. **Open PR: `release/v1.1.X` → `main`**: + - Title: `release: v1.1.X` + - Description: Include summary of changes from CHANGELOG.md + - Wait for review/approval + +4. **After PR merge to `main`**: + - Tag will be created automatically (or create manually): + ```bash + git checkout main + git pull origin main + git tag -a v1.1.X -m "Release v1.1.X" + git push origin v1.1.X + ``` + - Create GitHub Release using workflow: Actions → "Create Release" → Run workflow + +5. **Synchronize `develop`** (if needed): + ```bash + git checkout develop + git merge main # Only if main has commits not in develop + git push origin develop + ``` + +### Hotfix Flow + +1. **Create hotfix branch from `main`**: + ```bash + git checkout main + git pull origin main + git checkout -b hotfix/critical-bug + ``` + +2. **Make fix and commit**: + ```bash + # ... make changes ... + git commit -m "fix: critical bug description" + git push origin hotfix/critical-bug + ``` + +3. **Open PR: `hotfix/critical-bug` → `main`**: + - After merge, create tag and release + +4. **Merge `main` → `develop`** (synchronization): + ```bash + git checkout develop + git merge main + git push origin develop + ``` + +## Pull Requests + +- **Open PRs against `develop`** unless you are coordinating a release hotfix +- **Describe the user impact**, architectural notes, and testing performed +- **Ensure the branch merges cleanly** and CI is green before requesting review +- **Reference related issues** or discussions +- **Tag maintainers early** if you need design or architectural guidance + +## Versioning + +We use [Changesets](https://github.com/changesets/changesets) for version management: + +- **Add changeset** when making changes: + ```bash + yarn changeset + ``` + +- **Version bump** happens during release preparation: + ```bash + yarn changeset version + ``` + +- **Version format**: Semantic versioning (MAJOR.MINOR.PATCH) + +## Helpful Resources + +- 📚 Documentation: Check project README.md +- 💬 Issues: [GitHub Issues](https://github.com/your-org/your-repo/issues) + +Thanks for contributing! diff --git a/emails-previews/contact-form.tsx b/emails-previews/contact-form.tsx index dfaf0cf..a9ddb4a 100644 --- a/emails-previews/contact-form.tsx +++ b/emails-previews/contact-form.tsx @@ -1,5 +1,6 @@ import { defaultTheme } from "../src/templates/shared/theme"; -import { emailService } from "../src/templates/emails"; +import { EmailTemplateService } from "../src/modules/mpn-builder/services-local/email-template-service"; +import { TEMPLATES_NAMES } from "../src/templates/emails/types"; export const contactFormMockData: any = { contact_form: { @@ -11,8 +12,8 @@ export const contactFormMockData: any = { }; export default function ContactForm() { - const renderTemplate = emailService.renderSync({ - templateName: "contact-form", + const renderTemplate = new EmailTemplateService().renderSync({ + templateName: TEMPLATES_NAMES.CONTACT_FORM, data: contactFormMockData, options: { locale: "pl", diff --git a/emails-previews/inventory-level.tsx b/emails-previews/inventory-level.tsx index 781da83..2f65477 100644 --- a/emails-previews/inventory-level.tsx +++ b/emails-previews/inventory-level.tsx @@ -1,5 +1,6 @@ import { defaultTheme } from "../src/templates/shared/theme"; -import { emailService } from "../src/templates/emails"; +import { EmailTemplateService } from "../src/modules/mpn-builder/services-local/email-template-service"; +import { TEMPLATES_NAMES } from "../src/templates/emails/types"; export const inventoryLevelMockData: any = { inventory_level: { @@ -12,8 +13,8 @@ export const inventoryLevelMockData: any = { }; export default function InventoryLevel() { - const renderTemplate = emailService.renderSync({ - templateName: "inventory-level", + const renderTemplate = new EmailTemplateService().renderSync({ + templateName: TEMPLATES_NAMES.INVENTORY_LEVEL, data: inventoryLevelMockData, options: { locale: "pl", diff --git a/emails-previews/order-completed.tsx b/emails-previews/order-completed.tsx index 1bc2c23..14bac16 100644 --- a/emails-previews/order-completed.tsx +++ b/emails-previews/order-completed.tsx @@ -1,5 +1,6 @@ import { defaultTheme } from "../src/templates/shared/theme"; -import { emailService } from "../src/templates/emails"; +import { EmailTemplateService } from "../src/modules/mpn-builder/services-local/email-template-service"; +import { TEMPLATES_NAMES } from "../src/templates/emails/types"; export const orderCompletedMockData: any = { order: { @@ -70,8 +71,8 @@ export const orderCompletedMockData: any = { }; export default function OrderCompleted() { - const renderTemplate = emailService.renderSync({ - templateName: "order-completed", + const renderTemplate = new EmailTemplateService().renderSync({ + templateName: TEMPLATES_NAMES.ORDER_COMPLETED, data: orderCompletedMockData, options: { locale: "pl", diff --git a/emails-previews/order-placed.tsx b/emails-previews/order-placed.tsx index f032373..49e3d9e 100644 --- a/emails-previews/order-placed.tsx +++ b/emails-previews/order-placed.tsx @@ -1,5 +1,6 @@ import { defaultTheme } from "../src/templates/shared/theme"; -import { emailService } from "../src/templates/emails"; +import { EmailTemplateService } from "../src/modules/mpn-builder/services-local/email-template-service"; +import { TEMPLATES_NAMES } from "../src/templates/emails/types"; export const orderPlacedMockData: any = { order: { @@ -70,8 +71,8 @@ export const orderPlacedMockData: any = { }; export default function OrderPlaced() { - const renderTemplate = emailService.renderSync({ - templateName: "order-placed", + const renderTemplate = new EmailTemplateService().renderSync({ + templateName: TEMPLATES_NAMES.ORDER_PLACED, data: orderPlacedMockData, options: { locale: "pl", diff --git a/package.json b/package.json index efeb992..fca1c65 100755 --- a/package.json +++ b/package.json @@ -16,12 +16,6 @@ "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js", "./modules/*": "./.medusa/server/src/modules/*/index.js", "./providers/*": "./.medusa/server/src/providers/*/index.js", - "./templates/emails": "./.medusa/server/src/templates/emails/index.js", - "./templates/emails/*": "./.medusa/server/src/templates/emails/*/index.js", - "./templates/emails/types": "./.medusa/server/src/templates/emails/types.js", - "./templates/slack": "./.medusa/server/src/templates/slack/index.js", - "./templates/slack/*": "./.medusa/server/src/templates/slack/*/index.js", - "./templates/slack/types": "./.medusa/server/src/templates/slack/types.js", "./utils": "./.medusa/server/src/utils/index.js", "./*": "./.medusa/server/src/*.js", "./admin": { @@ -49,12 +43,20 @@ "changeset": "changeset", "version": "changeset version", "release": "changeset publish", - "release:manual": "npm run build && npm publish --access public" + "release:manual": "npm publish --access public", + "prepare-release": "bash scripts/prepare-release.sh" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@lexical/extension": "^0.39.0", + "@lexical/history": "^0.39.0", + "@lexical/html": "^0.39.0", + "@lexical/react": "^0.39.0", + "@lexical/rich-text": "^0.39.0", "@react-email/components": "^0.5.7", "@react-email/preview-server": "^4.3.1", "@react-email/render": "^1.4.0", + "lexical": "^0.39.0", "react-email": "^4.3.1" }, "devDependencies": { @@ -88,7 +90,8 @@ "ts-node": "^10.9.2", "typescript": "^5.6.2", "vite": "^5.2.11", - "yalc": "^1.0.0-pre.53" + "yalc": "^1.0.0-pre.53", + "@types/lodash": "^4.17.23" }, "engines": { "node": ">=20" diff --git a/scripts/prepare-release.sh b/scripts/prepare-release.sh new file mode 100755 index 0000000..8d70682 --- /dev/null +++ b/scripts/prepare-release.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Script to prepare release on develop and create release branch +# Usage: ./scripts/prepare-release.sh + +set -euo pipefail + +echo "🚀 Starting release preparation..." + +# Step 1: Prepare release on develop +echo "" +echo "📦 Step 1: Preparing release on develop..." +git checkout develop +git pull origin develop + +echo "Running changeset version..." +yarn changeset version + +# Check if there are changes +if git diff --quiet && git diff --cached --quiet; then + echo "⚠️ No changes detected after 'yarn changeset version'" + echo " This might mean there are no changesets to process." + exit 1 +fi + +echo "Staging changes..." +git add . + +echo "Committing version bump..." +git commit -m "chore: version packages" + +echo "Pushing to develop..." +git push origin develop + +# Step 2: Create release branch +echo "" +echo "🌿 Step 2: Creating release branch..." + +# Extract version from package.json +VERSION=$(node -p "require('./package.json').version") +RELEASE_BRANCH="release/v${VERSION}" + +echo "Detected version: ${VERSION}" +echo "Creating branch: ${RELEASE_BRANCH}" + +git checkout -b "${RELEASE_BRANCH}" +git push origin "${RELEASE_BRANCH}" + +echo "" +echo "✅ Release preparation complete!" +echo "" +echo "📋 Next steps:" +echo "1. Go to GitHub and create PR: ${RELEASE_BRANCH} → main" +echo "2. Title: release: v${VERSION}" +echo "3. Description: Copy relevant section from CHANGELOG.md" +echo "4. Merge PR to main" +echo "" +echo "✨ After merge, workflow will automatically:" +echo " - Create tag v${VERSION}" +echo " - Publish to npm" +echo " - Create GitHub Release" diff --git a/src/admin/builder/blocks/blocks-children/blocks-children.tsx b/src/admin/builder/blocks/blocks-children/blocks-children.tsx new file mode 100644 index 0000000..d49855f --- /dev/null +++ b/src/admin/builder/blocks/blocks-children/blocks-children.tsx @@ -0,0 +1,86 @@ +import { useFieldArray } from "react-hook-form" +import { + DndContext, + closestCenter +} from "@dnd-kit/core" +import { + SortableContext, + verticalListSortingStrategy, +} from "@dnd-kit/sortable" + +import { handleDragEnd } from "../../../../utils" +import { BlockItemWrapper } from "../components/block-item-wrapper" +import { BlockList } from "../components/block-list" +import { BlockItem } from "../components/block-item" +import { BlockItemFields } from "../components/block-item-fields" +import { BlockDropdownMenu } from "../components/block-dropdown" +import { BlocksRepeater } from "../blocks-repeater" + +export const BlocksChildren = (props: any) => { + const { blocks, form, path, sensors } = props + + const childrenPath = `${path}.children` + + const { fields: childFields, append: appendChild, move: moveChild, remove: removeChild } = useFieldArray({ + control: form.control, + name: childrenPath, + keyName: "rhf_id" + }) + + return ( + <> + handleDragEnd({ form, event, fields: childFields, move: moveChild, path: childrenPath })} + > + f.rhf_id)} + strategy={verticalListSortingStrategy} + > + + {childFields.map((field: any, index: number) => { + return ( + + + {field.type === "group" && ( + + )} + {field.type === "repeater" && ( + + )} + {field.type !== "group" && field.type !== "repeater" && ( + + )} + + + ) + })} + + + + +
+ +
+ + ) +} diff --git a/src/admin/builder/blocks/blocks-children/index.ts b/src/admin/builder/blocks/blocks-children/index.ts new file mode 100644 index 0000000..767eb61 --- /dev/null +++ b/src/admin/builder/blocks/blocks-children/index.ts @@ -0,0 +1 @@ +export { BlocksChildren } from "./blocks-children" \ No newline at end of file diff --git a/src/admin/builder/blocks/blocks-container/blocks-container.tsx b/src/admin/builder/blocks/blocks-container/blocks-container.tsx new file mode 100644 index 0000000..14d5f28 --- /dev/null +++ b/src/admin/builder/blocks/blocks-container/blocks-container.tsx @@ -0,0 +1,37 @@ +import { + Container, + Heading, +} from "@medusajs/ui" +import { useMemo, useState, useEffect } from "react" + +import { useAvailableTemplates } from "../../../../hooks/api/available-templates" +import { BlocksForm } from "../blocks-form/blocks-form" +import { useListTemplateBlocks } from "../../../../hooks/api/templates/blocks" + +export const BlocksContainer = ({ id, type = "email" }: { id: string, type?: string }) => { + const { data: availableTemplates } = useAvailableTemplates({ + type: type, + }) + + const { data: blocks, isLoading: isBlocksLoading } = useListTemplateBlocks({ + template_id: id + }) + + const [template, setTemplate] = useState(null) + + useEffect(() => { + if (availableTemplates?.templates && type) { + setTemplate(availableTemplates.templates.find((t) => t.id === type)) + } + }, [availableTemplates]) + + return ( + + Blocks list for {type} template + + {template && !isBlocksLoading && ( + + ) } + + ) +} diff --git a/src/admin/builder/blocks/blocks-container/index.tsx b/src/admin/builder/blocks/blocks-container/index.tsx new file mode 100644 index 0000000..a55ce35 --- /dev/null +++ b/src/admin/builder/blocks/blocks-container/index.tsx @@ -0,0 +1 @@ +export { BlocksContainer } from "./blocks-container" \ No newline at end of file diff --git a/src/admin/builder/blocks/blocks-form/blocks-form.tsx b/src/admin/builder/blocks/blocks-form/blocks-form.tsx new file mode 100644 index 0000000..5f39d16 --- /dev/null +++ b/src/admin/builder/blocks/blocks-form/blocks-form.tsx @@ -0,0 +1,170 @@ +import { + Button +} from "@medusajs/ui" +import { Trash } from "@medusajs/icons" +import { toast } from "@medusajs/ui" +import { useMemo, useState, useEffect } from "react" +import { useForm, useFieldArray } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import { z } from "zod" +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from "@dnd-kit/core" +import { + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from "@dnd-kit/sortable" + +import { handleDragEnd } from "../../../../utils" +import { BlockList } from "../components/block-list" +import { BlockItem } from "../components/block-item" +import { BlockItemWrapper } from "../components/block-item-wrapper" +import { BlockItemFields } from "../components/block-item-fields" +import { createBlockFormSchema } from "../../templates/templates-form/utils/block-form-schema" +import { baseBlocksSchema } from "../../templates/templates-form/types/schema" +import { BlockDropdownMenu } from "../components/block-dropdown" +import { useEditTemplateBlocks } from "../../../../hooks/api/templates/blocks" +import { useQueryClient } from "@tanstack/react-query" +import { BlocksChildren } from "../blocks-children" +import { BlocksRepeater } from "../blocks-repeater" + +type BlockFormValues = z.infer + +export const BlocksForm = (props: any) => { + const { template_id, template, blocks, items } = props + + const blockFormSchema = useMemo(() => { + return createBlockFormSchema( + blocks ?? [] + ) + }, [blocks]) + + const form = useForm({ + resolver: zodResolver(blockFormSchema), + defaultValues: { items: items.map((i: any) => ({ ...i, children: i.children ?? [] })) }, + mode: "onChange", + }) + + const itemsPath = "items" + + const { fields, append, remove, move, replace } = useFieldArray({ + control: form.control, + name: itemsPath, + keyName: "rhf_id" + }) + + useEffect(() => { + if (items) { + replace(items) + } + }, [items]) + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 10, + }, + }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ) + + const queryClient = useQueryClient() + + const { + mutateAsync: editTemplateBlocks, + isPending: isEditTemplateBlocksPending, + } = useEditTemplateBlocks() + + async function onSubmit(values: BlockFormValues) { + const payload = values.items.map((item, index) => ({ + ...item, + position: index, + })) + + const items = { + template_id: template_id, + blocks: payload, + } + + await editTemplateBlocks(items) + + queryClient.invalidateQueries({ + queryKey: ["template-blocks", template_id], + }) + + toast.success( + "Blocks updated successfully", + { + position: "top-right", + duration: 3000, + } + ) + } + + return ( +
+ handleDragEnd({ form, path: itemsPath, event, fields, move })} + > + f.rhf_id)} + strategy={verticalListSortingStrategy} + > + + {fields.map((field, index) => { + return ( + + + {field.type === "group" && ( + + ) } + {field.type === "repeater" && ( + + ) } + {field.type !== "group" && field.type !== "repeater" && ( + + )} + + + ) + })} + + + + +
+ + + +
+
+ ) +} diff --git a/src/admin/builder/blocks/blocks-form/index.ts b/src/admin/builder/blocks/blocks-form/index.ts new file mode 100644 index 0000000..62161a4 --- /dev/null +++ b/src/admin/builder/blocks/blocks-form/index.ts @@ -0,0 +1 @@ +export { BlocksForm } from "./blocks-form" \ No newline at end of file diff --git a/src/admin/builder/blocks/blocks-preview/blocks-preview-container.tsx b/src/admin/builder/blocks/blocks-preview/blocks-preview-container.tsx new file mode 100644 index 0000000..eb217c4 --- /dev/null +++ b/src/admin/builder/blocks/blocks-preview/blocks-preview-container.tsx @@ -0,0 +1,76 @@ +import { useState } from "react" +import { + Container, + Heading, + Select, + Text, +} from "@medusajs/ui" +import { OrderContextContainer } from "./contexts/order" +// import { ContactFormTemplateGroup } from "../../../notifications-templates/groups/contact-form" + +export const BlocksPreviewContainer = ({ + templateId, +}: { + templateId: string +}) => { + const [selectedContextType, setSelectedContextType] = + useState("") + + const contextTypes = [ + { + label: "Contact Form", + value: "contact_form", + }, + { label: "Order", value: "order" }, + ] + + return ( + +
+ Preview Template +
+
+
+ + Choose context: + + +
+ {selectedContextType && ( +
+
+ + Choosed context: {selectedContextType} + +
+ {selectedContextType === "order" && ( + + )} + {/* {selectedContextType === "contact_form" && ( + + )} */} +
+ )} +
+
+ ) +} diff --git a/src/admin/builder/blocks/blocks-preview/blocks-preview.tsx b/src/admin/builder/blocks/blocks-preview/blocks-preview.tsx new file mode 100644 index 0000000..a095782 --- /dev/null +++ b/src/admin/builder/blocks/blocks-preview/blocks-preview.tsx @@ -0,0 +1,48 @@ +import { useEffect, useState } from "react" +import { Alert } from "@medusajs/ui" +import { usePreview } from "../../../../hooks/api/preview" +import { contactFormMockData } from "../../../../../emails-previews/contact-form" +import { TEMPLATES_EMAILS_NAMES } from "../../../../modules/mpn-builder/types" + +export const BlocksPreview = ({ contextType, templateId, context }: { contextType: string, templateId: string, context: any }) => { + const [previewContext, setPreviewData] = + useState(null) + + const { data: preview, isLoading: isPreviewLoading } = + usePreview({ + templateId: templateId, + context: context, + contextType: contextType, + locale: "pl", + enabled: !!context, + extraKey: [context], + }) + + // console.log("context", context) + useEffect(() => { + if (preview) { + setPreviewData(preview) + } + }, [preview]) + + return ( +
+ {isPreviewLoading && ( + Loading preview... + )} + {previewContext && ( +
+