Skip to content

Commit

Permalink
Merge pull request #202 from porters-xyz/rate-limit-fe
Browse files Browse the repository at this point in the history
Rate limit fe
  • Loading branch information
wtfsayo committed Apr 22, 2024
2 parents 6afa05b + 236bb6d commit 1e66dbd
Show file tree
Hide file tree
Showing 20 changed files with 628 additions and 21 deletions.
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

0 comments on commit 1e66dbd

Please sign in to comment.