Skip to content

Commit

Permalink
ui: various fixes and enhancements (#2240)
Browse files Browse the repository at this point in the history
* fix: 404 error on stream delete

* fix: display error message for invalid payment card

* api: add email verification check to /reset-token endpoint

* ui: add 30 seconds time limit to password reset request

* tests: fix /reset-password test

* Update user.test.ts

* feat: add sonner package as toaster

* ui: add change password item to profile dropdown

* ui: update toast position

* revert: api changes related to password reset

* run prettier

* fix typo

Co-authored-by: Chase Adams <[email protected]>

* ui: replace toast with snackbar

* ci: run prettier

* Update index.tsx

---------

Co-authored-by: Chase Adams <[email protected]>
  • Loading branch information
suhailkakar and 0xcadams authored Jul 11, 2024
1 parent 00bce20 commit ca62f7f
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 8 deletions.
3 changes: 2 additions & 1 deletion packages/api/src/controllers/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,9 @@ describe("controllers/user", () => {
let req = await client.post(`/user/password/reset-token`, {
email: user.email,
});
expect(req.status).toBe(201);

// should return 201 when user email is valid
expect(req.status).toBe(201);
const tokens = await db.passwordResetToken.find([
sql`password_reset_token.data->>'userId' = ${userRes.id}`,
]);
Expand Down
2 changes: 1 addition & 1 deletion packages/www/components/PaymentMethod/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "react-credit-cards/es/styles-compiled.css";
const PaymentMethod = ({ data }) => {
return (
<Flex>
{data && (
{data?.card && (
<Cards
issuer={data.card.brand}
expiry={
Expand Down
26 changes: 23 additions & 3 deletions packages/www/components/PaymentMethodDialog/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import {
Flex,
Box,
Expand All @@ -11,6 +11,7 @@ import {
AlertDialogTitle,
AlertDialogContent,
AlertDialogCancel,
Alert,
} from "@livepeer/design-system";
import Spinner from "components/Spinner";
import { CardElement, useStripe, useElements } from "@stripe/react-stripe-js";
Expand All @@ -22,6 +23,7 @@ import { useTheme } from "next-themes";
const PaymentMethodDialog = ({ invalidateQuery }) => {
const { user, updateCustomerPaymentMethod } = useApi();
const [status, setStatus] = useState("initial");
const [errorMessage, setErrorMessage] = useState("");
const stripe = useStripe();
const { register, handleSubmit } = useForm();
const elements = useElements();
Expand All @@ -47,14 +49,16 @@ const PaymentMethodDialog = ({ invalidateQuery }) => {
const paymentMethod = result.paymentMethod;
if (result.error) {
setStatus("error");
setErrorMessage(result.error.message);
} else {
updateCustomerPaymentMethod({
stripeCustomerId,
stripeCustomerPaymentMethodId: paymentMethod.id,
})
// If the card is declined, display an error to the user.
.then((result: any) => {
if (result.error) {
if (result.errors) {
setErrorMessage(result.errors?.[0]?.split("\n")[0]);
setStatus("error");
// The card had an error when trying to attach it to a customer.
throw result;
Expand All @@ -64,6 +68,7 @@ const PaymentMethodDialog = ({ invalidateQuery }) => {
.then(onPaymentChangeComplete)
.catch((error) => {
console.log(error);

setStatus("error");
});
}
Expand Down Expand Up @@ -103,6 +108,10 @@ const PaymentMethodDialog = ({ invalidateQuery }) => {
});
};

useEffect(() => {
setErrorMessage("");
}, [open]);

return (
<AlertDialog open={open} onOpenChange={() => setOpen(!open)}>
<Flex css={{ ai: "center" }}>
Expand Down Expand Up @@ -131,7 +140,18 @@ const PaymentMethodDialog = ({ invalidateQuery }) => {
</Heading>
</AlertDialogTitle>

<Box css={{ mt: "$4" }}>
{errorMessage && (
<Alert
css={{
my: "$3",
color: "$red11",
}}
variant={"red"}>
{errorMessage}
</Alert>
)}

<Box css={{ mt: "$2" }}>
<Box css={{ color: "$hiContrast" }}>
<Box>
<Label css={{ mb: "$1", display: "block" }} htmlFor="name">
Expand Down
38 changes: 37 additions & 1 deletion packages/www/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
DropdownMenuTrigger,
DropdownMenuItem,
Button,
useSnackbar,
} from "@livepeer/design-system";
import ThemeSwitch from "../ThemeSwitch";
import Link from "next/link";
Expand All @@ -26,6 +27,7 @@ import {
UsageIcon,
BillingIcon,
} from "./NavIcons";
import { canSendEmail } from "lib/utils/can-send-email";
import { useApi } from "../../hooks";
import Router, { useRouter } from "next/router";
import {
Expand Down Expand Up @@ -170,9 +172,11 @@ const settingsSidebarItems = [

const Sidebar = ({ id }: { id: SidebarId }) => {
const { setProjectId, projectId, appendProjectId } = useProjectContext();
const { createProject, getProjects, logout, user } = useApi();
const { createProject, getProjects, logout, user, makePasswordResetToken } =
useApi();
const queryClient = useQueryClient();
const { pathname } = useRouter();
const [openSnackbar] = useSnackbar();

const [showCreateProjectAlert, setShowCreateProjectAlert] = useState(false);

Expand Down Expand Up @@ -223,6 +227,22 @@ const Sidebar = ({ id }: { id: SidebarId }) => {
return false;
};

const changePassword = async (e) => {
e.preventDefault();
const response = canSendEmail("resetPassword");
if (!response.canSend) {
openSnackbar(
`Please wait ${response.waitTime} seconds before sending another email.`,
);
return;
}
openSnackbar("Password reset link sent to your email.");
const res = await makePasswordResetToken(user.email);
if (res.errors) {
openSnackbar(res?.errors?.[0]);
}
};

return (
<>
<Box
Expand Down Expand Up @@ -356,6 +376,22 @@ const Sidebar = ({ id }: { id: SidebarId }) => {
}}>
<Text size="2">Plans</Text>
</DropdownMenuItem>
<DropdownMenuItem
css={{
py: "$3",
px: "$2",
borderRadius: "$1",
"&:hover": {
transition: ".2s",
bc: "$neutral4",
},
}}
key="changepassword-dropdown-item"
onClick={(e) => {
changePassword(e);
}}>
<Text size="2">Change Password</Text>
</DropdownMenuItem>
<DropdownMenuItem
css={{
py: "$3",
Expand Down
2 changes: 1 addition & 1 deletion packages/www/components/StreamDetails/Delete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const Delete = ({ stream, invalidate, ...props }) => {
e.preventDefault();
setSaving(true);
await deleteStream(stream.id);
Router.replace("/dashboard");
Router.replace("/");
await invalidate();
setSaving(false);
setOpen(false);
Expand Down
36 changes: 36 additions & 0 deletions packages/www/lib/utils/can-send-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Function to check if a user can request reset password or verfication email
*/

export function canSendEmail(emailType: "resetPassword" | "verifyEmail"): {
canSend: boolean;
waitTime: number;
} {
const lastSentKey: string = `lastSent_${emailType}`;
const lastSentTimestamp: string | null = localStorage.getItem(lastSentKey);

if (lastSentTimestamp) {
const currentTime: number = getCurrentTimestamp();
const timeDiff: number = currentTime - parseInt(lastSentTimestamp, 10);

if (timeDiff < 30) {
// If less than 30 seconds have passed, do not allow sending the email
const waitTime: number = 30 - timeDiff;
return {
canSend: false,
waitTime,
};
}
}

const newSentKey: string = `lastSent_${emailType}`;
localStorage.setItem(newSentKey, getCurrentTimestamp().toString());
return {
canSend: true,
waitTime: 0,
};
}

function getCurrentTimestamp(): number {
return Math.floor(Date.now() / 1000);
}
1 change: 0 additions & 1 deletion packages/www/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ const App = ({ Component, pageProps }) => {
}}>
<ProjectProvider>
<SyncProjectId />

<SnackbarProvider>
<QueryClientProvider client={queryClient}>
<ApiProvider>
Expand Down
9 changes: 9 additions & 0 deletions packages/www/pages/forgot-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useState } from "react";
import { useApi, useLoggedIn } from "hooks";
import Link from "next/link";
import { ForgotPassword as Content } from "content";
import { canSendEmail } from "lib/utils/can-send-email";

const ForgotPasswordPage = () => {
useLoggedIn(false);
Expand All @@ -22,6 +23,14 @@ const ForgotPasswordPage = () => {
const onSubmit = async ({ email }) => {
setLoading(true);
setErrors([]);
const response = canSendEmail("resetPassword");
if (!response.canSend) {
setLoading(false);
setErrors([
`Please wait ${response.waitTime} seconds before sending another email.`,
]);
return;
}
const res = await makePasswordResetToken(email);
if (res.errors) {
setLoading(false);
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -22183,6 +22183,11 @@ socks@^2.7.1:
ip-address "^9.0.5"
smart-buffer "^4.2.0"

sonner@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/sonner/-/sonner-1.5.0.tgz#af359f817063318415326b33aab54c5d17c747b7"
integrity sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==

sort-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz"
Expand Down

0 comments on commit ca62f7f

Please sign in to comment.