@flowselections/mailbox-orders 1.0.1

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 (69) hide show
  1. package/dist-lib/_core-safelist.d.ts +2 -0
  2. package/dist-lib/_core-safelist.d.ts.map +1 -0
  3. package/dist-lib/_core-safelist.js +15 -0
  4. package/dist-lib/components/MailboxInboxPage.d.ts +2 -0
  5. package/dist-lib/components/MailboxInboxPage.d.ts.map +1 -0
  6. package/dist-lib/components/MailboxInboxPage.js +61 -0
  7. package/dist-lib/components/MailboxProcessedPage.d.ts +2 -0
  8. package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -0
  9. package/dist-lib/components/MailboxProcessedPage.js +21 -0
  10. package/dist-lib/components/MailboxProposalsPage.d.ts +2 -0
  11. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -0
  12. package/dist-lib/components/MailboxProposalsPage.js +198 -0
  13. package/dist-lib/components/SearchableSelect.d.ts +18 -0
  14. package/dist-lib/components/SearchableSelect.d.ts.map +1 -0
  15. package/dist-lib/components/SearchableSelect.js +20 -0
  16. package/dist-lib/components/settings/ImapAccountsCard.d.ts +2 -0
  17. package/dist-lib/components/settings/ImapAccountsCard.d.ts.map +1 -0
  18. package/dist-lib/components/settings/ImapAccountsCard.js +136 -0
  19. package/dist-lib/components/settings/ImapProfilesCard.d.ts +2 -0
  20. package/dist-lib/components/settings/ImapProfilesCard.d.ts.map +1 -0
  21. package/dist-lib/components/settings/ImapProfilesCard.js +101 -0
  22. package/dist-lib/components/settings/MailboxOrderTemplateCard.d.ts +2 -0
  23. package/dist-lib/components/settings/MailboxOrderTemplateCard.d.ts.map +1 -0
  24. package/dist-lib/components/settings/MailboxOrderTemplateCard.js +98 -0
  25. package/dist-lib/components/settings/MailboxSettingsCard.d.ts +2 -0
  26. package/dist-lib/components/settings/MailboxSettingsCard.d.ts.map +1 -0
  27. package/dist-lib/components/settings/MailboxSettingsCard.js +85 -0
  28. package/dist-lib/index.d.ts +12 -0
  29. package/dist-lib/index.d.ts.map +1 -0
  30. package/dist-lib/index.js +34 -0
  31. package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
  32. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
  33. package/dist-lib/integrations/supabase/auth-attacher.js +15 -0
  34. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2978 -0
  35. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
  36. package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
  37. package/dist-lib/integrations/supabase/client.d.ts +2974 -0
  38. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  39. package/dist-lib/integrations/supabase/client.js +13 -0
  40. package/dist-lib/integrations/supabase/client.server.d.ts +2974 -0
  41. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
  42. package/dist-lib/integrations/supabase/client.server.js +30 -0
  43. package/dist-lib/integrations/supabase/types.d.ts +3119 -0
  44. package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
  45. package/dist-lib/integrations/supabase/types.js +8 -0
  46. package/dist-lib/lib/imap.functions.d.ts +32852 -0
  47. package/dist-lib/lib/imap.functions.d.ts.map +1 -0
  48. package/dist-lib/lib/imap.functions.js +235 -0
  49. package/dist-lib/lib/mailbox-ai.server.d.ts +32 -0
  50. package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -0
  51. package/dist-lib/lib/mailbox-ai.server.js +107 -0
  52. package/dist-lib/lib/mailbox-auto.server.d.ts +9 -0
  53. package/dist-lib/lib/mailbox-auto.server.d.ts.map +1 -0
  54. package/dist-lib/lib/mailbox-auto.server.js +198 -0
  55. package/dist-lib/lib/mailbox-template.functions.d.ts +17913 -0
  56. package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -0
  57. package/dist-lib/lib/mailbox-template.functions.js +106 -0
  58. package/dist-lib/lib/mailbox.functions.d.ts +32888 -0
  59. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -0
  60. package/dist-lib/lib/mailbox.functions.js +334 -0
  61. package/dist-lib/lib/utils.d.ts +3 -0
  62. package/dist-lib/lib/utils.d.ts.map +1 -0
  63. package/dist-lib/lib/utils.js +5 -0
  64. package/dist-lib/lib/validationSchemas.d.ts +15 -0
  65. package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
  66. package/dist-lib/lib/validationSchemas.js +25 -0
  67. package/dist-lib/styles.css +1 -0
  68. package/package.json +96 -0
  69. package/public/flowselections-assets/template-module/README.md +15 -0
@@ -0,0 +1,334 @@
1
+ // Server functions voor de Mailbox-bestellingen module
2
+ import { createServerFn } from "@tanstack/react-start";
3
+ import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
4
+ import { z } from "zod";
5
+ // ─── Settings ────────────────────────────────────────────────────────────────
6
+ export const getMailboxSettings = createServerFn({ method: "GET" })
7
+ .middleware([requireSupabaseAuth])
8
+ .handler(async ({ context }) => {
9
+ const { data, error } = await context.supabase
10
+ .from("mailbox_settings")
11
+ .select("*")
12
+ .limit(1)
13
+ .maybeSingle();
14
+ if (error)
15
+ throw new Error(error.message);
16
+ return { settings: data };
17
+ });
18
+ const SettingsInput = z.object({
19
+ imap_host: z.string().max(255),
20
+ imap_port: z.number().int().min(1).max(65535),
21
+ imap_username: z.string().max(255),
22
+ imap_use_tls: z.boolean(),
23
+ folder: z.string().max(255),
24
+ polling_enabled: z.boolean(),
25
+ });
26
+ export const saveMailboxSettings = createServerFn({ method: "POST" })
27
+ .middleware([requireSupabaseAuth])
28
+ .inputValidator((input) => SettingsInput.parse(input))
29
+ .handler(async ({ data, context }) => {
30
+ const { data: existing } = await context.supabase
31
+ .from("mailbox_settings").select("id").limit(1).maybeSingle();
32
+ if (existing?.id) {
33
+ const { error } = await context.supabase
34
+ .from("mailbox_settings").update(data).eq("id", existing.id);
35
+ if (error)
36
+ throw new Error(error.message);
37
+ }
38
+ else {
39
+ const { error } = await context.supabase
40
+ .from("mailbox_settings").insert({ ...data, singleton: true });
41
+ if (error)
42
+ throw new Error(error.message);
43
+ }
44
+ return { ok: true };
45
+ });
46
+ async function invokeMailboxImap(supabase, action) {
47
+ const { data, error } = await supabase.functions.invoke("mailbox-imap", {
48
+ body: { action },
49
+ });
50
+ if (error)
51
+ throw new Error(error.message ?? "Edge function aanroep mislukt");
52
+ return data;
53
+ }
54
+ export const testMailboxConnection = createServerFn({ method: "POST" })
55
+ .middleware([requireSupabaseAuth])
56
+ .handler(async ({ context }) => {
57
+ try {
58
+ return await invokeMailboxImap(context.supabase, "test");
59
+ }
60
+ catch (e) {
61
+ return { ok: false, error: e?.message ?? "Onbekende fout" };
62
+ }
63
+ });
64
+ // ─── Polling ─────────────────────────────────────────────────────────────────
65
+ export const pollMailbox = createServerFn({ method: "POST" })
66
+ .middleware([requireSupabaseAuth])
67
+ .handler(async ({ context }) => {
68
+ try {
69
+ const res = await invokeMailboxImap(context.supabase, "poll");
70
+ let autoProcessed = 0;
71
+ let autoApproved = 0;
72
+ if (res.ok && (res.inserted ?? 0) > 0) {
73
+ const { autoProcessNewMessagesInternal } = await import("./mailbox-auto.server");
74
+ const r = await autoProcessNewMessagesInternal();
75
+ autoProcessed = r.processed;
76
+ autoApproved = r.approved;
77
+ }
78
+ return { ok: !!res.ok, inserted: res.inserted ?? 0, autoProcessed, autoApproved, error: res.error };
79
+ }
80
+ catch (e) {
81
+ return { ok: false, error: e?.message ?? "Onbekende fout", inserted: 0 };
82
+ }
83
+ });
84
+ // ─── Messages & Proposals ────────────────────────────────────────────────────
85
+ export const listMessages = createServerFn({ method: "GET" })
86
+ .middleware([requireSupabaseAuth])
87
+ .handler(async ({ context }) => {
88
+ const { data, error } = await context.supabase
89
+ .from("mailbox_messages")
90
+ .select("id, uid, from_email, from_name, subject, received_at, status, error_message, created_at")
91
+ .order("received_at", { ascending: false })
92
+ .limit(200);
93
+ if (error)
94
+ throw new Error(error.message);
95
+ return { items: data ?? [] };
96
+ });
97
+ export const parseMessage = createServerFn({ method: "POST" })
98
+ .middleware([requireSupabaseAuth])
99
+ .inputValidator((input) => z.object({ messageId: z.string().uuid() }).parse(input))
100
+ .handler(async ({ data }) => {
101
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
102
+ const { data: msg, error: msgErr } = await supabaseAdmin
103
+ .from("mailbox_messages").select("*").eq("id", data.messageId).single();
104
+ if (msgErr || !msg)
105
+ throw new Error("Bericht niet gevonden");
106
+ await supabaseAdmin.from("mailbox_messages")
107
+ .update({ status: "parsing", error_message: null }).eq("id", msg.id);
108
+ try {
109
+ // Templatevelden ophalen
110
+ const { data: fields } = await supabaseAdmin
111
+ .from("mailbox_order_template_fields")
112
+ .select("*")
113
+ .order("sort_order", { ascending: true });
114
+ const templateFields = (fields ?? []).filter((f) => f.ai_enabled);
115
+ // Databron-catalogi
116
+ const [{ data: products }, { data: customers }, { data: suppliers }] = await Promise.all([
117
+ supabaseAdmin.from("products").select("id, product, product_type").order("product").range(0, 1999),
118
+ supabaseAdmin.from("customers").select("id, company_name, email").eq("is_active", true).order("company_name").range(0, 4999),
119
+ supabaseAdmin.from("suppliers").select("id, name").order("name").range(0, 999),
120
+ ]);
121
+ const catalog = (products ?? []).map((p) => ({ id: p.id, name: p.product }));
122
+ const plantTypeSet = new Set();
123
+ (products ?? []).forEach((p) => { if (p.product_type)
124
+ plantTypeSet.add(p.product_type); });
125
+ const plantTypes = Array.from(plantTypeSet).sort().map((v) => ({ id: v, name: v }));
126
+ // Bouw spec met allowed values per database-veld
127
+ const spec = templateFields.map((f) => {
128
+ let allowed;
129
+ if (f.source === "database") {
130
+ switch (f.field_type) {
131
+ case "customer":
132
+ allowed = (customers ?? []).map((c) => ({ id: c.id, name: c.company_name }));
133
+ break;
134
+ case "product":
135
+ allowed = catalog;
136
+ break;
137
+ case "plant_type":
138
+ allowed = plantTypes;
139
+ break;
140
+ case "supplier":
141
+ allowed = (suppliers ?? []).map((s) => ({ id: s.id, name: s.name }));
142
+ break;
143
+ }
144
+ }
145
+ return {
146
+ key: f.key,
147
+ label: f.label,
148
+ field_type: f.field_type,
149
+ ai_hint: f.ai_hint,
150
+ allowed,
151
+ };
152
+ });
153
+ const { parseOrderEmail } = await import("./mailbox-ai.server");
154
+ const parsed = await parseOrderEmail(msg.body_text ?? msg.body_html ?? "", msg.subject ?? "", spec, catalog);
155
+ // Backwards-compat: leid klant/leverdatum/notes af voor de bestaande kolommen
156
+ const customerId = parsed.fields["customer"]?.value;
157
+ const deliveryDate = parsed.fields["delivery_date"]?.value;
158
+ const notesValue = parsed.fields["notes"]?.value;
159
+ const matchedCustomerId = typeof customerId === "string" && customerId.length === 36 ? customerId : null;
160
+ // Fallback: match op e-mail als AI niets vond
161
+ let finalCustomerId = matchedCustomerId;
162
+ if (!finalCustomerId && msg.from_email) {
163
+ const { data: customer } = await supabaseAdmin
164
+ .from("customers").select("id").eq("email", msg.from_email).maybeSingle();
165
+ finalCustomerId = customer?.id ?? null;
166
+ }
167
+ const { data: proposal, error: propErr } = await supabaseAdmin
168
+ .from("mailbox_order_proposals").insert({
169
+ message_id: msg.id,
170
+ matched_customer_id: finalCustomerId,
171
+ confidence: parsed.overall_confidence,
172
+ delivery_date: typeof deliveryDate === "string" ? deliveryDate : null,
173
+ notes: typeof notesValue === "string" ? notesValue : null,
174
+ parsed_payload: parsed,
175
+ status: "pending",
176
+ }).select("id").single();
177
+ if (propErr)
178
+ throw new Error(propErr.message);
179
+ // Sla alle veldwaarden op
180
+ const fieldRows = Object.entries(parsed.fields).map(([key, v]) => ({
181
+ proposal_id: proposal.id,
182
+ field_key: key,
183
+ value: v.value === undefined ? null : v.value,
184
+ ai_confidence: v.confidence,
185
+ ai_filled: v.value !== null && v.value !== undefined,
186
+ needs_review: v.needs_review,
187
+ }));
188
+ if (fieldRows.length > 0) {
189
+ await supabaseAdmin.from("mailbox_proposal_field_values").insert(fieldRows);
190
+ }
191
+ if (parsed.lines.length > 0) {
192
+ const linesToInsert = parsed.lines.map((l, idx) => ({
193
+ proposal_id: proposal.id,
194
+ product_id: l.product_id,
195
+ raw_product_text: l.raw_product_text,
196
+ quantity: l.quantity,
197
+ unit: l.unit,
198
+ match_confidence: l.match_confidence,
199
+ sort_order: idx,
200
+ }));
201
+ await supabaseAdmin.from("mailbox_proposal_lines").insert(linesToInsert);
202
+ }
203
+ await supabaseAdmin.from("mailbox_messages")
204
+ .update({ status: "parsed" }).eq("id", msg.id);
205
+ return { ok: true, proposalId: proposal.id };
206
+ }
207
+ catch (e) {
208
+ await supabaseAdmin.from("mailbox_messages")
209
+ .update({ status: "failed", error_message: e?.message ?? "Parsefout" }).eq("id", msg.id);
210
+ return { ok: false, error: e?.message ?? "Parsefout" };
211
+ }
212
+ });
213
+ export const listProposals = createServerFn({ method: "GET" })
214
+ .middleware([requireSupabaseAuth])
215
+ .inputValidator((input) => z.object({ status: z.enum(["pending", "approved", "rejected"]).optional() }).parse(input ?? {}))
216
+ .handler(async ({ data, context }) => {
217
+ let q = context.supabase
218
+ .from("mailbox_order_proposals")
219
+ .select("*, mailbox_messages(subject, from_email, from_name, body_text, received_at), mailbox_proposal_lines(*), mailbox_proposal_field_values(*)")
220
+ .order("created_at", { ascending: false })
221
+ .limit(100);
222
+ if (data.status)
223
+ q = q.eq("status", data.status);
224
+ const { data: rows, error } = await q;
225
+ if (error)
226
+ throw new Error(error.message);
227
+ return { items: rows ?? [] };
228
+ });
229
+ const LineEdit = z.object({
230
+ id: z.string().uuid().optional(),
231
+ product_id: z.string().uuid().nullable(),
232
+ raw_product_text: z.string().max(500),
233
+ quantity: z.number().nullable(),
234
+ unit: z.string().max(50).nullable(),
235
+ });
236
+ const FieldValueInput = z.object({
237
+ field_key: z.string().min(1).max(64),
238
+ value: z.any().nullable(),
239
+ });
240
+ export const approveProposal = createServerFn({ method: "POST" })
241
+ .middleware([requireSupabaseAuth])
242
+ .inputValidator((input) => z.object({
243
+ proposalId: z.string().uuid(),
244
+ customerId: z.string().uuid().nullable(),
245
+ customerName: z.string().min(1).max(255),
246
+ deliveryDate: z.string().min(1).max(50),
247
+ notes: z.string().max(2000).nullable(),
248
+ lines: z.array(LineEdit).min(1),
249
+ fieldValues: z.array(FieldValueInput).max(100).optional(),
250
+ }).parse(input))
251
+ .handler(async ({ data, context }) => {
252
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
253
+ // Bouw custom_fields map vanuit alle templatevelden
254
+ const customFields = {};
255
+ (data.fieldValues ?? []).forEach((fv) => {
256
+ customFields[fv.field_key] = fv.value;
257
+ });
258
+ const orderNumber = `MB-${Date.now()}`;
259
+ const { data: order, error: orderErr } = await supabaseAdmin
260
+ .from("orders").insert({
261
+ order_number: orderNumber,
262
+ customer_name: data.customerName,
263
+ customer_id: data.customerId,
264
+ order_date: new Date().toISOString().slice(0, 10),
265
+ delivery_date: data.deliveryDate,
266
+ status: "nieuw",
267
+ total: "0",
268
+ notes: data.notes,
269
+ custom_fields: { ...customFields, source: "mailbox" },
270
+ }).select("id").single();
271
+ if (orderErr)
272
+ throw new Error(orderErr.message);
273
+ const items = data.lines.map((l) => ({
274
+ order_id: order.id,
275
+ product_id: l.product_id,
276
+ product_name: l.raw_product_text || "Onbekend product",
277
+ quantity: Math.max(1, Math.round(l.quantity ?? 1)),
278
+ unit: l.unit ?? "stuks",
279
+ }));
280
+ const { error: itemsErr } = await supabaseAdmin.from("order_items").insert(items);
281
+ if (itemsErr)
282
+ throw new Error(itemsErr.message);
283
+ // Sla finale veldwaarden op (overschrijft AI-waarden)
284
+ if (data.fieldValues && data.fieldValues.length > 0) {
285
+ for (const fv of data.fieldValues) {
286
+ await supabaseAdmin.from("mailbox_proposal_field_values").upsert({
287
+ proposal_id: data.proposalId,
288
+ field_key: fv.field_key,
289
+ value: fv.value === undefined ? null : fv.value,
290
+ ai_filled: false,
291
+ needs_review: false,
292
+ }, { onConflict: "proposal_id,field_key" });
293
+ }
294
+ }
295
+ await supabaseAdmin.from("mailbox_order_proposals").update({
296
+ status: "approved",
297
+ reviewed_by: context.userId,
298
+ reviewed_at: new Date().toISOString(),
299
+ created_order_id: order.id,
300
+ }).eq("id", data.proposalId);
301
+ const { data: prop } = await supabaseAdmin
302
+ .from("mailbox_order_proposals").select("message_id").eq("id", data.proposalId).single();
303
+ if (prop?.message_id) {
304
+ await supabaseAdmin.from("mailbox_messages").update({ status: "converted" }).eq("id", prop.message_id);
305
+ }
306
+ return { ok: true, orderId: order.id };
307
+ });
308
+ export const rejectProposal = createServerFn({ method: "POST" })
309
+ .middleware([requireSupabaseAuth])
310
+ .inputValidator((input) => z.object({ proposalId: z.string().uuid(), reason: z.string().max(500).optional() }).parse(input))
311
+ .handler(async ({ data, context }) => {
312
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
313
+ await supabaseAdmin.from("mailbox_order_proposals").update({
314
+ status: "rejected",
315
+ reviewed_by: context.userId,
316
+ reviewed_at: new Date().toISOString(),
317
+ notes: data.reason ?? null,
318
+ }).eq("id", data.proposalId);
319
+ return { ok: true };
320
+ });
321
+ export const listCustomersForPicker = createServerFn({ method: "GET" })
322
+ .middleware([requireSupabaseAuth])
323
+ .handler(async ({ context }) => {
324
+ const { data } = await context.supabase
325
+ .from("customers").select("id, company_name, email").eq("is_active", true).order("company_name").range(0, 4999);
326
+ return { items: data ?? [] };
327
+ });
328
+ export const listProductsForPicker = createServerFn({ method: "GET" })
329
+ .middleware([requireSupabaseAuth])
330
+ .handler(async ({ context }) => {
331
+ const { data } = await context.supabase
332
+ .from("products").select("id, product, unit").order("product").range(0, 4999);
333
+ return { items: data ?? [] };
334
+ });
@@ -0,0 +1,3 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
@@ -0,0 +1,5 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ export declare const loginSchema: z.ZodObject<{
3
+ email: z.ZodString;
4
+ password: z.ZodString;
5
+ }, z.core.$strip>;
6
+ export declare const bugReportSchema: z.ZodObject<{
7
+ title: z.ZodString;
8
+ description: z.ZodString;
9
+ }, z.core.$strip>;
10
+ export declare const ALLOWED_FILE_TYPES: string[];
11
+ export declare function validateFileUpload(file: File): {
12
+ valid: boolean;
13
+ error?: string;
14
+ };
15
+ //# sourceMappingURL=validationSchemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validationSchemas.d.ts","sourceRoot":"","sources":["../../src/lib/validationSchemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,WAAW;;;iBAGtB,CAAC;AAEH,eAAO,MAAM,eAAe;;;iBAG1B,CAAC;AAEH,eAAO,MAAM,kBAAkB,UAK9B,CAAC;AAIF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAQjF"}
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ export const loginSchema = z.object({
3
+ email: z.string().email("Ongeldig e-mailadres"),
4
+ password: z.string().min(6, "Wachtwoord moet minimaal 6 tekens bevatten"),
5
+ });
6
+ export const bugReportSchema = z.object({
7
+ title: z.string().min(1, "Titel is verplicht").max(200),
8
+ description: z.string().min(1, "Beschrijving is verplicht").max(5000),
9
+ });
10
+ export const ALLOWED_FILE_TYPES = [
11
+ "image/png",
12
+ "image/jpeg",
13
+ "image/gif",
14
+ "application/pdf",
15
+ ];
16
+ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
17
+ export function validateFileUpload(file) {
18
+ if (!ALLOWED_FILE_TYPES.includes(file.type)) {
19
+ return { valid: false, error: "Bestandstype niet toegestaan" };
20
+ }
21
+ if (file.size > MAX_FILE_SIZE) {
22
+ return { valid: false, error: "Bestand is te groot (max 10MB)" };
23
+ }
24
+ return { valid: true };
25
+ }
@@ -0,0 +1 @@
1
+ @source "./";
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@flowselections/mailbox-orders",
3
+ "version": "1.0.1",
4
+ "private": false,
5
+ "sideEffects": false,
6
+ "type": "module",
7
+ "main": "./dist-lib/index.js",
8
+ "types": "./dist-lib/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist-lib/index.d.ts",
12
+ "default": "./dist-lib/index.js"
13
+ },
14
+ "./styles": "./dist-lib/styles.css"
15
+ },
16
+ "files": [
17
+ "dist-lib",
18
+ "public"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "dev": "vite dev",
25
+ "build": "vite build",
26
+ "build:lib": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && node -e \"require('fs').writeFileSync('dist-lib/styles.css', '@source \\\"./\\\";\\n')\"",
27
+ "build:lib:watch": "tsc -p tsconfig.build.json --watch",
28
+ "build:dev": "vite build --mode development",
29
+ "preview": "vite preview",
30
+ "lint": "eslint ."
31
+ },
32
+ "peerDependencies": {
33
+ "@flowselections/core": "*",
34
+ "@tanstack/react-query": "^5.0.0",
35
+ "@tanstack/react-router": "^1.0.0",
36
+ "@tanstack/react-start": "^1.0.0",
37
+ "lucide-react": "^0.400.0",
38
+ "react": "^18 || ^19",
39
+ "react-dom": "^18 || ^19",
40
+ "sonner": "^2.0.0"
41
+ },
42
+ "dependencies": {
43
+ "@supabase/supabase-js": "^2.108.1"
44
+ },
45
+ "devDependencies": {
46
+ "@cloudflare/vite-plugin": "^1.25.5",
47
+ "@eslint/js": "^9.32.0",
48
+ "@flowselections/core": "^1.0.11",
49
+ "@lovable.dev/vite-tanstack-config": "^1.2.0",
50
+ "@rsbuild/core": "^1.0.0",
51
+ "@tailwindcss/vite": "^4.2.1",
52
+ "@tanstack/react-query": "^5.83.0",
53
+ "@tanstack/react-router": "^1.168.0",
54
+ "@tanstack/react-start": "^1.167.14",
55
+ "@tanstack/router-plugin": "^1.167.10",
56
+ "@types/node": "^22.16.5",
57
+ "@types/react": "^19.2.0",
58
+ "@types/react-dom": "^19.2.0",
59
+ "@vitejs/plugin-react": "^5.0.4",
60
+ "eslint": "^9.32.0",
61
+ "eslint-plugin-react-hooks": "^5.2.0",
62
+ "eslint-plugin-react-refresh": "^0.4.20",
63
+ "globals": "^15.15.0",
64
+ "lucide-react": "^0.575.0",
65
+ "react": "^19.2.0",
66
+ "react-dom": "^19.2.0",
67
+ "sonner": "^2.0.7",
68
+ "tailwindcss": "^4.2.1",
69
+ "tsc-alias": "^1.8.10",
70
+ "tw-animate-css": "^1.3.4",
71
+ "typescript": "^5.8.3",
72
+ "typescript-eslint": "^8.56.1",
73
+ "vite": "^7.3.1",
74
+ "vite-tsconfig-paths": "^6.0.2"
75
+ },
76
+ "flowselections": {
77
+ "moduleId": "Mailbox",
78
+ "pages": [
79
+ {
80
+ "export": "MailboxInboxPage",
81
+ "route": "mail-box-voor-bestellingen-erp-kwekers/inbox",
82
+ "title": "Inbox"
83
+ },
84
+ {
85
+ "export": "MailboxProposalsPage",
86
+ "route": "mail-box-voor-bestellingen-erp-kwekers/voorstellen",
87
+ "title": "Voorstellen"
88
+ },
89
+ {
90
+ "export": "MailboxProcessedPage",
91
+ "route": "mail-box-voor-bestellingen-erp-kwekers/verwerkt",
92
+ "title": "Verwerkt"
93
+ }
94
+ ]
95
+ }
96
+ }
@@ -0,0 +1,15 @@
1
+ # Module assets
2
+
3
+ Raster-afbeeldingen (PNG, JPEG) die deze module nodig heeft, worden hier geplaatst.
4
+
5
+ De setup CLI kopieert de inhoud van `public/` naar:
6
+ `shell/public/flowselections-assets/<module-id>/`
7
+
8
+ In de module-code verwijs je naar de afbeelding via een pad-string (GEEN import):
9
+ ```ts
10
+ const MODULE_LOGO = '/flowselections-assets/<module-id>/logo.png'
11
+ ```
12
+
13
+ Vervang `<module-id>` door de id die je in `src/index.ts` als moduleConfig-id hebt opgegeven.
14
+
15
+ Zie ook: AGENTS.md sectie "Afbeeldingen"