added loading indicator, fixed loading buttons

This commit is contained in:
Zai Shi 2024-04-14 11:13:21 +02:00
parent 5dcc831f96
commit 5ae06585f2
6 changed files with 76 additions and 21 deletions

View File

@ -66,6 +66,21 @@ export default function PageClient() {
</Button>
</div>
<div style={{ display: 'flex', gap: 5 }}>
<Button size='md' variant="primary" loading>
Button
</Button>
<Button size='md' variant="secondary" loading>
Button
</Button>
<Button size='md' variant="warning" loading>
Button
</Button>
<Button size='md' color='orange' loading>
Button
</Button>
</div>
<Separator />
<div style={{ display: 'flex', gap: 20}}>

View File

@ -27,6 +27,7 @@
"next-themes": "^0.2.1",
"oauth4webapi": "^2.10.3",
"react-icons": "^5.0.1",
"react-spinners-kit": "^1.9.1",
"server-only": "^0.0.1",
"styled-components": "^6.1.8",
"tailwindcss-scoped-preflight": "^2.1.0"

View File

@ -5,6 +5,7 @@ import { useDesign } from "../providers/design-provider";
import Color from 'color';
import styled from 'styled-components';
import { BORDER_RADIUS, FONT_FAMILY, FONT_SIZES, LINK_COLORS } from "../utils/constants";
import { PulseSpinner } from "react-spinners-kit";
function getColors({
propsColor,
@ -106,6 +107,7 @@ const StyledButton = styled.button<{
$activeBgColor: string,
$textColor: string,
$underline: boolean,
$loading: boolean,
}>`
border: 0;
border-radius: ${BORDER_RADIUS};
@ -143,6 +145,7 @@ const StyledButton = styled.button<{
}
font-family: ${FONT_FAMILY};
text-decoration: ${props => props.$underline ? 'underline' : 'none'};
position: relative;
`;
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
@ -150,7 +153,6 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
variant='primary',
size='md',
loading=false,
disabled=false,
...props
}, ref) => {
const { colors, colorMode } = useDesign();
@ -168,12 +170,17 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
$bgColor={buttonColors.bgColor}
$hoverBgColor={buttonColors.hoverBgColor}
$activeBgColor={buttonColors.activeBgColor}
$textColor={buttonColors.textColor}
$textColor={buttonColors.textColor}
$underline={variant === 'link'}
disabled={disabled || loading}
$loading={loading}
{...props}
>
{props.children}
<div style={{ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', visibility: loading ? 'visible' : 'hidden' }}>
<PulseSpinner size={20} color={buttonColors.textColor} />
</div>
<div style={{ visibility: loading ? 'hidden' : 'visible' }}>
{props.children}
</div>
</StyledButton>
);
}

View File

@ -14,7 +14,6 @@ function SettingSection(props: {
desc: string,
buttonText?: string,
buttonDisabled?: boolean,
buttonLoading?: boolean,
onButtonClick?: React.ComponentProps<typeof Button>["onClick"],
buttonVariant?: 'primary' | 'secondary',
children?: React.ReactNode,
@ -35,7 +34,6 @@ function SettingSection(props: {
<Button
disabled={props.buttonDisabled}
onClick={props.onButtonClick}
loading={props.buttonLoading}
variant={props.buttonVariant}
>
{props.buttonText}
@ -88,7 +86,6 @@ function ProfileSection() {
function EmailVerificationSection() {
const user = useUser();
const [emailSent, setEmailSent] = useState(false);
const [sendingEmail, setSendingEmail] = useState(false);
return (
<SettingSection
@ -102,12 +99,9 @@ function EmailVerificationSection() {
'Send Email'
: undefined
}
buttonLoading={sendingEmail}
onButtonClick={async () => {
setSendingEmail(true);
await user?.sendVerificationEmail();
setEmailSent(true);
setSendingEmail(false);
}}
>
{user?.primaryEmailVerified ?
@ -123,7 +117,6 @@ function PasswordSection() {
const [oldPasswordError, setOldPasswordError] = useState<string>('');
const [newPassword, setNewPassword] = useState<string>('');
const [newPasswordError, setNewPasswordError] = useState<string>('');
const [saving, setSaving] = useState(false);
if (user?.authMethod !== 'credential') {
return null;
@ -135,7 +128,6 @@ function PasswordSection() {
desc='Change your password'
buttonDisabled={!oldPassword || !newPassword}
buttonText='Save'
buttonLoading={saving}
onButtonClick={async () => {
if (oldPassword && newPassword) {
const errorMessage = getPasswordError(newPassword);

View File

@ -13,7 +13,6 @@ export default function CredentialSignIn() {
const [emailError, setEmailError] = useState('');
const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState('');
const [loading, setLoading] = useState(false);
const app = useStackApp();
const onSubmit = async () => {
@ -30,13 +29,7 @@ export default function CredentialSignIn() {
return;
}
setLoading(true);
let error;
try {
error = await app.signInWithCredential({ email, password });
} finally {
setLoading(false);
}
const error = await app.signInWithCredential({ email, password });
if (error instanceof KnownErrors.EmailPasswordMismatch) {
setPasswordError('Wrong email or password');
@ -79,7 +72,6 @@ export default function CredentialSignIn() {
<Button
style={{ marginTop: '1.5rem' }}
onClick={onSubmit}
loading={loading}
>
Sign In
</Button>

View File

@ -231,6 +231,9 @@ importers:
react-icons:
specifier: ^5.0.1
version: 5.0.1(react@18.2.0)
react-spinners-kit:
specifier: ^1.9.1
version: 1.9.1(styled-components@6.1.8)
server-only:
specifier: ^0.0.1
version: 0.0.1
@ -12286,6 +12289,11 @@ packages:
find-up: 3.0.0
dev: false
/polished@1.9.3:
resolution: {integrity: sha512-4NmSD7fMFlM8roNxs7YXPv7UFRbYzb0gufR5zBxJLRzY54+zFsavxBo6zsQzP9ep6Hh3pC2pTyrpSTBEaB6IkQ==}
deprecated: polished@2.X is no longer supported. Please upgrade to @latest for important bug and security fixes.
dev: false
/postcss-calc@8.2.4(postcss@8.4.35):
resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==}
peerDependencies:
@ -13024,6 +13032,18 @@ packages:
- vue-template-compiler
dev: false
/react-dom@16.14.0(react@16.14.0):
resolution: {integrity: sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==}
peerDependencies:
react: ^16.14.0
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
prop-types: 15.8.1
react: 16.14.0
scheduler: 0.19.1
dev: false
/react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
@ -13243,6 +13263,18 @@ packages:
tiny-warning: 1.0.3
dev: false
/react-spinners-kit@1.9.1(styled-components@6.1.8):
resolution: {integrity: sha512-QtAvSD7b1WkThY3pRKu6Sr+DZafnEufoOvug/uHprkKyZK6bg6TG5LC3Sy3JaRh6A/HACIcTNEWG+Ls0YDoSHg==}
peerDependencies:
styled-components: '>=2.0.0'
dependencies:
polished: 1.9.3
prop-types: 15.8.1
react: 16.14.0
react-dom: 16.14.0(react@16.14.0)
styled-components: 6.1.8(react-dom@18.2.0)(react@18.2.0)
dev: false
/react-style-singleton@2.2.1(@types/react@18.2.66)(react@18.2.0):
resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
engines: {node: '>=10'}
@ -13274,6 +13306,15 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/react@16.14.0:
resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==}
engines: {node: '>=0.10.0'}
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
prop-types: 15.8.1
dev: false
/react@18.2.0:
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
engines: {node: '>=0.10.0'}
@ -13733,6 +13774,13 @@ packages:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: false
/scheduler@0.19.1:
resolution: {integrity: sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==}
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
dev: false
/scheduler@0.23.0:
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
dependencies: