Integrate Beehiiv email verification in onboarding flow (#13)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675) - Add beehiiv widget for email verification - Extract subscriber ID from the JWT - Hash subscriber ID to be used as KYC ID Co-authored-by: Shreerang Kale <shreerangkale@gmail.com> Reviewed-on: cerc-io/testnet-onboarding-app#13
This commit is contained in:
parent
11f872032e
commit
c041f031dc
@ -23,6 +23,7 @@
|
|||||||
"crypto-browserify": "^3.12.0",
|
"crypto-browserify": "^3.12.0",
|
||||||
"ethers": "5.7.2",
|
"ethers": "5.7.2",
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
|
"jwt-decode": "^4.0.0",
|
||||||
"notistack": "^3.0.1",
|
"notistack": "^3.0.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
@ -11,6 +11,9 @@ import UserVerification from "./pages/UserVerification";
|
|||||||
import TermsAndConditions from "./pages/TermsAndConditions";
|
import TermsAndConditions from "./pages/TermsAndConditions";
|
||||||
import Header from "./components/Header";
|
import Header from "./components/Header";
|
||||||
import { WalletConnectProvider } from "./context/WalletConnectContext";
|
import { WalletConnectProvider } from "./context/WalletConnectContext";
|
||||||
|
import VerifyEmail from "./pages/VerifyEmail";
|
||||||
|
import Email from "./pages/Email";
|
||||||
|
import Thanks from "./pages/Thanks";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@ -19,7 +22,10 @@ function App() {
|
|||||||
<WalletConnectProvider>
|
<WalletConnectProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<TermsAndConditions />} />
|
<Route path="/" element={<TermsAndConditions />} />
|
||||||
|
<Route path="/verify-email" element={<VerifyEmail />} />
|
||||||
|
<Route path="/email" element={<Email/>} />
|
||||||
<Route path="/connect-wallet" element={<ConnectWallet />} />
|
<Route path="/connect-wallet" element={<ConnectWallet />} />
|
||||||
|
<Route path="/thanks" element={<Thanks />} />
|
||||||
<Route element={<SignPageLayout />}>
|
<Route element={<SignPageLayout />}>
|
||||||
<Route path="/sign-with-nitro-key" element={<SignWithNitroKey />} />
|
<Route path="/sign-with-nitro-key" element={<SignWithNitroKey />} />
|
||||||
<Route
|
<Route
|
||||||
|
@ -30,4 +30,6 @@ export const TNC_PARTICIPANT_CONTENT = `Lorem ipsum dolor sit amet, consectetur
|
|||||||
|
|
||||||
export const WALLET_DISCLAIMER_MSG = 'You are connecting to an experimental wallet! It is not secure. Do not use it elsewhere and/or for managing real assets.'
|
export const WALLET_DISCLAIMER_MSG = 'You are connecting to an experimental wallet! It is not secure. Do not use it elsewhere and/or for managing real assets.'
|
||||||
|
|
||||||
|
export const REDIRECT_EMAIL_MSG = 'Please check your inbox and click the link to verify your email address.'
|
||||||
|
|
||||||
export const ENABLE_KYC = false;
|
export const ENABLE_KYC = false;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import {useNavigate } from "react-router-dom";
|
import {useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { Button, Box, Container, Typography, colors } from "@mui/material";
|
import { Button, Box, Container, Typography, colors } from "@mui/material";
|
||||||
|
|
||||||
@ -10,14 +10,16 @@ const ConnectWallet = () => {
|
|||||||
const { connect, session } = useWalletConnectContext();
|
const { connect, session } = useWalletConnectContext();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
navigate("/sign-with-nitro-key");
|
navigate("/sign-with-nitro-key", {
|
||||||
|
state: location.state
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [session, navigate,]);
|
}, [session, navigate, location]);
|
||||||
|
|
||||||
const handler = async () => {
|
const handler = async () => {
|
||||||
await connect();
|
await connect();
|
||||||
|
33
src/pages/Email.tsx
Normal file
33
src/pages/Email.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { Box, Typography } from '@mui/material'
|
||||||
|
|
||||||
|
import { REDIRECT_EMAIL_MSG } from '../constants'
|
||||||
|
|
||||||
|
const Email = () => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
marginY={20}
|
||||||
|
marginX={50}
|
||||||
|
sx={{
|
||||||
|
border: 1,
|
||||||
|
borderColor: 'grey.500',
|
||||||
|
}}
|
||||||
|
padding={5}
|
||||||
|
|
||||||
|
>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
|
Thank you for registering!
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
{REDIRECT_EMAIL_MSG}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Email
|
@ -25,14 +25,14 @@ const SignWithCosmos = () => {
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const {message: innerMessage, cosmosAddress, receivedEthSig: ethSignature, kycIdHash} = location.state as {
|
const {message: innerMessage, cosmosAddress, receivedEthSig: ethSignature, subscriberIdHash} = location.state as {
|
||||||
message?: {
|
message?: {
|
||||||
msg: string;
|
msg: string;
|
||||||
address: string;
|
address: string;
|
||||||
};
|
};
|
||||||
cosmosAddress?: string;
|
cosmosAddress?: string;
|
||||||
receivedEthSig?: string;
|
receivedEthSig?: string;
|
||||||
kycIdHash?: string;
|
subscriberIdHash?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ethAddress = innerMessage!.address;
|
const ethAddress = innerMessage!.address;
|
||||||
@ -49,11 +49,11 @@ const SignWithCosmos = () => {
|
|||||||
participant: cosmosAddress!,
|
participant: cosmosAddress!,
|
||||||
ethPayload: innerMessage,
|
ethPayload: innerMessage,
|
||||||
ethSignature: ethSignature!,
|
ethSignature: ethSignature!,
|
||||||
kycId: kycIdHash!,
|
kycId: subscriberIdHash!,
|
||||||
role
|
role
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [cosmosAddress, innerMessage, ethSignature, kycIdHash, role]);
|
}, [cosmosAddress, innerMessage, ethSignature, subscriberIdHash, role]);
|
||||||
|
|
||||||
const handleTokenRequest = async () => {
|
const handleTokenRequest = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import React, { useState, useMemo, useEffect } from "react";
|
import React, { useState, useMemo, useEffect } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import canonicalStringify from "canonical-json";
|
import canonicalStringify from "canonical-json";
|
||||||
import { ethers } from "ethers";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@ -28,6 +27,7 @@ const SignWithNitroKey = () => {
|
|||||||
}, [session, signClient, checkPersistedState]);
|
}, [session, signClient, checkPersistedState]);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const [ethAddress, setEthAddress] = useState("");
|
const [ethAddress, setEthAddress] = useState("");
|
||||||
const [ethSignature, setEthSignature] = useState("");
|
const [ethSignature, setEthSignature] = useState("");
|
||||||
|
|
||||||
@ -68,14 +68,20 @@ const SignWithNitroKey = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const kycIdHash = ethers.utils.sha256(ethers.utils.toUtf8Bytes(cosmosAddress));
|
const state = location.state as {
|
||||||
|
subscriberIdHash?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.subscriberIdHash) {
|
||||||
|
throw new Error("Subscriber ID not found. Please verify your email and try again")
|
||||||
|
}
|
||||||
|
|
||||||
navigate("/sign-with-cosmos", {
|
navigate("/sign-with-cosmos", {
|
||||||
state: {
|
state: {
|
||||||
message,
|
message,
|
||||||
cosmosAddress,
|
cosmosAddress,
|
||||||
receivedEthSig,
|
receivedEthSig,
|
||||||
kycIdHash,
|
subscriberIdHash: state.subscriberIdHash,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ const TermsAndConditions = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleAccept = () => {
|
const handleAccept = () => {
|
||||||
navigate('/connect-wallet');
|
navigate('/verify-email');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
72
src/pages/Thanks.tsx
Normal file
72
src/pages/Thanks.tsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { jwtDecode } from "jwt-decode";
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
|
||||||
|
import { Box, colors, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
interface JwtPayload {
|
||||||
|
subscriber_id: string;
|
||||||
|
exp: number;
|
||||||
|
iss: string;
|
||||||
|
iat: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Thanks: React.FC = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [err, setErr] = useState<string>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const queryParams = new URLSearchParams(location.search);
|
||||||
|
const token = queryParams.get('jwt_token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(!token){
|
||||||
|
throw new Error("Invalid JWT Token")
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = jwtDecode(token) as JwtPayload;
|
||||||
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
if (!decoded.subscriber_id) {
|
||||||
|
throw new Error("Subscriber ID not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decoded.exp < currentTime) {
|
||||||
|
throw new Error("Token has expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriberIdBytes = ethers.utils.toUtf8Bytes(decoded.subscriber_id)
|
||||||
|
const subscriberIdHash = ethers.utils.sha256(subscriberIdBytes);
|
||||||
|
|
||||||
|
navigate('/connect-wallet', {
|
||||||
|
state:{
|
||||||
|
subscriberIdHash
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
setErr(String(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [location.search, navigate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
marginY={20}
|
||||||
|
marginX={50}
|
||||||
|
padding={5}
|
||||||
|
>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom color={colors.red[400]}>
|
||||||
|
{err ? err : "Loading..."}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Thanks;
|
@ -50,7 +50,7 @@ const UserVerification = () => {
|
|||||||
message,
|
message,
|
||||||
cosmosAddress,
|
cosmosAddress,
|
||||||
receivedEthSig,
|
receivedEthSig,
|
||||||
kycIdHash,
|
subscriberIdHash: kycIdHash,
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
}, [applicationSubmitted, kycId, navigate, cosmosAddress, message, receivedEthSig]);
|
}, [applicationSubmitted, kycId, navigate, cosmosAddress, message, receivedEthSig]);
|
||||||
|
24
src/pages/VerifyEmail.tsx
Normal file
24
src/pages/VerifyEmail.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const VerifyEmail = () => {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', margin: '20vh' }}>
|
||||||
|
<iframe
|
||||||
|
title="verify-email"
|
||||||
|
src="https://embeds.beehiiv.com/18aaa245-3652-4b0a-94a9-a87054df4914"
|
||||||
|
data-test-id="beehiiv-embed"
|
||||||
|
width="480"
|
||||||
|
height="320"
|
||||||
|
frameBorder="0"
|
||||||
|
scrolling="no"
|
||||||
|
style={{
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: '2px solid #e5e7eb',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
}}
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VerifyEmail
|
37
yarn.lock
37
yarn.lock
@ -9032,6 +9032,11 @@ junk@3.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
|
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
|
||||||
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
|
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
|
||||||
|
|
||||||
|
jwt-decode@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
|
||||||
|
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
|
||||||
|
|
||||||
keccak@^3.0.0:
|
keccak@^3.0.0:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d"
|
resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d"
|
||||||
@ -11985,16 +11990,7 @@ string-natural-compare@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, 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@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -12078,14 +12074,7 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", 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@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@ -13338,7 +13327,8 @@ workbox-window@6.6.1:
|
|||||||
"@types/trusted-types" "^2.0.2"
|
"@types/trusted-types" "^2.0.2"
|
||||||
workbox-core "6.6.1"
|
workbox-core "6.6.1"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
|
name wrap-ansi-cjs
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@ -13356,15 +13346,6 @@ wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.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.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
Loading…
Reference in New Issue
Block a user