@flowselections/twinfield-authenticatie-module-15-06-2026 1.0.5
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.
- package/dist-lib/_core-safelist.d.ts +2 -0
- package/dist-lib/_core-safelist.d.ts.map +1 -0
- package/dist-lib/_core-safelist.js +15 -0
- package/dist-lib/components/TemplateModuleLogo.d.ts +6 -0
- package/dist-lib/components/TemplateModuleLogo.d.ts.map +1 -0
- package/dist-lib/components/TemplateModuleLogo.js +4 -0
- package/dist-lib/components/TemplateModulePage.d.ts +2 -0
- package/dist-lib/components/TemplateModulePage.d.ts.map +1 -0
- package/dist-lib/components/TemplateModulePage.js +44 -0
- package/dist-lib/components/TemplatePage.d.ts +2 -0
- package/dist-lib/components/TemplatePage.d.ts.map +1 -0
- package/dist-lib/components/TemplatePage.js +4 -0
- package/dist-lib/components/settings/MijnModuleSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/MijnModuleSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MijnModuleSettingsCard.js +42 -0
- package/dist-lib/components/settings/TwinfieldAuthSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/TwinfieldAuthSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/TwinfieldAuthSettingsCard.js +295 -0
- package/dist-lib/hooks/useMijnModuleData.d.ts +13 -0
- package/dist-lib/hooks/useMijnModuleData.d.ts.map +1 -0
- package/dist-lib/hooks/useMijnModuleData.js +49 -0
- package/dist-lib/index.d.ts +9 -0
- package/dist-lib/index.d.ts.map +1 -0
- package/dist-lib/index.js +36 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-attacher.js +13 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +7090 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
- package/dist-lib/integrations/supabase/client.d.ts +7086 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.js +13 -0
- package/dist-lib/integrations/supabase/client.server.d.ts +7086 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.server.js +30 -0
- package/dist-lib/integrations/supabase/twinfield-admin.server.d.ts +7086 -0
- package/dist-lib/integrations/supabase/twinfield-admin.server.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/twinfield-admin.server.js +17 -0
- package/dist-lib/integrations/supabase/types.d.ts +7343 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/types.js +26 -0
- package/dist-lib/lib/accounting/twinfield/client.server.d.ts +17 -0
- package/dist-lib/lib/accounting/twinfield/client.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/client.server.js +21 -0
- package/dist-lib/lib/accounting/twinfield/hmac.server.d.ts +13 -0
- package/dist-lib/lib/accounting/twinfield/hmac.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/hmac.server.js +36 -0
- package/dist-lib/lib/accounting/twinfield/mapping.d.ts +31 -0
- package/dist-lib/lib/accounting/twinfield/mapping.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/mapping.js +69 -0
- package/dist-lib/lib/accounting/twinfield/push.server.d.ts +35 -0
- package/dist-lib/lib/accounting/twinfield/push.server.d.ts.map +1 -0
- package/dist-lib/lib/accounting/twinfield/push.server.js +163 -0
- package/dist-lib/lib/twinfield-adapter.functions.d.ts +14196 -0
- package/dist-lib/lib/twinfield-adapter.functions.d.ts.map +1 -0
- package/dist-lib/lib/twinfield-adapter.functions.js +40 -0
- package/dist-lib/lib/twinfield-sync.functions.d.ts +14196 -0
- package/dist-lib/lib/twinfield-sync.functions.d.ts.map +1 -0
- package/dist-lib/lib/twinfield-sync.functions.js +63 -0
- package/dist-lib/lib/twinfield-sync.server.d.ts +25 -0
- package/dist-lib/lib/twinfield-sync.server.d.ts.map +1 -0
- package/dist-lib/lib/twinfield-sync.server.js +829 -0
- package/dist-lib/lib/twinfield-token.server.d.ts +37 -0
- package/dist-lib/lib/twinfield-token.server.d.ts.map +1 -0
- package/dist-lib/lib/twinfield-token.server.js +349 -0
- package/dist-lib/lib/twinfield.functions.d.ts +70969 -0
- package/dist-lib/lib/twinfield.functions.d.ts.map +1 -0
- package/dist-lib/lib/twinfield.functions.js +436 -0
- package/dist-lib/lib/utils.d.ts +3 -0
- package/dist-lib/lib/utils.d.ts.map +1 -0
- package/dist-lib/lib/utils.js +5 -0
- package/dist-lib/lib/validationSchemas.d.ts +15 -0
- package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
- package/dist-lib/lib/validationSchemas.js +25 -0
- package/dist-lib/styles.css +1 -0
- package/package.json +87 -0
- package/public/flowselections-assets/twinfield-authenticatie/twinfield-logo.svg +218 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_core-safelist.d.ts","sourceRoot":"","sources":["../src/_core-safelist.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Forceert Tailwind classes die uit @flowselections/core komen maar niet
|
|
2
|
+
// voorkomen in de eigen broncode van deze module.
|
|
3
|
+
// Zonder dit bestand worden deze classes weggeoptimaliseerd op de
|
|
4
|
+
// Lovable productie build.
|
|
5
|
+
const _safelist = [
|
|
6
|
+
// Layout — Sidebar en shell structuur
|
|
7
|
+
'h-full', 'flex-col', 'flex-1', 'min-h-screen', 'ml-64', 'pt-16',
|
|
8
|
+
'items-start', 'items-center', 'justify-between', 'border-t',
|
|
9
|
+
'w-64', 'h-screen', 'h-20', 'overflow-y-auto',
|
|
10
|
+
// Grid — InstellingenPage
|
|
11
|
+
'grid', 'lg:grid-cols-2', 'gap-6', 'p-6', 'space-y-6',
|
|
12
|
+
// Toggle/Switch — aan/uit zichtbaarheid
|
|
13
|
+
'data-[state=checked]:bg-primary', 'data-[state=unchecked]:bg-input',
|
|
14
|
+
];
|
|
15
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplateModuleLogo.d.ts","sourceRoot":"","sources":["../../src/components/TemplateModuleLogo.tsx"],"names":[],"mappings":"AAQA,UAAU,uBAAuB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE,uBAAuB,+BAwBxE"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function TemplateModuleLogo({ className }) {
|
|
3
|
+
return (_jsxs("svg", { viewBox: "0 0 40 40", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: className, "aria-hidden": "true", children: [_jsx("rect", { width: "40", height: "40", rx: "8", fill: "currentColor", opacity: "0.1" }), _jsx("text", { x: "50%", y: "50%", dominantBaseline: "middle", textAnchor: "middle", fontSize: "18", fontWeight: "600", fill: "currentColor", children: "M" })] }));
|
|
4
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplateModulePage.d.ts","sourceRoot":"","sources":["../../src/components/TemplateModulePage.tsx"],"names":[],"mappings":"AAsBA,wBAAgB,kBAAkB,gCAmFjC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/TemplateModulePage.tsx — EXAMPLE PAGE COMPONENT
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Pagina-componenten horen in src/components/, NIET alleen in src/routes/.
|
|
6
|
+
// src/routes/ wordt NIET naar npm gepubliceerd (zie tsconfig.build.json exclude).
|
|
7
|
+
//
|
|
8
|
+
// GEEN <Header /> hier — die zit in _authenticated.tsx via staticData.title
|
|
9
|
+
// ============================================================================
|
|
10
|
+
import { useState, useEffect } from "react";
|
|
11
|
+
import { useAuth, supabase, Button, Card, CardContent, CardHeader, CardTitle, toast, } from "@flowselections/core";
|
|
12
|
+
import { TemplateModuleLogo } from './TemplateModuleLogo';
|
|
13
|
+
export function TemplateModulePage() {
|
|
14
|
+
// Auth is guaranteed — this user is already logged in
|
|
15
|
+
const { user, isAdmin } = useAuth();
|
|
16
|
+
// Example state — replace with your own
|
|
17
|
+
const [items, setItems] = useState([]);
|
|
18
|
+
const [loading, setLoading] = useState(true);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
loadData();
|
|
21
|
+
}, []);
|
|
22
|
+
const loadData = async () => {
|
|
23
|
+
setLoading(true);
|
|
24
|
+
try {
|
|
25
|
+
// Example query — replace "products" with your table
|
|
26
|
+
const { data, error } = await supabase
|
|
27
|
+
.from("products")
|
|
28
|
+
.select("*")
|
|
29
|
+
.limit(10);
|
|
30
|
+
if (error)
|
|
31
|
+
throw error;
|
|
32
|
+
// ✅ Altijd Array.isArray bewaken op server-function data
|
|
33
|
+
// const safeItems = Array.isArray(data?.items) ? data.items : [];
|
|
34
|
+
setItems(Array.isArray(data) ? data : []);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
toast.error("Fout bij het laden van gegevens");
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
setLoading(false);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
return (_jsxs("main", { className: "p-6 space-y-6", children: [isAdmin && (_jsxs("div", { className: "text-sm text-muted-foreground", children: ["Ingelogd als admin: ", user?.email] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: "Overzicht" }) }), _jsxs(CardContent, { children: [loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : items.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Geen gegevens gevonden." })) : (_jsxs("p", { className: "text-sm", children: [items.length, " items geladen."] })), _jsx(Button, { onClick: loadData, className: "mt-4", disabled: loading, children: "Vernieuwen" })] })] }), _jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(TemplateModuleLogo, { className: "w-6 h-6 text-primary" }), _jsx("span", { children: "SVG-component (voorkeur voor vector-logo's)" })] })] }));
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TemplatePage.d.ts","sourceRoot":"","sources":["../../src/components/TemplatePage.tsx"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,gCAS3B"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
export function TemplatePage() {
|
|
3
|
+
return (_jsxs("div", { className: "p-6", children: [_jsx("h1", { className: "text-2xl font-semibold", children: "Module pagina" }), _jsx("p", { className: "text-muted-foreground mt-2", children: "Vervang dit component met de pagina's van jouw module." })] }));
|
|
4
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MijnModuleSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/MijnModuleSettingsCard.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,sBAAsB,gCA+CrC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// src/components/settings/MijnModuleSettingsCard.tsx — EXAMPLE SETTINGS CARD
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// This is an example of a settings card for your module.
|
|
6
|
+
// It appears on the main settings page when registered in src/index.ts.
|
|
7
|
+
//
|
|
8
|
+
// RULES:
|
|
9
|
+
// - Must return a <Card> as its root element
|
|
10
|
+
// - Handles its own data fetching — no props are passed by the core
|
|
11
|
+
// - Use the same Card primitives from @flowselections/core
|
|
12
|
+
//
|
|
13
|
+
// WHAT TO DO:
|
|
14
|
+
// - Rename this file to match your card's purpose
|
|
15
|
+
// - Add your real settings UI
|
|
16
|
+
// - Register it in src/index.ts under settingsCards
|
|
17
|
+
// - Delete this file entirely if your module has no settings
|
|
18
|
+
// ============================================================================
|
|
19
|
+
import { useState } from "react";
|
|
20
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Button, Input, Label, toast, } from "@flowselections/core";
|
|
21
|
+
import { Settings2 } from "lucide-react";
|
|
22
|
+
export function MijnModuleSettingsCard() {
|
|
23
|
+
// Replace with your real settings state
|
|
24
|
+
const [setting, setSetting] = useState("");
|
|
25
|
+
const [saving, setSaving] = useState(false);
|
|
26
|
+
const handleSave = async () => {
|
|
27
|
+
setSaving(true);
|
|
28
|
+
try {
|
|
29
|
+
// Example: save to a table — replace with your real query
|
|
30
|
+
// const { error } = await supabase.from("my_settings").upsert({ ... });
|
|
31
|
+
// if (error) throw error;
|
|
32
|
+
toast.success("Instellingen opgeslagen");
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
toast.error("Fout bij het opslaan");
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
setSaving(false);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Settings2, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Module Instellingen" }), " "] }), _jsx(CardDescription, { children: "Beheer de instellingen voor deze module " })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "setting", children: "Instelling" }), _jsx(Input, { id: "setting", value: setting, onChange: (e) => setSetting(e.target.value), placeholder: "Waarde..." })] }), _jsx(Button, { onClick: handleSave, disabled: saving, children: saving ? "Opslaan..." : "Opslaan" })] })] }));
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TwinfieldAuthSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/components/settings/TwinfieldAuthSettingsCard.tsx"],"names":[],"mappings":"AA6FA,wBAAgB,yBAAyB,gCA0nBxC"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
4
|
+
import { Card, Button, Input, Label, Badge, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
|
|
5
|
+
import { ChevronUp, ChevronDown } from "lucide-react";
|
|
6
|
+
const twinfieldLogo = "/flowselections-assets/twinfield-authenticatie/twinfield-logo.svg";
|
|
7
|
+
import { saveTwinfieldSettings, getTwinfieldStatus, testTwinfieldConnection, setTwinfieldActiveEnvironment, listTwinfieldOffices, } from "../../lib/twinfield.functions";
|
|
8
|
+
import { triggerTwinfieldSync, getTwinfieldSyncState, } from "../../lib/twinfield-sync.functions";
|
|
9
|
+
import { listTwinfieldPushQueue, retryTwinfieldQueueItem, } from "../../lib/twinfield-adapter.functions";
|
|
10
|
+
import { cn } from "../../lib/utils";
|
|
11
|
+
const REDIRECT_URI = "https://twinfield-authenticatie-module-15-06-2026.lovable.app/api/public/twinfield/callback";
|
|
12
|
+
// ----------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ----------------------------------------------------------------------------
|
|
15
|
+
function StepNumber({ n }) {
|
|
16
|
+
return (_jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground text-sm font-semibold", children: n }));
|
|
17
|
+
}
|
|
18
|
+
function Step({ n, title, description, children, }) {
|
|
19
|
+
return (_jsxs("div", { className: "flex gap-4", children: [_jsx(StepNumber, { n: n }), _jsxs("div", { className: "flex-1 space-y-3 pt-0.5", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: title }), description && (_jsx("p", { className: "text-sm text-muted-foreground mt-1", children: description }))] }), _jsx("div", { children: children })] })] }));
|
|
20
|
+
}
|
|
21
|
+
function relativeTime(iso) {
|
|
22
|
+
if (!iso)
|
|
23
|
+
return "nog niet";
|
|
24
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
25
|
+
const min = Math.floor(diff / 60000);
|
|
26
|
+
if (min < 1)
|
|
27
|
+
return "zojuist";
|
|
28
|
+
if (min < 60)
|
|
29
|
+
return `${min} min geleden`;
|
|
30
|
+
const hr = Math.floor(min / 60);
|
|
31
|
+
if (hr < 24)
|
|
32
|
+
return `${hr} uur geleden`;
|
|
33
|
+
const d = Math.floor(hr / 24);
|
|
34
|
+
return `${d} dag${d === 1 ? "" : "en"} geleden`;
|
|
35
|
+
}
|
|
36
|
+
// ----------------------------------------------------------------------------
|
|
37
|
+
// Main composed card
|
|
38
|
+
// ----------------------------------------------------------------------------
|
|
39
|
+
export function TwinfieldAuthSettingsCard() {
|
|
40
|
+
const fetchStatus = useServerFn(getTwinfieldStatus);
|
|
41
|
+
const saveSettings = useServerFn(saveTwinfieldSettings);
|
|
42
|
+
const testConnection = useServerFn(testTwinfieldConnection);
|
|
43
|
+
const setEnv = useServerFn(setTwinfieldActiveEnvironment);
|
|
44
|
+
const trigger = useServerFn(triggerTwinfieldSync);
|
|
45
|
+
const getState = useServerFn(getTwinfieldSyncState);
|
|
46
|
+
const fetchOffices = useServerFn(listTwinfieldOffices);
|
|
47
|
+
// Connection state
|
|
48
|
+
const [clientId, setClientId] = useState("");
|
|
49
|
+
const [clientSecret, setClientSecret] = useState("");
|
|
50
|
+
const [environment, setEnvironment] = useState("sandbox");
|
|
51
|
+
const [administrationCode, setAdministrationCode] = useState("");
|
|
52
|
+
const [loading, setLoading] = useState(true);
|
|
53
|
+
const [saving, setSaving] = useState(false);
|
|
54
|
+
const [savingAdmin, setSavingAdmin] = useState(false);
|
|
55
|
+
const [testing, setTesting] = useState(false);
|
|
56
|
+
const [connected, setConnected] = useState(false);
|
|
57
|
+
const [hasSecret, setHasSecret] = useState(false);
|
|
58
|
+
const [hasClientId, setHasClientId] = useState(false);
|
|
59
|
+
const [existingClientId, setExistingClientId] = useState("");
|
|
60
|
+
// Sync state
|
|
61
|
+
const [busy, setBusy] = useState(null);
|
|
62
|
+
const [counts, setCounts] = useState({ customers: 0, invoices: 0 });
|
|
63
|
+
const [states, setStates] = useState([]);
|
|
64
|
+
const [offices, setOffices] = useState([]);
|
|
65
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
66
|
+
const fetchPushQueue = useServerFn(listTwinfieldPushQueue);
|
|
67
|
+
const retryQueueItem = useServerFn(retryTwinfieldQueueItem);
|
|
68
|
+
const [pushQueue, setPushQueue] = useState([]);
|
|
69
|
+
const [retrying, setRetrying] = useState(null);
|
|
70
|
+
const loadPushQueue = async () => {
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetchPushQueue();
|
|
73
|
+
setPushQueue(res.items ?? []);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// stille fout — push-queue is operationeel, niet kritiek voor de UI
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const loadStatus = async () => {
|
|
80
|
+
try {
|
|
81
|
+
const res = await fetchStatus();
|
|
82
|
+
setConnected(!!res.connected);
|
|
83
|
+
setHasSecret(!!res.has_client_secret);
|
|
84
|
+
setEnvironment(res.active_environment ?? "sandbox");
|
|
85
|
+
if (res.settings) {
|
|
86
|
+
const existing = res.settings.client_id ?? "";
|
|
87
|
+
setExistingClientId(existing);
|
|
88
|
+
setHasClientId(!!existing);
|
|
89
|
+
setClientId("");
|
|
90
|
+
setAdministrationCode(res.settings.administration_code ?? "");
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
setExistingClientId("");
|
|
94
|
+
setHasClientId(false);
|
|
95
|
+
setClientId("");
|
|
96
|
+
setAdministrationCode("");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
toast.error("Status kon niet geladen worden.");
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
setLoading(false);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const loadSyncState = async () => {
|
|
107
|
+
try {
|
|
108
|
+
const res = await getState();
|
|
109
|
+
const safeStates = Array.isArray(res?.states) ? res.states : [];
|
|
110
|
+
setStates(safeStates);
|
|
111
|
+
setCounts(res?.counts ?? { customers: 0, invoices: 0 });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// silent
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const loadOffices = async () => {
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetchOffices();
|
|
120
|
+
const list = Array.isArray(res?.offices) ? res.offices : [];
|
|
121
|
+
setOffices(list);
|
|
122
|
+
// Auto-vullen wanneer er precies één administratie is en het veld leeg is
|
|
123
|
+
setAdministrationCode((cur) => !cur && list.length === 1 ? list[0].code : cur);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// silent — handmatig typen blijft mogelijk
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
loadStatus();
|
|
131
|
+
loadSyncState();
|
|
132
|
+
loadPushQueue();
|
|
133
|
+
}, []);
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (connected)
|
|
136
|
+
loadOffices();
|
|
137
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
138
|
+
}, [connected]);
|
|
139
|
+
const handleSave = async () => {
|
|
140
|
+
const effectiveClientId = clientId.trim() || existingClientId;
|
|
141
|
+
if (!effectiveClientId) {
|
|
142
|
+
toast.error("Client ID is verplicht");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
setSaving(true);
|
|
146
|
+
try {
|
|
147
|
+
await saveSettings({
|
|
148
|
+
data: {
|
|
149
|
+
client_id: effectiveClientId,
|
|
150
|
+
client_secret: clientSecret || undefined,
|
|
151
|
+
environment,
|
|
152
|
+
administration_code: administrationCode || undefined,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
setClientId("");
|
|
156
|
+
setClientSecret("");
|
|
157
|
+
toast.success("Wijzigingen opgeslagen.");
|
|
158
|
+
await loadStatus();
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
const msg = e?.message;
|
|
162
|
+
toast.error(msg && msg.length < 120 ? msg : "Opslaan mislukt.");
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
setSaving(false);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const handleSaveAdmin = async () => {
|
|
169
|
+
if (!existingClientId) {
|
|
170
|
+
toast.error("Vul eerst je verbindingsgegevens in.");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
setSavingAdmin(true);
|
|
174
|
+
try {
|
|
175
|
+
await saveSettings({
|
|
176
|
+
data: {
|
|
177
|
+
client_id: existingClientId,
|
|
178
|
+
environment,
|
|
179
|
+
administration_code: administrationCode || undefined,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
toast.success("Instellingen opgeslagen.");
|
|
183
|
+
await loadStatus();
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
const msg = e?.message;
|
|
187
|
+
toast.error(msg && msg.length < 120 ? msg : "Opslaan mislukt.");
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
setSavingAdmin(false);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const handleTest = async () => {
|
|
194
|
+
setTesting(true);
|
|
195
|
+
try {
|
|
196
|
+
const res = await testConnection({ data: undefined });
|
|
197
|
+
if (res.ok)
|
|
198
|
+
toast.success(res.message);
|
|
199
|
+
else
|
|
200
|
+
toast.error(res.message);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
toast.error("Verbinding testen is mislukt.");
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
setTesting(false);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
const handleEnvChange = async (newEnv) => {
|
|
210
|
+
if (newEnv === environment)
|
|
211
|
+
return;
|
|
212
|
+
setEnvironment(newEnv);
|
|
213
|
+
try {
|
|
214
|
+
await setEnv({ data: { environment: newEnv } });
|
|
215
|
+
await loadStatus();
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
toast.error("Omgeving wijzigen vereist admin-rechten.");
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
const handleDelete = () => {
|
|
222
|
+
setClientId("");
|
|
223
|
+
setClientSecret("");
|
|
224
|
+
toast.message("Vul nieuwe verbindingsgegevens in en klik op opslaan om te vervangen.");
|
|
225
|
+
};
|
|
226
|
+
const runSync = async (entity) => {
|
|
227
|
+
setBusy(entity);
|
|
228
|
+
try {
|
|
229
|
+
const res = await trigger({ data: { entity_type: entity } });
|
|
230
|
+
if (res.ok)
|
|
231
|
+
toast.success(`${entity === "customers" ? "Debiteuren" : "Verkoopfacturen"} bijgewerkt (${res.upserted} rij(en)).`);
|
|
232
|
+
else
|
|
233
|
+
toast.error(`Bijwerken mislukt: ${res.error ?? "onbekende fout"}`);
|
|
234
|
+
await loadSyncState();
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
const msg = e?.message;
|
|
238
|
+
toast.error(msg && msg.length < 120 ? msg : "Bijwerken mislukt.");
|
|
239
|
+
}
|
|
240
|
+
finally {
|
|
241
|
+
setBusy(null);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const stateFor = (e) => states.find((s) => s.entity_type === e);
|
|
245
|
+
const isSandbox = environment === "sandbox";
|
|
246
|
+
return (_jsxs(Card, { className: "overflow-hidden", children: [_jsxs("div", { className: cn("flex items-start justify-between gap-4 p-6", isExpanded && "border-b"), children: [_jsxs("div", { className: "flex items-start gap-4", children: [_jsx("div", { className: "flex h-11 w-11 shrink-0 items-center justify-center rounded-lg bg-muted p-2", children: _jsx("img", { src: twinfieldLogo, alt: "Twinfield", className: "h-full w-full object-contain" }) }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("h2", { className: "text-lg font-semibold text-foreground", children: "Twinfield koppeling" }), _jsx(Badge, { variant: "secondary", className: "font-normal", children: isSandbox ? "Testomgeving" : "Live omgeving" }), _jsxs("span", { className: "inline-flex items-center gap-1.5 text-sm", children: [_jsx("span", { className: `h-2 w-2 rounded-full ${connected ? "bg-green-500" : "bg-muted-foreground/40"}` }), _jsx("span", { className: connected ? "text-foreground" : "text-muted-foreground", children: loading ? "Laden…" : connected ? "Verbonden" : "Niet verbonden" })] })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Verbind je Twinfield-account om automatisch facturen en debiteuren over te nemen." })] })] }), _jsx("button", { type: "button", onClick: () => setIsExpanded((v) => !v), className: "shrink-0 p-1 rounded-md hover:bg-muted transition-colors", "aria-label": isExpanded ? "Samenvouwen" : "Uitklappen", children: isExpanded ? (_jsx(ChevronUp, { className: "h-5 w-5 text-muted-foreground" })) : (_jsx(ChevronDown, { className: "h-5 w-5 text-muted-foreground" })) })] }), isExpanded && (_jsxs("div", { className: "space-y-8 p-6", children: [_jsx(Step, { n: 1, title: "Kies omgeving", description: "Kies of je wilt testen of live wilt werken.", children: _jsxs("div", { className: "inline-flex rounded-lg border bg-muted/40 p-1", children: [_jsx("button", { type: "button", onClick: () => handleEnvChange("sandbox"), className: `px-4 py-1.5 text-sm rounded-md transition-colors ${isSandbox
|
|
247
|
+
? "bg-background text-foreground shadow-sm"
|
|
248
|
+
: "text-muted-foreground hover:text-foreground"}`, children: "Testomgeving" }), _jsx("button", { type: "button", onClick: () => handleEnvChange("live"), className: `px-4 py-1.5 text-sm rounded-md transition-colors ${!isSandbox
|
|
249
|
+
? "bg-background text-foreground shadow-sm"
|
|
250
|
+
: "text-muted-foreground hover:text-foreground"}`, children: "Live omgeving" })] }) }), _jsx(Step, { n: 2, title: "Vul verbindingsgegevens in", description: "", children: _jsxs("div", { className: "rounded-lg border p-5 space-y-5", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("h4", { className: "font-semibold text-foreground", children: "Verbindingsgegevens" }), hasClientId && (_jsx(Badge, { className: "font-normal", children: "Actief" }))] }), hasClientId && (_jsx("button", { type: "button", onClick: handleDelete, className: "text-sm text-muted-foreground hover:text-foreground", children: "Verwijderen" }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "tw-client-id", children: "Client ID" }), _jsx(Input, { id: "tw-client-id", value: clientId, onChange: (e) => setClientId(e.target.value), placeholder: hasClientId ? "••••••••••••••••" : "", autoComplete: "off" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Te vinden in je Twinfield API-instellingen." }), hasClientId && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Laat leeg als je deze waarde niet wilt wijzigen." }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "tw-client-secret", children: "Client Secret" }), _jsx(Input, { id: "tw-client-secret", type: "password", value: clientSecret, onChange: (e) => setClientSecret(e.target.value), placeholder: hasSecret ? "••••••••" : "", autoComplete: "new-password" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Houd deze sleutel priv\u00E9." }), hasSecret && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Laat leeg als je deze waarde niet wilt wijzigen." }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "tw-redirect", children: "Doorverwijs-URL" }), _jsx(Input, { id: "tw-redirect", value: REDIRECT_URI, readOnly: true, className: "font-mono text-xs" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Voeg deze URL toe als toegestane callback in je Twinfield-app." })] }), _jsx("div", { children: _jsx(Button, { onClick: handleSave, disabled: saving, children: saving ? "Opslaan…" : "Wijzigingen opslaan" }) })] }) }), _jsx(Step, { n: 3, title: "Test de verbinding", description: "Controleer of je gegevens correct zijn en of de koppeling werkt.", children: _jsx(Button, { variant: "outline", onClick: handleTest, disabled: testing, children: testing ? "Testen…" : "Verbinding testen" }) }), _jsx(Step, { n: 4, title: "Kies administratie", description: "Geef aan uit welke Twinfield-administratie we facturen en debiteuren ophalen.", children: _jsxs("div", { className: "rounded-lg border p-5 space-y-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "tw-admin", children: "Administratiecode" }), offices.length > 0 ? (_jsxs(Select, { value: administrationCode || undefined, onValueChange: setAdministrationCode, children: [_jsx(SelectTrigger, { id: "tw-admin", children: _jsx(SelectValue, { placeholder: offices.length === 0
|
|
251
|
+
? "Geen administraties beschikbaar"
|
|
252
|
+
: "Kies een administratie", children: (() => {
|
|
253
|
+
const selected = offices.find((o) => o.code === administrationCode);
|
|
254
|
+
if (!selected)
|
|
255
|
+
return null;
|
|
256
|
+
return (_jsxs("span", { className: "flex items-baseline gap-2 truncate", children: [_jsx("span", { className: "truncate", children: selected.name ?? selected.code }), _jsx("span", { className: "text-[11px] text-muted-foreground font-mono", children: selected.code })] }));
|
|
257
|
+
})() }) }), _jsx(SelectContent, { children: offices.map((o) => (_jsx(SelectItem, { value: o.code, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { children: o.name ?? o.code }), _jsx("span", { className: "text-[11px] text-muted-foreground font-mono", children: o.code })] }) }, o.code))) })] })) : (_jsx(Input, { id: "tw-admin", value: administrationCode, onChange: (e) => setAdministrationCode(e.target.value), autoComplete: "off", placeholder: "Bijv. 1001" })), _jsx("p", { className: "text-xs text-muted-foreground", children: "De code van de administratie in Twinfield (ook wel \"company\" genoemd)." })] }), _jsx(Button, { onClick: handleSaveAdmin, disabled: savingAdmin, children: savingAdmin ? "Opslaan…" : "Instellingen opslaan" })] }) }), _jsxs(Step, { n: 5, title: "Gegevens up-to-date houden", description: "We halen je Twinfield-gegevens automatisch op en bewaren ze in je werkomgeving. Zo werkt alles effici\u00EBnt & snel.", children: [_jsx("div", { className: "space-y-3", children: ([
|
|
258
|
+
{
|
|
259
|
+
key: "customers",
|
|
260
|
+
label: "Debiteuren",
|
|
261
|
+
cadence: "Wordt elk uur automatisch bijgewerkt.",
|
|
262
|
+
countLabel: "Aantal debiteuren",
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
key: "invoices",
|
|
266
|
+
label: "Verkoopfacturen",
|
|
267
|
+
cadence: "Wordt elke 15 minuten automatisch bijgewerkt.",
|
|
268
|
+
countLabel: "Aantal facturen",
|
|
269
|
+
},
|
|
270
|
+
]).map((item) => {
|
|
271
|
+
const s = stateFor(item.key);
|
|
272
|
+
const count = item.key === "customers" ? counts.customers : counts.invoices;
|
|
273
|
+
return (_jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-semibold text-foreground", children: item.label }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: item.cadence })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => runSync(item.key), disabled: busy === item.key, children: busy === item.key ? "Bezig…" : "Nu bijwerken" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "rounded-md border bg-muted/30 px-3 py-2", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Laatst bijgewerkt" }), _jsx("div", { className: "text-sm font-medium text-foreground mt-0.5", children: relativeTime(s?.last_synced_at ?? null) })] }), _jsxs("div", { className: "rounded-md border bg-muted/30 px-3 py-2", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: item.countLabel }), _jsx("div", { className: "text-sm font-medium text-foreground mt-0.5", children: count })] })] }), s?.last_error && (_jsxs("div", { className: "rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-900", children: [_jsx("div", { className: "font-medium", children: "Laatste automatische poging mislukte tijdelijk" }), _jsx("div", { className: "mt-1 text-amber-800", children: "Twinfield reageerde niet correct. De volgende geplande update probeert het automatisch opnieuw \u2014 je kunt ook nu zelf op \"Nu bijwerken\" klikken." }), _jsxs("details", { className: "mt-1.5", children: [_jsx("summary", { className: "cursor-pointer text-amber-700 hover:text-amber-900", children: "Technische details" }), _jsx("div", { className: "mt-1 font-mono text-[11px] text-amber-900/80 break-all", children: s.last_error })] })] }))] }, item.key));
|
|
274
|
+
}) }), connected && pushQueue.length > 0 && (_jsxs("div", { className: "mt-6 rounded-lg border p-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsx("div", { className: "font-semibold text-foreground", children: "Uitgaande facturen (push-wachtrij)" }), _jsx("div", { className: "text-sm text-muted-foreground mt-0.5", children: "Laatste 20 facturen die vanuit de facturatiemodule naar Twinfield zijn gestuurd." })] }), _jsx(Button, { size: "sm", variant: "outline", onClick: loadPushQueue, children: "Vernieuwen" })] }), _jsx("div", { className: "mt-3 divide-y rounded-md border", children: pushQueue.map((q) => {
|
|
275
|
+
const variant = q.status === "done"
|
|
276
|
+
? "default"
|
|
277
|
+
: q.status === "dead" || q.status === "error"
|
|
278
|
+
? "destructive"
|
|
279
|
+
: "secondary";
|
|
280
|
+
return (_jsxs("div", { className: "flex items-start justify-between gap-3 px-3 py-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Badge, { variant: variant, children: q.status }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["poging ", q.attempts, "/5"] }), q.external_id && (_jsxs("span", { className: "text-xs font-mono text-foreground", children: ["#", q.external_id] }))] }), q.last_error && (_jsx("div", { className: "mt-1 text-xs text-muted-foreground break-all", children: q.last_error })), _jsx("div", { className: "mt-0.5 text-[11px] text-muted-foreground", children: relativeTime(q.updated_at ?? q.created_at) })] }), (q.status === "error" || q.status === "dead") && (_jsx(Button, { size: "sm", variant: "outline", disabled: retrying === q.id, onClick: async () => {
|
|
281
|
+
setRetrying(q.id);
|
|
282
|
+
try {
|
|
283
|
+
await retryQueueItem({ data: { id: q.id } });
|
|
284
|
+
toast.success("Opnieuw in de wachtrij geplaatst.");
|
|
285
|
+
await loadPushQueue();
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
toast.error(e?.message ?? "Opnieuw proberen mislukt");
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
setRetrying(null);
|
|
292
|
+
}
|
|
293
|
+
}, children: retrying === q.id ? "Bezig…" : "Opnieuw" }))] }, q.id));
|
|
294
|
+
}) })] }))] })] })), isExpanded && (_jsx("div", { className: "border-t px-6 py-4 text-center", children: _jsx("a", { href: "https://accounting.twinfield.com/webservices/documentation/", target: "_blank", rel: "noreferrer", className: "text-sm text-primary hover:underline", children: "Waar vind ik deze gegevens in Twinfield?" }) }))] }));
|
|
295
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface MijnItem {
|
|
2
|
+
id: string;
|
|
3
|
+
created_at: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
}
|
|
6
|
+
export declare function useMijnModuleData(): {
|
|
7
|
+
items: MijnItem[];
|
|
8
|
+
loading: boolean;
|
|
9
|
+
error: string | null;
|
|
10
|
+
reload: () => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=useMijnModuleData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMijnModuleData.d.ts","sourceRoot":"","sources":["../../src/hooks/useMijnModuleData.ts"],"names":[],"mappings":"AAkBA,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,iBAAiB;;;;;EAkChC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// src/hooks/useMijnModuleData.ts — EXAMPLE DATA HOOK
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Put your data fetching logic in hooks like this, not directly in components.
|
|
5
|
+
// This keeps your pages clean and makes it easy to reuse data across pages.
|
|
6
|
+
//
|
|
7
|
+
// WHAT TO DO:
|
|
8
|
+
// - Rename to match your data (e.g. useProducts, useOrders, useSuppliers)
|
|
9
|
+
// - Replace the example query with your real Supabase query
|
|
10
|
+
// - Replace MijnItem with your actual table's row type
|
|
11
|
+
// - Import and use in your page components
|
|
12
|
+
// - Delete this file if you prefer a different pattern
|
|
13
|
+
// ============================================================================
|
|
14
|
+
import { useState, useEffect, useCallback } from "react";
|
|
15
|
+
import { supabase, toast } from "@flowselections/core";
|
|
16
|
+
export function useMijnModuleData() {
|
|
17
|
+
const [items, setItems] = useState([]);
|
|
18
|
+
const [loading, setLoading] = useState(true);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const load = useCallback(async () => {
|
|
21
|
+
setLoading(true);
|
|
22
|
+
setError(null);
|
|
23
|
+
try {
|
|
24
|
+
const { data, error } = await supabase
|
|
25
|
+
.from("products") // ← CHANGE THIS to your table
|
|
26
|
+
.select("*")
|
|
27
|
+
.order("created_at", { ascending: false });
|
|
28
|
+
if (error)
|
|
29
|
+
throw error;
|
|
30
|
+
setItems(data ?? []);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
setError("Fout bij het laden van gegevens");
|
|
34
|
+
toast.error("Fout bij het laden van gegevens");
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
setLoading(false);
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
load();
|
|
42
|
+
}, [load]);
|
|
43
|
+
return {
|
|
44
|
+
items,
|
|
45
|
+
loading,
|
|
46
|
+
error,
|
|
47
|
+
reload: load,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FlowModule } from "@flowselections/core";
|
|
2
|
+
export * from './_core-safelist';
|
|
3
|
+
export * from './components/TemplatePage';
|
|
4
|
+
export * from './components/TemplateModulePage';
|
|
5
|
+
export { TemplateModuleLogo } from './components/TemplateModuleLogo';
|
|
6
|
+
export { TemplateModulePage } from "./components/TemplateModulePage";
|
|
7
|
+
export declare const myModule: FlowModule;
|
|
8
|
+
export declare const TwinfieldAuthenticatieModule: FlowModule;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAErE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,eAAO,MAAM,QAAQ,EAAE,UAYtB,CAAC;AAEF,eAAO,MAAM,4BAA4B,YAAW,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// src/index.ts — MODULE CONTRACT
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// This is the most important file in your module.
|
|
5
|
+
// The shell repo reads this file to know:
|
|
6
|
+
// - What nav item to show in the sidebar
|
|
7
|
+
// - What settings cards to add to the settings page
|
|
8
|
+
// - Your module's identity (id, name, version)
|
|
9
|
+
//
|
|
10
|
+
// WHAT TO DO:
|
|
11
|
+
// 1. Replace all PLACEHOLDER values below with your module's real values
|
|
12
|
+
// 2. Import your real settings card components (if any)
|
|
13
|
+
// 3. Add your nav item — use `children` if you need a dropdown
|
|
14
|
+
// 4. Done! You don't need to touch any other file for registration.
|
|
15
|
+
// ============================================================================
|
|
16
|
+
import { LayoutDashboard } from "lucide-react"; // ← Replace with your icon
|
|
17
|
+
export * from './_core-safelist';
|
|
18
|
+
export * from './components/TemplatePage';
|
|
19
|
+
export * from './components/TemplateModulePage';
|
|
20
|
+
export { TemplateModuleLogo } from './components/TemplateModuleLogo';
|
|
21
|
+
export { TemplateModulePage } from "./components/TemplateModulePage";
|
|
22
|
+
import { TwinfieldAuthSettingsCard } from "./components/settings/TwinfieldAuthSettingsCard";
|
|
23
|
+
export const myModule = {
|
|
24
|
+
id: "twinfield_authenticatie",
|
|
25
|
+
name: "Twinfield Authenticatie",
|
|
26
|
+
version: "1.0.0",
|
|
27
|
+
nav: {
|
|
28
|
+
label: "Mijn Module",
|
|
29
|
+
href: "/mijn-module",
|
|
30
|
+
icon: LayoutDashboard,
|
|
31
|
+
},
|
|
32
|
+
settingsCards: [
|
|
33
|
+
{ component: TwinfieldAuthSettingsCard, order: 10 },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
export const TwinfieldAuthenticatieModule = myModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-attacher.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/auth-attacher.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,kBAAkB,mHAS9B,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Client-only middleware that attaches the Supabase bearer token to serverFn RPCs.
|
|
2
|
+
// The body runs only on the client (createMiddleware().client(...)). We dynamic-import
|
|
3
|
+
// the supabase client so its module (which touches localStorage) is NEVER evaluated
|
|
4
|
+
// during SSR / loadEntries.
|
|
5
|
+
import { createMiddleware } from '@tanstack/react-start';
|
|
6
|
+
export const attachSupabaseAuth = createMiddleware({ type: 'function' }).client(async ({ next }) => {
|
|
7
|
+
const { supabase } = await import('./client');
|
|
8
|
+
const { data } = await supabase.auth.getSession();
|
|
9
|
+
const token = data.session?.access_token;
|
|
10
|
+
return next({
|
|
11
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
12
|
+
});
|
|
13
|
+
});
|