hazo_notify 1.1.3 → 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 +239 -20
  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:
@@ -64,10 +78,108 @@ For the template manager module, install the optional `hazo_connect` peer depend
64
78
  npm install hazo_connect
65
79
  ```
66
80
 
67
- Then run the migration in `migrations/001_create_template_tables.sql` against your database to create the required tables (`hazo_notify_template_cat` and `hazo_notify_templates`).
81
+ Then create the required tables (`hazo_notify_template_cat` and `hazo_notify_templates`) by running the appropriate SQL below for your database. The same SQL is also packaged at `node_modules/hazo_notify/migrations/001_create_template_tables.sql`.
68
82
 
69
83
  If `hazo_connect` is not installed, the emailer module still works independently.
70
84
 
85
+ #### Database Tables
86
+
87
+ The template manager requires two tables:
88
+
89
+ | Table | Purpose |
90
+ |-------|---------|
91
+ | `hazo_notify_template_cat` | Template categories (groups templates per scope) |
92
+ | `hazo_notify_templates` | Email templates with HTML, text, and variable definitions |
93
+
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`.
95
+
96
+ #### PostgreSQL Schema (v2.0.0+)
97
+
98
+ ```sql
99
+ CREATE TABLE IF NOT EXISTS hazo_notify_template_cat (
100
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
101
+ scope_id UUID,
102
+ template_category_name VARCHAR(100) NOT NULL,
103
+ created_at TIMESTAMPTZ DEFAULT NOW(),
104
+ changed_at TIMESTAMPTZ
105
+ );
106
+
107
+ CREATE INDEX IF NOT EXISTS idx_notify_template_cat_scope
108
+ ON hazo_notify_template_cat (scope_id);
109
+
110
+ CREATE TABLE IF NOT EXISTS hazo_notify_templates (
111
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
112
+ scope_id UUID,
113
+ template_category_id UUID NOT NULL REFERENCES hazo_notify_template_cat(id),
114
+ template_variables JSONB DEFAULT '{}',
115
+ template_name VARCHAR(100) NOT NULL,
116
+ template_html TEXT NOT NULL DEFAULT '',
117
+ template_text TEXT NOT NULL DEFAULT '',
118
+ email_type VARCHAR(10) NOT NULL DEFAULT 'user',
119
+ created_at TIMESTAMPTZ DEFAULT NOW(),
120
+ changed_at TIMESTAMPTZ
121
+ );
122
+
123
+ CREATE INDEX IF NOT EXISTS idx_notify_templates_scope
124
+ ON hazo_notify_templates (scope_id);
125
+
126
+ CREATE INDEX IF NOT EXISTS idx_notify_templates_category
127
+ ON hazo_notify_templates (template_category_id);
128
+
129
+ CREATE INDEX IF NOT EXISTS idx_notify_templates_name
130
+ ON hazo_notify_templates (scope_id, template_name);
131
+ ```
132
+
133
+ **Note**: `scope_id` is nullable. NULL represents global/system templates; non-NULL represents tenant-scoped templates.
134
+
135
+ #### SQLite Schema (v2.0.0+)
136
+
137
+ ```sql
138
+ CREATE TABLE IF NOT EXISTS hazo_notify_template_cat (
139
+ id TEXT PRIMARY KEY,
140
+ scope_id TEXT,
141
+ template_category_name TEXT NOT NULL,
142
+ created_at TEXT DEFAULT (datetime('now')),
143
+ changed_at TEXT
144
+ );
145
+
146
+ CREATE INDEX IF NOT EXISTS idx_notify_template_cat_scope
147
+ ON hazo_notify_template_cat (scope_id);
148
+
149
+ CREATE TABLE IF NOT EXISTS hazo_notify_templates (
150
+ id TEXT PRIMARY KEY,
151
+ scope_id TEXT,
152
+ template_category_id TEXT NOT NULL REFERENCES hazo_notify_template_cat(id),
153
+ template_variables TEXT DEFAULT '{}',
154
+ template_name TEXT NOT NULL,
155
+ template_html TEXT NOT NULL DEFAULT '',
156
+ template_text TEXT NOT NULL DEFAULT '',
157
+ email_type TEXT NOT NULL DEFAULT 'user',
158
+ created_at TEXT DEFAULT (datetime('now')),
159
+ changed_at TEXT
160
+ );
161
+
162
+ CREATE INDEX IF NOT EXISTS idx_notify_templates_scope
163
+ ON hazo_notify_templates (scope_id);
164
+
165
+ CREATE INDEX IF NOT EXISTS idx_notify_templates_category
166
+ ON hazo_notify_templates (template_category_id);
167
+
168
+ CREATE INDEX IF NOT EXISTS idx_notify_templates_name
169
+ ON hazo_notify_templates (scope_id, template_name);
170
+ ```
171
+
172
+ **Note**: `scope_id` is nullable. NULL represents global/system templates; non-NULL represents tenant-scoped templates.
173
+
174
+ #### Type Mapping (PostgreSQL ↔ SQLite)
175
+
176
+ | Concept | PostgreSQL | SQLite |
177
+ |---------|------------|--------|
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) |
180
+ | JSON column (`template_variables`) | `JSONB` | `TEXT` (serialized JSON) |
181
+ | Timestamps | `TIMESTAMPTZ` with `NOW()` default | `TEXT` with `datetime('now')` default |
182
+
71
183
  ## Quick Start
72
184
 
73
185
  1. **Create `.env.local` file** with your Zeptomail API key:
@@ -201,16 +313,19 @@ const result = await send_email({
201
313
  ```typescript
202
314
  {
203
315
  success: true,
204
- 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.…',
205
319
  message: 'Email sent successfully',
206
320
  raw_response: {
207
- status: 200,
208
- status_text: 'OK',
321
+ status: 201,
322
+ status_text: '',
209
323
  headers: { /* response headers */ },
210
324
  body: {
211
- data: {
212
- message_id: 'msg_abc123def456'
213
- }
325
+ data: [{ code: 'EM_104', additional_info: [], message: 'Email request received' }],
326
+ message: 'OK',
327
+ request_id: '7a6803.5aa56c8edd39e440.m1.…',
328
+ object: 'email'
214
329
  }
215
330
  }
216
331
  }
@@ -455,20 +570,18 @@ const result = await send_email({
455
570
  ```typescript
456
571
  {
457
572
  success: true,
458
- message_id: 'msg_xyz789abc123',
573
+ // message_id is Zeptomail's request_id (the unique identifier for the send)
574
+ message_id: '7a6803.5aa56c8edd39e440.m1.…',
459
575
  message: 'Email sent successfully',
460
576
  raw_response: {
461
- status: 200,
462
- status_text: 'OK',
463
- headers: {
464
- 'content-type': 'application/json',
465
- 'content-length': '156',
466
- // ... other headers
467
- },
577
+ status: 201,
578
+ status_text: '',
579
+ headers: { /* response headers */ },
468
580
  body: {
469
- data: {
470
- message_id: 'msg_xyz789abc123'
471
- }
581
+ data: [{ code: 'EM_104', additional_info: [], message: 'Email request received' }],
582
+ message: 'OK',
583
+ request_id: '7a6803.5aa56c8edd39e440.m1.…',
584
+ object: 'email'
472
585
  }
473
586
  }
474
587
  }
@@ -700,9 +813,115 @@ console.log('Emailer module:', config.emailer_module);
700
813
  console.log('From email:', config.from_email);
701
814
  ```
702
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
+
703
922
  ### Template Manager API
704
923
 
705
- #### `send_template_email(options, hazo_connect, org_id, config?): Promise<SendTemplateEmailResponse>`
924
+ #### `send_template_email(options, hazo_connect, scope_id, config?): Promise<SendTemplateEmailResponse>`
706
925
 
707
926
  Render a named template and send it as an email.
708
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"}