@shipfox/client-projects 13.0.0 → 14.0.1

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.
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 27 files with swc (93.49ms)
2
+ Successfully compiled: 27 files with swc (105.52ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @shipfox/client-projects
2
2
 
3
+ ## 14.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 6aa6c7a: Migrates the integrations, triggers, runners, and projects client surfaces to semantic spacing roles and enables `no-raw-spacing` for them. Adds the `gap-x-*` and `gap-y-*` axis roles and the `-mx-inline` bleed role so grids with differing column and row rhythm, and rows that cancel their own `px-tight`, stay on the density-aware scale.
8
+ - Updated dependencies [88bf8e8]
9
+ - Updated dependencies [6aa6c7a]
10
+ - Updated dependencies [5c56ba6]
11
+ - @shipfox/react-ui@1.0.0
12
+ - @shipfox/client-shell@14.0.1
13
+ - @shipfox/client-auth@14.0.1
14
+ - @shipfox/client-integrations@14.0.1
15
+ - @shipfox/client-agent@14.0.1
16
+ - @shipfox/client-ui@14.0.1
17
+
18
+ ## 14.0.0
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [baa7594]
23
+ - Updated dependencies [f8a98cb]
24
+ - Updated dependencies [1267eb3]
25
+ - Updated dependencies [b2d4550]
26
+ - @shipfox/react-ui@0.5.0
27
+ - @shipfox/client-ui@14.0.0
28
+ - @shipfox/client-integrations@14.0.0
29
+ - @shipfox/client-agent@14.0.0
30
+ - @shipfox/client-auth@14.0.0
31
+ - @shipfox/client-shell@14.0.0
32
+
3
33
  ## 13.0.0
4
34
 
5
35
  ### Major Changes
@@ -24,7 +24,7 @@ export function ModelProviderReminderBanner({ workspaceId }) {
24
24
  },
25
25
  children: [
26
26
  /*#__PURE__*/ _jsxs(AlertContent, {
27
- className: "pr-28",
27
+ className: "pr-[28px]",
28
28
  children: [
29
29
  /*#__PURE__*/ _jsx(AlertTitle, {
30
30
  children: "Finish setting up a model provider"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/model-provider-reminder-banner.tsx"],"sourcesContent":["import {useModelProviderConfigsQuery} from '@shipfox/client-agent';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {createTypedBrowserStorage, sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {\n Alert,\n AlertActions,\n AlertClose,\n AlertContent,\n AlertDescription,\n AlertTitle,\n} from '@shipfox/react-ui/alert';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\n\nexport function ModelProviderReminderBanner({workspaceId}: {workspaceId: string}) {\n const workspace = useActiveWorkspace();\n const configsQuery = useModelProviderConfigsQuery(workspaceId);\n const [dismissed, setDismissed] = useState(() => isReminderDismissed(workspaceId));\n const configs = configsQuery.data?.configs;\n const unconfigured =\n Array.isArray(configs) && configs.length === 0 && configsQuery.data?.defaultProviderId === null;\n\n if (!unconfigured || dismissed) return null;\n\n return (\n <Alert\n variant=\"info\"\n animated={false}\n onOpenChange={(open) => {\n if (!open) {\n dismissReminder(workspaceId);\n setDismissed(true);\n }\n }}\n >\n <AlertContent className=\"pr-28\">\n <AlertTitle>Finish setting up a model provider</AlertTitle>\n <AlertDescription>\n Add workspace credentials, or keep using the instance default.\n </AlertDescription>\n <AlertActions>\n <Button asChild size=\"sm\" variant=\"secondary\">\n <Link to=\"/w/$workspaceSlug/settings/agents\" params={{workspaceSlug: workspace.slug}}>\n Agents\n </Link>\n </Button>\n </AlertActions>\n </AlertContent>\n <AlertClose />\n </Alert>\n );\n}\n\nfunction isReminderDismissed(workspaceId: string): boolean {\n return reminderStorage(workspaceId).read() === true;\n}\n\nfunction dismissReminder(workspaceId: string): void {\n reminderStorage(workspaceId).write(true);\n}\n\nfunction reminderStorage(workspaceId: string) {\n return createTypedBrowserStorage(\n sessionStorageOrUndefined,\n {\n key: 'shipfox.modelProviderReminder.dismissed',\n lifetime: 'session' as const,\n principalScope: 'workspace' as const,\n serialize: (dismissed: boolean) => JSON.stringify(dismissed),\n parse: (raw: string) => raw === 'true',\n },\n {workspaceId},\n );\n}\n"],"names":["useModelProviderConfigsQuery","useActiveWorkspace","createTypedBrowserStorage","sessionStorageOrUndefined","Alert","AlertActions","AlertClose","AlertContent","AlertDescription","AlertTitle","Button","Link","useState","ModelProviderReminderBanner","workspaceId","workspace","configsQuery","dismissed","setDismissed","isReminderDismissed","configs","data","unconfigured","Array","isArray","length","defaultProviderId","variant","animated","onOpenChange","open","dismissReminder","className","asChild","size","to","params","workspaceSlug","slug","reminderStorage","read","write","key","lifetime","principalScope","serialize","JSON","stringify","parse","raw"],"mappings":";AAAA,SAAQA,4BAA4B,QAAO,wBAAwB;AACnE,SAAQC,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,yBAAyB,EAAEC,yBAAyB,QAAO,qBAAqB;AACxF,SACEC,KAAK,EACLC,YAAY,EACZC,UAAU,EACVC,YAAY,EACZC,gBAAgB,EAChBC,UAAU,QACL,0BAA0B;AACjC,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAE/B,OAAO,SAASC,4BAA4B,EAACC,WAAW,EAAwB;IAC9E,MAAMC,YAAYd;IAClB,MAAMe,eAAehB,6BAA6Bc;IAClD,MAAM,CAACG,WAAWC,aAAa,GAAGN,SAAS,IAAMO,oBAAoBL;IACrE,MAAMM,UAAUJ,aAAaK,IAAI,EAAED;IACnC,MAAME,eACJC,MAAMC,OAAO,CAACJ,YAAYA,QAAQK,MAAM,KAAK,KAAKT,aAAaK,IAAI,EAAEK,sBAAsB;IAE7F,IAAI,CAACJ,gBAAgBL,WAAW,OAAO;IAEvC,qBACE,MAACb;QACCuB,SAAQ;QACRC,UAAU;QACVC,cAAc,CAACC;YACb,IAAI,CAACA,MAAM;gBACTC,gBAAgBjB;gBAChBI,aAAa;YACf;QACF;;0BAEA,MAACX;gBAAayB,WAAU;;kCACtB,KAACvB;kCAAW;;kCACZ,KAACD;kCAAiB;;kCAGlB,KAACH;kCACC,cAAA,KAACK;4BAAOuB,OAAO;4BAACC,MAAK;4BAAKP,SAAQ;sCAChC,cAAA,KAAChB;gCAAKwB,IAAG;gCAAoCC,QAAQ;oCAACC,eAAetB,UAAUuB,IAAI;gCAAA;0CAAG;;;;;;0BAM5F,KAAChC;;;AAGP;AAEA,SAASa,oBAAoBL,WAAmB;IAC9C,OAAOyB,gBAAgBzB,aAAa0B,IAAI,OAAO;AACjD;AAEA,SAAST,gBAAgBjB,WAAmB;IAC1CyB,gBAAgBzB,aAAa2B,KAAK,CAAC;AACrC;AAEA,SAASF,gBAAgBzB,WAAmB;IAC1C,OAAOZ,0BACLC,2BACA;QACEuC,KAAK;QACLC,UAAU;QACVC,gBAAgB;QAChBC,WAAW,CAAC5B,YAAuB6B,KAAKC,SAAS,CAAC9B;QAClD+B,OAAO,CAACC,MAAgBA,QAAQ;IAClC,GACA;QAACnC;IAAW;AAEhB"}
1
+ {"version":3,"sources":["../../src/components/model-provider-reminder-banner.tsx"],"sourcesContent":["import {useModelProviderConfigsQuery} from '@shipfox/client-agent';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {createTypedBrowserStorage, sessionStorageOrUndefined} from '@shipfox/client-ui';\nimport {\n Alert,\n AlertActions,\n AlertClose,\n AlertContent,\n AlertDescription,\n AlertTitle,\n} from '@shipfox/react-ui/alert';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\n\nexport function ModelProviderReminderBanner({workspaceId}: {workspaceId: string}) {\n const workspace = useActiveWorkspace();\n const configsQuery = useModelProviderConfigsQuery(workspaceId);\n const [dismissed, setDismissed] = useState(() => isReminderDismissed(workspaceId));\n const configs = configsQuery.data?.configs;\n const unconfigured =\n Array.isArray(configs) && configs.length === 0 && configsQuery.data?.defaultProviderId === null;\n\n if (!unconfigured || dismissed) return null;\n\n return (\n <Alert\n variant=\"info\"\n animated={false}\n onOpenChange={(open) => {\n if (!open) {\n dismissReminder(workspaceId);\n setDismissed(true);\n }\n }}\n >\n <AlertContent className=\"pr-[28px]\">\n <AlertTitle>Finish setting up a model provider</AlertTitle>\n <AlertDescription>\n Add workspace credentials, or keep using the instance default.\n </AlertDescription>\n <AlertActions>\n <Button asChild size=\"sm\" variant=\"secondary\">\n <Link to=\"/w/$workspaceSlug/settings/agents\" params={{workspaceSlug: workspace.slug}}>\n Agents\n </Link>\n </Button>\n </AlertActions>\n </AlertContent>\n <AlertClose />\n </Alert>\n );\n}\n\nfunction isReminderDismissed(workspaceId: string): boolean {\n return reminderStorage(workspaceId).read() === true;\n}\n\nfunction dismissReminder(workspaceId: string): void {\n reminderStorage(workspaceId).write(true);\n}\n\nfunction reminderStorage(workspaceId: string) {\n return createTypedBrowserStorage(\n sessionStorageOrUndefined,\n {\n key: 'shipfox.modelProviderReminder.dismissed',\n lifetime: 'session' as const,\n principalScope: 'workspace' as const,\n serialize: (dismissed: boolean) => JSON.stringify(dismissed),\n parse: (raw: string) => raw === 'true',\n },\n {workspaceId},\n );\n}\n"],"names":["useModelProviderConfigsQuery","useActiveWorkspace","createTypedBrowserStorage","sessionStorageOrUndefined","Alert","AlertActions","AlertClose","AlertContent","AlertDescription","AlertTitle","Button","Link","useState","ModelProviderReminderBanner","workspaceId","workspace","configsQuery","dismissed","setDismissed","isReminderDismissed","configs","data","unconfigured","Array","isArray","length","defaultProviderId","variant","animated","onOpenChange","open","dismissReminder","className","asChild","size","to","params","workspaceSlug","slug","reminderStorage","read","write","key","lifetime","principalScope","serialize","JSON","stringify","parse","raw"],"mappings":";AAAA,SAAQA,4BAA4B,QAAO,wBAAwB;AACnE,SAAQC,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,yBAAyB,EAAEC,yBAAyB,QAAO,qBAAqB;AACxF,SACEC,KAAK,EACLC,YAAY,EACZC,UAAU,EACVC,YAAY,EACZC,gBAAgB,EAChBC,UAAU,QACL,0BAA0B;AACjC,SAAQC,MAAM,QAAO,2BAA2B;AAChD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAE/B,OAAO,SAASC,4BAA4B,EAACC,WAAW,EAAwB;IAC9E,MAAMC,YAAYd;IAClB,MAAMe,eAAehB,6BAA6Bc;IAClD,MAAM,CAACG,WAAWC,aAAa,GAAGN,SAAS,IAAMO,oBAAoBL;IACrE,MAAMM,UAAUJ,aAAaK,IAAI,EAAED;IACnC,MAAME,eACJC,MAAMC,OAAO,CAACJ,YAAYA,QAAQK,MAAM,KAAK,KAAKT,aAAaK,IAAI,EAAEK,sBAAsB;IAE7F,IAAI,CAACJ,gBAAgBL,WAAW,OAAO;IAEvC,qBACE,MAACb;QACCuB,SAAQ;QACRC,UAAU;QACVC,cAAc,CAACC;YACb,IAAI,CAACA,MAAM;gBACTC,gBAAgBjB;gBAChBI,aAAa;YACf;QACF;;0BAEA,MAACX;gBAAayB,WAAU;;kCACtB,KAACvB;kCAAW;;kCACZ,KAACD;kCAAiB;;kCAGlB,KAACH;kCACC,cAAA,KAACK;4BAAOuB,OAAO;4BAACC,MAAK;4BAAKP,SAAQ;sCAChC,cAAA,KAAChB;gCAAKwB,IAAG;gCAAoCC,QAAQ;oCAACC,eAAetB,UAAUuB,IAAI;gCAAA;0CAAG;;;;;;0BAM5F,KAAChC;;;AAGP;AAEA,SAASa,oBAAoBL,WAAmB;IAC9C,OAAOyB,gBAAgBzB,aAAa0B,IAAI,OAAO;AACjD;AAEA,SAAST,gBAAgBjB,WAAmB;IAC1CyB,gBAAgBzB,aAAa2B,KAAK,CAAC;AACrC;AAEA,SAASF,gBAAgBzB,WAAmB;IAC1C,OAAOZ,0BACLC,2BACA;QACEuC,KAAK;QACLC,UAAU;QACVC,gBAAgB;QAChBC,WAAW,CAAC5B,YAAuB6B,KAAKC,SAAS,CAAC9B;QAClD+B,OAAO,CAACC,MAAgBA,QAAQ;IAClC,GACA;QAACnC;IAAW;AAEhB"}
@@ -8,7 +8,7 @@ export function ProjectCrumb({ workspaceId, workspaceSlug, projectId, projectSlu
8
8
  const [open, setOpen] = useState(false);
9
9
  if (projectSlug && projectName) {
10
10
  return /*#__PURE__*/ _jsxs("div", {
11
- className: "flex items-center",
11
+ className: "flex items-center gap-tight",
12
12
  children: [
13
13
  /*#__PURE__*/ _jsx(Link, {
14
14
  to: "/w/$workspaceSlug/p/$projectSlug",
@@ -17,7 +17,7 @@ export function ProjectCrumb({ workspaceId, workspaceSlug, projectId, projectSlu
17
17
  projectSlug
18
18
  },
19
19
  "aria-current": "page",
20
- className: "text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors max-w-[240px] truncate",
20
+ className: "text-md font-medium text-foreground-neutral-base p-tight rounded-6 hover:bg-background-components-hover transition-colors max-w-[240px] truncate",
21
21
  children: projectName
22
22
  }),
23
23
  /*#__PURE__*/ _jsxs(Popover, {
@@ -31,7 +31,7 @@ export function ProjectCrumb({ workspaceId, workspaceSlug, projectId, projectSlu
31
31
  "aria-label": "Switch project",
32
32
  "aria-haspopup": "listbox",
33
33
  "aria-expanded": open,
34
- className: "ml-2 grid place-items-center size-24 rounded-4 text-foreground-neutral-muted hover:bg-background-components-hover hover:text-foreground-neutral-base transition-colors",
34
+ className: "grid place-items-center size-24 rounded-4 text-foreground-neutral-muted hover:bg-background-components-hover hover:text-foreground-neutral-base transition-colors",
35
35
  children: /*#__PURE__*/ _jsx(Icon, {
36
36
  name: "arrowDownSLine",
37
37
  className: "size-16"
@@ -65,7 +65,7 @@ export function ProjectCrumb({ workspaceId, workspaceSlug, projectId, projectSlu
65
65
  "aria-label": "Switch project",
66
66
  "aria-haspopup": "listbox",
67
67
  "aria-expanded": open,
68
- className: "flex items-center gap-2 text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors",
68
+ className: "flex items-center gap-tight text-md font-medium text-foreground-neutral-base p-tight rounded-6 hover:bg-background-components-hover transition-colors",
69
69
  children: [
70
70
  /*#__PURE__*/ _jsx("span", {
71
71
  className: "max-w-[240px] truncate",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/project-crumb.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Popover, PopoverContent, PopoverTrigger} from '@shipfox/react-ui/popover';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {ProjectSwitcher} from './project-switcher.js';\n\nexport interface ProjectCrumbProps {\n workspaceId: string;\n workspaceSlug: string;\n projectId?: string | undefined;\n projectSlug?: string | undefined;\n projectName?: string | undefined;\n}\n\nexport function ProjectCrumb({\n workspaceId,\n workspaceSlug,\n projectId,\n projectSlug,\n projectName,\n}: ProjectCrumbProps) {\n const [open, setOpen] = useState(false);\n\n if (projectSlug && projectName) {\n return (\n <div className=\"flex items-center\">\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug\"\n params={{workspaceSlug, projectSlug}}\n aria-current=\"page\"\n className=\"text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors max-w-[240px] truncate\"\n >\n {projectName}\n </Link>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch project\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"ml-2 grid place-items-center size-24 rounded-4 text-foreground-neutral-muted hover:bg-background-components-hover hover:text-foreground-neutral-base transition-colors\"\n >\n <Icon name=\"arrowDownSLine\" className=\"size-16\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-0\" align=\"start\" sideOffset={8}>\n <ProjectSwitcher\n workspaceId={workspaceId}\n workspaceSlug={workspaceSlug}\n activeProjectId={projectId}\n onSelect={() => setOpen(false)}\n />\n </PopoverContent>\n </Popover>\n </div>\n );\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch project\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"flex items-center gap-2 text-md font-medium text-foreground-neutral-base px-6 py-4 rounded-6 hover:bg-background-components-hover transition-colors\"\n >\n <span className=\"max-w-[240px] truncate\">All projects</span>\n <Icon name=\"arrowDownSLine\" className=\"size-16 text-foreground-neutral-muted\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-0\" align=\"start\" sideOffset={8}>\n <ProjectSwitcher\n workspaceId={workspaceId}\n workspaceSlug={workspaceSlug}\n onSelect={() => setOpen(false)}\n />\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["Icon","Popover","PopoverContent","PopoverTrigger","Link","useState","ProjectSwitcher","ProjectCrumb","workspaceId","workspaceSlug","projectId","projectSlug","projectName","open","setOpen","div","className","to","params","aria-current","onOpenChange","asChild","button","type","aria-label","aria-haspopup","aria-expanded","name","align","sideOffset","activeProjectId","onSelect","span"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAC/B,SAAQC,eAAe,QAAO,wBAAwB;AAUtD,OAAO,SAASC,aAAa,EAC3BC,WAAW,EACXC,aAAa,EACbC,SAAS,EACTC,WAAW,EACXC,WAAW,EACO;IAClB,MAAM,CAACC,MAAMC,QAAQ,GAAGT,SAAS;IAEjC,IAAIM,eAAeC,aAAa;QAC9B,qBACE,MAACG;YAAIC,WAAU;;8BACb,KAACZ;oBACCa,IAAG;oBACHC,QAAQ;wBAACT;wBAAeE;oBAAW;oBACnCQ,gBAAa;oBACbH,WAAU;8BAETJ;;8BAEH,MAACX;oBAAQY,MAAMA;oBAAMO,cAAcN;;sCACjC,KAACX;4BAAekB,OAAO;sCACrB,cAAA,KAACC;gCACCC,MAAK;gCACLC,cAAW;gCACXC,iBAAc;gCACdC,iBAAeb;gCACfG,WAAU;0CAEV,cAAA,KAAChB;oCAAK2B,MAAK;oCAAiBX,WAAU;;;;sCAG1C,KAACd;4BAAec,WAAU;4BAAgBY,OAAM;4BAAQC,YAAY;sCAClE,cAAA,KAACvB;gCACCE,aAAaA;gCACbC,eAAeA;gCACfqB,iBAAiBpB;gCACjBqB,UAAU,IAAMjB,QAAQ;;;;;;;IAMpC;IAEA,qBACE,MAACb;QAAQY,MAAMA;QAAMO,cAAcN;;0BACjC,KAACX;gBAAekB,OAAO;0BACrB,cAAA,MAACC;oBACCC,MAAK;oBACLC,cAAW;oBACXC,iBAAc;oBACdC,iBAAeb;oBACfG,WAAU;;sCAEV,KAACgB;4BAAKhB,WAAU;sCAAyB;;sCACzC,KAAChB;4BAAK2B,MAAK;4BAAiBX,WAAU;;;;;0BAG1C,KAACd;gBAAec,WAAU;gBAAgBY,OAAM;gBAAQC,YAAY;0BAClE,cAAA,KAACvB;oBACCE,aAAaA;oBACbC,eAAeA;oBACfsB,UAAU,IAAMjB,QAAQ;;;;;AAKlC"}
1
+ {"version":3,"sources":["../../src/components/project-crumb.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {Popover, PopoverContent, PopoverTrigger} from '@shipfox/react-ui/popover';\nimport {Link} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {ProjectSwitcher} from './project-switcher.js';\n\nexport interface ProjectCrumbProps {\n workspaceId: string;\n workspaceSlug: string;\n projectId?: string | undefined;\n projectSlug?: string | undefined;\n projectName?: string | undefined;\n}\n\nexport function ProjectCrumb({\n workspaceId,\n workspaceSlug,\n projectId,\n projectSlug,\n projectName,\n}: ProjectCrumbProps) {\n const [open, setOpen] = useState(false);\n\n if (projectSlug && projectName) {\n return (\n <div className=\"flex items-center gap-tight\">\n <Link\n to=\"/w/$workspaceSlug/p/$projectSlug\"\n params={{workspaceSlug, projectSlug}}\n aria-current=\"page\"\n className=\"text-md font-medium text-foreground-neutral-base p-tight rounded-6 hover:bg-background-components-hover transition-colors max-w-[240px] truncate\"\n >\n {projectName}\n </Link>\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch project\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"grid place-items-center size-24 rounded-4 text-foreground-neutral-muted hover:bg-background-components-hover hover:text-foreground-neutral-base transition-colors\"\n >\n <Icon name=\"arrowDownSLine\" className=\"size-16\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-0\" align=\"start\" sideOffset={8}>\n <ProjectSwitcher\n workspaceId={workspaceId}\n workspaceSlug={workspaceSlug}\n activeProjectId={projectId}\n onSelect={() => setOpen(false)}\n />\n </PopoverContent>\n </Popover>\n </div>\n );\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Switch project\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n className=\"flex items-center gap-tight text-md font-medium text-foreground-neutral-base p-tight rounded-6 hover:bg-background-components-hover transition-colors\"\n >\n <span className=\"max-w-[240px] truncate\">All projects</span>\n <Icon name=\"arrowDownSLine\" className=\"size-16 text-foreground-neutral-muted\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-[320px] p-0\" align=\"start\" sideOffset={8}>\n <ProjectSwitcher\n workspaceId={workspaceId}\n workspaceSlug={workspaceSlug}\n onSelect={() => setOpen(false)}\n />\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["Icon","Popover","PopoverContent","PopoverTrigger","Link","useState","ProjectSwitcher","ProjectCrumb","workspaceId","workspaceSlug","projectId","projectSlug","projectName","open","setOpen","div","className","to","params","aria-current","onOpenChange","asChild","button","type","aria-label","aria-haspopup","aria-expanded","name","align","sideOffset","activeProjectId","onSelect","span"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,QAAQ,QAAO,QAAQ;AAC/B,SAAQC,eAAe,QAAO,wBAAwB;AAUtD,OAAO,SAASC,aAAa,EAC3BC,WAAW,EACXC,aAAa,EACbC,SAAS,EACTC,WAAW,EACXC,WAAW,EACO;IAClB,MAAM,CAACC,MAAMC,QAAQ,GAAGT,SAAS;IAEjC,IAAIM,eAAeC,aAAa;QAC9B,qBACE,MAACG;YAAIC,WAAU;;8BACb,KAACZ;oBACCa,IAAG;oBACHC,QAAQ;wBAACT;wBAAeE;oBAAW;oBACnCQ,gBAAa;oBACbH,WAAU;8BAETJ;;8BAEH,MAACX;oBAAQY,MAAMA;oBAAMO,cAAcN;;sCACjC,KAACX;4BAAekB,OAAO;sCACrB,cAAA,KAACC;gCACCC,MAAK;gCACLC,cAAW;gCACXC,iBAAc;gCACdC,iBAAeb;gCACfG,WAAU;0CAEV,cAAA,KAAChB;oCAAK2B,MAAK;oCAAiBX,WAAU;;;;sCAG1C,KAACd;4BAAec,WAAU;4BAAgBY,OAAM;4BAAQC,YAAY;sCAClE,cAAA,KAACvB;gCACCE,aAAaA;gCACbC,eAAeA;gCACfqB,iBAAiBpB;gCACjBqB,UAAU,IAAMjB,QAAQ;;;;;;;IAMpC;IAEA,qBACE,MAACb;QAAQY,MAAMA;QAAMO,cAAcN;;0BACjC,KAACX;gBAAekB,OAAO;0BACrB,cAAA,MAACC;oBACCC,MAAK;oBACLC,cAAW;oBACXC,iBAAc;oBACdC,iBAAeb;oBACfG,WAAU;;sCAEV,KAACgB;4BAAKhB,WAAU;sCAAyB;;sCACzC,KAAChB;4BAAK2B,MAAK;4BAAiBX,WAAU;;;;;0BAG1C,KAACd;gBAAec,WAAU;gBAAgBY,OAAM;gBAAQC,YAAY;0BAClE,cAAA,KAACvB;oBACCE,aAAaA;oBACbC,eAAeA;oBACfsB,UAAU,IAAMjB,QAAQ;;;;;AAKlC"}
@@ -92,7 +92,7 @@ export function ProjectSwitcher({ workspaceId, workspaceSlug, activeProjectId, o
92
92
  children: [
93
93
  /*#__PURE__*/ _jsx(Icon, {
94
94
  name: "check",
95
- className: `size-16 mr-8 ${activeProjectId ? 'opacity-0' : 'opacity-100'}`
95
+ className: `size-16 ${activeProjectId ? 'opacity-0' : 'opacity-100'}`
96
96
  }),
97
97
  "All projects"
98
98
  ]
@@ -109,7 +109,7 @@ export function ProjectSwitcher({ workspaceId, workspaceSlug, activeProjectId, o
109
109
  children: [
110
110
  /*#__PURE__*/ _jsx(Icon, {
111
111
  name: "check",
112
- className: `size-16 mr-8 ${activeProjectId === project.id ? 'opacity-100' : 'opacity-0'}`
112
+ className: `size-16 ${activeProjectId === project.id ? 'opacity-100' : 'opacity-0'}`
113
113
  }),
114
114
  project.name
115
115
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/project-switcher.tsx"],"sourcesContent":["import {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@shipfox/react-ui/command';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {useNavigate} from '@tanstack/react-router';\nimport {type KeyboardEvent, useEffect, useState} from 'react';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport interface ProjectSwitcherProps {\n workspaceId: string;\n workspaceSlug: string;\n activeProjectId?: string | undefined;\n onSelect?: () => void;\n}\n\nexport function ProjectSwitcher({\n workspaceId,\n workspaceSlug,\n activeProjectId,\n onSelect,\n}: ProjectSwitcherProps) {\n const navigate = useNavigate();\n const [searchValue, setSearchValue] = useState('');\n const query = useProjectsInfiniteQuery(workspaceId);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // Eagerly fetch additional pages once the popover opens; the switcher should\n // show the full list, not just the first page.\n useEffect(() => {\n if (query.hasNextPage && !query.isFetchingNextPage) {\n query.fetchNextPage();\n }\n }, [query.hasNextPage, query.isFetchingNextPage, query.fetchNextPage]);\n\n const handleSelect = (projectId: string) => {\n const project = projects.find((candidate) => candidate.id === projectId);\n if (!project) return;\n navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {\n workspaceSlug,\n projectSlug: project.slug,\n },\n });\n onSelect?.();\n };\n\n const handleSelectAll = () => {\n navigate({to: '/w/$workspaceSlug', params: {workspaceSlug}});\n onSelect?.();\n };\n\n const handleCreate = () => {\n navigate({to: '/w/$workspaceSlug/projects/new', params: {workspaceSlug}});\n onSelect?.();\n };\n\n const normalizedSearch = searchValue.trim().toLowerCase();\n const hasSearchMatch =\n normalizedSearch.length === 0 ||\n 'all projects'.includes(normalizedSearch) ||\n projects.some((project) =>\n [project.id, project.name].some((value) => value.toLowerCase().includes(normalizedSearch)),\n );\n const shouldCreateFromEmptySearch = !query.isLoading && !query.isError && !hasSearchMatch;\n\n const handleCommandKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Enter' || !shouldCreateFromEmptySearch) {\n return;\n }\n\n event.preventDefault();\n handleCreate();\n };\n\n return (\n <Command onKeyDown={handleCommandKeyDown}>\n <CommandInput\n placeholder=\"Search projects...\"\n value={searchValue}\n onValueChange={setSearchValue}\n />\n <CommandList>\n {query.isError ? (\n <CommandEmpty>Couldn&apos;t load projects.</CommandEmpty>\n ) : query.isLoading ? (\n <CommandEmpty>Loading projects...</CommandEmpty>\n ) : projects.length === 0 ? (\n <CommandEmpty>No projects yet.</CommandEmpty>\n ) : (\n <CommandEmpty>No projects found.</CommandEmpty>\n )}\n <CommandGroup>\n <CommandItem value=\"__all\" keywords={['all projects']} onSelect={handleSelectAll}>\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${activeProjectId ? 'opacity-0' : 'opacity-100'}`}\n />\n All projects\n </CommandItem>\n </CommandGroup>\n {projects.length > 0 ? (\n <CommandGroup heading=\"Projects\">\n {projects.map((project) => (\n <CommandItem\n key={project.id}\n value={project.id}\n keywords={[project.name]}\n onSelect={() => handleSelect(project.id)}\n >\n <Icon\n name=\"check\"\n className={`size-16 mr-8 ${\n activeProjectId === project.id ? 'opacity-100' : 'opacity-0'\n }`}\n />\n {project.name}\n </CommandItem>\n ))}\n </CommandGroup>\n ) : null}\n </CommandList>\n <CommandSeparator />\n <CommandGroup forceMount>\n <CommandItem value=\"__create\" onSelect={handleCreate} forceMount>\n <Icon name=\"addLine\" className=\"size-16\" />\n Create project\n </CommandItem>\n </CommandGroup>\n </Command>\n );\n}\n"],"names":["Command","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","Icon","useNavigate","useEffect","useState","useProjectsInfiniteQuery","ProjectSwitcher","workspaceId","workspaceSlug","activeProjectId","onSelect","navigate","searchValue","setSearchValue","query","projects","data","pages","flatMap","page","hasNextPage","isFetchingNextPage","fetchNextPage","handleSelect","projectId","project","find","candidate","id","to","params","projectSlug","slug","handleSelectAll","handleCreate","normalizedSearch","trim","toLowerCase","hasSearchMatch","length","includes","some","name","value","shouldCreateFromEmptySearch","isLoading","isError","handleCommandKeyDown","event","key","preventDefault","onKeyDown","placeholder","onValueChange","keywords","className","heading","map","forceMount"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/components/project-switcher.tsx"],"sourcesContent":["import {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from '@shipfox/react-ui/command';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {useNavigate} from '@tanstack/react-router';\nimport {type KeyboardEvent, useEffect, useState} from 'react';\nimport {useProjectsInfiniteQuery} from '#hooks/api/projects.js';\n\nexport interface ProjectSwitcherProps {\n workspaceId: string;\n workspaceSlug: string;\n activeProjectId?: string | undefined;\n onSelect?: () => void;\n}\n\nexport function ProjectSwitcher({\n workspaceId,\n workspaceSlug,\n activeProjectId,\n onSelect,\n}: ProjectSwitcherProps) {\n const navigate = useNavigate();\n const [searchValue, setSearchValue] = useState('');\n const query = useProjectsInfiniteQuery(workspaceId);\n const projects = query.data?.pages.flatMap((page) => page.projects) ?? [];\n\n // Eagerly fetch additional pages once the popover opens; the switcher should\n // show the full list, not just the first page.\n useEffect(() => {\n if (query.hasNextPage && !query.isFetchingNextPage) {\n query.fetchNextPage();\n }\n }, [query.hasNextPage, query.isFetchingNextPage, query.fetchNextPage]);\n\n const handleSelect = (projectId: string) => {\n const project = projects.find((candidate) => candidate.id === projectId);\n if (!project) return;\n navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {\n workspaceSlug,\n projectSlug: project.slug,\n },\n });\n onSelect?.();\n };\n\n const handleSelectAll = () => {\n navigate({to: '/w/$workspaceSlug', params: {workspaceSlug}});\n onSelect?.();\n };\n\n const handleCreate = () => {\n navigate({to: '/w/$workspaceSlug/projects/new', params: {workspaceSlug}});\n onSelect?.();\n };\n\n const normalizedSearch = searchValue.trim().toLowerCase();\n const hasSearchMatch =\n normalizedSearch.length === 0 ||\n 'all projects'.includes(normalizedSearch) ||\n projects.some((project) =>\n [project.id, project.name].some((value) => value.toLowerCase().includes(normalizedSearch)),\n );\n const shouldCreateFromEmptySearch = !query.isLoading && !query.isError && !hasSearchMatch;\n\n const handleCommandKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Enter' || !shouldCreateFromEmptySearch) {\n return;\n }\n\n event.preventDefault();\n handleCreate();\n };\n\n return (\n <Command onKeyDown={handleCommandKeyDown}>\n <CommandInput\n placeholder=\"Search projects...\"\n value={searchValue}\n onValueChange={setSearchValue}\n />\n <CommandList>\n {query.isError ? (\n <CommandEmpty>Couldn&apos;t load projects.</CommandEmpty>\n ) : query.isLoading ? (\n <CommandEmpty>Loading projects...</CommandEmpty>\n ) : projects.length === 0 ? (\n <CommandEmpty>No projects yet.</CommandEmpty>\n ) : (\n <CommandEmpty>No projects found.</CommandEmpty>\n )}\n <CommandGroup>\n <CommandItem value=\"__all\" keywords={['all projects']} onSelect={handleSelectAll}>\n <Icon\n name=\"check\"\n className={`size-16 ${activeProjectId ? 'opacity-0' : 'opacity-100'}`}\n />\n All projects\n </CommandItem>\n </CommandGroup>\n {projects.length > 0 ? (\n <CommandGroup heading=\"Projects\">\n {projects.map((project) => (\n <CommandItem\n key={project.id}\n value={project.id}\n keywords={[project.name]}\n onSelect={() => handleSelect(project.id)}\n >\n <Icon\n name=\"check\"\n className={`size-16 ${\n activeProjectId === project.id ? 'opacity-100' : 'opacity-0'\n }`}\n />\n {project.name}\n </CommandItem>\n ))}\n </CommandGroup>\n ) : null}\n </CommandList>\n <CommandSeparator />\n <CommandGroup forceMount>\n <CommandItem value=\"__create\" onSelect={handleCreate} forceMount>\n <Icon name=\"addLine\" className=\"size-16\" />\n Create project\n </CommandItem>\n </CommandGroup>\n </Command>\n );\n}\n"],"names":["Command","CommandEmpty","CommandGroup","CommandInput","CommandItem","CommandList","CommandSeparator","Icon","useNavigate","useEffect","useState","useProjectsInfiniteQuery","ProjectSwitcher","workspaceId","workspaceSlug","activeProjectId","onSelect","navigate","searchValue","setSearchValue","query","projects","data","pages","flatMap","page","hasNextPage","isFetchingNextPage","fetchNextPage","handleSelect","projectId","project","find","candidate","id","to","params","projectSlug","slug","handleSelectAll","handleCreate","normalizedSearch","trim","toLowerCase","hasSearchMatch","length","includes","some","name","value","shouldCreateFromEmptySearch","isLoading","isError","handleCommandKeyDown","event","key","preventDefault","onKeyDown","placeholder","onValueChange","keywords","className","heading","map","forceMount"],"mappings":"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"}
@@ -16,18 +16,18 @@ import { Code, Text } from '@shipfox/react-ui/typography';
16
16
  const connectionsQuery = useSourceConnectionsQuery(workspace.id);
17
17
  const connection = connectionsQuery.data?.find((c)=>c.id === connectionId);
18
18
  return /*#__PURE__*/ _jsxs("section", {
19
- className: "flex flex-col gap-8 rounded-8 border border-border-neutral-base bg-background-neutral-base px-14 py-10 sm:flex-row sm:items-center sm:justify-between",
19
+ className: "flex flex-col gap-inline rounded-8 border border-border-neutral-base bg-background-neutral-base px-row py-row sm:flex-row sm:items-center sm:justify-between",
20
20
  "aria-label": "Project source",
21
21
  children: [
22
22
  /*#__PURE__*/ _jsxs("div", {
23
- className: "flex min-w-0 items-center gap-10",
23
+ className: "flex min-w-0 items-center gap-inline",
24
24
  children: [
25
25
  /*#__PURE__*/ _jsx(Icon, {
26
26
  name: providerIconName(connection?.provider),
27
27
  className: "size-20 shrink-0"
28
28
  }),
29
29
  /*#__PURE__*/ _jsxs("div", {
30
- className: "flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center sm:gap-10",
30
+ className: "flex min-w-0 flex-col gap-tight sm:flex-row sm:items-center sm:gap-inline",
31
31
  children: [
32
32
  connectionsQuery.isPending ? /*#__PURE__*/ _jsx(Skeleton, {
33
33
  className: "h-16 w-160"
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/source-strip.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {useSourceConnectionsQuery} from '@shipfox/client-integrations';\nimport {StatusBadge} from '@shipfox/react-ui/badge';\nimport {Icon, type IconName} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport type {DefinitionSyncSummary} from '#core/definition.js';\n\n/**\n * We cannot cheaply resolve `external_repository_id` → `owner/repo`\n * (no by-id endpoint; `listRepositories` is paginated by connection).\n * The strip surfaces the raw id as a `<Code>` chip with a tooltip carrying the\n * full string.\n */\nexport function SourceStrip({\n connectionId,\n externalRepositoryId,\n sync,\n isPending,\n}: {\n connectionId: string;\n externalRepositoryId: string;\n sync: DefinitionSyncSummary | null | undefined;\n isPending: boolean;\n}) {\n const workspace = useActiveWorkspace();\n const connectionsQuery = useSourceConnectionsQuery(workspace.id);\n const connection = connectionsQuery.data?.find((c) => c.id === connectionId);\n\n return (\n <section\n className=\"flex flex-col gap-8 rounded-8 border border-border-neutral-base bg-background-neutral-base px-14 py-10 sm:flex-row sm:items-center sm:justify-between\"\n aria-label=\"Project source\"\n >\n <div className=\"flex min-w-0 items-center gap-10\">\n <Icon name={providerIconName(connection?.provider)} className=\"size-20 shrink-0\" />\n <div className=\"flex min-w-0 flex-col gap-2 sm:flex-row sm:items-center sm:gap-10\">\n {connectionsQuery.isPending ? (\n <Skeleton className=\"h-16 w-160\" />\n ) : (\n <Text size=\"sm\" bold className=\"truncate\">\n {connection?.displayName ?? 'Connected source'}\n </Text>\n )}\n <Tooltip>\n <TooltipTrigger asChild>\n <Code variant=\"label\" className=\"truncate text-foreground-neutral-muted\">\n {externalRepositoryId}\n </Code>\n </TooltipTrigger>\n <TooltipContent>{externalRepositoryId}</TooltipContent>\n </Tooltip>\n </div>\n </div>\n <div className=\"shrink-0\">\n <SyncBadge sync={sync} isPending={isPending} />\n </div>\n </section>\n );\n}\n\nfunction providerIconName(provider: string | undefined): IconName {\n if (provider === 'github') return 'github' as IconName;\n return 'componentLine' as IconName;\n}\n\nfunction SyncBadge({\n sync,\n isPending,\n}: {\n sync: DefinitionSyncSummary | null | undefined;\n isPending: boolean;\n}) {\n if (isPending) return <StatusBadge variant=\"neutral\">Loading</StatusBadge>;\n if (sync === undefined) return <StatusBadge variant=\"neutral\">Unavailable</StatusBadge>;\n if (sync === null) return <StatusBadge variant=\"neutral\">No sync</StatusBadge>;\n\n const variantByStatus = {\n pending: 'neutral',\n syncing: 'info',\n succeeded: 'success',\n failed: 'error',\n } as const;\n\n return (\n <StatusBadge variant={variantByStatus[sync.status as keyof typeof variantByStatus]}>\n {sync.status}\n </StatusBadge>\n );\n}\n"],"names":["useActiveWorkspace","useSourceConnectionsQuery","StatusBadge","Icon","Skeleton","Tooltip","TooltipContent","TooltipTrigger","Code","Text","SourceStrip","connectionId","externalRepositoryId","sync","isPending","workspace","connectionsQuery","id","connection","data","find","c","section","className","aria-label","div","name","providerIconName","provider","size","bold","displayName","asChild","variant","SyncBadge","undefined","variantByStatus","pending","syncing","succeeded","failed","status"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,yBAAyB,QAAO,+BAA+B;AACvE,SAAQC,WAAW,QAAO,0BAA0B;AACpD,SAAQC,IAAI,QAAsB,yBAAyB;AAC3D,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AAGxD;;;;;CAKC,GACD,OAAO,SAASC,YAAY,EAC1BC,YAAY,EACZC,oBAAoB,EACpBC,IAAI,EACJC,SAAS,EAMV;IACC,MAAMC,YAAYf;IAClB,MAAMgB,mBAAmBf,0BAA0Bc,UAAUE,EAAE;IAC/D,MAAMC,aAAaF,iBAAiBG,IAAI,EAAEC,KAAK,CAACC,IAAMA,EAAEJ,EAAE,KAAKN;IAE/D,qBACE,MAACW;QACCC,WAAU;QACVC,cAAW;;0BAEX,MAACC;gBAAIF,WAAU;;kCACb,KAACpB;wBAAKuB,MAAMC,iBAAiBT,YAAYU;wBAAWL,WAAU;;kCAC9D,MAACE;wBAAIF,WAAU;;4BACZP,iBAAiBF,SAAS,iBACzB,KAACV;gCAASmB,WAAU;+CAEpB,KAACd;gCAAKoB,MAAK;gCAAKC,IAAI;gCAACP,WAAU;0CAC5BL,YAAYa,eAAe;;0CAGhC,MAAC1B;;kDACC,KAACE;wCAAeyB,OAAO;kDACrB,cAAA,KAACxB;4CAAKyB,SAAQ;4CAAQV,WAAU;sDAC7BX;;;kDAGL,KAACN;kDAAgBM;;;;;;;;0BAIvB,KAACa;gBAAIF,WAAU;0BACb,cAAA,KAACW;oBAAUrB,MAAMA;oBAAMC,WAAWA;;;;;AAI1C;AAEA,SAASa,iBAAiBC,QAA4B;IACpD,IAAIA,aAAa,UAAU,OAAO;IAClC,OAAO;AACT;AAEA,SAASM,UAAU,EACjBrB,IAAI,EACJC,SAAS,EAIV;IACC,IAAIA,WAAW,qBAAO,KAACZ;QAAY+B,SAAQ;kBAAU;;IACrD,IAAIpB,SAASsB,WAAW,qBAAO,KAACjC;QAAY+B,SAAQ;kBAAU;;IAC9D,IAAIpB,SAAS,MAAM,qBAAO,KAACX;QAAY+B,SAAQ;kBAAU;;IAEzD,MAAMG,kBAAkB;QACtBC,SAAS;QACTC,SAAS;QACTC,WAAW;QACXC,QAAQ;IACV;IAEA,qBACE,KAACtC;QAAY+B,SAASG,eAAe,CAACvB,KAAK4B,MAAM,CAAiC;kBAC/E5B,KAAK4B,MAAM;;AAGlB"}
1
+ {"version":3,"sources":["../../src/components/source-strip.tsx"],"sourcesContent":["import {useActiveWorkspace} from '@shipfox/client-auth';\nimport {useSourceConnectionsQuery} from '@shipfox/client-integrations';\nimport {StatusBadge} from '@shipfox/react-ui/badge';\nimport {Icon, type IconName} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Code, Text} from '@shipfox/react-ui/typography';\nimport type {DefinitionSyncSummary} from '#core/definition.js';\n\n/**\n * We cannot cheaply resolve `external_repository_id` → `owner/repo`\n * (no by-id endpoint; `listRepositories` is paginated by connection).\n * The strip surfaces the raw id as a `<Code>` chip with a tooltip carrying the\n * full string.\n */\nexport function SourceStrip({\n connectionId,\n externalRepositoryId,\n sync,\n isPending,\n}: {\n connectionId: string;\n externalRepositoryId: string;\n sync: DefinitionSyncSummary | null | undefined;\n isPending: boolean;\n}) {\n const workspace = useActiveWorkspace();\n const connectionsQuery = useSourceConnectionsQuery(workspace.id);\n const connection = connectionsQuery.data?.find((c) => c.id === connectionId);\n\n return (\n <section\n className=\"flex flex-col gap-inline rounded-8 border border-border-neutral-base bg-background-neutral-base px-row py-row sm:flex-row sm:items-center sm:justify-between\"\n aria-label=\"Project source\"\n >\n <div className=\"flex min-w-0 items-center gap-inline\">\n <Icon name={providerIconName(connection?.provider)} className=\"size-20 shrink-0\" />\n <div className=\"flex min-w-0 flex-col gap-tight sm:flex-row sm:items-center sm:gap-inline\">\n {connectionsQuery.isPending ? (\n <Skeleton className=\"h-16 w-160\" />\n ) : (\n <Text size=\"sm\" bold className=\"truncate\">\n {connection?.displayName ?? 'Connected source'}\n </Text>\n )}\n <Tooltip>\n <TooltipTrigger asChild>\n <Code variant=\"label\" className=\"truncate text-foreground-neutral-muted\">\n {externalRepositoryId}\n </Code>\n </TooltipTrigger>\n <TooltipContent>{externalRepositoryId}</TooltipContent>\n </Tooltip>\n </div>\n </div>\n <div className=\"shrink-0\">\n <SyncBadge sync={sync} isPending={isPending} />\n </div>\n </section>\n );\n}\n\nfunction providerIconName(provider: string | undefined): IconName {\n if (provider === 'github') return 'github' as IconName;\n return 'componentLine' as IconName;\n}\n\nfunction SyncBadge({\n sync,\n isPending,\n}: {\n sync: DefinitionSyncSummary | null | undefined;\n isPending: boolean;\n}) {\n if (isPending) return <StatusBadge variant=\"neutral\">Loading</StatusBadge>;\n if (sync === undefined) return <StatusBadge variant=\"neutral\">Unavailable</StatusBadge>;\n if (sync === null) return <StatusBadge variant=\"neutral\">No sync</StatusBadge>;\n\n const variantByStatus = {\n pending: 'neutral',\n syncing: 'info',\n succeeded: 'success',\n failed: 'error',\n } as const;\n\n return (\n <StatusBadge variant={variantByStatus[sync.status as keyof typeof variantByStatus]}>\n {sync.status}\n </StatusBadge>\n );\n}\n"],"names":["useActiveWorkspace","useSourceConnectionsQuery","StatusBadge","Icon","Skeleton","Tooltip","TooltipContent","TooltipTrigger","Code","Text","SourceStrip","connectionId","externalRepositoryId","sync","isPending","workspace","connectionsQuery","id","connection","data","find","c","section","className","aria-label","div","name","providerIconName","provider","size","bold","displayName","asChild","variant","SyncBadge","undefined","variantByStatus","pending","syncing","succeeded","failed","status"],"mappings":";AAAA,SAAQA,kBAAkB,QAAO,uBAAuB;AACxD,SAAQC,yBAAyB,QAAO,+BAA+B;AACvE,SAAQC,WAAW,QAAO,0BAA0B;AACpD,SAAQC,IAAI,QAAsB,yBAAyB;AAC3D,SAAQC,QAAQ,QAAO,6BAA6B;AACpD,SAAQC,OAAO,EAAEC,cAAc,EAAEC,cAAc,QAAO,4BAA4B;AAClF,SAAQC,IAAI,EAAEC,IAAI,QAAO,+BAA+B;AAGxD;;;;;CAKC,GACD,OAAO,SAASC,YAAY,EAC1BC,YAAY,EACZC,oBAAoB,EACpBC,IAAI,EACJC,SAAS,EAMV;IACC,MAAMC,YAAYf;IAClB,MAAMgB,mBAAmBf,0BAA0Bc,UAAUE,EAAE;IAC/D,MAAMC,aAAaF,iBAAiBG,IAAI,EAAEC,KAAK,CAACC,IAAMA,EAAEJ,EAAE,KAAKN;IAE/D,qBACE,MAACW;QACCC,WAAU;QACVC,cAAW;;0BAEX,MAACC;gBAAIF,WAAU;;kCACb,KAACpB;wBAAKuB,MAAMC,iBAAiBT,YAAYU;wBAAWL,WAAU;;kCAC9D,MAACE;wBAAIF,WAAU;;4BACZP,iBAAiBF,SAAS,iBACzB,KAACV;gCAASmB,WAAU;+CAEpB,KAACd;gCAAKoB,MAAK;gCAAKC,IAAI;gCAACP,WAAU;0CAC5BL,YAAYa,eAAe;;0CAGhC,MAAC1B;;kDACC,KAACE;wCAAeyB,OAAO;kDACrB,cAAA,KAACxB;4CAAKyB,SAAQ;4CAAQV,WAAU;sDAC7BX;;;kDAGL,KAACN;kDAAgBM;;;;;;;;0BAIvB,KAACa;gBAAIF,WAAU;0BACb,cAAA,KAACW;oBAAUrB,MAAMA;oBAAMC,WAAWA;;;;;AAI1C;AAEA,SAASa,iBAAiBC,QAA4B;IACpD,IAAIA,aAAa,UAAU,OAAO;IAClC,OAAO;AACT;AAEA,SAASM,UAAU,EACjBrB,IAAI,EACJC,SAAS,EAIV;IACC,IAAIA,WAAW,qBAAO,KAACZ;QAAY+B,SAAQ;kBAAU;;IACrD,IAAIpB,SAASsB,WAAW,qBAAO,KAACjC;QAAY+B,SAAQ;kBAAU;;IAC9D,IAAIpB,SAAS,MAAM,qBAAO,KAACX;QAAY+B,SAAQ;kBAAU;;IAEzD,MAAMG,kBAAkB;QACtBC,SAAS;QACTC,SAAS;QACTC,WAAW;QACXC,QAAQ;IACV;IAEA,qBACE,KAACtC;QAAY+B,SAASG,eAAe,CAACvB,KAAK4B,MAAM,CAAiC;kBAC/E5B,KAAK4B,MAAM;;AAGlB"}
@@ -183,10 +183,10 @@ export function CreateProjectPage() {
183
183
  const showRepoPicker = Boolean(selectedConnection);
184
184
  const filteredEmptyMessage = trimmedFilter ? `No repositories matching "${repoFilter.trim()}".` : 'No repositories visible to this connection.';
185
185
  return /*#__PURE__*/ _jsxs("div", {
186
- className: "flex w-full flex-col gap-24",
186
+ className: "flex w-full flex-col gap-section",
187
187
  children: [
188
188
  /*#__PURE__*/ _jsxs("header", {
189
- className: "flex flex-col gap-6",
189
+ className: "flex flex-col gap-inline",
190
190
  children: [
191
191
  /*#__PURE__*/ _jsx(Header, {
192
192
  id: "create-project-title",
@@ -207,7 +207,7 @@ export function CreateProjectPage() {
207
207
  role: "alert",
208
208
  type: "error",
209
209
  children: /*#__PURE__*/ _jsxs("div", {
210
- className: "flex w-full flex-wrap items-center justify-between gap-12",
210
+ className: "flex w-full flex-wrap items-center justify-between gap-cluster",
211
211
  children: [
212
212
  /*#__PURE__*/ _jsx(Text, {
213
213
  size: "sm",
@@ -232,20 +232,20 @@ export function CreateProjectPage() {
232
232
  },
233
233
  noValidate: true,
234
234
  "aria-labelledby": "create-project-title",
235
- className: "grid items-start gap-32 lg:grid-cols-[minmax(0,1fr)_340px]",
235
+ className: "grid items-start gap-region lg:grid-cols-[minmax(0,1fr)_340px]",
236
236
  children: [
237
237
  /*#__PURE__*/ _jsxs("div", {
238
- className: "flex min-w-0 flex-col gap-32",
238
+ className: "flex min-w-0 flex-col gap-region",
239
239
  children: [
240
240
  /*#__PURE__*/ _jsxs("section", {
241
- className: "flex flex-col gap-16",
241
+ className: "flex flex-col gap-group",
242
242
  "aria-label": "Source integration",
243
243
  children: [
244
244
  /*#__PURE__*/ _jsxs("div", {
245
- className: "flex items-start justify-between gap-16",
245
+ className: "flex items-start justify-between gap-group",
246
246
  children: [
247
247
  /*#__PURE__*/ _jsxs("div", {
248
- className: "flex flex-col gap-4",
248
+ className: "flex flex-col gap-tight",
249
249
  children: [
250
250
  /*#__PURE__*/ _jsx(Header, {
251
251
  variant: "h3",
@@ -281,11 +281,11 @@ export function CreateProjectPage() {
281
281
  ]
282
282
  }),
283
283
  showRepoPicker ? /*#__PURE__*/ _jsxs("section", {
284
- className: "flex flex-col gap-16",
284
+ className: "flex flex-col gap-group",
285
285
  "aria-label": "Repository",
286
286
  children: [
287
287
  /*#__PURE__*/ _jsxs("div", {
288
- className: "flex flex-col gap-4",
288
+ className: "flex flex-col gap-tight",
289
289
  children: [
290
290
  /*#__PURE__*/ _jsx(Header, {
291
291
  variant: "h3",
@@ -317,10 +317,10 @@ export function CreateProjectPage() {
317
317
  /*#__PURE__*/ _jsx("aside", {
318
318
  className: "lg:sticky lg:top-32",
319
319
  children: /*#__PURE__*/ _jsxs("div", {
320
- className: "flex flex-col gap-18 rounded-8 border border-border-neutral-base bg-background-neutral-base p-20",
320
+ className: "flex flex-col gap-group rounded-8 border border-border-neutral-base bg-background-neutral-base p-panel",
321
321
  children: [
322
322
  /*#__PURE__*/ _jsxs("div", {
323
- className: "flex flex-col gap-4",
323
+ className: "flex flex-col gap-tight",
324
324
  children: [
325
325
  /*#__PURE__*/ _jsx(Header, {
326
326
  variant: "h3",
@@ -426,7 +426,7 @@ export function CreateProjectPage() {
426
426
  }
427
427
  function ProjectSummary({ connection, repositoryName }) {
428
428
  return /*#__PURE__*/ _jsxs("div", {
429
- className: "flex min-w-0 items-center gap-10",
429
+ className: "flex min-w-0 items-center gap-inline",
430
430
  children: [
431
431
  repositoryName ? /*#__PURE__*/ _jsx(IntegrationIcon, {
432
432
  source: connection?.provider,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/create-project-page.tsx"],"sourcesContent":["import {slugifyName, slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {useMaybeActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionPicker,\n type IntegrationConnection,\n IntegrationIcon,\n RepositoryPicker,\n useRepositoriesInfiniteQuery,\n useSourceConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {displayNameFieldError, SlugField} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, Navigate, useNavigate} from '@tanstack/react-router';\nimport {useEffect, useRef, useState} from 'react';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport {type CreateProjectCommand, projectNameFromRepository} from '#core/project.js';\nimport {\n getProject,\n useCreateProjectMutation,\n useProjectSlugAvailability,\n} from '#hooks/api/projects.js';\nimport {projectErrorCopy} from '#project-error.js';\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function CreateProjectPage() {\n const workspace = useMaybeActiveWorkspace();\n const navigate = useNavigate();\n const createProject = useCreateProjectMutation();\n const errorRef = useRef<HTMLDivElement>(null);\n\n const connectionsQuery = useSourceConnectionsQuery(workspace?.id);\n const connections = connectionsQuery.data ?? [];\n\n const [selectedConnectionId, setSelectedConnectionId] = useState<string | undefined>();\n const singleConnectionId = connections.length === 1 ? connections[0]?.id : undefined;\n const effectiveSelectedConnectionId = selectedConnectionId ?? singleConnectionId;\n useEffect(() => {\n if (singleConnectionId && selectedConnectionId !== singleConnectionId) {\n setSelectedConnectionId(singleConnectionId);\n }\n }, [singleConnectionId, selectedConnectionId]);\n\n const selectedConnection: IntegrationConnection | undefined = connections.find(\n (connection) => connection.id === effectiveSelectedConnectionId,\n );\n\n const [repoFilter, setRepoFilter] = useState('');\n const debouncedRepoFilter = useDebouncedValue(repoFilter, 250);\n const trimmedFilter = debouncedRepoFilter.trim();\n\n const repositoriesQuery = useRepositoriesInfiniteQuery(\n effectiveSelectedConnectionId,\n trimmedFilter ? {search: trimmedFilter} : undefined,\n );\n const repositories = repositoriesQuery.data?.pages.flatMap((page) => page.repositories) ?? [];\n\n const [selectedRepositoryId, setSelectedRepositoryId] = useState<string | undefined>();\n useEffect(() => {\n if (!selectedRepositoryId && repositories[0]) {\n setSelectedRepositoryId(repositories[0].externalRepositoryId);\n }\n }, [repositories, selectedRepositoryId]);\n const selectedRepository = repositories.find(\n (repository) => repository.externalRepositoryId === selectedRepositoryId,\n );\n\n const [nameTouched, setNameTouched] = useState(false);\n const defaultProjectName = projectNameFromRepository(\n selectedRepository?.name ?? selectedRepositoryId ?? '',\n );\n const defaultProjectSlug = slugifyName(defaultProjectName, {fallback: 'project'});\n\n const [formError, setFormError] = useState<string | undefined>();\n const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);\n const [slugCheckEnabled, setSlugCheckEnabled] = useState(false);\n const [slugConflict, setSlugConflict] = useState(false);\n const workspaceId = workspace?.id;\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspaceId);\n\n const form = useForm({\n defaultValues: {name: defaultProjectName, slug: defaultProjectSlug},\n onSubmit: async ({value}) => {\n const projectSlug = slugManuallyEdited\n ? value.slug\n : slugifyName(value.name, {fallback: 'project'});\n await createProjectFromForm(nameTouched ? value.name : defaultProjectName, projectSlug);\n },\n });\n\n useEffect(() => {\n if (!nameTouched && form.state.values.name !== defaultProjectName) {\n form.setFieldValue('name', defaultProjectName);\n if (!slugManuallyEdited) {\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(defaultProjectName, {fallback: 'project'}));\n }\n }\n }, [defaultProjectName, form, nameTouched, slugManuallyEdited]);\n\n function selectConnection(connectionId: string) {\n setSelectedConnectionId(connectionId);\n setSelectedRepositoryId(undefined);\n }\n\n if (connectionsQuery.isPending) {\n return <FullPageLoader />;\n }\n\n if (!workspace) {\n return <FullPageLoader />;\n }\n\n if (!connectionsQuery.isError && connections.length === 0) {\n return (\n <Navigate\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n replace\n />\n );\n }\n\n async function createProjectFromForm(projectName: string, projectSlug: string) {\n setFormError(undefined);\n setSlugConflict(false);\n if (!workspace) {\n setFormError('Workspace is still loading. Try again in a moment.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedConnection) {\n setFormError('Choose a source integration before creating a project.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedRepository) {\n setFormError('Choose a repository before creating a project.');\n errorRef.current?.focus();\n return;\n }\n\n try {\n const command: CreateProjectCommand = {\n workspaceId: workspace.id,\n name: projectName,\n slug: projectSlug,\n source: {\n connectionId: selectedConnection.id,\n externalRepositoryId: selectedRepository.externalRepositoryId,\n },\n };\n const project = await createProject.mutateAsync(command);\n toast.success('Project created.');\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (error) {\n const copy = projectErrorCopy(error);\n if (copy.existingProjectId) {\n toast.info('Project already exists.');\n try {\n const project = await getProject(copy.existingProjectId);\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (recoveryError) {\n const recoveryCopy = projectErrorCopy(recoveryError);\n setFormError(`${recoveryCopy.title}: ${recoveryCopy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n return;\n }\n if (copy.slugConflict) {\n setSlugConflict(true);\n requestAnimationFrame(() => document.getElementById('project-slug')?.focus());\n return;\n }\n setFormError(`${copy.title}: ${copy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n }\n\n const showRepoPicker = Boolean(selectedConnection);\n const filteredEmptyMessage = trimmedFilter\n ? `No repositories matching \"${repoFilter.trim()}\".`\n : 'No repositories visible to this connection.';\n\n return (\n <div className=\"flex w-full flex-col gap-24\">\n <header className=\"flex flex-col gap-6\">\n <Header id=\"create-project-title\" variant=\"h1\">\n Create project\n </Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n A Shipfox project starts from a Git repository. Choose the repository Shipfox should\n track, then give the project a name.\n </Text>\n </header>\n\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n {connectionsQuery.isError ? (\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex w-full flex-wrap items-center justify-between gap-12\">\n <Text size=\"sm\" className=\"min-w-[240px] flex-1\">\n Could not load source integrations. Refresh the integrations list to continue.\n </Text>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => connectionsQuery.refetch()}\n className=\"shrink-0\"\n >\n Refresh integrations\n </Button>\n </div>\n </Callout>\n ) : null}\n\n <form\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n noValidate\n aria-labelledby=\"create-project-title\"\n className=\"grid items-start gap-32 lg:grid-cols-[minmax(0,1fr)_340px]\"\n >\n <div className=\"flex min-w-0 flex-col gap-32\">\n <section className=\"flex flex-col gap-16\" aria-label=\"Source integration\">\n <div className=\"flex items-start justify-between gap-16\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Source integration</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Choose the integration that can access the repository.\n </Text>\n </div>\n\n {connections.length === 1 ? (\n <Button asChild variant=\"transparent\" size=\"sm\" className=\"shrink-0\">\n <Link\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n >\n Add another integration\n </Link>\n </Button>\n ) : null}\n </div>\n\n {connections.length > 0 ? (\n <ConnectionPicker\n connections={connections}\n selectedConnectionId={effectiveSelectedConnectionId}\n onSelect={selectConnection}\n />\n ) : null}\n </section>\n\n {showRepoPicker ? (\n <section className=\"flex flex-col gap-16\" aria-label=\"Repository\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Repository</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Select the repository this project tracks.\n </Text>\n </div>\n\n <RepositoryPicker\n repositories={repositories}\n selectedRepositoryId={selectedRepositoryId}\n onSelect={setSelectedRepositoryId}\n isLoading={repositoriesQuery.isPending}\n isFetchingNextPage={repositoriesQuery.isFetchingNextPage}\n hasNextPage={repositoriesQuery.hasNextPage}\n onLoadMore={() => repositoriesQuery.fetchNextPage()}\n emptyMessage={filteredEmptyMessage}\n searchValue={repoFilter}\n onSearchChange={setRepoFilter}\n />\n </section>\n ) : null}\n </div>\n\n <aside className=\"lg:sticky lg:top-32\">\n <div className=\"flex flex-col gap-18 rounded-8 border border-border-neutral-base bg-background-neutral-base p-20\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Project details</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Confirm the source and create the project.\n </Text>\n </div>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n <div ref={errorRef} tabIndex={-1}>\n {formError}\n </div>\n </Callout>\n ) : null}\n\n <ProjectSummary\n connection={selectedConnection}\n repositoryName={selectedRepository?.fullName}\n />\n\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Project name\" id=\"project-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => {\n const nextName = event.target.value;\n setNameTouched(true);\n field.handleChange(nextName);\n if (!slugManuallyEdited) {\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(nextName, {fallback: 'project'}));\n }\n }}\n onBlur={field.handleBlur}\n placeholder=\"Platform\"\n />\n </FormField>\n )}\n </form.Field>\n\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createProjectBodySchema.shape.slug,\n onSubmit: createProjectBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"project-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => {\n setSlugManuallyEdited(true);\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n field.handleChange(value);\n }}\n onBlur={field.handleBlur}\n error={slugConflict ? 'This project slug is already in use.' : fieldError(field)}\n description={\n <span className=\"font-code\">\n {`/w/${workspace.slug}/p/${field.state.value}`}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n checkEnabled={slugCheckEnabled}\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button\n type=\"submit\"\n iconRight=\"chevronRight\"\n isLoading={createProject.isPending}\n disabled={!selectedConnection || !selectedRepository}\n className=\"w-full\"\n >\n Create project\n </Button>\n </div>\n </aside>\n </form>\n </div>\n );\n}\n\nfunction ProjectSummary({\n connection,\n repositoryName,\n}: {\n connection: IntegrationConnection | undefined;\n repositoryName: string | undefined;\n}) {\n return (\n <div className=\"flex min-w-0 items-center gap-10\">\n {repositoryName ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-20 shrink-0 text-foreground-neutral-base\"\n />\n ) : null}\n <Text\n size=\"sm\"\n bold\n className={repositoryName ? 'min-w-0 truncate' : 'text-foreground-neutral-muted'}\n >\n {repositoryName ?? 'Pick a repository'}\n </Text>\n </div>\n );\n}\n\nfunction useDebouncedValue<T>(value: T, delayMs: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const handle = setTimeout(() => setDebounced(value), delayMs);\n return () => clearTimeout(handle);\n }, [value, delayMs]);\n return debounced;\n}\n"],"names":["slugifyName","slugSchema","createProjectBodySchema","useMaybeActiveWorkspace","ConnectionPicker","IntegrationIcon","RepositoryPicker","useRepositoriesInfiniteQuery","useSourceConnectionsQuery","displayNameFieldError","SlugField","Button","Callout","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","Link","Navigate","useNavigate","useEffect","useRef","useState","ModelProviderReminderBanner","projectNameFromRepository","getProject","useCreateProjectMutation","useProjectSlugAvailability","projectErrorCopy","isSlugValid","value","safeParse","success","CreateProjectPage","workspace","navigate","createProject","errorRef","connectionsQuery","id","connections","data","selectedConnectionId","setSelectedConnectionId","singleConnectionId","length","undefined","effectiveSelectedConnectionId","selectedConnection","find","connection","repoFilter","setRepoFilter","debouncedRepoFilter","useDebouncedValue","trimmedFilter","trim","repositoriesQuery","search","repositories","pages","flatMap","page","selectedRepositoryId","setSelectedRepositoryId","externalRepositoryId","selectedRepository","repository","nameTouched","setNameTouched","defaultProjectName","name","defaultProjectSlug","fallback","formError","setFormError","slugManuallyEdited","setSlugManuallyEdited","slugCheckEnabled","setSlugCheckEnabled","slugConflict","setSlugConflict","workspaceId","checkProjectSlugAvailability","form","defaultValues","slug","onSubmit","projectSlug","createProjectFromForm","state","values","setFieldValue","selectConnection","connectionId","isPending","isError","to","params","workspaceSlug","replace","projectName","current","focus","command","source","project","mutateAsync","error","copy","existingProjectId","info","recoveryError","recoveryCopy","title","message","requestAnimationFrame","document","getElementById","showRepoPicker","Boolean","filteredEmptyMessage","div","className","header","variant","size","role","type","onClick","refetch","event","preventDefault","stopPropagation","handleSubmit","noValidate","aria-labelledby","section","aria-label","asChild","onSelect","isLoading","isFetchingNextPage","hasNextPage","onLoadMore","fetchNextPage","emptyMessage","searchValue","onSearchChange","aside","ref","tabIndex","ProjectSummary","repositoryName","fullName","Field","validators","onBlur","shape","field","label","onChange","nextName","target","handleChange","handleBlur","placeholder","description","span","checkEnabled","debounceMs","isValid","checkAvailability","iconRight","disabled","provider","aria-hidden","bold","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/create-project-page.tsx"],"sourcesContent":["import {slugifyName, slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {useMaybeActiveWorkspace} from '@shipfox/client-auth';\nimport {\n ConnectionPicker,\n type IntegrationConnection,\n IntegrationIcon,\n RepositoryPicker,\n useRepositoriesInfiniteQuery,\n useSourceConnectionsQuery,\n} from '@shipfox/client-integrations';\nimport {displayNameFieldError, SlugField} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {Link, Navigate, useNavigate} from '@tanstack/react-router';\nimport {useEffect, useRef, useState} from 'react';\nimport {ModelProviderReminderBanner} from '#components/model-provider-reminder-banner.js';\nimport {type CreateProjectCommand, projectNameFromRepository} from '#core/project.js';\nimport {\n getProject,\n useCreateProjectMutation,\n useProjectSlugAvailability,\n} from '#hooks/api/projects.js';\nimport {projectErrorCopy} from '#project-error.js';\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function CreateProjectPage() {\n const workspace = useMaybeActiveWorkspace();\n const navigate = useNavigate();\n const createProject = useCreateProjectMutation();\n const errorRef = useRef<HTMLDivElement>(null);\n\n const connectionsQuery = useSourceConnectionsQuery(workspace?.id);\n const connections = connectionsQuery.data ?? [];\n\n const [selectedConnectionId, setSelectedConnectionId] = useState<string | undefined>();\n const singleConnectionId = connections.length === 1 ? connections[0]?.id : undefined;\n const effectiveSelectedConnectionId = selectedConnectionId ?? singleConnectionId;\n useEffect(() => {\n if (singleConnectionId && selectedConnectionId !== singleConnectionId) {\n setSelectedConnectionId(singleConnectionId);\n }\n }, [singleConnectionId, selectedConnectionId]);\n\n const selectedConnection: IntegrationConnection | undefined = connections.find(\n (connection) => connection.id === effectiveSelectedConnectionId,\n );\n\n const [repoFilter, setRepoFilter] = useState('');\n const debouncedRepoFilter = useDebouncedValue(repoFilter, 250);\n const trimmedFilter = debouncedRepoFilter.trim();\n\n const repositoriesQuery = useRepositoriesInfiniteQuery(\n effectiveSelectedConnectionId,\n trimmedFilter ? {search: trimmedFilter} : undefined,\n );\n const repositories = repositoriesQuery.data?.pages.flatMap((page) => page.repositories) ?? [];\n\n const [selectedRepositoryId, setSelectedRepositoryId] = useState<string | undefined>();\n useEffect(() => {\n if (!selectedRepositoryId && repositories[0]) {\n setSelectedRepositoryId(repositories[0].externalRepositoryId);\n }\n }, [repositories, selectedRepositoryId]);\n const selectedRepository = repositories.find(\n (repository) => repository.externalRepositoryId === selectedRepositoryId,\n );\n\n const [nameTouched, setNameTouched] = useState(false);\n const defaultProjectName = projectNameFromRepository(\n selectedRepository?.name ?? selectedRepositoryId ?? '',\n );\n const defaultProjectSlug = slugifyName(defaultProjectName, {fallback: 'project'});\n\n const [formError, setFormError] = useState<string | undefined>();\n const [slugManuallyEdited, setSlugManuallyEdited] = useState(false);\n const [slugCheckEnabled, setSlugCheckEnabled] = useState(false);\n const [slugConflict, setSlugConflict] = useState(false);\n const workspaceId = workspace?.id;\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspaceId);\n\n const form = useForm({\n defaultValues: {name: defaultProjectName, slug: defaultProjectSlug},\n onSubmit: async ({value}) => {\n const projectSlug = slugManuallyEdited\n ? value.slug\n : slugifyName(value.name, {fallback: 'project'});\n await createProjectFromForm(nameTouched ? value.name : defaultProjectName, projectSlug);\n },\n });\n\n useEffect(() => {\n if (!nameTouched && form.state.values.name !== defaultProjectName) {\n form.setFieldValue('name', defaultProjectName);\n if (!slugManuallyEdited) {\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(defaultProjectName, {fallback: 'project'}));\n }\n }\n }, [defaultProjectName, form, nameTouched, slugManuallyEdited]);\n\n function selectConnection(connectionId: string) {\n setSelectedConnectionId(connectionId);\n setSelectedRepositoryId(undefined);\n }\n\n if (connectionsQuery.isPending) {\n return <FullPageLoader />;\n }\n\n if (!workspace) {\n return <FullPageLoader />;\n }\n\n if (!connectionsQuery.isError && connections.length === 0) {\n return (\n <Navigate\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n replace\n />\n );\n }\n\n async function createProjectFromForm(projectName: string, projectSlug: string) {\n setFormError(undefined);\n setSlugConflict(false);\n if (!workspace) {\n setFormError('Workspace is still loading. Try again in a moment.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedConnection) {\n setFormError('Choose a source integration before creating a project.');\n errorRef.current?.focus();\n return;\n }\n if (!selectedRepository) {\n setFormError('Choose a repository before creating a project.');\n errorRef.current?.focus();\n return;\n }\n\n try {\n const command: CreateProjectCommand = {\n workspaceId: workspace.id,\n name: projectName,\n slug: projectSlug,\n source: {\n connectionId: selectedConnection.id,\n externalRepositoryId: selectedRepository.externalRepositoryId,\n },\n };\n const project = await createProject.mutateAsync(command);\n toast.success('Project created.');\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (error) {\n const copy = projectErrorCopy(error);\n if (copy.existingProjectId) {\n toast.info('Project already exists.');\n try {\n const project = await getProject(copy.existingProjectId);\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug',\n params: {workspaceSlug: workspace.slug, projectSlug: project.slug},\n });\n } catch (recoveryError) {\n const recoveryCopy = projectErrorCopy(recoveryError);\n setFormError(`${recoveryCopy.title}: ${recoveryCopy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n return;\n }\n if (copy.slugConflict) {\n setSlugConflict(true);\n requestAnimationFrame(() => document.getElementById('project-slug')?.focus());\n return;\n }\n setFormError(`${copy.title}: ${copy.message}`);\n requestAnimationFrame(() => errorRef.current?.focus());\n }\n }\n\n const showRepoPicker = Boolean(selectedConnection);\n const filteredEmptyMessage = trimmedFilter\n ? `No repositories matching \"${repoFilter.trim()}\".`\n : 'No repositories visible to this connection.';\n\n return (\n <div className=\"flex w-full flex-col gap-section\">\n <header className=\"flex flex-col gap-inline\">\n <Header id=\"create-project-title\" variant=\"h1\">\n Create project\n </Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n A Shipfox project starts from a Git repository. Choose the repository Shipfox should\n track, then give the project a name.\n </Text>\n </header>\n\n <ModelProviderReminderBanner workspaceId={workspace.id} />\n\n {connectionsQuery.isError ? (\n <Callout role=\"alert\" type=\"error\">\n <div className=\"flex w-full flex-wrap items-center justify-between gap-cluster\">\n <Text size=\"sm\" className=\"min-w-[240px] flex-1\">\n Could not load source integrations. Refresh the integrations list to continue.\n </Text>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => connectionsQuery.refetch()}\n className=\"shrink-0\"\n >\n Refresh integrations\n </Button>\n </div>\n </Callout>\n ) : null}\n\n <form\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n noValidate\n aria-labelledby=\"create-project-title\"\n className=\"grid items-start gap-region lg:grid-cols-[minmax(0,1fr)_340px]\"\n >\n <div className=\"flex min-w-0 flex-col gap-region\">\n <section className=\"flex flex-col gap-group\" aria-label=\"Source integration\">\n <div className=\"flex items-start justify-between gap-group\">\n <div className=\"flex flex-col gap-tight\">\n <Header variant=\"h3\">Source integration</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Choose the integration that can access the repository.\n </Text>\n </div>\n\n {connections.length === 1 ? (\n <Button asChild variant=\"transparent\" size=\"sm\" className=\"shrink-0\">\n <Link\n to=\"/w/$workspaceSlug/integrations\"\n params={{workspaceSlug: workspace.slug}}\n >\n Add another integration\n </Link>\n </Button>\n ) : null}\n </div>\n\n {connections.length > 0 ? (\n <ConnectionPicker\n connections={connections}\n selectedConnectionId={effectiveSelectedConnectionId}\n onSelect={selectConnection}\n />\n ) : null}\n </section>\n\n {showRepoPicker ? (\n <section className=\"flex flex-col gap-group\" aria-label=\"Repository\">\n <div className=\"flex flex-col gap-tight\">\n <Header variant=\"h3\">Repository</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Select the repository this project tracks.\n </Text>\n </div>\n\n <RepositoryPicker\n repositories={repositories}\n selectedRepositoryId={selectedRepositoryId}\n onSelect={setSelectedRepositoryId}\n isLoading={repositoriesQuery.isPending}\n isFetchingNextPage={repositoriesQuery.isFetchingNextPage}\n hasNextPage={repositoriesQuery.hasNextPage}\n onLoadMore={() => repositoriesQuery.fetchNextPage()}\n emptyMessage={filteredEmptyMessage}\n searchValue={repoFilter}\n onSearchChange={setRepoFilter}\n />\n </section>\n ) : null}\n </div>\n\n <aside className=\"lg:sticky lg:top-32\">\n <div className=\"flex flex-col gap-group rounded-8 border border-border-neutral-base bg-background-neutral-base p-panel\">\n <div className=\"flex flex-col gap-tight\">\n <Header variant=\"h3\">Project details</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Confirm the source and create the project.\n </Text>\n </div>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n <div ref={errorRef} tabIndex={-1}>\n {formError}\n </div>\n </Callout>\n ) : null}\n\n <ProjectSummary\n connection={selectedConnection}\n repositoryName={selectedRepository?.fullName}\n />\n\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Project name\" id=\"project-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => {\n const nextName = event.target.value;\n setNameTouched(true);\n field.handleChange(nextName);\n if (!slugManuallyEdited) {\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n form.setFieldValue('slug', slugifyName(nextName, {fallback: 'project'}));\n }\n }}\n onBlur={field.handleBlur}\n placeholder=\"Platform\"\n />\n </FormField>\n )}\n </form.Field>\n\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createProjectBodySchema.shape.slug,\n onSubmit: createProjectBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"project-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => {\n setSlugManuallyEdited(true);\n setSlugCheckEnabled(true);\n setSlugConflict(false);\n field.handleChange(value);\n }}\n onBlur={field.handleBlur}\n error={slugConflict ? 'This project slug is already in use.' : fieldError(field)}\n description={\n <span className=\"font-code\">\n {`/w/${workspace.slug}/p/${field.state.value}`}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n checkEnabled={slugCheckEnabled}\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button\n type=\"submit\"\n iconRight=\"chevronRight\"\n isLoading={createProject.isPending}\n disabled={!selectedConnection || !selectedRepository}\n className=\"w-full\"\n >\n Create project\n </Button>\n </div>\n </aside>\n </form>\n </div>\n );\n}\n\nfunction ProjectSummary({\n connection,\n repositoryName,\n}: {\n connection: IntegrationConnection | undefined;\n repositoryName: string | undefined;\n}) {\n return (\n <div className=\"flex min-w-0 items-center gap-inline\">\n {repositoryName ? (\n <IntegrationIcon\n source={connection?.provider}\n aria-hidden\n className=\"size-20 shrink-0 text-foreground-neutral-base\"\n />\n ) : null}\n <Text\n size=\"sm\"\n bold\n className={repositoryName ? 'min-w-0 truncate' : 'text-foreground-neutral-muted'}\n >\n {repositoryName ?? 'Pick a repository'}\n </Text>\n </div>\n );\n}\n\nfunction useDebouncedValue<T>(value: T, delayMs: number): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n const handle = setTimeout(() => setDebounced(value), delayMs);\n return () => clearTimeout(handle);\n }, [value, delayMs]);\n return debounced;\n}\n"],"names":["slugifyName","slugSchema","createProjectBodySchema","useMaybeActiveWorkspace","ConnectionPicker","IntegrationIcon","RepositoryPicker","useRepositoriesInfiniteQuery","useSourceConnectionsQuery","displayNameFieldError","SlugField","Button","Callout","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","Link","Navigate","useNavigate","useEffect","useRef","useState","ModelProviderReminderBanner","projectNameFromRepository","getProject","useCreateProjectMutation","useProjectSlugAvailability","projectErrorCopy","isSlugValid","value","safeParse","success","CreateProjectPage","workspace","navigate","createProject","errorRef","connectionsQuery","id","connections","data","selectedConnectionId","setSelectedConnectionId","singleConnectionId","length","undefined","effectiveSelectedConnectionId","selectedConnection","find","connection","repoFilter","setRepoFilter","debouncedRepoFilter","useDebouncedValue","trimmedFilter","trim","repositoriesQuery","search","repositories","pages","flatMap","page","selectedRepositoryId","setSelectedRepositoryId","externalRepositoryId","selectedRepository","repository","nameTouched","setNameTouched","defaultProjectName","name","defaultProjectSlug","fallback","formError","setFormError","slugManuallyEdited","setSlugManuallyEdited","slugCheckEnabled","setSlugCheckEnabled","slugConflict","setSlugConflict","workspaceId","checkProjectSlugAvailability","form","defaultValues","slug","onSubmit","projectSlug","createProjectFromForm","state","values","setFieldValue","selectConnection","connectionId","isPending","isError","to","params","workspaceSlug","replace","projectName","current","focus","command","source","project","mutateAsync","error","copy","existingProjectId","info","recoveryError","recoveryCopy","title","message","requestAnimationFrame","document","getElementById","showRepoPicker","Boolean","filteredEmptyMessage","div","className","header","variant","size","role","type","onClick","refetch","event","preventDefault","stopPropagation","handleSubmit","noValidate","aria-labelledby","section","aria-label","asChild","onSelect","isLoading","isFetchingNextPage","hasNextPage","onLoadMore","fetchNextPage","emptyMessage","searchValue","onSearchChange","aside","ref","tabIndex","ProjectSummary","repositoryName","fullName","Field","validators","onBlur","shape","field","label","onChange","nextName","target","handleChange","handleBlur","placeholder","description","span","checkEnabled","debounceMs","isValid","checkAvailability","iconRight","disabled","provider","aria-hidden","bold","delayMs","debounced","setDebounced","handle","setTimeout","clearTimeout"],"mappings":"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"}
@@ -118,10 +118,10 @@ function ProjectSettingsForm({ project }) {
118
118
  return /*#__PURE__*/ _jsxs(_Fragment, {
119
119
  children: [
120
120
  /*#__PURE__*/ _jsxs("div", {
121
- className: "flex min-w-0 flex-col gap-24",
121
+ className: "flex min-w-0 flex-col gap-section",
122
122
  children: [
123
123
  /*#__PURE__*/ _jsxs("header", {
124
- className: "flex flex-col gap-6",
124
+ className: "flex flex-col gap-inline",
125
125
  children: [
126
126
  /*#__PURE__*/ _jsx(Header, {
127
127
  variant: "h1",
@@ -140,7 +140,7 @@ function ProjectSettingsForm({ project }) {
140
140
  children: formError
141
141
  }) : null,
142
142
  /*#__PURE__*/ _jsxs("form", {
143
- className: "flex max-w-[560px] flex-col gap-16",
143
+ className: "flex max-w-[560px] flex-col gap-group",
144
144
  noValidate: true,
145
145
  onSubmit: (event)=>{
146
146
  event.preventDefault();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/pages/project-settings-page.tsx"],"sourcesContent":["import {slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {ApiError} from '@shipfox/client-api';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n displayNameFieldError,\n QueryLoadError,\n SlugChangeWarning,\n SlugField,\n} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {useMaybeActiveProjectQuery} from '#chrome.js';\nimport type {Project} from '#core/project.js';\nimport {useProjectSlugAvailability, useUpdateProjectMutation} from '#hooks/api/projects.js';\nimport {projectSettingsErrorToFormError} from './project-settings-form-errors.js';\n\ninterface ProjectSettingsValues {\n name: string;\n slug: string;\n}\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function ProjectSettingsPage() {\n const projectQuery = useMaybeActiveProjectQuery();\n if (projectQuery.isPending) return <FullPageLoader />;\n if (projectQuery.isError && projectQuery.data === undefined) {\n if (projectQuery.error instanceof ApiError && projectQuery.error.status === 404) {\n return (\n <EmptyState\n icon=\"errorWarningLine\"\n tone=\"error\"\n title=\"Project not found\"\n description=\"This project doesn't exist, or you don't have access to it.\"\n />\n );\n }\n return <QueryLoadError query={projectQuery} subject=\"project\" />;\n }\n const project = projectQuery.data;\n if (!project) {\n return (\n <EmptyState\n icon=\"errorWarningLine\"\n tone=\"error\"\n title=\"Project not found\"\n description=\"This project doesn't exist, or you don't have access to it.\"\n />\n );\n }\n return (\n <ProjectSettingsForm key={`${project.id}:${project.name}:${project.slug}`} project={project} />\n );\n}\n\nfunction ProjectSettingsForm({project}: {project: Project}) {\n const workspace = useActiveWorkspace();\n const navigate = useNavigate();\n const updateProject = useUpdateProjectMutation();\n const [warningOpen, setWarningOpen] = useState(false);\n const [pendingValues, setPendingValues] = useState<ProjectSettingsValues>();\n const [formError, setFormError] = useState<string>();\n\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspace.id, project.id);\n\n const form = useForm({\n defaultValues: {name: project.name, slug: project.slug},\n onSubmit: async ({value}) => {\n if (value.slug !== project.slug) {\n setPendingValues(value);\n setWarningOpen(true);\n return;\n }\n await save(value);\n },\n });\n\n async function save(values: ProjectSettingsValues) {\n setFormError(undefined);\n const nameChanged = values.name !== project.name;\n const slugChanged = values.slug !== project.slug;\n if (!nameChanged && !slugChanged) return;\n const command = {\n projectId: project.id,\n ...(nameChanged ? {name: values.name} : {}),\n ...(slugChanged ? {slug: values.slug} : {}),\n };\n\n try {\n const updated = await updateProject.mutateAsync(command);\n setPendingValues(undefined);\n setWarningOpen(false);\n toast.success('Project settings saved.');\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug/settings/general',\n params: {workspaceSlug: workspace.slug, projectSlug: updated.slug},\n });\n } catch (error) {\n setPendingValues(undefined);\n setWarningOpen(false);\n const mapped = projectSettingsErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (previous) => ({\n ...previous,\n errorMap: {...previous.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n }\n\n return (\n <>\n <div className=\"flex min-w-0 flex-col gap-24\">\n <header className=\"flex flex-col gap-6\">\n <Header variant=\"h1\">General</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Update the project name and the slug used in its URLs.\n </Text>\n </header>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <form\n className=\"flex max-w-[560px] flex-col gap-16\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Project name\" id=\"project-settings-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createProjectBodySchema.shape.slug,\n onSubmit: createProjectBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"project-settings-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => field.handleChange(value)}\n onBlur={field.handleBlur}\n error={fieldError(field)}\n description={\n <span className=\"break-all font-code\">\n /w/{workspace.slug}/p/{field.state.value}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n currentSlug={project.slug}\n checkEnabled\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button type=\"submit\" isLoading={updateProject.isPending} className=\"self-start\">\n Save changes\n </Button>\n </form>\n </div>\n\n <SlugChangeWarning\n open={warningOpen}\n onOpenChange={setWarningOpen}\n entityLabel=\"project\"\n isLoading={updateProject.isPending}\n onConfirm={() => {\n if (pendingValues) void save(pendingValues);\n }}\n />\n </>\n );\n}\n"],"names":["slugSchema","createProjectBodySchema","ApiError","useActiveWorkspace","displayNameFieldError","QueryLoadError","SlugChangeWarning","SlugField","Button","Callout","EmptyState","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","useNavigate","useState","useMaybeActiveProjectQuery","useProjectSlugAvailability","useUpdateProjectMutation","projectSettingsErrorToFormError","isSlugValid","value","safeParse","success","ProjectSettingsPage","projectQuery","isPending","isError","data","undefined","error","status","icon","tone","title","description","query","subject","project","ProjectSettingsForm","id","name","slug","workspace","navigate","updateProject","warningOpen","setWarningOpen","pendingValues","setPendingValues","formError","setFormError","checkProjectSlugAvailability","form","defaultValues","onSubmit","save","values","nameChanged","slugChanged","command","projectId","updated","mutateAsync","to","params","workspaceSlug","projectSlug","mapped","kind","setFieldMeta","field","previous","errorMap","onServer","message","div","className","header","variant","size","role","type","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","shape","label","state","onChange","handleChange","target","handleBlur","span","placeholder","currentSlug","checkEnabled","debounceMs","isValid","checkAvailability","isLoading","open","onOpenChange","entityLabel","onConfirm"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/pages/project-settings-page.tsx"],"sourcesContent":["import {slugSchema} from '@shipfox/api-common-dto';\nimport {createProjectBodySchema} from '@shipfox/api-projects-dto';\nimport {ApiError} from '@shipfox/client-api';\nimport {useActiveWorkspace} from '@shipfox/client-auth';\nimport {\n displayNameFieldError,\n QueryLoadError,\n SlugChangeWarning,\n SlugField,\n} from '@shipfox/client-ui';\nimport {Button} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {EmptyState} from '@shipfox/react-ui/empty-state';\nimport {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';\nimport {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {useForm} from '@tanstack/react-form';\nimport {useNavigate} from '@tanstack/react-router';\nimport {useState} from 'react';\nimport {useMaybeActiveProjectQuery} from '#chrome.js';\nimport type {Project} from '#core/project.js';\nimport {useProjectSlugAvailability, useUpdateProjectMutation} from '#hooks/api/projects.js';\nimport {projectSettingsErrorToFormError} from './project-settings-form-errors.js';\n\ninterface ProjectSettingsValues {\n name: string;\n slug: string;\n}\n\nfunction isSlugValid(value: string): boolean {\n return slugSchema.safeParse(value).success;\n}\n\nexport function ProjectSettingsPage() {\n const projectQuery = useMaybeActiveProjectQuery();\n if (projectQuery.isPending) return <FullPageLoader />;\n if (projectQuery.isError && projectQuery.data === undefined) {\n if (projectQuery.error instanceof ApiError && projectQuery.error.status === 404) {\n return (\n <EmptyState\n icon=\"errorWarningLine\"\n tone=\"error\"\n title=\"Project not found\"\n description=\"This project doesn't exist, or you don't have access to it.\"\n />\n );\n }\n return <QueryLoadError query={projectQuery} subject=\"project\" />;\n }\n const project = projectQuery.data;\n if (!project) {\n return (\n <EmptyState\n icon=\"errorWarningLine\"\n tone=\"error\"\n title=\"Project not found\"\n description=\"This project doesn't exist, or you don't have access to it.\"\n />\n );\n }\n return (\n <ProjectSettingsForm key={`${project.id}:${project.name}:${project.slug}`} project={project} />\n );\n}\n\nfunction ProjectSettingsForm({project}: {project: Project}) {\n const workspace = useActiveWorkspace();\n const navigate = useNavigate();\n const updateProject = useUpdateProjectMutation();\n const [warningOpen, setWarningOpen] = useState(false);\n const [pendingValues, setPendingValues] = useState<ProjectSettingsValues>();\n const [formError, setFormError] = useState<string>();\n\n const checkProjectSlugAvailability = useProjectSlugAvailability(workspace.id, project.id);\n\n const form = useForm({\n defaultValues: {name: project.name, slug: project.slug},\n onSubmit: async ({value}) => {\n if (value.slug !== project.slug) {\n setPendingValues(value);\n setWarningOpen(true);\n return;\n }\n await save(value);\n },\n });\n\n async function save(values: ProjectSettingsValues) {\n setFormError(undefined);\n const nameChanged = values.name !== project.name;\n const slugChanged = values.slug !== project.slug;\n if (!nameChanged && !slugChanged) return;\n const command = {\n projectId: project.id,\n ...(nameChanged ? {name: values.name} : {}),\n ...(slugChanged ? {slug: values.slug} : {}),\n };\n\n try {\n const updated = await updateProject.mutateAsync(command);\n setPendingValues(undefined);\n setWarningOpen(false);\n toast.success('Project settings saved.');\n await navigate({\n to: '/w/$workspaceSlug/p/$projectSlug/settings/general',\n params: {workspaceSlug: workspace.slug, projectSlug: updated.slug},\n });\n } catch (error) {\n setPendingValues(undefined);\n setWarningOpen(false);\n const mapped = projectSettingsErrorToFormError(error);\n if (mapped.kind === 'field') {\n form.setFieldMeta(mapped.field, (previous) => ({\n ...previous,\n errorMap: {...previous.errorMap, onServer: mapped.message},\n }));\n } else {\n setFormError(mapped.message);\n }\n }\n }\n\n return (\n <>\n <div className=\"flex min-w-0 flex-col gap-section\">\n <header className=\"flex flex-col gap-inline\">\n <Header variant=\"h1\">General</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Update the project name and the slug used in its URLs.\n </Text>\n </header>\n\n {formError ? (\n <Callout role=\"alert\" type=\"error\">\n {formError}\n </Callout>\n ) : null}\n\n <form\n className=\"flex max-w-[560px] flex-col gap-group\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n event.stopPropagation();\n void form.handleSubmit();\n }}\n >\n <form.Field\n name=\"name\"\n validators={{\n onBlur: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n onSubmit: ({value}) =>\n displayNameFieldError(value, 'Project name', createProjectBodySchema.shape.name),\n }}\n >\n {(field) => (\n <FormField label=\"Project name\" id=\"project-settings-name\" error={fieldError(field)}>\n <FormFieldInput\n name=\"name\"\n type=\"text\"\n value={field.state.value}\n onChange={(event) => field.handleChange(event.target.value)}\n onBlur={field.handleBlur}\n />\n </FormField>\n )}\n </form.Field>\n\n <form.Field\n name=\"slug\"\n validators={{\n onBlur: createProjectBodySchema.shape.slug,\n onSubmit: createProjectBodySchema.shape.slug,\n }}\n >\n {(field) => (\n <SlugField\n id=\"project-settings-slug\"\n label=\"Project slug\"\n name=\"slug\"\n value={field.state.value}\n onChange={(value) => field.handleChange(value)}\n onBlur={field.handleBlur}\n error={fieldError(field)}\n description={\n <span className=\"break-all font-code\">\n /w/{workspace.slug}/p/{field.state.value}\n </span>\n }\n placeholder=\"platform\"\n className=\"font-code\"\n currentSlug={project.slug}\n checkEnabled\n debounceMs={0}\n isValid={isSlugValid}\n checkAvailability={checkProjectSlugAvailability}\n />\n )}\n </form.Field>\n\n <Button type=\"submit\" isLoading={updateProject.isPending} className=\"self-start\">\n Save changes\n </Button>\n </form>\n </div>\n\n <SlugChangeWarning\n open={warningOpen}\n onOpenChange={setWarningOpen}\n entityLabel=\"project\"\n isLoading={updateProject.isPending}\n onConfirm={() => {\n if (pendingValues) void save(pendingValues);\n }}\n />\n </>\n );\n}\n"],"names":["slugSchema","createProjectBodySchema","ApiError","useActiveWorkspace","displayNameFieldError","QueryLoadError","SlugChangeWarning","SlugField","Button","Callout","EmptyState","FormField","FormFieldInput","fieldError","FullPageLoader","toast","Header","Text","useForm","useNavigate","useState","useMaybeActiveProjectQuery","useProjectSlugAvailability","useUpdateProjectMutation","projectSettingsErrorToFormError","isSlugValid","value","safeParse","success","ProjectSettingsPage","projectQuery","isPending","isError","data","undefined","error","status","icon","tone","title","description","query","subject","project","ProjectSettingsForm","id","name","slug","workspace","navigate","updateProject","warningOpen","setWarningOpen","pendingValues","setPendingValues","formError","setFormError","checkProjectSlugAvailability","form","defaultValues","onSubmit","save","values","nameChanged","slugChanged","command","projectId","updated","mutateAsync","to","params","workspaceSlug","projectSlug","mapped","kind","setFieldMeta","field","previous","errorMap","onServer","message","div","className","header","variant","size","role","type","noValidate","event","preventDefault","stopPropagation","handleSubmit","Field","validators","onBlur","shape","label","state","onChange","handleChange","target","handleBlur","span","placeholder","currentSlug","checkEnabled","debounceMs","isValid","checkAvailability","isLoading","open","onOpenChange","entityLabel","onConfirm"],"mappings":"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"}