Skip to content

Commit 4b484d0

Browse files
Merge pull request #75 from corbitsdev/cl-11948-show-action-errors-as-one-quiet-toast-not-the-red-inline-bar
fix(web): show action errors as toasts
2 parents d6c6b46 + f9073be commit 4b484d0

10 files changed

Lines changed: 26 additions & 62 deletions

‎apps/web/src/pages/collaborators.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
import { wording } from "../config.ts";
1111
import { useCallback, useEffect, useState } from "react";
12+
import { toast } from "sonner";
1213
import { Banner, Button, QuietSelect, StateLabel } from "../components.jsx";
1314
import { productClient } from "../api/transport.ts";
1415
import { workspaceTenantId, type HubUser } from "../api/auth.ts";
@@ -108,7 +109,7 @@ export function Collaborators({ user }: { user: HubUser | null }) {
108109
setEmail("");
109110
await read();
110111
} catch (cause) {
111-
setError(await refusal(cause));
112+
toast.error(await refusal(cause));
112113
} finally {
113114
setBusy(false);
114115
}
@@ -122,7 +123,7 @@ export function Collaborators({ user }: { user: HubUser | null }) {
122123
await client.revokeInvitation(await workspaceTenantId(), invitationId);
123124
await read();
124125
} catch (cause) {
125-
setError(await refusal(cause));
126+
toast.error(await refusal(cause));
126127
} finally {
127128
setBusy(false);
128129
}

‎apps/web/src/pages/custom-providers.tsx‎

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import type {
3939
ModelCatalog,
4040
ModelProvider,
4141
} from "@corbits-builder/schemas/models";
42-
import { Banner, Button, QuietState } from "../components.jsx";
42+
import { Button, QuietState } from "../components.jsx";
4343
import { keys } from "../queries/keys.ts";
4444
import { productClient } from "../api/transport.ts";
4545
import { workspaceTenantId } from "../api/auth.ts";
@@ -128,13 +128,12 @@ export function message(cause: unknown): string {
128128
return cause instanceof Error ? cause.message : String(cause);
129129
}
130130

131-
/** Title, lead, the fields, the last error, and Cancel or Back beside the dialog's action. */
131+
/** Title, lead, the fields, and Cancel or Back beside the dialog's action. */
132132
export function ProviderDialog(props: {
133133
open: boolean;
134134
onOpenChange: (open: boolean) => void;
135135
title: string;
136136
description: string;
137-
error: string | null;
138137
busy: boolean;
139138
ready: boolean;
140139
/** Whether submitting tests a connection first. */
@@ -148,7 +147,7 @@ export function ProviderDialog(props: {
148147
focusDialog?: boolean;
149148
children: ReactNode;
150149
}) {
151-
const { busy, error, onBack, onOpenChange } = props;
150+
const { busy, onBack, onOpenChange } = props;
152151
return (
153152
<Dialog open={props.open} onOpenChange={onOpenChange}>
154153
<DialogContent
@@ -178,7 +177,6 @@ export function ProviderDialog(props: {
178177
}
179178
>
180179
{props.children}
181-
{error ? <Banner tone="error" title={error} /> : null}
182180
</DialogBody>
183181
<DialogFooter>
184182
<Button
@@ -724,12 +722,10 @@ export function CustomProviderRow({
724722
provider,
725723
apis,
726724
onChanged,
727-
onError,
728725
}: {
729726
provider: ModelProvider;
730727
apis: Apis;
731728
onChanged: () => void;
732-
onError: (message: string | null) => void;
733729
}) {
734730
const [open, setOpen] = useState(false);
735731
const listId = `custom-models-${provider.id}`;
@@ -755,7 +751,6 @@ export function CustomProviderRow({
755751
provider={provider}
756752
apis={apis}
757753
onChanged={onChanged}
758-
onError={onError}
759754
/>
760755
</span>
761756
</div>
@@ -780,7 +775,6 @@ function CustomProviderModels({
780775
provider.models.map((model) => ({ ...modelDraftOf(model), fixed: true })),
781776
);
782777
const [saveState, setSaveState] = useState<SaveState | null>(null);
783-
const [error, setError] = useState<string | null>(null);
784778
const saved = useRef(JSON.stringify(parsedModels(models)));
785779

786780
// Saves once the list is complete and differs from what the hub holds; a half-typed model waits.
@@ -789,7 +783,6 @@ function CustomProviderModels({
789783
const snapshot = JSON.stringify(parsed);
790784
if (!parsed || snapshot === saved.current) return;
791785
setSaveState("saving");
792-
setError(null);
793786
try {
794787
await productClient().updateCustomProvider(
795788
await workspaceTenantId(),
@@ -808,7 +801,7 @@ function CustomProviderModels({
808801
onChanged();
809802
} catch (cause) {
810803
setSaveState("failed");
811-
setError(message(cause));
804+
toast.error(message(cause));
812805
}
813806
};
814807

@@ -861,7 +854,6 @@ function CustomProviderModels({
861854
{copy.custom.addModel}
862855
</Button>
863856
</div>
864-
{error ? <Banner tone="error" title={error} /> : null}
865857
</>
866858
);
867859
}
@@ -871,12 +863,10 @@ function CustomProviderActions({
871863
provider,
872864
apis,
873865
onChanged,
874-
onError,
875866
}: {
876867
provider: ModelProvider;
877868
apis: Apis;
878869
onChanged: () => void;
879-
onError: (message: string | null) => void;
880870
}) {
881871
const queryClient = useQueryClient();
882872
const [editing, setEditing] = useState(false);
@@ -886,7 +876,6 @@ function CustomProviderActions({
886876

887877
const remove = async () => {
888878
setRemoving(true);
889-
onError(null);
890879
try {
891880
await productClient().removeCustomProvider(
892881
await workspaceTenantId(),
@@ -896,7 +885,7 @@ function CustomProviderActions({
896885
await queryClient.invalidateQueries({ queryKey: keys.modelCatalog });
897886
onChanged();
898887
} catch (cause) {
899-
onError(message(cause));
888+
toast.error(message(cause));
900889
} finally {
901890
setRemoving(false);
902891
}
@@ -971,20 +960,18 @@ function EditCustomProviderDialog({
971960
const queryClient = useQueryClient();
972961
const form = useCustomProviderForm(apis, provider);
973962
const [busy, setBusy] = useState(false);
974-
const [error, setError] = useState<string | null>(null);
975963

976964
const save = async () => {
977965
if (!form.ready || busy) return;
978966
setBusy(true);
979-
setError(null);
980967
try {
981968
await form.save();
982969
toast.success(copy.custom.saved(form.name));
983970
await queryClient.invalidateQueries({ queryKey: keys.modelCatalog });
984971
onSaved();
985972
onClose();
986973
} catch (cause) {
987-
setError(message(cause));
974+
toast.error(message(cause));
988975
} finally {
989976
setBusy(false);
990977
}
@@ -996,7 +983,6 @@ function EditCustomProviderDialog({
996983
onOpenChange={(open) => (open || busy ? undefined : onClose())}
997984
title={copy.custom.editTitle(provider.label)}
998985
description={copy.custom.editLead}
999-
error={error}
1000986
busy={busy}
1001987
ready={form.ready}
1002988
tests={form.tests}

‎apps/web/src/pages/invite-accept.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
import { wording } from "../config.ts";
1111
import { useEffect, useState } from "react";
12+
import { toast } from "sonner";
1213
import { Banner, Button, Mark, StateLabel } from "../components.jsx";
1314
import { productClient } from "../api/transport.ts";
1415
import { getHubSession, type HubUser } from "../api/auth.ts";
@@ -58,12 +59,11 @@ export function InvitationAccept({ invitationId }: { invitationId: string }) {
5859

5960
const accept = async () => {
6061
setBusy(true);
61-
setFailed(null);
6262
try {
6363
const result = await productClient().acceptInvitation(invitationId);
6464
setAccepted(result);
6565
} catch (cause) {
66-
setFailed(cause instanceof Error ? cause.message : String(cause));
66+
toast.error(cause instanceof Error ? cause.message : String(cause));
6767
} finally {
6868
setBusy(false);
6969
}

‎apps/web/src/pages/model-setup.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function ModelSetup({
6161
onConnected();
6262
};
6363
const catalog = useModelCatalog();
64-
const { logins, signingIn, error, signIn } = useSubscriptionLogins(connected);
64+
const { logins, signingIn, signIn } = useSubscriptionLogins(connected);
6565
const apiKeyProviders = (catalog.data?.providers ?? []).filter(
6666
(provider) => provider.kind === "api_key",
6767
);
@@ -85,7 +85,6 @@ export function ModelSetup({
8585
{catalog.error ? (
8686
<Banner tone="error" title={catalog.error.message} />
8787
) : null}
88-
{error ? <Banner tone="error" title={error} /> : null}
8988
<KindCardGrid
9089
label={copy.loginGroup}
9190
className="provider-tiles"

‎apps/web/src/pages/project-menu.tsx‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {
3535
restoreProject,
3636
shipProject,
3737
} from "../api/projects.ts";
38-
import { Banner, Button, type NoticeAction } from "../components.jsx";
38+
import { Button, type NoticeAction } from "../components.jsx";
3939
import { Dictated } from "../dictation.jsx";
4040

4141
/** What the menu needs of a project: the summaries (rail, top bar) both have it. */
@@ -324,7 +324,6 @@ export function ProjectInfoDialog({
324324
const [title, setTitle] = useState(project.title);
325325
const [saving, setSaving] = useState(false);
326326
const [archiving, setArchiving] = useState(false);
327-
const [error] = useState<string | null>(null);
328327

329328
const act = async (work: () => Promise<unknown>) => {
330329
try {
@@ -366,7 +365,6 @@ export function ProjectInfoDialog({
366365
<DialogTitle>{project.title}</DialogTitle>
367366
</DialogHeader>
368367
<DialogBody>
369-
{error ? <Banner tone="error" title={error} /> : null}
370368
<div className="field">
371369
<label htmlFor={`project-name-${project.id}`}>Name</label>
372370
<Dictated value={title} onValueChange={setTitle} align="center">

‎apps/web/src/pages/projects.tsx‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,17 @@ export function Projects({
4444
const [problem, setProblem] = useState("");
4545
const [profile, setProfile] = useState<string>();
4646
const [busy, setBusy] = useState<"start" | "import" | null>(null);
47-
const [error, setError] = useState<string | null>(null);
4847
const starting = busy === "start";
4948
const shell = useShellReply(useRef<HTMLElement>(null));
5049

5150
const start = async () => {
5251
if (busy !== null) return;
5352
if (shell.answers(problem)) return setProblem("");
5453
if (!canStartProject(problem)) {
55-
setError(wording.message.projectTooShort);
54+
toast.error(wording.message.projectTooShort);
5655
return;
5756
}
5857
setBusy("start");
59-
setError(null);
6058
try {
6159
const created = await createProject({
6260
problemStatement: problem.trim(),
@@ -69,7 +67,7 @@ export function Projects({
6967
// shows it until the conversation records it.
7068
onOpen(created.projectId, opening);
7169
} catch (cause) {
72-
setError(
70+
toast.error(
7371
cause instanceof ApiFailure ? cause.detail.message : String(cause),
7472
);
7573
} finally {
@@ -143,7 +141,6 @@ export function Projects({
143141
<h1 id="new-project-title">{wording.newProject.title}</h1>
144142
<p>{wording.newProject.lead}</p>
145143
</hgroup>
146-
{error ? <Banner tone="error" title={error} /> : null}
147144
{needsProvider ? (
148145
<Banner tone="warning" title={wording.providers.none}>
149146
<p>{wording.providers.noneHint}</p>

0 commit comments

Comments
 (0)