1
0
Fork 0
forked from OpenNeo/impress

Compare commits

..

10 commits

Author SHA1 Message Date
2ca349d5b0 Always convert petName to image hash in loadCustomPetData 2026-01-03 21:07:26 -05:00
067da33025 Replace trades pages with redirects to Classic DTI
The immediate motivation here is that there's slowness rn, and I'm
digging through the slow query log for suspicious targets (hard to tell
when even normal queries are getting slowed down by whatever the
culprit is!), and Impress 2020's constantly-recalcuated trade activity
dates were one of them.

So, rather than update this page to use the new table columns Classic
now uses, I figured, let's Delete Things and reduce surface area. The
re-merge continues!

If we're lucky, this change alone will stop the bursts of slowness. If
not, we'll keep looking for suspicious targets!
2024-09-30 11:49:10 -07:00
35479bf80e Remove filtering colors by prank column, now that we've deleted it 2024-09-27 19:41:52 -07:00
054dd484a5 Ahh beans, don't block the page JS on the analytics script
The analytics service is down rn for reasons I haven't had time to investigate, and oops, using the `defer` attribute *does* defer the script until the page has loaded, but it *also* maintains the ordering.

I could also use `async` here, but I don't know enough about the Plausible script to know if it cares about the DOM being loaded. Instead, I'll keep it the same, but move it after the Next.js script tag.
2024-08-27 11:09:47 -07:00
00d57518f1 Add matchu@deathstar SSH key
I got a new laptop!
2024-08-19 10:52:06 -07:00
c81ffb6ac2 Remove unused @testing-library packages
Huh, guess I wrote some tests with these, then removed them, then never
removed these deps?

Only noticed because deploying failed on them for some reason, saying
the Node version was incompatible… I'm surprised by that, I don't know
when the locked version would have changed, maybe the team uploaded
some new metadata about our older versions and their compatibility
info?

In any case, we don't need the libraries, so *why* their install is
suddenly failing doesn't really matter too much. Goodbye!
2024-04-17 09:43:55 -07:00
37bac38973 Oops, fix list caching when removing items via Impress 2020
I realized this months ago when adding `updated_at` changes to the
other actions around here, and I just forgot this one, and I've had a
sticky on my desk about it since then! Finally done!

Before this change, clicking "I own this" or "I want this" on the item
page, while you own/want the item and it's in a specific list, would
remove the item from the list but *not* update the list's `updated_at`
timestamp. Now, it does!

This affects caching on main DTI, which relies on the list's
`updated_at` to decide when to render new HTML.
2024-04-17 09:37:50 -07:00
242665bd02 Oh wow, don't use the images.neopets.com asset proxy anymore either!
Huh, I was writing up an API inventory doc to send to TNT, and was
gonna explain why we proxy these assets… but turns out we don't need to
anymore! Nice!

This is a bit fragile if they ever change their headers, so I'll
mention that in the doc, but for now, yeah sure let's save the planet
some computational effort!
2024-04-06 03:39:05 -07:00
1617b82b18 Don't use the pets.neopets.com asset proxy anymore
Ah right, now that HTTPS works, we can skip this!
2024-04-06 03:26:36 -07:00
584fed70c6 Oops, touch list updated_at when adding/removing items
Oh right, I forgot that 2020 has endpoints to add/remove items from
lists, so I didn't add logic that corresponds to the `touch: true` we
recently added in the Rails app to power caching!

This created a bug where adding/removing items from lists from the item
page would _not_ cause the cache to update in the Rails app when
sharing your list with others.

In this change, we add `UPDATE` queries to emulate that same logic!
2024-03-09 11:51:00 -08:00
15 changed files with 212 additions and 1197 deletions

View file

@ -14,3 +14,4 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIu5a+mp2KKSGkOGWQPrARCrsqJS4g2vK7TmRIbj/YBh Matchu's Desktop (Leviathan 2023)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKFwWryq6slOQqkrJ7HIig7BvEQVQeH19hFwb+9VpXgz Matchu's Laptop (Ebon Hawk)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINq0HDYIUwRnrlKBWyGWJbJsx3M8nLg4nRxaA+9lJp+o Matchu's Laptop (Death Star)

View file

@ -29,6 +29,17 @@ module.exports = {
destination: "/user/:userId/lists",
permanent: true,
},
{
source: "/items/:itemId/trades/offering",
destination:
"https://impress.openneo.net/items/:itemId/trades/offering",
permanent: true,
},
{
source: "/items/:itemId/trades/seeking",
destination: "https://impress.openneo.net/items/:itemId/trades/seeking",
permanent: true,
},
];
},
};

View file

@ -16,9 +16,6 @@
"@sendgrid/mail": "^7.2.6",
"@sentry/react": "^5.30.0",
"@sentry/tracing": "^5.30.0",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/node": "^14.14.22",
"@types/react": "^18.2.34",
"@types/react-dom": "^17.0.0",

View file

@ -46,12 +46,6 @@ class MyDocument extends Document {
`,
}}
/>
<script
defer
data-domain="impress-2020.openneo.net"
src="https://analytics.openneo.net/js/script.js"
dangerouslySetInnerHTML={{ __html: `` }}
/>
<link
rel="preload"
href="/fonts/Delicious-Heavy.otf"
@ -73,6 +67,12 @@ class MyDocument extends Document {
<body>
<Main />
<NextScript />
<script
defer
data-domain="impress-2020.openneo.net"
src="https://analytics.openneo.net/js/script.js"
dangerouslySetInnerHTML={{ __html: `` }}
/>
</body>
</Html>
);

View file

@ -17,11 +17,8 @@ export async function getValidPetPoses() {
const largestColorIdPromise = getLargestColorId(db);
const distinctPetStatesPromise = getDistinctPetStates(db);
const [
largestSpeciesId,
largestColorId,
distinctPetStates,
] = await Promise.all([
const [largestSpeciesId, largestColorId, distinctPetStates] =
await Promise.all([
largestSpeciesIdPromise,
largestColorIdPromise,
distinctPetStatesPromise,
@ -90,7 +87,7 @@ async function getLargestSpeciesId(db) {
}
async function getLargestColorId(db) {
const [rows] = await db.query(`SELECT max(id) FROM colors WHERE prank = 0`);
const [rows] = await db.query(`SELECT max(id) FROM colors`);
return rows[0]["max(id)"];
}
@ -124,7 +121,7 @@ async function handle(req, res) {
async function handleWithBeeline(req, res) {
beeline.withTrace(
{ name: "api/validPetPoses", operation_name: "api/validPetPoses" },
() => handle(req, res)
() => handle(req, res),
);
}

View file

@ -1,54 +0,0 @@
import { GetServerSideProps } from "next";
import { ItemTradesOfferingPage } from "../../../../src/app/ItemTradesPage";
import { gql, loadGraphqlQuery } from "../../../../src/server/ssr-graphql";
// @ts-ignore doesn't understand module.exports
import { oneDay, oneWeek } from "../../../../src/server/util";
export default function ItemTradesOfferingPageWrapper() {
return <ItemTradesOfferingPage />;
}
export const getServerSideProps: GetServerSideProps = async ({
params,
res,
}) => {
if (params?.itemId == null) {
throw new Error(`assertion error: itemId param is missing`);
}
// Load the most important, most stable item data to get onto the page ASAP.
// We'll cache it real hard, to help it load extra-fast for popular items!
const { errors, graphqlState } = await loadGraphqlQuery({
query: gql`
query ItemsTradesOffering_GetServerSideProps($itemId: ID!) {
item(id: $itemId) {
id
name
thumbnailUrl
description
isNc
isPb
createdAt
}
}
`,
variables: { itemId: params.itemId },
});
if (errors) {
console.warn(
`[SSR: /items/[itemId]/trades/offering] Skipping GraphQL preloading, got errors:`
);
for (const error of errors) {
console.warn(`[SSR: /items/[itemId]/trades/offering]`, error);
}
return { props: { graphqlState: {} } };
}
// Cache this very aggressively, because it's such stable data!
res.setHeader(
"Cache-Control",
`public, s-maxage=${oneDay}, stale-while-revalidate=${oneWeek}`
);
return { props: { graphqlState } };
};

View file

@ -1,54 +0,0 @@
import { GetServerSideProps } from "next";
import { ItemTradesSeekingPage } from "../../../../src/app/ItemTradesPage";
import { gql, loadGraphqlQuery } from "../../../../src/server/ssr-graphql";
// @ts-ignore doesn't understand module.exports
import { oneDay, oneWeek } from "../../../../src/server/util";
export default function ItemTradesSeekingPageWrapper() {
return <ItemTradesSeekingPage />;
}
export const getServerSideProps: GetServerSideProps = async ({
params,
res,
}) => {
if (params?.itemId == null) {
throw new Error(`assertion error: itemId param is missing`);
}
// Load the most important, most stable item data to get onto the page ASAP.
// We'll cache it real hard, to help it load extra-fast for popular items!
const { errors, graphqlState } = await loadGraphqlQuery({
query: gql`
query ItemsTradesSeeking_GetServerSideProps($itemId: ID!) {
item(id: $itemId) {
id
name
thumbnailUrl
description
isNc
isPb
createdAt
}
}
`,
variables: { itemId: params.itemId },
});
if (errors) {
console.warn(
`[SSR: /items/[itemId]/trades/seeking] Skipping GraphQL preloading, got errors:`
);
for (const error of errors) {
console.warn(`[SSR: /items/[itemId]/trades/seeking]`, error);
}
return { props: { graphqlState: {} } };
}
// Cache this very aggressively, because it's such stable data!
res.setHeader(
"Cache-Control",
`public, s-maxage=${oneDay}, stale-while-revalidate=${oneWeek}`
);
return { props: { graphqlState } };
};

View file

@ -92,7 +92,7 @@ export function ItemPageContent({ itemId, isEmbedded = false }) {
}
}
`,
{ variables: { itemId }, returnPartialData: true }
{ variables: { itemId }, returnPartialData: true },
);
if (error) {
@ -302,7 +302,7 @@ const ItemPageOwnWantListsDropdownButton = React.forwardRef(
</Flex>
</Flex>
);
}
},
);
function ItemPageOwnWantListsDropdownContent({ closetLists, item }) {
@ -336,7 +336,7 @@ function ItemPageOwnWantsListsDropdownRow({ closetList, item }) {
}
}
`,
{ context: { sendAuth: true } }
{ context: { sendAuth: true } },
);
const [sendRemoveFromListMutation] = useMutation(
@ -352,7 +352,7 @@ function ItemPageOwnWantsListsDropdownRow({ closetList, item }) {
}
}
`,
{ context: { sendAuth: true } }
{ context: { sendAuth: true } },
);
const onChange = React.useCallback(
@ -397,7 +397,13 @@ function ItemPageOwnWantsListsDropdownRow({ closetList, item }) {
});
}
},
[closetList, item, sendAddToListMutation, sendRemoveFromListMutation, toast]
[
closetList,
item,
sendAddToListMutation,
sendRemoveFromListMutation,
toast,
],
);
return (
@ -445,7 +451,7 @@ function ItemPageOwnButton({ itemId, isChecked }) {
context: { sendAuth: true },
},
],
}
},
);
const [sendRemoveMutation] = useMutation(
@ -476,7 +482,7 @@ function ItemPageOwnButton({ itemId, isChecked }) {
context: { sendAuth: true },
},
],
}
},
);
return (
@ -571,7 +577,7 @@ function ItemPageWantButton({ itemId, isChecked }) {
context: { sendAuth: true },
},
],
}
},
);
const [sendRemoveMutation] = useMutation(
@ -602,7 +608,7 @@ function ItemPageWantButton({ itemId, isChecked }) {
context: { sendAuth: true },
},
],
}
},
);
return (
@ -676,7 +682,7 @@ function ItemPageTradeLinks({ itemId, isEmbedded }) {
}
}
`,
{ variables: { itemId } }
{ variables: { itemId } },
);
if (error) {
@ -690,7 +696,7 @@ function ItemPageTradeLinks({ itemId, isEmbedded }) {
</Box>
<SubtleSkeleton isLoaded={!loading}>
<ItemPageTradeLink
href={`/items/${itemId}/trades/offering`}
href={`https://impress.openneo.net/items/${itemId}/trades/offering`}
count={data?.item?.numUsersOfferingThis || 0}
label="offering"
colorScheme="green"
@ -699,7 +705,7 @@ function ItemPageTradeLinks({ itemId, isEmbedded }) {
</SubtleSkeleton>
<SubtleSkeleton isLoaded={!loading}>
<ItemPageTradeLink
href={`/items/${itemId}/trades/seeking`}
href={`https://impress.openneo.net/items/${itemId}/trades/seeking`}
count={data?.item?.numUsersSeekingThis || 0}
label="seeking"
colorScheme="blue"
@ -769,7 +775,7 @@ function IconCheckbox({ icon, isChecked, ...props }) {
function ItemPageOutfitPreview({ itemId }) {
const idealPose = React.useMemo(
() => (Math.random() > 0.5 ? "HAPPY_FEM" : "HAPPY_MASC"),
[]
[],
);
const [petState, setPetState] = React.useState({
// We'll fill these in once the canonical appearance data arrives.
@ -787,11 +793,11 @@ function ItemPageOutfitPreview({ itemId }) {
});
const [preferredSpeciesId, setPreferredSpeciesId] = useLocalStorage(
"DTIItemPreviewPreferredSpeciesId",
null
null,
);
const [preferredColorId, setPreferredColorId] = useLocalStorage(
"DTIItemPreviewPreferredColorId",
null
null,
);
const setPetStateFromUserAction = React.useCallback(
@ -826,7 +832,7 @@ function ItemPageOutfitPreview({ itemId }) {
return newPetState;
}),
[setPreferredColorId, setPreferredSpeciesId]
[setPreferredColorId, setPreferredSpeciesId],
);
// We don't need to reload this query when preferred species/color change, so
@ -845,7 +851,11 @@ function ItemPageOutfitPreview({ itemId }) {
// query after this loads, because our Apollo cache can't detect the
// shared item appearance. (For standard colors though, our logic to
// cover standard-color switches works for this preloading too.)
const { loading: loadingGQL, error: errorGQL, data } = useQuery(
const {
loading: loadingGQL,
error: errorGQL,
data,
} = useQuery(
gql`
query ItemPageOutfitPreview(
$itemId: ID!
@ -920,7 +930,7 @@ function ItemPageOutfitPreview({ itemId }) {
appearanceId: canonicalPetAppearance?.id,
});
},
}
},
);
const compatibleBodies =
@ -936,7 +946,7 @@ function ItemPageOutfitPreview({ itemId }) {
compatibleBodies.length === 1 &&
!compatibleBodies[0].representsAllBodies &&
(data?.item?.name || "").includes(
data?.item?.canonicalAppearance?.body?.canonicalAppearance?.species?.name
data?.item?.canonicalAppearance?.body?.canonicalAppearance?.species?.name,
);
const couldProbablyModelMoreData = !isProbablySpeciesSpecific;
@ -982,7 +992,7 @@ function ItemPageOutfitPreview({ itemId }) {
appearanceId: null,
});
},
[valids, idealPose, setPetStateFromUserAction]
[valids, idealPose, setPetStateFromUserAction],
);
const borderColor = useColorModeValue("green.700", "green.400");
@ -1201,8 +1211,8 @@ function ExpandOnGroupHover({ children, ...props }) {
// I don't think this is possible, but I'd like to know if it happens!
logAndCapture(
new Error(
`Measurer node not ready during effect. Transition won't be smooth.`
)
`Measurer node not ready during effect. Transition won't be smooth.`,
),
);
return;
}
@ -1283,8 +1293,8 @@ export function ItemZonesInfo({
const sortedZonesAndTheirBodies = [...zoneLabelsAndTheirBodies].sort((a, b) =>
buildSortKeyForZoneLabelsAndTheirBodies(a).localeCompare(
buildSortKeyForZoneLabelsAndTheirBodies(b)
)
buildSortKeyForZoneLabelsAndTheirBodies(b),
),
);
const restrictedZoneLabels = [
@ -1296,8 +1306,8 @@ export function ItemZonesInfo({
// preview available in the list has the zones listed here.
const bodyGroups = new Set(
zoneLabelsAndTheirBodies.map(({ bodies }) =>
bodies.map((b) => b.id).join(",")
)
bodies.map((b) => b.id).join(","),
),
);
const showBodyInfo = bodyGroups.size > 1;

View file

@ -30,7 +30,11 @@ function SpeciesFacesPicker({
// the query all the time, and have Apollo happen to satisfy it fast?
// The semantics of returning our colorful random set could be weird…
const selectedColorIsBasic = colorIsBasic(selectedColorId);
const { loading: loadingGQL, error, data } = useQuery(
const {
loading: loadingGQL,
error,
data,
} = useQuery(
gql`
query SpeciesFacesPicker($selectedColorId: ID!) {
color(id: $selectedColorId) {
@ -52,11 +56,11 @@ function SpeciesFacesPicker({
variables: { selectedColorId },
skip: selectedColorId == null || selectedColorIsBasic,
onError: (e) => console.error(e),
}
},
);
const allBodiesAreCompatible = compatibleBodies.some(
(body) => body.representsAllBodies
(body) => body.representsAllBodies,
);
const compatibleBodyIds = compatibleBodies.map((body) => body.id);
@ -64,7 +68,7 @@ function SpeciesFacesPicker({
const allSpeciesFaces = DEFAULT_SPECIES_FACES.map((defaultSpeciesFace) => {
const providedSpeciesFace = speciesFacesFromData.find(
(f) => f.species.id === defaultSpeciesFace.speciesId
(f) => f.species.id === defaultSpeciesFace.speciesId,
);
if (providedSpeciesFace) {
return {
@ -259,10 +263,10 @@ const SpeciesFaceOption = React.memo(
`}
>
<CrossFadeImage
src={`https://pets.neopets-asset-proxy.openneo.net/cp/${neopetsImageHash}/${emotionId}/1.png`}
src={`https://pets.neopets.com/cp/${neopetsImageHash}/${emotionId}/1.png`}
srcSet={
`https://pets.neopets-asset-proxy.openneo.net/cp/${neopetsImageHash}/${emotionId}/1.png 1x, ` +
`https://pets.neopets-asset-proxy.openneo.net/cp/${neopetsImageHash}/${emotionId}/6.png 2x`
`https://pets.neopets.com/cp/${neopetsImageHash}/${emotionId}/1.png 1x, ` +
`https://pets.neopets.com/cp/${neopetsImageHash}/${emotionId}/6.png 2x`
}
alt={speciesName}
width={55}
@ -320,7 +324,7 @@ const SpeciesFaceOption = React.memo(
)}
</ClassNames>
);
}
},
);
/**

View file

@ -1,517 +0,0 @@
import React from "react";
import { ClassNames } from "@emotion/react";
import {
Box,
Button,
Flex,
Skeleton,
useColorModeValue,
useToken,
} from "@chakra-ui/react";
import gql from "graphql-tag";
import { useQuery } from "@apollo/client";
import Link from "next/link";
import { useRouter } from "next/router";
import { Heading2 } from "./util";
import ItemPageLayout from "./ItemPageLayout";
import useCurrentUser from "./components/useCurrentUser";
import { ChevronDownIcon, ChevronUpIcon } from "@chakra-ui/icons";
import Head from "next/head";
export function ItemTradesOfferingPage() {
return (
<ItemTradesPage
title="Trades: Offering"
userHeading="Owner"
compareColumnLabel="Trade for your…"
tradesQuery={gql`
query ItemTradesTableOffering($itemId: ID!) {
item(id: $itemId) {
id
trades: tradesOffering {
id
user {
id
username
lastTradeActivity
matchingItems: itemsTheyWantThatCurrentUserOwns {
id
name
}
}
closetList {
id
name
}
}
}
}
`}
/>
);
}
export function ItemTradesSeekingPage() {
return (
<ItemTradesPage
title="Trades: Seeking"
userHeading="Seeker"
compareColumnLabel="Trade for their…"
tradesQuery={gql`
query ItemTradesTableSeeking($itemId: ID!) {
item(id: $itemId) {
id
trades: tradesSeeking {
id
user {
id
username
lastTradeActivity
matchingItems: itemsTheyOwnThatCurrentUserWants {
id
name
}
}
closetList {
id
name
}
}
}
}
`}
/>
);
}
function ItemTradesPage({
title,
userHeading,
compareColumnLabel,
tradesQuery,
}) {
const { query } = useRouter();
const { itemId } = query;
const { error, data } = useQuery(
gql`
query ItemTradesPage($itemId: ID!) {
item(id: $itemId) {
id
name
isNc
isPb
thumbnailUrl
description
createdAt
ncTradeValueText
}
}
`,
{ variables: { itemId }, returnPartialData: true }
);
if (error) {
return <Box color="red.400">{error.message}</Box>;
}
return (
<>
<Head>
{data?.item?.name && (
<title>
{data?.item?.name} | {title} | Dress to Impress
</title>
)}
</Head>
<ItemPageLayout item={data?.item}>
<Heading2 marginTop="6" marginBottom="4">
{title}
</Heading2>
<ItemTradesTable
itemId={itemId}
userHeading={userHeading}
compareColumnLabel={compareColumnLabel}
tradesQuery={tradesQuery}
/>
</ItemPageLayout>
</>
);
}
function ItemTradesTable({
itemId,
userHeading,
compareColumnLabel,
tradesQuery,
}) {
const { isLoggedIn } = useCurrentUser();
const { loading, error, data } = useQuery(tradesQuery, {
variables: { itemId },
context: { sendAuth: true },
});
const [isShowingInactiveTrades, setIsShowingInactiveTrades] = React.useState(
false
);
const shouldShowCompareColumn = isLoggedIn;
// We partially randomize trade sorting, but we want it to stay stable across
// re-renders. To do this, we can use `getTradeSortKey`, which will either
// build a new sort key for the trade, or return the cached one from the
// `tradeSortKeys` map.
const tradeSortKeys = React.useMemo(() => new Map(), []);
const getTradeSortKey = (trade) => {
if (!tradeSortKeys.has(trade.id)) {
tradeSortKeys.set(
trade.id,
getVaguelyRandomizedTradeSortKey(
trade.user.lastTradeActivity,
trade.user.matchingItems.length
)
);
}
return tradeSortKeys.get(trade.id);
};
const allTrades = [...(data?.item?.trades || [])];
// Only trades from users active within the last 6 months are shown by
// default. The user can toggle to the full view, though!
const sixMonthsAgo = new Date();
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
const activeTrades = allTrades.filter(
(t) => new Date(t.user.lastTradeActivity) > sixMonthsAgo
);
const trades = isShowingInactiveTrades ? allTrades : activeTrades;
trades.sort((a, b) => getTradeSortKey(b).localeCompare(getTradeSortKey(a)));
const numInactiveTrades = allTrades.length - activeTrades.length;
if (error) {
return <Box color="red.400">{error.message}</Box>;
}
const minorColumnWidth = {
base: shouldShowCompareColumn ? "23%" : "30%",
md: "20ex",
};
return (
<ClassNames>
{({ css }) => (
<Box>
<Box
as="table"
width="100%"
boxShadow="md"
className={css`
/* Chakra doesn't have props for these! */
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
`}
>
<Box as="thead" fontSize={{ base: "xs", sm: "sm" }}>
<Box as="tr">
<ItemTradesTableCell as="th" width={minorColumnWidth}>
{/* A small wording tweak to fit better on the xsmall screens! */}
<Box display={{ base: "none", sm: "block" }}>Last active</Box>
<Box display={{ base: "block", sm: "none" }}>Last edit</Box>
</ItemTradesTableCell>
{shouldShowCompareColumn && (
<ItemTradesTableCell as="th" width={minorColumnWidth}>
<Box display={{ base: "none", sm: "block" }}>
{compareColumnLabel}
</Box>
<Box display={{ base: "block", sm: "none" }}>Matches</Box>
</ItemTradesTableCell>
)}
<ItemTradesTableCell as="th" width={minorColumnWidth}>
{userHeading}
</ItemTradesTableCell>
<ItemTradesTableCell as="th">List</ItemTradesTableCell>
</Box>
</Box>
<Box as="tbody">
{loading && (
<>
<ItemTradesTableRowSkeleton
shouldShowCompareColumn={shouldShowCompareColumn}
/>
<ItemTradesTableRowSkeleton
shouldShowCompareColumn={shouldShowCompareColumn}
/>
<ItemTradesTableRowSkeleton
shouldShowCompareColumn={shouldShowCompareColumn}
/>
<ItemTradesTableRowSkeleton
shouldShowCompareColumn={shouldShowCompareColumn}
/>
<ItemTradesTableRowSkeleton
shouldShowCompareColumn={shouldShowCompareColumn}
/>
</>
)}
{!loading &&
trades.length > 0 &&
trades.map((trade) => (
<ItemTradesTableRow
key={trade.id}
href={`/user/${trade.user.id}/lists#list-${trade.closetList.id}`}
username={trade.user.username}
listName={trade.closetList.name}
lastTradeActivity={trade.user.lastTradeActivity}
matchingItems={trade.user.matchingItems}
shouldShowCompareColumn={shouldShowCompareColumn}
/>
))}
{!loading && trades.length === 0 && (
<Box as="tr">
<ItemTradesTableCell
colSpan={shouldShowCompareColumn ? 4 : 3}
textAlign="center"
fontStyle="italic"
>
No trades yet!
</ItemTradesTableCell>
</Box>
)}
</Box>
</Box>
{numInactiveTrades > 0 && (
<Flex justify="center">
<Button
size="sm"
variant="outline"
marginTop="4"
onClick={() => setIsShowingInactiveTrades((s) => !s)}
>
{isShowingInactiveTrades ? (
<>
<ChevronUpIcon marginRight="2" />
Hide {numInactiveTrades} older trades
<ChevronUpIcon marginLeft="2" />
</>
) : (
<>
<ChevronDownIcon marginRight="2" />
Show {numInactiveTrades} more older trades
<ChevronDownIcon marginLeft="2" />
</>
)}
</Button>
</Flex>
)}
</Box>
)}
</ClassNames>
);
}
function ItemTradesTableRow({
href,
username,
listName,
lastTradeActivity,
matchingItems,
shouldShowCompareColumn,
}) {
const { push: pushHistory } = useRouter();
const onClick = React.useCallback(() => pushHistory(href), [
pushHistory,
href,
]);
const focusBackground = useColorModeValue("gray.100", "gray.600");
const sortedMatchingItems = [...matchingItems].sort((a, b) =>
a.name.localeCompare(b.name)
);
return (
<ClassNames>
{({ css }) => (
<Box
as="tr"
cursor="pointer"
_hover={{ background: focusBackground }}
_focusWithin={{ background: focusBackground }}
onClick={onClick}
>
<ItemTradesTableCell fontSize="xs">
{formatVagueDate(lastTradeActivity)}
</ItemTradesTableCell>
{shouldShowCompareColumn && (
<ItemTradesTableCell fontSize="xs">
{matchingItems.length > 0 ? (
<Box as="ul">
{sortedMatchingItems.slice(0, 4).map((item) => (
<Box key={item.id} as="li">
<Box
lineHeight="1.5"
maxHeight="1.5em"
overflow="hidden"
textOverflow="ellipsis"
whiteSpace="nowrap"
>
{item.name}
</Box>
</Box>
))}
{matchingItems.length > 4 && (
<Box as="li">+ {matchingItems.length - 4} more</Box>
)}
</Box>
) : (
<>
<Box display={{ base: "none", sm: "block" }}>No matches</Box>
<Box display={{ base: "block", sm: "none" }}>None</Box>
</>
)}
</ItemTradesTableCell>
)}
<ItemTradesTableCell fontSize="xs">{username}</ItemTradesTableCell>
<ItemTradesTableCell fontSize="sm">
<Link href={href} passHref>
<Box
as="a"
className={css`
&:hover,
&:focus,
tr:hover &,
tr:focus-within & {
text-decoration: underline;
}
`}
>
{listName}
</Box>
</Link>
</ItemTradesTableCell>
</Box>
)}
</ClassNames>
);
}
function ItemTradesTableRowSkeleton({ shouldShowCompareColumn }) {
return (
<Box as="tr">
<ItemTradesTableCell>
<Skeleton width="100%">X</Skeleton>
</ItemTradesTableCell>
<ItemTradesTableCell>
<Skeleton width="100%">X</Skeleton>
</ItemTradesTableCell>
<ItemTradesTableCell>
<Skeleton width="100%">X</Skeleton>
</ItemTradesTableCell>
{shouldShowCompareColumn && (
<ItemTradesTableCell>
<Skeleton width="100%">X</Skeleton>
</ItemTradesTableCell>
)}
</Box>
);
}
function ItemTradesTableCell({ children, as = "td", ...props }) {
const borderColor = useColorModeValue("gray.300", "gray.400");
const borderColorCss = useToken("colors", borderColor);
const borderRadiusCss = useToken("radii", "md");
return (
<ClassNames>
{({ css }) => (
<Box
as={as}
paddingX="4"
paddingY="2"
textAlign="left"
className={css`
/* Lol sigh, getting this right is way more involved than I wish it
* were. What I really want is border-collapse and a simple 1px border,
* but that disables border-radius. So, we homebrew it by giving all
* cells bottom and right borders, but only the cells on the edges a
* top or left border; and then target the exact 4 corner cells to
* round them. Pretty old-school tbh 🙃 */
border-bottom: 1px solid ${borderColorCss};
border-right: 1px solid ${borderColorCss};
thead tr:first-of-type & {
border-top: 1px solid ${borderColorCss};
}
&:first-of-type {
border-left: 1px solid ${borderColorCss};
}
thead tr:first-of-type &:first-of-type {
border-top-left-radius: ${borderRadiusCss};
}
thead tr:first-of-type &:last-of-type {
border-top-right-radius: ${borderRadiusCss};
}
tbody tr:last-of-type &:first-of-type {
border-bottom-left-radius: ${borderRadiusCss};
}
tbody tr:last-of-type &:last-of-type {
border-bottom-right-radius: ${borderRadiusCss};
}
`}
{...props}
>
{children}
</Box>
)}
</ClassNames>
);
}
function isThisWeek(date) {
const startOfThisWeek = new Date();
startOfThisWeek.setDate(startOfThisWeek.getDate() - 7);
return date > startOfThisWeek;
}
const shortMonthYearFormatter = new Intl.DateTimeFormat("en", {
month: "short",
year: "numeric",
});
function formatVagueDate(dateString) {
const date = new Date(dateString);
if (isThisWeek(date)) {
return "This week";
}
return shortMonthYearFormatter.format(date);
}
function getVaguelyRandomizedTradeSortKey(dateString, numMatchingItems) {
const date = new Date(dateString);
const hasMatchingItems = numMatchingItems >= 1;
// "This week" sorts after all other dates, but with a random factor! I don't
// want people worrying about gaming themselves up to the very top, just be
// active and trust the system 😅 (I figure that, if you care enough to "game"
// the system by faking activity every week, you probably also care enough to
// be... making real trades every week lmao)
//
// We also prioritize having matches, but we don't bother to sort _how many_
// matches, to decrease the power of gaming with large honeypot lists, and
// because it's hard to judge how good matches are anyway.
if (isThisWeek(date)) {
const matchingItemsKey = hasMatchingItems
? "ZZmatchingZZ"
: "AAnotmatchingAA";
return `ZZZthisweekZZZ-${matchingItemsKey}-${Math.random()}`;
}
return dateString;
}

View file

@ -117,7 +117,7 @@ export function useCommonStyles() {
*/
export function safeImageUrl(
urlString,
{ crossOrigin = null, preferArchive = false } = {}
{ crossOrigin = null, preferArchive = false } = {},
) {
if (urlString == null) {
return urlString;
@ -133,13 +133,13 @@ export function safeImageUrl(
// So, we provide "http://images.neopets.com" as the base URL when
// parsing. Most URLs are absolute and will ignore it, but relative URLs
// will resolve relative to that base.
"http://images.neopets.com"
"http://images.neopets.com",
);
} catch (e) {
logAndCapture(
new Error(
`safeImageUrl could not parse URL: ${urlString}. Returning a placeholder.`
)
`safeImageUrl could not parse URL: ${urlString}. Returning a placeholder.`,
),
);
return "https://impress-2020.openneo.net/__error__URL-was-not-parseable__";
}
@ -154,12 +154,16 @@ export function safeImageUrl(
if (preferArchive) {
const archiveUrl = new URL(
`/api/readFromArchive`,
window.location.origin
window.location.origin,
);
archiveUrl.search = new URLSearchParams({ url: url.toString() });
url = archiveUrl;
} else if (crossOrigin) {
url.host = "images.neopets-asset-proxy.openneo.net";
// NOTE: Previously we would rewrite this to our proxy that adds an
// `Access-Control-Allow-Origin` header (images.neopets-asset-proxy.
// openneo.net), but images.neopets.com now includes this header for us!
//
// So, do nothing!
}
} else if (
url.origin === "http://pets.neopets.com" ||
@ -175,8 +179,8 @@ export function safeImageUrl(
logAndCapture(
new Error(
`safeImageUrl was provided an unsafe URL, but we don't know how to ` +
`upgrade it to HTTPS: ${urlString}. Returning a placeholder.`
)
`upgrade it to HTTPS: ${urlString}. Returning a placeholder.`,
),
);
return "https://impress-2020.openneo.net/__error__URL-was-not-HTTPS__";
}
@ -197,11 +201,11 @@ export function safeImageUrl(
export function useDebounce(
value,
delay,
{ waitForFirstPause = false, initialValue = null, forceReset = null } = {}
{ waitForFirstPause = false, initialValue = null, forceReset = null } = {},
) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = React.useState(
waitForFirstPause ? initialValue : value
waitForFirstPause ? initialValue : value,
);
React.useEffect(
@ -218,7 +222,7 @@ export function useDebounce(
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
[value, delay], // Only re-call effect if value or delay changes
);
// The `forceReset` option helps us decide whether to set the value
@ -322,7 +326,7 @@ export function useLocalStorage(key, initialValue) {
console.error(error);
}
},
[key]
[key],
);
const reloadValue = React.useCallback(() => {
@ -349,7 +353,7 @@ export function useLocalStorage(key, initialValue) {
export function loadImage(
rawSrc,
{ crossOrigin = null, preferArchive = false } = {}
{ crossOrigin = null, preferArchive = false } = {},
) {
const src = safeImageUrl(rawSrc, { crossOrigin, preferArchive });
const image = new Image();
@ -403,7 +407,7 @@ export function loadable(load, options) {
// Return a component that renders nothing, while we reload!
return () => null;
}),
options
options,
);
}

View file

@ -33,13 +33,11 @@ export async function loadCustomPetData(petName) {
// prepending "@", which is a special code that can *also* be used in the
// CustomPetService in place of name, to get a pet's appearance from its image
// hash.
if (petName.match(/^[0-9]/)) {
const imageHash = await loadImageHashFromPetName(petName);
console.debug(
`[loadCustomPetData] Converted pet name ${petName} to @${imageHash}`,
);
petName = "@" + imageHash;
}
try {
return neopetsAmfphpCall("CustomPetService.getViewerData", [petName]);

View file

@ -60,7 +60,7 @@ const buildColorLoader = (db) => {
const colorLoader = new DataLoader(async (colorIds) => {
const qs = colorIds.map((_) => "?").join(",");
const [rows] = await db.execute(
`SELECT * FROM colors WHERE id IN (${qs}) AND prank = 0`,
`SELECT * FROM colors WHERE id IN (${qs})`,
colorIds,
);
@ -75,7 +75,7 @@ const buildColorLoader = (db) => {
});
colorLoader.loadAll = async () => {
const [rows] = await db.execute(`SELECT * FROM colors WHERE prank = 0`);
const [rows] = await db.execute(`SELECT * FROM colors`);
const entities = rows.map(normalizeRow);
for (const color of entities) {

View file

@ -1018,12 +1018,45 @@ const resolvers = {
return null;
}
await db.query(
const connection = await db.getConnection();
try {
// Get the relevant lists this is currently in.
await connection.beginTransaction();
const [rows] = await connection.query(
`SELECT DISTINCT list_id FROM closet_hangers
WHERE item_id = ? AND user_id = ? AND owned = ? AND
list_id IS NOT NULL`,
[itemId, currentUserId, true],
);
// Mark all these lists as updated.
const listIds = rows.map((row) => row.list_id);
const qs = listIds.map((_) => "?");
const now = new Date();
await connection.query(
`UPDATE closet_lists SET updated_at = ? WHERE id IN (${qs})`,
[now, ...listIds],
);
// Delete all these hangers. (NOTE: This includes ones not in a list!)
await connection.query(
`DELETE FROM closet_hangers
WHERE item_id = ? AND user_id = ? AND owned = ?;`,
[itemId, currentUserId, true],
);
await connection.commit();
} catch (error) {
try {
await connection.rollback();
} catch (error2) {
console.warn(`Error rolling back transaction`, error2);
}
throw error;
} finally {
await connection.release();
}
return { id: itemId };
},
addToItemsCurrentUserWants: async (
@ -1083,12 +1116,45 @@ const resolvers = {
return null;
}
await db.query(
const connection = await db.getConnection();
try {
// Get the relevant lists this is currently in.
await connection.beginTransaction();
const [rows] = await connection.query(
`SELECT DISTINCT list_id FROM closet_hangers
WHERE item_id = ? AND user_id = ? AND owned = ? AND
list_id IS NOT NULL`,
[itemId, currentUserId, false],
);
// Mark all these lists as updated.
const listIds = rows.map((row) => row.list_id);
const qs = listIds.map((_) => "?");
const now = new Date();
await connection.query(
`UPDATE closet_lists SET updated_at = ? WHERE id IN (${qs})`,
[now, ...listIds],
);
// Delete all these hangers. (NOTE: This includes ones not in a list!)
await connection.query(
`DELETE FROM closet_hangers
WHERE item_id = ? AND user_id = ? AND owned = ?;`,
[itemId, currentUserId, false],
);
await connection.commit();
} catch (error) {
try {
await connection.rollback();
} catch (error2) {
console.warn(`Error rolling back transaction`, error2);
}
throw error;
} finally {
await connection.release();
}
return { id: itemId };
},
addItemToClosetList: async (
@ -1138,6 +1204,14 @@ const resolvers = {
[itemId, userId, ownsOrWantsItems === "OWNS", listId, 1, now, now],
);
// Finally, touch the `updated_at` timestamp for the list.
await connection.query(
`
UPDATE closet_lists SET updated_at = ? WHERE id = ? LIMIT 1;
`,
[now, listId],
);
await connection.commit();
} catch (error) {
try {
@ -1182,6 +1256,9 @@ const resolvers = {
const connection = await db.getConnection();
try {
await connection.beginTransaction();
const now = new Date();
// First, remove the hanger from the list.
await connection.query(
`
DELETE FROM closet_hangers
@ -1190,6 +1267,16 @@ const resolvers = {
[...listMatcherValues, itemId],
);
// Then, touch the `updated_at` timestamp for the list.
if (!closetListRef.isDefaultList) {
await connection.query(
`
UPDATE closet_lists SET updated_at = ? WHERE id = ? LIMIT 1;
`,
[now, closetListRef.id],
);
}
if (ensureInSomeList) {
// If requested, we check whether the item is still in *some* list of
// the same own/want type. If not, we add it to the default list.
@ -1202,7 +1289,6 @@ const resolvers = {
);
if (rows[0].count === 0) {
const now = new Date();
await connection.query(
`
INSERT INTO closet_hangers
@ -1221,6 +1307,8 @@ const resolvers = {
} catch (error) {
console.warn(`Error rolling back transaction`, error);
}
throw error;
} finally {
await connection.release();
}

498
yarn.lock
View file

@ -832,7 +832,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.22.13":
"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.22.13":
version: 7.22.13
resolution: "@babel/code-frame@npm:7.22.13"
dependencies:
@ -2211,17 +2211,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime-corejs3@npm:^7.10.2":
version: 7.23.2
resolution: "@babel/runtime-corejs3@npm:7.23.2"
dependencies:
core-js-pure: "npm:^3.30.2"
regenerator-runtime: "npm:^0.14.0"
checksum: 1362f04cae16d99175961e4113618e5ae210e17053605d4cd2c7b93b3a0334fcfe6a689601d20c12f8946cd8a472430e25f0bf09b7dcd851f63fd82749fd7503
languageName: node
linkType: hard
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.5.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.7, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.7, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
version: 7.23.2
resolution: "@babel/runtime@npm:7.23.2"
dependencies:
@ -3370,29 +3360,6 @@ __metadata:
languageName: node
linkType: hard
"@jest/types@npm:^24.9.0":
version: 24.9.0
resolution: "@jest/types@npm:24.9.0"
dependencies:
"@types/istanbul-lib-coverage": "npm:^2.0.0"
"@types/istanbul-reports": "npm:^1.1.1"
"@types/yargs": "npm:^13.0.0"
checksum: 990b03f5e27de292a7fea6b12cd87256dd281263afe37020cad5dceb0b775945a528bafdbc2e41bf8a29c346f94a7aa5580517c5c65a2b33f245f43d3b9b4694
languageName: node
linkType: hard
"@jest/types@npm:^25.5.0":
version: 25.5.0
resolution: "@jest/types@npm:25.5.0"
dependencies:
"@types/istanbul-lib-coverage": "npm:^2.0.0"
"@types/istanbul-reports": "npm:^1.1.1"
"@types/yargs": "npm:^15.0.0"
chalk: "npm:^3.0.0"
checksum: f47c6e98c99d3fd562f2be6c339f41d3c7092e9587b8524fe71411f9c8b8e71f50475278a10e534f56c729ccd3e3b55e3aa20e4b0a2c5c47ded1ba53e0aef286
languageName: node
linkType: hard
"@jimp/bmp@npm:^0.14.0":
version: 0.14.0
resolution: "@jimp/bmp@npm:0.14.0"
@ -4339,13 +4306,6 @@ __metadata:
languageName: node
linkType: hard
"@sheerun/mutationobserver-shim@npm:^0.3.2":
version: 0.3.3
resolution: "@sheerun/mutationobserver-shim@npm:0.3.3"
checksum: 14e7e53805a4320577d88f44fd786b4f226be314e44e97a2889bc01fc05e72ce1faa3f3e27720b9b9a2e0e4bf2e3fe1864df9ac35aac52ca2c29a49c5ac5521e
languageName: node
linkType: hard
"@smithy/abort-controller@npm:^2.0.12":
version: 2.0.12
resolution: "@smithy/abort-controller@npm:2.0.12"
@ -4900,77 +4860,6 @@ __metadata:
languageName: node
linkType: hard
"@testing-library/dom@npm:*":
version: 9.3.3
resolution: "@testing-library/dom@npm:9.3.3"
dependencies:
"@babel/code-frame": "npm:^7.10.4"
"@babel/runtime": "npm:^7.12.5"
"@types/aria-query": "npm:^5.0.1"
aria-query: "npm:5.1.3"
chalk: "npm:^4.1.0"
dom-accessibility-api: "npm:^0.5.9"
lz-string: "npm:^1.5.0"
pretty-format: "npm:^27.0.2"
checksum: c3bbd67503634fd955233dc172531640656701fe35ecb9a83f85e5965874b786452f5e7c26b4f8b3b4fc4379f3a80193c74425b57843ba191f4845e22b0ac483
languageName: node
linkType: hard
"@testing-library/dom@npm:^6.15.0":
version: 6.16.0
resolution: "@testing-library/dom@npm:6.16.0"
dependencies:
"@babel/runtime": "npm:^7.8.4"
"@sheerun/mutationobserver-shim": "npm:^0.3.2"
"@types/testing-library__dom": "npm:^6.12.1"
aria-query: "npm:^4.0.2"
dom-accessibility-api: "npm:^0.3.0"
pretty-format: "npm:^25.1.0"
wait-for-expect: "npm:^3.0.2"
checksum: 9ae7087cd2aa64eb84950512b532bb5cff0797ece26692564910141a879884119c6680cfa5c328a25edb79d38f2b2d98647f3023d897da1abbd71f069b196ed0
languageName: node
linkType: hard
"@testing-library/jest-dom@npm:^4.2.4":
version: 4.2.4
resolution: "@testing-library/jest-dom@npm:4.2.4"
dependencies:
"@babel/runtime": "npm:^7.5.1"
chalk: "npm:^2.4.1"
css: "npm:^2.2.3"
css.escape: "npm:^1.5.1"
jest-diff: "npm:^24.0.0"
jest-matcher-utils: "npm:^24.0.0"
lodash: "npm:^4.17.11"
pretty-format: "npm:^24.0.0"
redent: "npm:^3.0.0"
checksum: b91c91bc736e7d93f062b9597961e8a5a6a3a909ff961db4fcb0dfa03170939f19d2d6bda231c500269ccbe21e910470e7955670e69614621a91bf2e1918daed
languageName: node
linkType: hard
"@testing-library/react@npm:^9.3.2":
version: 9.5.0
resolution: "@testing-library/react@npm:9.5.0"
dependencies:
"@babel/runtime": "npm:^7.8.4"
"@testing-library/dom": "npm:^6.15.0"
"@types/testing-library__react": "npm:^9.1.2"
peerDependencies:
react: "*"
react-dom: "*"
checksum: 37e0ba5e7a9112f5a59d4ebddf84b198ff04ad9bb0d78e8a74604ac87d997fd4092991af6a1871ee2fc07cb40b2b1bccf3a8bcd3d357cbf0355527bd4b80cfc1
languageName: node
linkType: hard
"@testing-library/user-event@npm:^7.1.2":
version: 7.2.1
resolution: "@testing-library/user-event@npm:7.2.1"
peerDependencies:
"@testing-library/dom": ">=5"
checksum: cb02ec4d02beaef21f7e770a3fa93d8385e68d767ccc42ff719de60ccf80a80359d7baa857322071e459bbb8a7515922d03b0464298cec9fc953bf287f0c2760
languageName: node
linkType: hard
"@tootallnate/once@npm:1":
version: 1.1.2
resolution: "@tootallnate/once@npm:1.1.2"
@ -4987,13 +4876,6 @@ __metadata:
languageName: node
linkType: hard
"@types/aria-query@npm:^5.0.1":
version: 5.0.3
resolution: "@types/aria-query@npm:5.0.3"
checksum: 5b82fab31fc6a1d51d36a1f7a91fd78dfbb4c47c6c8da65c712d8d3bf24208e81f26850763ced7e671b54a5c21252fbc15ebb74bed135faa0cfc4ee746375de4
languageName: node
linkType: hard
"@types/body-parser@npm:*":
version: 1.19.4
resolution: "@types/body-parser@npm:1.19.4"
@ -5115,32 +4997,6 @@ __metadata:
languageName: node
linkType: hard
"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0":
version: 2.0.5
resolution: "@types/istanbul-lib-coverage@npm:2.0.5"
checksum: e15cfc01a7ac60062f771314c959011bae7de7ceaef8e294f13427a11f21741cbfac98ad8cd9ecbf0e3d72ab7ddc327bacb3fab32c6b26ab19dbbbc1a69a9d3b
languageName: node
linkType: hard
"@types/istanbul-lib-report@npm:*":
version: 3.0.2
resolution: "@types/istanbul-lib-report@npm:3.0.2"
dependencies:
"@types/istanbul-lib-coverage": "npm:*"
checksum: c168e425c95c167d83c7cbd65ff6b620cc53c5ef199a58428758586bbc28faf5c51291667e4455777b47ada12381e53fce7b92e32f431f85d8ac8025074d1908
languageName: node
linkType: hard
"@types/istanbul-reports@npm:^1.1.1":
version: 1.1.2
resolution: "@types/istanbul-reports@npm:1.1.2"
dependencies:
"@types/istanbul-lib-coverage": "npm:*"
"@types/istanbul-lib-report": "npm:*"
checksum: 80b76715f4ac74a4ddfc82d7942b2faaefbe9fdce8e7dfdfa497b3fb60a3e707b632c6e70e1565cfe30045eaebaf7aad0d6c3d102652d1da8fdb0bf095924eb3
languageName: node
linkType: hard
"@types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9":
version: 7.0.14
resolution: "@types/json-schema@npm:7.0.14"
@ -5296,15 +5152,6 @@ __metadata:
languageName: node
linkType: hard
"@types/react-dom@npm:*":
version: 18.2.14
resolution: "@types/react-dom@npm:18.2.14"
dependencies:
"@types/react": "npm:*"
checksum: 1f79a7708d038cd651bdb21e01a99c594761bc9a40a565abe98958e1d27facfeb6e9824ddf6ae3504e7a56568f0f3da2380fe52ac18477b5864d2d5cf1386a9e
languageName: node
linkType: hard
"@types/react-dom@npm:^17.0.0":
version: 17.0.22
resolution: "@types/react-dom@npm:17.0.22"
@ -5314,7 +5161,7 @@ __metadata:
languageName: node
linkType: hard
"@types/react@npm:*, @types/react@npm:>=16.0.0, @types/react@npm:^18.2.34":
"@types/react@npm:>=16.0.0, @types/react@npm:^18.2.34":
version: 18.2.34
resolution: "@types/react@npm:18.2.34"
dependencies:
@ -5371,35 +5218,6 @@ __metadata:
languageName: node
linkType: hard
"@types/testing-library__dom@npm:*":
version: 7.5.0
resolution: "@types/testing-library__dom@npm:7.5.0"
dependencies:
"@testing-library/dom": "npm:*"
checksum: 24da773a611764958ca2f124d6b8ceea3781ac40c7b71869f36132491fe760fc2731f5ebf06a7052f95e53ea9c5d6f77270732f89de163e0c1423bc590a353eb
languageName: node
linkType: hard
"@types/testing-library__dom@npm:^6.12.1":
version: 6.14.0
resolution: "@types/testing-library__dom@npm:6.14.0"
dependencies:
pretty-format: "npm:^24.3.0"
checksum: 586f74496801eb1ba50f4b2aa357eab3eb97ca91b068c9f6239f302792b7cae984d38166c5c3eee130bfa60cfba3c596b9f0226672346c1bacbe86152e1d91db
languageName: node
linkType: hard
"@types/testing-library__react@npm:^9.1.2":
version: 9.1.3
resolution: "@types/testing-library__react@npm:9.1.3"
dependencies:
"@types/react-dom": "npm:*"
"@types/testing-library__dom": "npm:*"
pretty-format: "npm:^25.1.0"
checksum: 28403de3f09e44fbf34ea4dd0cf6c7ba285f5eb75e00ac8ddaf4460593d1ae6707049b36b500f1eecc80f936b10114745dac623e5dd10fab57ec9a1889184b0d
languageName: node
linkType: hard
"@types/warning@npm:^3.0.0":
version: 3.0.2
resolution: "@types/warning@npm:3.0.2"
@ -5416,31 +5234,6 @@ __metadata:
languageName: node
linkType: hard
"@types/yargs-parser@npm:*":
version: 21.0.2
resolution: "@types/yargs-parser@npm:21.0.2"
checksum: 422b8c59e21d9594e5a94afa45a3692d96c14f8fc7554bb1c1c390276815f09996ce0f8ed11893b6f8b2efc4ced686231dca5be6d76a4c4ceb56534474e95aca
languageName: node
linkType: hard
"@types/yargs@npm:^13.0.0":
version: 13.0.12
resolution: "@types/yargs@npm:13.0.12"
dependencies:
"@types/yargs-parser": "npm:*"
checksum: 81fdac6832d69f2f2a33bb3d77887f571677d5a9ccfd5a171ff3e76252a6c6a9773850a0df6ba9ed0328433a36596488ec4e2ce5d9bc49d713a59bbfef8e12a0
languageName: node
linkType: hard
"@types/yargs@npm:^15.0.0":
version: 15.0.17
resolution: "@types/yargs@npm:15.0.17"
dependencies:
"@types/yargs-parser": "npm:*"
checksum: ddb97bae547f02eefc04353b1df6f2928ca81ae86d5e163965190f7b359342bde5392a0b1866b16ceb96d1c17ebfa7a6bf9faf165744c23635d8858668567b0a
languageName: node
linkType: hard
"@types/yauzl@npm:^2.9.1":
version: 2.10.2
resolution: "@types/yauzl@npm:2.10.2"
@ -5818,14 +5611,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-regex@npm:^4.0.0":
version: 4.1.1
resolution: "ansi-regex@npm:4.1.1"
checksum: d36d34234d077e8770169d980fed7b2f3724bfa2a01da150ccd75ef9707c80e883d27cdf7a0eac2f145ac1d10a785a8a855cffd05b85f778629a0db62e7033da
languageName: node
linkType: hard
"ansi-regex@npm:^5.0.0, ansi-regex@npm:^5.0.1":
"ansi-regex@npm:^5.0.1":
version: 5.0.1
resolution: "ansi-regex@npm:5.0.1"
checksum: 9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737
@ -5846,7 +5632,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-styles@npm:^3.2.0, ansi-styles@npm:^3.2.1":
"ansi-styles@npm:^3.2.1":
version: 3.2.1
resolution: "ansi-styles@npm:3.2.1"
dependencies:
@ -5864,13 +5650,6 @@ __metadata:
languageName: node
linkType: hard
"ansi-styles@npm:^5.0.0":
version: 5.2.0
resolution: "ansi-styles@npm:5.2.0"
checksum: 9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df
languageName: node
linkType: hard
"ansi-styles@npm:^6.1.0":
version: 6.2.1
resolution: "ansi-styles@npm:6.2.1"
@ -6164,25 +5943,6 @@ __metadata:
languageName: node
linkType: hard
"aria-query@npm:5.1.3":
version: 5.1.3
resolution: "aria-query@npm:5.1.3"
dependencies:
deep-equal: "npm:^2.0.5"
checksum: edcbc8044c4663d6f88f785e983e6784f98cb62b4ba1e9dd8d61b725d0203e4cfca38d676aee984c31f354103461102a3d583aa4fbe4fd0a89b679744f4e5faf
languageName: node
linkType: hard
"aria-query@npm:^4.0.2":
version: 4.2.2
resolution: "aria-query@npm:4.2.2"
dependencies:
"@babel/runtime": "npm:^7.10.2"
"@babel/runtime-corejs3": "npm:^7.10.2"
checksum: 7e224fbbb4de8210c5d8cbaf0e1a22caa78f2068bf231f4c75302bd77eeba1c3e3b97912080535140be60174720d2ac817e5d6fec18592951b4b6488d4da7cdc
languageName: node
linkType: hard
"aria-query@npm:^5.3.0":
version: 5.3.0
resolution: "aria-query@npm:5.3.0"
@ -6913,7 +6673,7 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^2.0.1, chalk@npm:^2.4.1, chalk@npm:^2.4.2":
"chalk@npm:^2.4.2":
version: 2.4.2
resolution: "chalk@npm:2.4.2"
dependencies:
@ -6924,16 +6684,6 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^3.0.0":
version: 3.0.0
resolution: "chalk@npm:3.0.0"
dependencies:
ansi-styles: "npm:^4.1.0"
supports-color: "npm:^7.1.0"
checksum: ee650b0a065b3d7a6fda258e75d3a86fc8e4effa55871da730a9e42ccb035bf5fd203525e5a1ef45ec2582ecc4f65b47eb11357c526b84dd29a14fb162c414d2
languageName: node
linkType: hard
"chalk@npm:^4.0.0, chalk@npm:^4.1.0":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
@ -7303,7 +7053,7 @@ __metadata:
languageName: node
linkType: hard
"core-js-pure@npm:^3.10.2, core-js-pure@npm:^3.30.2":
"core-js-pure@npm:^3.10.2":
version: 3.33.2
resolution: "core-js-pure@npm:3.33.2"
checksum: 9de1cc6e64371c1b48d547a75840472a2c39277dbe3dd74adc4c172f05f078218ce69e42e30f663d26a94a181e761325141028c2c0a1d452c8e4a383befa2e25
@ -7381,25 +7131,6 @@ __metadata:
languageName: node
linkType: hard
"css.escape@npm:^1.5.1":
version: 1.5.1
resolution: "css.escape@npm:1.5.1"
checksum: 5e09035e5bf6c2c422b40c6df2eb1529657a17df37fda5d0433d722609527ab98090baf25b13970ca754079a0f3161dd3dfc0e743563ded8cfa0749d861c1525
languageName: node
linkType: hard
"css@npm:^2.2.3":
version: 2.2.4
resolution: "css@npm:2.2.4"
dependencies:
inherits: "npm:^2.0.3"
source-map: "npm:^0.6.1"
source-map-resolve: "npm:^0.5.2"
urix: "npm:^0.1.0"
checksum: 496fa66568ebd9e51b3153817dd36ec004a45780da6f818e13117e3c4e50b774af41fff70a6ff2fa03777b239c4028ff655fe571b20964b90e886441cd141569
languageName: node
linkType: hard
"cssfilter@npm:0.0.10":
version: 0.0.10
resolution: "cssfilter@npm:0.0.10"
@ -7516,32 +7247,6 @@ __metadata:
languageName: node
linkType: hard
"deep-equal@npm:^2.0.5":
version: 2.2.2
resolution: "deep-equal@npm:2.2.2"
dependencies:
array-buffer-byte-length: "npm:^1.0.0"
call-bind: "npm:^1.0.2"
es-get-iterator: "npm:^1.1.3"
get-intrinsic: "npm:^1.2.1"
is-arguments: "npm:^1.1.1"
is-array-buffer: "npm:^3.0.2"
is-date-object: "npm:^1.0.5"
is-regex: "npm:^1.1.4"
is-shared-array-buffer: "npm:^1.0.2"
isarray: "npm:^2.0.5"
object-is: "npm:^1.1.5"
object-keys: "npm:^1.1.1"
object.assign: "npm:^4.1.4"
regexp.prototype.flags: "npm:^1.5.0"
side-channel: "npm:^1.0.4"
which-boxed-primitive: "npm:^1.0.2"
which-collection: "npm:^1.0.1"
which-typed-array: "npm:^1.1.9"
checksum: 07b46a9a848efdab223abc7e3ba612ef9168d88970c3400df185d5840a30ca384749c996ae5d7af844d6b27c42587fb73a4445c63e38aac77c2d0ed9a63faa87
languageName: node
linkType: hard
"deep-extend@npm:^0.6.0":
version: 0.6.0
resolution: "deep-extend@npm:0.6.0"
@ -7737,13 +7442,6 @@ __metadata:
languageName: node
linkType: hard
"diff-sequences@npm:^24.9.0":
version: 24.9.0
resolution: "diff-sequences@npm:24.9.0"
checksum: c7c6cec09502e8266fa499e5b1f359349529b4019135b6a6ae4441a7f48bd518b286d33255376a47e9e970c78527355d0ca3f58d01d6513f6b565283d56600b9
languageName: node
linkType: hard
"diff@npm:^4.0.1":
version: 4.0.2
resolution: "diff@npm:4.0.2"
@ -7778,20 +7476,6 @@ __metadata:
languageName: node
linkType: hard
"dom-accessibility-api@npm:^0.3.0":
version: 0.3.0
resolution: "dom-accessibility-api@npm:0.3.0"
checksum: c6d44a4d0ce2bb24308a57c805bbfaf25ba415bc5a243a18eb6cd2dbb2024fd2dd04b6222c50c24df1a0e1d29d65fca72926325cbde6c08c937766a1965f3ad6
languageName: node
linkType: hard
"dom-accessibility-api@npm:^0.5.9":
version: 0.5.16
resolution: "dom-accessibility-api@npm:0.5.16"
checksum: b2c2eda4fae568977cdac27a9f0c001edf4f95a6a6191dfa611e3721db2478d1badc01db5bb4fa8a848aeee13e442a6c2a4386d65ec65a1436f24715a2f8d053
languageName: node
linkType: hard
"dom-helpers@npm:^5.0.1, dom-helpers@npm:^5.1.3":
version: 5.2.1
resolution: "dom-helpers@npm:5.2.1"
@ -8030,23 +7714,6 @@ __metadata:
languageName: node
linkType: hard
"es-get-iterator@npm:^1.1.3":
version: 1.1.3
resolution: "es-get-iterator@npm:1.1.3"
dependencies:
call-bind: "npm:^1.0.2"
get-intrinsic: "npm:^1.1.3"
has-symbols: "npm:^1.0.3"
is-arguments: "npm:^1.1.1"
is-map: "npm:^2.0.2"
is-set: "npm:^2.0.2"
is-string: "npm:^1.0.7"
isarray: "npm:^2.0.5"
stop-iteration-iterator: "npm:^1.0.0"
checksum: ebd11effa79851ea75d7f079405f9d0dc185559fd65d986c6afea59a0ff2d46c2ed8675f19f03dce7429d7f6c14ff9aede8d121fbab78d75cfda6a263030bac0
languageName: node
linkType: hard
"es-iterator-helpers@npm:^1.0.12, es-iterator-helpers@npm:^1.0.15":
version: 1.0.15
resolution: "es-iterator-helpers@npm:1.0.15"
@ -9792,9 +9459,6 @@ __metadata:
"@sendgrid/mail": "npm:^7.2.6"
"@sentry/react": "npm:^5.30.0"
"@sentry/tracing": "npm:^5.30.0"
"@testing-library/jest-dom": "npm:^4.2.4"
"@testing-library/react": "npm:^9.3.2"
"@testing-library/user-event": "npm:^7.1.2"
"@types/node": "npm:^14.14.22"
"@types/react": "npm:^18.2.34"
"@types/react-dom": "npm:^17.0.0"
@ -9913,7 +9577,7 @@ __metadata:
languageName: node
linkType: hard
"internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.5":
"internal-slot@npm:^1.0.5":
version: 1.0.6
resolution: "internal-slot@npm:1.0.6"
dependencies:
@ -9963,7 +9627,7 @@ __metadata:
languageName: node
linkType: hard
"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1":
"is-arguments@npm:^1.0.4":
version: 1.1.1
resolution: "is-arguments@npm:1.1.1"
dependencies:
@ -10167,7 +9831,7 @@ __metadata:
languageName: node
linkType: hard
"is-map@npm:^2.0.1, is-map@npm:^2.0.2":
"is-map@npm:^2.0.1":
version: 2.0.2
resolution: "is-map@npm:2.0.2"
checksum: 119ff9137a37fd131a72fab3f4ab8c9d6a24b0a1ee26b4eff14dc625900d8675a97785eea5f4174265e2006ed076cc24e89f6e57ebd080a48338d914ec9168a5
@ -10246,7 +9910,7 @@ __metadata:
languageName: node
linkType: hard
"is-set@npm:^2.0.1, is-set@npm:^2.0.2":
"is-set@npm:^2.0.1":
version: 2.0.2
resolution: "is-set@npm:2.0.2"
checksum: 5f8bd1880df8c0004ce694e315e6e1e47a3452014be792880bb274a3b2cdb952fdb60789636ca6e084c7947ca8b7ae03ccaf54c93a7fcfed228af810559e5432
@ -10429,25 +10093,6 @@ __metadata:
languageName: node
linkType: hard
"jest-diff@npm:^24.0.0, jest-diff@npm:^24.9.0":
version: 24.9.0
resolution: "jest-diff@npm:24.9.0"
dependencies:
chalk: "npm:^2.0.1"
diff-sequences: "npm:^24.9.0"
jest-get-type: "npm:^24.9.0"
pretty-format: "npm:^24.9.0"
checksum: de8f57a6532d95f325478bb963507e055c962fb1255e4c0c3610853c729994a690fe7ec04bf18c5dd922ced6ae0e8e251910909b77d426e6fda96940f10f4f8e
languageName: node
linkType: hard
"jest-get-type@npm:^24.9.0":
version: 24.9.0
resolution: "jest-get-type@npm:24.9.0"
checksum: af1da287a14e5de5888b0114e92cd4042050852d32982d412e1465a8d69cb0a22702c7c491c56eb664e05a1391c1d6eeeb840e249a76d4f6159c402a4dfde56d
languageName: node
linkType: hard
"jest-image-snapshot@npm:^4.3.0":
version: 4.5.1
resolution: "jest-image-snapshot@npm:4.5.1"
@ -10467,18 +10112,6 @@ __metadata:
languageName: node
linkType: hard
"jest-matcher-utils@npm:^24.0.0":
version: 24.9.0
resolution: "jest-matcher-utils@npm:24.9.0"
dependencies:
chalk: "npm:^2.0.1"
jest-diff: "npm:^24.9.0"
jest-get-type: "npm:^24.9.0"
pretty-format: "npm:^24.9.0"
checksum: f5cd624d22d77a105267cf6c50bec0dcf2627ccd385d461e8cf6a0a8a97ca8ecb0a6f2f4282f43a4c55bb5bc9047fa77e0e7a04bfb07a80f153a045bf5b1b57f
languageName: node
linkType: hard
"jimp@npm:^0.14.0":
version: 0.14.0
resolution: "jimp@npm:0.14.0"
@ -11003,7 +10636,7 @@ __metadata:
languageName: node
linkType: hard
"lodash@npm:>=4.17.21, lodash@npm:^4.17.11, lodash@npm:^4.17.19, lodash@npm:^4.17.4":
"lodash@npm:>=4.17.21, lodash@npm:^4.17.19, lodash@npm:^4.17.4":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c
@ -11125,15 +10758,6 @@ __metadata:
languageName: node
linkType: hard
"lz-string@npm:^1.5.0":
version: 1.5.0
resolution: "lz-string@npm:1.5.0"
bin:
lz-string: bin/bin.js
checksum: 36128e4de34791838abe979b19927c26e67201ca5acf00880377af7d765b38d1c60847e01c5ec61b1a260c48029084ab3893a3925fd6e48a04011364b089991b
languageName: node
linkType: hard
"make-dir@npm:^2.0.0, make-dir@npm:^2.1.0":
version: 2.1.0
resolution: "make-dir@npm:2.1.0"
@ -11325,13 +10949,6 @@ __metadata:
languageName: node
linkType: hard
"min-indent@npm:^1.0.0":
version: 1.0.1
resolution: "min-indent@npm:1.0.1"
checksum: 7e207bd5c20401b292de291f02913230cb1163abca162044f7db1d951fa245b174dc00869d40dd9a9f32a885ad6a5f3e767ee104cf278f399cb4e92d3f582d5c
languageName: node
linkType: hard
"minimalistic-assert@npm:^1.0.1":
version: 1.0.1
resolution: "minimalistic-assert@npm:1.0.1"
@ -11845,16 +11462,6 @@ __metadata:
languageName: node
linkType: hard
"object-is@npm:^1.1.5":
version: 1.1.5
resolution: "object-is@npm:1.1.5"
dependencies:
call-bind: "npm:^1.0.2"
define-properties: "npm:^1.1.3"
checksum: 8c263fb03fc28f1ffb54b44b9147235c5e233dc1ca23768e7d2569740b5d860154d7cc29a30220fe28ed6d8008e2422aefdebfe987c103e1c5d190cf02d9d886
languageName: node
linkType: hard
"object-keys@npm:^1.1.1":
version: 1.1.1
resolution: "object-keys@npm:1.1.1"
@ -12465,41 +12072,6 @@ __metadata:
languageName: node
linkType: hard
"pretty-format@npm:^24.0.0, pretty-format@npm:^24.3.0, pretty-format@npm:^24.9.0":
version: 24.9.0
resolution: "pretty-format@npm:24.9.0"
dependencies:
"@jest/types": "npm:^24.9.0"
ansi-regex: "npm:^4.0.0"
ansi-styles: "npm:^3.2.0"
react-is: "npm:^16.8.4"
checksum: 1e75c0ae55dab8953a5fe8025aab0a6d6090773561b672a7a00108f6cfb7dace198b27143392382dff913cb71f6fbc10ed23beaddf2117c380588a3b575825f0
languageName: node
linkType: hard
"pretty-format@npm:^25.1.0":
version: 25.5.0
resolution: "pretty-format@npm:25.5.0"
dependencies:
"@jest/types": "npm:^25.5.0"
ansi-regex: "npm:^5.0.0"
ansi-styles: "npm:^4.0.0"
react-is: "npm:^16.12.0"
checksum: cbcf79f57a96f5eb9970722614a360539940606a20a924f6202e309433af4ad5b71ba210b6b3efcdcdad178f9aefa74f04a447d86520d721fbe155ff43b33112
languageName: node
linkType: hard
"pretty-format@npm:^27.0.2":
version: 27.5.1
resolution: "pretty-format@npm:27.5.1"
dependencies:
ansi-regex: "npm:^5.0.1"
ansi-styles: "npm:^5.0.0"
react-is: "npm:^17.0.1"
checksum: 0cbda1031aa30c659e10921fa94e0dd3f903ecbbbe7184a729ad66f2b6e7f17891e8c7d7654c458fa4ccb1a411ffb695b4f17bbcd3fe075fabe181027c4040ed
languageName: node
linkType: hard
"private@npm:~0.1.5":
version: 0.1.8
resolution: "private@npm:0.1.8"
@ -12795,20 +12367,13 @@ __metadata:
languageName: node
linkType: hard
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.8.4":
"react-is@npm:^16.12.0, react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
checksum: 33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1
languageName: node
linkType: hard
"react-is@npm:^17.0.1":
version: 17.0.2
resolution: "react-is@npm:17.0.2"
checksum: 2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053
languageName: node
linkType: hard
"react-lifecycles-compat@npm:^3.0.4":
version: 3.0.4
resolution: "react-lifecycles-compat@npm:3.0.4"
@ -12979,16 +12544,6 @@ __metadata:
languageName: node
linkType: hard
"redent@npm:^3.0.0":
version: 3.0.0
resolution: "redent@npm:3.0.0"
dependencies:
indent-string: "npm:^4.0.0"
strip-indent: "npm:^3.0.0"
checksum: d64a6b5c0b50eb3ddce3ab770f866658a2b9998c678f797919ceb1b586bab9259b311407280bd80b804e2a7c7539b19238ae6a2a20c843f1a7fcff21d48c2eae
languageName: node
linkType: hard
"reflect.getprototypeof@npm:^1.0.4":
version: 1.0.4
resolution: "reflect.getprototypeof@npm:1.0.4"
@ -13807,7 +13362,7 @@ __metadata:
languageName: node
linkType: hard
"source-map-resolve@npm:^0.5.0, source-map-resolve@npm:^0.5.2":
"source-map-resolve@npm:^0.5.0":
version: 0.5.3
resolution: "source-map-resolve@npm:0.5.3"
dependencies:
@ -13914,15 +13469,6 @@ __metadata:
languageName: node
linkType: hard
"stop-iteration-iterator@npm:^1.0.0":
version: 1.0.0
resolution: "stop-iteration-iterator@npm:1.0.0"
dependencies:
internal-slot: "npm:^1.0.4"
checksum: c4158d6188aac510d9e92925b58709207bd94699e9c31186a040c80932a687f84a51356b5895e6dc72710aad83addb9411c22171832c9ae0e6e11b7d61b0dfb9
languageName: node
linkType: hard
"stoppable@npm:^1.1.0":
version: 1.1.0
resolution: "stoppable@npm:1.1.0"
@ -14101,15 +13647,6 @@ __metadata:
languageName: node
linkType: hard
"strip-indent@npm:^3.0.0":
version: 3.0.0
resolution: "strip-indent@npm:3.0.0"
dependencies:
min-indent: "npm:^1.0.0"
checksum: ae0deaf41c8d1001c5d4fbe16cb553865c1863da4fae036683b474fa926af9fc121e155cb3fc57a68262b2ae7d5b8420aa752c97a6428c315d00efe2a3875679
languageName: node
linkType: hard
"strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1":
version: 3.1.1
resolution: "strip-json-comments@npm:3.1.1"
@ -15039,13 +14576,6 @@ __metadata:
languageName: node
linkType: hard
"wait-for-expect@npm:^3.0.2":
version: 3.0.2
resolution: "wait-for-expect@npm:3.0.2"
checksum: 9616b82381f6571a0f3ed235a62ecdaf529816c9eef04f665ee1b8b515c3b7e1dc90d781111b6284bb777f45c4742f6c058d549a44d9d6e367ffd4d34f5f3b9e
languageName: node
linkType: hard
"warning@npm:^4.0.3":
version: 4.0.3
resolution: "warning@npm:4.0.3"