hazo_notify 1.1.4 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +163 -41
  2. package/dist/components/template_manager/category_dialog.d.ts +11 -0
  3. package/dist/components/template_manager/category_dialog.d.ts.map +1 -0
  4. package/dist/components/template_manager/category_dialog.js +41 -0
  5. package/dist/components/template_manager/category_dialog.js.map +1 -0
  6. package/dist/components/template_manager/category_tree.d.ts +13 -0
  7. package/dist/components/template_manager/category_tree.d.ts.map +1 -0
  8. package/dist/components/template_manager/category_tree.js +45 -0
  9. package/dist/components/template_manager/category_tree.js.map +1 -0
  10. package/dist/components/template_manager/index.d.ts +10 -0
  11. package/dist/components/template_manager/index.d.ts.map +1 -0
  12. package/dist/components/template_manager/index.js +8 -0
  13. package/dist/components/template_manager/index.js.map +1 -0
  14. package/dist/components/template_manager/preview_dialog.d.ts +11 -0
  15. package/dist/components/template_manager/preview_dialog.d.ts.map +1 -0
  16. package/dist/components/template_manager/preview_dialog.js +15 -0
  17. package/dist/components/template_manager/preview_dialog.js.map +1 -0
  18. package/dist/components/template_manager/template_dialog.d.ts +12 -0
  19. package/dist/components/template_manager/template_dialog.d.ts.map +1 -0
  20. package/dist/components/template_manager/template_dialog.js +46 -0
  21. package/dist/components/template_manager/template_dialog.js.map +1 -0
  22. package/dist/components/template_manager/template_editor.d.ts +12 -0
  23. package/dist/components/template_manager/template_editor.d.ts.map +1 -0
  24. package/dist/components/template_manager/template_editor.js +57 -0
  25. package/dist/components/template_manager/template_editor.js.map +1 -0
  26. package/dist/components/template_manager/template_globals_admin.d.ts +8 -0
  27. package/dist/components/template_manager/template_globals_admin.d.ts.map +1 -0
  28. package/dist/components/template_manager/template_globals_admin.js +27 -0
  29. package/dist/components/template_manager/template_globals_admin.js.map +1 -0
  30. package/dist/components/template_manager/template_manager_admin.d.ts +44 -0
  31. package/dist/components/template_manager/template_manager_admin.d.ts.map +1 -0
  32. package/dist/components/template_manager/template_manager_admin.js +272 -0
  33. package/dist/components/template_manager/template_manager_admin.js.map +1 -0
  34. package/dist/lib/emailer/emailer.d.ts.map +1 -0
  35. package/dist/lib/emailer/emailer.js.map +1 -0
  36. package/dist/lib/emailer/index.d.ts.map +1 -0
  37. package/dist/lib/emailer/index.js.map +1 -0
  38. package/dist/lib/emailer/providers/index.d.ts.map +1 -0
  39. package/dist/lib/emailer/providers/index.js.map +1 -0
  40. package/dist/lib/emailer/providers/pop3_provider.d.ts.map +1 -0
  41. package/dist/lib/emailer/providers/pop3_provider.js.map +1 -0
  42. package/dist/lib/emailer/providers/smtp_provider.d.ts.map +1 -0
  43. package/dist/lib/emailer/providers/smtp_provider.js.map +1 -0
  44. package/dist/lib/emailer/providers/zeptomail_provider.d.ts.map +1 -0
  45. package/dist/{emailer → lib/emailer}/providers/zeptomail_provider.js +9 -6
  46. package/dist/lib/emailer/providers/zeptomail_provider.js.map +1 -0
  47. package/dist/lib/emailer/types.d.ts.map +1 -0
  48. package/dist/lib/emailer/types.js.map +1 -0
  49. package/dist/lib/emailer/utils/constants.d.ts.map +1 -0
  50. package/dist/lib/emailer/utils/constants.js.map +1 -0
  51. package/dist/lib/emailer/utils/index.d.ts.map +1 -0
  52. package/dist/lib/emailer/utils/index.js.map +1 -0
  53. package/dist/lib/emailer/utils/logger.d.ts.map +1 -0
  54. package/dist/lib/emailer/utils/logger.js.map +1 -0
  55. package/dist/lib/emailer/utils/validation.d.ts.map +1 -0
  56. package/dist/lib/emailer/utils/validation.js.map +1 -0
  57. package/dist/lib/index.d.ts.map +1 -0
  58. package/dist/lib/index.js.map +1 -0
  59. package/dist/lib/template_manager/cache/scope_chain_cache.d.ts +26 -0
  60. package/dist/lib/template_manager/cache/scope_chain_cache.d.ts.map +1 -0
  61. package/dist/lib/template_manager/cache/scope_chain_cache.js +60 -0
  62. package/dist/lib/template_manager/cache/scope_chain_cache.js.map +1 -0
  63. package/dist/{template_manager → lib/template_manager}/config/config_loader.d.ts +16 -15
  64. package/dist/lib/template_manager/config/config_loader.d.ts.map +1 -0
  65. package/dist/lib/template_manager/config/config_loader.js +98 -0
  66. package/dist/lib/template_manager/config/config_loader.js.map +1 -0
  67. package/dist/{template_manager → lib/template_manager}/config/constants.d.ts +4 -54
  68. package/dist/lib/template_manager/config/constants.d.ts.map +1 -0
  69. package/dist/{template_manager → lib/template_manager}/config/constants.js +4 -84
  70. package/dist/lib/template_manager/config/constants.js.map +1 -0
  71. package/dist/lib/template_manager/config/index.d.ts.map +1 -0
  72. package/dist/lib/template_manager/config/index.js.map +1 -0
  73. package/dist/{template_manager → lib/template_manager}/db/category_repository.d.ts +22 -14
  74. package/dist/lib/template_manager/db/category_repository.d.ts.map +1 -0
  75. package/dist/{template_manager → lib/template_manager}/db/category_repository.js +127 -83
  76. package/dist/lib/template_manager/db/category_repository.js.map +1 -0
  77. package/dist/lib/template_manager/db/index.d.ts +6 -0
  78. package/dist/lib/template_manager/db/index.d.ts.map +1 -0
  79. package/dist/lib/template_manager/db/index.js +6 -0
  80. package/dist/lib/template_manager/db/index.js.map +1 -0
  81. package/dist/{template_manager → lib/template_manager}/db/template_repository.d.ts +25 -28
  82. package/dist/lib/template_manager/db/template_repository.d.ts.map +1 -0
  83. package/dist/lib/template_manager/db/template_repository.js +507 -0
  84. package/dist/lib/template_manager/db/template_repository.js.map +1 -0
  85. package/dist/lib/template_manager/engine/handlebars_engine.d.ts.map +1 -0
  86. package/dist/lib/template_manager/engine/handlebars_engine.js.map +1 -0
  87. package/dist/lib/template_manager/engine/index.d.ts.map +1 -0
  88. package/dist/lib/template_manager/engine/index.js.map +1 -0
  89. package/dist/{template_manager → lib/template_manager}/engine/variable_resolver.d.ts +13 -14
  90. package/dist/lib/template_manager/engine/variable_resolver.d.ts.map +1 -0
  91. package/dist/{template_manager → lib/template_manager}/engine/variable_resolver.js +25 -33
  92. package/dist/lib/template_manager/engine/variable_resolver.js.map +1 -0
  93. package/dist/lib/template_manager/index.d.ts +56 -0
  94. package/dist/lib/template_manager/index.d.ts.map +1 -0
  95. package/dist/lib/template_manager/index.js +68 -0
  96. package/dist/lib/template_manager/index.js.map +1 -0
  97. package/dist/lib/template_manager/init.d.ts +54 -0
  98. package/dist/lib/template_manager/init.d.ts.map +1 -0
  99. package/dist/lib/template_manager/init.js +85 -0
  100. package/dist/lib/template_manager/init.js.map +1 -0
  101. package/dist/lib/template_manager/registry.d.ts +23 -0
  102. package/dist/lib/template_manager/registry.d.ts.map +1 -0
  103. package/dist/lib/template_manager/registry.js +33 -0
  104. package/dist/lib/template_manager/registry.js.map +1 -0
  105. package/dist/lib/template_manager/seed/sync.d.ts +8 -0
  106. package/dist/lib/template_manager/seed/sync.d.ts.map +1 -0
  107. package/dist/lib/template_manager/seed/sync.js +77 -0
  108. package/dist/lib/template_manager/seed/sync.js.map +1 -0
  109. package/dist/{template_manager → lib/template_manager}/template_manager.d.ts +25 -27
  110. package/dist/lib/template_manager/template_manager.d.ts.map +1 -0
  111. package/dist/{template_manager → lib/template_manager}/template_manager.js +41 -55
  112. package/dist/lib/template_manager/template_manager.js.map +1 -0
  113. package/dist/{template_manager → lib/template_manager}/types.d.ts +32 -125
  114. package/dist/lib/template_manager/types.d.ts.map +1 -0
  115. package/dist/lib/template_manager/types.js +12 -0
  116. package/dist/lib/template_manager/types.js.map +1 -0
  117. package/dist/lib/template_manager/utils/index.d.ts.map +1 -0
  118. package/dist/lib/template_manager/utils/index.js.map +1 -0
  119. package/dist/lib/template_manager/utils/system_variables.d.ts.map +1 -0
  120. package/dist/lib/template_manager/utils/system_variables.js.map +1 -0
  121. package/dist/lib/template_manager/utils/validation.d.ts.map +1 -0
  122. package/dist/lib/template_manager/utils/validation.js.map +1 -0
  123. package/dist/lib/utils.d.ts +3 -0
  124. package/dist/lib/utils.d.ts.map +1 -0
  125. package/dist/lib/utils.js +6 -0
  126. package/dist/lib/utils.js.map +1 -0
  127. package/migrations/002_scope_migration.sql +93 -0
  128. package/package.json +57 -37
  129. package/dist/emailer/emailer.d.ts.map +0 -1
  130. package/dist/emailer/emailer.js.map +0 -1
  131. package/dist/emailer/index.d.ts.map +0 -1
  132. package/dist/emailer/index.js.map +0 -1
  133. package/dist/emailer/providers/index.d.ts.map +0 -1
  134. package/dist/emailer/providers/index.js.map +0 -1
  135. package/dist/emailer/providers/pop3_provider.d.ts.map +0 -1
  136. package/dist/emailer/providers/pop3_provider.js.map +0 -1
  137. package/dist/emailer/providers/smtp_provider.d.ts.map +0 -1
  138. package/dist/emailer/providers/smtp_provider.js.map +0 -1
  139. package/dist/emailer/providers/zeptomail_provider.d.ts.map +0 -1
  140. package/dist/emailer/providers/zeptomail_provider.js.map +0 -1
  141. package/dist/emailer/types.d.ts.map +0 -1
  142. package/dist/emailer/types.js.map +0 -1
  143. package/dist/emailer/utils/constants.d.ts.map +0 -1
  144. package/dist/emailer/utils/constants.js.map +0 -1
  145. package/dist/emailer/utils/index.d.ts.map +0 -1
  146. package/dist/emailer/utils/index.js.map +0 -1
  147. package/dist/emailer/utils/logger.d.ts.map +0 -1
  148. package/dist/emailer/utils/logger.js.map +0 -1
  149. package/dist/emailer/utils/validation.d.ts.map +0 -1
  150. package/dist/emailer/utils/validation.js.map +0 -1
  151. package/dist/index.d.ts.map +0 -1
  152. package/dist/index.js.map +0 -1
  153. package/dist/template_manager/config/config_loader.d.ts.map +0 -1
  154. package/dist/template_manager/config/config_loader.js +0 -154
  155. package/dist/template_manager/config/config_loader.js.map +0 -1
  156. package/dist/template_manager/config/constants.d.ts.map +0 -1
  157. package/dist/template_manager/config/constants.js.map +0 -1
  158. package/dist/template_manager/config/index.d.ts.map +0 -1
  159. package/dist/template_manager/config/index.js.map +0 -1
  160. package/dist/template_manager/db/category_repository.d.ts.map +0 -1
  161. package/dist/template_manager/db/category_repository.js.map +0 -1
  162. package/dist/template_manager/db/index.d.ts +0 -6
  163. package/dist/template_manager/db/index.d.ts.map +0 -1
  164. package/dist/template_manager/db/index.js +0 -6
  165. package/dist/template_manager/db/index.js.map +0 -1
  166. package/dist/template_manager/db/template_repository.d.ts.map +0 -1
  167. package/dist/template_manager/db/template_repository.js +0 -674
  168. package/dist/template_manager/db/template_repository.js.map +0 -1
  169. package/dist/template_manager/engine/handlebars_engine.d.ts.map +0 -1
  170. package/dist/template_manager/engine/handlebars_engine.js.map +0 -1
  171. package/dist/template_manager/engine/index.d.ts.map +0 -1
  172. package/dist/template_manager/engine/index.js.map +0 -1
  173. package/dist/template_manager/engine/variable_resolver.d.ts.map +0 -1
  174. package/dist/template_manager/engine/variable_resolver.js.map +0 -1
  175. package/dist/template_manager/index.d.ts +0 -32
  176. package/dist/template_manager/index.d.ts.map +0 -1
  177. package/dist/template_manager/index.js +0 -41
  178. package/dist/template_manager/index.js.map +0 -1
  179. package/dist/template_manager/template_manager.d.ts.map +0 -1
  180. package/dist/template_manager/template_manager.js.map +0 -1
  181. package/dist/template_manager/types.d.ts.map +0 -1
  182. package/dist/template_manager/types.js +0 -11
  183. package/dist/template_manager/types.js.map +0 -1
  184. package/dist/template_manager/utils/index.d.ts.map +0 -1
  185. package/dist/template_manager/utils/index.js.map +0 -1
  186. package/dist/template_manager/utils/system_variables.d.ts.map +0 -1
  187. package/dist/template_manager/utils/system_variables.js.map +0 -1
  188. package/dist/template_manager/utils/validation.d.ts.map +0 -1
  189. package/dist/template_manager/utils/validation.js.map +0 -1
  190. /package/dist/{emailer → lib/emailer}/emailer.d.ts +0 -0
  191. /package/dist/{emailer → lib/emailer}/emailer.js +0 -0
  192. /package/dist/{emailer → lib/emailer}/index.d.ts +0 -0
  193. /package/dist/{emailer → lib/emailer}/index.js +0 -0
  194. /package/dist/{emailer → lib/emailer}/providers/index.d.ts +0 -0
  195. /package/dist/{emailer → lib/emailer}/providers/index.js +0 -0
  196. /package/dist/{emailer → lib/emailer}/providers/pop3_provider.d.ts +0 -0
  197. /package/dist/{emailer → lib/emailer}/providers/pop3_provider.js +0 -0
  198. /package/dist/{emailer → lib/emailer}/providers/smtp_provider.d.ts +0 -0
  199. /package/dist/{emailer → lib/emailer}/providers/smtp_provider.js +0 -0
  200. /package/dist/{emailer → lib/emailer}/providers/zeptomail_provider.d.ts +0 -0
  201. /package/dist/{emailer → lib/emailer}/types.d.ts +0 -0
  202. /package/dist/{emailer → lib/emailer}/types.js +0 -0
  203. /package/dist/{emailer → lib/emailer}/utils/constants.d.ts +0 -0
  204. /package/dist/{emailer → lib/emailer}/utils/constants.js +0 -0
  205. /package/dist/{emailer → lib/emailer}/utils/index.d.ts +0 -0
  206. /package/dist/{emailer → lib/emailer}/utils/index.js +0 -0
  207. /package/dist/{emailer → lib/emailer}/utils/logger.d.ts +0 -0
  208. /package/dist/{emailer → lib/emailer}/utils/logger.js +0 -0
  209. /package/dist/{emailer → lib/emailer}/utils/validation.d.ts +0 -0
  210. /package/dist/{emailer → lib/emailer}/utils/validation.js +0 -0
  211. /package/dist/{index.d.ts → lib/index.d.ts} +0 -0
  212. /package/dist/{index.js → lib/index.js} +0 -0
  213. /package/dist/{template_manager → lib/template_manager}/config/index.d.ts +0 -0
  214. /package/dist/{template_manager → lib/template_manager}/config/index.js +0 -0
  215. /package/dist/{template_manager → lib/template_manager}/engine/handlebars_engine.d.ts +0 -0
  216. /package/dist/{template_manager → lib/template_manager}/engine/handlebars_engine.js +0 -0
  217. /package/dist/{template_manager → lib/template_manager}/engine/index.d.ts +0 -0
  218. /package/dist/{template_manager → lib/template_manager}/engine/index.js +0 -0
  219. /package/dist/{template_manager → lib/template_manager}/utils/index.d.ts +0 -0
  220. /package/dist/{template_manager → lib/template_manager}/utils/index.js +0 -0
  221. /package/dist/{template_manager → lib/template_manager}/utils/system_variables.d.ts +0 -0
  222. /package/dist/{template_manager → lib/template_manager}/utils/system_variables.js +0 -0
  223. /package/dist/{template_manager → lib/template_manager}/utils/validation.d.ts +0 -0
  224. /package/dist/{template_manager → lib/template_manager}/utils/validation.js +0 -0
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Hazo Notify
2
2
 
3
- A reusable component library for sending email notifications with template management. Implements Zeptomail API integration with support for text and HTML emails, Handlebars-based email templates, multiple attachments, and comprehensive security features.
3
+ A reusable component library for sending email notifications with scope-aware template management. Implements Zeptomail API integration with support for text and HTML emails, Handlebars-based email templates with multi-tenant resolution, multiple attachments, and comprehensive security features.
4
+
5
+ **Version**: 3.0.0
4
6
 
5
7
  ## Features
6
8
 
@@ -34,6 +36,18 @@ The package automatically installs required dependencies including `hazo_config`
34
36
 
35
37
  **Note:** This package is an ESM module (`"type": "module"`) and requires Node.js 18+ with ESM support.
36
38
 
39
+ ### Upgrading from v1.x to v2.0.0
40
+
41
+ v2.0.0 introduces scope-aware template management, which requires a database migration:
42
+
43
+ 1. **Run database migration**: Execute `migrations/002_scope_migration.sql` against your database to rename `org_id` and `root_org_id` columns to `scope_id`.
44
+ 2. **Update environment variables**: Add `HAZO_NOTIFY_CORS_ORIGINS` to `.env.local` or configure `cors_allowed_origins` in `hazo_notify_config.ini`.
45
+ 3. **Add auth permissions**: Register `notify_templates_admin` and `notify_templates_super_admin` roles in your auth system.
46
+ 4. **Initialize template manager**: Call `init_template_manager(...)` in your `instrumentation.ts` or app bootstrap.
47
+ 5. **Update Tailwind config** (if using admin UI): Add `@source "../node_modules/hazo_notify/dist";` to your Tailwind CSS entry.
48
+
49
+ The `send_email()` API is fully backward-compatible; no changes required for existing code.
50
+
37
51
  ### Optional: Enhanced Logging with hazo_logs
38
52
 
39
53
  For structured logging with file rotation, install the optional `hazo_logs` peer dependency:
@@ -74,30 +88,28 @@ The template manager requires two tables:
74
88
 
75
89
  | Table | Purpose |
76
90
  |-------|---------|
77
- | `hazo_notify_template_cat` | Template categories (groups templates per organization) |
91
+ | `hazo_notify_template_cat` | Template categories (groups templates per scope) |
78
92
  | `hazo_notify_templates` | Email templates with HTML, text, and variable definitions |
79
93
 
80
- Both tables are scoped by `org_id` and `root_org_id` for multi-tenant isolation. `hazo_notify_templates.template_category_id` references `hazo_notify_template_cat.id`.
94
+ Both tables use `scope_id` (nullable UUID) for multi-tenant isolation: NULL = global/system templates; non-NULL = tenant-scoped templates. `hazo_notify_templates.template_category_id` references `hazo_notify_template_cat.id`.
81
95
 
82
- #### PostgreSQL Schema
96
+ #### PostgreSQL Schema (v2.0.0+)
83
97
 
84
98
  ```sql
85
99
  CREATE TABLE IF NOT EXISTS hazo_notify_template_cat (
86
100
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
87
- org_id UUID NOT NULL,
88
- root_org_id UUID NOT NULL,
101
+ scope_id UUID,
89
102
  template_category_name VARCHAR(100) NOT NULL,
90
103
  created_at TIMESTAMPTZ DEFAULT NOW(),
91
104
  changed_at TIMESTAMPTZ
92
105
  );
93
106
 
94
- CREATE INDEX IF NOT EXISTS idx_notify_template_cat_org
95
- ON hazo_notify_template_cat (org_id);
107
+ CREATE INDEX IF NOT EXISTS idx_notify_template_cat_scope
108
+ ON hazo_notify_template_cat (scope_id);
96
109
 
97
110
  CREATE TABLE IF NOT EXISTS hazo_notify_templates (
98
111
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
99
- org_id UUID NOT NULL,
100
- root_org_id UUID NOT NULL,
112
+ scope_id UUID,
101
113
  template_category_id UUID NOT NULL REFERENCES hazo_notify_template_cat(id),
102
114
  template_variables JSONB DEFAULT '{}',
103
115
  template_name VARCHAR(100) NOT NULL,
@@ -108,35 +120,35 @@ CREATE TABLE IF NOT EXISTS hazo_notify_templates (
108
120
  changed_at TIMESTAMPTZ
109
121
  );
110
122
 
111
- CREATE INDEX IF NOT EXISTS idx_notify_templates_org
112
- ON hazo_notify_templates (org_id);
123
+ CREATE INDEX IF NOT EXISTS idx_notify_templates_scope
124
+ ON hazo_notify_templates (scope_id);
113
125
 
114
126
  CREATE INDEX IF NOT EXISTS idx_notify_templates_category
115
127
  ON hazo_notify_templates (template_category_id);
116
128
 
117
129
  CREATE INDEX IF NOT EXISTS idx_notify_templates_name
118
- ON hazo_notify_templates (org_id, template_name);
130
+ ON hazo_notify_templates (scope_id, template_name);
119
131
  ```
120
132
 
121
- #### SQLite Schema
133
+ **Note**: `scope_id` is nullable. NULL represents global/system templates; non-NULL represents tenant-scoped templates.
134
+
135
+ #### SQLite Schema (v2.0.0+)
122
136
 
123
137
  ```sql
124
138
  CREATE TABLE IF NOT EXISTS hazo_notify_template_cat (
125
139
  id TEXT PRIMARY KEY,
126
- org_id TEXT NOT NULL,
127
- root_org_id TEXT NOT NULL,
140
+ scope_id TEXT,
128
141
  template_category_name TEXT NOT NULL,
129
142
  created_at TEXT DEFAULT (datetime('now')),
130
143
  changed_at TEXT
131
144
  );
132
145
 
133
- CREATE INDEX IF NOT EXISTS idx_notify_template_cat_org
134
- ON hazo_notify_template_cat (org_id);
146
+ CREATE INDEX IF NOT EXISTS idx_notify_template_cat_scope
147
+ ON hazo_notify_template_cat (scope_id);
135
148
 
136
149
  CREATE TABLE IF NOT EXISTS hazo_notify_templates (
137
150
  id TEXT PRIMARY KEY,
138
- org_id TEXT NOT NULL,
139
- root_org_id TEXT NOT NULL,
151
+ scope_id TEXT,
140
152
  template_category_id TEXT NOT NULL REFERENCES hazo_notify_template_cat(id),
141
153
  template_variables TEXT DEFAULT '{}',
142
154
  template_name TEXT NOT NULL,
@@ -147,21 +159,24 @@ CREATE TABLE IF NOT EXISTS hazo_notify_templates (
147
159
  changed_at TEXT
148
160
  );
149
161
 
150
- CREATE INDEX IF NOT EXISTS idx_notify_templates_org
151
- ON hazo_notify_templates (org_id);
162
+ CREATE INDEX IF NOT EXISTS idx_notify_templates_scope
163
+ ON hazo_notify_templates (scope_id);
152
164
 
153
165
  CREATE INDEX IF NOT EXISTS idx_notify_templates_category
154
166
  ON hazo_notify_templates (template_category_id);
155
167
 
156
168
  CREATE INDEX IF NOT EXISTS idx_notify_templates_name
157
- ON hazo_notify_templates (org_id, template_name);
169
+ ON hazo_notify_templates (scope_id, template_name);
158
170
  ```
159
171
 
172
+ **Note**: `scope_id` is nullable. NULL represents global/system templates; non-NULL represents tenant-scoped templates.
173
+
160
174
  #### Type Mapping (PostgreSQL ↔ SQLite)
161
175
 
162
176
  | Concept | PostgreSQL | SQLite |
163
177
  |---------|------------|--------|
164
178
  | Primary / foreign keys | `UUID` (with `gen_random_uuid()` default) | `TEXT` (UUID string supplied by app) |
179
+ | Scope key (`scope_id`) | `UUID` (nullable) | `TEXT` (nullable, UUID string or NULL) |
165
180
  | JSON column (`template_variables`) | `JSONB` | `TEXT` (serialized JSON) |
166
181
  | Timestamps | `TIMESTAMPTZ` with `NOW()` default | `TEXT` with `datetime('now')` default |
167
182
 
@@ -298,16 +313,19 @@ const result = await send_email({
298
313
  ```typescript
299
314
  {
300
315
  success: true,
301
- message_id: 'msg_abc123def456',
316
+ // message_id is the request_id Zeptomail assigns to the send. Use it for
317
+ // delivery lookup in Zeptomail logs.
318
+ message_id: '7a6803.5aa56c8edd39e440.m1.…',
302
319
  message: 'Email sent successfully',
303
320
  raw_response: {
304
- status: 200,
305
- status_text: 'OK',
321
+ status: 201,
322
+ status_text: '',
306
323
  headers: { /* response headers */ },
307
324
  body: {
308
- data: {
309
- message_id: 'msg_abc123def456'
310
- }
325
+ data: [{ code: 'EM_104', additional_info: [], message: 'Email request received' }],
326
+ message: 'OK',
327
+ request_id: '7a6803.5aa56c8edd39e440.m1.…',
328
+ object: 'email'
311
329
  }
312
330
  }
313
331
  }
@@ -552,20 +570,18 @@ const result = await send_email({
552
570
  ```typescript
553
571
  {
554
572
  success: true,
555
- message_id: 'msg_xyz789abc123',
573
+ // message_id is Zeptomail's request_id (the unique identifier for the send)
574
+ message_id: '7a6803.5aa56c8edd39e440.m1.…',
556
575
  message: 'Email sent successfully',
557
576
  raw_response: {
558
- status: 200,
559
- status_text: 'OK',
560
- headers: {
561
- 'content-type': 'application/json',
562
- 'content-length': '156',
563
- // ... other headers
564
- },
577
+ status: 201,
578
+ status_text: '',
579
+ headers: { /* response headers */ },
565
580
  body: {
566
- data: {
567
- message_id: 'msg_xyz789abc123'
568
- }
581
+ data: [{ code: 'EM_104', additional_info: [], message: 'Email request received' }],
582
+ message: 'OK',
583
+ request_id: '7a6803.5aa56c8edd39e440.m1.…',
584
+ object: 'email'
569
585
  }
570
586
  }
571
587
  }
@@ -797,9 +813,115 @@ console.log('Emailer module:', config.emailer_module);
797
813
  console.log('From email:', config.from_email);
798
814
  ```
799
815
 
816
+ ## Template Manager (v2.0.0+)
817
+
818
+ The scope-aware template manager allows you to define, store, and render email templates with multi-tenant isolation. Templates are stored in the database and support hierarchical resolution: tenant-scoped templates take precedence over global system templates.
819
+
820
+ ### Initialization
821
+
822
+ In your app's `instrumentation.ts` or bootstrap code, initialize the template manager once:
823
+
824
+ ```typescript
825
+ import { init_template_manager, register_template_type } from 'hazo_notify/template_manager';
826
+ import { createHazoConnect } from 'hazo_connect/server';
827
+
828
+ export async function register() {
829
+ const hazo_connect = await createHazoConnect();
830
+
831
+ // Register template types (optional, but recommended)
832
+ register_template_type({
833
+ type_id: 'welcome_email',
834
+ display_name: 'Welcome Email',
835
+ variables: {
836
+ user_name: { type: 'string', description: 'User full name' },
837
+ login_url: { type: 'string', description: 'Link to login page' }
838
+ }
839
+ });
840
+
841
+ // Initialize template manager with optional scope resolver
842
+ await init_template_manager({
843
+ hazo_connect,
844
+ scope_resolver: async (scope_id) => {
845
+ // Optional: resolve tenant hierarchy (e.g., org > parent_org > system)
846
+ // Return { scope_id, parent_scope_id? }
847
+ return { scope_id };
848
+ },
849
+ system_templates: [
850
+ // Optional: define system templates to auto-seed
851
+ {
852
+ template_name: 'welcome_email',
853
+ type_id: 'welcome_email',
854
+ template_html: '<h1>Welcome, {{user_name}}!</h1><p><a href="{{login_url}}">Log in here</a></p>',
855
+ template_text: 'Welcome, {{user_name}}! Visit {{login_url}} to log in.'
856
+ }
857
+ ]
858
+ });
859
+ }
860
+ ```
861
+
862
+ ### Sending Template Emails
863
+
864
+ ```typescript
865
+ import { send_template_email } from 'hazo_notify/template_manager';
866
+
867
+ const result = await send_template_email({
868
+ template_name: 'welcome_email',
869
+ variables: {
870
+ user_name: 'Alice',
871
+ login_url: 'https://app.example.com/login'
872
+ },
873
+ to: 'alice@example.com',
874
+ subject: 'Welcome to Our App'
875
+ }, hazo_connect, scope_id);
876
+
877
+ if (result.success) {
878
+ console.log('Email sent:', result.message_id);
879
+ } else {
880
+ console.error('Failed:', result.error);
881
+ }
882
+ ```
883
+
884
+ ### Admin UI
885
+
886
+ Mount the admin UI in a protected Next.js page:
887
+
888
+ ```typescript
889
+ // app/admin/templates/page.tsx
890
+ import { TemplateManagerAdmin, TemplateGlobalsAdmin } from 'hazo_notify/template_manager_admin';
891
+ import { requireAdmin } from '@/lib/auth'; // Your auth check
892
+
893
+ export default async function TemplatesPage() {
894
+ await requireAdmin();
895
+
896
+ return (
897
+ <div className="space-y-8">
898
+ <section>
899
+ <h1>Tenant Templates</h1>
900
+ <TemplateManagerAdmin scope_id={current_tenant_id} />
901
+ </section>
902
+ <section>
903
+ <h1>System Templates (Global)</h1>
904
+ <TemplateGlobalsAdmin />
905
+ </section>
906
+ </div>
907
+ );
908
+ }
909
+ ```
910
+
911
+ **Permission requirements**:
912
+ - `notify_templates_admin` — read/write templates
913
+ - `notify_templates_super_admin` — delete system templates
914
+
915
+ **Tailwind v4 requirement**: If using the admin UI, add this to your `globals.css`:
916
+
917
+ ```css
918
+ @import "tailwindcss";
919
+ @source "../node_modules/hazo_notify/dist";
920
+ ```
921
+
800
922
  ### Template Manager API
801
923
 
802
- #### `send_template_email(options, hazo_connect, org_id, config?): Promise<SendTemplateEmailResponse>`
924
+ #### `send_template_email(options, hazo_connect, scope_id, config?): Promise<SendTemplateEmailResponse>`
803
925
 
804
926
  Render a named template and send it as an email.
805
927
 
@@ -0,0 +1,11 @@
1
+ import type { TemplateCategory } from '../../lib/template_manager/index.js';
2
+ interface CategoryDialogProps {
3
+ open: boolean;
4
+ on_close: () => void;
5
+ on_save: (name: string) => void;
6
+ category?: TemplateCategory | null;
7
+ loading?: boolean;
8
+ }
9
+ export declare function CategoryDialog({ open, on_close, on_save, category, loading, }: CategoryDialogProps): import("react/jsx-runtime").JSX.Element;
10
+ export default CategoryDialog;
11
+ //# sourceMappingURL=category_dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category_dialog.d.ts","sourceRoot":"","sources":["../../../src/components/template_manager/category_dialog.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAE5E,UAAU,mBAAmB;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,OAAe,GAChB,EAAE,mBAAmB,2CAoFrB;AAED,eAAe,cAAc,CAAC"}
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Category Dialog Component
5
+ *
6
+ * Dialog for creating and editing template categories
7
+ */
8
+ import { useState, useEffect } from 'react';
9
+ import { Button, Input, Label, HazoUiDialogRoot, HazoUiDialogContent, HazoUiDialogDescription, HazoUiDialogFooter, HazoUiDialogHeader, HazoUiDialogTitle, } from 'hazo_ui';
10
+ export function CategoryDialog({ open, on_close, on_save, category, loading = false, }) {
11
+ const [name, set_name] = useState('');
12
+ const [error, set_error] = useState(null);
13
+ const is_edit = !!category;
14
+ useEffect(() => {
15
+ if (open) {
16
+ set_name(category?.template_category_name || '');
17
+ set_error(null);
18
+ }
19
+ }, [open, category]);
20
+ const handle_submit = (e) => {
21
+ e.preventDefault();
22
+ const trimmed = name.trim();
23
+ if (!trimmed) {
24
+ set_error('Category name is required');
25
+ return;
26
+ }
27
+ if (trimmed.length > 100) {
28
+ set_error('Category name must be 100 characters or less');
29
+ return;
30
+ }
31
+ on_save(trimmed);
32
+ };
33
+ return (_jsx(HazoUiDialogRoot, { open: open, onOpenChange: (open) => !open && on_close(), children: _jsx(HazoUiDialogContent, { className: "cls_category_dialog sm:max-w-[425px]", children: _jsxs("form", { onSubmit: handle_submit, children: [_jsxs(HazoUiDialogHeader, { children: [_jsx(HazoUiDialogTitle, { children: is_edit ? 'Edit Category' : 'New Category' }), _jsx(HazoUiDialogDescription, { children: is_edit
34
+ ? 'Update the category name.'
35
+ : 'Create a new template category to organize your email templates.' })] }), _jsx("div", { className: "cls_dialog_body grid gap-4 py-4", children: _jsxs("div", { className: "cls_form_field grid gap-2", children: [_jsx(Label, { htmlFor: "category_name", children: "Category Name" }), _jsx(Input, { id: "category_name", value: name, onChange: (e) => {
36
+ set_name(e.target.value);
37
+ set_error(null);
38
+ }, placeholder: "e.g., Marketing, Transactional, Notifications", disabled: loading, autoFocus: true }), error && (_jsx("p", { className: "cls_error_message text-sm text-destructive", children: error }))] }) }), _jsxs(HazoUiDialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: on_close, disabled: loading, children: "Cancel" }), _jsx(Button, { type: "submit", disabled: loading, children: loading ? 'Saving...' : is_edit ? 'Save Changes' : 'Create' })] })] }) }) }));
39
+ }
40
+ export default CategoryDialog;
41
+ //# sourceMappingURL=category_dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category_dialog.js","sourceRoot":"","sources":["../../../src/components/template_manager/category_dialog.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { TemplateCategory, EmailTemplate, CategoryTreeItem } from '../../lib/template_manager/index.js';
2
+ interface CategoryTreeProps {
3
+ categories: CategoryTreeItem[];
4
+ selected_template_id?: string | null;
5
+ on_select_template: (template: EmailTemplate) => void;
6
+ on_create_category: () => void;
7
+ on_delete_category: (category: TemplateCategory) => void;
8
+ on_create_template: (category: TemplateCategory) => void;
9
+ loading?: boolean;
10
+ }
11
+ export declare function CategoryTree({ categories, selected_template_id, on_select_template, on_create_category, on_delete_category, on_create_template, loading, }: CategoryTreeProps): import("react/jsx-runtime").JSX.Element;
12
+ export default CategoryTree;
13
+ //# sourceMappingURL=category_tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category_tree.d.ts","sourceRoot":"","sources":["../../../src/components/template_manager/category_tree.tsx"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EACjB,MAAM,qCAAqC,CAAC;AAE7C,UAAU,iBAAiB;IACzB,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,kBAAkB,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,kBAAkB,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACzD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,EAC3B,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,OAAe,GAChB,EAAE,iBAAiB,2CAiFnB;AAwHD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Category Tree Component
5
+ *
6
+ * Displays template categories in an expandable tree structure
7
+ */
8
+ import { useState, useEffect } from 'react';
9
+ import { ChevronRight, ChevronDown, Plus, Trash2, FileText, FolderOpen } from 'lucide-react';
10
+ import { Button, Card, CardContent, CardHeader, CardTitle, ScrollArea, Collapsible, CollapsibleContent, CollapsibleTrigger, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from 'hazo_ui';
11
+ import { cn } from '../../lib/utils.js';
12
+ export function CategoryTree({ categories, selected_template_id, on_select_template, on_create_category, on_delete_category, on_create_template, loading = false, }) {
13
+ const [expanded_categories, set_expanded_categories] = useState(new Set());
14
+ const toggle_category = (category_id) => {
15
+ set_expanded_categories((prev) => {
16
+ const next = new Set(prev);
17
+ if (next.has(category_id)) {
18
+ next.delete(category_id);
19
+ }
20
+ else {
21
+ next.add(category_id);
22
+ }
23
+ return next;
24
+ });
25
+ };
26
+ // Expand categories that contain the selected template
27
+ useEffect(() => {
28
+ if (selected_template_id) {
29
+ for (const item of categories) {
30
+ const has_selected = item.templates.some((t) => t.id === selected_template_id);
31
+ if (has_selected) {
32
+ set_expanded_categories((prev) => new Set(prev).add(item.category.id));
33
+ }
34
+ }
35
+ }
36
+ }, [selected_template_id, categories]);
37
+ return (_jsxs(Card, { className: "cls_category_tree h-full", children: [_jsx(CardHeader, { className: "cls_category_tree_header pb-2", children: _jsx("div", { className: "cls_category_tree_title_row flex items-center justify-between", children: _jsx(CardTitle, { className: "text-lg", children: "Templates" }) }) }), _jsx(CardContent, { className: "cls_category_tree_content p-2", children: _jsx(ScrollArea, { className: "h-[calc(100vh-200px)]", children: _jsxs("div", { className: "cls_category_list space-y-1", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "cls_add_category_btn w-full justify-start text-primary hover:text-primary hover:bg-primary/10", onClick: on_create_category, disabled: loading, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "New Category"] }), loading ? (_jsx("div", { className: "cls_loading_state p-4 text-center text-muted-foreground", children: "Loading..." })) : categories.length === 0 ? (_jsx("div", { className: "cls_empty_state p-4 text-center text-muted-foreground", children: "No categories yet" })) : (categories.map((item) => (_jsx(CategoryItem, { item: item, is_expanded: expanded_categories.has(item.category.id), selected_template_id: selected_template_id, on_toggle: () => toggle_category(item.category.id), on_select_template: on_select_template, on_delete_category: () => on_delete_category(item.category), on_create_template: () => on_create_template(item.category) }, item.category.id))))] }) }) })] }));
38
+ }
39
+ function CategoryItem({ item, is_expanded, selected_template_id, on_toggle, on_select_template, on_delete_category, on_create_template, }) {
40
+ const { category, templates } = item;
41
+ return (_jsx(Collapsible, { open: is_expanded, onOpenChange: on_toggle, children: _jsxs("div", { className: "cls_category_item group", children: [_jsxs("div", { className: "cls_category_header flex items-center", children: [_jsx(CollapsibleTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "cls_category_toggle flex-1 justify-start p-2 h-auto", children: [is_expanded ? (_jsx(ChevronDown, { className: "h-4 w-4 mr-1 shrink-0" })) : (_jsx(ChevronRight, { className: "h-4 w-4 mr-1 shrink-0" })), _jsx(FolderOpen, { className: "h-4 w-4 mr-2 text-muted-foreground shrink-0" }), _jsx("span", { className: "truncate", children: category.template_category_name }), _jsx("span", { className: "ml-auto text-xs text-muted-foreground", children: templates.length })] }) }), _jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "cls_delete_category_btn h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity text-destructive hover:text-destructive hover:bg-destructive/10", children: _jsx(Trash2, { className: "h-4 w-4" }) }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Delete Category" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to delete \"", category.template_category_name, "\"? This will also delete all ", templates.length, " templates in this category. This action cannot be undone."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Cancel" }), _jsx(AlertDialogAction, { onClick: on_delete_category, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Delete" })] })] })] })] }), _jsx(CollapsibleContent, { children: _jsxs("div", { className: "cls_template_list ml-4 border-l border-border pl-2 space-y-1", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "cls_add_template_btn w-full justify-start text-sm text-primary hover:text-primary hover:bg-primary/10 h-8", onClick: on_create_template, children: [_jsx(Plus, { className: "h-3 w-3 mr-2" }), "New Template"] }), templates.map((template) => (_jsxs(Button, { variant: "ghost", size: "sm", className: cn('cls_template_item w-full justify-start text-sm h-8', selected_template_id === template.id &&
42
+ 'bg-accent text-accent-foreground'), onClick: () => on_select_template(template), children: [_jsx(FileText, { className: "h-3 w-3 mr-2 shrink-0" }), _jsx("span", { className: "truncate", children: template.template_name }), template.scope_id === null && (_jsx("span", { className: "ml-auto text-xs text-muted-foreground bg-muted px-1 rounded", children: "global" }))] }, template.id)))] }) })] }) }));
43
+ }
44
+ export default CategoryTree;
45
+ //# sourceMappingURL=category_tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category_tree.js","sourceRoot":"","sources":["../../../src/components/template_manager/category_tree.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export { CategoryTree } from './category_tree.js';
2
+ export { CategoryDialog } from './category_dialog.js';
3
+ export { TemplateDialog } from './template_dialog.js';
4
+ export { TemplateEditor } from './template_editor.js';
5
+ export { PreviewDialog } from './preview_dialog.js';
6
+ export { TemplateManagerAdmin } from './template_manager_admin.js';
7
+ export type { TemplateManagerAdminProps } from './template_manager_admin.js';
8
+ export { TemplateGlobalsAdmin } from './template_globals_admin.js';
9
+ export type { TemplateGlobalsAdminProps } from './template_globals_admin.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/template_manager/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,YAAY,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,YAAY,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { CategoryTree } from './category_tree.js';
2
+ export { CategoryDialog } from './category_dialog.js';
3
+ export { TemplateDialog } from './template_dialog.js';
4
+ export { TemplateEditor } from './template_editor.js';
5
+ export { PreviewDialog } from './preview_dialog.js';
6
+ export { TemplateManagerAdmin } from './template_manager_admin.js';
7
+ export { TemplateGlobalsAdmin } from './template_globals_admin.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/components/template_manager/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,11 @@
1
+ interface PreviewDialogProps {
2
+ open: boolean;
3
+ on_close: () => void;
4
+ rendered_html?: string;
5
+ rendered_text?: string;
6
+ loading?: boolean;
7
+ error?: string;
8
+ }
9
+ export declare function PreviewDialog({ open, on_close, rendered_html, rendered_text, loading, error, }: PreviewDialogProps): import("react/jsx-runtime").JSX.Element;
10
+ export default PreviewDialog;
11
+ //# sourceMappingURL=preview_dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview_dialog.d.ts","sourceRoot":"","sources":["../../../src/components/template_manager/preview_dialog.tsx"],"names":[],"mappings":"AAmBA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,aAAa,EACb,OAAe,EACf,KAAK,GACN,EAAE,kBAAkB,2CAwEpB;AAED,eAAe,aAAa,CAAC"}
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Preview Dialog Component
5
+ *
6
+ * Dialog for previewing rendered email templates
7
+ */
8
+ import { useState } from 'react';
9
+ import { Button, ScrollArea, HazoUiDialogRoot, HazoUiDialogContent, HazoUiDialogDescription, HazoUiDialogHeader, HazoUiDialogTitle, } from 'hazo_ui';
10
+ export function PreviewDialog({ open, on_close, rendered_html, rendered_text, loading = false, error, }) {
11
+ const [active_tab, set_active_tab] = useState('html');
12
+ return (_jsx(HazoUiDialogRoot, { open: open, onOpenChange: (open) => !open && on_close(), children: _jsxs(HazoUiDialogContent, { className: "cls_preview_dialog max-w-4xl max-h-[90vh]", children: [_jsxs(HazoUiDialogHeader, { children: [_jsx(HazoUiDialogTitle, { children: "Template Preview" }), _jsx(HazoUiDialogDescription, { children: "Preview how your email will look when rendered with variables." })] }), _jsxs("div", { className: "cls_preview_tabs flex gap-2 mb-4", children: [_jsx(Button, { variant: active_tab === 'html' ? 'default' : 'outline', size: "sm", onClick: () => set_active_tab('html'), children: "HTML Preview" }), _jsx(Button, { variant: active_tab === 'text' ? 'default' : 'outline', size: "sm", onClick: () => set_active_tab('text'), children: "Plain Text" })] }), _jsx(ScrollArea, { className: "h-[60vh]", children: loading ? (_jsx("div", { className: "cls_preview_loading flex items-center justify-center h-full", children: _jsx("p", { className: "text-muted-foreground", children: "Rendering preview..." }) })) : error ? (_jsxs("div", { className: "cls_preview_error p-4 bg-destructive/10 rounded-lg", children: [_jsx("p", { className: "text-destructive font-medium", children: "Error rendering preview" }), _jsx("p", { className: "text-sm text-destructive/80 mt-1", children: error })] })) : active_tab === 'html' ? (_jsx("div", { className: "cls_html_preview", children: rendered_html ? (_jsx("iframe", { srcDoc: rendered_html, title: "Email Preview", className: "w-full h-[55vh] border rounded-lg bg-white", sandbox: "allow-same-origin" })) : (_jsx("div", { className: "cls_empty_preview p-8 text-center text-muted-foreground border rounded-lg", children: "No HTML content to preview" })) })) : (_jsx("div", { className: "cls_text_preview", children: rendered_text ? (_jsx("pre", { className: "p-4 bg-muted rounded-lg text-sm whitespace-pre-wrap font-mono", children: rendered_text })) : (_jsx("div", { className: "cls_empty_preview p-8 text-center text-muted-foreground border rounded-lg", children: "No plain text content to preview" })) })) })] }) }));
13
+ }
14
+ export default PreviewDialog;
15
+ //# sourceMappingURL=preview_dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview_dialog.js","sourceRoot":"","sources":["../../../src/components/template_manager/preview_dialog.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EACL,MAAM,EACN,UAAU,EACV,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AAWjB,MAAM,UAAU,aAAa,CAAC,EAC5B,IAAI,EACJ,QAAQ,EACR,aAAa,EACb,aAAa,EACb,OAAO,GAAG,KAAK,EACf,KAAK,GACc;IACnB,MAAM,CAAC,UAAU,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAkB,MAAM,CAAC,CAAC;IAEvE,OAAO,CACL,KAAC,gBAAgB,IAAC,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,QAAQ,EAAE,YACvE,MAAC,mBAAmB,IAAC,SAAS,EAAC,2CAA2C,aACxE,MAAC,kBAAkB,eACjB,KAAC,iBAAiB,mCAAqC,EACvD,KAAC,uBAAuB,iFAEE,IACP,EAErB,eAAK,SAAS,EAAC,kCAAkC,aAC/C,KAAC,MAAM,IACL,OAAO,EAAE,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EACtD,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,6BAG9B,EACT,KAAC,MAAM,IACL,OAAO,EAAE,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EACtD,IAAI,EAAC,IAAI,EACT,OAAO,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,2BAG9B,IACL,EAEN,KAAC,UAAU,IAAC,SAAS,EAAC,UAAU,YAC7B,OAAO,CAAC,CAAC,CAAC,CACT,cAAK,SAAS,EAAC,6DAA6D,YAC1E,YAAG,SAAS,EAAC,uBAAuB,qCAAyB,GACzD,CACP,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CACV,eAAK,SAAS,EAAC,oDAAoD,aACjE,YAAG,SAAS,EAAC,8BAA8B,wCAA4B,EACvE,YAAG,SAAS,EAAC,kCAAkC,YAAE,KAAK,GAAK,IACvD,CACP,CAAC,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,CAC1B,cAAK,SAAS,EAAC,kBAAkB,YAC9B,aAAa,CAAC,CAAC,CAAC,CACf,iBACE,MAAM,EAAE,aAAa,EACrB,KAAK,EAAC,eAAe,EACrB,SAAS,EAAC,4CAA4C,EACtD,OAAO,EAAC,mBAAmB,GAC3B,CACH,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,2EAA2E,2CAEpF,CACP,GACG,CACP,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,kBAAkB,YAC9B,aAAa,CAAC,CAAC,CAAC,CACf,cAAK,SAAS,EAAC,+DAA+D,YAC3E,aAAa,GACV,CACP,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,2EAA2E,iDAEpF,CACP,GACG,CACP,GACU,IACO,GACL,CACpB,CAAC;AACJ,CAAC;AAED,eAAe,aAAa,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { TemplateCategory, TemplateTypeDefinition } from '../../lib/template_manager/index.js';
2
+ interface TemplateDialogProps {
3
+ open: boolean;
4
+ on_close: () => void;
5
+ on_save: (template_name: string) => void;
6
+ category: TemplateCategory | null;
7
+ template_types: TemplateTypeDefinition[];
8
+ loading?: boolean;
9
+ }
10
+ export declare function TemplateDialog({ open, on_close, on_save, category, template_types, loading, }: TemplateDialogProps): import("react/jsx-runtime").JSX.Element;
11
+ export default TemplateDialog;
12
+ //# sourceMappingURL=template_dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template_dialog.d.ts","sourceRoot":"","sources":["../../../src/components/template_manager/template_dialog.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAEpG,UAAU,mBAAmB;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,sBAAsB,EAAE,CAAC;IACzC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,cAAc,EACd,OAAe,GAChB,EAAE,mBAAmB,2CAiHrB;AAED,eAAe,cAAc,CAAC"}
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Template Dialog Component
5
+ *
6
+ * Dialog for creating new email templates
7
+ */
8
+ import { useState, useEffect } from 'react';
9
+ import { Button, Input, Label, HazoUiDialogRoot, HazoUiDialogContent, HazoUiDialogDescription, HazoUiDialogFooter, HazoUiDialogHeader, HazoUiDialogTitle, } from 'hazo_ui';
10
+ export function TemplateDialog({ open, on_close, on_save, category, template_types, loading = false, }) {
11
+ const [name, set_name] = useState('');
12
+ const [error, set_error] = useState(null);
13
+ useEffect(() => {
14
+ if (open) {
15
+ set_name('');
16
+ set_error(null);
17
+ }
18
+ }, [open]);
19
+ const handle_submit = (e) => {
20
+ e.preventDefault();
21
+ const trimmed = name.trim().toLowerCase().replace(/\s+/g, '_');
22
+ if (!trimmed) {
23
+ set_error('Template name is required');
24
+ return;
25
+ }
26
+ // Validate format
27
+ if (!/^[a-z][a-z0-9_]*$/.test(trimmed)) {
28
+ set_error('Template name must start with a letter and contain only lowercase letters, numbers, and underscores');
29
+ return;
30
+ }
31
+ if (trimmed.length > 100) {
32
+ set_error('Template name must be 100 characters or less');
33
+ return;
34
+ }
35
+ on_save(trimmed);
36
+ };
37
+ return (_jsx(HazoUiDialogRoot, { open: open, onOpenChange: (open) => !open && on_close(), children: _jsx(HazoUiDialogContent, { className: "cls_template_dialog sm:max-w-[500px]", children: _jsxs("form", { onSubmit: handle_submit, children: [_jsxs(HazoUiDialogHeader, { children: [_jsx(HazoUiDialogTitle, { children: "New Email Template" }), _jsxs(HazoUiDialogDescription, { children: ["Create a new email template in", ' ', _jsx("strong", { children: category?.template_category_name || 'this category' }), "."] })] }), _jsxs("div", { className: "cls_dialog_body grid gap-4 py-4", children: [_jsxs("div", { className: "cls_form_field grid gap-2", children: [_jsx(Label, { htmlFor: "template_name", children: "Template Name" }), _jsx(Input, { id: "template_name", value: name, onChange: (e) => {
38
+ set_name(e.target.value);
39
+ set_error(null);
40
+ }, placeholder: "e.g., welcome_email, order_confirmation", disabled: loading, autoFocus: true }), _jsx("p", { className: "cls_hint text-xs text-muted-foreground", children: "Use lowercase letters, numbers, and underscores only." }), error && (_jsx("p", { className: "cls_error_message text-sm text-destructive", children: error }))] }), template_types.length > 0 && (_jsxs("div", { className: "cls_template_types", children: [_jsx(Label, { className: "text-sm text-muted-foreground", children: "Suggested template types:" }), _jsx("div", { className: "cls_type_buttons flex flex-wrap gap-2 mt-2", children: template_types.map((type) => (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => {
41
+ set_name(type.template_name);
42
+ set_error(null);
43
+ }, className: "text-xs", children: type.template_label }, type.template_name))) })] }))] }), _jsxs(HazoUiDialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", onClick: on_close, disabled: loading, children: "Cancel" }), _jsx(Button, { type: "submit", disabled: loading, children: loading ? 'Creating...' : 'Create Template' })] })] }) }) }));
44
+ }
45
+ export default TemplateDialog;
46
+ //# sourceMappingURL=template_dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template_dialog.js","sourceRoot":"","sources":["../../../src/components/template_manager/template_dialog.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { EmailTemplate, TemplateTypeDefinition } from '../../lib/template_manager/index.js';
2
+ interface TemplateEditorProps {
3
+ template: EmailTemplate | null;
4
+ template_types: TemplateTypeDefinition[];
5
+ on_save: (html: string, text: string) => void;
6
+ on_delete: () => void;
7
+ on_preview: (html: string, text: string) => void;
8
+ loading?: boolean;
9
+ }
10
+ export declare function TemplateEditor({ template, template_types, on_save, on_delete, on_preview, loading, }: TemplateEditorProps): import("react/jsx-runtime").JSX.Element;
11
+ export default TemplateEditor;
12
+ //# sourceMappingURL=template_editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template_editor.d.ts","sourceRoot":"","sources":["../../../src/components/template_manager/template_editor.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EACV,aAAa,EACb,sBAAsB,EACvB,MAAM,qCAAqC,CAAC;AAG7C,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,cAAc,EAAE,sBAAsB,EAAE,CAAC;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,cAAc,EACd,OAAO,EACP,SAAS,EACT,UAAU,EACV,OAAe,GAChB,EAAE,mBAAmB,2CAoJrB;AAED,eAAe,cAAc,CAAC"}