diff --git a/.github/workflows/push-dev-environment.yaml b/.github/workflows/push-dev-environment.yaml index 5a7f72e6..1f84bfb2 100644 --- a/.github/workflows/push-dev-environment.yaml +++ b/.github/workflows/push-dev-environment.yaml @@ -11,10 +11,10 @@ jobs: environment: dev steps: - name: Checkout Repo - uses: actions/checkout@v1 + uses: actions/checkout@v2.4.0 - name: Setup Docker - uses: docker-practice/actions-setup-docker@0.0.1 + uses: docker-practice/actions-setup-docker@1.0.8 - name: Log in to docker registry run: | diff --git a/.github/workflows/push-prod-environment.yaml b/.github/workflows/push-prod-environment.yaml index 6c8d9ad5..1b823f40 100644 --- a/.github/workflows/push-prod-environment.yaml +++ b/.github/workflows/push-prod-environment.yaml @@ -11,10 +11,10 @@ jobs: environment: production steps: - name: Checkout Repo - uses: actions/checkout@v1 + uses: actions/checkout@v2.4.0 - name: Setup Docker - uses: docker-practice/actions-setup-docker@0.0.1 + uses: docker-practice/actions-setup-docker@1.0.8 - name: Log in to docker registry run: | diff --git a/hapi/src/routes/add-join-request.route.js b/hapi/src/routes/add-join-request.route.js index 3d3c83f3..5e310879 100644 --- a/hapi/src/routes/add-join-request.route.js +++ b/hapi/src/routes/add-join-request.route.js @@ -2,6 +2,8 @@ const Joi = require('joi') const Boom = require('@hapi/boom') const { joinRequestService, affiliateService } = require('../services') +const { mailUtil } = require('../utils') +const { mailTemplate } = require('../utils/templates') module.exports = { method: 'POST', @@ -14,9 +16,19 @@ module.exports = { const joinRequest = await joinRequestService.findByAccount(input.account) const hasKYC = await affiliateService.checkKyc(input.account) - if (isAnInvitee || joinRequest?.length || !hasKYC) + if (isAnInvitee || joinRequest?.length) throw Boom.badRequest('Account does not meet requirements') + if (!hasKYC) { + mailUtil.send({ + account: input.account, + to: input.email, + subject: + 'Further action is required to activate your Proton Affiliate account', + template: mailTemplate.generateRejectionByKYC + }) + } + const transaction = await joinRequestService.addJoinRequest(input) return { id: transaction.id } diff --git a/hapi/src/routes/send-confirmation.route.js b/hapi/src/routes/send-confirmation.route.js index f33cdaf7..7c8658a6 100644 --- a/hapi/src/routes/send-confirmation.route.js +++ b/hapi/src/routes/send-confirmation.route.js @@ -2,8 +2,11 @@ const Joi = require('joi') const Boom = require('@hapi/boom') const { - mailUtil: { sendConfirmation } + mailUtil: { send } } = require('../utils') +const { + mailTemplate: { generateConfirmation } +} = require('../utils/templates') const { joinRequestService: { findByAccount } } = require('../services') @@ -15,10 +18,11 @@ module.exports = { try { for (const account of input.accounts) { const { email } = await findByAccount(account) - await sendConfirmation({ + await send({ account: account, to: email, - subject: 'You are ready to share your Proton referral link!' + subject: 'Welcome to the Proton Affiliate Program!', + template: generateConfirmation }) } diff --git a/hapi/src/utils/mail.util.js b/hapi/src/utils/mail.util.js index b5168176..2ca9389a 100644 --- a/hapi/src/utils/mail.util.js +++ b/hapi/src/utils/mail.util.js @@ -2,11 +2,8 @@ const nodemailer = require('nodemailer') const { mailConfig: { host, port, user, pass } } = require('../config') -const { - mailTemplate: { generateConfirmationMail } -} = require('./templates') -const sendConfirmation = async ({ account, to, subject }) => { +const send = async ({ account, to, subject, template }) => { try { const transporter = nodemailer.createTransport({ host, @@ -20,7 +17,7 @@ const sendConfirmation = async ({ account, to, subject }) => { from: `Proton Affiliate <${user}>`, to, subject, - html: generateConfirmationMail({ account }) + html: template({ account }) }) } catch (error) { console.log(error) @@ -28,5 +25,5 @@ const sendConfirmation = async ({ account, to, subject }) => { } module.exports = { - sendConfirmation + send } diff --git a/hapi/src/utils/templates/mail.template.js b/hapi/src/utils/templates/mail.template.js index b82a21ed..1b4778fb 100644 --- a/hapi/src/utils/templates/mail.template.js +++ b/hapi/src/utils/templates/mail.template.js @@ -1,4 +1,4 @@ -const generateConfirmationMail = ({ account }) => { +const generateConfirmation = ({ account }) => { return ` @@ -74,6 +74,79 @@ const generateConfirmationMail = ({ account }) => { ` } +const generateRejectionByKYC = ({ account }) => { + return ` + + + + + + + + +
+ + + +

+ ${account} - Regarding Your Proton On-Chain Referral Program Request +

+

+ Your Proton referral link is not ready yet. To begin using the Proton Referral Program and start earning rewards, you must first complete the KYC (Know Your Client) authentication process in the Proton wallet. +

+
+

+ Please, go to the Proton wallet app and submit your KYC application. Once completed, you can start using your referral link. To find out what countries can complete a KYC, go to the Proton wallet app or ask in the Proton Telegram chat: https://t.me/protonxpr. If you're still having problems after confirming this information, please get in touch with us through Telegram: https://t.me/eoscr. +

+

+ Best Regards, +

+
+

+ The Proton Affiliate Team +
+ (Edenia, SoftAtom) +

+
+ + +
+

+ THIS PROJECT WAS FUNDED THROUGH THE PROTON GOVERNANCE COMMITTEE WORKER PROPOSAL SYSTEM +

+

+ + Apply Here for Funding + +

+
+ + + + + + + + + + + + + + + + + + +
+
+ +
+ + ` +} + module.exports = { - generateConfirmationMail + generateConfirmation, + generateRejectionByKYC } diff --git a/hasura/metadata/actions.graphql b/hasura/metadata/actions.graphql index 644b21c7..860f737c 100644 --- a/hasura/metadata/actions.graphql +++ b/hasura/metadata/actions.graphql @@ -31,3 +31,7 @@ type AddJoinRequestOutput { id: String! } +type SendJoinRequestRejectionOutput { + success: Boolean! +} + diff --git a/hasura/metadata/actions.yaml b/hasura/metadata/actions.yaml index bc956dfe..03df3ea7 100644 --- a/hasura/metadata/actions.yaml +++ b/hasura/metadata/actions.yaml @@ -25,4 +25,5 @@ custom_types: - name: AddReferralOutput - name: SendConfirmationOutput - name: AddJoinRequestOutput + - name: SendJoinRequestRejectionOutput scalars: [] diff --git a/hasura/metadata/databases/default/functions/functions.yaml b/hasura/metadata/databases/default/functions/functions.yaml new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/hasura/metadata/databases/default/functions/functions.yaml @@ -0,0 +1 @@ +[] diff --git a/webapp/.gitignore b/webapp/.gitignore index 496daea7..61cf35b8 100644 --- a/webapp/.gitignore +++ b/webapp/.gitignore @@ -16,3 +16,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +.DS_Store \ No newline at end of file diff --git a/webapp/src/components/Loader/index.js b/webapp/src/components/Loader/index.js index 74347e47..4f8fd096 100644 --- a/webapp/src/components/Loader/index.js +++ b/webapp/src/components/Loader/index.js @@ -11,7 +11,7 @@ const Loader = () => { return (
- +
) } diff --git a/webapp/src/components/Loader/styles.js b/webapp/src/components/Loader/styles.js index 218ab7a8..860cfe87 100644 --- a/webapp/src/components/Loader/styles.js +++ b/webapp/src/components/Loader/styles.js @@ -3,6 +3,7 @@ export default () => ({ justifyContent: 'center', alignItems: 'center', display: 'flex', - minHeight: '100%' + minHeight: '100%', + minWidth: '100%' } }) diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json index 93bcc6de..cd8fbbdf 100644 --- a/webapp/src/language/en.json +++ b/webapp/src/language/en.json @@ -11,7 +11,8 @@ "PENDING_PAYMENT": "Pending payment", "PAYMENT_REJECTED": "Rejected", "EXPIRED": "Expired", - "PAID": "Paid" + "PAID": "Paid", + "allStatus": "All status" }, "routes": { "/>title": "Affiliate Account Referrals Program on Proton Blockchain", @@ -167,8 +168,7 @@ "menuAllRoles": "All roles", "menuAdminRole": "Admin only", "menuReferrerRole": "Referrers only", - "verified": "Verified", - "pending": "Pending" + "verified": "Verified" }, "footer": { "poweredBy": "Powered by", diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json index 66fa692a..b6782b98 100644 --- a/webapp/src/language/es.json +++ b/webapp/src/language/es.json @@ -3,7 +3,14 @@ "login": "Ingresar", "signOut": "Salir", "lightMode": "Modo Claro", - "darkMode": "Modo Oscuro" + "darkMode": "Modo Oscuro", + "PENDING_USER_REGISTRATION": "Pendiente", + "PENDING_KYC_VERIFICATION": "KYC pendiente", + "PENDING_PAYMENT": "Pago pendiente", + "PAYMENT_REJECTED": "Rechazado", + "EXPIRED": "Expirado", + "PAID": "Pagado", + "allStatus": "Todos los estados" }, "routes": { "/>title": "Programa de afiliados de Proton", diff --git a/webapp/src/routes/Admin/index.js b/webapp/src/routes/Admin/index.js index 8ca180c3..2dabfd1c 100644 --- a/webapp/src/routes/Admin/index.js +++ b/webapp/src/routes/Admin/index.js @@ -21,6 +21,7 @@ import Modal from '../../components/Modal' import Accordion from '../../components/Accordion' import FloatingMenu from '../../components/FloatingButton' import HistoryModal from '../../components/HistoryModal' +import Loader from '../../components/Loader' import { affiliateUtil, getUALError, @@ -295,6 +296,8 @@ const Admin = () => { const [newUsersPagination, setNewUsersPagination] = useState( initNewUsersPagination ) + const [fetchingData, setFetchingData] = useState(false) + const [refPayFilterRowsBy, setRefPayFilterRowsBy] = useState() const [filterRowsBy, setFilterRowsBy] = useState() const [userRows, setUserRows] = useState([]) const [userPagination, setUserPagination] = useState({}) @@ -339,6 +342,7 @@ const Admin = () => { cursor: users.cursor }) setUserRows(pagination.cursor ? [...userRows, ...newRows] : newRows) + setFetchingData(false) } const deleteNewUsers = async (showSnack = true) => { @@ -349,13 +353,11 @@ const Admin = () => { } }) - await loadNewUsers({ - variables: { - offset: newUsersPagination.page * newUsersPagination.rowsPerPage, - limit: newUsersPagination.rowsPerPage, - where: { - state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } - } + await loadJoinRequestUsers({ + offset: newUsersPagination.page * newUsersPagination.rowsPerPage, + limit: newUsersPagination.rowsPerPage, + where: { + state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } } }) @@ -365,6 +367,7 @@ const Admin = () => { setOpenInfoModal(false) setSelected({ tableName: null }) setUserAccounts([]) + reloadJoinRequestUsers() } catch (error) { showMessage({ type: 'error', content: error }) } @@ -408,12 +411,14 @@ const Admin = () => { }) reloadUsers() + reloadJoinRequestUsers() } catch (error) { showMessage({ type: 'error', content: getUALError(error) }) } } - const reloadUsers = () => { + const reloadUsers = async () => { + setFetchingData(true) setUserPagination({ hasMore: false, cursor: '' @@ -421,6 +426,12 @@ const Admin = () => { setTimeout(handleOnLoadMoreUsers, 1500) } + const reloadJoinRequestUsers = async () => { + setFetchingData(true) + setNewUsersPagination(initNewUsersPagination) + setTimeout(loadJoinRequestUsers, 1500) + } + const handleOnSelectItem = (tableName, items, accounts) => { if (!items.length) { setSelected({ tableName: null }) @@ -438,13 +449,11 @@ const Admin = () => { page })) - await loadNewUsers({ - variables: { - offset: page * newUsersPagination.rowsPerPage, - limit: newUsersPagination.rowsPerPage, - where: { - state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } - } + await loadJoinRequestUsers({ + offset: page * newUsersPagination.rowsPerPage, + limit: newUsersPagination.rowsPerPage, + where: { + state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } } }) } @@ -455,20 +464,21 @@ const Admin = () => { rowsPerPage: e.target.value })) - await loadNewUsers({ - variables: { - offset: newUsersPagination.page * e.target.value, - limit: e.target.value, - where: { - state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } - } + await loadJoinRequestUsers({ + offset: newUsersPagination.page * e.target.value, + limit: e.target.value, + where: { + state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } } }) } const handleOnLoadMoreReferrals = async usePagination => { const pagination = usePagination ? referralPagination : {} - const referrals = await affiliateUtil.getReferrals(pagination.cursor) + const referrals = await affiliateUtil.getReferralsByStatus( + pagination.cursor, + refPayFilterRowsBy + ) const invitees = (referrals.rows || []).map(item => item.invitee) const { data } = await loadHistoryByInvites({ invitees }) const newRows = (referrals.rows || []).map(row => { @@ -487,9 +497,11 @@ const Admin = () => { cursor: referrals.cursor }) setReferralRows(pagination.cursor ? [...referralRows, ...newRows] : newRows) + setFetchingData(false) } const reloadReferrals = () => { + setFetchingData(true) setReferralPagination({ hasMore: false, cursor: '' @@ -682,6 +694,23 @@ const Admin = () => { setRejectPayment({ isOpen: false, previousModal: null }) } + const loadJoinRequestUsers = async ({ + offset = 0, + limit = 5, + where = { + state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } + } + } = {}) => { + await loadNewUsers({ + variables: { + offset, + limit, + where + } + }) + setFetchingData(false) + } + useEffect(() => { if (loading || !joinRequest) return @@ -731,21 +760,17 @@ const Admin = () => { useEffect(() => { handleOnLoadMoreUsers() handleOnLoadMoreReferrals() - loadNewUsers({ - variables: { - offset: 0, - limit: 5, - where: { - state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } - } - } - }) + loadJoinRequestUsers() }, []) useEffect(() => { reloadUsers() }, [filterRowsBy]) + useEffect(() => { + reloadReferrals() + }, [refPayFilterRowsBy]) + return ( @@ -754,7 +779,20 @@ const Admin = () => { {t('pageInfo')} - + setRefPayFilterRowsBy(filterValue)} + > { { + approveNewUser() + setOpenFAB(false) + }} onClickReject={() => { setOpenInfoModal(true) setOpenFAB(false) @@ -848,10 +890,6 @@ const Admin = () => { setOpenFAB(false) }} allowPayment={allowPayment} - onClickApproveNewUser={() => { - approveNewUser() - setOpenFAB(false) - }} /> @@ -941,6 +979,12 @@ const Admin = () => { + + + + + + ) } diff --git a/webapp/src/routes/Admin/styles.js b/webapp/src/routes/Admin/styles.js index 08293e56..e0aeff01 100644 --- a/webapp/src/routes/Admin/styles.js +++ b/webapp/src/routes/Admin/styles.js @@ -212,6 +212,13 @@ export default theme => ({ width: 514 } }, + loaderModal: { + borderRadius: 100, + backgroundColor: theme.palette.common.white, + padding: theme.spacing(2, 2), + width: 80, + height: 80 + }, btnRejectModal: { display: 'flex', justifyContent: 'space-between', diff --git a/webapp/src/routes/Home/index.js b/webapp/src/routes/Home/index.js index 1396e4fb..9cb81801 100644 --- a/webapp/src/routes/Home/index.js +++ b/webapp/src/routes/Home/index.js @@ -116,6 +116,11 @@ const Home = () => { }) setChecked(false) setEmail('') + setIsValidAccount({ + showHelper: true, + isValid: false, + message: t('accountHelperError2') + }) } catch (error) { showMessage({ type: 'error', content: error.message }) } @@ -203,12 +208,10 @@ const Home = () => { state: { _eq: affiliateUtil.JOIN_REQUEST_STATUS.pending } } }) - const hasKyc = await affiliateUtil.checkKyc(account) - const isValid = !isAnInvitee && !joinRequest.length && hasKyc + const isValid = !isAnInvitee && !joinRequest.length const errorMessageTag = (isAnInvitee ? 'accountHelperError' : '') || - (joinRequest.length ? 'accountHelperError2' : '') || - (!hasKyc ? 'accountHelperError3' : 'accountHelperText') + (joinRequest.length ? 'accountHelperError2' : 'accountHelperText') setIsValidAccount({ showHelper: true, diff --git a/webapp/src/utils/affiliate.js b/webapp/src/utils/affiliate.js index 68983166..51cb9ffa 100644 --- a/webapp/src/utils/affiliate.js +++ b/webapp/src/utils/affiliate.js @@ -30,6 +30,7 @@ const JOIN_REQUEST_STATUS = { pending: 'pending', approved: 'approved' } +const MIN_PAGE_SIZE = 10 const getUserRole = async accountName => { if (!accountName) { @@ -219,7 +220,7 @@ const getUsersByRole = async (lowerBound, filterRowsBy) => { ({ role }) => !filterRowsBy || role === ROLES[filterRowsBy] ) - if (!(filteredUsers.length < 10 && users.hasMore)) { + if (!(filteredUsers.length < MIN_PAGE_SIZE && users.hasMore)) { return { rows: filteredUsers, cursor: users.cursor, @@ -271,6 +272,33 @@ const getParams = async () => { return rows.length > 0 ? rows[0] : {} } +const getReferralsByStatus = async (lowerBound, filterRowsBy) => { + const users = await getReferrals(lowerBound) + const filteredUsers = users.rows.filter( + ({ status }) => !filterRowsBy || status === REFERRAL_STATUS[filterRowsBy] + ) + + if (!(filteredUsers.length < MIN_PAGE_SIZE && users.hasMore)) { + return { + rows: filteredUsers, + cursor: users.cursor, + hasMore: users.hasMore + } + } + + const { + rows: tempRows, + cursor: tempCursor, + hasMore: tempHasMore + } = await getReferralsByStatus(users.cursor, filterRowsBy) + + return { + rows: tempRows ? [...filteredUsers, ...tempRows] : filteredUsers, + cursor: tempCursor, + hasMore: tempHasMore + } +} + const getReferrals = async lowerBound => { const { rows, @@ -361,6 +389,7 @@ export const affiliateUtil = { getUser, isAccountValidAsReferrer, isAccountValidAsInvitee, + getReferralsByStatus, getReferrals, getParams } diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 0c8aca0f..104707c4 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -8005,20 +8005,20 @@ fsevents@^1.2.7: "fsevents@patch:fsevents@^1.2.7#~builtin": version: 1.2.13 - resolution: "fsevents@patch:fsevents@npm%3A1.2.13#~builtin::version=1.2.13&hash=1cc4b2" + resolution: "fsevents@patch:fsevents@npm%3A1.2.13#~builtin::version=1.2.13&hash=18f3a7" dependencies: bindings: ^1.5.0 nan: ^2.12.1 - checksum: b264407498db2cfdcc2a05287334a4160c985a88e4a989e2f2f8dcc6afc8b04a4fcd82c797266442452e11c1fb07d7747d138b078fe4bb1f8f4fd2a6f2484d7e + checksum: 2587e64097c1251ae549440b6347954ef3c5ce390061f9e0f0810e83d5d604bb7222c382e7e3c39fc2ba4da53f115b118ea5359ca3bb0bcb96576596bc685d5a languageName: node linkType: hard "fsevents@patch:fsevents@^2.1.2#~builtin, fsevents@patch:fsevents@^2.1.3#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": version: 2.3.2 - resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=1cc4b2" + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=18f3a7" dependencies: node-gyp: latest - checksum: 78db9daf1f6526a49cefee3917cc988f62dc7f25b5dd80ad6de4ffc4af7f0cab7491ac737626ff53e482a111bc53aac9e411fe3602458eca36f6a003ecf69c16 + checksum: edbd0fd80be379c14409605f77e52fdc78a119e17f875e8b90a220c3e5b29e54a1477c21d91fd30b957ea4866406dc3ff87b61432d2840ff8866b309e5866140 languageName: node linkType: hard @@ -14650,31 +14650,31 @@ resolve@^2.0.0-next.3: "resolve@patch:resolve@1.18.1#~builtin": version: 1.18.1 - resolution: "resolve@patch:resolve@npm%3A1.18.1#~builtin::version=1.18.1&hash=00b1ff" + resolution: "resolve@patch:resolve@npm%3A1.18.1#~builtin::version=1.18.1&hash=d4691f" dependencies: is-core-module: ^2.0.0 path-parse: ^1.0.6 - checksum: 3a5051499a570cf94d74353d494cacadbfa489107def201f87e26cabd80d000bd8abccbe247783b86b06d86ce2c646eee5c55900c71cbf1ad2043a67a92b0242 + checksum: 32933bdf7bcc93bb7e01646f60a471b2cee0262b465449170ce02f4efea71868e4a9624aded28ce3935bc333028c2cc10885ac2b578125381a9de6481e8c1ce2 languageName: node linkType: hard "resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.18.1#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.3.2#~builtin, resolve@patch:resolve@^1.8.1#~builtin": version: 1.20.0 - resolution: "resolve@patch:resolve@npm%3A1.20.0#~builtin::version=1.20.0&hash=00b1ff" + resolution: "resolve@patch:resolve@npm%3A1.20.0#~builtin::version=1.20.0&hash=d4691f" dependencies: is-core-module: ^2.2.0 path-parse: ^1.0.6 - checksum: bed00be983cd20a8af0e7840664f655c4b269786dbd9595c5f156cd9d8a0050e65cdbbbdafc30ee9b6245b230c78a2c8ab6447a52545b582f476c29adb188cc5 + checksum: 028141533a81a4515c8ac07e38a0ccde5e2722a0fa6bb83ac53ec463a764f1b7c021dcb98fc9a511a4f6e403f354d034bf250fcf9b7b8399bceb2a889ddf78ff languageName: node linkType: hard "resolve@patch:resolve@^2.0.0-next.3#~builtin": version: 2.0.0-next.3 - resolution: "resolve@patch:resolve@npm%3A2.0.0-next.3#~builtin::version=2.0.0-next.3&hash=00b1ff" + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.3#~builtin::version=2.0.0-next.3&hash=d4691f" dependencies: is-core-module: ^2.2.0 path-parse: ^1.0.6 - checksum: eb88c5e53843bc022215744307a5f5664446c0fdb8f43c33456dce98d5ee6b3162d0cd0a177bb6f1c3d5c8bf01391ac7ab2de0e936e35318725fb40ba7efdaf6 + checksum: 2b145f11f797d477c355e53dc70a1b991d95e7954ea6ab258bc1905d4c1658d477a7bcf912eda56c9643080206fe1f44a53fe96559e8da17140347447ce9c025 languageName: node linkType: hard @@ -16541,11 +16541,11 @@ typescript@*: "typescript@patch:typescript@*#~builtin": version: 4.4.4 - resolution: "typescript@patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=d8b4e7" + resolution: "typescript@patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=6454cb" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 4a639b6886be13616582ab82abdb4ffbbf5b0458069d13c10cd5d44fc1cafa33eab005e8ac8691ad8fae249fee85844bb7d523263c4568fe9a2ca31cd3c91c3d + checksum: aaf433508f31ca748d391e488ccb10485407f5920084fff35926076e6089009474929c1c43d0d5a0dafca65fec51550e032e0f0b28ce63c68432adc70b1bc9b3 languageName: node linkType: hard