@simplysm/sd-cli 13.0.70 → 13.0.72

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 (103) hide show
  1. package/dist/commands/init.d.ts +4 -5
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +26 -8
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/sd-cli-entry.d.ts.map +1 -1
  6. package/dist/sd-cli-entry.js +0 -20
  7. package/dist/sd-cli-entry.js.map +1 -1
  8. package/package.json +4 -4
  9. package/src/commands/init.ts +40 -21
  10. package/src/sd-cli-entry.ts +0 -24
  11. package/templates/init/{.prettierrc.yaml.hbs → .prettierrc.yaml} +1 -1
  12. package/templates/init/eslint.config.ts +15 -0
  13. package/templates/init/mise.toml +3 -0
  14. package/templates/init/package.json.hbs +8 -7
  15. package/templates/init/packages/client-admin/index.html.hbs +144 -0
  16. package/templates/init/packages/client-admin/package.json.hbs +26 -0
  17. package/templates/init/packages/client-admin/public/assets/logo-landscape.png +0 -0
  18. package/templates/init/packages/client-admin/public/assets/logo.png +0 -0
  19. package/templates/init/packages/client-admin/src/App.tsx +42 -0
  20. package/templates/init/packages/client-admin/src/dev/DevDialog.tsx +34 -0
  21. package/templates/{add-client/__CLIENT__/src/main.css.hbs → init/packages/client-admin/src/main.css} +1 -1
  22. package/templates/init/packages/client-admin/src/main.tsx.hbs +146 -0
  23. package/templates/init/packages/client-admin/src/providers/AppServiceProvider.tsx.hbs +103 -0
  24. package/templates/init/packages/client-admin/src/providers/AppStructureProvider.tsx +84 -0
  25. package/templates/init/packages/client-admin/src/providers/AuthProvider.tsx.hbs +71 -0
  26. package/templates/init/packages/client-admin/src/providers/configureSharedData.ts.hbs +67 -0
  27. package/templates/init/packages/client-admin/src/views/auth/LoginView.tsx +132 -0
  28. package/templates/init/packages/client-admin/src/views/home/HomeView.tsx +108 -0
  29. package/templates/init/packages/client-admin/src/views/home/base/employee/EmployeeDetail.tsx.hbs +262 -0
  30. package/templates/init/packages/client-admin/src/views/home/base/employee/EmployeeSheet.tsx.hbs +271 -0
  31. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RoleDetail.tsx.hbs +154 -0
  32. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RolePermissionDetail.tsx.hbs +123 -0
  33. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RolePermissionView.tsx +52 -0
  34. package/templates/init/packages/client-admin/src/views/home/base/role-permission/RoleSheet.tsx.hbs +125 -0
  35. package/templates/init/packages/client-admin/src/views/home/main/MainView.tsx.hbs +13 -0
  36. package/templates/init/packages/client-admin/src/views/home/my-info/MyInfoDetail.tsx.hbs +248 -0
  37. package/templates/init/packages/client-admin/src/views/home/system/system-log/SystemLogSheet.tsx.hbs +169 -0
  38. package/templates/init/packages/client-admin/src/views/not-found/NotFoundView.tsx +15 -0
  39. package/templates/init/packages/client-admin/tailwind.config.ts +10 -0
  40. package/templates/init/packages/db-main/package.json.hbs +13 -0
  41. package/templates/init/packages/db-main/src/MainDbContext.ts +20 -0
  42. package/templates/init/packages/db-main/src/dataLogExt.ts +127 -0
  43. package/templates/init/packages/db-main/src/index.ts +10 -0
  44. package/templates/init/packages/db-main/src/tables/Employee.ts +24 -0
  45. package/templates/init/packages/db-main/src/tables/EmployeeConfig.ts +13 -0
  46. package/templates/init/packages/db-main/src/tables/Role.ts +9 -0
  47. package/templates/init/packages/db-main/src/tables/RolePermission.ts +13 -0
  48. package/templates/init/packages/db-main/src/tables/_DataLog.ts +19 -0
  49. package/templates/init/packages/db-main/src/tables/_Log.ts +16 -0
  50. package/templates/init/packages/server/package.json.hbs +20 -0
  51. package/templates/init/packages/server/public-dev/dev//354/264/210/352/270/260/355/231/224.xlsx +0 -0
  52. package/templates/init/packages/server/src/index.ts +4 -0
  53. package/templates/init/packages/server/src/main.ts.hbs +34 -0
  54. package/templates/init/packages/server/src/services/AuthService.ts.hbs +171 -0
  55. package/templates/init/packages/server/src/services/DevService.ts.hbs +94 -0
  56. package/templates/init/packages/server/src/services/EmployeeService.ts.hbs +122 -0
  57. package/templates/init/packages/server/src/services/RoleService.ts.hbs +59 -0
  58. package/templates/init/{pnpm-workspace.yaml.hbs → pnpm-workspace.yaml} +3 -1
  59. package/templates/init/sd.config.ts.hbs +30 -1
  60. package/templates/init/tests/e2e/package.json.hbs +16 -0
  61. package/templates/init/tests/e2e/src/e2e.spec.ts +36 -0
  62. package/templates/init/tests/e2e/src/employee-crud.ts +204 -0
  63. package/templates/init/tests/e2e/src/login.ts +61 -0
  64. package/templates/init/tests/e2e/vitest.setup.ts.hbs +220 -0
  65. package/templates/init/tsconfig.json.hbs +0 -11
  66. package/templates/init/{vitest.config.ts.hbs → vitest.config.ts} +16 -12
  67. package/tests/infra/WorkerManager.spec.ts +1 -1
  68. package/tests/replace-deps.spec.ts +2 -2
  69. package/tests/run-lint.spec.ts +6 -6
  70. package/tests/run-typecheck.spec.ts +3 -3
  71. package/tests/sd-cli.spec.ts +1 -1
  72. package/dist/commands/add-client.d.ts +0 -18
  73. package/dist/commands/add-client.d.ts.map +0 -1
  74. package/dist/commands/add-client.js +0 -79
  75. package/dist/commands/add-client.js.map +0 -6
  76. package/dist/commands/add-server.d.ts +0 -18
  77. package/dist/commands/add-server.d.ts.map +0 -1
  78. package/dist/commands/add-server.js +0 -83
  79. package/dist/commands/add-server.js.map +0 -6
  80. package/dist/utils/config-editor.d.ts +0 -17
  81. package/dist/utils/config-editor.d.ts.map +0 -1
  82. package/dist/utils/config-editor.js +0 -79
  83. package/dist/utils/config-editor.js.map +0 -6
  84. package/src/commands/add-client.ts +0 -126
  85. package/src/commands/add-server.ts +0 -138
  86. package/src/utils/config-editor.ts +0 -141
  87. package/templates/add-client/__CLIENT__/index.html.hbs +0 -13
  88. package/templates/add-client/__CLIENT__/package.json.hbs +0 -16
  89. package/templates/add-client/__CLIENT__/src/App.tsx.hbs +0 -65
  90. package/templates/add-client/__CLIENT__/src/appStructure.ts.hbs +0 -20
  91. package/templates/add-client/__CLIENT__/src/main.tsx.hbs +0 -24
  92. package/templates/add-client/__CLIENT__/src/pages/HomePage.tsx.hbs +0 -9
  93. package/templates/add-client/__CLIENT__/tailwind.config.ts.hbs +0 -15
  94. package/templates/add-server/__SERVER__/package.json.hbs +0 -10
  95. package/templates/add-server/__SERVER__/src/main.ts.hbs +0 -14
  96. package/templates/init/.gitignore.hbs +0 -26
  97. package/templates/init/.npmrc.hbs +0 -1
  98. package/templates/init/eslint.config.ts.hbs +0 -5
  99. package/templates/init/mise.toml.hbs +0 -3
  100. package/tests/config-editor.spec.ts +0 -160
  101. /package/templates/init/{.prettierignore.hbs → .prettierignore} +0 -0
  102. /package/templates/{add-client/__CLIENT__ → init/packages/client-admin}/public/favicon.ico +0 -0
  103. /package/templates/init/{stylelint.config.ts.hbs → stylelint.config.ts} +0 -0
@@ -0,0 +1,146 @@
1
+ import { render } from "solid-js/web";
2
+ import { HashRouter, Navigate, Route } from "@solidjs/router";
3
+ import { For } from "solid-js";
4
+ import {
5
+ DialogProvider,
6
+ PrintProvider,
7
+ SystemProvider,
8
+ useLogger,
9
+ useSyncStorage,
10
+ } from "@simplysm/solid";
11
+ import { DateTime, env, jsonStringify } from "@simplysm/core-common";
12
+ import { expr } from "@{{projectName}}/db-main";
13
+ import { App } from "./App";
14
+
15
+ import { NotFoundView } from "./views/not-found/NotFoundView";
16
+ import { AppStructureProvider, useAppStructure } from "./providers/AppStructureProvider";
17
+ import { AppServiceProvider, useAppService } from "./providers/AppServiceProvider";
18
+ import { AuthProvider, useAuth } from "./providers/AuthProvider";
19
+ import { configureSharedData } from "./providers/configureSharedData";
20
+ import "./main.css";
21
+ import { LoginView } from "./views/auth/LoginView";
22
+ import { HomeView } from "./views/home/HomeView";
23
+
24
+ function AppRoot() {
25
+ const appService = useAppService();
26
+ const auth = useAuth();
27
+ const appStructure = useAppStructure();
28
+
29
+ // SyncStorage
30
+ useSyncStorage()!.configure((origin) => ({
31
+ async getItem(key) {
32
+ const info = auth.authInfo();
33
+ if (!info) return origin.getItem(key);
34
+
35
+ return appService.orm.connectWithoutTransaction(async (db) => {
36
+ const row = await db
37
+ .employeeConfig()
38
+ .where((c) => [expr.eq(c.employeeId, info.employeeId), expr.eq(c.code, key)])
39
+ .first();
40
+ return row?.valueJson ?? null;
41
+ });
42
+ },
43
+
44
+ async setItem(key, value) {
45
+ const info = auth.authInfo();
46
+ if (!info) {
47
+ await origin.setItem(key, value);
48
+ return;
49
+ }
50
+
51
+ await appService.orm.connect(async (db) => {
52
+ await db
53
+ .employeeConfig()
54
+ .where((c) => [expr.eq(c.employeeId, info.employeeId), expr.eq(c.code, key)])
55
+ .upsert(() => ({
56
+ employeeId: expr.val("number", info.employeeId),
57
+ code: expr.val("string", key),
58
+ valueJson: expr.val("string", value),
59
+ }));
60
+ });
61
+ },
62
+
63
+ async removeItem(key) {
64
+ const info = auth.authInfo();
65
+ if (!info) {
66
+ await origin.removeItem(key);
67
+ return;
68
+ }
69
+
70
+ await appService.orm.connect(async (db) => {
71
+ await db
72
+ .employeeConfig()
73
+ .where((c) => [expr.eq(c.employeeId, info.employeeId), expr.eq(c.code, key)])
74
+ .delete();
75
+ });
76
+ },
77
+ }));
78
+
79
+ // Logger
80
+ useLogger().configure((origin) => ({
81
+ async write(severity, ...data) {
82
+ if (env.DEV) {
83
+ await origin.write(severity, ...data);
84
+ return;
85
+ }
86
+
87
+ try {
88
+ const message = data.map((d) => (typeof d === "string" ? d : jsonStringify(d))).join(" ");
89
+
90
+ await appService.orm.connect(async (db) => {
91
+ await db._log().insert([
92
+ {
93
+ clientName: "client-admin",
94
+ dateTime: new DateTime(),
95
+ severity,
96
+ message,
97
+ employeeId: auth.authInfo()?.employeeId,
98
+ },
99
+ ]);
100
+ });
101
+ } catch (err) {
102
+ await origin.write(severity, ...data);
103
+ await origin.write("error", "로그 저장 실패:", err);
104
+ }
105
+ },
106
+ }));
107
+
108
+ // SharedData
109
+ configureSharedData();
110
+
111
+ return (
112
+ <HashRouter>
113
+ <Route path="/" component={App}>
114
+ <Route path="/" component={() => <Navigate href="/login" />} />
115
+ <Route path="/login" component={LoginView} />
116
+ <Route path="/home" component={HomeView}>
117
+ <Route path="/" component={() => <Navigate href="/home/main" />} />
118
+ <For each={appStructure.usableRoutes()}>
119
+ {(r) => <Route path={r.path} component={r.component} />}
120
+ </For>
121
+ <Route path="*" component={NotFoundView} />
122
+ </Route>
123
+ <Route path="*" component={NotFoundView} />
124
+ </Route>
125
+ </HashRouter>
126
+ );
127
+ }
128
+
129
+ render(
130
+ () => (
131
+ <SystemProvider clientName="client-admin">
132
+ <AppServiceProvider>
133
+ <AuthProvider>
134
+ <AppStructureProvider>
135
+ <PrintProvider>
136
+ <DialogProvider>
137
+ <AppRoot />
138
+ </DialogProvider>
139
+ </PrintProvider>
140
+ </AppStructureProvider>
141
+ </AuthProvider>
142
+ </AppServiceProvider>
143
+ </SystemProvider>
144
+ ),
145
+ document.getElementById("root")!,
146
+ );
@@ -0,0 +1,103 @@
1
+ import {
2
+ createContext,
3
+ createSignal,
4
+ onMount,
5
+ Show,
6
+ useContext,
7
+ type ParentComponent,
8
+ } from "solid-js";
9
+ import { useServiceClient } from "@simplysm/solid";
10
+ import {
11
+ createOrmClientConnector,
12
+ type RemoteService,
13
+ type ServiceClient,
14
+ } from "@simplysm/service-client";
15
+ import type {
16
+ AuthServiceMethods,
17
+ DevServiceMethods,
18
+ EmployeeServiceMethods,
19
+ } from "@{{projectName}}/server";
20
+ import { MainDbContext, type MainDbContext as MainDbContextType } from "@{{projectName}}/db-main";
21
+
22
+ export interface IAppServiceClient {
23
+ auth: RemoteService<AuthServiceMethods>;
24
+ dev: RemoteService<DevServiceMethods>;
25
+ employee: RemoteService<EmployeeServiceMethods>;
26
+ sc: ServiceClient;
27
+ orm: {
28
+ connect: <R>(
29
+ fn: (db: MainDbContextType) => Promise<R> | R,
30
+ connOpt?: { configName?: string },
31
+ ) => Promise<R>;
32
+ connectWithoutTransaction: <R>(
33
+ fn: (db: MainDbContextType) => Promise<R> | R,
34
+ connOpt?: { configName?: string },
35
+ ) => Promise<R>;
36
+ };
37
+ }
38
+
39
+ const AppServiceContext = createContext<IAppServiceClient>();
40
+
41
+ export const AppServiceProvider: ParentComponent = (props) => {
42
+ const sc = useServiceClient();
43
+ const [ready, setReady] = createSignal(false);
44
+
45
+ let value: IAppServiceClient;
46
+
47
+ onMount(async () => {
48
+ await sc.connect();
49
+ document.querySelector(".app-loading")?.remove();
50
+
51
+ const client = sc.get();
52
+ const connector = createOrmClientConnector(client);
53
+
54
+ const ormHelper = {
55
+ connect: async <R,>(
56
+ fn: (db: MainDbContextType) => Promise<R> | R,
57
+ connOpt?: { configName?: string },
58
+ ) => {
59
+ return connector.connect(
60
+ {
61
+ dbContextDef: MainDbContext,
62
+ connOpt: { configName: connOpt?.configName ?? "default" },
63
+ },
64
+ fn,
65
+ );
66
+ },
67
+ connectWithoutTransaction: async <R,>(
68
+ fn: (db: MainDbContextType) => Promise<R> | R,
69
+ connOpt?: { configName?: string },
70
+ ) => {
71
+ return connector.connectWithoutTransaction(
72
+ {
73
+ dbContextDef: MainDbContext,
74
+ connOpt: { configName: connOpt?.configName ?? "default" },
75
+ },
76
+ fn,
77
+ );
78
+ },
79
+ };
80
+
81
+ value = {
82
+ auth: client.getService<AuthServiceMethods>("AuthService"),
83
+ dev: client.getService<DevServiceMethods>("DevService"),
84
+ employee: client.getService<EmployeeServiceMethods>("EmployeeService"),
85
+ sc: client,
86
+ orm: ormHelper,
87
+ };
88
+
89
+ setReady(true);
90
+ });
91
+
92
+ return (
93
+ <Show when={ready()}>
94
+ <AppServiceContext.Provider value={value!}>{props.children}</AppServiceContext.Provider>
95
+ </Show>
96
+ );
97
+ };
98
+
99
+ export function useAppService(): IAppServiceClient {
100
+ const ctx = useContext(AppServiceContext);
101
+ if (!ctx) throw new Error("AppServiceProvider is required.");
102
+ return ctx;
103
+ }
@@ -0,0 +1,84 @@
1
+ import { lazy } from "solid-js";
2
+ import { createAppStructure } from "@simplysm/solid";
3
+ import { useAuth } from "./AuthProvider";
4
+
5
+ const MainView = lazy(() =>
6
+ import("../views/home/main/MainView").then((m) => ({ default: m.MainView })),
7
+ );
8
+ const MyInfoDetail = lazy(() =>
9
+ import("../views/home/my-info/MyInfoDetail").then((m) => ({ default: m.MyInfoDetail })),
10
+ );
11
+ const EmployeeSheet = lazy(() =>
12
+ import("../views/home/base/employee/EmployeeSheet").then((m) => ({ default: m.EmployeeSheet })),
13
+ );
14
+ const RolePermissionView = lazy(() =>
15
+ import("../views/home/base/role-permission/RolePermissionView").then((m) => ({
16
+ default: m.RolePermissionView,
17
+ })),
18
+ );
19
+ const SystemLogSheet = lazy(() =>
20
+ import("../views/home/system/system-log/SystemLogSheet").then((m) => ({
21
+ default: m.SystemLogSheet,
22
+ })),
23
+ );
24
+
25
+ export const { AppStructureProvider, useAppStructure } = createAppStructure(() => {
26
+ const auth = useAuth();
27
+ return {
28
+ permRecord: () => auth.authInfo()?.permissions,
29
+ items: [
30
+ {
31
+ code: "home",
32
+ title: "홈",
33
+ children: [
34
+ {
35
+ code: "main",
36
+ title: "메인",
37
+ component: MainView,
38
+ },
39
+ {
40
+ code: "my-info",
41
+ title: "설정",
42
+ component: MyInfoDetail,
43
+ isNotMenu: true,
44
+ },
45
+ {
46
+ code: "base",
47
+ title: "기준정보",
48
+ children: [
49
+ {
50
+ code: "employee",
51
+ title: "직원",
52
+ component: EmployeeSheet,
53
+ perms: ["use", "edit"],
54
+ subPerms: [
55
+ { title: "인증정보", code: "auth", perms: ["use", "edit"] },
56
+ { title: "개인정보", code: "personal", perms: ["use", "edit"] },
57
+ { title: "급여정보", code: "payroll", perms: ["use", "edit"] },
58
+ ],
59
+ },
60
+ {
61
+ code: "role-permission",
62
+ title: "권한그룹",
63
+ component: RolePermissionView,
64
+ perms: ["use", "edit"],
65
+ },
66
+ ],
67
+ },
68
+ {
69
+ code: "system",
70
+ title: "시스템",
71
+ children: [
72
+ {
73
+ code: "system-log",
74
+ title: "시스템로그",
75
+ component: SystemLogSheet,
76
+ perms: ["use"],
77
+ },
78
+ ],
79
+ },
80
+ ],
81
+ },
82
+ ],
83
+ };
84
+ });
@@ -0,0 +1,71 @@
1
+ import { createContext, useContext, type Accessor, type ParentComponent } from "solid-js";
2
+ import { createSignal } from "solid-js";
3
+ import { useLocalStorage } from "@simplysm/solid";
4
+ import { useAppService } from "./AppServiceProvider";
5
+ import type { IAuthData } from "@{{projectName}}/server";
6
+
7
+ interface IAuthContextValue {
8
+ authInfo: Accessor<IAuthData | undefined>;
9
+ login: (email: string, password: string) => Promise<void>;
10
+ logout: () => void;
11
+ tryReloadAuth: () => Promise<boolean>;
12
+ }
13
+
14
+ const AuthContext = createContext<IAuthContextValue>();
15
+
16
+ export const AuthProvider: ParentComponent = (props) => {
17
+ const appService = useAppService();
18
+ const [authInfo, setAuthInfo] = createSignal<IAuthData>();
19
+ const [token, setToken] = useLocalStorage<string | undefined>("auth-token");
20
+
21
+ const login = async (email: string, password: string) => {
22
+ const result = await appService.auth.login(email, password);
23
+
24
+ const { token: newToken, ...authData } = result;
25
+ setToken(newToken);
26
+ await appService.sc.auth(newToken);
27
+
28
+ setAuthInfo(authData);
29
+ };
30
+
31
+ const logout = () => {
32
+ setToken(undefined);
33
+ setAuthInfo(undefined);
34
+ };
35
+
36
+ const tryReloadAuth = async (): Promise<boolean> => {
37
+ const currentToken = token();
38
+ if (currentToken == null || currentToken === "") return false;
39
+
40
+ try {
41
+ await appService.sc.auth(currentToken);
42
+
43
+ const result = await appService.auth.refresh();
44
+
45
+ const { token: newToken, ...authData } = result;
46
+ setToken(newToken);
47
+ await appService.sc.auth(newToken);
48
+
49
+ setAuthInfo(authData);
50
+
51
+ return true;
52
+ } catch (err) {
53
+ // eslint-disable-next-line no-console
54
+ console.warn("Failed to reload auth:", err);
55
+ setToken(undefined);
56
+ return false;
57
+ }
58
+ };
59
+
60
+ return (
61
+ <AuthContext.Provider value=\{{ authInfo, login, logout, tryReloadAuth }}>
62
+ {props.children}
63
+ </AuthContext.Provider>
64
+ );
65
+ };
66
+
67
+ export function useAuth(): IAuthContextValue {
68
+ const ctx = useContext(AuthContext);
69
+ if (!ctx) throw new Error("AuthProvider is required.");
70
+ return ctx;
71
+ }
@@ -0,0 +1,67 @@
1
+ import { useSharedData } from "@simplysm/solid";
2
+ import { expr } from "@{{projectName}}/db-main";
3
+ import { useAppService } from "./AppServiceProvider";
4
+
5
+ export type EmployeeSharedItem = {
6
+ id: number;
7
+ name: string;
8
+ email: string | undefined;
9
+ isDeleted: boolean;
10
+ };
11
+
12
+ export type RoleSharedItem = {
13
+ id: number;
14
+ name: string;
15
+ isDeleted: boolean;
16
+ };
17
+
18
+ export type AppSharedData = {
19
+ employee: EmployeeSharedItem;
20
+ role: RoleSharedItem;
21
+ };
22
+
23
+ export function configureSharedData() {
24
+ const appService = useAppService();
25
+
26
+ useSharedData<AppSharedData>().configure((_origin) => ({
27
+ employee: {
28
+ fetch: async (changeKeys) => {
29
+ return appService.orm.connect(async (db) => {
30
+ let qr = db.employee().select((item) => ({
31
+ id: item.id,
32
+ name: item.name,
33
+ email: item.email,
34
+ isDeleted: item.isDeleted,
35
+ }));
36
+ if (changeKeys) {
37
+ qr = qr.where((item) => [expr.in(item.id, changeKeys)]);
38
+ }
39
+ return qr.result();
40
+ });
41
+ },
42
+ orderBy: [[(item) => item.name, "asc"]],
43
+ getKey: (item) => item.id,
44
+ getSearchText: (item) => item.name,
45
+ getIsHidden: (item) => item.isDeleted,
46
+ },
47
+ role: {
48
+ fetch: async (changeKeys) => {
49
+ return appService.orm.connect(async (db) => {
50
+ let qr = db.role().select((item) => ({
51
+ id: item.id,
52
+ name: item.name,
53
+ isDeleted: item.isDeleted,
54
+ }));
55
+ if (changeKeys) {
56
+ qr = qr.where((item) => [expr.in(item.id, changeKeys)]);
57
+ }
58
+ return qr.result();
59
+ });
60
+ },
61
+ orderBy: [[(item) => item.name, "asc"]],
62
+ getKey: (item) => item.id,
63
+ getSearchText: (item) => item.name,
64
+ getIsHidden: (item) => item.isDeleted,
65
+ },
66
+ }));
67
+ }
@@ -0,0 +1,132 @@
1
+ import { createSignal, onMount } from "solid-js";
2
+ import { createStore } from "solid-js/store";
3
+ import { useNavigate } from "@solidjs/router";
4
+ import {
5
+ BusyContainer,
6
+ Button,
7
+ Card,
8
+ FormGroup,
9
+ Icon,
10
+ TextInput,
11
+ ThemeToggle,
12
+ useLocalStorage,
13
+ useNotification,
14
+ } from "@simplysm/solid";
15
+ import { useAuth } from "../../providers/AuthProvider";
16
+ import clsx from "clsx";
17
+ import { IconLock, IconMail } from "@tabler/icons-solidjs";
18
+
19
+ type Data = {
20
+ email?: string;
21
+ password?: string;
22
+ };
23
+
24
+ export function LoginView() {
25
+ const auth = useAuth();
26
+ const navigate = useNavigate();
27
+ const noti = useNotification();
28
+
29
+ const [busyCount, setBusyCount] = createSignal(0);
30
+ const [ready, setReady] = createSignal(false);
31
+
32
+ const [lastEmail, setLastEmail] = useLocalStorage<string | undefined>("last-login-email");
33
+ const [data, setData] = createStore<Data>({ email: lastEmail() });
34
+
35
+ onMount(async () => {
36
+ const ok = await auth.tryReloadAuth();
37
+ if (ok) {
38
+ navigate("/home/main", { replace: true });
39
+ return;
40
+ }
41
+ setReady(true);
42
+ });
43
+
44
+ const handleSubmit = async (e: Event) => {
45
+ e.preventDefault();
46
+ if (busyCount() > 0) return;
47
+
48
+ const currEmail = data.email!;
49
+ const currPassword = data.password!;
50
+
51
+ setBusyCount((c) => c + 1);
52
+ try {
53
+ await auth.login(currEmail, currPassword);
54
+ setLastEmail(currEmail);
55
+ navigate("/home/main", { replace: true });
56
+ } catch (err) {
57
+ noti.error(err, "로그인 오류");
58
+ }
59
+ setBusyCount((c) => c - 1);
60
+ };
61
+
62
+ return (
63
+ <BusyContainer
64
+ ready={ready()}
65
+ busy={busyCount() > 0}
66
+ class={clsx("flex items-center justify-center", "pb-64", "bg-base-100 dark:bg-base-900")}
67
+ >
68
+ <div class={"max-w-sm"}>
69
+ {/* Logo */}
70
+ <div class={clsx("flex justify-center", "mb-4", "animate-fade-in")}>
71
+ <img src="assets/logo.png" alt="logo" class="scale-75" />
72
+ </div>
73
+
74
+ <Card class={clsx("rounded-2xl p-8", "animate-fade-in [animation-delay:0.3s]")}>
75
+ {/* Form */}
76
+ <form onSubmit={handleSubmit}>
77
+ <FormGroup class={"w-full"}>
78
+ <FormGroup.Item>
79
+ <TextInput
80
+ class="w-full"
81
+ type="email"
82
+ placeholder="이메일을 입력하세요"
83
+ required
84
+ touchMode
85
+ size="lg"
86
+ autocomplete="employeename"
87
+ value={data.email}
88
+ onValueChange={(v) => setData("email", v)}
89
+ >
90
+ <TextInput.Prefix>
91
+ <Icon icon={IconMail} />
92
+ </TextInput.Prefix>
93
+ </TextInput>
94
+ </FormGroup.Item>
95
+ <FormGroup.Item>
96
+ <TextInput
97
+ required
98
+ touchMode
99
+ class="w-full"
100
+ type="password"
101
+ placeholder="비밀번호를 입력하세요"
102
+ size="lg"
103
+ autocomplete="current-password"
104
+ value={data.password}
105
+ onValueChange={(v) => setData("password", v)}
106
+ >
107
+ <TextInput.Prefix>
108
+ <Icon icon={IconLock} />
109
+ </TextInput.Prefix>
110
+ </TextInput>
111
+ </FormGroup.Item>
112
+
113
+ <FormGroup.Item>
114
+ <Button
115
+ theme="primary"
116
+ variant="solid"
117
+ class="w-full"
118
+ type="submit"
119
+ size="xl"
120
+ disabled={busyCount() > 0}
121
+ >
122
+ 로그인
123
+ </Button>
124
+ </FormGroup.Item>
125
+ </FormGroup>
126
+ </form>
127
+ </Card>
128
+ </div>
129
+ <ThemeToggle class="fixed bottom-4 right-4" />
130
+ </BusyContainer>
131
+ );
132
+ }