When transparent Dynamic Client Registration is rejected with an actionable reason (e.g. a server that refuses our redirect URI), surface the message as an inline error card on the bring-your-own-app recovery view instead of a transient toast, so the reason stays visible while the user registers an app.
Unified · Split
| @@ -751,6 +751,10 @@ function AddAccountModalView(props: AddAccountModalProps) { · 750 unmodified lines | ||
| 751 | 751 | const [dcrBusy, setDcrBusy] = useState(false); |
| 752 | 752 | const [dcrFailed, setDcrFailed] = useState(false); |
| 753 | 753 | const [oauthFallbackProbe, setOAuthFallbackProbe] = useState<DcrProbeResult | null>(null); |
| 754 | + // When transparent DCR is rejected with an actionable reason (e.g. the server | |
| 755 | + // refuses our redirect URI), surface it as an inline error card on the | |
| 756 | + // bring-your-own-app recovery view instead of a transient toast. | |
| 757 | + const [dcrFallbackMessage, setDcrFallbackMessage] = useState<string | null>(null); | |
| 754 | 758 | // FIX 3 escape hatch: when no registered app matched the integration's |
| 755 | 759 | // endpoints, the unmatched apps are collapsed behind an opt-in expander. |
| 756 | 760 | const [showOtherApps, setShowOtherApps] = useState(false); |
| @@ -817,6 +821,7 @@ function AddAccountModalView(props: AddAccountModalProps) { · 60 unmodified lines | ||
| 817 | 821 | setPickedApp(null); |
| 818 | 822 | setOAuthFallbackProbe(null); |
| 819 | 823 | setDcrFailed(false); |
| 824 | + setDcrFallbackMessage(null); | |
| 820 | 825 | }, [initialState, allMethods, defaultOwner, ownerOptions]); |
| 821 | 826 | |
| 822 | 827 | useEffect(() => { |
| @@ -1004,6 +1009,7 @@ function AddAccountModalView(props: AddAccountModalProps) { · 181 unmodified lines | ||
| 1004 | 1009 | setOnePasswordItemId(""); |
| 1005 | 1010 | setDcrFailed(false); |
| 1006 | 1011 | setOAuthFallbackProbe(null); |
| 1012 | + setDcrFallbackMessage(null); | |
| 1007 | 1013 | }; |
| 1008 | 1014 | |
| 1009 | 1015 | // A just-created custom method joins the in-session list and is auto-selected |
| @@ -1267,10 +1273,10 @@ function AddAccountModalView(props: AddAccountModalProps) { · 257 unmodified lines | ||
| 1267 | 1273 | if (outcome.kind === "fallback") { |
| 1268 | 1274 | setOAuthFallbackProbe("probe" in outcome ? outcome.probe : null); |
| 1269 | 1275 | setDcrFailed(true); |
| 1270 | − toast.error( | |
| 1271 | − ("message" in outcome ? outcome.message : undefined) ?? | |
| 1272 | − "Automatic setup unavailable. Register an app instead.", | |
| 1273 | − ); | |
| 1276 | + // Surface the server's actionable rejection reason on the recovery view as | |
| 1277 | + // an inline error card. Generic fallbacks (no message) fall through to the | |
| 1278 | + // "register an app" empty state, which already guides the user. | |
| 1279 | + setDcrFallbackMessage("message" in outcome ? (outcome.message ?? null) : null); | |
| 1274 | 1280 | } |
| 1275 | 1281 | }; |
| 1276 | 1282 | |
| @@ -1511,6 +1517,22 @@ function AddAccountModalView(props: AddAccountModalProps) { · 234 unmodified lines | ||
| 1511 | 1517 | <p className="text-xs text-muted-foreground">Loading OAuth apps…</p> |
| 1512 | 1518 | ) : ( |
| 1513 | 1519 | <div className="space-y-3"> |
| 1520 | + {dcrFallbackMessage ? ( | |
| 1521 | + <div | |
| 1522 | + role="alert" | |
| 1523 | + className="space-y-1 rounded-lg border border-destructive/40 bg-destructive/5 px-3 py-3" | |
| 1524 | + > | |
| 1525 | + <p className="text-sm font-medium text-destructive"> | |
| 1526 | + Couldn't set up {integrationName} automatically | |
| 1527 | + </p> | |
| 1528 | + <p className="text-xs text-muted-foreground"> | |
| 1529 | + {dcrFallbackMessage} | |
| 1530 | + </p> | |
| 1531 | + <p className="text-xs text-muted-foreground"> | |
| 1532 | + Register an app below to connect. | |
| 1533 | + </p> | |
| 1534 | + </div> | |
| 1535 | + ) : null} | |
| 1514 | 1536 | {/* No registered app matched the integration's endpoint: |
| 1515 | 1537 | empty state + a prominent register CTA, and an opt-in |
| 1516 | 1538 | collapsed "use a different registered app" escape hatch. */} |