History

Fix floating auth tab strip for transparent-DCR OAuth

When a transparent-DCR (or CIMD) OAuth integration also exposed a custom-method '+' (createCustomMethod), the method tab strip rendered but its TabsContent card was gated out by a 'dcrActive ? null' wrapper, leaving the tabs floating with no body and a detached-looking border. That same wrapper also made the in-card 'No app to choose' DCR explainer unreachable dead code. Always render TabsContent and let the inner dcrActive / cimdActive branches show the explainer, so the tab strip gets its card back.

Rhys Sullivan committed 4 months ago

Commit 429a0261 parent 9d1f0271 file · +167 −169Browse files

Showing the changes to packages (1 of 1 file). Show all

Unified · Split

packages/react/src/components/add-account-modal.tsx+167−169
@@ -1722,186 +1722,184 @@ function AddAccountModalView(props: AddAccountModalProps) { · 1721 unmodified lines
17221722 </div>
17231723 </TabsList>
17241724
1725− {dcrActive ? null : (
1726− <TabsContent
1727− value={methodId}
1728− className={cn(
1729− "mt-0 min-w-0 space-y-5",
1730− // No-auth renders no fields, so skip the framed box that
1731− // would otherwise show up as an empty bordered panel.
1732− // Otherwise the panel attaches to the tab header above
1733− // (square top, rounded bottom).
1734− isNoAuth
1735− ? null
1736− : "rounded-b-md rounded-t-none border border-border/60 bg-muted/15 p-4",
1737− )}
1738− >
1739− {method?.placements && !isEnvMethod && singleInput && !singleCredentialAffix
1740− ? (() => {
1741− const shown = method.placements.filter((p) => p.carrier !== "env");
1742− return shown.length > 0 ? (
1743− <div className="flex flex-wrap gap-x-3.5 gap-y-1">
1744− {shown.map((placement, i: number) => (
1745− <PlacementLine key={i} placement={placement} />
1746− ))}
1747− </div>
1748− ) : null;
1749− })()
1750− : null}
1751−
1752− {!isNoAuth && (
1753− <div className="space-y-2">
1754− <StepHeader index={2} label={authStepLabel} />
1755−
1756− {isOAuth && method ? (
1757− cimdActive ? (
1758− <div className="space-y-2 rounded-lg border border-ring/40 bg-accent/30 px-3 py-3">
1759− <p className="text-sm font-medium">No app registration</p>
1760− <p className="text-xs text-muted-foreground">
1761− {cimdConnecting
1762− ? `Connecting to ${integrationName}…`
1763− : `${integrationName} supports Client ID Metadata Document OAuth. We'll use this Executor host's public client metadata document and sign you in.`}
1764− </p>
1765− </div>
1766− ) : dcrActive ? (
1767− // Transparent DCR: no picker. We register an app for you and run
1768− // the OAuth flow with a single Connect click.
1769− <div className="space-y-2 rounded-lg border border-ring/40 bg-accent/30 px-3 py-3">
1770− <p className="text-sm font-medium">No app to choose</p>
1771− <p className="text-xs text-muted-foreground">
1772− {dcrConnecting
1773− ? `Connecting to ${integrationName}…`
1774− : `${integrationName} supports automatic setup. We register an app for you and sign you in — no client ID or app to pick.`}
1775− </p>
1776− </div>
1777− ) : oauthLoading ? (
1778− <p className="text-xs text-muted-foreground">Loading OAuth apps…</p>
1779− ) : (
1780− <div className="space-y-3">
1781− {dcrFallbackMessage ? (
1782− <div
1783− role="alert"
1784− className="space-y-1 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-3"
1785− >
1786− <p className="text-sm font-medium text-destructive">
1787− Couldn&apos;t set up {integrationName} automatically
1788− </p>
1789− <p className="text-xs text-muted-foreground">
1790− {dcrFallbackMessage}
1791− </p>
1792− <p className="text-xs text-muted-foreground">
1793− Register an app below to connect.
1794− </p>
1795− </div>
1796− ) : null}
1797− {/* No registered app matched the integration's endpoint:
1725+ <TabsContent
1726+ value={methodId}
1727+ className={cn(
1728+ "mt-0 min-w-0 space-y-5",
1729+ // No-auth renders no fields, so skip the framed box that
1730+ // would otherwise show up as an empty bordered panel.
1731+ // Otherwise the panel attaches to the tab header above
1732+ // (square top, rounded bottom).
1733+ isNoAuth
1734+ ? null
1735+ : "rounded-b-md rounded-t-none border border-border/60 bg-muted/15 p-4",
1736+ )}
1737+ >
1738+ {method?.placements && !isEnvMethod && singleInput && !singleCredentialAffix
1739+ ? (() => {
1740+ const shown = method.placements.filter((p) => p.carrier !== "env");
1741+ return shown.length > 0 ? (
1742+ <div className="flex flex-wrap gap-x-3.5 gap-y-1">
1743+ {shown.map((placement, i: number) => (
1744+ <PlacementLine key={i} placement={placement} />
1745+ ))}
1746+ </div>
1747+ ) : null;
1748+ })()
1749+ : null}
1750+
1751+ {!isNoAuth && (
1752+ <div className="space-y-2">
1753+ <StepHeader index={2} label={authStepLabel} />
1754+
1755+ {isOAuth && method ? (
1756+ cimdActive ? (
1757+ <div className="space-y-2 rounded-lg border border-ring/40 bg-accent/30 px-3 py-3">
1758+ <p className="text-sm font-medium">No app registration</p>
1759+ <p className="text-xs text-muted-foreground">
1760+ {cimdConnecting
1761+ ? `Connecting to ${integrationName}…`
1762+ : `${integrationName} supports Client ID Metadata Document OAuth. We'll use this Executor host's public client metadata document and sign you in.`}
1763+ </p>
1764+ </div>
1765+ ) : dcrActive ? (
1766+ // Transparent DCR: no picker. We register an app for you and run
1767+ // the OAuth flow with a single Connect click.
1768+ <div className="space-y-2 rounded-lg border border-ring/40 bg-accent/30 px-3 py-3">
1769+ <p className="text-sm font-medium">No app to choose</p>
1770+ <p className="text-xs text-muted-foreground">
1771+ {dcrConnecting
1772+ ? `Connecting to ${integrationName}…`
1773+ : `${integrationName} supports automatic setup. We register an app for you and sign you in — no client ID or app to pick.`}
1774+ </p>
1775+ </div>
1776+ ) : oauthLoading ? (
1777+ <p className="text-xs text-muted-foreground">Loading OAuth apps…</p>
1778+ ) : (
1779+ <div className="space-y-3">
1780+ {dcrFallbackMessage ? (
1781+ <div
1782+ role="alert"
1783+ className="space-y-1 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-3"
1784+ >
1785+ <p className="text-sm font-medium text-destructive">
1786+ Couldn&apos;t set up {integrationName} automatically
1787+ </p>
1788+ <p className="text-xs text-muted-foreground">
1789+ {dcrFallbackMessage}
1790+ </p>
1791+ <p className="text-xs text-muted-foreground">
1792+ Register an app below to connect.
1793+ </p>
1794+ </div>
1795+ ) : null}
1796+ {/* No registered app matched the integration's endpoint:
17981797 empty state + a prominent register CTA, and an opt-in
17991798 collapsed "use a different registered app" escape hatch. */}
1800− {oauthDisplayRegisterCTA && (
1801− <div className="space-y-3 rounded-lg border border-border/60 bg-muted/20 px-3 py-3">
1802− <p className="text-sm font-medium">
1803− No app for {integrationName} yet
1804− </p>
1805− <p className="text-xs text-muted-foreground">
1806− None of your registered apps target this integration's OAuth
1807− endpoint. Register one to connect.
1808− </p>
1809− <div className="flex flex-wrap items-center gap-2 pt-1">
1799+ {oauthDisplayRegisterCTA && (
1800+ <div className="space-y-3 rounded-lg border border-border/60 bg-muted/20 px-3 py-3">
1801+ <p className="text-sm font-medium">
1802+ No app for {integrationName} yet
1803+ </p>
1804+ <p className="text-xs text-muted-foreground">
1805+ None of your registered apps target this integration's OAuth
1806+ endpoint. Register one to connect.
1807+ </p>
1808+ <div className="flex flex-wrap items-center gap-2 pt-1">
1809+ <Button
1810+ type="button"
1811+ size="sm"
1812+ onClick={() => setRegisteringOAuthClient(true)}
1813+ >
1814+ {dcrFallbackMessage
1815+ ? "Manually register an app"
1816+ : "Register app"}
1817+ </Button>
1818+ {oauthOtherApps.length > 0 && !showOtherApps ? (
18101819 <Button
18111820 type="button"
1821+ variant="outline"
18121822 size="sm"
1813− onClick={() => setRegisteringOAuthClient(true)}
1823+ onClick={() => setShowOtherApps(true)}
18141824 >
1815− {dcrFallbackMessage
1816− ? "Manually register an app"
1817− : "Register app"}
1825+ Use another app
18181826 </Button>
1819− {oauthOtherApps.length > 0 && !showOtherApps ? (
1820− <Button
1821− type="button"
1822− variant="outline"
1823− size="sm"
1824− onClick={() => setShowOtherApps(true)}
1825− >
1826− Use another app
1827− </Button>
1828− ) : null}
1829− </div>
1830− {oauthOtherApps.length > 0 && showOtherApps ? (
1831− <RadioGroup
1832− value={selectedApp}
1833− onValueChange={setPickedApp}
1834− className="gap-2 pt-1"
1835− >
1836− {oauthOtherApps.map((app: OAuthClientOption) => (
1837− <OAuthAppRadioRow
1838− key={String(app.slug)}
1839− app={app}
1840− idPrefix="other-app"
1841− variant="other"
1842− showOwnerLabel={ownerDisplay.showOwnerLabels}
1843− onManage={manageHandlersFor(app)}
1844− />
1845− ))}
1846− </RadioGroup>
18471827 ) : null}
18481828 </div>
1849− )}
1829+ {oauthOtherApps.length > 0 && showOtherApps ? (
1830+ <RadioGroup
1831+ value={selectedApp}
1832+ onValueChange={setPickedApp}
1833+ className="gap-2 pt-1"
1834+ >
1835+ {oauthOtherApps.map((app: OAuthClientOption) => (
1836+ <OAuthAppRadioRow
1837+ key={String(app.slug)}
1838+ app={app}
1839+ idPrefix="other-app"
1840+ variant="other"
1841+ showOwnerLabel={ownerDisplay.showOwnerLabels}
1842+ onManage={manageHandlersFor(app)}
1843+ />
1844+ ))}
1845+ </RadioGroup>
1846+ ) : null}
1847+ </div>
1848+ )}
18501849
1851− {oauthApps.length > 0 && (
1852− <RadioGroup
1853− value={selectedApp}
1854− onValueChange={setPickedApp}
1855− className="gap-2"
1850+ {oauthApps.length > 0 && (
1851+ <RadioGroup
1852+ value={selectedApp}
1853+ onValueChange={setPickedApp}
1854+ className="gap-2"
1855+ >
1856+ {oauthApps.map((app: OAuthClientOption) => (
1857+ <OAuthAppRadioRow
1858+ key={String(app.slug)}
1859+ app={app}
1860+ idPrefix="app"
1861+ variant="matched"
1862+ showOwnerLabel={ownerDisplay.showOwnerLabels}
1863+ onManage={manageHandlersFor(app)}
1864+ />
1865+ ))}
1866+ <Button
1867+ type="button"
1868+ variant="outline"
1869+ className="h-auto justify-start gap-3 rounded-lg border-dashed border-border/60 px-3 py-2.5 text-sm font-normal text-muted-foreground hover:text-foreground"
1870+ onClick={() => setRegisteringOAuthClient(true)}
18561871 >
1857− {oauthApps.map((app: OAuthClientOption) => (
1858− <OAuthAppRadioRow
1859− key={String(app.slug)}
1860− app={app}
1861− idPrefix="app"
1862− variant="matched"
1863− showOwnerLabel={ownerDisplay.showOwnerLabels}
1864− onManage={manageHandlersFor(app)}
1865− />
1866− ))}
1867− <Button
1868− type="button"
1869− variant="outline"
1870− className="h-auto justify-start gap-3 rounded-lg border-dashed border-border/60 px-3 py-2.5 text-sm font-normal text-muted-foreground hover:text-foreground"
1871− onClick={() => setRegisteringOAuthClient(true)}
1872− >
1873− <PlusIcon className="size-4" />
1874− Register a new app
1875− </Button>
1876− </RadioGroup>
1877− )}
1878− </div>
1879− )
1880− ) : (
1881− <CredentialValueFields
1882− inputs={credentialInputs}
1883− singleInput={singleInput}
1884− showLabels={isEnvMethod}
1885− affix={singleCredentialAffix}
1886− allowExternalProvider={!isEnvMethod}
1887− values={values}
1888− onValuesChange={setValues}
1889− origin={credentialOrigin}
1890− onOriginChange={(next) => {
1891− setCredentialOrigin(next);
1892− if (next === "paste") setOnePasswordItemId("");
1893− }}
1894− onePasswordItemId={onePasswordItemId}
1895− onOnePasswordItemIdChange={setOnePasswordItemId}
1896− />
1897− )}
1898− {isOAuth && oauthPopup.error ? (
1899− <p className="text-xs text-destructive">{oauthPopup.error}</p>
1900− ) : null}
1901− </div>
1902− )}
1903− </TabsContent>
1904− )}
1872+ <PlusIcon className="size-4" />
1873+ Register a new app
1874+ </Button>
1875+ </RadioGroup>
1876+ )}
1877+ </div>
1878+ )
1879+ ) : (
1880+ <CredentialValueFields
1881+ inputs={credentialInputs}
1882+ singleInput={singleInput}
1883+ showLabels={isEnvMethod}
1884+ affix={singleCredentialAffix}
1885+ allowExternalProvider={!isEnvMethod}
1886+ values={values}
1887+ onValuesChange={setValues}
1888+ origin={credentialOrigin}
1889+ onOriginChange={(next) => {
1890+ setCredentialOrigin(next);
1891+ if (next === "paste") setOnePasswordItemId("");
1892+ }}
1893+ onePasswordItemId={onePasswordItemId}
1894+ onOnePasswordItemIdChange={setOnePasswordItemId}
1895+ />
1896+ )}
1897+ {isOAuth && oauthPopup.error ? (
1898+ <p className="text-xs text-destructive">{oauthPopup.error}</p>
1899+ ) : null}
1900+ </div>
1901+ )}
1902+ </TabsContent>
19051903 </Tabs>
19061904 )}
19071905