elcrm 1.1.13 → 1.1.15
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/README.md +1 -1
- package/dist/index.js +1982 -1443
- package/package.json +3 -3
- package/templates/elcrm-cursor/rules/elcrm-jsdoc.mdc +1 -1
- package/templates/elcrm-cursor/rules/elcrm-packages.mdc +1 -1
- package/templates/elcrm-cursor/rules/elcrm-ui.mdc +3 -1
- package/templates/elcrm-docs/BUTTON.elCRM.md +3 -3
- package/templates/elcrm-docs/CLI.elCRM.md +26 -12
- package/templates/elcrm-docs/COMPONENTS.elCRM.md +18 -10
- package/templates/elcrm-docs/FORM.elCRM.md +11 -3
- package/templates/elcrm-docs/ICONS.elCRM.md +2 -0
- package/templates/elcrm-docs/NOTIFICATIONS.elCRM.md +1 -1
- package/templates/elcrm-docs/SERVER.elCRM.md +3 -1
- package/templates/orbit/README.md +12 -0
- package/templates/orbit/index.html +28 -0
- package/templates/orbit/package.json +39 -0
- package/templates/orbit/src/App.tsx +34 -0
- package/templates/orbit/src/component/Nav.tsx +139 -0
- package/templates/orbit/src/lazyRoute.ts +4 -0
- package/templates/orbit/src/lib/icon.tsx +48 -0
- package/templates/orbit/src/lib/theme.ts +39 -0
- package/templates/orbit/src/main.tsx +54 -0
- package/templates/orbit/src/modules/Analytics/index.tsx +5 -0
- package/templates/orbit/src/modules/Catalog/index.tsx +7 -0
- package/templates/orbit/src/modules/Customers/index.tsx +5 -0
- package/templates/orbit/src/modules/Finances/index.tsx +5 -0
- package/templates/orbit/src/modules/Home/index.tsx +317 -0
- package/templates/orbit/src/modules/Integrations/index.tsx +7 -0
- package/templates/orbit/src/modules/Link/index.tsx +7 -0
- package/templates/orbit/src/modules/Marketing/index.tsx +5 -0
- package/templates/orbit/src/modules/Placeholder.tsx +13 -0
- package/templates/orbit/src/modules/Settings/index.tsx +22 -0
- package/templates/orbit/src/modules/Store/index.tsx +7 -0
- package/templates/orbit/src/pages/Auth.tsx +45 -0
- package/templates/orbit/src/pages/Main.tsx +29 -0
- package/templates/orbit/src/router-data.d.ts +10 -0
- package/templates/orbit/src/state.ts +28 -0
- package/templates/orbit/src/style/elcrm.css +27 -0
- package/templates/orbit/src/style/theme-dark.css +148 -0
- package/templates/orbit/src/style/theme-light.css +148 -0
- package/templates/orbit/src/style/theme.css +177 -0
- package/templates/orbit/src/vite-env.d.ts +1 -0
- package/templates/orbit/tsconfig.json +19 -0
- package/templates/orbit/vite.config.ts +14 -0
- package/templates/panel/README.md +1 -1
- package/templates/panel/web/package.json +9 -9
- package/templates/panel/web/src/pages/Auth.tsx +1 -1
- package/templates/panel/web/src/style/theme-dark.css +9 -32
- package/templates/panel/web/src/style/theme-light.css +9 -32
- package/templates/panel/web/src/style/theme.css +20 -39
- package/templates/panel/web/vite.config.ts +3 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import App from "./App";
|
|
4
|
+
import { createErrorBoundary } from "@elcrm/error-boundary";
|
|
5
|
+
import { Router } from "@elcrm/router";
|
|
6
|
+
import { Api } from "@elcrm/api";
|
|
7
|
+
import { ELCRM_STACK } from "@elcrm/core";
|
|
8
|
+
import { applyTheme } from "./lib/theme";
|
|
9
|
+
|
|
10
|
+
(globalThis as typeof globalThis & { React: typeof React }).React = React;
|
|
11
|
+
|
|
12
|
+
import "./style/elcrm.css";
|
|
13
|
+
|
|
14
|
+
void ELCRM_STACK;
|
|
15
|
+
applyTheme();
|
|
16
|
+
|
|
17
|
+
Router.createRouter({
|
|
18
|
+
defaults: { page: "Main", module: "Home", range: "week" },
|
|
19
|
+
auth: "Auth",
|
|
20
|
+
paths: {
|
|
21
|
+
page: "./pages/{segment}",
|
|
22
|
+
module: "./modules/{segment}/index",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
Api.configure({
|
|
27
|
+
onLogout: () => {
|
|
28
|
+
Router.setRouter("page", "Auth");
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
Api.create({
|
|
33
|
+
url: "/v1",
|
|
34
|
+
key: "",
|
|
35
|
+
method: "POST",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const ErrorBoundary = createErrorBoundary({
|
|
39
|
+
FallbackComponent: ({ error, resetError }) => (
|
|
40
|
+
<div className="app-error">
|
|
41
|
+
<h2>Ошибка интерфейса</h2>
|
|
42
|
+
<pre>{error?.message}</pre>
|
|
43
|
+
<button type="button" onClick={resetError}>
|
|
44
|
+
Повторить
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
51
|
+
<ErrorBoundary>
|
|
52
|
+
<App />
|
|
53
|
+
</ErrorBoundary>,
|
|
54
|
+
);
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActionGroup,
|
|
4
|
+
Badge,
|
|
5
|
+
Card,
|
|
6
|
+
Dropdown,
|
|
7
|
+
EmptyState,
|
|
8
|
+
Item,
|
|
9
|
+
ItemColumn,
|
|
10
|
+
List,
|
|
11
|
+
Menu,
|
|
12
|
+
PageHead,
|
|
13
|
+
PageShell,
|
|
14
|
+
Stack,
|
|
15
|
+
Stat,
|
|
16
|
+
TextGroup,
|
|
17
|
+
} from "@elcrm/components";
|
|
18
|
+
import { Button } from "@elcrm/button";
|
|
19
|
+
import { Notice } from "@elcrm/notice";
|
|
20
|
+
import { Router } from "@elcrm/router";
|
|
21
|
+
import { Ico, Icon } from "../../lib/icon";
|
|
22
|
+
import useStateData from "../../state";
|
|
23
|
+
|
|
24
|
+
const RANGE: Record<string, { views: string; visits: string; orders: string; conv: string; convTone: "success" | "danger" }> = {
|
|
25
|
+
day: {
|
|
26
|
+
views: "812",
|
|
27
|
+
visits: "140",
|
|
28
|
+
orders: "21",
|
|
29
|
+
conv: "15%",
|
|
30
|
+
convTone: "success",
|
|
31
|
+
},
|
|
32
|
+
week: {
|
|
33
|
+
views: "5,094",
|
|
34
|
+
visits: "794",
|
|
35
|
+
orders: "1,234",
|
|
36
|
+
conv: "78%",
|
|
37
|
+
convTone: "danger",
|
|
38
|
+
},
|
|
39
|
+
month: {
|
|
40
|
+
views: "18,440",
|
|
41
|
+
visits: "3,102",
|
|
42
|
+
orders: "4,880",
|
|
43
|
+
conv: "62%",
|
|
44
|
+
convTone: "success",
|
|
45
|
+
},
|
|
46
|
+
year: {
|
|
47
|
+
views: "210,118",
|
|
48
|
+
visits: "40,220",
|
|
49
|
+
orders: "52,010",
|
|
50
|
+
conv: "54%",
|
|
51
|
+
convTone: "success",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** Простая кривая выручки — без сторонней библиотеки. */
|
|
56
|
+
function RevenueChart() {
|
|
57
|
+
return (
|
|
58
|
+
<svg
|
|
59
|
+
viewBox="0 0 640 180"
|
|
60
|
+
width="100%"
|
|
61
|
+
height="180"
|
|
62
|
+
aria-label="Revenue"
|
|
63
|
+
role="img"
|
|
64
|
+
>
|
|
65
|
+
<path
|
|
66
|
+
d="M0 140 C 40 130, 80 90, 120 100 S 200 40, 240 70 S 320 150, 360 110 S 440 30, 480 50 S 560 90, 640 20"
|
|
67
|
+
fill="none"
|
|
68
|
+
stroke="currentColor"
|
|
69
|
+
strokeWidth="2"
|
|
70
|
+
/>
|
|
71
|
+
</svg>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Дашборд Home: метрики, выручка, товары, активность.
|
|
77
|
+
*/
|
|
78
|
+
export default function Home() {
|
|
79
|
+
const { user } = useStateData(["user"]);
|
|
80
|
+
const range = String(Router.useRouter("range") || "week");
|
|
81
|
+
const metrics = RANGE[range] ?? RANGE.week;
|
|
82
|
+
const [banner, setBanner] = React.useState(true);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<PageShell>
|
|
86
|
+
<PageHead
|
|
87
|
+
title={`Hello, ${user.displayName}!`}
|
|
88
|
+
actions={
|
|
89
|
+
<Button
|
|
90
|
+
variant="primary"
|
|
91
|
+
width="s"
|
|
92
|
+
before={<Icon d={Ico.globe} size={16} />}
|
|
93
|
+
label="Open Site"
|
|
94
|
+
onClick={() => Notice.Remark("Open Site")}
|
|
95
|
+
/>
|
|
96
|
+
}
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
{banner ? (
|
|
100
|
+
<Card>
|
|
101
|
+
<Stack
|
|
102
|
+
direction="row"
|
|
103
|
+
gap="0.75rem"
|
|
104
|
+
style={{ alignItems: "center" }}
|
|
105
|
+
>
|
|
106
|
+
<Icon d={Ico.crown} />
|
|
107
|
+
<TextGroup title="Upgrade your plan to unlock advanced features" />
|
|
108
|
+
<ActionGroup
|
|
109
|
+
label="Plan"
|
|
110
|
+
items={[
|
|
111
|
+
<Button
|
|
112
|
+
key="plan"
|
|
113
|
+
variant="ghost"
|
|
114
|
+
width="s"
|
|
115
|
+
label="Select Plan"
|
|
116
|
+
onClick={() => Router.setRouter("module", "Settings")}
|
|
117
|
+
/>,
|
|
118
|
+
<Button
|
|
119
|
+
key="close"
|
|
120
|
+
variant="ghost"
|
|
121
|
+
width="s"
|
|
122
|
+
label="×"
|
|
123
|
+
aria-label="Close"
|
|
124
|
+
onClick={() => setBanner(false)}
|
|
125
|
+
/>,
|
|
126
|
+
]}
|
|
127
|
+
/>
|
|
128
|
+
</Stack>
|
|
129
|
+
</Card>
|
|
130
|
+
) : null}
|
|
131
|
+
|
|
132
|
+
<PageHead
|
|
133
|
+
title="Welcome to Orbit"
|
|
134
|
+
subtitle="A quick guide to getting your first sale."
|
|
135
|
+
/>
|
|
136
|
+
<Stack direction="grid" columns={2} gap="0.75rem">
|
|
137
|
+
<Item variant="card">
|
|
138
|
+
<ItemColumn
|
|
139
|
+
icon={<Icon d={Ico.box} />}
|
|
140
|
+
title="Stock your store"
|
|
141
|
+
description="Add products so customers can buy."
|
|
142
|
+
aside={
|
|
143
|
+
<Button
|
|
144
|
+
variant="secondary"
|
|
145
|
+
width="s"
|
|
146
|
+
size="s"
|
|
147
|
+
label="Set Up"
|
|
148
|
+
onClick={() => Router.setRouter("module", "Catalog")}
|
|
149
|
+
/>
|
|
150
|
+
}
|
|
151
|
+
/>
|
|
152
|
+
</Item>
|
|
153
|
+
<Item variant="card">
|
|
154
|
+
<ItemColumn
|
|
155
|
+
icon={<Icon d={Ico.truck} />}
|
|
156
|
+
title="Set your shipping"
|
|
157
|
+
description="Choose how orders are delivered."
|
|
158
|
+
aside={
|
|
159
|
+
<Button
|
|
160
|
+
variant="secondary"
|
|
161
|
+
width="s"
|
|
162
|
+
size="s"
|
|
163
|
+
label="Set Up"
|
|
164
|
+
onClick={() => Router.setRouter("module", "Settings")}
|
|
165
|
+
/>
|
|
166
|
+
}
|
|
167
|
+
/>
|
|
168
|
+
</Item>
|
|
169
|
+
</Stack>
|
|
170
|
+
|
|
171
|
+
<PageHead
|
|
172
|
+
title="Overview performance"
|
|
173
|
+
actions={
|
|
174
|
+
<Menu
|
|
175
|
+
value={range}
|
|
176
|
+
onSelect={(v) => Router.setRouter("range", v)}
|
|
177
|
+
items={[
|
|
178
|
+
{ value: "day", label: "Day" },
|
|
179
|
+
{ value: "week", label: "Week" },
|
|
180
|
+
{ value: "month", label: "Month" },
|
|
181
|
+
{ value: "year", label: "Year" },
|
|
182
|
+
]}
|
|
183
|
+
/>
|
|
184
|
+
}
|
|
185
|
+
/>
|
|
186
|
+
<Stack direction="grid" columns={4} gap="0.75rem">
|
|
187
|
+
<Stat
|
|
188
|
+
label="Total Views"
|
|
189
|
+
value={
|
|
190
|
+
<>
|
|
191
|
+
{metrics.views}{" "}
|
|
192
|
+
<Badge tone="success" size="s">
|
|
193
|
+
+432
|
|
194
|
+
</Badge>
|
|
195
|
+
</>
|
|
196
|
+
}
|
|
197
|
+
/>
|
|
198
|
+
<Stat
|
|
199
|
+
label="Visits"
|
|
200
|
+
value={
|
|
201
|
+
<>
|
|
202
|
+
{metrics.visits}{" "}
|
|
203
|
+
<Badge tone="success" size="s">
|
|
204
|
+
+345
|
|
205
|
+
</Badge>
|
|
206
|
+
</>
|
|
207
|
+
}
|
|
208
|
+
/>
|
|
209
|
+
<Stat
|
|
210
|
+
label="Orders"
|
|
211
|
+
value={
|
|
212
|
+
<>
|
|
213
|
+
{metrics.orders}{" "}
|
|
214
|
+
<Badge tone="success" size="s">
|
|
215
|
+
+345
|
|
216
|
+
</Badge>
|
|
217
|
+
</>
|
|
218
|
+
}
|
|
219
|
+
/>
|
|
220
|
+
<Stat
|
|
221
|
+
label="Conversion Rate"
|
|
222
|
+
value={
|
|
223
|
+
<>
|
|
224
|
+
{metrics.conv}{" "}
|
|
225
|
+
<Badge tone={metrics.convTone} size="s">
|
|
226
|
+
{metrics.convTone === "danger" ? "0.6%" : "+2.1%"}
|
|
227
|
+
</Badge>
|
|
228
|
+
</>
|
|
229
|
+
}
|
|
230
|
+
tone={metrics.convTone}
|
|
231
|
+
/>
|
|
232
|
+
</Stack>
|
|
233
|
+
|
|
234
|
+
<Stack direction="grid" columns={["2fr", "1fr"]} gap="0.75rem">
|
|
235
|
+
<Card>
|
|
236
|
+
<Stack direction="row" gap="0.5rem" style={{ alignItems: "center" }}>
|
|
237
|
+
<TextGroup title="Revenue" />
|
|
238
|
+
<Dropdown
|
|
239
|
+
label="Last Year"
|
|
240
|
+
items={[
|
|
241
|
+
{ value: "year", label: "Last Year" },
|
|
242
|
+
{ value: "month", label: "Last Month" },
|
|
243
|
+
]}
|
|
244
|
+
/>
|
|
245
|
+
</Stack>
|
|
246
|
+
<TextGroup
|
|
247
|
+
title="$14,020,110"
|
|
248
|
+
description="You gained +$420.00 this month"
|
|
249
|
+
/>
|
|
250
|
+
<RevenueChart />
|
|
251
|
+
</Card>
|
|
252
|
+
<Stack direction="column" gap="0.75rem">
|
|
253
|
+
<Card>
|
|
254
|
+
<ItemColumn
|
|
255
|
+
icon={<Icon d={Ico.bell} />}
|
|
256
|
+
title="The Black Friday Starts Tomorrow"
|
|
257
|
+
description="Prepare discounts and stock."
|
|
258
|
+
aside={
|
|
259
|
+
<Button
|
|
260
|
+
variant="ghost"
|
|
261
|
+
width="s"
|
|
262
|
+
size="s"
|
|
263
|
+
label="Learn More"
|
|
264
|
+
onClick={() => Notice.Remark("Shop advisor")}
|
|
265
|
+
/>
|
|
266
|
+
}
|
|
267
|
+
/>
|
|
268
|
+
</Card>
|
|
269
|
+
<Card>
|
|
270
|
+
<Stack direction="row" gap="0.5rem" style={{ alignItems: "center" }}>
|
|
271
|
+
<TextGroup title="Products" />
|
|
272
|
+
<Button
|
|
273
|
+
variant="ghost"
|
|
274
|
+
width="s"
|
|
275
|
+
size="s"
|
|
276
|
+
label="See All"
|
|
277
|
+
onClick={() => Router.setRouter("module", "Catalog")}
|
|
278
|
+
/>
|
|
279
|
+
</Stack>
|
|
280
|
+
<List>
|
|
281
|
+
<Item as="li">
|
|
282
|
+
<ItemColumn title="Active listings" extra={<Badge size="s">6</Badge>} />
|
|
283
|
+
</Item>
|
|
284
|
+
<Item as="li">
|
|
285
|
+
<ItemColumn title="Expired" extra={<Badge size="s">2</Badge>} />
|
|
286
|
+
</Item>
|
|
287
|
+
<Item as="li">
|
|
288
|
+
<ItemColumn title="Sold out" extra={<Badge size="s">5</Badge>} />
|
|
289
|
+
</Item>
|
|
290
|
+
</List>
|
|
291
|
+
</Card>
|
|
292
|
+
<Card>
|
|
293
|
+
<Stack direction="row" gap="0.5rem" style={{ alignItems: "center" }}>
|
|
294
|
+
<TextGroup title="Recent Activities" />
|
|
295
|
+
<Button variant="ghost" width="s" size="s" label="See All" />
|
|
296
|
+
</Stack>
|
|
297
|
+
<EmptyState
|
|
298
|
+
title="You have no recent activity."
|
|
299
|
+
icon={<Icon d={Ico.bell} size={28} />}
|
|
300
|
+
/>
|
|
301
|
+
</Card>
|
|
302
|
+
</Stack>
|
|
303
|
+
</Stack>
|
|
304
|
+
|
|
305
|
+
<div style={{ position: "fixed", right: "1.25rem", bottom: "1.25rem" }}>
|
|
306
|
+
<Button
|
|
307
|
+
variant="primary"
|
|
308
|
+
width="s"
|
|
309
|
+
aria-label="Chat"
|
|
310
|
+
before={<Icon d={Ico.chat} />}
|
|
311
|
+
label="Chat"
|
|
312
|
+
onClick={() => Notice.Remark("Chat")}
|
|
313
|
+
/>
|
|
314
|
+
</div>
|
|
315
|
+
</PageShell>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EmptyState, PageHead, PageShell } from "@elcrm/components";
|
|
2
|
+
|
|
3
|
+
type Props = { title: string; description: string };
|
|
4
|
+
|
|
5
|
+
/** Заглушка раздела Orbit. */
|
|
6
|
+
export function Placeholder({ title, description }: Props) {
|
|
7
|
+
return (
|
|
8
|
+
<PageShell>
|
|
9
|
+
<PageHead title={title} subtitle={description} />
|
|
10
|
+
<EmptyState title="Nothing here yet" description={description} />
|
|
11
|
+
</PageShell>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Card, PageHead, PageShell } from "@elcrm/components";
|
|
3
|
+
import { Button } from "@elcrm/button";
|
|
4
|
+
import { getTheme, toggleTheme, type ThemeMode } from "../../lib/theme";
|
|
5
|
+
|
|
6
|
+
export default function Settings() {
|
|
7
|
+
const [theme, setThemeState] = React.useState<ThemeMode>(() => getTheme());
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<PageShell>
|
|
11
|
+
<PageHead title="Settings" subtitle="Appearance and store." />
|
|
12
|
+
<Card title="Theme">
|
|
13
|
+
<Button
|
|
14
|
+
variant="secondary"
|
|
15
|
+
width="s"
|
|
16
|
+
label={`Switch to ${theme === "dark" ? "light" : "dark"}`}
|
|
17
|
+
onClick={() => setThemeState(toggleTheme())}
|
|
18
|
+
/>
|
|
19
|
+
</Card>
|
|
20
|
+
</PageShell>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Section } from "@elcrm/components";
|
|
3
|
+
import { Router } from "@elcrm/router";
|
|
4
|
+
import { Button } from "@elcrm/button";
|
|
5
|
+
import { Notice } from "@elcrm/notice";
|
|
6
|
+
import { PasswordField, StringField, useForm } from "@elcrm/form";
|
|
7
|
+
import useStateData from "../state";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Вход без API: любой логин/пароль → Main.
|
|
11
|
+
*/
|
|
12
|
+
export default function Auth() {
|
|
13
|
+
const form = useForm({ login: "orbix", password: "orbit" });
|
|
14
|
+
|
|
15
|
+
const onSend = () => {
|
|
16
|
+
const data = form.getValues();
|
|
17
|
+
const login = String(data.login ?? "").trim();
|
|
18
|
+
const password = String(data.password ?? "");
|
|
19
|
+
if (!login || !password) {
|
|
20
|
+
Notice.Error("Fill in login and password");
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
useStateData.setState({
|
|
24
|
+
user: {
|
|
25
|
+
displayName: login,
|
|
26
|
+
email: `${login}@mail.com`,
|
|
27
|
+
shop: "Mob Shop",
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
Notice.Success("Welcome");
|
|
31
|
+
Router.mergeRouter({ module: "Home", page: "Main" });
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Section
|
|
36
|
+
variant="auth"
|
|
37
|
+
title="Orbit"
|
|
38
|
+
description="Demo login — any credentials"
|
|
39
|
+
actions={<Button onSend={onSend} label="Sign in" />}
|
|
40
|
+
>
|
|
41
|
+
<StringField name="login" form={form} label="Login" />
|
|
42
|
+
<PasswordField name="password" form={form} label="Password" />
|
|
43
|
+
</Section>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Layout, Loading } from "@elcrm/components";
|
|
2
|
+
import Nav from "../component/Nav";
|
|
3
|
+
import { LoadRoute } from "@elcrm/router";
|
|
4
|
+
import { createLazyResolver } from "../lazyRoute";
|
|
5
|
+
|
|
6
|
+
const resolveModule = createLazyResolver((segment) =>
|
|
7
|
+
import(`../modules/${segment}/index.tsx`),
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Оболочка: `Layout sidebar` + модули.
|
|
12
|
+
*/
|
|
13
|
+
export default function Main() {
|
|
14
|
+
const root = document.getElementById("root");
|
|
15
|
+
if (root) root.dataset.page = "main";
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Layout sidebar={<Nav />}>
|
|
19
|
+
<LoadRoute
|
|
20
|
+
variant="module"
|
|
21
|
+
defaultName="Home"
|
|
22
|
+
resolveLazyPage={resolveModule}
|
|
23
|
+
loading={<Loading />}
|
|
24
|
+
>
|
|
25
|
+
<span aria-hidden />
|
|
26
|
+
</LoadRoute>
|
|
27
|
+
</Layout>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { State } from "@elcrm/state";
|
|
2
|
+
|
|
3
|
+
export type OrbitUser = {
|
|
4
|
+
displayName: string;
|
|
5
|
+
email: string;
|
|
6
|
+
shop: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type Store = {
|
|
10
|
+
user: OrbitUser;
|
|
11
|
+
shop: string;
|
|
12
|
+
setShop: (shop: string) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Локальный store витрины (без обязательного API).
|
|
17
|
+
*/
|
|
18
|
+
const useStateData = State.create<Store>((set) => ({
|
|
19
|
+
user: {
|
|
20
|
+
displayName: "Orbix",
|
|
21
|
+
email: "orbix@mail.com",
|
|
22
|
+
shop: "Mob Shop",
|
|
23
|
+
},
|
|
24
|
+
shop: "Mob Shop",
|
|
25
|
+
setShop: (shop) => set({ shop }),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export default useStateData;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* База пакетов @elcrm/* + обе темы panel.
|
|
3
|
+
* Порядок важен: сначала CSS пакетов, затем theme-* перекрывают токены.
|
|
4
|
+
*
|
|
5
|
+
* @elcrm/button@1.0.2+ — только style.css (цвета кнопок в theme-light/dark).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* структура компонентов */
|
|
9
|
+
@import "@elcrm/notice/style.css";
|
|
10
|
+
@import "@elcrm/alert/style.css";
|
|
11
|
+
@import "@elcrm/button/style.css";
|
|
12
|
+
@import "@elcrm/search/style.css";
|
|
13
|
+
@import "@elcrm/modal/style.css";
|
|
14
|
+
@import "@elcrm/components/style.css";
|
|
15
|
+
|
|
16
|
+
/* дефолтные токены пакетов (light + dark по data-theme) */
|
|
17
|
+
@import "@elcrm/form/light.css";
|
|
18
|
+
@import "@elcrm/form/dark.css";
|
|
19
|
+
@import "@elcrm/alert/light.css";
|
|
20
|
+
@import "@elcrm/alert/dark.css";
|
|
21
|
+
@import "@elcrm/notice/light.css";
|
|
22
|
+
@import "@elcrm/notice/dark.css";
|
|
23
|
+
|
|
24
|
+
/* общие размеры/геометрия, затем цвета light/dark */
|
|
25
|
+
@import "./theme.css";
|
|
26
|
+
@import "./theme-light.css";
|
|
27
|
+
@import "./theme-dark.css";
|