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.

Files changed (34) hide show
  1. package/dist/{RouterListItemButton-CvfZk2zD.js → RouterListItemButton-DeaQB4ym.js} +1 -1
  2. package/dist/{RouterListItemButton-CvfZk2zD.js.map → RouterListItemButton-DeaQB4ym.js.map} +1 -1
  3. package/dist/components/core/Layout.d.ts +1 -1
  4. package/dist/components/core/navigation/NavigationList.d.ts +4 -4
  5. package/dist/components/core/navigation/NavigationListItem.d.ts +3 -3
  6. package/dist/components.js +2 -2
  7. package/dist/hooks.js +1 -1
  8. package/dist/index.js +617 -621
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/auth/msalClient.d.ts +8 -2
  11. package/dist/middleware.js +11 -11
  12. package/dist/models/Navigation.d.ts +23 -11
  13. package/dist/{queries-DzKY6YXz.js → queries-D-DV5lCw.js} +3 -3
  14. package/dist/{queries-DzKY6YXz.js.map → queries-D-DV5lCw.js.map} +1 -1
  15. package/dist/query.js +2 -2
  16. package/dist/{queryClient-uWNhcABg.js → queryClient-B__OEZ70.js} +1 -1
  17. package/dist/{queryClient-uWNhcABg.js.map → queryClient-B__OEZ70.js.map} +1 -1
  18. package/dist/{msalClient-BLrbVP5z.js → utils-C4oJ0tr5.js} +58 -47
  19. package/dist/utils-C4oJ0tr5.js.map +1 -0
  20. package/dist/utils.js +5 -5
  21. package/package.json +38 -6
  22. package/skills/api-routes/SKILL.md +251 -0
  23. package/skills/auth/SKILL.md +268 -0
  24. package/skills/data-grid/SKILL.md +229 -0
  25. package/skills/database-schema/SKILL.md +182 -0
  26. package/skills/dialogs-notifications/SKILL.md +241 -0
  27. package/skills/forms-validation/SKILL.md +331 -0
  28. package/skills/forms-validation/references/field-components.md +212 -0
  29. package/skills/layout-navigation/SKILL.md +259 -0
  30. package/skills/project-initialization/SKILL.md +181 -0
  31. package/skills/project-structure/SKILL.md +157 -0
  32. package/skills/tanstack-db-collections/SKILL.md +270 -0
  33. package/skills/ui-pages/SKILL.md +278 -0
  34. 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-DzKY6YXz.js";
2
- import { t as n } from "./queryClient-uWNhcABg.js";
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 };
@@ -4,4 +4,4 @@ var t = new e();
4
4
  //#endregion
5
5
  export { t };
6
6
 
7
- //# sourceMappingURL=queryClient-uWNhcABg.js.map
7
+ //# sourceMappingURL=queryClient-B__OEZ70.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"queryClient-uWNhcABg.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
+ {"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, PublicClientApplication as a } from "@azure/msal-browser";
4
- import { createEnv as o } from "@t3-oss/env-core";
5
- import { createClientOnlyFn as s, createIsomorphicFn as c } from "@tanstack/react-start";
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 l = o({
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
- }), u = o({
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
- }), d = "#00506E", f = "#64DC00", p = class {
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
- }, m = (e) => ({
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: l.VITE_APP_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: l.VITE_APP_TITLE
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
- }), h = (e) => async () => {
90
- let t = await C();
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
- }, g = (e) => {
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
- }, _ = (e) => e.replaceAll(/([a-z])([A-Z])/g, "$1-$2").replaceAll(/[\s_]+/g, "-").replaceAll(/[^a-zA-Z0-9-]/g, "").toLowerCase().replaceAll(/-+/g, "-").replaceAll(/(^-|-$)/g, ""), v = (e) => {
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
- }, y = (t) => ({
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 { p as a, y as c, m as d, _ as f, S as i, g as l, u as m, C as n, d as o, l as p, b as r, f as s, w as t, h as u };
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=msalClient-BLrbVP5z.js.map
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 { a as e, m as t, t as n, u as r } from "./msalClient-BLrbVP5z.js";
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: t.ENTRA_CLIENT_ID,
13
- clientSecret: t.ENTRA_CLIENT_SECRET,
14
- authority: `https://login.microsoftonline.com/${t.ENTRA_TENANT_ID}`
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 { e as Platform, s as createEnv, n as getAccessToken, p as getAppToken, f as getTokenOnBehalfOf, r as requirePermission, a as t, i as uuidv7 };
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.0",
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.5",
89
+ "i18next": "^25.10.9",
59
90
  "i18next-browser-languagedetector": "^8.2.1",
60
- "react-i18next": "^16.6.3"
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.41.0",
75
- "oxlint": "^1.56.0",
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.