diff --git a/pkgs/frontend/app/components/BasicButton.tsx b/pkgs/frontend/app/components/BasicButton.tsx new file mode 100644 index 0000000..234cdee --- /dev/null +++ b/pkgs/frontend/app/components/BasicButton.tsx @@ -0,0 +1,19 @@ +import CommonButton from "./common/CommonButton"; + +interface BasicButtonProps extends React.ComponentProps { + children: React.ReactNode; +} + +export const BasicButton = ({ children, ...props }: BasicButtonProps) => { + return ( + + {children} + + ); +}; diff --git a/pkgs/frontend/app/components/Header.tsx b/pkgs/frontend/app/components/Header.tsx index f3f3314..9009c5c 100644 --- a/pkgs/frontend/app/components/Header.tsx +++ b/pkgs/frontend/app/components/Header.tsx @@ -1,13 +1,15 @@ import { useState, useEffect } from "react"; -import { Box, Text } from "@chakra-ui/react"; -import { WorkspaceIcon } from "./WorkspaceIcon"; -import { UserIcon } from "./UserIcon"; +import { Box, Flex, Text } from "@chakra-ui/react"; +import { WorkspaceIcon } from "./icon/WorkspaceIcon"; +import { UserIcon } from "./icon/UserIcon"; import { useLocation } from "@remix-run/react"; -const NO_HEADER_PATHS: string[] = ["/login"]; // 適宜ヘッダーが不要なページのパスを追加 +const NO_HEADER_PATHS: string[] = ["/login", "/signup"]; // 適宜ヘッダーが不要なページのパスを追加 +const WORKSPACES_PATHS: string[] = ["/workspaces"]; // 適宜ワークスペースが未選択な状態のページのパスを追加 const HEADER_SIZE: number = 12; // 偶数のnumberだとアイコンが対応しているため望ましい const headerTextStyle = { + color: "gray.800", my: "auto", wordBreak: "break-word", flex: "1", @@ -43,7 +45,8 @@ export const Header = () => { isWalletConnected && isUserTobanEnsFound ) { - return isWorkspaceSelected && workspaceName + return !WORKSPACES_PATHS.includes(pathname) || + (isWorkspaceSelected && workspaceName) ? HeaderType.WorkspaceAndUserIcons : HeaderType.UserIconOnly; } @@ -59,38 +62,29 @@ export const Header = () => { workspaceName, ]); - return ( - - {headerType !== HeaderType.NonHeader && ( - <> - - {headerType === HeaderType.UserIconOnly && ( - - Workspaces - - )} - {headerType === HeaderType.WorkspaceAndUserIcons && ( - <> - - - {workspaceName} - - - )} - - - - )} - + return headerType !== HeaderType.NonHeader ? ( + + + {headerType === HeaderType.UserIconOnly && ( + + Workspaces + + )} + {headerType === HeaderType.WorkspaceAndUserIcons && ( + <> + + + {workspaceName} + + + )} + + + + ) : ( + <> ); }; diff --git a/pkgs/frontend/app/components/UserIcon.tsx b/pkgs/frontend/app/components/UserIcon.tsx deleted file mode 100644 index d6472a2..0000000 --- a/pkgs/frontend/app/components/UserIcon.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { FaCircleUser } from "react-icons/fa6"; -import { CommonIcon } from "./common/CommonIcon"; - -interface UserIconProps { - userImageUrl: string | undefined; - size: number; -} - -export const UserIcon = ({ userImageUrl, size }: UserIconProps) => { - return ( - - } - /> - ); -}; diff --git a/pkgs/frontend/app/components/common/CommonButton.tsx b/pkgs/frontend/app/components/common/CommonButton.tsx index 3b32311..3861ad7 100644 --- a/pkgs/frontend/app/components/common/CommonButton.tsx +++ b/pkgs/frontend/app/components/common/CommonButton.tsx @@ -12,8 +12,8 @@ export const CommonButton = ({ children, width = "full", size = "md", - backgroundColor, - color, + backgroundColor = "yellow.400", + color = "gray.800", ...props }: CommonButtonProps) => { return ( @@ -22,6 +22,7 @@ export const CommonButton = ({ size={size} backgroundColor={backgroundColor} color={color} + borderRadius="12px" {...props} > {children} diff --git a/pkgs/frontend/app/components/common/CommonIcon.tsx b/pkgs/frontend/app/components/common/CommonIcon.tsx index 2a94a5c..ed32b50 100644 --- a/pkgs/frontend/app/components/common/CommonIcon.tsx +++ b/pkgs/frontend/app/components/common/CommonIcon.tsx @@ -3,7 +3,7 @@ import { Box, Image } from "@chakra-ui/react"; interface CommonIconProps { imageUrl: string | undefined; - size: number; + size: number | "full"; fallbackIconComponent?: ReactNode; } @@ -28,6 +28,7 @@ export const CommonIcon = ({ my="auto" borderRadius="full" flexShrink={0} + overflow="hidden" > {!showFallbackIcon ? ( setShowFallbackIcon(true)} /> ) : ( diff --git a/pkgs/frontend/app/components/common/CommonInput.tsx b/pkgs/frontend/app/components/common/CommonInput.tsx index 7ed24d7..b052289 100644 --- a/pkgs/frontend/app/components/common/CommonInput.tsx +++ b/pkgs/frontend/app/components/common/CommonInput.tsx @@ -5,6 +5,18 @@ interface CommonInputProps extends Omit { onChange: (event: React.ChangeEvent) => void; } -export const CommonInput = ({ value, onChange }: CommonInputProps) => { - return ; +export const CommonInput = ({ + value, + placeholder, + onChange, +}: CommonInputProps) => { + return ( + + ); }; diff --git a/pkgs/frontend/app/components/icon/UserIcon.tsx b/pkgs/frontend/app/components/icon/UserIcon.tsx new file mode 100644 index 0000000..90205f6 --- /dev/null +++ b/pkgs/frontend/app/components/icon/UserIcon.tsx @@ -0,0 +1,28 @@ +import { FaCircleUser } from "react-icons/fa6"; +import { CommonIcon } from "../common/CommonIcon"; + +interface UserIconProps { + userImageUrl: string | undefined; + size?: number | "full"; +} + +export const UserIcon = ({ userImageUrl, size = "full" }: UserIconProps) => { + return ( + + } + /> + ); +}; diff --git a/pkgs/frontend/app/components/WorkspaceIcon.tsx b/pkgs/frontend/app/components/icon/WorkspaceIcon.tsx similarity index 83% rename from pkgs/frontend/app/components/WorkspaceIcon.tsx rename to pkgs/frontend/app/components/icon/WorkspaceIcon.tsx index 2fa32fa..26585c4 100644 --- a/pkgs/frontend/app/components/WorkspaceIcon.tsx +++ b/pkgs/frontend/app/components/icon/WorkspaceIcon.tsx @@ -1,14 +1,14 @@ import { FaPeopleGroup } from "react-icons/fa6"; -import { CommonIcon } from "./common/CommonIcon"; +import { CommonIcon } from "../common/CommonIcon"; interface WorkspaceIconProps { workspaceImageUrl?: string; - size: number; + size?: number | "full"; } export const WorkspaceIcon = ({ workspaceImageUrl, - size, + size = "full", }: WorkspaceIconProps) => { return ( { /> - - + +
- {children} + + {children} + diff --git a/pkgs/frontend/app/routes/api.namestone.$action.tsx b/pkgs/frontend/app/routes/api.namestone.$action.tsx new file mode 100644 index 0000000..c014cb3 --- /dev/null +++ b/pkgs/frontend/app/routes/api.namestone.$action.tsx @@ -0,0 +1,52 @@ +import { ActionFunction, LoaderFunction } from "@remix-run/node"; +import NameStone, { NameData } from "namestone-sdk"; + +const ns = new NameStone(import.meta.env.VITE_NAMESTONE_API_KEY); +const domain = "toban.eth"; + +export const loader: LoaderFunction = async ({ request, params }) => { + const { action } = params; + + switch (action) { + case "resolve-names": + const addresses = new URL(request.url).searchParams.get("addresses"); + if (!addresses) return Response.json([]); + + const resolvedNames = await Promise.all( + addresses.split(",").map((address) => ns.getNames({ domain, address })) + ); + return Response.json(resolvedNames); + case "resolve-addresses": + const names = new URL(request.url).searchParams.get("names"); + if (!names) return Response.json([]); + + const resolvedAddresses = await Promise.all( + names + .split(",") + .map((name) => + ns.searchNames({ domain, name, exact_match: 1 as any }) + ) + ); + return Response.json(resolvedAddresses); + default: + throw Response.json({ message: "Not Found" }, { status: 404 }); + } +}; + +export const action: ActionFunction = async ({ request, params }) => { + const { method } = request; + const { action } = params; + + if (method === "POST") { + switch (action) { + case "set-name": + const { name, address, text_records } = await request.json(); + await ns.setName({ domain, name, address, text_records }); + return Response.json({ message: "OK" }); + default: + throw Response.json({ message: "Not Found" }, { status: 404 }); + } + } + + throw Response.json({ message: "Not Found" }, { status: 404 }); +}; diff --git a/pkgs/frontend/app/routes/login.tsx b/pkgs/frontend/app/routes/login.tsx index 6f5f15d..f3ec0f4 100644 --- a/pkgs/frontend/app/routes/login.tsx +++ b/pkgs/frontend/app/routes/login.tsx @@ -1,33 +1,84 @@ +import { Box, Text, Float } from "@chakra-ui/react"; import { usePrivy, useWallets } from "@privy-io/react-auth"; -import { FC, useCallback } from "react"; -import CommonButton from "~/components/common/CommonButton"; +import { useFetcher, useNavigate } from "@remix-run/react"; +import { useNamesByAddresses } from "hooks/useENS"; +import { useActiveWallet } from "hooks/useWallet"; +import { FC, useCallback, useEffect } from "react"; +import { BasicButton } from "~/components/BasicButton"; +import { CommonIcon } from "~/components/common/CommonIcon"; const Login: FC = () => { - const { connectOrCreateWallet, user, logout } = usePrivy(); + const navigate = useNavigate(); + const { connectOrCreateWallet, logout } = usePrivy(); const { wallets } = useWallets(); + const { wallet, smartWallet, isSmartWallet } = useActiveWallet(); + const { fetchNames } = useNamesByAddresses(); + // ToDo:Metamask、Privyアカウント、どちらともディスコネクトできないので修正する const disconnectWallets = useCallback(async () => { - await Promise.all(wallets.map((wallet) => wallet.disconnect())); - }, [wallets]); + if (wallets.length === 0) return; + if (isSmartWallet) { + logout(); + } else { + Promise.all(wallets.map((wallet) => wallet.disconnect())); + } + }, [wallets, isSmartWallet]); + + useEffect(() => { + const afterLogin = async () => { + if (!wallet && !smartWallet) return; + + const names = await fetchNames([ + isSmartWallet ? smartWallet?.account?.address! : wallet.address, + ]); + + if (names?.[0].length === 0) { + navigate("/signup"); + } else { + navigate("/workspace"); + } + }; + + afterLogin(); + }, [wallet, navigate]); return ( <> - - login - - - {wallets.length > 0 && ( - - disconnect - - )} - {user ? ( - - logout - - ) : ( - <> - )} + + + + + + Toban -当番- + + + + {wallets.length === 0 ? ( + Login + ) : !isSmartWallet ? ( + Logout + ) : ( + Logout + )} + ); }; diff --git a/pkgs/frontend/app/routes/signup.tsx b/pkgs/frontend/app/routes/signup.tsx new file mode 100644 index 0000000..ac3ac05 --- /dev/null +++ b/pkgs/frontend/app/routes/signup.tsx @@ -0,0 +1,116 @@ +import { Box, Float, Input, Text } from "@chakra-ui/react"; +import { useWallets } from "@privy-io/react-auth"; +import { useNavigate } from "@remix-run/react"; +import { useAddressesByNames, useSetName } from "hooks/useENS"; +import { useUploadImageFileToIpfs } from "hooks/useIpfs"; +import { useActiveWallet } from "hooks/useWallet"; +import { TextRecords } from "namestone-sdk"; +import { FC, useEffect, useMemo, useState } from "react"; +import { BasicButton } from "~/components/BasicButton"; +import { CommonInput } from "~/components/common/CommonInput"; +import { UserIcon } from "~/components/icon/UserIcon"; + +const Login: FC = () => { + const navigate = useNavigate(); + + const [userName, setUserName] = useState(""); + + const { + uploadImageFileToIpfs, + imageFile, + setImageFile, + isLoading: isIpfsLoading, + } = useUploadImageFileToIpfs(); + + const { wallet, smartWallet, isSmartWallet } = useActiveWallet(); + + const { setName, isLoading: isSetNameLoading } = useSetName(); + + const names = useMemo(() => { + return userName ? [userName] : []; + }, [userName]); + const { addresses } = useAddressesByNames(names); + + const availableName = useMemo(() => { + if (!userName) return false; + + return addresses?.[0]?.length === 0; + }, [userName, addresses]); + + const handleSubmit = async () => { + if (!smartWallet && !wallet) return; + + const params: { + name: string; + address: string; + text_records: TextRecords; + } = { + name: userName, + address: isSmartWallet ? smartWallet?.account?.address! : wallet?.address, + text_records: {}, + }; + + if (imageFile) { + const res = await uploadImageFileToIpfs(); + params.text_records.avatar = res?.ipfsUri!; + } + + await setName(params); + + navigate("/workspace"); + }; + + return ( + <> + + { + const file = e.target.files?.[0]; + if (file && file.type.startsWith("image/")) { + setImageFile(file); + } else { + alert("画像ファイルを選択してください"); + } + }} + /> + + + + setUserName(e.target.value)} + /> + + {availableName + ? "この名前は利用可能です" + : "この名前は利用できません"} + + + + + 保存 + + + + ); +}; + +export default Login; diff --git a/pkgs/frontend/app/routes/workspace.tsx b/pkgs/frontend/app/routes/workspace.tsx new file mode 100644 index 0000000..546c2b1 --- /dev/null +++ b/pkgs/frontend/app/routes/workspace.tsx @@ -0,0 +1,7 @@ +import { FC } from "react"; + +const Workspace: FC = () => { + return
Workspace
; +}; + +export default Workspace; diff --git a/pkgs/frontend/hooks/useBigBang.ts b/pkgs/frontend/hooks/useBigBang.ts index fb084fd..b91c813 100644 --- a/pkgs/frontend/hooks/useBigBang.ts +++ b/pkgs/frontend/hooks/useBigBang.ts @@ -3,7 +3,7 @@ import { BIGBANG_ABI } from "abi/bigbang"; import { useCallback, useState } from "react"; import { Address, decodeEventLog, encodeFunctionData } from "viem"; import { BIGBANG_ADDRESS } from "./useContracts"; -import { useSmartAccountClient } from "./useSmartWallet"; +import { useSmartAccountClient } from "./useWallet"; import { publicClient } from "./useViem"; /** diff --git a/pkgs/frontend/hooks/useENS.ts b/pkgs/frontend/hooks/useENS.ts index 2e90166..4c080d9 100644 --- a/pkgs/frontend/hooks/useENS.ts +++ b/pkgs/frontend/hooks/useENS.ts @@ -1,26 +1,73 @@ -import { useEffect, useState } from "react"; -import NameStone, { NameData } from "namestone-sdk"; +import { useCallback, useEffect, useState } from "react"; +import { NameData, TextRecords } from "namestone-sdk"; +import axios from "axios"; -const ns = new NameStone(import.meta.env.VITE_NAMESTONE_API_KEY); -const domain = "toban.eth"; - -export const useNamesByAddresses = (addresses: string[]) => { +export const useNamesByAddresses = (addresses?: string[]) => { const [names, setNames] = useState([]); useEffect(() => { - const fetchNames = async () => { + if (!addresses) return; + fetchNames(addresses); + }, [addresses]); + + const fetchNames = async (addresses: string[]) => { + try { + const { data } = await axios.get("/api/namestone/resolve-names", { + params: { addresses: addresses.join(",") }, + }); + setNames(data); + return data as NameData[][]; + } catch (error) { + console.error(error); + } + }; + + return { names, fetchNames }; +}; + +export const useAddressesByNames = (names?: string[]) => { + const [addresses, setAddresses] = useState([]); + + const fetchAddresses = useCallback(async (resolveNames: string[]) => { + try { + const { data } = await axios.get("/api/namestone/resolve-addresses", { + params: { names: resolveNames.join(",") }, + }); + setAddresses(data); + return data as NameData[][]; + } catch (error) { + console.error(error); + } + }, []); + + useEffect(() => { + if (!names || names.length === 0 || names.includes("")) return; + fetchAddresses(names); + }, [names]); + + return { addresses, fetchAddresses }; +}; + +export const useSetName = () => { + const [isLoading, setIsLoading] = useState(false); + + const setName = useCallback( + async (params: { + name: string; + address?: string; + text_records?: TextRecords; + }) => { + if (!params.address || !params.name) return; + setIsLoading(true); try { - const data: NameData[][] = await Promise.all( - addresses.map((address) => ns.getNames({ domain, address })) - ); - setNames(data); + await axios.post("/api/namestone/set-name", params); } catch (error) { console.error(error); } - }; - - fetchNames(); - }, [addresses]); + setIsLoading(false); + }, + [] + ); - return names; + return { setName, isLoading }; }; diff --git a/pkgs/frontend/hooks/useFractionToken.ts b/pkgs/frontend/hooks/useFractionToken.ts index 0f3b1d5..829c53f 100644 --- a/pkgs/frontend/hooks/useFractionToken.ts +++ b/pkgs/frontend/hooks/useFractionToken.ts @@ -5,7 +5,7 @@ import { FRACTION_TOKEN_ADDRESS, fractionTokenBaseConfig, } from "./useContracts"; -import { useSmartAccountClient } from "./useSmartWallet"; +import { useSmartAccountClient } from "./useWallet"; import { publicClient } from "./useViem"; /** diff --git a/pkgs/frontend/hooks/useHats.ts b/pkgs/frontend/hooks/useHats.ts index 52d4270..0f7b2e4 100644 --- a/pkgs/frontend/hooks/useHats.ts +++ b/pkgs/frontend/hooks/useHats.ts @@ -5,7 +5,7 @@ import { useCallback, useState } from "react"; import { Address, decodeEventLog, encodeFunctionData } from "viem"; import { base, optimism, sepolia } from "viem/chains"; import { HATS_ADDRESS } from "./useContracts"; -import { useSmartAccountClient } from "./useSmartWallet"; +import { useSmartAccountClient } from "./useWallet"; import { publicClient } from "./useViem"; // ############################################################### diff --git a/pkgs/frontend/hooks/useSplitsCreator.ts b/pkgs/frontend/hooks/useSplitsCreator.ts index 019d985..babfe44 100644 --- a/pkgs/frontend/hooks/useSplitsCreator.ts +++ b/pkgs/frontend/hooks/useSplitsCreator.ts @@ -1,7 +1,7 @@ import { SPLITS_CREATOR_ABI } from "abi/splits"; import { useCallback, useState } from "react"; import { AbiItemArgs, Address, encodeFunctionData } from "viem"; -import { useSmartAccountClient } from "./useSmartWallet"; +import { useSmartAccountClient } from "./useWallet"; import { publicClient } from "./useViem"; /** diff --git a/pkgs/frontend/hooks/useSmartWallet.ts b/pkgs/frontend/hooks/useWallet.ts similarity index 83% rename from pkgs/frontend/hooks/useSmartWallet.ts rename to pkgs/frontend/hooks/useWallet.ts index 14530be..7441f92 100644 --- a/pkgs/frontend/hooks/useSmartWallet.ts +++ b/pkgs/frontend/hooks/useWallet.ts @@ -2,7 +2,7 @@ import { useWallets } from "@privy-io/react-auth"; import { createSmartAccountClient, SmartAccountClient } from "permissionless"; import { toSimpleSmartAccount } from "permissionless/accounts"; import { createPimlicoClient } from "permissionless/clients/pimlico"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { http } from "viem"; import { entryPoint07Address } from "viem/account-abstraction"; import { currentChain, publicClient } from "./useViem"; @@ -70,3 +70,19 @@ export const useSmartAccountClient = () => { return client; }; + +export const useActiveWallet = () => { + const { wallets } = useWallets(); + + const smartWallet = useSmartAccountClient(); + + const wallet = useMemo(() => { + return wallets[0]; + }, [wallets]); + + const isSmartWallet = useMemo(() => { + return smartWallet ? true : false; + }, [smartWallet]); + + return { wallet, smartWallet, isSmartWallet }; +}; diff --git a/pkgs/frontend/package.json b/pkgs/frontend/package.json index 89172bd..137bb70 100644 --- a/pkgs/frontend/package.json +++ b/pkgs/frontend/package.json @@ -25,6 +25,7 @@ "@remix-run/react": "^2.15.0", "@remix-run/serve": "^2.15.0", "@solana/web3.js": "^1.95.5", + "axios": "^1.7.9", "isbot": "^5.1.11", "lucide-react": "^0.399.0", "namestone-sdk": "^0.2.11", diff --git a/pkgs/frontend/public/images/toban-logo.svg b/pkgs/frontend/public/images/toban-logo.svg new file mode 100644 index 0000000..3adb2ba --- /dev/null +++ b/pkgs/frontend/public/images/toban-logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pkgs/frontend/public/static/logo.svg b/pkgs/frontend/public/static/logo.svg deleted file mode 100644 index 620fe26..0000000 --- a/pkgs/frontend/public/static/logo.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/pkgs/frontend/vite.config.ts b/pkgs/frontend/vite.config.ts index fc1db80..b632cba 100644 --- a/pkgs/frontend/vite.config.ts +++ b/pkgs/frontend/vite.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ v3_relativeSplatPath: true, v3_throwAbortReason: true, }, - ssr: false, + ssr: true, }), tsconfigPaths(), ], diff --git a/yarn.lock b/yarn.lock index 19a67d1..9a59181 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7000,6 +7000,15 @@ axios@^1.5.1, axios@^1.7.2, axios@^1.7.7: form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.7.9: + version "1.7.9" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" @@ -16874,7 +16883,7 @@ string-hash@^1.1.3: resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -16891,6 +16900,15 @@ string-width@^2.1.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -16999,7 +17017,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -17013,6 +17031,13 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -18652,7 +18677,7 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -18670,6 +18695,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"