wcz-layout 7.6.0 → 7.6.2
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.
Potentially problematic release.
This version of wcz-layout might be problematic. Click here for more details.
- package/dist/{RouterListItemButton-CvfZk2zD.js → RouterListItemButton-DeaQB4ym.js} +1 -1
- package/dist/{RouterListItemButton-CvfZk2zD.js.map → RouterListItemButton-DeaQB4ym.js.map} +1 -1
- package/dist/components/core/Layout.d.ts +1 -1
- package/dist/components/core/navigation/NavigationList.d.ts +4 -4
- package/dist/components/core/navigation/NavigationListItem.d.ts +3 -3
- package/dist/components.js +2 -2
- package/dist/hooks.js +1 -1
- package/dist/index.js +617 -621
- package/dist/index.js.map +1 -1
- package/dist/lib/auth/msalClient.d.ts +8 -2
- package/dist/middleware.js +11 -11
- package/dist/models/Navigation.d.ts +23 -11
- package/dist/{queries-DzKY6YXz.js → queries-D-DV5lCw.js} +3 -3
- package/dist/{queries-DzKY6YXz.js.map → queries-D-DV5lCw.js.map} +1 -1
- package/dist/query.js +2 -2
- package/dist/{queryClient-uWNhcABg.js → queryClient-B__OEZ70.js} +1 -1
- package/dist/{queryClient-uWNhcABg.js.map → queryClient-B__OEZ70.js.map} +1 -1
- package/dist/{msalClient-BLrbVP5z.js → utils-C4oJ0tr5.js} +58 -47
- package/dist/utils-C4oJ0tr5.js.map +1 -0
- package/dist/utils.js +5 -5
- package/package.json +38 -6
- package/skills/api-routes/SKILL.md +251 -0
- package/skills/auth/SKILL.md +268 -0
- package/skills/data-grid/SKILL.md +229 -0
- package/skills/database-schema/SKILL.md +182 -0
- package/skills/dialogs-notifications/SKILL.md +241 -0
- package/skills/forms-validation/SKILL.md +331 -0
- package/skills/forms-validation/references/field-components.md +212 -0
- package/skills/layout-navigation/SKILL.md +259 -0
- package/skills/project-initialization/SKILL.md +181 -0
- package/skills/project-structure/SKILL.md +157 -0
- package/skills/tanstack-db-collections/SKILL.md +270 -0
- package/skills/ui-pages/SKILL.md +278 -0
- package/dist/msalClient-BLrbVP5z.js.map +0 -1
package/dist/query.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as e, t } from "./queries-
|
|
2
|
-
import { t as n } from "./queryClient-
|
|
1
|
+
import { n as e, t } from "./queries-D-DV5lCw.js";
|
|
2
|
+
import { t as n } from "./queryClient-B__OEZ70.js";
|
|
3
3
|
export { t as query, n as queryClient, e as useUploadFile };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queryClient-
|
|
1
|
+
{"version":3,"file":"queryClient-B__OEZ70.js","names":["QueryClient","queryClient"],"sources":["../src/lib/queryClient.ts"],"sourcesContent":["import { QueryClient } from \"@tanstack/react-query\";\n\nexport const queryClient = new QueryClient();\n"],"mappings":";;AAEA,IAAaC,IAAc,IAAID,GAAa"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { permissions as e, scopes as t } from "virtual:wcz-layout";
|
|
2
2
|
import { z as n } from "zod";
|
|
3
|
-
import { EventType as r, InteractionRequiredAuthError as i,
|
|
4
|
-
import { createEnv as
|
|
5
|
-
import { createClientOnlyFn as
|
|
3
|
+
import { EventType as r, InteractionRequiredAuthError as i, NavigationClient as a, PublicClientApplication as o } from "@azure/msal-browser";
|
|
4
|
+
import { createEnv as s } from "@t3-oss/env-core";
|
|
5
|
+
import { createClientOnlyFn as c, createIsomorphicFn as l } from "@tanstack/react-start";
|
|
6
6
|
//#region src/env.ts
|
|
7
|
-
var
|
|
7
|
+
var u = s({
|
|
8
8
|
clientPrefix: "VITE_",
|
|
9
9
|
client: {
|
|
10
10
|
VITE_ENTRA_CLIENT_ID: n.string(),
|
|
@@ -14,7 +14,7 @@ var l = o({
|
|
|
14
14
|
},
|
|
15
15
|
runtimeEnv: import.meta.env,
|
|
16
16
|
emptyStringAsUndefined: !0
|
|
17
|
-
}),
|
|
17
|
+
}), d = s({
|
|
18
18
|
server: {
|
|
19
19
|
ENTRA_CLIENT_ID: n.string(),
|
|
20
20
|
ENTRA_TENANT_ID: n.string(),
|
|
@@ -22,7 +22,49 @@ var l = o({
|
|
|
22
22
|
},
|
|
23
23
|
runtimeEnv: process.env,
|
|
24
24
|
emptyStringAsUndefined: !0
|
|
25
|
-
}),
|
|
25
|
+
}), f = new o({ auth: {
|
|
26
|
+
clientId: u.VITE_ENTRA_CLIENT_ID,
|
|
27
|
+
authority: `https://login.microsoftonline.com/${u.VITE_ENTRA_TENANT_ID}`,
|
|
28
|
+
redirectUri: "/"
|
|
29
|
+
} });
|
|
30
|
+
f.initialize().then(() => (f.addEventCallback((e) => {
|
|
31
|
+
if (e.eventType === r.LOGIN_SUCCESS && e.payload) {
|
|
32
|
+
let t = e.payload.account;
|
|
33
|
+
f.setActiveAccount(t);
|
|
34
|
+
}
|
|
35
|
+
}), f.handleRedirectPromise())).then((e) => {
|
|
36
|
+
e?.account && f.setActiveAccount(e.account);
|
|
37
|
+
});
|
|
38
|
+
var p = class extends a {
|
|
39
|
+
constructor(e) {
|
|
40
|
+
super(), this.navigate = e;
|
|
41
|
+
}
|
|
42
|
+
async navigateInternal(e, t) {
|
|
43
|
+
let n = e.replace(location.origin, "");
|
|
44
|
+
return this.navigate({
|
|
45
|
+
to: n,
|
|
46
|
+
replace: t.noHistory
|
|
47
|
+
}), !1;
|
|
48
|
+
}
|
|
49
|
+
}, m = /* @__PURE__ */ new Set(), h = (e) => (m.add(e), () => {
|
|
50
|
+
m.delete(e);
|
|
51
|
+
}), g = l().server(() => null).client(async () => {
|
|
52
|
+
let e = f.getActiveAccount();
|
|
53
|
+
return e?.idToken ? E(T(e.idToken)) : null;
|
|
54
|
+
}), _ = c(async (e) => {
|
|
55
|
+
let n = f.getActiveAccount();
|
|
56
|
+
if (!n) throw Error("No active account. User not signed in.");
|
|
57
|
+
let r = [...t[e]];
|
|
58
|
+
try {
|
|
59
|
+
let { accessToken: e } = await f.acquireTokenSilent({
|
|
60
|
+
scopes: r,
|
|
61
|
+
account: n
|
|
62
|
+
});
|
|
63
|
+
return e;
|
|
64
|
+
} catch (e) {
|
|
65
|
+
throw e instanceof i && m.forEach((e) => e(r)), e;
|
|
66
|
+
}
|
|
67
|
+
}), v = "#00506E", y = "#64DC00", b = class {
|
|
26
68
|
static get isAndroid() {
|
|
27
69
|
return /android/i.test(this.userAgent);
|
|
28
70
|
}
|
|
@@ -38,21 +80,21 @@ var l = o({
|
|
|
38
80
|
static get userAgent() {
|
|
39
81
|
return typeof navigator > "u" ? "" : navigator.userAgent;
|
|
40
82
|
}
|
|
41
|
-
},
|
|
83
|
+
}, x = (e) => ({
|
|
42
84
|
meta: [
|
|
43
85
|
{ charSet: "utf-8" },
|
|
44
86
|
{
|
|
45
87
|
name: "viewport",
|
|
46
88
|
content: "width=device-width, initial-scale=1"
|
|
47
89
|
},
|
|
48
|
-
{ title:
|
|
90
|
+
{ title: u.VITE_APP_TITLE },
|
|
49
91
|
{
|
|
50
92
|
name: "og:type",
|
|
51
93
|
content: "website"
|
|
52
94
|
},
|
|
53
95
|
{
|
|
54
96
|
name: "og:title",
|
|
55
|
-
content:
|
|
97
|
+
content: u.VITE_APP_TITLE
|
|
56
98
|
},
|
|
57
99
|
{
|
|
58
100
|
name: "og:image",
|
|
@@ -86,21 +128,21 @@ var l = o({
|
|
|
86
128
|
href: "/favicon.ico"
|
|
87
129
|
}
|
|
88
130
|
]
|
|
89
|
-
}),
|
|
90
|
-
let t = await
|
|
131
|
+
}), S = (e) => async () => {
|
|
132
|
+
let t = await g();
|
|
91
133
|
if (!t?.hasPermission(e)) throw Error("You do not have permission to access this page.");
|
|
92
134
|
return { user: t };
|
|
93
|
-
},
|
|
135
|
+
}, C = (e) => {
|
|
94
136
|
let { meta: t } = e.state;
|
|
95
137
|
return {
|
|
96
138
|
isTouched: t.isTouched,
|
|
97
139
|
hasError: !!t.errors.length,
|
|
98
140
|
helperText: t.errors[0]?.message
|
|
99
141
|
};
|
|
100
|
-
},
|
|
142
|
+
}, w = (e) => e.replaceAll(/([a-z])([A-Z])/g, "$1-$2").replaceAll(/[\s_]+/g, "-").replaceAll(/[^a-zA-Z0-9-]/g, "").toLowerCase().replaceAll(/-+/g, "-").replaceAll(/(^-|-$)/g, ""), T = (e) => {
|
|
101
143
|
let t = e.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"), n = decodeURIComponent(atob(t).split("").map((e) => "%" + ("00" + e.charCodeAt(0).toString(16)).slice(-2)).join(""));
|
|
102
144
|
return JSON.parse(n);
|
|
103
|
-
},
|
|
145
|
+
}, E = (t) => ({
|
|
104
146
|
id: t.sub,
|
|
105
147
|
name: t.name?.split("/")[0],
|
|
106
148
|
email: t.preferred_username?.toLowerCase(),
|
|
@@ -108,39 +150,8 @@ var l = o({
|
|
|
108
150
|
employeeId: t.employeeId?.toUpperCase() || "",
|
|
109
151
|
companyName: t.companyName || "",
|
|
110
152
|
hasPermission: (n) => e[n].some((e) => (t.groups ?? []).includes(e))
|
|
111
|
-
}), b = new a({ auth: {
|
|
112
|
-
clientId: l.VITE_ENTRA_CLIENT_ID,
|
|
113
|
-
authority: `https://login.microsoftonline.com/${l.VITE_ENTRA_TENANT_ID}`,
|
|
114
|
-
redirectUri: "/"
|
|
115
|
-
} });
|
|
116
|
-
await b.initialize().then(() => (b.addEventCallback((e) => {
|
|
117
|
-
if (e.eventType === r.LOGIN_SUCCESS && e.payload) {
|
|
118
|
-
let t = e.payload.account;
|
|
119
|
-
b.setActiveAccount(t);
|
|
120
|
-
}
|
|
121
|
-
}), b.handleRedirectPromise())).then((e) => {
|
|
122
|
-
e?.account && b.setActiveAccount(e.account);
|
|
123
|
-
});
|
|
124
|
-
var x = /* @__PURE__ */ new Set(), S = (e) => (x.add(e), () => {
|
|
125
|
-
x.delete(e);
|
|
126
|
-
}), C = c().server(() => null).client(async () => {
|
|
127
|
-
let e = b.getActiveAccount();
|
|
128
|
-
return e?.idToken ? y(v(e.idToken)) : null;
|
|
129
|
-
}), w = s(async (e) => {
|
|
130
|
-
let n = b.getActiveAccount();
|
|
131
|
-
if (!n) throw Error("No active account. User not signed in.");
|
|
132
|
-
let r = [...t[e]];
|
|
133
|
-
try {
|
|
134
|
-
let { accessToken: e } = await b.acquireTokenSilent({
|
|
135
|
-
scopes: r,
|
|
136
|
-
account: n
|
|
137
|
-
});
|
|
138
|
-
return e;
|
|
139
|
-
} catch (e) {
|
|
140
|
-
throw e instanceof i && x.forEach((e) => e(r)), e;
|
|
141
|
-
}
|
|
142
153
|
});
|
|
143
154
|
//#endregion
|
|
144
|
-
export {
|
|
155
|
+
export { C as a, w as c, g as d, f, d as h, E as i, p as l, u as m, v as n, S as o, h as p, y as r, x as s, b as t, _ as u };
|
|
145
156
|
|
|
146
|
-
//# sourceMappingURL=
|
|
157
|
+
//# sourceMappingURL=utils-C4oJ0tr5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils-C4oJ0tr5.js","names":["AuthenticationResult","EventMessage","EventType","InteractionRequiredAuthError","NavigationClient","NavigationOptions","PublicClientApplication","useNavigate","createClientOnlyFn","createIsomorphicFn","scopes","definedScopes","clientEnv","buildUser","decodeJwt","User","pca","auth","clientId","VITE_ENTRA_CLIENT_ID","authority","VITE_ENTRA_TENANT_ID","redirectUri","initialize","then","addEventCallback","event","eventType","LOGIN_SUCCESS","payload","account","setActiveAccount","handleRedirectPromise","response","TanStackNavigationClient","constructor","navigate","ReturnType","navigateInternal","url","options","relative","replace","location","origin","to","noHistory","TokenInteractionListener","Array","tokenInteractionListeners","Set","subscribeToTokenInteractionRequired","listener","add","delete","getUser","server","client","Promise","getActiveAccount","idToken","getAccessToken","scopeKey","Error","accessToken","acquireTokenSilent","error","forEach","AnyFieldApi","permissions","clientEnv","TokenPayload","User","getUser","WISTRON_PRIMARY_COLOR","WISTRON_SECONDARY_COLOR","Platform","isAndroid","test","userAgent","isIOS","isWindows","isMacOS","navigator","RootRouteHeadOptions","manifest","rootRouteHead","options","meta","charSet","name","content","title","VITE_APP_TITLE","links","rel","sizes","href","type","requirePermission","permissionKey","user","hasPermission","Error","FormOmittedProps","getFieldStatus","field","state","isTouched","hasError","errors","length","helperText","message","toKebabCase","str","replaceAll","toLowerCase","decodeJwt","token","base64Payload","split","base64","replace","payload","decodeURIComponent","atob","map","c","charCodeAt","toString","slice","join","JSON","parse","buildUser","id","sub","email","preferred_username","department","toUpperCase","employeeId","companyName","key","allowedGroups","some","k","groups","includes"],"sources":["../src/env.ts","../src/lib/auth/msalClient.ts","../src/lib/utils.ts"],"sourcesContent":["import { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\n\nexport const clientEnv = createEnv({\n clientPrefix: \"VITE_\",\n client: {\n VITE_ENTRA_CLIENT_ID: z.string(),\n VITE_ENTRA_TENANT_ID: z.string(),\n VITE_APP_TITLE: z.string(),\n VITE_MUI_LICENSE_KEY: z.string(),\n },\n runtimeEnv: import.meta.env,\n emptyStringAsUndefined: true,\n});\n\nexport const serverEnv = createEnv({\n server: {\n ENTRA_CLIENT_ID: z.string(),\n ENTRA_TENANT_ID: z.string(),\n ENTRA_CLIENT_SECRET: z.string(),\n },\n runtimeEnv: process.env,\n emptyStringAsUndefined: true,\n});\n","import {\r\n AuthenticationResult,\r\n EventMessage,\r\n EventType,\r\n InteractionRequiredAuthError,\r\n NavigationClient,\r\n NavigationOptions,\r\n PublicClientApplication,\r\n} from \"@azure/msal-browser\";\r\nimport { useNavigate } from \"@tanstack/react-router\";\r\nimport { createClientOnlyFn, createIsomorphicFn } from \"@tanstack/react-start\";\r\nimport { scopes as definedScopes } from \"virtual:wcz-layout\";\r\nimport { clientEnv } from \"~/env\";\r\nimport { buildUser, decodeJwt } from \"~/lib/utils\";\r\nimport type { User } from \"~/models/User\";\r\n\r\nexport const pca = new PublicClientApplication({\r\n auth: {\r\n clientId: clientEnv.VITE_ENTRA_CLIENT_ID,\r\n authority: `https://login.microsoftonline.com/${clientEnv.VITE_ENTRA_TENANT_ID}`,\r\n redirectUri: \"/\",\r\n },\r\n});\r\n\r\npca\r\n .initialize()\r\n .then(() => {\r\n pca.addEventCallback((event: EventMessage) => {\r\n if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {\r\n const payload = event.payload as AuthenticationResult;\r\n const account = payload.account;\r\n pca.setActiveAccount(account);\r\n }\r\n });\r\n\r\n return pca.handleRedirectPromise();\r\n })\r\n .then((response) => {\r\n if (response?.account) pca.setActiveAccount(response.account);\r\n });\r\n\r\nexport class TanStackNavigationClient extends NavigationClient {\r\n constructor(private navigate: ReturnType<typeof useNavigate>) {\r\n super();\r\n }\r\n async navigateInternal(url: string, options: NavigationOptions) {\r\n const relative = url.replace(location.origin, \"\");\r\n this.navigate({ to: relative, replace: options.noHistory });\r\n return false;\r\n }\r\n}\r\n\r\ntype TokenInteractionListener = (scopes: Array<string>) => void;\r\n\r\nconst tokenInteractionListeners = new Set<TokenInteractionListener>();\r\n\r\nexport const subscribeToTokenInteractionRequired = (listener: TokenInteractionListener) => {\r\n tokenInteractionListeners.add(listener);\r\n\r\n return () => {\r\n tokenInteractionListeners.delete(listener);\r\n };\r\n};\r\n\r\nexport const getUser = createIsomorphicFn()\r\n .server(() => null)\r\n .client(async (): Promise<User | null> => {\r\n const account = pca.getActiveAccount();\r\n if (!account?.idToken) return null;\r\n\r\n return buildUser(decodeJwt(account.idToken));\r\n });\r\n\r\n/**\r\n * Token Acquisition: Get authenticated access token.\r\n * Use when: Making API calls from the browser to secured endpoints\r\n */\r\nexport const getAccessToken = createClientOnlyFn(async (scopeKey: keyof typeof definedScopes) => {\r\n const account = pca.getActiveAccount();\r\n if (!account) throw new Error(\"No active account. User not signed in.\");\r\n\r\n const scopes = [...definedScopes[scopeKey]];\r\n\r\n try {\r\n const { accessToken } = await pca.acquireTokenSilent({ scopes, account });\r\n return accessToken;\r\n } catch (error) {\r\n if (error instanceof InteractionRequiredAuthError) {\r\n tokenInteractionListeners.forEach((listener) => listener(scopes));\r\n }\r\n\r\n throw error;\r\n }\r\n});\r\n","import type { AnyFieldApi } from \"@tanstack/react-form\";\r\nimport { permissions } from \"virtual:wcz-layout\";\r\nimport { clientEnv } from \"~/env\";\r\nimport type { TokenPayload } from \"~/models/TokenPayload\";\r\nimport type { User } from \"~/models/User\";\r\nimport { getUser } from \"./auth/msalClient\";\r\n\r\nexport const WISTRON_PRIMARY_COLOR = \"#00506E\";\r\nexport const WISTRON_SECONDARY_COLOR = \"#64DC00\";\r\n\r\nexport class Platform {\r\n static get isAndroid() {\r\n return /android/i.test(this.userAgent);\r\n }\r\n static get isIOS() {\r\n return /iPad|iPhone|iPod/.test(this.userAgent);\r\n }\r\n static get isWindows() {\r\n return /windows/i.test(this.userAgent);\r\n }\r\n static get isMacOS() {\r\n return /Macintosh|MacIntel|MacPPC|Mac68K/.test(this.userAgent);\r\n }\r\n\r\n private static get userAgent() {\r\n return typeof navigator === \"undefined\" ? \"\" : navigator.userAgent;\r\n }\r\n}\r\n\r\ninterface RootRouteHeadOptions {\r\n manifest?: string;\r\n}\r\n\r\nexport const rootRouteHead = (options?: RootRouteHeadOptions) => ({\r\n meta: [\r\n { charSet: \"utf-8\" },\r\n { name: \"viewport\", content: \"width=device-width, initial-scale=1\" },\r\n { title: clientEnv.VITE_APP_TITLE },\r\n { name: \"og:type\", content: \"website\" },\r\n { name: \"og:title\", content: clientEnv.VITE_APP_TITLE },\r\n { name: \"og:image\", content: \"/favicon-32x32.png\" },\r\n ],\r\n links: [\r\n { rel: \"apple-touch-icon\", sizes: \"180x180\", href: \"/apple-touch-icon.png\" },\r\n { rel: \"icon\", type: \"image/png\", sizes: \"32x32\", href: \"/favicon-32x32.png\" },\r\n { rel: \"icon\", type: \"image/png\", sizes: \"16x16\", href: \"/favicon-16x16.png\" },\r\n { rel: \"manifest\", href: options?.manifest || \"/manifest.json\" },\r\n { rel: \"icon\", href: \"/favicon.ico\" },\r\n ],\r\n});\r\n\r\nexport const requirePermission = (permissionKey: keyof typeof permissions) => {\r\n return async () => {\r\n const user = await getUser();\r\n\r\n if (!user?.hasPermission(permissionKey))\r\n throw new Error(\"You do not have permission to access this page.\");\r\n\r\n return { user };\r\n };\r\n};\r\n\r\n/* Internal Utils */\r\nexport type FormOmittedProps =\r\n | \"name\"\r\n | \"value\"\r\n | \"onChange\"\r\n | \"onBlur\"\r\n | \"error\"\r\n | \"helperText\"\r\n | \"renderInput\"\r\n | \"type\"\r\n | \"aria-label\";\r\n\r\nexport const getFieldStatus = (field: AnyFieldApi) => {\r\n const { meta } = field.state;\r\n\r\n const isTouched = meta.isTouched;\r\n const hasError = !!meta.errors.length;\r\n const helperText = meta.errors[0]?.message;\r\n\r\n return { isTouched, hasError, helperText };\r\n};\r\n\r\nexport const toKebabCase = (str: string): string => {\r\n return str\r\n .replaceAll(/([a-z])([A-Z])/g, \"$1-$2\")\r\n .replaceAll(/[\\s_]+/g, \"-\")\r\n .replaceAll(/[^a-zA-Z0-9-]/g, \"\")\r\n .toLowerCase()\r\n .replaceAll(/-+/g, \"-\")\r\n .replaceAll(/(^-|-$)/g, \"\");\r\n};\r\n\r\nexport const decodeJwt = (token: string) => {\r\n const base64Payload = token.split(\".\")[1];\r\n const base64 = base64Payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\r\n const payload = decodeURIComponent(\r\n atob(base64)\r\n .split(\"\")\r\n .map((c) => \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2))\r\n .join(\"\"),\r\n );\r\n return JSON.parse(payload) as TokenPayload;\r\n};\r\n\r\nexport const buildUser = (payload: TokenPayload): User => ({\r\n id: payload.sub,\r\n name: payload.name?.split(\"/\")[0],\r\n email: payload.preferred_username?.toLowerCase(),\r\n department: payload.department?.toUpperCase() || \"\",\r\n employeeId: payload.employeeId?.toUpperCase() || \"\",\r\n companyName: payload.companyName || \"\",\r\n hasPermission: (key: keyof typeof permissions) => {\r\n const allowedGroups = permissions[key];\r\n return allowedGroups.some((k) => (payload.groups ?? []).includes(k));\r\n },\r\n});\r\n"],"mappings":"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"}
|
package/dist/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { h as e, o as t, t as n, u as r } from "./utils-C4oJ0tr5.js";
|
|
2
2
|
import { t as i } from "./dist-Bxzg1_9c.js";
|
|
3
3
|
import { t as a } from "i18next";
|
|
4
4
|
import { scopes as o } from "virtual:wcz-layout";
|
|
@@ -9,9 +9,9 @@ import { ConfidentialClientApplication as l } from "@azure/msal-node";
|
|
|
9
9
|
var u = null;
|
|
10
10
|
function d() {
|
|
11
11
|
return u ??= new l({ auth: {
|
|
12
|
-
clientId:
|
|
13
|
-
clientSecret:
|
|
14
|
-
authority: `https://login.microsoftonline.com/${
|
|
12
|
+
clientId: e.ENTRA_CLIENT_ID,
|
|
13
|
+
clientSecret: e.ENTRA_CLIENT_SECRET,
|
|
14
|
+
authority: `https://login.microsoftonline.com/${e.ENTRA_TENANT_ID}`
|
|
15
15
|
} }), u;
|
|
16
16
|
}
|
|
17
17
|
var f = c(async (e, t) => {
|
|
@@ -30,6 +30,6 @@ var f = c(async (e, t) => {
|
|
|
30
30
|
return i.accessToken;
|
|
31
31
|
});
|
|
32
32
|
//#endregion
|
|
33
|
-
export {
|
|
33
|
+
export { n as Platform, s as createEnv, r as getAccessToken, p as getAppToken, f as getTokenOnBehalfOf, t as requirePermission, a as t, i as uuidv7 };
|
|
34
34
|
|
|
35
35
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wcz-layout",
|
|
3
|
-
"version": "7.6.
|
|
3
|
+
"version": "7.6.2",
|
|
4
4
|
"private": false,
|
|
5
|
+
"keywords": [
|
|
6
|
+
"tanstack-intent"
|
|
7
|
+
],
|
|
5
8
|
"files": [
|
|
6
|
-
"dist"
|
|
9
|
+
"dist",
|
|
10
|
+
"skills",
|
|
11
|
+
"!skills/_artifacts"
|
|
7
12
|
],
|
|
8
13
|
"type": "module",
|
|
9
14
|
"sideEffects": false,
|
|
15
|
+
"types": "./dist/exports/index.d.ts",
|
|
16
|
+
"typesVersions": {
|
|
17
|
+
"*": {
|
|
18
|
+
"components": [
|
|
19
|
+
"./dist/exports/components.d.ts"
|
|
20
|
+
],
|
|
21
|
+
"hooks": [
|
|
22
|
+
"./dist/exports/hooks.d.ts"
|
|
23
|
+
],
|
|
24
|
+
"middleware": [
|
|
25
|
+
"./dist/exports/middleware.d.ts"
|
|
26
|
+
],
|
|
27
|
+
"models": [
|
|
28
|
+
"./dist/exports/models.d.ts"
|
|
29
|
+
],
|
|
30
|
+
"query": [
|
|
31
|
+
"./dist/exports/query.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"utils": [
|
|
34
|
+
"./dist/exports/utils.d.ts"
|
|
35
|
+
],
|
|
36
|
+
"vite": [
|
|
37
|
+
"./dist/exports/vite.d.ts"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
10
41
|
"exports": {
|
|
11
42
|
".": {
|
|
12
43
|
"types": "./dist/exports/index.d.ts",
|
|
@@ -55,12 +86,13 @@
|
|
|
55
86
|
"@azure/msal-node": "^5.1.1",
|
|
56
87
|
"@azure/msal-react": "^5.1.0",
|
|
57
88
|
"@t3-oss/env-core": "^0.13.11",
|
|
58
|
-
"i18next": "^25.10.
|
|
89
|
+
"i18next": "^25.10.9",
|
|
59
90
|
"i18next-browser-languagedetector": "^8.2.1",
|
|
60
|
-
"react-i18next": "^16.6.
|
|
91
|
+
"react-i18next": "^16.6.6"
|
|
61
92
|
},
|
|
62
93
|
"devDependencies": {
|
|
63
94
|
"@rolldown/plugin-babel": "^0.2.2",
|
|
95
|
+
"@tanstack/intent": "^0.0.23",
|
|
64
96
|
"@types/file-saver": "^2.0.7",
|
|
65
97
|
"@types/node": "^25.3.5",
|
|
66
98
|
"@types/react": "^19.2.14",
|
|
@@ -71,8 +103,8 @@
|
|
|
71
103
|
"husky": "^9.1.7",
|
|
72
104
|
"jose": "^6.2.2",
|
|
73
105
|
"nitro": "^3.0.1-alpha.2",
|
|
74
|
-
"oxfmt": "^0.
|
|
75
|
-
"oxlint": "^1.
|
|
106
|
+
"oxfmt": "^0.42.0",
|
|
107
|
+
"oxlint": "^1.57.0",
|
|
76
108
|
"react-dropzone": "^15.0.0",
|
|
77
109
|
"react-intersection-observer": "^10.0.3",
|
|
78
110
|
"react-number-format": "^5.4.5",
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-routes
|
|
3
|
+
description: >
|
|
4
|
+
Create TanStack Start file-based API routes with createFileRoute or
|
|
5
|
+
createServerFn. Wire authorizationMiddleware(permissionKey) and
|
|
6
|
+
validationMiddleware(schema) in server.middleware. Use createHandlers
|
|
7
|
+
for CRUD with Drizzle queries. Response.json patterns. Covers the
|
|
8
|
+
{ middleware, handler } object shape for handlers needing validation.
|
|
9
|
+
Activate when creating or modifying API endpoints or server functions.
|
|
10
|
+
type: core
|
|
11
|
+
library: wcz-layout
|
|
12
|
+
library_version: "7.6.1"
|
|
13
|
+
requires:
|
|
14
|
+
- database-schema
|
|
15
|
+
sources:
|
|
16
|
+
- "wcz-layout:src/middleware/authMiddleware.ts"
|
|
17
|
+
- "wcz-layout:src/middleware/validationMiddleware.ts"
|
|
18
|
+
- "wcz-layout:src/routes/api/"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# API Routes & Server Functions
|
|
22
|
+
|
|
23
|
+
## Setup
|
|
24
|
+
|
|
25
|
+
API routes live under `src/routes/api/` and follow TanStack Router file-based conventions:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
src/routes/api/
|
|
29
|
+
├── health.ts # GET /api/health
|
|
30
|
+
└── todos/
|
|
31
|
+
├── index.ts # GET /api/todos, POST /api/todos
|
|
32
|
+
└── $id.ts # GET /api/todos/:id, PUT /api/todos/:id, DELETE /api/todos/:id
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Core Patterns
|
|
36
|
+
|
|
37
|
+
### Basic CRUD API route
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// src/routes/api/todos/index.ts
|
|
41
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
42
|
+
import { createHandlers } from "@tanstack/start/api";
|
|
43
|
+
import { authorizationMiddleware, validationMiddleware } from "wcz-layout/middleware";
|
|
44
|
+
import { todoTable } from "~/lib/db/schemas/todo";
|
|
45
|
+
import { TodoInsertSchema } from "~/schemas/todo";
|
|
46
|
+
|
|
47
|
+
export const Route = createFileRoute("/api/todos")({
|
|
48
|
+
server: {
|
|
49
|
+
middleware: [authorizationMiddleware("all")],
|
|
50
|
+
handlers: createHandlers({
|
|
51
|
+
GET: async ({ context }) => {
|
|
52
|
+
const items = await context.db.select().from(todoTable);
|
|
53
|
+
return Response.json(items);
|
|
54
|
+
},
|
|
55
|
+
POST: {
|
|
56
|
+
middleware: [validationMiddleware(TodoInsertSchema)],
|
|
57
|
+
handler: async ({ context }) => {
|
|
58
|
+
const [item] = await context.db.insert(todoTable).values(context.data).returning();
|
|
59
|
+
return Response.json(item, { status: 201 });
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Route with path parameters
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// src/routes/api/todos/$id.ts
|
|
71
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
72
|
+
import { createHandlers } from "@tanstack/start/api";
|
|
73
|
+
import { eq } from "drizzle-orm";
|
|
74
|
+
import { authorizationMiddleware, validationMiddleware } from "wcz-layout/middleware";
|
|
75
|
+
import { todoTable } from "~/lib/db/schemas/todo";
|
|
76
|
+
import { TodoSchema } from "~/schemas/todo";
|
|
77
|
+
|
|
78
|
+
export const Route = createFileRoute("/api/todos/$id")({
|
|
79
|
+
server: {
|
|
80
|
+
middleware: [authorizationMiddleware("all")],
|
|
81
|
+
handlers: createHandlers({
|
|
82
|
+
GET: async ({ context, params }) => {
|
|
83
|
+
const [item] = await context.db.select().from(todoTable).where(eq(todoTable.id, params.id));
|
|
84
|
+
if (!item) return new Response(null, { status: 404 });
|
|
85
|
+
return Response.json(item);
|
|
86
|
+
},
|
|
87
|
+
PUT: {
|
|
88
|
+
middleware: [validationMiddleware(TodoSchema)],
|
|
89
|
+
handler: async ({ context, params }) => {
|
|
90
|
+
const [item] = await context.db
|
|
91
|
+
.update(todoTable)
|
|
92
|
+
.set(context.data)
|
|
93
|
+
.where(eq(todoTable.id, params.id))
|
|
94
|
+
.returning();
|
|
95
|
+
return Response.json(item);
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
DELETE: async ({ context, params }) => {
|
|
99
|
+
await context.db.delete(todoTable).where(eq(todoTable.id, params.id));
|
|
100
|
+
return new Response(null, { status: 204 });
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Server functions (RPC-style)
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { createServerFn } from "@tanstack/start";
|
|
111
|
+
|
|
112
|
+
const getTodos = createServerFn({ method: "GET" })
|
|
113
|
+
.middleware([authorizationMiddleware("all")])
|
|
114
|
+
.handler(async ({ context }) => {
|
|
115
|
+
return context.db.select().from(todoTable);
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Server functions are typed end-to-end. Use them for operations that don't need REST semantics.
|
|
120
|
+
|
|
121
|
+
### Middleware composition
|
|
122
|
+
|
|
123
|
+
`authorizationMiddleware(permissionKey)` already includes `authenticationMiddleware` internally. It verifies the JWT token and injects `context.user` with a `User` object that has `hasPermission()`. If the user lacks the permission, it returns 403.
|
|
124
|
+
|
|
125
|
+
`validationMiddleware(schema)` parses `request.json()` against the Zod schema and injects `context.data` with the typed result. On failure, returns 400 with the first field error.
|
|
126
|
+
|
|
127
|
+
## Common Mistakes
|
|
128
|
+
|
|
129
|
+
### HIGH Chaining authenticationMiddleware then authorizationMiddleware
|
|
130
|
+
|
|
131
|
+
Wrong:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
middleware: [authenticationMiddleware(), authorizationMiddleware("admin")],
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Correct:
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
middleware: [authorizationMiddleware("admin")],
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`authorizationMiddleware` already composes `authenticationMiddleware` internally. Chaining both duplicates JWT verification.
|
|
144
|
+
|
|
145
|
+
Source: maintainer interview
|
|
146
|
+
|
|
147
|
+
Cross-skill: See also skills/auth/SKILL.md § Common Mistakes
|
|
148
|
+
|
|
149
|
+
### HIGH Forgetting validationMiddleware for mutations
|
|
150
|
+
|
|
151
|
+
Wrong:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
POST: async ({ request, context }) => {
|
|
155
|
+
const data = await request.json();
|
|
156
|
+
await context.db.insert(todoTable).values(data).returning();
|
|
157
|
+
},
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Correct:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
POST: {
|
|
164
|
+
middleware: [validationMiddleware(TodoInsertSchema)],
|
|
165
|
+
handler: async ({ context }) => {
|
|
166
|
+
const [item] = await context.db
|
|
167
|
+
.insert(todoTable)
|
|
168
|
+
.values(context.data)
|
|
169
|
+
.returning();
|
|
170
|
+
return Response.json(item, { status: 201 });
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
POST/PUT handlers without `validationMiddleware` accept unvalidated JSON. The middleware parses `request.json()` with Zod and injects typed `context.data`.
|
|
176
|
+
|
|
177
|
+
Source: wcz-layout:src/middleware/validationMiddleware.ts
|
|
178
|
+
|
|
179
|
+
### HIGH Returning data without Response.json wrapper
|
|
180
|
+
|
|
181
|
+
Wrong:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
GET: async ({ context }) => {
|
|
185
|
+
return await context.db.select().from(todoTable);
|
|
186
|
+
},
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Correct:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
GET: async ({ context }) => {
|
|
193
|
+
const items = await context.db.select().from(todoTable);
|
|
194
|
+
return Response.json(items);
|
|
195
|
+
},
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
TanStack Start API route handlers must return `Response` objects. Returning raw objects doesn't set content-type headers correctly.
|
|
199
|
+
|
|
200
|
+
Source: consumer project example
|
|
201
|
+
|
|
202
|
+
### MEDIUM Using plain function for POST instead of middleware object
|
|
203
|
+
|
|
204
|
+
Wrong:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
POST: async ({ request, context }) => {
|
|
208
|
+
// no way to attach validationMiddleware
|
|
209
|
+
},
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Correct:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
POST: {
|
|
216
|
+
middleware: [validationMiddleware(TodoInsertSchema)],
|
|
217
|
+
handler: async ({ context }) => {
|
|
218
|
+
// context.data is typed from schema
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
When a handler needs per-method middleware (like validation), it must use the `{ middleware, handler }` object shape, not a plain async function.
|
|
224
|
+
|
|
225
|
+
Source: consumer project example
|
|
226
|
+
|
|
227
|
+
### MEDIUM Using invalid permission key string
|
|
228
|
+
|
|
229
|
+
`authorizationMiddleware(permissionKey)` is typed to `keyof typeof permissions` from your app's `permissions.ts`. Using a string not defined there causes a compile error.
|
|
230
|
+
|
|
231
|
+
Source: wcz-layout:src/middleware/authMiddleware.ts
|
|
232
|
+
|
|
233
|
+
### HIGH Tension: Simplicity vs. full-stack rigor
|
|
234
|
+
|
|
235
|
+
Quick prototypes may skip middleware to move fast. Always include `authorizationMiddleware` and `validationMiddleware` from the start — retrofitting security later is error-prone.
|
|
236
|
+
|
|
237
|
+
See also: skills/project-initialization/SKILL.md § Common Mistakes
|
|
238
|
+
|
|
239
|
+
### HIGH Tension: Client-side first vs. server-side data
|
|
240
|
+
|
|
241
|
+
`defaultSsr: false` means pages render on the client. Do not use SSR data fetching patterns (loader + dehydrate). API routes serve data to TanStack DB collections which manage client-side state.
|
|
242
|
+
|
|
243
|
+
See also: skills/tanstack-db-collections/SKILL.md § Core Patterns
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
See also:
|
|
248
|
+
|
|
249
|
+
- skills/tanstack-db-collections/SKILL.md — Collections consume these API routes via axios.
|
|
250
|
+
- skills/auth/SKILL.md — Every API route uses authorizationMiddleware.
|
|
251
|
+
- skills/database-schema/SKILL.md — Schemas feed into validationMiddleware.
|