forked from OpenNeo/impress
Compare commits
10 commits
eb915ae851
...
2ca349d5b0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ca349d5b0 | |||
| 067da33025 | |||
| 35479bf80e | |||
| 054dd484a5 | |||
| 00d57518f1 | |||
| c81ffb6ac2 | |||
| 37bac38973 | |||
| 242665bd02 | |||
| 1617b82b18 | |||
| 584fed70c6 |
15 changed files with 212 additions and 1197 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 } };
|
||||
};
|
||||
|
|
@ -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 } };
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
498
yarn.lock
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue