@open-mercato/core 0.4.2-canary-ad4e7882e9 → 0.4.2-canary-c71ef83148
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/modules/auth/frontend/login.js +25 -7
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/customer-todos/widget.js +2 -1
- package/dist/modules/customers/widgets/dashboard/customer-todos/widget.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/new-customers/widget.js +2 -1
- package/dist/modules/customers/widgets/dashboard/new-customers/widget.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/new-deals/widget.js +2 -1
- package/dist/modules/customers/widgets/dashboard/new-deals/widget.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/next-interactions/widget.js +2 -1
- package/dist/modules/customers/widgets/dashboard/next-interactions/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js +2 -1
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/auth/frontend/login.tsx +30 -8
- package/src/modules/customers/widgets/dashboard/customer-todos/widget.ts +2 -2
- package/src/modules/customers/widgets/dashboard/new-customers/widget.ts +2 -2
- package/src/modules/customers/widgets/dashboard/new-deals/widget.ts +2 -2
- package/src/modules/customers/widgets/dashboard/next-interactions/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.ts +2 -2
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.ts +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useState } from "react";
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
4
|
import Image from "next/image";
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
@@ -63,7 +63,10 @@ function looksLikeJsonString(value) {
|
|
|
63
63
|
}
|
|
64
64
|
function LoginPage() {
|
|
65
65
|
const t = useT();
|
|
66
|
-
const translate = (
|
|
66
|
+
const translate = useCallback(
|
|
67
|
+
(key, fallback, params) => translateWithFallback(t, key, fallback, params),
|
|
68
|
+
[t]
|
|
69
|
+
);
|
|
67
70
|
const router = useRouter();
|
|
68
71
|
const searchParams = useSearchParams();
|
|
69
72
|
const requireRole = (searchParams.get("requireRole") || searchParams.get("role") || "").trim();
|
|
@@ -77,6 +80,8 @@ function LoginPage() {
|
|
|
77
80
|
const [tenantId, setTenantId] = useState(null);
|
|
78
81
|
const [tenantName, setTenantName] = useState(null);
|
|
79
82
|
const [tenantLoading, setTenantLoading] = useState(false);
|
|
83
|
+
const [tenantInvalid, setTenantInvalid] = useState(null);
|
|
84
|
+
const showTenantInvalid = tenantId != null && tenantInvalid === tenantId;
|
|
80
85
|
useEffect(() => {
|
|
81
86
|
const tenantParam = (searchParams.get("tenant") || "").trim();
|
|
82
87
|
if (tenantParam) {
|
|
@@ -93,10 +98,17 @@ function LoginPage() {
|
|
|
93
98
|
useEffect(() => {
|
|
94
99
|
if (!tenantId) {
|
|
95
100
|
setTenantName(null);
|
|
101
|
+
setTenantInvalid(null);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (tenantInvalid === tenantId) {
|
|
105
|
+
setTenantName(null);
|
|
106
|
+
setTenantLoading(false);
|
|
96
107
|
return;
|
|
97
108
|
}
|
|
98
109
|
let active = true;
|
|
99
110
|
setTenantLoading(true);
|
|
111
|
+
setTenantInvalid(null);
|
|
100
112
|
apiCall(
|
|
101
113
|
`/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`
|
|
102
114
|
).then(({ result }) => {
|
|
@@ -107,11 +119,13 @@ function LoginPage() {
|
|
|
107
119
|
}
|
|
108
120
|
const message = translate("auth.login.errors.tenantInvalid", "Tenant not found. Clear the tenant selection and try again.");
|
|
109
121
|
setTenantName(null);
|
|
110
|
-
|
|
122
|
+
setTenantInvalid(tenantId);
|
|
123
|
+
setError(null);
|
|
111
124
|
}).catch(() => {
|
|
112
125
|
if (!active) return;
|
|
113
126
|
setTenantName(null);
|
|
114
|
-
|
|
127
|
+
setTenantInvalid(tenantId);
|
|
128
|
+
setError(null);
|
|
115
129
|
}).finally(() => {
|
|
116
130
|
if (active) setTenantLoading(false);
|
|
117
131
|
});
|
|
@@ -124,6 +138,7 @@ function LoginPage() {
|
|
|
124
138
|
clearTenantCookie();
|
|
125
139
|
setTenantId(null);
|
|
126
140
|
setTenantName(null);
|
|
141
|
+
setTenantInvalid(null);
|
|
127
142
|
const params = new URLSearchParams(searchParams);
|
|
128
143
|
params.delete("tenant");
|
|
129
144
|
setError(null);
|
|
@@ -216,13 +231,16 @@ function LoginPage() {
|
|
|
216
231
|
!!translatedFeatures.length && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-center text-xs text-blue-900", children: translate("auth.login.featureDenied", "You don't have access to this feature ({feature}). Please contact your administrator.", {
|
|
217
232
|
feature: translatedFeatures.join(", ")
|
|
218
233
|
}) }),
|
|
219
|
-
|
|
234
|
+
showTenantInvalid ? /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700", children: [
|
|
235
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium", children: translate("auth.login.errors.tenantInvalid", "Tenant not found. Clear the tenant selection and try again.") }),
|
|
236
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "mt-2 text-red-700", onClick: handleClearTenant, children: translate("auth.login.tenantClear", "Clear") })
|
|
237
|
+
] }) : tenantId ? /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900", children: [
|
|
220
238
|
/* @__PURE__ */ jsx("div", { className: "font-medium", children: tenantLoading ? translate("auth.login.tenantLoading", "Loading tenant details...") : translate("auth.login.tenantBanner", "You're logging in to {tenant} tenant.", {
|
|
221
239
|
tenant: tenantName || tenantId
|
|
222
240
|
}) }),
|
|
223
241
|
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "mt-2 text-emerald-900", onClick: handleClearTenant, children: translate("auth.login.tenantClear", "Clear") })
|
|
224
|
-
] }),
|
|
225
|
-
error && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700", role: "alert", "aria-live": "polite", children: error }),
|
|
242
|
+
] }) : null,
|
|
243
|
+
error && !showTenantInvalid && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700", role: "alert", "aria-live": "polite", children: error }),
|
|
226
244
|
/* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
|
|
227
245
|
/* @__PURE__ */ jsx(Label, { htmlFor: "email", children: t("auth.email") }),
|
|
228
246
|
/* @__PURE__ */ jsx(Input, { id: "email", name: "email", type: "email", required: true, "aria-invalid": !!error })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/auth/frontend/login.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport { useEffect, useState } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Card, CardContent, CardHeader, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { clearAllOperations } from '@open-mercato/ui/backend/operations/store'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\n\nconst loginTenantKey = 'om_login_tenant'\nconst loginTenantCookieMaxAge = 60 * 60 * 24 * 14\n\nfunction readTenantCookie() {\n if (typeof document === 'undefined') return null\n const entries = document.cookie.split(';')\n for (const entry of entries) {\n const [name, ...rest] = entry.trim().split('=')\n if (name === loginTenantKey) return decodeURIComponent(rest.join('='))\n }\n return null\n}\n\nfunction setTenantCookie(value: string) {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=${encodeURIComponent(value)}; path=/; max-age=${loginTenantCookieMaxAge}; samesite=lax`\n}\n\nfunction clearTenantCookie() {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=; path=/; max-age=0; samesite=lax`\n}\n\nfunction extractErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const entry of payload) {\n const resolved = extractErrorMessage(entry)\n if (resolved) return resolved\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n const candidates: unknown[] = [\n record.error,\n record.message,\n record.detail,\n record.details,\n record.description,\n ]\n for (const candidate of candidates) {\n const resolved = extractErrorMessage(candidate)\n if (resolved) return resolved\n }\n }\n return null\n}\n\nfunction looksLikeJsonString(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.startsWith('{') || trimmed.startsWith('[')\n}\n\nexport default function LoginPage() {\n const t = useT()\n const translate = (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params)\n const router = useRouter()\n const searchParams = useSearchParams()\n const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()\n const requireFeature = (searchParams.get('requireFeature') || '').trim()\n const requiredRoles = requireRole ? requireRole.split(',').map((value) => value.trim()).filter(Boolean) : []\n const requiredFeatures = requireFeature ? requireFeature.split(',').map((value) => value.trim()).filter(Boolean) : []\n const translatedRoles = requiredRoles.map((role) => translate(`auth.roles.${role}`, role))\n const translatedFeatures = requiredFeatures.map((feature) => translate(`features.${feature}`, feature))\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [tenantId, setTenantId] = useState<string | null>(null)\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [tenantLoading, setTenantLoading] = useState(false)\n\n useEffect(() => {\n const tenantParam = (searchParams.get('tenant') || '').trim()\n if (tenantParam) {\n setTenantId(tenantParam)\n window.localStorage.setItem(loginTenantKey, tenantParam)\n setTenantCookie(tenantParam)\n return\n }\n const storedTenant = window.localStorage.getItem(loginTenantKey) || readTenantCookie()\n if (storedTenant) {\n setTenantId(storedTenant)\n }\n }, [searchParams])\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n return\n }\n let active = true\n setTenantLoading(true)\n apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n if (result?.ok && result.tenant) {\n setTenantName(result.tenant.name)\n return\n }\n const message = translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')\n setTenantName(null)\n setError(message)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n setError(translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.'))\n })\n .finally(() => {\n if (active) setTenantLoading(false)\n })\n return () => {\n active = false\n }\n }, [tenantId, translate])\n\n function handleClearTenant() {\n window.localStorage.removeItem(loginTenantKey)\n clearTenantCookie()\n setTenantId(null)\n setTenantName(null)\n const params = new URLSearchParams(searchParams)\n params.delete('tenant')\n setError(null)\n const query = params.toString()\n router.replace(query ? `/login?${query}` : '/login')\n }\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n if (requiredRoles.length) form.set('requireRole', requiredRoles.join(','))\n const res = await fetch('/api/auth/login', { method: 'POST', body: form })\n if (res.redirected) {\n clearAllOperations()\n // NextResponse.redirect from API\n router.replace(res.url)\n return\n }\n if (!res.ok) {\n const fallback = (() => {\n if (res.status === 403) {\n return translate(\n 'auth.login.errors.permissionDenied',\n 'You do not have permission to access this area. Please contact your administrator.',\n )\n }\n if (res.status === 401 || res.status === 400) {\n return translate('auth.login.errors.invalidCredentials', 'Invalid email or password')\n }\n return translate('auth.login.errors.generic', 'An error occurred. Please try again.')\n })()\n const cloned = res.clone()\n let errorMessage = ''\n const contentType = res.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n try {\n const data = await res.json()\n errorMessage = extractErrorMessage(data) || ''\n } catch {\n try {\n const text = await cloned.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n } else {\n try {\n const text = await res.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n setError(errorMessage || fallback)\n return\n }\n // In case API returns 200 with JSON\n const data = await res.json().catch(() => null)\n clearAllOperations()\n if (data && data.redirect) {\n router.replace(data.redirect)\n }\n } catch (err: unknown) {\n // Handle any errors thrown (e.g., network errors or thrown exceptions)\n const message = err instanceof Error ? err.message : ''\n setError(message || translate('auth.login.errors.generic', 'An error occurred. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader className=\"flex flex-col items-center gap-4 text-center p-10\">\n <Image alt={translate('auth.login.logoAlt', 'Open Mercato logo')} src=\"/open-mercato.svg\" width={150} height={150} priority />\n <h1 className=\"text-2xl font-semibold\">{translate('auth.login.brandName', 'Open Mercato')}</h1>\n <CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate>\n {tenantId ? (\n <input type=\"hidden\" name=\"tenantId\" value={tenantId} />\n ) : null}\n {!!translatedRoles.length && (\n <div className=\"rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-center text-xs text-blue-900\">\n {translate(\n translatedRoles.length > 1 ? 'auth.login.requireRolesMessage' : 'auth.login.requireRoleMessage',\n translatedRoles.length > 1\n ? 'Access requires one of the following roles: {roles}'\n : 'Access requires role: {roles}',\n { roles: translatedRoles.join(', ') },\n )}\n </div>\n )}\n {!!translatedFeatures.length && (\n <div className=\"rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-center text-xs text-blue-900\">\n {translate('auth.login.featureDenied', \"You don't have access to this feature ({feature}). Please contact your administrator.\", {\n feature: translatedFeatures.join(', '),\n })}\n </div>\n )}\n {tenantId && (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900\">\n <div className=\"font-medium\">\n {tenantLoading\n ? translate('auth.login.tenantLoading', 'Loading tenant details...')\n : translate('auth.login.tenantBanner', \"You're logging in to {tenant} tenant.\", {\n tenant: tenantName || tenantId,\n })}\n </div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"mt-2 text-emerald-900\" onClick={handleClearTenant}>\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n )}\n {error && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700\" role=\"alert\" aria-live=\"polite\">\n {error}\n </div>\n )}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <Input id=\"email\" name=\"email\" type=\"email\" required aria-invalid={!!error} />\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.password')}</Label>\n <Input id=\"password\" name=\"password\" type=\"password\" required aria-invalid={!!error} />\n </div>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input type=\"checkbox\" name=\"remember\" className=\"accent-foreground\" />\n <span>{translate('auth.login.rememberMe', 'Remember me')}</span>\n </label>\n <button disabled={submitting} className=\"h-10 rounded-md bg-foreground text-background mt-2 hover:opacity-90 transition disabled:opacity-60\">\n {submitting ? translate('auth.login.loading', 'Loading...') : translate('auth.signIn', 'Sign in')}\n </button>\n <div className=\"text-xs text-muted-foreground mt-2\">\n <Link className=\"underline\" href=\"/reset\">\n {translate('auth.login.forgotPassword', 'Forgot password?')}\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { useCallback, useEffect, useState } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Card, CardContent, CardHeader, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { clearAllOperations } from '@open-mercato/ui/backend/operations/store'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\n\nconst loginTenantKey = 'om_login_tenant'\nconst loginTenantCookieMaxAge = 60 * 60 * 24 * 14\n\nfunction readTenantCookie() {\n if (typeof document === 'undefined') return null\n const entries = document.cookie.split(';')\n for (const entry of entries) {\n const [name, ...rest] = entry.trim().split('=')\n if (name === loginTenantKey) return decodeURIComponent(rest.join('='))\n }\n return null\n}\n\nfunction setTenantCookie(value: string) {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=${encodeURIComponent(value)}; path=/; max-age=${loginTenantCookieMaxAge}; samesite=lax`\n}\n\nfunction clearTenantCookie() {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=; path=/; max-age=0; samesite=lax`\n}\n\nfunction extractErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const entry of payload) {\n const resolved = extractErrorMessage(entry)\n if (resolved) return resolved\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n const candidates: unknown[] = [\n record.error,\n record.message,\n record.detail,\n record.details,\n record.description,\n ]\n for (const candidate of candidates) {\n const resolved = extractErrorMessage(candidate)\n if (resolved) return resolved\n }\n }\n return null\n}\n\nfunction looksLikeJsonString(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.startsWith('{') || trimmed.startsWith('[')\n}\n\nexport default function LoginPage() {\n const t = useT()\n const translate = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n const router = useRouter()\n const searchParams = useSearchParams()\n const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()\n const requireFeature = (searchParams.get('requireFeature') || '').trim()\n const requiredRoles = requireRole ? requireRole.split(',').map((value) => value.trim()).filter(Boolean) : []\n const requiredFeatures = requireFeature ? requireFeature.split(',').map((value) => value.trim()).filter(Boolean) : []\n const translatedRoles = requiredRoles.map((role) => translate(`auth.roles.${role}`, role))\n const translatedFeatures = requiredFeatures.map((feature) => translate(`features.${feature}`, feature))\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [tenantId, setTenantId] = useState<string | null>(null)\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [tenantLoading, setTenantLoading] = useState(false)\n const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)\n const showTenantInvalid = tenantId != null && tenantInvalid === tenantId\n\n useEffect(() => {\n const tenantParam = (searchParams.get('tenant') || '').trim()\n if (tenantParam) {\n setTenantId(tenantParam)\n window.localStorage.setItem(loginTenantKey, tenantParam)\n setTenantCookie(tenantParam)\n return\n }\n const storedTenant = window.localStorage.getItem(loginTenantKey) || readTenantCookie()\n if (storedTenant) {\n setTenantId(storedTenant)\n }\n }, [searchParams])\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n setTenantInvalid(null)\n return\n }\n if (tenantInvalid === tenantId) {\n setTenantName(null)\n setTenantLoading(false)\n return\n }\n let active = true\n setTenantLoading(true)\n setTenantInvalid(null)\n apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n if (result?.ok && result.tenant) {\n setTenantName(result.tenant.name)\n return\n }\n const message = translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .finally(() => {\n if (active) setTenantLoading(false)\n })\n return () => {\n active = false\n }\n }, [tenantId, translate])\n\n function handleClearTenant() {\n window.localStorage.removeItem(loginTenantKey)\n clearTenantCookie()\n setTenantId(null)\n setTenantName(null)\n setTenantInvalid(null)\n const params = new URLSearchParams(searchParams)\n params.delete('tenant')\n setError(null)\n const query = params.toString()\n router.replace(query ? `/login?${query}` : '/login')\n }\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n if (requiredRoles.length) form.set('requireRole', requiredRoles.join(','))\n const res = await fetch('/api/auth/login', { method: 'POST', body: form })\n if (res.redirected) {\n clearAllOperations()\n // NextResponse.redirect from API\n router.replace(res.url)\n return\n }\n if (!res.ok) {\n const fallback = (() => {\n if (res.status === 403) {\n return translate(\n 'auth.login.errors.permissionDenied',\n 'You do not have permission to access this area. Please contact your administrator.',\n )\n }\n if (res.status === 401 || res.status === 400) {\n return translate('auth.login.errors.invalidCredentials', 'Invalid email or password')\n }\n return translate('auth.login.errors.generic', 'An error occurred. Please try again.')\n })()\n const cloned = res.clone()\n let errorMessage = ''\n const contentType = res.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n try {\n const data = await res.json()\n errorMessage = extractErrorMessage(data) || ''\n } catch {\n try {\n const text = await cloned.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n } else {\n try {\n const text = await res.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n setError(errorMessage || fallback)\n return\n }\n // In case API returns 200 with JSON\n const data = await res.json().catch(() => null)\n clearAllOperations()\n if (data && data.redirect) {\n router.replace(data.redirect)\n }\n } catch (err: unknown) {\n // Handle any errors thrown (e.g., network errors or thrown exceptions)\n const message = err instanceof Error ? err.message : ''\n setError(message || translate('auth.login.errors.generic', 'An error occurred. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader className=\"flex flex-col items-center gap-4 text-center p-10\">\n <Image alt={translate('auth.login.logoAlt', 'Open Mercato logo')} src=\"/open-mercato.svg\" width={150} height={150} priority />\n <h1 className=\"text-2xl font-semibold\">{translate('auth.login.brandName', 'Open Mercato')}</h1>\n <CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate>\n {tenantId ? (\n <input type=\"hidden\" name=\"tenantId\" value={tenantId} />\n ) : null}\n {!!translatedRoles.length && (\n <div className=\"rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-center text-xs text-blue-900\">\n {translate(\n translatedRoles.length > 1 ? 'auth.login.requireRolesMessage' : 'auth.login.requireRoleMessage',\n translatedRoles.length > 1\n ? 'Access requires one of the following roles: {roles}'\n : 'Access requires role: {roles}',\n { roles: translatedRoles.join(', ') },\n )}\n </div>\n )}\n {!!translatedFeatures.length && (\n <div className=\"rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-center text-xs text-blue-900\">\n {translate('auth.login.featureDenied', \"You don't have access to this feature ({feature}). Please contact your administrator.\", {\n feature: translatedFeatures.join(', '),\n })}\n </div>\n )}\n {showTenantInvalid ? (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700\">\n <div className=\"font-medium\">{translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')}</div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"mt-2 text-red-700\" onClick={handleClearTenant}>\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : tenantId ? (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900\">\n <div className=\"font-medium\">\n {tenantLoading\n ? translate('auth.login.tenantLoading', 'Loading tenant details...')\n : translate('auth.login.tenantBanner', \"You're logging in to {tenant} tenant.\", {\n tenant: tenantName || tenantId,\n })}\n </div>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"mt-2 text-emerald-900\" onClick={handleClearTenant}>\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : null}\n {error && !showTenantInvalid && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700\" role=\"alert\" aria-live=\"polite\">\n {error}\n </div>\n )}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <Input id=\"email\" name=\"email\" type=\"email\" required aria-invalid={!!error} />\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.password')}</Label>\n <Input id=\"password\" name=\"password\" type=\"password\" required aria-invalid={!!error} />\n </div>\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input type=\"checkbox\" name=\"remember\" className=\"accent-foreground\" />\n <span>{translate('auth.login.rememberMe', 'Remember me')}</span>\n </label>\n <button disabled={submitting} className=\"h-10 rounded-md bg-foreground text-background mt-2 hover:opacity-90 transition disabled:opacity-60\">\n {submitting ? translate('auth.login.loading', 'Loading...') : translate('auth.signIn', 'Sign in')}\n </button>\n <div className=\"text-xs text-muted-foreground mt-2\">\n <Link className=\"underline\" href=\"/reset\">\n {translate('auth.login.forgotPassword', 'Forgot password?')}\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["data"]
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateCustomerTodoSettings } from "./config.js";
|
|
3
|
+
const CustomerTodosWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "customers.dashboard.todos",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/widgets/dashboard/customer-todos/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateCustomerTodoSettings, type CustomerTodoWidgetSettings } from './config'\nconst CustomerTodosWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<CustomerTodoWidgetSettings> = {\n metadata: {\n id: 'customers.dashboard.todos',\n title: 'Customer Todos',\n description: 'Review the latest tasks linked to customers and jump directly to their records.',\n features: ['dashboards.view', 'customers.widgets.todos'],\n defaultSize: 'md',\n defaultEnabled: true,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['customers', 'activities'],\n category: 'customers',\n icon: 'check-square',\n supportsRefresh: true,\n },\n Widget: CustomerTodosWidget,\n hydrateSettings: hydrateCustomerTodoSettings,\n dehydrateSettings: (settings) => ({\n pageSize: settings.pageSize,\n }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,mCAAoE;AAC/F,MAAM,sBAAsB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAE/E,MAAM,SAA4D;AAAA,EAChE,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,mBAAmB,yBAAyB;AAAA,IACvD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,YAAY;AAAA,IAChC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,mBAAmB,CAAC,cAAc;AAAA,IAChC,UAAU,SAAS;AAAA,EACrB;AACF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateNewCustomersSettings } from "./config.js";
|
|
3
|
+
const CustomerNewCustomersWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "customers.dashboard.newCustomers",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/widgets/dashboard/new-customers/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateNewCustomersSettings, type CustomerNewCustomersSettings } from './config'\nconst CustomerNewCustomersWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<CustomerNewCustomersSettings> = {\n metadata: {\n id: 'customers.dashboard.newCustomers',\n title: 'New Customers',\n description: 'Track the most recently added customers to follow up quickly.',\n features: ['dashboards.view', 'customers.widgets.new-customers'],\n defaultSize: 'sm',\n defaultEnabled: true,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['customers'],\n category: 'customers',\n icon: 'user-plus',\n supportsRefresh: true,\n },\n Widget: CustomerNewCustomersWidget,\n hydrateSettings: hydrateNewCustomersSettings,\n dehydrateSettings: (settings) => ({\n pageSize: settings.pageSize,\n kind: settings.kind,\n }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,mCAAsE;AACjG,MAAM,6BAA6B,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAEtF,MAAM,SAA8D;AAAA,EAClE,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,mBAAmB,iCAAiC;AAAA,IAC/D,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,WAAW;AAAA,IAClB,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,mBAAmB,CAAC,cAAc;AAAA,IAChC,UAAU,SAAS;AAAA,IACnB,MAAM,SAAS;AAAA,EACjB;AACF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateNewDealsSettings } from "./config.js";
|
|
3
|
+
const CustomerNewDealsWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "customers.dashboard.newDeals",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/widgets/dashboard/new-deals/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateNewDealsSettings, type CustomerNewDealsSettings } from './config'\nconst CustomerNewDealsWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<CustomerNewDealsSettings> = {\n metadata: {\n id: 'customers.dashboard.newDeals',\n title: 'New Deals',\n description: 'Track the most recently created customer deals to follow up quickly.',\n features: ['dashboards.view', 'customers.widgets.new-deals'],\n defaultSize: 'sm',\n defaultEnabled: true,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['customers', 'deals'],\n category: 'customers',\n icon: 'handshake',\n supportsRefresh: true,\n },\n Widget: CustomerNewDealsWidget,\n hydrateSettings: hydrateNewDealsSettings,\n dehydrateSettings: (settings) => ({\n pageSize: settings.pageSize,\n }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,+BAA8D;AACzF,MAAM,yBAAyB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAElF,MAAM,SAA0D;AAAA,EAC9D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,mBAAmB,6BAA6B;AAAA,IAC3D,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,OAAO;AAAA,IAC3B,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,mBAAmB,CAAC,cAAc;AAAA,IAChC,UAAU,SAAS;AAAA,EACrB;AACF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_SETTINGS,
|
|
4
4
|
hydrateNextInteractionsSettings
|
|
5
5
|
} from "./config.js";
|
|
6
|
+
const CustomerNextInteractionsWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
6
7
|
const widget = {
|
|
7
8
|
metadata: {
|
|
8
9
|
id: "customers.dashboard.nextInteractions",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/widgets/dashboard/next-interactions/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport {\n DEFAULT_SETTINGS,\n hydrateNextInteractionsSettings,\n type CustomerNextInteractionsSettings,\n} from './config'\nconst CustomerNextInteractionsWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<CustomerNextInteractionsSettings> = {\n metadata: {\n id: 'customers.dashboard.nextInteractions',\n title: 'Next Customer Interactions',\n description: 'See the customers with the next interactions scheduled to stay proactive.',\n features: ['dashboards.view', 'customers.widgets.next-interactions'],\n defaultSize: 'md',\n defaultEnabled: true,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['customers', 'activities'],\n category: 'customers',\n icon: 'calendar',\n supportsRefresh: true,\n },\n Widget: CustomerNextInteractionsWidget,\n hydrateSettings: hydrateNextInteractionsSettings,\n dehydrateSettings: (settings) => ({\n pageSize: settings.pageSize,\n includePast: settings.includePast,\n }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,MAAM,iCAAiC,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAE1F,MAAM,SAAkE;AAAA,EACtE,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,mBAAmB,qCAAqC;AAAA,IACnE,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,YAAY;AAAA,IAChC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,mBAAmB,CAAC,cAAc;AAAA,IAChC,UAAU,SAAS;AAAA,IACnB,aAAa,SAAS;AAAA,EACxB;AACF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const AovKpiWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.aovKpi",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/aov-kpi/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type AovKpiSettings } from './config'\nconst AovKpiWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<AovKpiSettings> = {\n metadata: {\n id: 'dashboards.analytics.aovKpi',\n title: 'Average Order Value',\n description: 'Average order value with period comparison',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'sm',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'kpi'],\n category: 'analytics',\n icon: 'trending-up',\n supportsRefresh: true,\n },\n Widget: AovKpiWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, showComparison: s.showComparison }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAA4C;AACvE,MAAM,eAAe,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAExE,MAAM,SAAgD;AAAA,EACpD,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,KAAK;AAAA,IAClC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,gBAAgB,EAAE,eAAe;AACxF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const NewCustomersKpiWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.newCustomersKpi",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type NewCustomersKpiSettings } from './config'\nconst NewCustomersKpiWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<NewCustomersKpiSettings> = {\n metadata: {\n id: 'dashboards.analytics.newCustomersKpi',\n title: 'Customer Growth',\n description: 'New customer count with period comparison',\n features: ['analytics.view', 'customers.people.view'],\n defaultSize: 'sm',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'customers', 'kpi'],\n category: 'analytics',\n icon: 'user-plus',\n supportsRefresh: true,\n },\n Widget: NewCustomersKpiWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, showComparison: s.showComparison }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAAqD;AAChF,MAAM,wBAAwB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAEjF,MAAM,SAAyD;AAAA,EAC7D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,uBAAuB;AAAA,IACpD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,aAAa,KAAK;AAAA,IACtC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,gBAAgB,EAAE,eAAe;AACxF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const OrdersByStatusWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.ordersByStatus",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/orders-by-status/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type OrdersByStatusSettings } from './config'\nconst OrdersByStatusWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<OrdersByStatusSettings> = {\n metadata: {\n id: 'dashboards.analytics.ordersByStatus',\n title: 'Orders by Status',\n description: 'Distribution of orders by status',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'sm',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'chart'],\n category: 'analytics',\n icon: 'pie-chart',\n supportsRefresh: true,\n },\n Widget: OrdersByStatusWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, variant: s.variant }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAAoD;AAC/E,MAAM,uBAAuB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAEhF,MAAM,SAAwD;AAAA,EAC5D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,OAAO;AAAA,IACpC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,SAAS,EAAE,QAAQ;AAC1E;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const OrdersKpiWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.ordersKpi",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/orders-kpi/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type OrdersKpiSettings } from './config'\nconst OrdersKpiWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<OrdersKpiSettings> = {\n metadata: {\n id: 'dashboards.analytics.ordersKpi',\n title: 'Orders',\n description: 'Total order count with period comparison',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'sm',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'kpi'],\n category: 'analytics',\n icon: 'shopping-cart',\n supportsRefresh: true,\n },\n Widget: OrdersKpiWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, showComparison: s.showComparison }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAA+C;AAC1E,MAAM,kBAAkB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAE3E,MAAM,SAAmD;AAAA,EACvD,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,KAAK;AAAA,IAClC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,gBAAgB,EAAE,eAAe;AACxF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const PipelineSummaryWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.pipelineSummary",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type PipelineSummarySettings } from './config'\nconst PipelineSummaryWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<PipelineSummarySettings> = {\n metadata: {\n id: 'dashboards.analytics.pipelineSummary',\n title: 'Pipeline Summary',\n description: 'Deal value by pipeline stage',\n features: ['analytics.view', 'customers.deals.view'],\n defaultSize: 'md',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'customers', 'deals', 'chart'],\n category: 'analytics',\n icon: 'git-branch',\n supportsRefresh: true,\n },\n Widget: PipelineSummaryWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAAqD;AAChF,MAAM,wBAAwB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAEjF,MAAM,SAAyD;AAAA,EAC7D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,sBAAsB;AAAA,IACnD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,aAAa,SAAS,OAAO;AAAA,IACjD,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU;AACtD;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const RevenueKpiWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.revenueKpi",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type RevenueKpiSettings } from './config'\nconst RevenueKpiWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<RevenueKpiSettings> = {\n metadata: {\n id: 'dashboards.analytics.revenueKpi',\n title: 'Revenue',\n description: 'Total revenue with period comparison',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'sm',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'kpi'],\n category: 'analytics',\n icon: 'dollar-sign',\n supportsRefresh: true,\n },\n Widget: RevenueKpiWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, showComparison: s.showComparison }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAAgD;AAC3E,MAAM,mBAAmB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAE5E,MAAM,SAAoD;AAAA,EACxD,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,KAAK;AAAA,IAClC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,gBAAgB,EAAE,eAAe;AACxF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const RevenueTrendWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.revenueTrend",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/revenue-trend/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type RevenueTrendSettings } from './config'\nconst RevenueTrendWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<RevenueTrendSettings> = {\n metadata: {\n id: 'dashboards.analytics.revenueTrend',\n title: 'Revenue Trend',\n description: 'Revenue over time with customizable granularity',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'lg',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'chart'],\n category: 'analytics',\n icon: 'line-chart',\n supportsRefresh: true,\n },\n Widget: RevenueTrendWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, granularity: s.granularity, showArea: s.showArea }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAAkD;AAC7E,MAAM,qBAAqB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAE9E,MAAM,SAAsD;AAAA,EAC1D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,OAAO;AAAA,IACpC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,aAAa,EAAE,aAAa,UAAU,EAAE,SAAS;AACxG;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const SalesByRegionWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.salesByRegion",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/sales-by-region/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type SalesByRegionSettings } from './config'\nconst SalesByRegionWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<SalesByRegionSettings> = {\n metadata: {\n id: 'dashboards.analytics.salesByRegion',\n title: 'Sales by Region',\n description: 'Revenue distribution by shipping region',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'md',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'geography', 'chart'],\n category: 'analytics',\n icon: 'map-pin',\n supportsRefresh: true,\n },\n Widget: SalesByRegionWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, limit: s.limit }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAAmD;AAC9E,MAAM,sBAAsB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAE/E,MAAM,SAAuD;AAAA,EAC3D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,aAAa,OAAO;AAAA,IACjD,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,OAAO,EAAE,MAAM;AACtE;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const TopCustomersWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.topCustomers",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/top-customers/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type TopCustomersSettings } from './config'\nconst TopCustomersWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<TopCustomersSettings> = {\n metadata: {\n id: 'dashboards.analytics.topCustomers',\n title: 'Top Customers',\n description: 'Top customers by revenue',\n features: ['analytics.view', 'sales.orders.view', 'customers.people.view'],\n defaultSize: 'md',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'customers', 'table'],\n category: 'analytics',\n icon: 'users',\n supportsRefresh: true,\n },\n Widget: TopCustomersWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, limit: s.limit }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAAkD;AAC7E,MAAM,qBAAqB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAE9E,MAAM,SAAsD;AAAA,EAC1D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,qBAAqB,uBAAuB;AAAA,IACzE,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,aAAa,OAAO;AAAA,IACjD,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,OAAO,EAAE,MAAM;AACtE;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { lazyDashboardWidget } from "@open-mercato/shared/modules/dashboard/widgets";
|
|
2
2
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
3
|
+
const TopProductsWidget = lazyDashboardWidget(() => import("./widget.client.js"));
|
|
3
4
|
const widget = {
|
|
4
5
|
metadata: {
|
|
5
6
|
id: "dashboards.analytics.topProducts",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/top-products/widget.ts"],
|
|
4
|
-
"sourcesContent": ["import type
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { DEFAULT_SETTINGS, hydrateSettings, type TopProductsSettings } from './config'\nconst TopProductsWidget = lazyDashboardWidget(() => import('./widget.client'))\n\nconst widget: DashboardWidgetModule<TopProductsSettings> = {\n metadata: {\n id: 'dashboards.analytics.topProducts',\n title: 'Top Products',\n description: 'Top-selling products by revenue',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'md',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'products', 'chart'],\n category: 'analytics',\n icon: 'bar-chart-2',\n supportsRefresh: true,\n },\n Widget: TopProductsWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, limit: s.limit, layout: s.layout }),\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,2BAAuD;AAChE,SAAS,kBAAkB,uBAAiD;AAC5E,MAAM,oBAAoB,oBAAoB,MAAM,OAAO,iBAAiB,CAAC;AAE7E,MAAM,SAAqD;AAAA,EACzD,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,YAAY,OAAO;AAAA,IAChD,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,OAAO,EAAE,OAAO,QAAQ,EAAE,OAAO;AACxF;AAEA,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.2-canary-
|
|
3
|
+
"version": "0.4.2-canary-c71ef83148",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.2-canary-
|
|
210
|
+
"@open-mercato/shared": "0.4.2-canary-c71ef83148",
|
|
211
211
|
"@xyflow/react": "^12.6.0",
|
|
212
212
|
"date-fns": "^4.1.0",
|
|
213
213
|
"date-fns-tz": "^3.2.0"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client"
|
|
2
|
-
import { useEffect, useState } from 'react'
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
3
3
|
import Image from 'next/image'
|
|
4
4
|
import Link from 'next/link'
|
|
5
5
|
import { useRouter, useSearchParams } from 'next/navigation'
|
|
@@ -69,8 +69,11 @@ function looksLikeJsonString(value: string): boolean {
|
|
|
69
69
|
|
|
70
70
|
export default function LoginPage() {
|
|
71
71
|
const t = useT()
|
|
72
|
-
const translate = (
|
|
73
|
-
|
|
72
|
+
const translate = useCallback(
|
|
73
|
+
(key: string, fallback: string, params?: Record<string, string | number>) =>
|
|
74
|
+
translateWithFallback(t, key, fallback, params),
|
|
75
|
+
[t],
|
|
76
|
+
)
|
|
74
77
|
const router = useRouter()
|
|
75
78
|
const searchParams = useSearchParams()
|
|
76
79
|
const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()
|
|
@@ -84,6 +87,8 @@ export default function LoginPage() {
|
|
|
84
87
|
const [tenantId, setTenantId] = useState<string | null>(null)
|
|
85
88
|
const [tenantName, setTenantName] = useState<string | null>(null)
|
|
86
89
|
const [tenantLoading, setTenantLoading] = useState(false)
|
|
90
|
+
const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)
|
|
91
|
+
const showTenantInvalid = tenantId != null && tenantInvalid === tenantId
|
|
87
92
|
|
|
88
93
|
useEffect(() => {
|
|
89
94
|
const tenantParam = (searchParams.get('tenant') || '').trim()
|
|
@@ -102,10 +107,17 @@ export default function LoginPage() {
|
|
|
102
107
|
useEffect(() => {
|
|
103
108
|
if (!tenantId) {
|
|
104
109
|
setTenantName(null)
|
|
110
|
+
setTenantInvalid(null)
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
if (tenantInvalid === tenantId) {
|
|
114
|
+
setTenantName(null)
|
|
115
|
+
setTenantLoading(false)
|
|
105
116
|
return
|
|
106
117
|
}
|
|
107
118
|
let active = true
|
|
108
119
|
setTenantLoading(true)
|
|
120
|
+
setTenantInvalid(null)
|
|
109
121
|
apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(
|
|
110
122
|
`/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,
|
|
111
123
|
)
|
|
@@ -117,12 +129,14 @@ export default function LoginPage() {
|
|
|
117
129
|
}
|
|
118
130
|
const message = translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')
|
|
119
131
|
setTenantName(null)
|
|
120
|
-
|
|
132
|
+
setTenantInvalid(tenantId)
|
|
133
|
+
setError(null)
|
|
121
134
|
})
|
|
122
135
|
.catch(() => {
|
|
123
136
|
if (!active) return
|
|
124
137
|
setTenantName(null)
|
|
125
|
-
|
|
138
|
+
setTenantInvalid(tenantId)
|
|
139
|
+
setError(null)
|
|
126
140
|
})
|
|
127
141
|
.finally(() => {
|
|
128
142
|
if (active) setTenantLoading(false)
|
|
@@ -137,6 +151,7 @@ export default function LoginPage() {
|
|
|
137
151
|
clearTenantCookie()
|
|
138
152
|
setTenantId(null)
|
|
139
153
|
setTenantName(null)
|
|
154
|
+
setTenantInvalid(null)
|
|
140
155
|
const params = new URLSearchParams(searchParams)
|
|
141
156
|
params.delete('tenant')
|
|
142
157
|
setError(null)
|
|
@@ -249,7 +264,14 @@ export default function LoginPage() {
|
|
|
249
264
|
})}
|
|
250
265
|
</div>
|
|
251
266
|
)}
|
|
252
|
-
{
|
|
267
|
+
{showTenantInvalid ? (
|
|
268
|
+
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700">
|
|
269
|
+
<div className="font-medium">{translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')}</div>
|
|
270
|
+
<Button type="button" variant="ghost" size="sm" className="mt-2 text-red-700" onClick={handleClearTenant}>
|
|
271
|
+
{translate('auth.login.tenantClear', 'Clear')}
|
|
272
|
+
</Button>
|
|
273
|
+
</div>
|
|
274
|
+
) : tenantId ? (
|
|
253
275
|
<div className="rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900">
|
|
254
276
|
<div className="font-medium">
|
|
255
277
|
{tenantLoading
|
|
@@ -262,8 +284,8 @@ export default function LoginPage() {
|
|
|
262
284
|
{translate('auth.login.tenantClear', 'Clear')}
|
|
263
285
|
</Button>
|
|
264
286
|
</div>
|
|
265
|
-
)}
|
|
266
|
-
{error && (
|
|
287
|
+
) : null}
|
|
288
|
+
{error && !showTenantInvalid && (
|
|
267
289
|
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700" role="alert" aria-live="polite">
|
|
268
290
|
{error}
|
|
269
291
|
</div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import CustomerTodosWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateCustomerTodoSettings, type CustomerTodoWidgetSettings } from './config'
|
|
3
|
+
const CustomerTodosWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<CustomerTodoWidgetSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import CustomerNewCustomersWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateNewCustomersSettings, type CustomerNewCustomersSettings } from './config'
|
|
3
|
+
const CustomerNewCustomersWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<CustomerNewCustomersSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import CustomerNewDealsWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateNewDealsSettings, type CustomerNewDealsSettings } from './config'
|
|
3
|
+
const CustomerNewDealsWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<CustomerNewDealsSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import CustomerNextInteractionsWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import {
|
|
4
3
|
DEFAULT_SETTINGS,
|
|
5
4
|
hydrateNextInteractionsSettings,
|
|
6
5
|
type CustomerNextInteractionsSettings,
|
|
7
6
|
} from './config'
|
|
7
|
+
const CustomerNextInteractionsWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
8
8
|
|
|
9
9
|
const widget: DashboardWidgetModule<CustomerNextInteractionsSettings> = {
|
|
10
10
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import AovKpiWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type AovKpiSettings } from './config'
|
|
3
|
+
const AovKpiWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<AovKpiSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import NewCustomersKpiWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type NewCustomersKpiSettings } from './config'
|
|
3
|
+
const NewCustomersKpiWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<NewCustomersKpiSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import OrdersByStatusWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type OrdersByStatusSettings } from './config'
|
|
3
|
+
const OrdersByStatusWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<OrdersByStatusSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import OrdersKpiWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type OrdersKpiSettings } from './config'
|
|
3
|
+
const OrdersKpiWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<OrdersKpiSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import PipelineSummaryWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type PipelineSummarySettings } from './config'
|
|
3
|
+
const PipelineSummaryWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<PipelineSummarySettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import RevenueKpiWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type RevenueKpiSettings } from './config'
|
|
3
|
+
const RevenueKpiWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<RevenueKpiSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import RevenueTrendWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type RevenueTrendSettings } from './config'
|
|
3
|
+
const RevenueTrendWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<RevenueTrendSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import SalesByRegionWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type SalesByRegionSettings } from './config'
|
|
3
|
+
const SalesByRegionWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<SalesByRegionSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import TopCustomersWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type TopCustomersSettings } from './config'
|
|
3
|
+
const TopCustomersWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<TopCustomersSettings> = {
|
|
6
6
|
metadata: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import TopProductsWidget from './widget.client'
|
|
1
|
+
import { lazyDashboardWidget, type DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'
|
|
3
2
|
import { DEFAULT_SETTINGS, hydrateSettings, type TopProductsSettings } from './config'
|
|
3
|
+
const TopProductsWidget = lazyDashboardWidget(() => import('./widget.client'))
|
|
4
4
|
|
|
5
5
|
const widget: DashboardWidgetModule<TopProductsSettings> = {
|
|
6
6
|
metadata: {
|