mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
Show alert when there's an error in UI components
This commit is contained in:
parent
ba8de5c8f8
commit
035ba57d66
@ -5,7 +5,7 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { InputField, SwitchListField } from "@/components/form-fields";
|
||||
import { runAsynchronously, wait } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronously, runAsynchronouslyWithAlert, wait } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { useRouter } from "@/components/router";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@ -79,7 +79,7 @@ export default function PageClient () {
|
||||
</div>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={e => runAsynchronously(form.handleSubmit(onSubmit)(e))} className="space-y-4">
|
||||
<form onSubmit={e => runAsynchronouslyWithAlert(form.handleSubmit(onSubmit)(e))} className="space-y-4">
|
||||
|
||||
<InputField required control={form.control} name="displayName" label="Project Name" placeholder="My Project" />
|
||||
|
||||
@ -127,4 +127,4 @@ export default function PageClient () {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import * as yup from "yup";
|
||||
import { useEffect, useId, useState } from "react";
|
||||
import { ActionDialog, ActionDialogProps } from "@/components/action-dialog";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Form } from "@/components/ui/form";
|
||||
@ -90,7 +90,7 @@ export function FormDialog<F extends FieldValues>(
|
||||
{...props}
|
||||
open={props.open ?? openState}
|
||||
onOpenChange={(open) => { if(open) setOpenState(true); props.onOpenChange?.(open); }}
|
||||
onClose={() => { form.reset(); setOpenState(false); runAsynchronously(props.onClose?.()); }}
|
||||
onClose={() => { form.reset(); setOpenState(false); runAsynchronouslyWithAlert(props.onClose?.()); }}
|
||||
okButton={{
|
||||
onClick: async () => "prevent-close",
|
||||
...(typeof props.okButton == "boolean" ? {} : props.okButton),
|
||||
@ -103,7 +103,7 @@ export function FormDialog<F extends FieldValues>(
|
||||
}}
|
||||
>
|
||||
<Form {...form}>
|
||||
<form onSubmit={e => runAsynchronously(form.handleSubmit(onSubmit)(e))} className="space-y-4" id={formId}>
|
||||
<form onSubmit={e => runAsynchronouslyWithAlert(form.handleSubmit(onSubmit)(e))} className="space-y-4" id={formId}>
|
||||
{props.render(form)}
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@ -12,7 +12,7 @@ import { Button } from "./ui/button";
|
||||
import React, { useEffect, useId, useRef, useState } from "react";
|
||||
import { Label } from "./ui/label";
|
||||
import { DelayedInput, Input } from "./ui/input";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { Accordion } from "@radix-ui/react-accordion";
|
||||
import { AccordionContent, AccordionItem, AccordionTrigger } from "./ui/accordion";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
@ -114,7 +114,7 @@ export function SettingInput(props: {
|
||||
<DelayedInput
|
||||
className="max-w-lg"
|
||||
defaultValue={props.defaultValue}
|
||||
onChange={(e) => runAsynchronously(props.onChange?.(e.target.value))}
|
||||
onChange={(e) => runAsynchronouslyWithAlert(props.onChange?.(e.target.value))}
|
||||
/>
|
||||
{props.actions}
|
||||
</div>
|
||||
@ -177,10 +177,10 @@ export function FormSettingCard<F extends FieldValues>(
|
||||
</>
|
||||
}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={e => runAsynchronously(form.handleSubmit(onSubmit)(e))} className="space-y-4" id={formId}>
|
||||
<form onSubmit={e => runAsynchronouslyWithAlert(form.handleSubmit(onSubmit)(e))} className="space-y-4" id={formId}>
|
||||
{props.render(form)}
|
||||
</form>
|
||||
</Form>
|
||||
</SettingCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { Spinner } from "./spinner";
|
||||
import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-callback";
|
||||
|
||||
@ -75,7 +75,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
{...props}
|
||||
ref={ref}
|
||||
disabled={props.disabled || loading}
|
||||
onClick={(e) => runAsynchronously(handleClick(e))}
|
||||
onClick={(e) => runAsynchronouslyWithAlert(handleClick(e))}
|
||||
>
|
||||
{loading && <Spinner className="mr-2" />}
|
||||
{children}
|
||||
|
||||
@ -28,7 +28,7 @@ export default function ErrorPage(props: {
|
||||
{props.description}
|
||||
</Typography>
|
||||
|
||||
<Button onClick={() => runAsynchronously(router.push(props.redirectUrl))}>
|
||||
<Button onClick={() => router.push(props.redirectUrl)}>
|
||||
{props.redirectText}
|
||||
</Button>
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import * as React from "react";
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { useAsyncCallback } from "@stackframe/stack-shared/dist/hooks/use-async-callback";
|
||||
import { Spinner } from "./spinner";
|
||||
|
||||
@ -55,8 +55,8 @@ const Switch = React.forwardRef<
|
||||
<OriginalSwitch
|
||||
{...props}
|
||||
ref={ref}
|
||||
onClick={(e) => runAsynchronously(handleClick(e))}
|
||||
onCheckedChange={(checked) => runAsynchronously(handleCheckedChange(checked))}
|
||||
onClick={(e) => runAsynchronouslyWithAlert(handleClick(e))}
|
||||
onCheckedChange={(checked) => runAsynchronouslyWithAlert(handleCheckedChange(checked))}
|
||||
disabled={props.disabled || loading}
|
||||
style={{
|
||||
visibility: loading ? "hidden" : "visible",
|
||||
|
||||
@ -96,10 +96,25 @@ class ErrorDuringRunAsynchronously extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export function runAsynchronouslyWithAlert(...args: Parameters<typeof runAsynchronously>) {
|
||||
return runAsynchronously(
|
||||
args[0],
|
||||
{
|
||||
...args[1],
|
||||
onError: error => {
|
||||
alert(`An unhandled error occurred. Please ${process.env.NODE_ENV === "development" ? `check the browser console for the full error. ${error}` : "report this to the developer."}`);
|
||||
args[1]?.onError?.(error);
|
||||
},
|
||||
},
|
||||
...args.slice(2) as [],
|
||||
);
|
||||
}
|
||||
|
||||
export function runAsynchronously(
|
||||
promiseOrFunc: void | Promise<unknown> | (() => void | Promise<unknown>) | undefined,
|
||||
options: {
|
||||
ignoreErrors?: boolean,
|
||||
noErrorLogging?: boolean,
|
||||
onError?: (error: Error) => void,
|
||||
} = {},
|
||||
): void {
|
||||
if (typeof promiseOrFunc === "function") {
|
||||
@ -116,7 +131,8 @@ export function runAsynchronously(
|
||||
cause: error,
|
||||
}
|
||||
);
|
||||
if (!options.ignoreErrors) {
|
||||
options.onError?.(newError);
|
||||
if (!options.noErrorLogging) {
|
||||
captureError("runAsynchronously", newError);
|
||||
}
|
||||
});
|
||||
|
||||
@ -7,7 +7,7 @@ import FormWarningText from "./form-warning";
|
||||
import PasswordField from "./password-field";
|
||||
import { useStackApp } from "..";
|
||||
import { Button, Input, Label, Link } from "../components-core";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
|
||||
const schema = yup.object().shape({
|
||||
email: yup.string().email('Please enter a valid email').required('Please enter your email'),
|
||||
@ -30,7 +30,7 @@ export default function CredentialSignIn() {
|
||||
return (
|
||||
<form
|
||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'stretch' }}
|
||||
onSubmit={e => runAsynchronously(handleSubmit(onSubmit)(e))}
|
||||
onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}
|
||||
noValidate
|
||||
>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
|
||||
@ -7,7 +7,7 @@ import PasswordField from "./password-field";
|
||||
import FormWarningText from "./form-warning";
|
||||
import { useStackApp } from "..";
|
||||
import { Label, Input, Button } from "../components-core";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { getPasswordError } from "@stackframe/stack-shared/dist/helpers/password";
|
||||
|
||||
const schema = yup.object().shape({
|
||||
@ -41,7 +41,7 @@ export default function CredentialSignUp() {
|
||||
return (
|
||||
<form
|
||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'stretch' }}
|
||||
onSubmit={e => runAsynchronously(handleSubmit(onSubmit)(e))}
|
||||
onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}
|
||||
noValidate
|
||||
>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
|
||||
@ -6,7 +6,7 @@ import * as yup from "yup";
|
||||
import FormWarningText from "./form-warning";
|
||||
import { useStackApp } from "..";
|
||||
import { Button, Input, Label } from "../components-core";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
|
||||
const schema = yup.object().shape({
|
||||
email: yup.string().email('Please enter a valid email').required('Please enter your email')
|
||||
@ -27,7 +27,7 @@ export default function ForgotPassword({ onSent }: { onSent?: () => void }) {
|
||||
return (
|
||||
<form
|
||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'stretch' }}
|
||||
onSubmit={e => runAsynchronously(handleSubmit(onSubmit)(e))}
|
||||
onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}
|
||||
noValidate
|
||||
>
|
||||
<Label htmlFor="email">Your Email</Label>
|
||||
|
||||
@ -7,7 +7,7 @@ import * as yup from "yup";
|
||||
import FormWarningText from "./form-warning";
|
||||
import { useStackApp } from "..";
|
||||
import { Button, Input, Label } from "../components-core";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
|
||||
const schema = yup.object().shape({
|
||||
email: yup.string().email('Please enter a valid email').required('Please enter your email')
|
||||
@ -34,7 +34,7 @@ export default function MagicLinkSignIn() {
|
||||
return (
|
||||
<form
|
||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'stretch' }}
|
||||
onSubmit={e => runAsynchronously(handleSubmit(onSubmit)(e))}
|
||||
onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}
|
||||
noValidate
|
||||
>
|
||||
<Label htmlFor="email">Email</Label>
|
||||
|
||||
@ -12,7 +12,7 @@ import RedirectMessageCard from "./redirect-message-card";
|
||||
import MessageCard from "./message-card";
|
||||
import MaybeFullPage from "./maybe-full-page";
|
||||
import { Button, Label, Text } from "../components-core";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
|
||||
const schema = yup.object().shape({
|
||||
password: yup.string().required('Please enter your password').test({
|
||||
@ -71,7 +71,7 @@ export default function PasswordResetInner(
|
||||
|
||||
<form
|
||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'stretch' }}
|
||||
onSubmit={e => runAsynchronously(handleSubmit(onSubmit)(e))}
|
||||
onSubmit={e => runAsynchronouslyWithAlert(handleSubmit(onSubmit)(e))}
|
||||
noValidate
|
||||
>
|
||||
<Label htmlFor="password">New Password</Label>
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
Skeleton,
|
||||
CurrentUser,
|
||||
} from "..";
|
||||
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
|
||||
import UserAvatar from "./user-avatar";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { typedEntries, typedFromEntries } from "@stackframe/stack-shared/dist/utils/objects";
|
||||
@ -40,7 +40,7 @@ const icons = typedFromEntries(typedEntries({
|
||||
function Item(props: { text: string, icon: React.ReactNode, onClick: () => void | Promise<void> }) {
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
onClick={() => runAsynchronously(props.onClick)}
|
||||
onClick={() => runAsynchronouslyWithAlert(props.onClick)}
|
||||
style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}
|
||||
>
|
||||
{props.icon}
|
||||
@ -116,17 +116,17 @@ function UserButtonInnerInner(props: UserButtonProps & { user: CurrentUser | nul
|
||||
<DropdownMenuSeparator />
|
||||
{user && <Item
|
||||
text="Account settings"
|
||||
onClick={() => runAsynchronously(router.push(app.urls.accountSettings))}
|
||||
onClick={() => router.push(app.urls.accountSettings)}
|
||||
icon={icons.CircleUser}
|
||||
/>}
|
||||
{!user && <Item
|
||||
text="Sign in"
|
||||
onClick={() => runAsynchronously(router.push(app.urls.signIn))}
|
||||
onClick={() => router.push(app.urls.signIn)}
|
||||
icon={icons.LogIn}
|
||||
/>}
|
||||
{!user && <Item
|
||||
text="Sign up"
|
||||
onClick={() => runAsynchronously(router.push(app.urls.signUp))}
|
||||
onClick={() => router.push(app.urls.signUp)}
|
||||
icon={icons.UserPlus}
|
||||
/>}
|
||||
{user && props.extraItems && props.extraItems.map((item, index) => (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user