Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rate limit fe #202

Merged
merged 8 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions web-portal/frontend/components/apps/appRuleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ApprovedChainForm from "./forms/approvedChainsForm";
import AllowedUserAgentsForm from "./forms/allowedUserAgentsForm";
import AllowedOriginsForm from "./forms/allowedOriginsForm";
import _ from "lodash";
import RateLimitForm from "./forms/rateLimitForm";

export default function CreateAppRuleModal() {
const searchParams = useSearchParams();
Expand All @@ -29,6 +30,7 @@ export default function CreateAppRuleModal() {
{shouldOpen === "secret-key" && <SecretKeyForm />}
{shouldOpen === "approved-chains" && <ApprovedChainForm />}
{shouldOpen === "allowed-user-agents" && <AllowedUserAgentsForm />}
{shouldOpen === "rate-limit" && <RateLimitForm />}
</Modal>
);
}
5 changes: 4 additions & 1 deletion web-portal/frontend/components/apps/appRules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ const AppRules: React.FC<{ rule: Partial<IRuleType> }> = ({ rule }) => {
color: "white",
}}
>
{item.value}
{item.value
?.replace("P1D", "Daily")
.replace("P1W", "Weekly")
.replace("P1M", "Monthly")}
</Pill>
));

Expand Down
14 changes: 5 additions & 9 deletions web-portal/frontend/components/apps/forms/allowedOriginsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,14 @@ import _ from "lodash";
import { useForm, matches } from "@mantine/form";
import { IconPlus } from "@tabler/icons-react";
import { useUpdateRuleMutation } from "@frontend/utils/hooks";
import {
useParams,
usePathname,
useRouter,
useSearchParams,
} from "next/navigation";
import { useParams, useSearchParams } from "next/navigation";
import { useAtom, useAtomValue } from "jotai";
import { existingRuleValuesAtom, ruleTypesAtom } from "@frontend/utils/atoms";
import { IRuleType } from "@frontend/utils/types";

export default function AllowedOriginsForm() {
const appId = useParams()?.app as string;
const searchParams = useSearchParams();
const router = useRouter();
const path = usePathname();
const rule = searchParams?.get("rule") as string;
const { mutateAsync, isPending, isSuccess } = useUpdateRuleMutation(
appId,
Expand All @@ -38,6 +31,9 @@ export default function AllowedOriginsForm() {
"Enter a valid url that starts with http or https",
),
},
initialValues: {
url: "",
},
});

const handleValueRemove = (val: string) =>
Expand Down Expand Up @@ -77,7 +73,7 @@ export default function AllowedOriginsForm() {
onClick={() => {
if (formValidation().hasErrors) return;
setValue((current: any) => [form.values.url, ...current]),
form.setFieldValue("url", "");
form.reset();
}}
>
<IconPlus />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default function AllowedUserAgentsForm() {
);
const [value, setValue] = useAtom(existingRuleValuesAtom);
const ruleTypes = useAtomValue(ruleTypesAtom);
const router = useRouter();

const validationRule = _.get(
_.find(ruleTypes, (r: IRuleType) => r.name === rule),
"validationValue",
Expand All @@ -29,6 +29,9 @@ export default function AllowedUserAgentsForm() {
validate: {
userAgent: matches(ruleRegex, "Enter a valid url user agent string"),
},
initialValues: {
userAgent: "",
},
});

const handleValueRemove = (val: string) =>
Expand Down Expand Up @@ -67,7 +70,7 @@ export default function AllowedUserAgentsForm() {
onClick={() => {
if (formValidation().hasErrors) return;
setValue((current: any) => [form.values.userAgent, ...current]),
form.setFieldValue("userAgent", "");
form.reset();
}}
>
<IconPlus />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import { IEndpoint } from "@frontend/utils/types";
import { SearchableMultiSelect } from "@frontend/components/common/SearchableMultiSelect";

import { useUpdateRuleMutation } from "@frontend/utils/hooks";
import { useSearchParams, useParams, useRouter } from "next/navigation";
import { useSearchParams, useParams } from "next/navigation";

export default function ApprovedChainForm() {
const list = useAtomValue(endpointsAtom) as IEndpoint[];
const items = _.map(list, "name");

const router = useRouter();
const appId = useParams()?.app as string;
const searchParams = useSearchParams();
const rule = searchParams?.get("rule") as string;
Expand Down
152 changes: 152 additions & 0 deletions web-portal/frontend/components/apps/forms/rateLimitForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import _ from "lodash";
import { useAtom, useAtomValue } from "jotai";
import { existingRuleValuesAtom, ruleTypesAtom } from "@frontend/utils/atoms";
import { useForm } from "@mantine/form";
import {
Button,
Flex,
Pill,
Stack,
Text,
Select,
NumberInput,
} from "@mantine/core";
import { IconPlus } from "@tabler/icons-react";
import { IRuleType } from "@frontend/utils/types";

import { useUpdateRuleMutation } from "@frontend/utils/hooks";
import { useSearchParams, useParams } from "next/navigation";

const periodOptions = [
{ label: "Weekly", value: "P1W" },
{ label: "Monthly", value: "P1M" },
{ label: "Daily", value: "P1D" },
];

export default function RateLimitForm() {
const appId = useParams()?.app as string;
const searchParams = useSearchParams();
const rule = searchParams?.get("rule") as string;
const { mutateAsync, isPending, isSuccess } = useUpdateRuleMutation(
appId,
rule,
);

const [value, setValue] = useAtom(existingRuleValuesAtom);
const ruleTypes = useAtomValue(ruleTypesAtom);

const validationRule = _.get(
_.find(ruleTypes, (r: IRuleType) => r.name === rule),
"validationValue",
);
const ruleRegex = new RegExp(validationRule as string);
const form = useForm({
validate: {
requests: (val) => {
return _.isInteger(Number(val)) && Number(val) > 0
? null
: "Please enter a valid number";
},
period: (val: string) => {
const isValid = periodOptions.some((option) => {
return option.value === val;
});

const alreadyExists = value.some((v) => v.includes(val));
if (alreadyExists) {
return "Period already exists";
}
return isValid ? null : "Please select a valid period";
},
},
initialValues: {
requests: "",
period: "",
},
});

const handleValueRemove = (val: string) =>
setValue((current) => current.filter((v) => v !== val));

const formValidation = () => form.validate();

const values = value?.map((val) => (
<Pill
key={val}
withRemoveButton
onRemove={() => handleValueRemove(val)}
size="lg"
m={2}
bg="blue"
style={{
color: "white",
}}
>
{val
?.replace("P1D", "Daily")
.replace("P1W", "Weekly")
.replace("P1M", "Monthly")}
</Pill>
));

const handleSubmit = () => {
const isValid = value.map((v) => (ruleRegex.test(v) ? true : false));
isValid.includes(false)
? form.setErrors({ requests: "Invalid value" })
: mutateAsync(value);
};

return (
<Stack>
<Flex dir="row" align="flex-end" gap={4}>
<NumberInput
label="Allowed # of Requests"
placeholder="Enter a valid number"
inputWrapperOrder={["label", "input", "description"]}
style={{ width: "100%" }}
{...form.getInputProps("requests")}
/>
<Select
maw={120}
data={periodOptions}
label="Period"
placeholder="Period"
inputWrapperOrder={["label", "input", "description"]}
{...form.getInputProps("period")}
/>
<Button
h={36}
onClick={() => {
if (formValidation().hasErrors) return;
if (value.some((v) => v.includes(form.values.period))) {
return form.setErrors({ period: "Period already exists" });
}
setValue((current: any) => [
form.values.requests + `/` + form.values.period,
...current,
]),
form.reset();
}}
>
<IconPlus />
</Button>
</Flex>

<Text c="red.6" size="xs">
{form.errors.requests}
<br />
{form.errors.period}
</Text>
<Flex wrap={"wrap"}>{values}</Flex>

<Button
fullWidth
style={{ marginTop: 32 }}
onClick={handleSubmit}
loading={isPending && !isSuccess}
>
Update Rate Limits
</Button>
</Stack>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export function SearchableMultiSelect({
onRemove={() => handleValueRemove(item)}
bg="blue"
c="#fff"
size="lg"
>
{item}
</Pill>
Expand Down
2 changes: 1 addition & 1 deletion web-portal/frontend/components/dashboard/insights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Card,
RingProgress,
} from "@mantine/core";
import React, { useEffect, useState } from "react";
import React from "react";
import classes from "@frontend/styles/insight.module.css";
import { AreaChart } from "@mantine/charts";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
Expand Down
7 changes: 7 additions & 0 deletions web-portal/frontend/components/swap/Redeem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Redeem() {
return (
<div>
<h1>Redeem</h1>
</div>
);
}
74 changes: 74 additions & 0 deletions web-portal/frontend/components/swap/SearchableSelectModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useState } from "react";
import { Modal, TextInput, Text, Flex, Stack } from "@mantine/core";
import _ from "lodash";
import Image from "next/image";
import { IToken } from "@frontend/utils/types";

interface Props {
options: IToken[];
opened: boolean;
onSelect: (option: IToken) => void;
onClose: () => void;
}

export const SearchableSelectModal: React.FC<Props> = ({
options,
onSelect,
opened,
onClose,
}) => {
const [searchValue, setSearchValue] = useState<string | null>(null);

const onChange = (newValue: string) => {
setSearchValue(newValue);
};

const filteredOptions = _.filter(options, (option: IToken) =>
typeof searchValue === "string"
? _.toLower(option.symbol).includes(_.toLower(searchValue))
: options,
);

return (
<Modal
opened={opened}
onClose={onClose}
title="Select token to swap"
centered
>
<TextInput
size="lg"
radius="md"
style={{ marginBottom: 20 }}
placeholder="Search..."
defaultValue={""}
onChange={(event) => onChange(event.target.value)}
title="Select Token to swap"
/>
<Stack>
{filteredOptions.map((option) => (
<Flex
key={_.get(option, "address")}
onClick={() => onSelect(option)}
align="center"
p={8}
>
<Image
src={_.get(option, "logoURI")}
alt="ETH"
width={32}
height={32}
style={{ marginRight: 20, borderRadius: 50 }}
/>
<Stack gap={1}>
<Text style={{ fontWeight: 700 }}>
{_.get(option, "symbol") + ` - ` + _.get(option, "name")}
</Text>
<Text size="sm">{_.get(option, "address")}</Text>
</Stack>
</Flex>
))}
</Stack>
</Modal>
);
};
Loading