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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1982 -1443
  3. package/package.json +3 -3
  4. package/templates/elcrm-cursor/rules/elcrm-jsdoc.mdc +1 -1
  5. package/templates/elcrm-cursor/rules/elcrm-packages.mdc +1 -1
  6. package/templates/elcrm-cursor/rules/elcrm-ui.mdc +3 -1
  7. package/templates/elcrm-docs/BUTTON.elCRM.md +3 -3
  8. package/templates/elcrm-docs/CLI.elCRM.md +26 -12
  9. package/templates/elcrm-docs/COMPONENTS.elCRM.md +18 -10
  10. package/templates/elcrm-docs/FORM.elCRM.md +11 -3
  11. package/templates/elcrm-docs/ICONS.elCRM.md +2 -0
  12. package/templates/elcrm-docs/NOTIFICATIONS.elCRM.md +1 -1
  13. package/templates/elcrm-docs/SERVER.elCRM.md +3 -1
  14. package/templates/orbit/README.md +12 -0
  15. package/templates/orbit/index.html +28 -0
  16. package/templates/orbit/package.json +39 -0
  17. package/templates/orbit/src/App.tsx +34 -0
  18. package/templates/orbit/src/component/Nav.tsx +139 -0
  19. package/templates/orbit/src/lazyRoute.ts +4 -0
  20. package/templates/orbit/src/lib/icon.tsx +48 -0
  21. package/templates/orbit/src/lib/theme.ts +39 -0
  22. package/templates/orbit/src/main.tsx +54 -0
  23. package/templates/orbit/src/modules/Analytics/index.tsx +5 -0
  24. package/templates/orbit/src/modules/Catalog/index.tsx +7 -0
  25. package/templates/orbit/src/modules/Customers/index.tsx +5 -0
  26. package/templates/orbit/src/modules/Finances/index.tsx +5 -0
  27. package/templates/orbit/src/modules/Home/index.tsx +317 -0
  28. package/templates/orbit/src/modules/Integrations/index.tsx +7 -0
  29. package/templates/orbit/src/modules/Link/index.tsx +7 -0
  30. package/templates/orbit/src/modules/Marketing/index.tsx +5 -0
  31. package/templates/orbit/src/modules/Placeholder.tsx +13 -0
  32. package/templates/orbit/src/modules/Settings/index.tsx +22 -0
  33. package/templates/orbit/src/modules/Store/index.tsx +7 -0
  34. package/templates/orbit/src/pages/Auth.tsx +45 -0
  35. package/templates/orbit/src/pages/Main.tsx +29 -0
  36. package/templates/orbit/src/router-data.d.ts +10 -0
  37. package/templates/orbit/src/state.ts +28 -0
  38. package/templates/orbit/src/style/elcrm.css +27 -0
  39. package/templates/orbit/src/style/theme-dark.css +148 -0
  40. package/templates/orbit/src/style/theme-light.css +148 -0
  41. package/templates/orbit/src/style/theme.css +177 -0
  42. package/templates/orbit/src/vite-env.d.ts +1 -0
  43. package/templates/orbit/tsconfig.json +19 -0
  44. package/templates/orbit/vite.config.ts +14 -0
  45. package/templates/panel/README.md +1 -1
  46. package/templates/panel/web/package.json +9 -9
  47. package/templates/panel/web/src/pages/Auth.tsx +1 -1
  48. package/templates/panel/web/src/style/theme-dark.css +9 -32
  49. package/templates/panel/web/src/style/theme-light.css +9 -32
  50. package/templates/panel/web/src/style/theme.css +20 -39
  51. 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,5 @@
1
+ import { Placeholder } from "../Placeholder";
2
+
3
+ export default function Analytics() {
4
+ return <Placeholder title="Analytics" description="Traffic and conversion." />;
5
+ }
@@ -0,0 +1,7 @@
1
+ import { Placeholder } from "../Placeholder";
2
+
3
+ export default function Catalog() {
4
+ return (
5
+ <Placeholder title="Catalog" description="Products and collections." />
6
+ );
7
+ }
@@ -0,0 +1,5 @@
1
+ import { Placeholder } from "../Placeholder";
2
+
3
+ export default function Customers() {
4
+ return <Placeholder title="Customers" description="People who bought from you." />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { Placeholder } from "../Placeholder";
2
+
3
+ export default function Finances() {
4
+ return <Placeholder title="Finances" description="Payouts and invoices." />;
5
+ }
@@ -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,7 @@
1
+ import { Placeholder } from "../Placeholder";
2
+
3
+ export default function Integrations() {
4
+ return (
5
+ <Placeholder title="Integrations" description="Connect payments and apps." />
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ import { Placeholder } from "../Placeholder";
2
+
3
+ export default function Link() {
4
+ return (
5
+ <Placeholder title="Sell Via Link" description="Share a checkout link." />
6
+ );
7
+ }
@@ -0,0 +1,5 @@
1
+ import { Placeholder } from "../Placeholder";
2
+
3
+ export default function Marketing() {
4
+ return <Placeholder title="Marketing" description="Campaigns and discounts." />;
5
+ }
@@ -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,7 @@
1
+ import { Placeholder } from "../Placeholder";
2
+
3
+ export default function Store() {
4
+ return (
5
+ <Placeholder title="Online Store" description="Theme and domain." />
6
+ );
7
+ }
@@ -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,10 @@
1
+ import "@elcrm/router";
2
+
3
+ declare module "@elcrm/router" {
4
+ interface RouterDataMap {
5
+ page: string;
6
+ module: string;
7
+ range: string;
8
+ revenuePeriod: string;
9
+ }
10
+ }
@@ -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";