72 Commits
Author SHA1 Message Date
Verticool 9dcc6860f4 telegram fullscreen /viewContent 2025-03-21 13:28:16 +06:00
Verticool 1b96d049c7 useAuth additional validation checks: localStorage ton_proof save > use > remove & selectWallet check 2025-03-18 22:42:25 +06:00
user 46f3b22ec9 fix filename 2025-03-17 14:03:25 +03:00
user 7d8096be4c edit file extension 2025-03-17 13:01:43 +03:00
Verticool 2351ee51eb prettier config, dwnld button, dwnld content, chain login > purchase, selectWallet post in useAuth 2025-03-16 20:38:43 +06:00
user 5f4b13d7ba fix misprint 2025-03-14 00:42:25 +03:00
user 0b1dc23cc6 free upload support 2025-03-14 00:37:26 +03:00
user 2274cb4a4a fix favicon, page title 2025-03-07 10:29:25 +03:00
user 060785a11a purchaseContent edit query 2025-03-05 10:09:04 +03:00
user 4645093029 deploy commit 2025-02-28 19:37:44 +03:00
Verticool 1c1ee5df16 chunk upload, modals, env update 2025-02-28 21:49:36 +06:00
Verticool 7cb7a7d73a authTwa, router udpate > protectedLayout 2025-02-27 04:41:09 +06:00
Verticool 0bc77e44f8 /viewContent tg starts buy button. /uploadContent parsing address fix 2025-02-26 22:46:59 +06:00
user d10065cfcc fix 2025-02-20 10:14:54 +03:00
user d42f6eb9e8 enable traces & add metrika 2025-02-20 10:05:04 +03:00
user f675a4ca49 Revert "try change sentry -> hawk catcher"
This reverts commit 9aee5f10d2.
2025-02-19 20:23:56 +03:00
user 9aee5f10d2 try change sentry -> hawk catcher 2025-02-19 18:26:06 +03:00
Verticool add9cb471e fix 2025-02-18 22:25:28 +06:00
Verticool a58c73c112 sentry env modes, /viewContent disconnect button, env 2025-02-18 22:25:15 +06:00
Verticool 90d629876b sentry dsn fix 2025-02-18 20:41:56 +06:00
Verticool e34e65b337 sentry error test button in routes /sentryCheck 2025-02-18 18:23:47 +06:00
Verticool fc8e8cba5e sentry, /viewContent /uploadContent modals, /uploadContent buttons 2025-02-18 18:03:15 +06:00
user b952a76ccb fix bugs 2025-02-03 14:59:12 +03:00
user f1decf7448 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	.gitignore
2025-02-03 14:31:58 +03:00
user e7d5acc155 ignore env 2025-02-03 14:31:23 +03:00
Verticool dd62962de8 Merge remote-tracking branch 'akrom/add-reset-attachment' 2025-02-03 11:09:16 +06:00
Verticool 3e9564d7b9 auth flow fix, validation for auth in steps /uploadContnt 2025-02-03 11:04:15 +06:00
Saidakrom Rakhmedov 7f7dde7b28 Add replace icon 2025-02-01 20:53:51 +05:00
Saidakrom Rakhmedov fc6fb5003e fix warnings 2025-02-01 20:29:49 +05:00
Saidakrom Rakhmedov 1d22ac2173 add reset attachment 2025-02-01 20:21:56 +05:00
Verticool 7705908484 tg buttons /viewContent, tags, validation address form and more /uploadContent 2025-02-01 19:17:40 +06:00
user 372f84d1b1 remove unused vars 2025-01-30 03:17:01 +03:00
user 9c2a415886 add hashtag input 2025-01-30 03:15:02 +03:00
user 0fc292829f unused var 2025-01-26 14:09:45 +03:00
user 691dea4bc8 fix address 2025-01-22 20:31:32 +03:00
user 578c82f6dd fix address 2025-01-22 20:29:01 +03:00
user 881ff70a1c try fix buffer 2025-01-22 20:23:46 +03:00
user 58d2d3aa17 add royalty autocomplete 2025-01-22 19:37:55 +03:00
user 9c4851d35f video poster & default address royalty 2025-01-21 12:29:39 +03:00
rakhimovkamran a81aee843f | Auth on view content 2024-10-28 16:46:19 +05:00
rakhimovkamran 778db1ce9c | Auth on view content 2024-10-27 23:29:08 +05:00
rakhimovkamran 39fef5cb2a | Auth on view content 2024-10-27 23:18:06 +05:00
rakhimovkamran ed33936e4f | Auth on view content 2024-10-27 20:07:11 +05:00
rakhimovkamran 9d2b4c158f | Auth on view content 2024-10-27 19:30:31 +05:00
rakhimovkamran 71284b722c | Auth on view content 2024-10-27 19:13:36 +05:00
rakhimovkamran a9111959cd | Auth on view content 2024-10-27 17:57:03 +05:00
rakhimovkamran e4f3ddb6cd | Fixed price and add button to upload own content 2024-09-23 20:08:53 +05:00
rakhimovkamran d298c58e08 | Fixed price and add button to upload own content 2024-09-13 19:52:41 +05:00
rakhimovkamran 0531e60222 | Fixed price and add button to upload own content 2024-09-13 17:47:02 +05:00
rakhimovkamran 9cef81a3c1 | Fixed price and add button to upload own content 2024-08-31 02:36:21 +05:00
rakhimovkamran 1e7e773cb1 | remove wallet disconnection 2024-08-24 03:15:45 +05:00
user 2440fdf61a only deploy commit 2024-08-23 08:30:57 +03:00
user 0ecc2b4a3c edit default license type 2024-08-21 23:47:42 +03:00
rakhimovkamran c5c36cb078 | update audio player & other fixes 2024-08-19 05:01:17 +05:00
Oleg Yakovenko b03533dea6 removed auth from data step 2024-08-07 13:09:29 +03:00
Oleg Yakovenko 939de83654 added real ton proof
fixed timeout on splash screen
2024-08-07 12:42:02 +03:00
Oleg Yakovenko bf99462835 don't throw error. 2024-07-15 03:54:46 +03:00
Oleg Yakovenko afc1670ac0 added error handling 2024-07-15 03:51:51 +03:00
Oleg Yakovenko 1b79b15a31 added ton_proof 2024-07-15 03:04:19 +03:00
Oleg Yakovenko 99c9f00110 async, logs 2024-07-14 23:45:29 +03:00
Oleg Yakovenko 6d5f73e90e call auth 10 ms after welcom step 2024-07-14 21:51:30 +03:00
Oleg Yakovenko f875a6afc0 call auth.twa even if (tonConnectUI.connected) on welcome step
enlarge gif
2024-07-14 21:34:17 +03:00
Oleg Yakovenko 08dc809e9d logs 2024-07-14 21:12:42 +03:00
Oleg Yakovenko b4ceaa387a added logs
changed gif
2024-07-14 20:57:29 +03:00
Oleg Yakovenko 72ad5b3a21 added player config 2024-07-09 17:05:14 +03:00
user 419eed81e6 fix unused vars 2024-05-25 07:20:57 +03:00
user 195ee714e9 remove unused moment 2024-05-24 14:15:19 +03:00
user 6d37b74706 feat: viewContent page by mik 2024-05-24 13:48:39 +03:00
user 35c9a6704d dev@locazia: mik fix 2024-04-02 14:24:34 +03:00
user 54275d997b fix 2024-03-28 01:41:46 +03:00
user 8c217ab321 dev@locazia: mik ver 2024-03-28 01:26:43 +03:00
user 5c3500d17c dev@locazia: try add vercel.json 2024-03-05 16:52:28 +03:00
48 changed files with 3733 additions and 571 deletions

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
.idea .idea
node_modules node_modules
.DS_Store .DS_Store
dist
.env*
+7
View File
@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 4,
"printWidth": 100,
"trailingComma": "es5"
}
+1 -1
View File
@@ -1,9 +1,9 @@
# React + TypeScript + Vite # React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available: Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+20 -2
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link href="/vite.svg" rel="icon" type="image/svg+xml" /> <link href="/favicon.png" rel="icon" type="image/png">
<meta content="width=device-width, initial-scale=1.0" name="viewport" /> <meta content="width=device-width, initial-scale=1.0" name="viewport" />
<meta <meta
@@ -14,9 +14,27 @@
<script src="https://telegram.org/js/telegram-web-app.js"></script> <script src="https://telegram.org/js/telegram-web-app.js"></script>
<title>MyMusic</title> <title>MY</title>
</head> </head>
<body> <body>
<!-- Yandex.Metrika counter -->
<script type="text/javascript" >
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
ym(99984890, "init", {
clickmap:true,
trackLinks:true,
accurateTrackBounce:true,
webvisor:true
});
</script>
<noscript><div><img src="https://mc.yandex.ru/watch/99984890" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
<div id="root"></div> <div id="root"></div>
<script src="/src/entry.tsx" type="module"></script> <script src="/src/entry.tsx" type="module"></script>
</body> </body>
+670 -7
View File
@@ -9,8 +9,13 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.3.4", "@hookform/resolvers": "^3.3.4",
"@sentry/react": "^9.1.0",
"@telegram-apps/sdk": "^3.5.1",
"@ton/core": "^0.59.1",
"@tonconnect/ui-react": "^2.0.2",
"@vkruglikov/react-telegram-web-app": "^2.1.9", "@vkruglikov/react-telegram-web-app": "^2.1.9",
"axios": "^1.6.7", "axios": "^1.6.7",
"buffer": "^6.0.3",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@@ -18,6 +23,7 @@
"react-player": "^2.15.1", "react-player": "^2.15.1",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-router-dom": "^6.22.2", "react-router-dom": "^6.22.2",
"react-tag-input": "^6.10.3",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",
"zod": "^3.22.4", "zod": "^3.22.4",
"zustand": "^4.5.2" "zustand": "^4.5.2"
@@ -1108,6 +1114,27 @@
"url": "https://opencollective.com/unts" "url": "https://opencollective.com/unts"
} }
}, },
"node_modules/@react-dnd/asap": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz",
"integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==",
"license": "MIT",
"peer": true
},
"node_modules/@react-dnd/invariant": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz",
"integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==",
"license": "MIT",
"peer": true
},
"node_modules/@react-dnd/shallowequal": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz",
"integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==",
"license": "MIT",
"peer": true
},
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.15.2", "version": "1.15.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz",
@@ -1285,6 +1312,324 @@
"win32" "win32"
] ]
}, },
"node_modules/@sentry-internal/browser-utils": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.1.0.tgz",
"integrity": "sha512-S1uT+kkFlstWpwnaBTIJSwwAID8PS3aA0fIidOjNezeoUE5gOvpsjDATo9q+sl6FbGWynxMz6EnYSrq/5tuaBQ==",
"license": "MIT",
"dependencies": {
"@sentry/core": "9.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/feedback": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.1.0.tgz",
"integrity": "sha512-jTDCqkqH3QDC8m9WO4mB06hqnBRsl3p7ozoh0E774UvNB6blOEZjShhSGMMEy5jbbJajPWsOivCofUtFAwbfGw==",
"license": "MIT",
"dependencies": {
"@sentry/core": "9.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.1.0.tgz",
"integrity": "sha512-E2xrUoms90qvm0BVOuaZ8QfkMoTUEgoIW/35uOeaqNcL7uOIj8c5cSEQQKit2Dr7CL6W+Ci5c6Khdyd5C0NL5w==",
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "9.1.0",
"@sentry/core": "9.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay-canvas": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.1.0.tgz",
"integrity": "sha512-gxredVe+mOgfNqDJ3dTLiRON3FK1rZ8d0LHp7TICK/umLkWFkuso0DbNeyKU+3XCEjCr9VM7ZRqTDMzmY6zyVg==",
"license": "MIT",
"dependencies": {
"@sentry-internal/replay": "9.1.0",
"@sentry/core": "9.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/browser": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.1.0.tgz",
"integrity": "sha512-G55e5j77DqRW3LkalJLAjRRfuyKrjHaKTnwIYXa6ycO+Q1+l14pEUxu+eK5Abu2rtSdViwRSb5/G6a/miSUlYA==",
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "9.1.0",
"@sentry-internal/feedback": "9.1.0",
"@sentry-internal/replay": "9.1.0",
"@sentry-internal/replay-canvas": "9.1.0",
"@sentry/core": "9.1.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/core": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.1.0.tgz",
"integrity": "sha512-djWEzSBpMgqdF3GQuxO+kXCUX+Mgq42G4Uah/HSUBvPDHKipMmyWlutGRoFyVPPOnCDgpHu3wCt83wbpEyVmDw==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/react": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.1.0.tgz",
"integrity": "sha512-aP2sXHH+erbomuzU762ktg340IGDh8zD7ueuqwBwGu98fhCpTYsLXiS85I29tUvPLljwNU9puLPmxbgW4iZ2tQ==",
"license": "MIT",
"dependencies": {
"@sentry/browser": "9.1.0",
"@sentry/core": "9.1.0",
"hoist-non-react-statics": "^3.3.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"react": "^16.14.0 || 17.x || 18.x || 19.x"
}
},
"node_modules/@telegram-apps/bridge": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@telegram-apps/bridge/-/bridge-2.4.0.tgz",
"integrity": "sha512-Lp/vhspF7okK8zXvSWWirunKXOPE6Gr11o9VBne4VmKG/yHRhEW7Pf07ncPtXLLzI6wW8+VYc3khsHPABJymEw==",
"license": "MIT",
"dependencies": {
"@telegram-apps/signals": "^1.1.1",
"@telegram-apps/toolkit": "^2.0.0",
"@telegram-apps/transformers": "^2.2.0",
"@telegram-apps/types": "^2.0.0",
"better-promises": "^0.4.0",
"error-kid": "^0.0.4",
"mitt": "^3.0.1",
"valibot": "1.0.0-beta.14"
}
},
"node_modules/@telegram-apps/navigation": {
"version": "1.0.13",
"resolved": "https://registry.npmjs.org/@telegram-apps/navigation/-/navigation-1.0.13.tgz",
"integrity": "sha512-TsUueB5LQp77GQHoMa93nq26Uw7GJjrFCPbyseMVU7aBBxAc+8CV2IYytRwcVp5sv/q7ThK5X4JaKn2V1yBHDQ==",
"license": "MIT",
"dependencies": {
"@telegram-apps/bridge": "^1.9.2",
"@telegram-apps/signals": "^1.1.1",
"@telegram-apps/toolkit": "^1.1.1"
}
},
"node_modules/@telegram-apps/navigation/node_modules/@telegram-apps/bridge": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@telegram-apps/bridge/-/bridge-1.9.2.tgz",
"integrity": "sha512-SJLcNWLXhbbZr9MiqFH/g2ceuitSJKMxUIZysK4zUNyTUNuonrQG80Q/yrO+XiNbKUj8WdDNM86NBARhuyyinQ==",
"license": "MIT",
"dependencies": {
"@telegram-apps/signals": "^1.1.1",
"@telegram-apps/toolkit": "^1.1.1",
"@telegram-apps/transformers": "^1.2.2",
"@telegram-apps/types": "^1.2.1"
}
},
"node_modules/@telegram-apps/navigation/node_modules/@telegram-apps/toolkit": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@telegram-apps/toolkit/-/toolkit-1.1.1.tgz",
"integrity": "sha512-+vhKx6ngfvjyTE6Xagl3z1TPVbfx5s7xAkcYzCdHYUo6T60jLIqLgyZMcI1UPoIAMuMu1pHoO+p8QNCj/+tFmw==",
"license": "MIT"
},
"node_modules/@telegram-apps/navigation/node_modules/@telegram-apps/transformers": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@telegram-apps/transformers/-/transformers-1.2.2.tgz",
"integrity": "sha512-vvMwXckd1D7Ozc0h66PSUwF5QLrRV9HlGJFFeBuUex8QEk5mSPtsJkLiqB8aBbwuFDa91+TUSM/CxqPZO/e9YQ==",
"license": "MIT",
"dependencies": {
"@telegram-apps/toolkit": "^1.1.1",
"@telegram-apps/types": "^1.2.1"
}
},
"node_modules/@telegram-apps/navigation/node_modules/@telegram-apps/types": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@telegram-apps/types/-/types-1.2.1.tgz",
"integrity": "sha512-so4HLh7clur0YyMthi9KVIgWoGpZdXlFOuQjk3+Q5NAvJZ11nAheBSwPlGw/Ko92+zwvrSBE/lQyN2+p17RP+w==",
"license": "MIT"
},
"node_modules/@telegram-apps/sdk": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/@telegram-apps/sdk/-/sdk-3.5.1.tgz",
"integrity": "sha512-m/ynpSozXsqq6Kfb6M9fm8SD6x/+jqvTFT59FuuOBvC+G8eMd0F+fvZdr6Pj0I6IL9M67nrfm92JpTu/aVNEjw==",
"license": "MIT",
"dependencies": {
"@telegram-apps/bridge": "^2.4.0",
"@telegram-apps/navigation": "^1.0.13",
"@telegram-apps/signals": "^1.1.1",
"@telegram-apps/toolkit": "^2.0.0",
"@telegram-apps/transformers": "^2.2.0",
"@telegram-apps/types": "^2.0.0",
"better-promises": "^0.4.0",
"error-kid": "^0.0.4",
"valibot": "1.0.0-beta.14"
}
},
"node_modules/@telegram-apps/signals": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@telegram-apps/signals/-/signals-1.1.1.tgz",
"integrity": "sha512-vz37r8lemGpPzDiBRfqpXYBynzmy3SFnY6zfHsTZABTYYt0b0WQZyU5mFDqqqugGhka78Gy11xmr9csgy4YgGA==",
"license": "MIT"
},
"node_modules/@telegram-apps/toolkit": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@telegram-apps/toolkit/-/toolkit-2.0.0.tgz",
"integrity": "sha512-1GKTLBNme1Phu/gFvgS9NWPq+LhPfzSIfnwhcF9I/6tCdufrLRcVaSMRiK9R4VDYD6iZUyj+a2l250qWAxxjQQ==",
"license": "MIT"
},
"node_modules/@telegram-apps/transformers": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@telegram-apps/transformers/-/transformers-2.2.0.tgz",
"integrity": "sha512-wqXXOukhEjZKhzdq5vG1LkxWL11DApbmUKzk+3nA/ki3TLyD3awVTOXbpoNdOwFl2xliIooYcsUOEl4WCyyLGw==",
"license": "MIT",
"dependencies": {
"@telegram-apps/toolkit": "^2.0.0",
"@telegram-apps/types": "^2.0.0",
"valibot": "1.0.0-beta.14"
}
},
"node_modules/@telegram-apps/types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@telegram-apps/types/-/types-2.0.0.tgz",
"integrity": "sha512-yF499FJK82a2IDNDAQdrmVH3sgFZl/QFNdVZKgWpgtunIVJ1fres5wi9+4aUBRVIdQwZOZZqB/AOvYYuYXsq3Q==",
"license": "MIT"
},
"node_modules/@ton/core": {
"version": "0.59.1",
"resolved": "https://registry.npmjs.org/@ton/core/-/core-0.59.1.tgz",
"integrity": "sha512-SxFBAvutYJaIllTkv82vbHTJhJI6NxzqUhi499CDEjJEZ9i6i9lHJiK2df4dlLAb/4SiWX6+QUzESkK4DEdnCw==",
"license": "MIT",
"dependencies": {
"symbol.inspect": "1.0.1"
},
"peerDependencies": {
"@ton/crypto": ">=3.2.0"
}
},
"node_modules/@ton/crypto": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz",
"integrity": "sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ton/crypto-primitives": "2.1.0",
"jssha": "3.2.0",
"tweetnacl": "1.0.3"
}
},
"node_modules/@ton/crypto-primitives": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz",
"integrity": "sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow==",
"license": "MIT",
"peer": true,
"dependencies": {
"jssha": "3.2.0"
}
},
"node_modules/@ton/crypto-primitives/node_modules/jssha": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz",
"integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==",
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/@ton/crypto/node_modules/jssha": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz",
"integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==",
"license": "BSD-3-Clause",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/@tonconnect/isomorphic-eventsource": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.2.tgz",
"integrity": "sha512-B4UoIjPi0QkvIzZH5fV3BQLWrqSYABdrzZQSI9sJA9aA+iC0ohOzFwVVGXanlxeDAy1bcvPbb29f6sVUk0UnnQ==",
"license": "Apache-2.0",
"dependencies": {
"eventsource": "^2.0.2"
}
},
"node_modules/@tonconnect/isomorphic-fetch": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.3.tgz",
"integrity": "sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w==",
"license": "Apache-2.0",
"dependencies": {
"node-fetch": "^2.6.9"
}
},
"node_modules/@tonconnect/protocol": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.2.6.tgz",
"integrity": "sha512-kyoDz5EqgsycYP+A+JbVsAUYHNT059BCrK+m0pqxykMODwpziuSAXfwAZmHcg8v7NB9VKYbdFY55xKeXOuEd0w==",
"license": "Apache-2.0",
"dependencies": {
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1"
}
},
"node_modules/@tonconnect/sdk": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@tonconnect/sdk/-/sdk-3.0.6.tgz",
"integrity": "sha512-dJipe0Cw43p/7o3Pa6Y6h0QMDtY2V2YKzwdCqcYvmyCYadBNmvA+8ScH9QK5GpkngRJnYaWq+321lAaQTFpUwA==",
"license": "Apache-2.0",
"dependencies": {
"@tonconnect/isomorphic-eventsource": "^0.0.2",
"@tonconnect/isomorphic-fetch": "^0.0.3",
"@tonconnect/protocol": "^2.2.6"
}
},
"node_modules/@tonconnect/ui": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@tonconnect/ui/-/ui-2.0.11.tgz",
"integrity": "sha512-5TOhfEDeyY8R9oyEGavLU+DRmDW3wSGyxVshWhHisi8597cZIuG39HHNbP05WJBBjmVm/Kh/bhcHtfx7lQp/Ig==",
"license": "Apache-2.0",
"dependencies": {
"@tonconnect/sdk": "3.0.6",
"classnames": "^2.3.2",
"csstype": "^3.1.1",
"deepmerge": "^4.2.2",
"ua-parser-js": "^1.0.35"
}
},
"node_modules/@tonconnect/ui-react": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/@tonconnect/ui-react/-/ui-react-2.0.11.tgz",
"integrity": "sha512-h8E4zlbdNBJCAPgg6+O5ZkVDZh8mvnc82VRmhInSPLFAr6qDZbE+qSjRVm4lkuN1N/m24lhkDXFCFjvJ9CgCow==",
"license": "Apache-2.0",
"dependencies": {
"@tonconnect/ui": "2.0.11"
},
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
}
},
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1344,11 +1689,26 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"node_modules/@types/lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT",
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.11.24", "version": "20.11.24",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz",
"integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
@@ -1916,6 +2276,35 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
}, },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/better-promises": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/better-promises/-/better-promises-0.4.0.tgz",
"integrity": "sha512-AcKkTUSd4o1vMf41eBbHW1NkY7vrXeNI6etitGdQE54WFXsF2wkfonrKA06Za7lViRNyT/cMvj5z+DScqhYW8A==",
"license": "MIT",
"dependencies": {
"error-kid": "^0.0.4"
}
},
"node_modules/big-integer": { "node_modules/big-integer": {
"version": "1.6.52", "version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
@@ -2001,6 +2390,30 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@@ -2108,6 +2521,12 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/clsx": { "node_modules/clsx": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
@@ -2191,8 +2610,7 @@
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
"devOptional": true
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
@@ -2296,6 +2714,18 @@
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true "dev": true
}, },
"node_modules/dnd-core": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-14.0.1.tgz",
"integrity": "sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@react-dnd/asap": "^4.0.0",
"@react-dnd/invariant": "^2.0.0",
"redux": "^4.1.1"
}
},
"node_modules/doctrine": { "node_modules/doctrine": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -2326,6 +2756,12 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true "dev": true
}, },
"node_modules/error-kid": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/error-kid/-/error-kid-0.0.4.tgz",
"integrity": "sha512-x+yQhY56SorLMnX6kOf+z3JCv2QBurcWEDcIjgxYtVr4fGeCfAtOdZOCyWttkHHDFPtL2PqnaRUmphbmALJd9w==",
"license": "MIT"
},
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.22.5", "version": "1.22.5",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz",
@@ -2957,11 +3393,19 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/eventsource": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
"integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
"dev": true
}, },
"node_modules/fast-diff": { "node_modules/fast-diff": {
"version": "1.3.0", "version": "1.3.0",
@@ -3448,6 +3892,35 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"license": "BSD-3-Clause",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@@ -3928,6 +4401,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -4047,6 +4526,12 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -4096,6 +4581,26 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true "dev": true
}, },
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.14", "version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
@@ -4712,6 +5217,47 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-dnd": {
"version": "14.0.5",
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-14.0.5.tgz",
"integrity": "sha512-9i1jSgbyVw0ELlEVt/NkCUkxy1hmhJOkePoCH713u75vzHGyXhPDm28oLfc2NMSBjZRM1Y+wRjHXJT3sPrTy+A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@react-dnd/invariant": "^2.0.0",
"@react-dnd/shallowequal": "^2.0.0",
"dnd-core": "14.0.1",
"fast-deep-equal": "^3.1.3",
"hoist-non-react-statics": "^3.3.2"
},
"peerDependencies": {
"@types/hoist-non-react-statics": ">= 3.3.1",
"@types/node": ">= 12",
"@types/react": ">= 16",
"react": ">= 16.14"
},
"peerDependenciesMeta": {
"@types/hoist-non-react-statics": {
"optional": true
},
"@types/node": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/react-dnd-html5-backend": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-14.1.0.tgz",
"integrity": "sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==",
"license": "MIT",
"peer": true,
"dependencies": {
"dnd-core": "14.0.1"
}
},
"node_modules/react-dom": { "node_modules/react-dom": {
"version": "18.2.0", "version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -4828,6 +5374,33 @@
"react-dom": ">=16.8" "react-dom": ">=16.8"
} }
}, },
"node_modules/react-tag-input": {
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/react-tag-input/-/react-tag-input-6.10.3.tgz",
"integrity": "sha512-IVneBnT0/7N8FktHsFUA2UVDvcwLSJoWDTdEm/Ei/r5O6iBABaVw1RMh6SlG+P7oummvzSY6EBjGCXr/Ntq8rQ==",
"license": "MIT",
"dependencies": {
"@types/lodash-es": "^4.17.12",
"classnames": "~2.3.1",
"lodash-es": "^4.17.21"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/ad1992"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dnd": "^14.0.2",
"react-dnd-html5-backend": "^14.0.0",
"react-dom": "^18.2.0"
}
},
"node_modules/react-tag-input/node_modules/classnames": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.3.tgz",
"integrity": "sha512-1inzZmicIFcmUya7PGtUQeXtcF7zZpPnxtQoYOrz0uiOBGlLFa4ik4361seYL2JCcRDIyfdFHiwQolESFlw+Og==",
"license": "MIT"
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -4849,6 +5422,16 @@
"node": ">=8.10.0" "node": ">=8.10.0"
} }
}, },
"node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.14.1", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@@ -5383,6 +5966,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/symbol.inspect": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/symbol.inspect/-/symbol.inspect-1.0.1.tgz",
"integrity": "sha512-YQSL4duoHmLhsTD1Pw8RW6TZ5MaTX5rXJnqacJottr2P2LZBF/Yvrc3ku4NUpMOm8aM0KOCqM+UAkMA5HWQCzQ==",
"license": "ISC"
},
"node_modules/synckit": { "node_modules/synckit": {
"version": "0.8.8", "version": "0.8.8",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz",
@@ -5496,6 +6085,12 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
@@ -5564,6 +6159,18 @@
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"dev": true "dev": true
}, },
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"license": "Unlicense"
},
"node_modules/tweetnacl-util": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
"integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==",
"license": "Unlicense"
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -5665,7 +6272,7 @@
"version": "5.3.3", "version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"dev": true, "devOptional": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -5674,6 +6281,32 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/ua-parser-js": {
"version": "1.0.40",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz",
"integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"license": "MIT",
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -5693,7 +6326,7 @@
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true "devOptional": true
}, },
"node_modules/unload": { "node_modules/unload": {
"version": "2.2.0", "version": "2.2.0",
@@ -5757,6 +6390,20 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true "dev": true
}, },
"node_modules/valibot": {
"version": "1.0.0-beta.14",
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-beta.14.tgz",
"integrity": "sha512-tLyV2rE5QL6U29MFy3xt4AqMrn+/HErcp2ZThASnQvPMwfSozjV1uBGKIGiegtZIGjinJqn0SlBdannf18wENA==",
"license": "MIT",
"peerDependencies": {
"typescript": ">=5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "5.1.4", "version": "5.1.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz",
@@ -5831,6 +6478,22 @@
} }
} }
}, },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+6
View File
@@ -11,8 +11,13 @@
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.3.4", "@hookform/resolvers": "^3.3.4",
"@sentry/react": "^9.1.0",
"@telegram-apps/sdk": "^3.5.1",
"@ton/core": "^0.59.1",
"@tonconnect/ui-react": "^2.0.2",
"@vkruglikov/react-telegram-web-app": "^2.1.9", "@vkruglikov/react-telegram-web-app": "^2.1.9",
"axios": "^1.6.7", "axios": "^1.6.7",
"buffer": "^6.0.3",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@@ -20,6 +25,7 @@
"react-player": "^2.15.1", "react-player": "^2.15.1",
"react-query": "^3.39.3", "react-query": "^3.39.3",
"react-router-dom": "^6.22.2", "react-router-dom": "^6.22.2",
"react-tag-input": "^6.10.3",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",
"zod": "^3.22.4", "zod": "^3.22.4",
"zustand": "^4.5.2" "zustand": "^4.5.2"
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

+23 -19
View File
@@ -1,26 +1,30 @@
import "~/app/styles/globals.css"; import '~/app/styles/globals.css';
import '~/shared/libs/buffer';
import { useEffect } from "react"; import { useEffect } from 'react';
import { useExpand, useWebApp } from "@vkruglikov/react-telegram-web-app"; import { useExpand, useWebApp } from '@vkruglikov/react-telegram-web-app';
import { Providers } from "~/app/providers";
import { AppRouter } from "~/app/router";
import { Providers } from '~/app/providers';
import { AppRouter } from '~/app/router';
import { Notification } from '~/shared/ui/notification';
import { init } from '@telegram-apps/sdk';
export const App = () => { export const App = () => {
const WebApp = useWebApp(); const WebApp = useWebApp();
const [, expand] = useExpand(); const [, expand] = useExpand();
useEffect(() => { useEffect(() => {
WebApp.enableClosingConfirmation(); init();
expand(); WebApp.enableClosingConfirmation();
expand();
WebApp.setHeaderColor("#1d1d1b"); WebApp.setHeaderColor('#1d1d1b');
WebApp.setBackgroundColor("#1d1d1b"); WebApp.setBackgroundColor('#1d1d1b');
}, []); }, []);
return ( return (
<Providers> <Providers>
<AppRouter /> <AppRouter />
</Providers> <Notification />
); </Providers>
);
}; };
+10 -3
View File
@@ -1,6 +1,7 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { QueryClientProvider } from "react-query"; import { QueryClientProvider } from "react-query";
import { WebAppProvider } from "@vkruglikov/react-telegram-web-app"; import { WebAppProvider } from "@vkruglikov/react-telegram-web-app";
import { TonConnectUIProvider } from "@tonconnect/ui-react";
import { queryClient } from "~/shared/libs"; import { queryClient } from "~/shared/libs";
@@ -11,9 +12,15 @@ type ProvidersProps = {
export const Providers = ({ children }: ProvidersProps) => { export const Providers = ({ children }: ProvidersProps) => {
return ( return (
<WebAppProvider options={{ smoothButtonsTransition: true }}> <WebAppProvider options={{ smoothButtonsTransition: true }}>
<QueryClientProvider client={queryClient}> <TonConnectUIProvider
<main className="antialiased">{children}</main> manifestUrl={
</QueryClientProvider> "https://my-public-node-1.projscale.dev/api/tonconnect-manifest.json"
}
>
<QueryClientProvider client={queryClient}>
<main className="antialiased">{children}</main>
</QueryClientProvider>
</TonConnectUIProvider>
</WebAppProvider> </WebAppProvider>
); );
}; };
+2
View File
@@ -1,3 +1,5 @@
export const Routes = { export const Routes = {
Root: "/uploadContent", Root: "/uploadContent",
ViewContent: "/viewContent",
SentryCheck: "/sentryCheck",
}; };
+25 -1
View File
@@ -2,9 +2,33 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Routes } from "~/app/router/constants"; import { Routes } from "~/app/router/constants";
import { RootPage } from "~/pages/root"; import { RootPage } from "~/pages/root";
import { ViewContentPage } from "~/pages/view-content";
import { ProtectedLayout } from "./protected-layout";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ path: Routes.Root, element: <RootPage /> }, {
element: <ProtectedLayout />,
children: [
{ path: Routes.Root, element: <RootPage /> },
{ path: Routes.ViewContent, element: <ViewContentPage /> },
{
path: Routes.SentryCheck,
element: (
<div className="flex h-screen items-center justify-center">
<button
type="button"
className="btn btn-primary border bg-red-900 p-2"
onClick={() => {
throw new Error("Sentry Test Error");
}}
>
Sentry Test Error
</button>
</div>
),
},
],
},
]); ]);
export const AppRouter = () => { export const AppRouter = () => {
+20
View File
@@ -0,0 +1,20 @@
import { useEffect, useState } from 'react';
import { Outlet } from 'react-router-dom';
import { useAuthTwa } from '~/shared/services/authTwa';
export const ProtectedLayout = () => {
const auth = useAuthTwa();
const [isInitialLoad, setIsInitialLoad] = useState(true);
useEffect(() => {
void auth.mutateAsync().finally(() => {
setIsInitialLoad(false);
});
}, []);
if (isInitialLoad || auth.isLoading) {
return null;
}
return <Outlet />;
};
+117
View File
@@ -22,4 +22,121 @@
a { a {
@apply transition-all active:opacity-60; @apply transition-all active:opacity-60;
} }
/*Input Range*/
/* Custom styles for the range input */
input[type="range"] {
-webkit-appearance: none;
/* Remove default appearance */
width: 100%;
/* Full width */
height: 2px;
/* Track height */
background: linear-gradient(to right,
white 0%,
/* Start color of passed track */
white var(--value-percentage, 0%),
/* End color of passed track */
gray var(--value-percentage, 0%),
/* Start color of remaining track */
gray 100%
/* End color of remaining track */
);
border-radius: 9999px;
/* Rounded-full track */
outline: none;
/* Remove outline */
transition: background 0.3s;
}
/* Style the thumb (toggle) */
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 9px;
/* Thumb width */
height: 9px;
/* Thumb height */
background: #fff;
/* Thumb color (blue-500) */
border-radius: 9999px;
/* Rounded-full thumb */
cursor: pointer;
/* Pointer cursor on hover */
transition: background 0.3s;
}
/* For Firefox */
input[type="range"]::-moz-range-thumb {
appearance: none;
width: 9px;
/* Thumb width */
height: 9px;
/* Thumb height */
background: #fff;
/* Thumb color (blue-500) */
border-radius: 9999px;
/* Rounded-full thumb */
cursor: pointer;
/* Pointer cursor on hover */
transition: background 0.3s;
}
input[type="range"]::-moz-range-track {
-webkit-appearance: none;
/* Remove default appearance */
width: 100%;
/* Full width */
height: 2px;
/* Track height */
background: linear-gradient(to right,
white 0%,
/* Start color of passed track */
white var(--value-percentage, 0%),
/* End color of passed track */
gray var(--value-percentage, 0%),
/* Start color of remaining track */
gray 100%
/* End color of remaining track */
);
border-radius: 9999px;
/* Rounded-full track */
outline: none;
/* Remove outline */
transition: background 0.3s;
}
/* English comment: override react-tag-input classes */
.ReactTags__tagInputField {
@apply bg-[#2B2B2B] outline-none w-full h-8 text-sm !important;
@apply border border-white px-[10px] py-[18px] !important;
@apply whitespace-pre !important;
/* English comment:
'bg-transparent' to blend with your dark background
'text-white' to have white text
'placeholder:text-gray-400' to see the placeholder text in a lighter gray
'outline-none' to remove default borders
*/
}
/* English comment: style for the tag itself when it's rendered */
.ReactTags__selected .ReactTags__tag {
@apply bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 mb-2 rounded mr-1 !important;
/* English comment:
'bg-[#363636]' to have a dark gray background
'text-white' keeps the text white
'text-sm' smaller text
'inline-flex items-center' for better alignment
'px-2 py-1 rounded mr-1' for spacing and rounding
*/
}
.ReactTags__selected .ReactTags__remove {
@apply ml-1 text-gray hover:text-white cursor-pointer !important;
/* English comment:
'ml-1' a small margin to separate the 'x' or close symbol
'text-gray-400' by default, and change to white on hover
'cursor-pointer' so it looks clickable
*/
}
} }
+1
View File
@@ -1,3 +1,4 @@
import './shared/services/sentry/index.ts';
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
+6 -1
View File
@@ -1,7 +1,12 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv { interface ImportMetaEnv {
readonly VITE_API_BASE_URL: string; readonly VITE_SENTRY_DSN: string
readonly VITE_API_BASE_URL: string
readonly VITE_API_BASE_STORAGE_URL: string
readonly MODE: 'development' | 'production'
readonly PROD: boolean
readonly DEV: boolean
} }
interface ImportMeta { interface ImportMeta {
+2 -2
View File
@@ -1,16 +1,16 @@
import { WelcomeStep } from "./steps/welcome-step";
import { DataStep } from "./steps/data-step"; import { DataStep } from "./steps/data-step";
import { RoyaltyStep } from "./steps/royalty-step"; import { RoyaltyStep } from "./steps/royalty-step";
import { PresubmitStep } from "./steps/presubmit-step"; import { PresubmitStep } from "./steps/presubmit-step";
import { useSteps } from "~/shared/hooks/use-steps"; import { useSteps } from "~/shared/hooks/use-steps";
import { PriceStep } from "~/pages/root/steps/price-step"; import { PriceStep } from "~/pages/root/steps/price-step";
import { WelcomeStep } from "~/pages/root/steps/welcome-step";
export const RootPage = () => { export const RootPage = () => {
const { ActiveSection } = useSteps(({ nextStep, prevStep }) => { const { ActiveSection } = useSteps(({ nextStep, prevStep }) => {
return [ return [
<WelcomeStep nextStep={nextStep} />, <WelcomeStep nextStep={nextStep} />,
<DataStep prevStep={prevStep} nextStep={nextStep} />, <DataStep nextStep={nextStep} />,
// <AuthorsStep prevStep={prevStep} nextStep={nextStep} />, // <AuthorsStep prevStep={prevStep} nextStep={nextStep} />,
<RoyaltyStep prevStep={prevStep} nextStep={nextStep} />, <RoyaltyStep prevStep={prevStep} nextStep={nextStep} />,
<PriceStep prevStep={prevStep} nextStep={nextStep} />, <PriceStep prevStep={prevStep} nextStep={nextStep} />,
@@ -1,6 +1,5 @@
import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app"; import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app";
import { Replace } from "~/shared/ui/icons/replace";
import { XMark } from "~/shared/ui/icons/x-mark.tsx";
type CoverButtonProps = { type CoverButtonProps = {
src: string; src: string;
@@ -36,8 +35,8 @@ export const CoverButton = ({ src, onClick }: CoverButtonProps) => {
} }
> >
<div /> <div />
<div className={"flex gap-2 text-sm"}>Удалить</div> <div className={"flex gap-2 text-sm"}>Изменить</div>
<XMark /> <Replace />
</button> </button>
</div> </div>
); );
@@ -0,0 +1,75 @@
import { useHapticFeedback, useWebApp } from "@vkruglikov/react-telegram-web-app";
import { useEffect } from "react";
import { Button } from "~/shared/ui/button";
type DisclaimerModalProps = {
onConfirm(): void;
};
export const DisclaimerModal = ({
onConfirm,
}: DisclaimerModalProps) => {
const [impactOccurred] = useHapticFeedback();
const WebApp = useWebApp();
useEffect(() => {
// Отключаем вертикальные свайпы при монтировании компонента
if (WebApp && WebApp.disableVerticalSwipes) {
WebApp.disableVerticalSwipes();
}
// Включаем вертикальные свайпы обратно при размонтировании
return () => {
if (WebApp && WebApp.enableVerticalSwipes) {
WebApp.enableVerticalSwipes();
}
};
}, []);
const handleClick = (fn: () => void) => {
impactOccurred("light");
fn();
};
return (
<div
className={
"fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center bg-black/80 px-[15px]"
}
>
<div className={"flex flex-col max-h-[80vh] w-[85vw] max-w-md"}>
<div
className={
"border border-white bg-[#1D1D1B] px-[15px] py-[16px] text-start flex flex-col gap-12 h-full overflow-y-auto"
}
>
<div className="flex flex-col gap-6">
<p className="mt-2">
Внимание!
</p>
<p>
MY снимает с себя ответственность за правомерность загрузки контента пользователем.
</p>
<p className="flex flex-col">
<span className="tracking-[.25em] w-full">
Сервис исходит из личной
</span>
ответственности пользователя перед законом и третьими лицами. MY категорически не приемлет любые виды пиратства, но признает за Пользователем право принятия самостоятельных решений.
</p>
<p className="flex flex-col">
<span className="tracking-[.25em] w-full">
Перед загрузкой контента
</span>
необходимо убедиться, что первые 30 секунд контента, которые будут использоваться для превью, не содержат материалов, нарушающих возрастное ограничение 18+
</p>
</div>
</div>
<Button
className={"mt-[20px] sticky bottom-0"}
label={"Принять и продолжить"}
includeArrows={false}
onClick={() => handleClick(onConfirm)}
/>
</div>
</div>
);
};
+96 -31
View File
@@ -1,4 +1,4 @@
import { useMemo } from "react"; import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
@@ -12,19 +12,23 @@ import { CoverButton } from "~/pages/root/steps/data-step/components/cover-butto
import { HiddenFileInput } from "~/shared/ui/hidden-file-input"; import { HiddenFileInput } from "~/shared/ui/hidden-file-input";
import { useRootStore } from "~/shared/stores/root"; import { useRootStore } from "~/shared/stores/root";
import { Checkbox } from "~/shared/ui/checkbox"; import { Checkbox } from "~/shared/ui/checkbox";
import { BackButton } from "~/shared/ui/back-button"; import { AudioPlayer } from "~/shared/ui/audio-player";
import { HashtagInput } from "~/shared/ui/hashtag-input";
import { Replace } from "~/shared/ui/icons/replace";
import { DisclaimerModal } from "./components/disclaimer-modal";
type DataStepProps = { type DataStepProps = {
prevStep(): void;
nextStep(): void; nextStep(): void;
}; };
export const DataStep = ({ nextStep, prevStep }: DataStepProps) => { export const DataStep = ({ nextStep }: DataStepProps) => {
const rootStore = useRootStore(); const rootStore = useRootStore();
const [disclaimerAccepted, setDisclaimerAccepted] = useState(false);
const formSchema = useMemo(() => { const formSchema = useMemo(() => {
return z.object({ return z.object({
name: z.string().min(1), name: z.string().min(1, "Обязательное поле"),
author: z.string().optional(), author: z.string().optional(),
}); });
}, []); }, []);
@@ -57,9 +61,33 @@ export const DataStep = ({ nextStep, prevStep }: DataStepProps) => {
})(); })();
}; };
const handleFileReset = () => {
rootStore.setFile(null);
rootStore.setFileSrc('');
rootStore.setFileType('');
}
useEffect(() => {
const storedValue = localStorage.getItem('disclaimerAccepted');
if (storedValue === 'true') {
setDisclaimerAccepted(true);
}
}, []);
const handleConfirmDisclaimer = () => {
setDisclaimerAccepted(true);
localStorage.setItem('disclaimerAccepted', 'true');
};
return ( return (
<section className={"mt-4 px-4 pb-8"}> <section className={"mt-4 px-4 pb-8"}>
<BackButton onClick={prevStep} />
{(rootStore.fileSrc && !disclaimerAccepted) &&
(<DisclaimerModal
onConfirm={() => {
handleConfirmDisclaimer()}}
/>)}
<div className={"mb-[30px] flex flex-col text-sm"}> <div className={"mb-[30px] flex flex-col text-sm"}>
<span className={"ml-4"}>/Заполните информацию о контенте</span> <span className={"ml-4"}>/Заполните информацию о контенте</span>
@@ -72,6 +100,7 @@ export const DataStep = ({ nextStep, prevStep }: DataStepProps) => {
<FormLabel label={"Название"}> <FormLabel label={"Название"}>
<Input <Input
placeholder={"[ Введите название ]"} placeholder={"[ Введите название ]"}
error={form.formState.errors?.name}
{...form.register("name")} {...form.register("name")}
/> />
</FormLabel> </FormLabel>
@@ -79,22 +108,30 @@ export const DataStep = ({ nextStep, prevStep }: DataStepProps) => {
<FormLabel label={"Имя автора/исполнителя"}> <FormLabel label={"Имя автора/исполнителя"}>
<Input <Input
placeholder={"[ введите имя автора/исполнителя ]"} placeholder={"[ введите имя автора/исполнителя ]"}
error={form.formState.errors?.author}
{...form.register("author")} {...form.register("author")}
/> />
</FormLabel> </FormLabel>
<FormLabel label={"Файл"}> <FormLabel label={"Хэштеги"}>
<HiddenFileInput <HashtagInput />
id={"file"} </FormLabel>
shouldProcess={false}
accept={"video/mp4,video/x-m4v,video/*,audio/mp3,audio/*"}
onChange={(file) => {
rootStore.setFile(file);
rootStore.setFileSrc(URL.createObjectURL(file));
}}
/>
{!rootStore.fileSrc && <FileButton htmlFor={"file"} />} <FormLabel label={"Файл"}>
{!rootStore.fileSrc && <>
<HiddenFileInput
id={"file"}
shouldProcess={false}
accept={"video/mp4,video/x-m4v,video/*,audio/mp3,audio/*"}
onChange={(file) => {
rootStore.setFile(file);
rootStore.setFileSrc(URL.createObjectURL(file));
rootStore.setFileType(file.type); // Save the file type for conditional rendering
}}
/>
<FileButton htmlFor={"file"} />
</>}
{rootStore.fileSrc && ( {rootStore.fileSrc && (
<div <div
@@ -102,17 +139,42 @@ export const DataStep = ({ nextStep, prevStep }: DataStepProps) => {
"w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm" "w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm"
} }
> >
<ReactPlayer {rootStore.fileType?.startsWith("audio") ? (
playsinline={true} <AudioPlayer src={rootStore.fileSrc} />
controls={true} ) : (
width={"100%"} <ReactPlayer
url={rootStore.fileSrc} playsinline={true}
/> controls={true}
width="100%"
config={{ file: { attributes: { playsInline: true } } }}
url={rootStore.fileSrc}
/>
)}
<button
onClick={handleFileReset}
className={
"flex w-full items-center justify-between gap-1 border border-white px-[10px] py-[8px]"
}
>
<div />
<div className={"flex gap-2 text-sm"}>Изменить выбор</div>
<Replace />
</button>
</div> </div>
)} )}
</FormLabel> </FormLabel>
<div className={"flex flex-col gap-2"}> <div className={"flex flex-col gap-2"}>
<FormLabel
label={"Разрешить скачивание"}
labelClassName={"flex"}
formLabelAddon={
<Checkbox
onClick={() => rootStore.setAllowDwnld(!rootStore.allowDwnld)}
checked={rootStore.allowDwnld}
/>
}
/>
<FormLabel <FormLabel
label={"Разрешить обложку"} label={"Разрешить обложку"}
labelClassName={"flex"} labelClassName={"flex"}
@@ -126,13 +188,7 @@ export const DataStep = ({ nextStep, prevStep }: DataStepProps) => {
{rootStore.allowCover && ( {rootStore.allowCover && (
<FormLabel label={"Обложка"}> <FormLabel label={"Обложка"}>
<HiddenFileInput
id={"cover"}
accept={"image/*"}
onChange={(cover) => {
rootStore.setCover(cover);
}}
/>
{rootStore.cover ? ( {rootStore.cover ? (
<CoverButton <CoverButton
@@ -142,7 +198,16 @@ export const DataStep = ({ nextStep, prevStep }: DataStepProps) => {
}} }}
/> />
) : ( ) : (
<FileButton htmlFor={"cover"} /> <>
<HiddenFileInput
id={"cover"}
accept={"image/*"}
onChange={(cover) => {
rootStore.setCover(cover);
}}
/>
<FileButton htmlFor={"cover"} />
</>
)} )}
</FormLabel> </FormLabel>
)} )}
@@ -0,0 +1,75 @@
import { useHapticFeedback, useWebApp } from "@vkruglikov/react-telegram-web-app";
import { useEffect } from "react";
import { Button } from "~/shared/ui/button";
type ErrorUploadProps = {
onConfirm(): void;
};
export const ErrorUploadModal = ({
onConfirm,
}: ErrorUploadProps) => {
const [impactOccurred] = useHapticFeedback();
const WebApp = useWebApp();
useEffect(() => {
// Отключаем вертикальные свайпы при монтировании компонента
if (WebApp && WebApp.disableVerticalSwipes) {
WebApp.disableVerticalSwipes();
}
// Включаем вертикальные свайпы обратно при размонтировании
return () => {
if (WebApp && WebApp.enableVerticalSwipes) {
WebApp.enableVerticalSwipes();
}
};
}, []);
const handleClick = (fn: () => void) => {
impactOccurred("light");
fn();
};
return (
<div
className={
"fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center bg-black/80 px-[15px]"
}
>
<div className={"flex flex-col max-h-[80vh] w-[85vw] max-w-md"}>
<div
className={
"border border-white bg-[#1D1D1B] px-[15px] py-[16px] text-start flex flex-col gap-12 h-full overflow-y-auto"
}
>
<div className="flex flex-col gap-6">
<p className="mt-2">
Внимание!
</p>
<p>
Произошла ошибка при загрузке видео.
</p>
<p className="flex flex-col">
<span className="tracking-[.25em] w-full">
Загрузка не завершена
</span>
из-за технических проблем или превышения допустимых ограничений. Вы можете попробовать загрузить файл ещё раз или выбрать другой файл.
</p>
<p className="flex flex-col">
<span className="tracking-[.25em] w-full">
Если проблема повторяется
</span>
обратитесь в техническую поддержку сервиса MY, предоставив подробную информацию о загружаемом файле и возникшей ошибке.
</p>
</div>
</div>
<Button
className={"mt-[20px] sticky bottom-0"}
label={"Понятно"}
includeArrows={false}
onClick={() => handleClick(onConfirm)}
/>
</div>
</div>
);
};
+274 -202
View File
@@ -2,7 +2,7 @@ import {
useHapticFeedback, useHapticFeedback,
useWebApp, useWebApp,
} from "@vkruglikov/react-telegram-web-app"; } from "@vkruglikov/react-telegram-web-app";
import { useState } from "react"; import { useEffect, useState } from "react";
import ReactPlayer from "react-player/lazy"; import ReactPlayer from "react-player/lazy";
import { Button } from "~/shared/ui/button"; import { Button } from "~/shared/ui/button";
@@ -12,6 +12,10 @@ import { useUploadFile } from "~/shared/services/file";
import { Progress } from "~/shared/ui/progress"; import { Progress } from "~/shared/ui/progress";
import { useCreateNewContent } from "~/shared/services/content"; import { useCreateNewContent } from "~/shared/services/content";
import { BackButton } from "~/shared/ui/back-button"; import { BackButton } from "~/shared/ui/back-button";
import { useTonConnectUI } from "@tonconnect/ui-react";
import { ErrorUploadModal } from "./components/error-upload-modal";
type PresubmitStepProps = { type PresubmitStepProps = {
prevStep(): void; prevStep(): void;
@@ -20,6 +24,8 @@ type PresubmitStepProps = {
export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => { export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
const WebApp = useWebApp(); const WebApp = useWebApp();
const [tonConnectUI] = useTonConnectUI();
const rootStore = useRootStore(); const rootStore = useRootStore();
const [impactOccurred] = useHapticFeedback(); const [impactOccurred] = useHapticFeedback();
@@ -30,36 +36,50 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
const createContent = useCreateNewContent(); const createContent = useCreateNewContent();
const [isErrorUploadModal, setIsErrorUploadModal] = useState(false);
const handleErrorUploadModal = () => {
setIsErrorUploadModal(false);
uploadFile.resetUploadError();
uploadCover.resetUploadError();
};
useEffect(() => {
if (uploadFile.uploadError || uploadCover.uploadError) {
setIsErrorUploadModal(true);
}
}, [uploadFile.uploadError, uploadCover.uploadError]);
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
let coverUploadResult = { content_id_v1: "" }; let coverUploadResult = { content_id_v1: "" };
const fileUploadResult = await uploadFile.mutateAsync( const fileUploadResult = await uploadFile.mutateAsync(
rootStore.file as File, rootStore.file as File,
); );
if (rootStore.allowCover && rootStore.cover) { if (rootStore.allowCover && rootStore.cover) {
coverUploadResult = await uploadCover.mutateAsync( coverUploadResult = await uploadCover.mutateAsync(
rootStore.cover as File, rootStore.cover as File,
); );
} }
await createContent.mutateAsync({ const createContentResponse = await createContent.mutateAsync({
title: rootStore.name, title: rootStore.name,
// Это для одного автора // Это для одного автора
...(rootStore.author ...(rootStore.author
? { authors: [rootStore.author] } ? { authors: [rootStore.author] }
: { authors: [] }), : { authors: [] }),
// Откомментировать при условии того что вы принимаете много авторов // Откомментировать при условии того что вы принимаете много авторов
// следует отметить что вы должны еще откомментровать AuthorsStep в RootPage // следует отметить что вы должны еще откомментровать AuthorsStep в RootPage
// authors: rootStore.authors, // authors: rootStore.authors,
downloadable: rootStore.allowDwnld,
content: fileUploadResult.content_id_v1, content: fileUploadResult.content_id_v1,
image: coverUploadResult.content_id_v1, image: coverUploadResult.content_id_v1,
price: String(rootStore.price * 10 ** 9), price: String(rootStore.price * 10 ** 9),
hashtags: rootStore.hashtags,
royaltyParams: rootStore.royalty.map((member) => ({ royaltyParams: rootStore.royalty.map((member) => ({
...member, ...member,
value: member.value * 100, value: member.value * 100,
@@ -69,234 +89,286 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
// Если откомментировать поле resaleLicensePrice в price-step то // Если откомментировать поле resaleLicensePrice в price-step то
// это отработает как надо // это отработает как надо
...(rootStore.allowResale ...(rootStore.allowResale
? { ? {
allowResale: true, allowResale: true,
resaleLicensePrice: String( resaleLicensePrice: String(
rootStore.licenseResalePrice * 10 ** 9, rootStore.licenseResalePrice * 10 ** 9,
), ),
} }
: { allowResale: false, resaleLicensePrice: "0" }), : { allowResale: false, resaleLicensePrice: "0" }),
}); });
if (createContentResponse.data) {
if (createContentResponse.data.address != "free") {
const transactionResponse = await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 120,
messages: [
{
amount: createContentResponse.data.amount,
address: createContentResponse.data.address,
payload: createContentResponse.data.payload,
},
],
});
if (transactionResponse.boc) {
WebApp.close();
} else {
console.error("Transaction failed:", transactionResponse);
}
}
}
WebApp.close(); WebApp.close();
} catch (error) { // @ts-expect-error Type issues
} catch (error: never) {
console.error("An error occurred during the submission process:", error); console.error("An error occurred during the submission process:", error);
alert(`Возникла ошибка, ${JSON.stringify(error)}`);
if (error?.status === 400) {
alert(
"Введенные данные неверные, проверьте правильность введенных данных.",
);
}
} }
}; };
return ( return (
<section className={"mt-4 px-4 pb-8"}> <section className={"mt-4 px-4 pb-8"}>
<BackButton onClick={prevStep} /> {isErrorUploadModal && (
<ErrorUploadModal
onConfirm={() => {
handleErrorUploadModal()}}
/>)}
<BackButton onClick={prevStep} />
<div className={"mb-[30px] flex flex-col text-sm"}> <div className={"mb-[30px] flex flex-col text-sm"}>
<span className={"ml-4"}>/Подтвердите правильность данных</span> <span className={"ml-4"}>/Подтвердите правильность данных</span>
<div> <div>
5/<span className={"text-[#7B7B7B]"}>5</span> 5/<span className={"text-[#7B7B7B]"}>5</span>
</div>
</div> </div>
</div>
<section className={"flex flex-col gap-2"}> <section className={"flex flex-col gap-2"}>
<FormLabel label={"Название"}> <FormLabel label={"Название"}>
<div
className={
"w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"
}
>
{rootStore.name}
</div>
</FormLabel>
{rootStore.author && (
<FormLabel label={"Имя автора/исполнителя"}>
<div <div
className={ className={
"w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold" "w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"
} }
> >
{rootStore.author} {rootStore.name}
</div> </div>
</FormLabel> </FormLabel>
)}
<FormLabel label={"Цена"}> {rootStore.author && (
<div <FormLabel label={"Имя автора/исполнителя"}>
className={ <div
"w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold" className={
} "w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"
> }
{rootStore.price} TON >
</div> {rootStore.author}
</FormLabel> </div>
</FormLabel>
{rootStore.allowResale && (
<FormLabel label={"Цена копии"}>
<div
className={
"w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"
}
>
{rootStore.licenseResalePrice} TON
</div>
</FormLabel>
)}
<FormLabel label={"Файл"}>
{rootStore.fileSrc && !uploadFile.isUploading && (
<div
className={
"w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm"
}
>
<ReactPlayer
playsinline={true}
controls={true}
width={"100%"}
url={rootStore.fileSrc}
/>
</div>
)} )}
{uploadFile.isUploading && uploadFile.isLoading && ( {rootStore.author && (
<Progress value={uploadFile.uploadProgress} /> <FormLabel label={"Теги"}>
)} <div
</FormLabel> className="flex flex-wrap gap-1">
{rootStore.hashtags.map((tag, index) => (
{rootStore.allowCover && ( <div
<FormLabel label={"Обложка"}> key={index}
<div className={
className={ "bg-[#363636] text-white text-sm inline-flex items-center px-2 py-1 rounded mr-1"
"flex w-full items-center border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold" }
}
>
<div
style={{
height: isCoverExpanded ? "261px" : "68px",
}}
className={"bg-bg w-full"}
> >
{rootStore.cover && !uploadCover.isUploading && ( {tag}
<img
src={URL.createObjectURL(rootStore.cover)}
alt={"cover"}
className={"h-full w-full object-cover object-center"}
/>
)}
{uploadCover.isUploading && uploadCover.isLoading && (
<Progress value={uploadCover.uploadProgress} />
)}
</div> </div>
))}
</div>
</FormLabel>
)}
<button <FormLabel label={"Цена"}>
onClick={() => { <div
impactOccurred("light"); className={
setCoverExpanded((p) => !p); "w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"
}} }
style={{ >
height: isCoverExpanded ? "261px" : "68px", {rootStore.price} TON
}}
className={"flex w-[45px] items-center justify-center"}
>
{!isCoverExpanded && (
<svg
width="45"
height="14"
viewBox="0 0 45 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M31.2963 5.18519H29.2222V3.11111H28.1852V5.18519H26.1111V6.22222H28.1852V8.2963H29.2222V6.22222H31.2963V5.18519Z"
fill="white"
/>
<path
d="M33.0841 9.33333C33.9396 8.31744 34.4083 7.0318 34.4074 5.7037C34.4074 4.57562 34.0729 3.47287 33.4462 2.5349C32.8194 1.59693 31.9286 0.865871 30.8864 0.434171C29.8442 0.00247134 28.6974 -0.110481 27.591 0.109598C26.4846 0.329676 25.4683 0.872901 24.6706 1.67058C23.8729 2.46826 23.3297 3.48456 23.1096 4.59097C22.8895 5.69738 23.0025 6.8442 23.4342 7.88642C23.8659 8.92863 24.5969 9.81943 25.5349 10.4462C26.4729 11.0729 27.5756 11.4074 28.7037 11.4074C30.0318 11.4083 31.3174 10.9396 32.3333 10.0841L36.2668 14L37 13.2668L33.0841 9.33333ZM28.7037 10.3704C27.7807 10.3704 26.8785 10.0967 26.111 9.5839C25.3436 9.07112 24.7455 8.34228 24.3923 7.48956C24.0391 6.63684 23.9466 5.69853 24.1267 4.79328C24.3068 3.88804 24.7512 3.05652 25.4039 2.40387C26.0565 1.75123 26.888 1.30677 27.7933 1.12671C28.6985 0.946644 29.6368 1.03906 30.4896 1.39227C31.3423 1.74548 32.0711 2.34362 32.5839 3.11104C33.0967 3.87847 33.3704 4.78073 33.3704 5.7037C33.369 6.94096 32.8769 8.12715 32.002 9.00202C31.1271 9.87689 29.941 10.369 28.7037 10.3704Z"
fill="white"
/>
</svg>
)}
{isCoverExpanded && (
<svg
width="45"
height="15"
viewBox="0 0 45 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M31.2963 5.68519H26.1111V6.72222L31.2963 6.72222L31.2963 5.68519Z"
fill="white"
/>
<path
d="M33.0841 9.83333C33.9396 8.81744 34.4083 7.5318 34.4074 6.20371C34.4074 5.07562 34.0729 3.97287 33.4462 3.0349C32.8194 2.09693 31.9286 1.36587 30.8864 0.934171C29.8442 0.502471 28.6974 0.389519 27.591 0.609598C26.4846 0.829676 25.4683 1.3729 24.6706 2.17058C23.8729 2.96826 23.3297 3.98456 23.1096 5.09097C22.8895 6.19738 23.0025 7.3442 23.4342 8.38642C23.8659 9.42863 24.5969 10.3194 25.5349 10.9462C26.4729 11.5729 27.5756 11.9074 28.7037 11.9074C30.0318 11.9083 31.3174 11.4396 32.3333 10.5841L36.2668 14.5L37 13.7668L33.0841 9.83333ZM28.7037 10.8704C27.7807 10.8704 26.8785 10.5967 26.111 10.0839C25.3436 9.57112 24.7455 8.84228 24.3923 7.98956C24.0391 7.13684 23.9466 6.19853 24.1267 5.29328C24.3068 4.38804 24.7512 3.55652 25.4039 2.90387C26.0565 2.25123 26.888 1.80677 27.7933 1.62671C28.6985 1.44664 29.6368 1.53906 30.4896 1.89227C31.3423 2.24548 32.0711 2.84362 32.5839 3.61104C33.0967 4.37847 33.3704 5.28073 33.3704 6.20371C33.369 7.44096 32.8769 8.62715 32.002 9.50202C31.1271 10.3769 29.941 10.869 28.7037 10.8704Z"
fill="white"
/>
</svg>
)}
</button>
</div> </div>
</FormLabel> </FormLabel>
)}
{rootStore.royalty.map((royalty, index) => ( {rootStore.allowResale && (
<div key={index} className={"flex flex-col gap-[20px]"}> <FormLabel label={"Цена копии"}>
<div className={"flex w-full items-center gap-1"}> <div
<div className={"w-[83%]"}> className={
<FormLabel "w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"
labelClassName={"flex"} }
label={`Роялти_${index + 1}`} >
{rootStore.licenseResalePrice} TON
</div>
</FormLabel>
)}
<FormLabel label={"Файл"}>
{rootStore.fileSrc && !uploadFile.isUploading && (
<div
className={
"w-full border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm"
}
>
<ReactPlayer
playsinline={true}
controls={true}
width="100%"
config={{ file: { attributes: { playsInline: true } } }}
url={rootStore.fileSrc}
/>
</div>
)}
{uploadFile.isUploading && uploadFile.isLoading && (
<Progress value={uploadFile.uploadProgress} />
)}
</FormLabel>
{rootStore.allowCover && (
<FormLabel label={"Обложка"}>
<div
className={
"flex w-full items-center border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"
}
> >
<div <div
className={ style={{
"break-all border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold" height: isCoverExpanded ? "261px" : "68px",
} }}
className={"bg-bg w-full"}
> >
{royalty.address} {rootStore.cover && !uploadCover.isUploading && (
</div> <img
</FormLabel> src={URL.createObjectURL(rootStore.cover)}
</div> alt={"cover"}
className={"h-full w-full object-cover object-center"}
/>
)}
<div className={"h-auto w-[18%]"}> {uploadCover.isUploading && uploadCover.isLoading && (
<FormLabel labelClassName={"text-center"} label={"%"}> <Progress value={uploadCover.uploadProgress} />
<div )}
className={ </div>
"flex items-center justify-center border border-white bg-[#2B2B2B] py-[8px] text-sm font-bold"
} <button
onClick={() => {
impactOccurred("light");
setCoverExpanded((p) => !p);
}}
style={{
height: isCoverExpanded ? "261px" : "68px",
}}
className={"flex w-[45px] items-center justify-center"}
> >
{royalty.value.toString()} {!isCoverExpanded && (
</div> <svg
</FormLabel> width="45"
</div> height="14"
</div> viewBox="0 0 45 14"
</div> fill="none"
))} xmlns="http://www.w3.org/2000/svg"
>
<path
d="M31.2963 5.18519H29.2222V3.11111H28.1852V5.18519H26.1111V6.22222H28.1852V8.2963H29.2222V6.22222H31.2963V5.18519Z"
fill="white"
/>
<path
d="M33.0841 9.33333C33.9396 8.31744 34.4083 7.0318 34.4074 5.7037C34.4074 4.57562 34.0729 3.47287 33.4462 2.5349C32.8194 1.59693 31.9286 0.865871 30.8864 0.434171C29.8442 0.00247134 28.6974 -0.110481 27.591 0.109598C26.4846 0.329676 25.4683 0.872901 24.6706 1.67058C23.8729 2.46826 23.3297 3.48456 23.1096 4.59097C22.8895 5.69738 23.0025 6.8442 23.4342 7.88642C23.8659 8.92863 24.5969 9.81943 25.5349 10.4462C26.4729 11.0729 27.5756 11.4074 28.7037 11.4074C30.0318 11.4083 31.3174 10.9396 32.3333 10.0841L36.2668 14L37 13.2668L33.0841 9.33333ZM28.7037 10.3704C27.7807 10.3704 26.8785 10.0967 26.111 9.5839C25.3436 9.07112 24.7455 8.34228 24.3923 7.48956C24.0391 6.63684 23.9466 5.69853 24.1267 4.79328C24.3068 3.88804 24.7512 3.05652 25.4039 2.40387C26.0565 1.75123 26.888 1.30677 27.7933 1.12671C28.6985 0.946644 29.6368 1.03906 30.4896 1.39227C31.3423 1.74548 32.0711 2.34362 32.5839 3.11104C33.0967 3.87847 33.3704 4.78073 33.3704 5.7037C33.369 6.94096 32.8769 8.12715 32.002 9.00202C31.1271 9.87689 29.941 10.369 28.7037 10.3704Z"
fill="white"
/>
</svg>
)}
{/*{rootStore.authors.map((author, index) => (*/} {isCoverExpanded && (
{/* <FormLabel key={index} label={`Автор_${index + 1}`}>*/} <svg
{/* <div*/} width="45"
{/* className={*/} height="15"
{/* "break-all border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"*/} viewBox="0 0 45 15"
{/* }*/} fill="none"
{/* >*/} xmlns="http://www.w3.org/2000/svg"
{/* {author}*/} >
{/* </div>*/} <path
{/* </FormLabel>*/} d="M31.2963 5.68519H26.1111V6.72222L31.2963 6.72222L31.2963 5.68519Z"
{/*))}*/} fill="white"
/>
<path
d="M33.0841 9.83333C33.9396 8.81744 34.4083 7.5318 34.4074 6.20371C34.4074 5.07562 34.0729 3.97287 33.4462 3.0349C32.8194 2.09693 31.9286 1.36587 30.8864 0.934171C29.8442 0.502471 28.6974 0.389519 27.591 0.609598C26.4846 0.829676 25.4683 1.3729 24.6706 2.17058C23.8729 2.96826 23.3297 3.98456 23.1096 5.09097C22.8895 6.19738 23.0025 7.3442 23.4342 8.38642C23.8659 9.42863 24.5969 10.3194 25.5349 10.9462C26.4729 11.5729 27.5756 11.9074 28.7037 11.9074C30.0318 11.9083 31.3174 11.4396 32.3333 10.5841L36.2668 14.5L37 13.7668L33.0841 9.83333ZM28.7037 10.8704C27.7807 10.8704 26.8785 10.5967 26.111 10.0839C25.3436 9.57112 24.7455 8.84228 24.3923 7.98956C24.0391 7.13684 23.9466 6.19853 24.1267 5.29328C24.3068 4.38804 24.7512 3.55652 25.4039 2.90387C26.0565 2.25123 26.888 1.80677 27.7933 1.62671C28.6985 1.44664 29.6368 1.53906 30.4896 1.89227C31.3423 2.24548 32.0711 2.84362 32.5839 3.61104C33.0967 4.37847 33.3704 5.28073 33.3704 6.20371C33.369 7.44096 32.8769 8.62715 32.002 9.50202C31.1271 10.3769 29.941 10.869 28.7037 10.8704Z"
fill="white"
/>
</svg>
)}
</button>
</div>
</FormLabel>
)}
{rootStore.royalty.map((royalty, index) => (
<div key={index} className={"flex flex-col gap-[20px]"}>
<div className={"flex w-full items-center gap-1"}>
<div className={"w-[83%]"}>
<FormLabel
labelClassName={"flex"}
label={`Роялти_${index + 1}`}
>
<div
className={
"break-all border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"
}
>
{royalty.address}
</div>
</FormLabel>
</div>
<div className={"h-auto w-[18%]"}>
<FormLabel labelClassName={"text-center"} label={"%"}>
<div
className={
"flex items-center justify-center border border-white bg-[#2B2B2B] py-[8px] text-sm font-bold"
}
>
{royalty.value.toString()}
</div>
</FormLabel>
</div>
</div>
</div>
))}
{/*{rootStore.authors.map((author, index) => (*/}
{/* <FormLabel key={index} label={`Автор_${index + 1}`}>*/}
{/* <div*/}
{/* className={*/}
{/* "break-all border border-white bg-[#2B2B2B] px-[10px] py-[8px] text-sm font-bold"*/}
{/* }*/}
{/* >*/}
{/* {author}*/}
{/* </div>*/}
{/* </FormLabel>*/}
{/*))}*/}
</section>
<Button
isLoading={
uploadFile.isLoading ||
uploadCover.isLoading ||
createContent.isLoading
}
onClick={handleSubmit}
label={"Все верно!"}
className={"mt-[30px] py-[16px]"}
includeArrows={true}
/>
</section> </section>
<Button
isLoading={
uploadFile.isLoading ||
uploadCover.isLoading ||
createContent.isLoading
}
onClick={handleSubmit}
label={"Все верно!"}
className={"mt-[30px] py-[16px]"}
includeArrows={true}
/>
</section>
); );
}; };
+57 -116
View File
@@ -9,10 +9,10 @@ import { Button } from "~/shared/ui/button";
import { useRootStore } from "~/shared/stores/root"; import { useRootStore } from "~/shared/stores/root";
import { BackButton } from "~/shared/ui/back-button"; import { BackButton } from "~/shared/ui/back-button";
const MIN_PRICE = 0.07; const MIN_PRICE = 0.15;
const MIN_RESALE_PRICE = 0.07; const MIN_RESALE_PRICE = 0.15;
const RECOMMENDED_PRICE = 0.15; // const RECOMMENDED_PRICE = 0.15;
// const RECOMMENDED_RESALE_PRICE = 0.15; // const RECOMMENDED_RESALE_PRICE = 0.15;
type PriceStepProps = { type PriceStepProps = {
@@ -24,48 +24,31 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
const rootStore = useRootStore(); const rootStore = useRootStore();
const formSchema = useMemo(() => { const formSchema = useMemo(() => {
const parsePrice = (value: unknown) => {
if (typeof value === "string" || typeof value === "number") {
const stringValue = value.toString().replace(",", ".");
const parsedValue = parseFloat(stringValue);
return isNaN(parsedValue) ? undefined : parsedValue;
}
return undefined;
};
if (rootStore.allowResale) { if (rootStore.allowResale) {
return z.object({ return z.object({
price: z.preprocess( price: z.preprocess(
(value) => { parsePrice,
const parsed = parseFloat(value as string); z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
return isNaN(parsed) ? undefined : parsed;
},
z
.number()
.min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`),
), ),
resaleLicensePrice: z resaleLicensePrice: z
.preprocess( .preprocess(parsePrice, z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_RESALE_PRICE, `Цена копии должна быть минимум ${MIN_RESALE_PRICE} TON.`))
(value) => { .optional(),
if (value === undefined || value === "" || value === 0)
return undefined;
const parsed = parseFloat(value as string);
return isNaN(parsed) ? undefined : parsed;
},
z
.number()
.min(
MIN_RESALE_PRICE,
`Цена копии должна быть минимум ${MIN_RESALE_PRICE} TON.`,
),
)
.optional(),
}); });
} }
return z.object({ return z.object({
price: z.preprocess( price: z.preprocess(
(value) => { parsePrice,
const parsed = parseFloat(value as string); z.number({required_error: 'Цена не соответствует требованиям'}).min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`)
return isNaN(parsed) ? undefined : parsed;
},
z.number().min(MIN_PRICE, `Цена должна быть минимум ${MIN_PRICE} TON.`),
), ),
}); });
}, [rootStore.allowResale]); }, [rootStore.allowResale]);
@@ -75,12 +58,10 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
const form = useForm<FormValues>({ const form = useForm<FormValues>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
mode: "onChange", mode: "onChange",
defaultValues: { defaultValues: {
price: rootStore.price, price: rootStore.price || MIN_PRICE,
//@ts-expect-error Fix typings //@ts-expect-error Fix typings
resaleLicensePrice: rootStore?.licenseResalePrice, resaleLicensePrice: rootStore?.licenseResalePrice || MIN_RESALE_PRICE,
}, },
}); });
@@ -92,13 +73,11 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
form.handleSubmit(async (values: FormValues) => { form.handleSubmit(async (values: FormValues) => {
try { try {
rootStore.setPrice(values.price); rootStore.setPrice(values.price);
//@ts-expect-error Fix typings //@ts-expect-error Fix typings
if (values?.resaleLicensePrice) { if (values?.resaleLicensePrice) {
//@ts-expect-error Fix typings //@ts-expect-error Fix typings
rootStore.setLicenseResalePrice(values?.resaleLicensePrice); rootStore.setLicenseResalePrice(values?.resaleLicensePrice);
} }
nextStep(); nextStep();
} catch (error) { } catch (error) {
console.error("Error: ", error); console.error("Error: ", error);
@@ -107,81 +86,43 @@ export const PriceStep = ({ nextStep, prevStep }: PriceStepProps) => {
}; };
return ( return (
<section className={"mt-4 px-4 pb-8"}> <section className={"mt-4 px-4 pb-8"}>
<BackButton onClick={prevStep} /> <BackButton onClick={prevStep} />
<div className={"mb-[30px] flex flex-col text-sm"}>
<div className={"mb-[30px] flex flex-col text-sm"}> <span className={"ml-4"}>/Укажите цену</span>
<span className={"ml-4"}>/Укажите цену</span> <div>
<div> 4/<span className={"text-[#7B7B7B]"}>5</span>
4/<span className={"text-[#7B7B7B]"}>5</span>
</div>
</div>
<div className={"flex flex-col gap-[20px]"}>
<FormLabel label={"Цена TON"}>
<div className={"my-2 flex flex-col gap-1.5"}>
<p className={"text-xs"}>Минимальная стоимость {MIN_PRICE} TON.</p>
<p className={"text-xs"}>
Рекомендуемая стоимость {RECOMMENDED_PRICE} TON.
</p>
</div> </div>
</div>
<Input <div className={"flex flex-col gap-[20px]"}>
error={form.formState.errors?.price} <FormLabel label={"Цена продажи TON"}>
placeholder={"[ Введите цену ]"} <div className={"my-2 flex flex-col gap-1.5"}>
{...form.register("price")} <p className={"text-xs"}>Минимальная стоимость {MIN_PRICE} TON.</p>
/> {/* <p className={"text-xs"}>Рекомендуемая стоимость {RECOMMENDED_PRICE} TON.</p> */}
</FormLabel> </div>
<Input
{/*<div className={"flex flex-col gap-2"}>*/} error={form.formState.errors?.price}
{/* <FormLabel*/} placeholder={"[ Введите цену ]"}
{/* labelClassName={"flex"}*/} inputMode="decimal"
{/* label={"Разрешить копии"}*/} pattern="[0-9]*[.,]?[0-9]*"
{/* formLabelAddon={*/} {...form.register("price", {
{/* <Checkbox*/} onChange: (e) => {
{/* checked={rootStore.allowResale}*/} const value = e.target.value;
{/* onClick={() => {*/} if (!/^\d*[.,]?\d*$/.test(value)) {
{/* rootStore.setAllowResale(!rootStore.allowResale);*/} e.target.value = value.replace(/[^\d.,]/g, '');
{/* }}*/} }
{/* />*/} }
{/* }*/} })}
{/* />*/} />
</FormLabel>
{/* {rootStore.allowResale && (*/} </div>
{/* <FormLabel label={"Цена копии TON"}>*/} <Button
{/* <div className={"my-2 flex flex-col gap-1.5"}>*/} className={"mt-[30px]"}
{/* <p className={"text-xs"}>*/} onClick={handleSubmit}
{/* Это цена, по которой пользователи будут покупать и*/} includeArrows={true}
{/* перепродавать ваш контент.*/} label={"Далее"}
{/* </p>*/} disabled={!form.formState.isValid}
/>
{/* <p className={"text-xs"}>*/} </section>
{/* Минимальная стоимость {MIN_RESALE_PRICE} TON.*/}
{/* </p>*/}
{/* <p className={"text-xs"}>*/}
{/* Рекомендуемая стоимость {RECOMMENDED_RESALE_PRICE} TON.*/}
{/* </p>*/}
{/* </div>*/}
{/* <Input*/}
{/* //@ts-expect-error Fix typings*/}
{/* error={form.formState.errors?.resaleLicensePrice}*/}
{/* placeholder={"[ Введите цену копии ]"}*/}
{/* //@ts-expect-error Fix typings*/}
{/* {...form.register("resaleLicensePrice")}*/}
{/* />*/}
{/* </FormLabel>*/}
{/* )}*/}
{/*</div>*/}
</div>
<Button
className={"mt-[30px]"}
onClick={handleSubmit}
includeArrows={true}
label={"Далее"}
disabled={!form.formState.isValid}
/>
</section>
); );
}; };
+50 -5
View File
@@ -1,4 +1,4 @@
import { useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app"; import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app";
import { Input } from "~/shared/ui/input"; import { Input } from "~/shared/ui/input";
@@ -11,17 +11,31 @@ import { Spread } from "~/shared/ui/icons/spread.tsx";
import { ConfirmModal } from "~/pages/root/steps/royalty-step/components/confirm-modal"; import { ConfirmModal } from "~/pages/root/steps/royalty-step/components/confirm-modal";
import { useRootStore } from "~/shared/stores/root"; import { useRootStore } from "~/shared/stores/root";
import { BackButton } from "~/shared/ui/back-button"; import { BackButton } from "~/shared/ui/back-button";
import { useTonConnectUI } from "@tonconnect/ui-react";
import { Address } from "@ton/core";
import { FieldError } from "react-hook-form";
type RoyaltyStepProps = { type RoyaltyStepProps = {
prevStep(): void; prevStep(): void;
nextStep(): void; nextStep(): void;
}; };
const isValidTonAddress = (address: string): boolean => {
try {
if (!address) return false;
Address.parse(address);
return true;
} catch {
return false;
}
};
export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => { export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
const [impactOccurred] = useHapticFeedback(); const [impactOccurred] = useHapticFeedback();
const [isDeleteAllOpen, setDeleteAllOpen] = useState(false); const [isDeleteAllOpen, setDeleteAllOpen] = useState(false);
const [isSpreadOpen, setSpreadOpen] = useState(false); const [isSpreadOpen, setSpreadOpen] = useState(false);
const [addressErrors, setAddressErrors] = useState<Record<number, FieldError | undefined>>({});
const { royalty, setRoyalty, isPercentHintOpen, setPercentHintOpen } = const { royalty, setRoyalty, isPercentHintOpen, setPercentHintOpen } =
useRootStore(); useRootStore();
@@ -41,12 +55,24 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
}; };
const handleWalletChange = (index: number, address: string) => { const handleWalletChange = (index: number, address: string) => {
const isValid = isValidTonAddress(address);
setAddressErrors({
...addressErrors,
[index]: !isValid
? {
type: 'validation',
message: 'Неверный адрес TON'
}
: undefined
});
const newRoyalty = royalty.map((member, i) => const newRoyalty = royalty.map((member, i) =>
i === index ? { ...member, address } : member, i === index ? { ...member, address } : member
); );
setRoyalty(newRoyalty); setRoyalty(newRoyalty);
}; };
const handlePercentChange = (index: number, value: string) => { const handlePercentChange = (index: number, value: string) => {
const percentNumber = parseInt(value, 10) || 0; const percentNumber = parseInt(value, 10) || 0;
const newRoyalty = royalty.map((royalty, i) => const newRoyalty = royalty.map((royalty, i) =>
@@ -66,11 +92,29 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
const isValid = useMemo(() => { const isValid = useMemo(() => {
return ( return (
royalty.every((member) => member.address && member.value >= 0) && royalty.every((member) => isValidTonAddress(member.address) && member.value >= 0) &&
royalty.reduce((acc, curr) => acc + curr.value, 0) === 100 royalty.reduce((acc, curr) => acc + curr.value, 0) === 100
); );
}, [royalty]); }, [royalty]);
const [tonConnectUI] = useTonConnectUI();
// Устанавливаем адрес из tonConnectUI.account при загрузке страницы
useEffect(() => {
if (!tonConnectUI.account) return;
if (royalty.length === 0) {
// First initialization with 100%
setRoyalty([{
address: Address.parse(tonConnectUI.account.address).toString({
bounceable: false,
urlSafe: true,
testOnly: false,
}),
value: 100
}]);
}
}, [tonConnectUI.account, setRoyalty, royalty]);
return ( return (
<section className={"mt-4 px-4 pb-8"}> <section className={"mt-4 px-4 pb-8"}>
{isPercentHintOpen && ( {isPercentHintOpen && (
@@ -117,7 +161,7 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
<section className={"flex flex-col gap-1.5"}> <section className={"flex flex-col gap-1.5"}>
{royalty.map((member, index) => ( {royalty.map((member, index) => (
<div key={index} className={"flex flex-col gap-[20px]"}> <div key={index} className={"flex flex-col gap-[20px]"}>
<div className={"flex w-full items-center gap-1"}> <div className={"flex w-full items-start gap-1"}>
<div className={"w-[83%]"}> <div className={"w-[83%]"}>
<FormLabel <FormLabel
labelClassName={"flex"} labelClassName={"flex"}
@@ -134,7 +178,8 @@ export const RoyaltyStep = ({ nextStep, prevStep }: RoyaltyStepProps) => {
value={member.address} value={member.address}
onChange={(e) => handleWalletChange(index, e.target.value)} onChange={(e) => handleWalletChange(index, e.target.value)}
placeholder={"[ Введите адрес криптокошелька TON ]"} placeholder={"[ Введите адрес криптокошелька TON ]"}
/> error={addressErrors[index]}
/>
</FormLabel> </FormLabel>
</div> </div>
+160 -24
View File
@@ -1,45 +1,181 @@
import { useEffect, useState } from "react";
import { useTonConnectUI } from "@tonconnect/ui-react";
import { Button } from "~/shared/ui/button"; import { Button } from "~/shared/ui/button";
import { useAuth } from "~/shared/services/auth"; import { useAuth } from "~/shared/services/auth";
import { Address } from "@ton/core";
type WelcomeStepProps = { type WelcomeStepProps = {
nextStep(): void; nextStep(): void;
}; };
export const WelcomeStep = ({ nextStep }: WelcomeStepProps) => { export const WelcomeStep = ({ nextStep }: WelcomeStepProps) => {
const [tonConnectUI] = useTonConnectUI();
const [isLoaded, setLoaded] = useState(false);
const [isConnected, setIsConnected] = useState(tonConnectUI.connected);
const [address, setAddress] = useState('');
console.log("💩💩💩 enter WelcomeStep");
const auth = useAuth(); const auth = useAuth();
console.log("💩💩💩 after useAuth");
useEffect(() => {
localStorage.setItem('disclaimerAccepted', 'false');
}, []);
useEffect(() => {
const unsubscribe = tonConnectUI.onStatusChange((wallet) => {
setIsConnected(!!wallet);
});
return () => {
unsubscribe();
};
}, [tonConnectUI]);
useEffect(() => {
if(tonConnectUI.account){
setAddress(Address.parse(tonConnectUI.account?.address).toString({
bounceable: false,
urlSafe: true,
testOnly: false,
}),)
}
}, [tonConnectUI.account]);
const handleNextClick = async () => { const handleNextClick = async () => {
const res = await auth.mutateAsync(); if (tonConnectUI.connected) {
sessionStorage.setItem("auth_v1_token", res.data.auth_v1_token); await auth.mutateAsync();
nextStep(); nextStep();
} else {
try {
await tonConnectUI.openModal();
await auth.mutateAsync();
} catch (error) {
console.error('Failed to connect or authenticate:', error);
}
}
}; };
const handleDisconnect = async () => {
if (isConnected){
try {
await tonConnectUI.disconnect();
} catch (error) {
console.error('Failed to disconnect:', error);
}
}
}
// useEffect(() => {
// const first = setTimeout(async () => {
// console.log("💩💩💩 call auth");
// await auth.mutateAsync();
// }, 1000);
//
// const second = setTimeout(() => {
// setLoaded(true);
//
// if (tonConnectUI.connected) {
// nextStep();
// }
// }, 4000);
//
// return () => {
// clearTimeout(first);
// clearTimeout(second);
// };
// }, [tonConnectUI.connected]);
useEffect(() => {
const timeout = setTimeout(() => {
setLoaded(true)
}, 5000);
return () => {
clearTimeout(timeout);
}
}, []);
if (!isLoaded) {
return (
<section
className={"relative flex h-[100vh] items-center justify-center"}
>
<img alt={"splash"} className={"mb-20 w-[50%]"} src={"/splash.gif"} />
</section>
);
}
return ( return (
<section className={"mt-4 px-4"}> <section className={"mt-4 flex flex-col justify-between min-h-[calc(100vh-32px)] px-4"}>
<div className={"flex gap-2 text-sm"}> <div className="flex items-center justify-center overflow-hidden w-[100%] h-[400px]">
<span>/ Добро пожаловать в MY</span> <img
alt={"splash"}
className={"w-[50%] shrink-0"}
src={"/splash.gif"}
/>
</div>
<div className={"flex items-center gap-0"}> <div className={"flex flex-col mt-4"}>
<span>[</span> <div className={"flex gap-2 text-sm"}>
<div className={"mb-0.5 h-3 w-3 rounded-full bg-primary"} /> <span>/ Добро пожаловать в MY</span>
<span>]:</span>
<div className={"flex items-center gap-0"}>
<span>[</span>
<div className={"mb-0.5 h-3 w-3 rounded-full bg-primary"} />
<span>]:</span>
</div>
</div> </div>
</div>
<div className={"mt-2"}>
<p className={"text-sm"}>
децентрализованную систему монетизации контента. для продолжения
необходимо подключить криптокошелек TON
</p>
</div>
<Button {isConnected ?
label={"Подключить криптокошелёк TON"} <>
className={"mt-[30px]"} <div className={"mt-2"}>
includeArrows={true} <p className={"text-sm"}>
isLoading={auth.isLoading} Вы зарегистрированы под кошельком: <br/>
onClick={handleNextClick} <span className={"font-bold"}>{address}</span>
/> </p>
</div>
<div className="flex flex-col">
<Button
className={"mt-[30px]"}
label={"Продолжить"}
includeArrows={false}
isLoading={auth.isLoading}
onClick={handleNextClick}
/>
<Button
className={"mt-[20px] bg-inherit"}
label={"Изменить кошелек"}
includeArrows={false}
isLoading={false}
onClick={handleDisconnect}
/>
</div>
</>
:
<>
<div className={"mt-2"}>
<p className={"text-sm"}>
Здесь вы можете загрузить свой контент. <br/> Для продолжения подключите кошелек.
</p>
</div>
<div className={"mt-2"}>
<p className={"text-sm"}>
Не волнуйтесь. Вы сможете поменять свой выбор в любой момент.
</p>
</div>
<Button
className={"mt-[30px]"}
label={"Подключить криптокошелёк TON"}
includeArrows={true}
isLoading={auth.isLoading}
onClick={handleNextClick}
/>
</>}
</div>
</section> </section>
); );
}; };
@@ -0,0 +1,71 @@
import { useHapticFeedback, useWebApp } from "@vkruglikov/react-telegram-web-app";
import { useEffect } from "react";
import { Button } from "~/shared/ui/button";
type CongratsModalProps = {
onConfirm(): void;
};
export const CongratsModal = ({
onConfirm,
}: CongratsModalProps) => {
const [impactOccurred] = useHapticFeedback();
const WebApp = useWebApp();
const handleClick = (fn: () => void) => {
impactOccurred("light");
fn();
};
useEffect(() => {
// Отключаем вертикальные свайпы при монтировании компонента
if (WebApp && WebApp.disableVerticalSwipes) {
WebApp.disableVerticalSwipes();
}
// Включаем вертикальные свайпы обратно при размонтировании
return () => {
if (WebApp && WebApp.enableVerticalSwipes) {
WebApp.enableVerticalSwipes();
}
};
}, []);
return (
<div
className={
"fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center bg-black/80 px-[15px]"
}
>
<div className={"flex flex-col max-h-[80vh] w-[85vw] max-w-md"}>
<div
className={
"border border-white bg-[#1D1D1B] px-[15px] py-[16px] text-start flex flex-col gap-6 h-full overflow-y-auto"
}
>
<p className="mt-4">
<span className="text-xl">🎉</span>
<span className="px-1 font-bold">Поздравляем с покупкой!</span>
<span className="text-xl">🎉</span>
</p>
<p className="">
Ваш контент уже в пути! В ближайшее время в чат-боте <strong>MY Player</strong> вам придёт сообщение с доступом к купленному материалу. В этом чат-боте будет храниться весь ваш приобретённый контент.
</p>
<p className="flex flex-col">
<strong className="w-full">Теперь вы можете самостоятельно осуществить продажу купленного контента.</strong>
<span>
Перешлите сообщение из чата My-Player друзьям или опубликуйте в своём Telegram-канале. Каждый, кто купит контент по вашей ссылке, принесёт вам доход, а автору — заслуженное вознаграждение.
</span>
</p>
<p className="flex flex-col">
Спасибо, что выбираете MY!
</p>
</div>
<Button
className={"mt-[20px]"}
label={"Ок"}
includeArrows={false}
onClick={() => handleClick(onConfirm)}
/>
</div>
</div>
);
};
@@ -0,0 +1,69 @@
import { useHapticFeedback, useWebApp } from "@vkruglikov/react-telegram-web-app";
import { useEffect } from "react";
import { Button } from "~/shared/ui/button";
type ErrorModalProps = {
onConfirm(): void;
};
export const ErrorModal = ({
onConfirm,
}: ErrorModalProps) => {
const [impactOccurred] = useHapticFeedback();
const WebApp = useWebApp();
const handleClick = (fn: () => void) => {
impactOccurred("light");
fn();
};
useEffect(() => {
// Отключаем вертикальные свайпы при монтировании компонента
if (WebApp && WebApp.disableVerticalSwipes) {
WebApp.disableVerticalSwipes();
}
// Включаем вертикальные свайпы обратно при размонтировании
return () => {
if (WebApp && WebApp.enableVerticalSwipes) {
WebApp.enableVerticalSwipes();
}
};
}, []);
return (
<div
className={
"fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center bg-black/80 px-[15px]"
}
>
<div className={"flex flex-col max-h-[80vh] w-[85vw] max-w-md"}>
<div
className={
"border border-white bg-[#1D1D1B] px-[15px] py-[16px] text-start flex flex-col gap-6 h-full overflow-y-auto"
}
>
<p className="mt-4">
<span className="font-bold">Ошибка запроса транзакции</span>
</p>
<p className="">
Не удалось отправить запрос на выполнение транзакции.
</p>
<p className="flex flex-col">
<span>
Попробуйте переподключить кошелек и повторить попытку. Если ошибка сохраняется, попробуйте запросить транзакцию еще раз.
</span>
</p>
<p className="flex flex-col">
Если проблема не исчезает, убедитесь, что ваш кошелек работает корректно, или свяжитесь с поддержкой.
</p>
</div>
<Button
className={"mt-[20px]"}
label={"Ок"}
includeArrows={false}
onClick={() => handleClick(onConfirm)}
/>
</div>
</div>
);
};
+307
View File
@@ -0,0 +1,307 @@
import ReactPlayer from 'react-player/lazy';
import { useTonConnectUI } from '@tonconnect/ui-react';
import { useWebApp } from '@vkruglikov/react-telegram-web-app';
import { Button } from '~/shared/ui/button';
import { usePurchaseContent, useViewContent } from '~/shared/services/content';
import { fromNanoTON } from '~/shared/utils';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { AudioPlayer } from '~/shared/ui/audio-player';
import { useAuth } from '~/shared/services/auth';
import { CongratsModal } from './components/congrats-modal';
import { ErrorModal } from './components/error-modal';
import { useRootStore } from '~/shared/stores/root';
import { useTgFullscreen } from '~/shared/hooks/use-tgfullscreen';
type InvoiceStatus = 'paid' | 'failed' | 'cancelled' | 'pending';
// Add type for invoice event
interface InvoiceEvent {
url: string;
status: InvoiceStatus;
}
export const ViewContentPage = () => {
const WebApp = useWebApp();
const { addNotification } = useRootStore();
const { data: content, refetch: refetchContent } = useViewContent(
WebApp.initDataUnsafe?.start_param
);
const { mutateAsync: purchaseContent } = usePurchaseContent();
const [tonConnectUI] = useTonConnectUI();
const { isFullscreen, toggleFullscreen } = useTgFullscreen();
const auth = useAuth();
const [isCongratsModal, setIsCongratsModal] = useState(false);
const [isErrorModal, setIsErrorModal] = useState(false);
const handleBuyContentTON = useCallback(async () => {
try {
// Helper function to wait for wallet connection
const waitForConnection = async (timeoutMs = 30000, intervalMs = 500) => {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
if (tonConnectUI.connected) {
addNotification('Кошелек подключен', 'success');
return true;
}
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
addNotification('Время ожидания подключения кошелька истекло', 'warning');
return false; // Timed out
};
// If not connected, start connection process
if (!tonConnectUI.connected) {
console.log('DEBUG: Wallet not connected, opening modal');
// Open connection modal
await tonConnectUI.openModal();
// Wait for connection
const connected = await waitForConnection();
if (!connected) {
console.log('DEBUG: Connection timed out or was cancelled');
return;
}
console.log('DEBUG: Connection successful, authenticating');
await auth.mutateAsync();
} else {
// Already connected, just authenticate
await auth.mutateAsync();
}
// Proceed with purchase
console.log('DEBUG: Proceeding with purchase');
const contentResponse = await purchaseContent({
content_address: WebApp.initDataUnsafe?.start_param,
license_type: 'resale',
});
const transactionResponse = await tonConnectUI.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 86400, // 24 hours
messages: [
{
amount: contentResponse.data.amount,
address: contentResponse.data.address,
payload: contentResponse.data.payload,
},
],
});
if (transactionResponse.boc) {
void refetchContent();
setIsCongratsModal(true);
console.log(transactionResponse.boc, 'PURCHASED');
} else {
setIsErrorModal(true);
console.error('Transaction failed:', transactionResponse);
}
} catch (error) {
setIsErrorModal(true);
console.error('Error handling Ton Connect subscription:', error);
}
}, [auth, purchaseContent, refetchContent, tonConnectUI, WebApp.initDataUnsafe?.start_param]);
const handleBuyContentStars = useCallback(async () => {
try {
if (!content?.data?.invoice.url) {
console.error('No invoice URL available');
return;
}
// Add event listener for invoice closing with typed event
const handleInvoiceClosed = (event: InvoiceEvent) => {
if (event.url === content.data.invoice.url) {
if (event.status === 'paid') {
void refetchContent();
setIsCongratsModal(true);
} else if (event.status === 'failed' || event.status === 'cancelled') {
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
}
}
};
WebApp.onEvent('invoiceClosed', handleInvoiceClosed);
await WebApp.openInvoice(content.data.invoice.url, (status: InvoiceStatus) => {
console.log('Invoice status:', status);
if (status === 'paid') {
void refetchContent();
setIsCongratsModal(true);
} else if (status === 'failed' || status === 'cancelled') {
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
}
});
return () => {
WebApp.offEvent('invoiceClosed', handleInvoiceClosed);
};
} catch (error) {
console.error('Payment failed:', error);
// setIsErrorModal(true); // Turn on if need in error modal. Update text in it to match both way of payment errors
}
}, [content, refetchContent]);
const haveLicense = useMemo(() => {
document.title = content?.data?.display_options?.metadata?.name;
return (
content?.data?.have_licenses?.includes('listen') ||
content?.data?.have_licenses?.includes('resale')
);
}, [content]);
useEffect(() => {
const interval = setInterval(() => {
void refetchContent();
}, 5000);
return () => clearInterval(interval);
}, []);
const handleConfirmCongrats = () => {
setIsCongratsModal(!isCongratsModal);
};
const handleErrorModal = () => {
setIsErrorModal(!isErrorModal);
};
const handleDwnldContent = async () => {
try {
const fileUrl = content?.data?.display_options?.content_url;
const fileName = content?.data?.display_options?.metadata?.name || 'content';
const fileFormat = content?.data?.content_ext || '.raw';
await WebApp.downloadFile({
url: fileUrl,
file_name: fileName + '.' + fileFormat,
});
} catch (error) {
console.error('Error downloading content:', error);
}
};
return (
<main className={'min-h-screen flex w-full flex-col gap-[50px] px-4 '}>
{isCongratsModal && <CongratsModal onConfirm={handleConfirmCongrats} />}
{isErrorModal && <ErrorModal onConfirm={handleErrorModal} />}
<div
className={`${isFullscreen ? 'left-[50%] translate-x-[-50%] fixed top-11 lg:top-2' : 'left-4 absolute top-2'} z-50`}
>
<button
onClick={toggleFullscreen}
className="bg-primary bg-opacity-80 w-8 h-8 flex items-center justify-center rounded-full text-white"
>
<svg
width="20"
height="20"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2 6V2H6M8 2H12V6M12 8V12H8M6 12H2V8"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</div>
<div className="mt-[50px]">
{content?.data?.content_type.startsWith('audio') &&
content?.data?.display_options?.metadata?.image && (
<div className={'mt-[30px] h-[314px] w-full'}>
<img
alt={'content_image'}
className={'h-full w-full object-cover object-center'}
src={content?.data?.display_options?.metadata?.image}
/>
</div>
)}
{content?.data?.content_type.startsWith('audio') ? (
<AudioPlayer src={content?.data?.display_options?.content_url} />
) : (
<div className="w-full video-container">
<ReactPlayer
playsinline={true}
controls={true}
width="100%"
height="auto"
className="react-player"
allowFullScreen={true}
url={content?.data?.display_options?.content_url}
config={{
file: {
attributes: {
playsInline: true,
autoPlay: true,
poster:
content?.data?.display_options?.metadata?.image ||
undefined,
},
},
}}
/>
</div>
)}
</div>
<section className={'flex flex-col'}>
<h1 className={'text-[20px] font-bold'}>
{content?.data?.display_options?.metadata?.name}
</h1>
{/*<h2>Russian</h2>*/}
{/*<h2>2022</h2>*/}
<p className={'mt-2 text-[12px]'}>
{content?.data?.display_options?.metadata?.description}
</p>
</section>
<div className="mt-auto pb-2">
{content?.data?.downloadable && (
<Button
onClick={() => handleDwnldContent()}
className={'h-[48px] mb-4'}
label={`Скачать контент`}
/>
)}
{!haveLicense && (
<div className="flex flex-row gap-4">
<Button
onClick={handleBuyContentTON}
className={'mb-4 h-[48px] px-2'}
label={`Купить за ${fromNanoTON(content?.data?.encrypted?.license?.resale?.price)} ТОН`}
includeArrows={content?.data?.invoice ? false : true}
/>
{content?.data?.invoice && (
<Button
onClick={handleBuyContentStars}
className={'mb-4 h-[48px] px-2'}
label={`Купить за ${content?.data?.invoice?.amount} ⭐️`}
/>
)}
</div>
)}
<Button
onClick={() => {
WebApp.openTelegramLink(`https://t.me/MY_UploaderRobot`);
}}
className={'h-[48px] bg-darkred'}
label={`Загрузить свой контент`}
/>
{tonConnectUI.connected && (
<Button
onClick={() => {
tonConnectUI.disconnect();
}}
className={'h-[48px] bg-darkred mt-4'}
label={`Отключить кошелек`}
/>
)}
</div>
</main>
);
};
+20 -7
View File
@@ -1,4 +1,8 @@
import { ReactNode, useMemo, useState } from "react"; import { useTonConnectUI } from "@tonconnect/ui-react";
import { ReactNode, useEffect, useMemo, useState } from "react";
const CHECK_INTERVAL = 20000;
export const useSteps = ( export const useSteps = (
sections: ({ sections: ({
@@ -9,15 +13,24 @@ export const useSteps = (
prevStep(): void; prevStep(): void;
}) => ReactNode[], }) => ReactNode[],
) => { ) => {
const [tonConnectUI] = useTonConnectUI();
const [step, setStep] = useState(0); const [step, setStep] = useState(0);
const nextStep = () => { // If connection is lost, reset the step
return setStep((s) => s + 1); useEffect(() => {
}; const interval = setInterval(() => {
if (!tonConnectUI.connected && step !== 0) {
setStep(0);
}
}, CHECK_INTERVAL);
const prevStep = () => { return () => clearInterval(interval);
return setStep((s) => s - 1); }, []);
};
const nextStep = () => setStep((s) => s + 1);
const prevStep = () => setStep((s) => s - 1);
const ActiveSection = useMemo(() => { const ActiveSection = useMemo(() => {
return sections({ nextStep, prevStep })[step]; return sections({ nextStep, prevStep })[step];
@@ -0,0 +1,87 @@
import { useState, useCallback, useEffect } from 'react';
import { useWebApp } from '@vkruglikov/react-telegram-web-app';
import { viewport } from '@telegram-apps/sdk';
/**
* Hook for managing Telegram WebApp fullscreen state
* @returns {Object} Object containing isFullscreen state and toggleFullscreen function
*/
export const useTgFullscreen = () => {
const WebApp = useWebApp();
const [isFullscreen, setIsFullscreen] = useState(false);
// Initialize viewport once on mount
useEffect(() => {
// Mount viewport if available
if (viewport.mount.isAvailable() && !viewport.isMounted()) {
viewport.mount();
}
// Check current fullscreen state
if (viewport.isFullscreen && typeof viewport.isFullscreen === 'function') {
setIsFullscreen(viewport.isFullscreen());
}
// Use the WebApp event system for tracking changes
const handleViewportChanged = () => {
if (WebApp.isExpanded) {
setIsFullscreen(true);
}
};
try {
WebApp.onEvent('viewportChanged', handleViewportChanged);
} catch (error) {
console.warn('Could not add viewportChanged event listener:', error);
}
return () => {
try {
WebApp.offEvent('viewportChanged', handleViewportChanged);
} catch (error) {
console.warn('Could not remove viewportChanged event listener:', error);
}
};
}, [WebApp]);
const toggleFullscreen = useCallback(async () => {
try {
if (!isFullscreen) {
// Try to enter fullscreen
if (viewport.requestFullscreen && viewport.requestFullscreen.isAvailable()) {
await viewport.requestFullscreen();
setIsFullscreen(true);
} else if (viewport.expand && viewport.expand.isAvailable()) {
viewport.expand();
setIsFullscreen(true);
} else {
WebApp.expand();
setIsFullscreen(true);
}
} else {
// Try to exit fullscreen
if (viewport.exitFullscreen && viewport.exitFullscreen.isAvailable()) {
await viewport.exitFullscreen();
setIsFullscreen(false);
} else {
setIsFullscreen(false);
}
}
} catch (error) {
console.error('Error toggling fullscreen:', error);
if (!isFullscreen) {
try {
WebApp.expand();
setIsFullscreen(true);
} catch {
console.error('All fullscreen methods failed');
}
} else {
setIsFullscreen(false);
}
}
}, [WebApp, isFullscreen]);
return { isFullscreen, toggleFullscreen };
};
+2
View File
@@ -0,0 +1,2 @@
import { Buffer } from 'buffer';
globalThis.Buffer = Buffer;
+8 -8
View File
@@ -1,17 +1,17 @@
import axios from "axios"; import axios from 'axios';
export const APP_API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string; export const APP_API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
export const request = axios.create({ export const request = axios.create({
baseURL: APP_API_BASE_URL, baseURL: APP_API_BASE_URL,
}); });
request.interceptors.request.use((config) => { request.interceptors.request.use((config) => {
const auth_v1_token = sessionStorage.getItem("auth_v1_token"); const auth_v1_token = localStorage.getItem('auth_v1_token');
if (auth_v1_token) { if (auth_v1_token) {
config.headers.Authorization = auth_v1_token; config.headers.Authorization = auth_v1_token;
} }
return config; return config;
}); });
+304 -15
View File
@@ -1,22 +1,311 @@
import { useMutation } from "react-query"; import { useRef, useEffect } from 'react';
import { useWebApp } from "@vkruglikov/react-telegram-web-app"; import { useTonConnectUI } from '@tonconnect/ui-react';
import { useMutation } from 'react-query';
import { request } from '~/shared/libs';
import { useWebApp } from '@vkruglikov/react-telegram-web-app';
import { request } from "~/shared/libs"; const sessionStorageKey = 'auth_v1_token';
const tonProofStorageKey = 'stored_ton_proof';
const payloadTTLMS = 1000 * 60 * 20;
export const useAuth = () => { export const useAuth = () => {
const WebApp = useWebApp(); const WebApp = useWebApp();
const [tonConnectUI] = useTonConnectUI();
const interval = useRef<ReturnType<typeof setInterval> | undefined>();
return useMutation(["auth"], () => { // Store ton_proof when it becomes available
return request.post<{ useEffect(() => {
connected_wallet: null | { if (
version: string; tonConnectUI.wallet?.connectItems?.tonProof &&
address: string; !('error' in tonConnectUI.wallet.connectItems.tonProof) &&
ton_balance: string; tonConnectUI.wallet.account
}; ) {
console.log('DEBUG: Storing ton_proof for future use');
localStorage.setItem(
tonProofStorageKey,
JSON.stringify({
timestamp: Date.now(),
account: tonConnectUI.wallet.account,
proof: tonConnectUI.wallet.connectItems.tonProof.proof,
})
);
}
}, [tonConnectUI.wallet?.connectItems?.tonProof, tonConnectUI.wallet?.account]);
auth_v1_token: string; const makeAuthRequest = async (params: {
}>("/auth.twa", { twa_data: string;
twa_data: WebApp.initData, ton_proof?: {
account: any;
ton_proof: any;
};
}) => {
try {
const res = await request.post<{
connected_wallet: null | {
version: string;
address: string;
ton_balance: string;
};
auth_v1_token: string;
}>('/auth.twa', params);
if (res?.data?.auth_v1_token) {
localStorage.setItem(sessionStorageKey, res.data.auth_v1_token);
// If we sent a proof, it was accepted, so keep record of that
if (params.ton_proof) {
console.log('DEBUG: Auth with proof successful');
}
} else {
throw new Error('Failed to get auth token');
}
return res;
} catch (error) {
// If we were using ton_proof and it failed, clear stored proof
if (params.ton_proof) {
console.log('DEBUG: Auth with proof failed, clearing stored proof');
localStorage.removeItem(tonProofStorageKey);
}
throw error;
}
};
const makeSelectWalletRequest = async (params: { wallet_address: string }) => {
try {
const res = await request.post('/auth.selectWallet', params);
return res;
} catch (error: any) {
// Check for 404 error (wallet not found or invalid)
if (error.response?.status === 404) {
console.log('DEBUG: Wallet selection failed with 404, disconnecting');
await tonConnectUI.disconnect();
localStorage.removeItem(sessionStorageKey);
localStorage.removeItem(tonProofStorageKey);
}
throw error;
}
};
// Helper to prepare the connection parameters with proof requirements
const prepareConnectParams = async () => {
console.log('DEBUG: Preparing connect parameters');
// Set to loading state first
tonConnectUI.setConnectRequestParameters({ state: 'loading' });
try {
// Get the payload/token from backend
const value = await request.post<{ auth_v1_token: string }>('/auth.twa', {
twa_data: WebApp.initData,
});
if (value?.data?.auth_v1_token) {
console.log('DEBUG: Got token for connect params');
// Set the parameters to ready with tonProof requirement
tonConnectUI.setConnectRequestParameters({
state: 'ready',
value: { tonProof: value.data.auth_v1_token },
});
return true;
} else {
console.log('DEBUG: No token received for connect params');
tonConnectUI.setConnectRequestParameters(null);
return false;
}
} catch (error) {
console.error('DEBUG: Error preparing connect params:', error);
tonConnectUI.setConnectRequestParameters(null);
return false;
}
};
// Helper to check if connection is capable of transactions
const isConnectionValid = () => {
if (!tonConnectUI.connected || !tonConnectUI.wallet) {
return false;
}
return true;
};
return useMutation(['auth'], async () => {
clearInterval(interval.current);
let authResult;
console.log('DEBUG: Starting auth flow');
try {
// Case 1: Not connected - need to connect and get proof
if (!tonConnectUI.connected) {
console.log('DEBUG: No wallet connection, starting flow');
localStorage.removeItem(sessionStorageKey);
// Prepare connection parameters (this sets up the proof requirement)
const prepared = await prepareConnectParams();
if (!prepared) {
throw new Error('Failed to prepare connection parameters');
}
// Start periodic refresh of the payload
interval.current = setInterval(prepareConnectParams, payloadTTLMS);
// Open the modal - this will not resolve until connection or cancellation
await tonConnectUI.openModal();
// Check if connection was successful
if (!tonConnectUI.connected) {
console.log('DEBUG: Connection cancelled or failed');
throw new Error('Wallet connection cancelled or failed');
}
// Check if we have a proof after connection
if (
tonConnectUI.wallet?.connectItems?.tonProof &&
!('error' in tonConnectUI.wallet.connectItems.tonProof)
) {
console.log('DEBUG: Got proof after connection');
try {
// Try auth with the proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
ton_proof: {
account: tonConnectUI.wallet.account,
ton_proof: tonConnectUI.wallet.connectItems.tonProof.proof,
},
});
} catch (error) {
// If auth with proof fails, throw the error
console.error('DEBUG: Auth with fresh proof failed:', error);
throw error;
}
} else {
console.log('DEBUG: No proof available after connection');
// If we can't get proof but we're connected, try auth without it
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} else {
// Case 2: Already connected - try to use stored proof first
console.log('DEBUG: Already connected');
// Check if we have a valid stored proof
const storedProofData = localStorage.getItem(tonProofStorageKey);
if (storedProofData) {
try {
const proofData = JSON.parse(storedProofData);
// Check if the proof matches current wallet and is not too old
if (tonConnectUI.wallet?.account?.address === proofData.account.address) {
console.log('DEBUG: Using stored proof');
// Try auth with stored proof but ignore errors
try {
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
ton_proof: {
account: proofData.account,
ton_proof: proofData.proof,
},
});
// If successful, remove stored proof as it's been used
localStorage.removeItem(tonProofStorageKey);
} catch (error) {
console.log(
'DEBUG: Auth with stored proof failed, proceeding without it'
);
// Fall back to auth without proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} else {
console.log('DEBUG: Stored proof address mismatch');
localStorage.removeItem(tonProofStorageKey);
// Auth without proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} catch (error) {
console.error('DEBUG: Error parsing stored proof:', error);
localStorage.removeItem(tonProofStorageKey);
// Auth without proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} else {
// No stored proof, check if we have a live proof
if (
tonConnectUI.wallet?.connectItems?.tonProof &&
!('error' in tonConnectUI.wallet.connectItems.tonProof)
) {
console.log('DEBUG: Using live proof from wallet');
try {
// Try auth with the live proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
ton_proof: {
account: tonConnectUI.wallet.account,
ton_proof: tonConnectUI.wallet.connectItems.tonProof.proof,
},
});
} catch (error) {
console.log(
'DEBUG: Auth with live proof failed, proceeding without it'
);
// Fall back to auth without proof
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
} else {
// Connected without proof - already authenticated
console.log('DEBUG: Connected without proof, proceeding without it');
authResult = await makeAuthRequest({
twa_data: WebApp.initData,
});
}
}
}
// Always try to select wallet after auth (this validates the connection)
if (tonConnectUI.wallet?.account?.address) {
console.log('DEBUG: Selecting wallet', tonConnectUI.wallet.account.address);
try {
await makeSelectWalletRequest({
wallet_address: tonConnectUI.wallet.account.address,
});
// Additional validation check
if (!isConnectionValid()) {
console.log('DEBUG: Connection validation failed, disconnecting');
await tonConnectUI.disconnect();
localStorage.removeItem(sessionStorageKey);
localStorage.removeItem(tonProofStorageKey);
throw new Error('Connection validation failed');
}
} catch (error) {
// Errors from makeSelectWalletRequest are already handled
console.error('DEBUG: Wallet selection failed:', error);
throw error;
}
}
return authResult;
} catch (error) {
console.error('DEBUG: Auth flow failed:', error);
throw error;
} finally {
clearInterval(interval.current);
}
}); });
});
}; };
+26
View File
@@ -0,0 +1,26 @@
import { useMutation } from "react-query";
import { request } from "~/shared/libs";
import { useWebApp } from "@vkruglikov/react-telegram-web-app";
const sessionStorageKey = "auth_v1_token";
export const useAuthTwa = () => {
const WebApp = useWebApp();
const makeAuthRequest = async () => {
const res = await request.post<{
auth_v1_token: string;
}>("/auth.twa", {
twa_data: WebApp.initData,
});
if (res?.data?.auth_v1_token) {
localStorage.setItem(sessionStorageKey, res.data.auth_v1_token);
} else {
throw new Error("Failed to get auth token");
}
return res;
};
return useMutation(["auth"], makeAuthRequest);
};
+57 -13
View File
@@ -1,4 +1,4 @@
import { useMutation } from "react-query"; import { useMutation, useQuery } from "react-query";
import { request } from "~/shared/libs"; import { request } from "~/shared/libs";
import { Royalty } from "~/shared/stores/root"; import { Royalty } from "~/shared/stores/root";
@@ -8,31 +8,75 @@ type UseCreateNewContentPayload = {
content: string; content: string;
image: string; image: string;
description: string; description: string;
hashtags: string[];
price: string; price: string;
resaleLicensePrice: string; // nanoTON bignum (default = 0) resaleLicensePrice: string; // nanoTON bignum (default = 0)
allowResale: boolean; allowResale: boolean;
authors: string[]; authors: string[];
royaltyParams: Royalty[]; royaltyParams: Royalty[];
downloadable: boolean;
}; };
export const useCreateNewContent = () => { export const useCreateNewContent = () => {
return useMutation( return useMutation(
["create-new-content"], ["create-new-content"],
(payload: UseCreateNewContentPayload) => { (payload: UseCreateNewContentPayload) => {
return request.post<{ return request.post<{
message: string; address: string;
}>("/blockchain.sendNewContentMessage", payload); amount: string;
}, payload: string;
}>("/blockchain.sendNewContentMessage", payload, {
headers: {
Authorization: localStorage.getItem('auth_v1_token') ?? ""
}
});
},
); );
}; };
// export const usePurchaseContent = () => {
// return useMutation(
// ["purchase-content"],
// (payload: { content_address: string; price: string }) => {
// return request.post<{
// message: string;
// }>("/blockchain.sendPurchaseContentMessage", payload);
// },
// );
// };
export const useViewContent = (contentId: string) => {
return useQuery(["view", "content", contentId], () => {
return request.get(`/content.view/${contentId}`, {
headers: {
Authorization: localStorage.getItem('auth_v1_token') ?? ""
}
});
});
};
export const usePurchaseContent = () => { export const usePurchaseContent = () => {
return useMutation( return useMutation(
["purchase-content"], ["purchase", "content"],
(payload: { content_address: string; price: string }) => { ({
return request.post<{ content_address,
message: string; license_type,
}>("/blockchain.sendPurchaseContentMessage", payload); }: {
}, content_address: string;
license_type: "listen" | "resale";
}) => {
return request.post<{
address: string;
amount: string;
payload: string;
}>("/blockchain.sendPurchaseContentMessage", {
content_address,
license_type,
}, {
headers: {
Authorization: localStorage.getItem('auth_v1_token') ?? ""
}
});
},
); );
}; };
+186 -30
View File
@@ -1,45 +1,201 @@
import { useMutation } from "react-query"; import { useMutation } from "react-query";
import { useState } from "react"; import { useState } from "react";
import { request } from "~/shared/libs"; import { request } from "~/shared/libs";
const STORAGE_API_URL = import.meta.env.VITE_API_BASE_STORAGE_URL;
const MAX_CHUNK_SIZE = 80 * 1024 * 1024; // 80 MB
export const useUploadFile = () => { export const useUploadFile = () => {
const [uploadProgress, setUploadProgress] = useState(0); const [uploadProgress, setUploadProgress] = useState(0);
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const [uploadError, setUploadError] = useState<Error | null>(null);
const mutation = useMutation(["upload-file"], (file: File) => { const mutation = useMutation(["upload-file"], async (file: File) => {
console.log(`Начинаем загрузку файла: ${file.name} (${file.size} байт)`);
setIsUploading(true); setIsUploading(true);
setUploadProgress(0);
setUploadError(null); // Сбрасываем ошибку перед началом новой загрузки
const formData = new FormData(); try {
formData.append("file", file); // Для маленьких файлов используем обычную загрузку, но с теми же заголовками
if (file.size <= MAX_CHUNK_SIZE) {
console.log("Используем обычную загрузку (файл <= MAX_CHUNK_SIZE)");
return request // Подготавливаем заголовки - такие же, как для чанковой загрузки
.post<{ const headers: Record<string, string> = {
content_sha256: string; "X-File-Name": btoa(unescape(encodeURIComponent(file.name))), // Имя файла в base64
content_id_v1: string; "X-Chunk-Start": "0", // Начинаем с позиции 0
content_url: string; "Content-Type": file.type || "application/octet-stream",
}>("/storage", formData, { "X-Last-Chunk": "1" // Это единственный и последний чанк
headers: { };
"Content-Type": "multipart/form-data",
},
onUploadProgress: (progressEvent) => { // Добавляем заголовок авторизации
const percentCompleted = Math.round( const authToken = localStorage.getItem('auth_v1_token');
(progressEvent.loaded * 100) / (progressEvent?.total as number) ?? if (authToken) {
0, headers["Authorization"] = authToken;
); }
setUploadProgress(percentCompleted);
}, console.log("Заголовки запроса:", headers);
})
.then((response) => { try {
setIsUploading(false); const response = await request.post<{
return response.data; upload_id?: string;
}) content_sha256?: string;
.catch((error) => { content_id?: string;
setIsUploading(false); content_id_v1?: string;
throw error; content_url?: string;
}); }>("", file, { // Отправляем файл напрямую вместо FormData
baseURL: STORAGE_API_URL,
headers,
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / (progressEvent?.total as number || file.size)
);
setUploadProgress(Math.min(99, percentCompleted));
},
});
console.log("Ответ на обычную загрузку:", response.data);
setUploadProgress(100);
return {
content_sha256: response.data.content_sha256 || "",
content_id_v1: response.data.content_id_v1 || response.data.content_id || "",
content_url: response.data.content_url || ""
};
} catch (error) {
console.error("Ошибка при обычной загрузке:", error);
setUploadError(error instanceof Error ? error : new Error('Ошибка при загрузке файла'));
throw error;
}
}
// Для больших файлов используем чанковую загрузку
console.log("Используем чанкированную загрузку (файл > MAX_CHUNK_SIZE)");
let offset = 0;
let uploadId: string | null = null;
let chunkNumber = 0;
// Загружаем файл по чанкам
while (offset < file.size) {
chunkNumber++;
const chunkEnd = Math.min(offset + MAX_CHUNK_SIZE, file.size);
const chunk = file.slice(offset, chunkEnd);
console.log(`Загрузка чанка #${chunkNumber} начиная с байта ${offset}`);
// Определяем, является ли текущий чанк последним
const isLastChunk = chunkEnd === file.size;
// Подготавливаем заголовки
const headers: Record<string, string> = {
"X-File-Name": btoa(unescape(encodeURIComponent(file.name))), // Имя файла в base64
"X-Chunk-Start": offset.toString(),
"Content-Type": file.type || "application/octet-stream"
};
// Добавляем маркер последнего чанка, если это последний чанк
if (isLastChunk) {
headers["X-Last-Chunk"] = "1";
}
// Добавляем заголовок авторизации
const authToken = localStorage.getItem('auth_v1_token');
if (authToken) {
headers["Authorization"] = authToken;
}
// Если есть uploadId, добавляем его в заголовки
if (uploadId) {
headers["X-Upload-ID"] = uploadId;
}
console.log("Заголовки запроса:", headers);
try {
const response = await request.post<{
upload_id?: string;
current_size?: number;
content_id?: string;
content_sha256?: string;
content_id_v1?: string;
content_url?: string;
}>("", chunk, {
baseURL: STORAGE_API_URL,
headers,
onUploadProgress: (progressEvent) => {
// Прогресс загрузки текущего чанка
const overallProgress = offset + progressEvent.loaded;
const percentCompleted = Math.round((overallProgress / file.size) * 100);
setUploadProgress(Math.min(99, percentCompleted));
},
});
console.log(`Ответ на чанк #${chunkNumber}:`, response.data);
// Сохраняем uploadId из первого ответа, если не установлен
if (!uploadId && response.data.upload_id) {
uploadId = response.data.upload_id;
console.log("Получен upload_id:", uploadId);
}
// Проверка на наличие content_id
if (response.data.content_id) {
console.log("Загрузка завершена. ID файла:", response.data.content_id);
setUploadProgress(100);
return {
content_sha256: response.data.content_sha256 || "",
content_id_v1: response.data.content_id_v1 || response.data.content_id || "",
content_url: response.data.content_url || ""
};
}
// Обновляем смещение на основе ответа сервера
if (response.data.current_size !== undefined) {
offset = response.data.current_size;
console.log(`Сервер сообщает current_size: ${offset}`);
} else {
console.warn("Неожиданный ответ от сервера, отсутствует current_size");
const error = new Error("Missing current_size in response");
setUploadError(error);
throw error;
}
} catch (error: any) {
console.error(`Ошибка при загрузке чанка #${chunkNumber}:`, error);
if (error.response) {
console.error("Ответ сервера:", error.response.status, error.response.data);
}
setUploadError(error instanceof Error ? error : new Error(`Ошибка при загрузке чанка #${chunkNumber}`));
throw error;
}
}
const error = new Error("Ошибка загрузки файла: все чанки загружены, но content_id не получен");
console.error(error.message);
setUploadError(error);
throw error;
} catch (error: any) {
console.error("Ошибка при загрузке:", error);
if (error.response) {
console.error("Ответ сервера:", error.response.status, error.response.data);
}
setUploadError(error instanceof Error ? error : new Error("Неизвестная ошибка при загрузке"));
setIsUploading(false);
throw error;
} finally {
setIsUploading(false);
}
}); });
return { ...mutation, uploadProgress, isUploading }; // Сбросить ошибку
const resetUploadError = () => setUploadError(null);
return {
...mutation,
uploadProgress,
isUploading,
uploadError,
resetUploadError
};
}; };
+43
View File
@@ -0,0 +1,43 @@
import { useEffect } from "react";
import * as Sentry from "@sentry/react";
import {
createRoutesFromChildren,
matchRoutes,
useLocation,
useNavigationType,
} from "react-router-dom";
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
integrations: [
// See docs for support of different versions of variation of react router
// https://docs.sentry.io/platforms/javascript/guides/react/configuration/integrations/react-router/
Sentry.reactRouterV6BrowserTracingIntegration({
useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
}),
Sentry.replayIntegration(),
],
enabled: import.meta.env.PROD, // Only enable in production
environment: import.meta.env.MODE, // Set the environment
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for tracing.
// Learn more at
// https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
tracesSampleRate: 1,
// Set `tracePropagationTargets` to control for which URLs trace propagation should be enabled
tracePropagationTargets: [
// /^\//,
// new RegExp(`^${import.meta.env.VITE_API_BASE_URL}`),
],
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
// Learn more at
// https://docs.sentry.io/platforms/javascript/session-replay/configuration/#general-integration-configuration
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 0,
});
+104 -51
View File
@@ -1,82 +1,135 @@
import { create } from "zustand"; import { create } from 'zustand';
export type Royalty = { export type Royalty = {
address: string; address: string;
value: number; value: number;
}; };
type NotificationType = 'success' | 'danger' | 'warning' | 'info';
interface Notification {
id: string;
message: string;
type: NotificationType;
createdAt: number;
}
type RootStore = { type RootStore = {
name: string; name: string;
setName: (name: string) => void; setName: (name: string) => void;
author: string; author: string;
setAuthor: (author: string) => void; setAuthor: (author: string) => void;
file: File | null; file: File | null;
setFile: (file: File) => void; setFile: (file: File | null) => void;
fileSrc: string; fileType: string;
setFileSrc: (fileSrc: string) => void; setFileType: (type: string) => void;
allowCover: boolean; fileSrc: string;
setAllowCover: (allowCover: boolean) => void; setFileSrc: (fileSrc: string) => void;
cover: File | null; allowCover: boolean;
setCover: (cover: File | null) => void; setAllowCover: (allowCover: boolean) => void;
isPercentHintOpen: boolean; allowDwnld: boolean;
setPercentHintOpen: (isPercentHintOpen: boolean) => void; setAllowDwnld: (allowDwnld: boolean) => void;
authors: string[]; cover: File | null;
setAuthors: (authors: string[]) => void; setCover: (cover: File | null) => void;
royalty: Royalty[]; isPercentHintOpen: boolean;
setRoyalty: (authors: Royalty[]) => void; setPercentHintOpen: (isPercentHintOpen: boolean) => void;
price: number; authors: string[];
setPrice: (price: number) => void; setAuthors: (authors: string[]) => void;
allowResale: boolean; royalty: Royalty[];
setAllowResale: (allowResale: boolean) => void; setRoyalty: (authors: Royalty[]) => void;
licenseResalePrice: number; price: number;
setLicenseResalePrice: (licenseResalePrice: number) => void; setPrice: (price: number) => void;
allowResale: boolean;
setAllowResale: (allowResale: boolean) => void;
licenseResalePrice: number;
setLicenseResalePrice: (licenseResalePrice: number) => void;
hashtags: string[];
setHashtags: (hashtags: string[]) => void;
notifications: Notification[];
addNotification: (message: string, type: NotificationType) => void;
removeNotification: (id: string) => void;
}; };
export const useRootStore = create<RootStore>((set) => ({ export const useRootStore = create<RootStore>((set) => ({
name: "", name: '',
setName: (name) => set({ name }), setName: (name) => set({ name }),
author: "", author: '',
setAuthor: (author) => set({ author }), setAuthor: (author) => set({ author }),
file: null, file: null,
setFile: (file) => set({ file }), setFile: (file) => set({ file }),
fileSrc: "", fileType: '',
setFileSrc: (fileSrc) => set({ fileSrc }), setFileType: (fileType) => set({ fileType }),
allowCover: false, fileSrc: '',
setAllowCover: (allowCover) => set({ allowCover }), setFileSrc: (fileSrc) => set({ fileSrc }),
cover: null, allowCover: false,
setCover: (cover) => set({ cover }), setAllowCover: (allowCover) => set({ allowCover }),
isPercentHintOpen: true, allowDwnld: false,
setPercentHintOpen: (isPercentHintOpen) => set({ isPercentHintOpen }), setAllowDwnld: (allowDwnld) => set({ allowDwnld }),
authors: [], cover: null,
setAuthors: (authors) => set({ authors }), setCover: (cover) => set({ cover }),
royalty: [{ address: "", value: 100 }], isPercentHintOpen: true,
setRoyalty: (royalty) => set({ royalty }), setPercentHintOpen: (isPercentHintOpen) => set({ isPercentHintOpen }),
price: 0, authors: [],
setPrice: (price: number) => set({ price }), setAuthors: (authors) => set({ authors }),
allowResale: false, royalty: [],
setAllowResale: (allowResale) => set({ allowResale }), setRoyalty: (royalty) => set({ royalty }),
licenseResalePrice: 0, price: 0.15,
setLicenseResalePrice: (licenseResalePrice) => set({ licenseResalePrice }), setPrice: (price: number) => set({ price }),
allowResale: false,
setAllowResale: (allowResale) => set({ allowResale }),
licenseResalePrice: 0,
setLicenseResalePrice: (licenseResalePrice) => set({ licenseResalePrice }),
hashtags: [],
setHashtags: (hashtags: string[]) => set({ hashtags }),
notifications: [],
addNotification: (message: string, type: NotificationType = 'info') => {
const id = Date.now().toString();
const notification = {
id,
message,
type,
createdAt: Date.now(),
};
set((state) => ({
notifications: [...state.notifications, notification],
}));
},
removeNotification: (id: string) => {
set((state) => ({
notifications: state.notifications.filter((notification) => notification.id !== id),
}));
},
})); }));
+130
View File
@@ -0,0 +1,130 @@
import { FC, useEffect, useRef, useState } from "react";
interface AudioPlayerProps {
src: string;
}
export const AudioPlayer: FC<AudioPlayerProps> = ({ src }) => {
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [currentTime, setCurrentTime] = useState<number>(0);
const [duration, setDuration] = useState<number>(0);
const audioRef = useRef<HTMLAudioElement | null>(null);
const rangeRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
const audio = audioRef.current;
if (!audio) return;
const handleLoadedMetadata = () => {
setDuration(audio.duration);
};
const handleTimeUpdate = () => {
setCurrentTime(audio.currentTime);
updateRangeBackground(audio.currentTime);
};
audio.addEventListener("loadedmetadata", handleLoadedMetadata);
audio.addEventListener("timeupdate", handleTimeUpdate);
return () => {
audio.removeEventListener("loadedmetadata", handleLoadedMetadata);
audio.removeEventListener("timeupdate", handleTimeUpdate);
};
}, []);
const togglePlayPause = () => {
const audio = audioRef.current;
if (!audio) return;
if (isPlaying) {
audio.pause();
} else {
audio.play();
}
setIsPlaying(!isPlaying);
};
const handleSliderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const audio = audioRef.current;
if (!audio) return;
const newTime = parseFloat(e.target.value);
audio.currentTime = newTime;
setCurrentTime(newTime);
updateRangeBackground(newTime);
};
const formatTime = (time: number): string => {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds < 10 ? "0" : ""}${seconds}`;
};
const updateRangeBackground = (currentTime: number) => {
if (rangeRef.current) {
const percentage = (currentTime / duration) * 100;
rangeRef.current.style.setProperty(
"--value-percentage",
`${percentage}%`,
);
}
};
return (
<div className="flex items-center space-x-4">
<audio ref={audioRef} autoPlay={true} src={src} />
<button
onClick={togglePlayPause}
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white text-gray focus:outline-none"
>
{isPlaying ? (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="size-6"
>
<path
fillRule="evenodd"
d="M6.75 5.25a.75.75 0 0 1 .75-.75H9a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H7.5a.75.75 0 0 1-.75-.75V5.25Zm7.5 0A.75.75 0 0 1 15 4.5h1.5a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H15a.75.75 0 0 1-.75-.75V5.25Z"
clipRule="evenodd"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="size-6"
>
<path
fillRule="evenodd"
d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z"
clipRule="evenodd"
/>
</svg>
)}
</button>
<div className={"flex w-full flex-col gap-2"}>
<input
type="range"
ref={rangeRef}
min="0"
max={duration}
value={currentTime}
onChange={handleSliderChange}
className="flex-grow"
/>
<div className="flex items-center justify-between text-xs">
<span>{formatTime(currentTime)}</span>
<span>{formatTime(duration)}</span>
</div>
</div>
</div>
);
};
+63
View File
@@ -0,0 +1,63 @@
import { useState } from "react";
import { WithContext as ReactTags, SEPARATORS } from "react-tag-input";
import { useRootStore } from "~/shared/stores/root";
// English comment: no extra Tag interface, just cast to any
export const HashtagInput = () => {
const { hashtags, setHashtags } = useRootStore();
// English comment: local state as string[] for simplicity
const [tags, setTags] = useState<string[]>(hashtags);
const separators = [SEPARATORS.ENTER, SEPARATORS.COMMA]
const handleDelete = (i: number) => {
const newTags = tags.filter((_, index) => index !== i);
setTags(newTags);
setHashtags(newTags);
};
// English comment: pass "any" to the function
const handleAddition = (newTag: any) => {
// Clean up text from commas and trim whitespace
const text = newTag?.text || newTag?.id || "";
const cleanText = text.replace(/,/g, '').trim();
// Skip empty tags
if (!cleanText) return;
const updatedTags = [...tags, cleanText];
setTags(updatedTags);
setHashtags(updatedTags);
};
// Simulate Enter keypress for Android
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
bubbles: true,
cancelable: true,
which: 13,
keyCode: 13,
});
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Unidentified') {
const lastChar = e.currentTarget.value.slice(-1);
if (lastChar === ',') {
e.currentTarget.dispatchEvent(enterEvent);
}
}
};
return (
<ReactTags
tags={tags.map((t) => ({ id: t, text: t })) as any}
separators={separators as any}
handleDelete={handleDelete as any}
handleAddition={handleAddition as any}
allowDragDrop={false}
placeholder="[ введите тэги через запятую ]"
inputProps={{
onKeyUp: handleKeyUp as any
}}
/>
);
};
+9
View File
@@ -0,0 +1,9 @@
export const Replace = () => {
return (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="1">
<path d="M15.75 6.01C15.9293 6.01 16.0939 6.07292 16.2229 6.17788C17.3108 7.09184 18.0024 8.46516 18.0024 10C18.0024 12.6888 15.8801 14.8818 13.2193 14.9954L13.0024 15H8.56057L9.78032 16.2197C10.0466 16.4859 10.0708 16.9026 9.85294 17.1962L9.78032 17.2803C9.51406 17.5466 9.09739 17.5708 8.80378 17.3529L8.71966 17.2803L6.21966 14.7803C5.9534 14.5141 5.92919 14.0974 6.14704 13.8038L6.21966 13.7197L8.71966 11.2197C9.01256 10.9268 9.48743 10.9268 9.78032 11.2197C10.0466 11.4859 10.0708 11.9026 9.85294 12.1962L9.78032 12.2803L8.56057 13.5H13.0024C14.871 13.5 16.3975 12.0357 16.4972 10.192L16.5024 10C16.5024 8.91885 16.0122 7.95219 15.2419 7.31017C15.0935 7.17538 15 6.97861 15 6.76C15 6.34579 15.3358 6.01 15.75 6.01ZM10.2197 2.71967C10.4859 2.4534 10.9026 2.4292 11.1962 2.64705L11.2803 2.71967L13.7803 5.21967L13.8529 5.30379C14.0466 5.56478 14.049 5.92299 13.8601 6.18638L13.7803 6.28033L11.2803 8.78033L11.1962 8.85295C10.9352 9.0466 10.577 9.04899 10.3136 8.86012L10.2197 8.78033L10.147 8.69621C9.9534 8.43522 9.951 8.07701 10.1399 7.81362L10.2197 7.71967L11.4386 6.5H6.99757C5.12901 6.5 3.60245 7.96428 3.50275 9.80796L3.49757 10C3.49757 11.0831 3.98958 12.0514 4.7623 12.6934C4.9085 12.8289 4.99999 13.0238 4.99999 13.24C4.99999 13.6542 4.66421 13.99 4.24999 13.99C4.05871 13.99 3.88415 13.9184 3.75166 13.8005C2.67865 12.8872 1.99757 11.5232 1.99757 10C1.99757 7.31124 4.11988 5.11818 6.78068 5.00462L6.99757 5H11.4386L10.2197 3.78033L10.147 3.69621C9.92919 3.4026 9.9534 2.98594 10.2197 2.71967Z" fill="white" />
</g>
</svg>
);
};
+77
View File
@@ -0,0 +1,77 @@
import { useEffect, useState } from 'react';
import { useRootStore } from '~/shared/stores/root';
interface AnimatedNotification {
id: string;
message: string;
type: 'success' | 'danger' | 'warning' | 'info';
isExiting: boolean;
}
export const Notification = () => {
const { notifications, removeNotification } = useRootStore();
const [animatedNotifications, setAnimatedNotifications] = useState<AnimatedNotification[]>([]);
useEffect(() => {
const currentIds = animatedNotifications.map((n) => n.id);
const newNotifications = notifications
.filter((n) => !currentIds.includes(n.id))
.map((n) => ({ ...n, isExiting: false }));
if (newNotifications.length > 0) {
newNotifications.forEach((notification) => {
setTimeout(() => {
handleRemove(notification.id);
}, 4000);
});
setAnimatedNotifications((prev) => [...prev, ...newNotifications]);
}
}, [notifications]);
// Функция для начала анимации удаления
const handleRemove = (id: string) => {
setAnimatedNotifications((prev) =>
prev.map((n) => (n.id === id ? { ...n, isExiting: true } : n))
);
setTimeout(() => {
setAnimatedNotifications((prev) => prev.filter((n) => n.id !== id));
removeNotification(id);
}, 300);
};
return (
<div className="fixed inset-x-0 top-2 z-50 flex flex-col items-center gap-2 pointer-events-none">
{animatedNotifications.map((notification) => (
<div
key={notification.id}
onClick={() => handleRemove(notification.id)}
className={`
p-2 rounded shadow-md relative transition-all duration-300 max-w-sm
transform origin-top pointer-events-auto cursor-pointer
${notification.isExiting ? 'animate-fade-out' : 'animate-slide-in-top'}
${getNotificationClass(notification.type)}
`}
>
<div className="text-center">{notification.message}</div>
</div>
))}
</div>
);
};
const getNotificationClass = (type: 'success' | 'danger' | 'warning' | 'info'): string => {
const baseStyle = 'border border-white bg-opacity-70 backdrop-blur-sm';
switch (type) {
case 'success':
return `${baseStyle} bg-primary text-white`;
case 'danger':
return `${baseStyle} bg-primary text-white`;
case 'warning':
return `${baseStyle} bg-primary text-white`;
case 'info':
default:
return `${baseStyle} bg-primary text-white`;
}
};
+2
View File
@@ -17,3 +17,5 @@ export const processFile = async (file: File) => {
export const getIndexArray = (len: number) => export const getIndexArray = (len: number) =>
new Array(len).fill("").map((_, i) => i); new Array(len).fill("").map((_, i) => i);
export const fromNanoTON = (amount: string) => Number(amount) / 10 ** 9;
+29 -9
View File
@@ -1,13 +1,33 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: { theme: {
extend: { extend: {
colors: { colors: {
gray: "#1d1d1b", gray: '#1d1d1b',
primary: "#e40615", primary: '#e40615',
}, },
keyframes: {
'slide-in-top': {
'0%': {
transform: 'translateY(-1rem)',
opacity: '0',
},
'100%': {
transform: 'translateY(0)',
opacity: '1',
},
},
'fade-out': {
'0%': { opacity: '1' },
'100%': { opacity: '0' },
},
},
animation: {
'slide-in-top': 'slide-in-top 0.3s ease-out forwards',
'fade-out': 'fade-out 0.3s ease-out forwards',
},
},
}, },
}, plugins: [],
plugins: [],
}; };
+3
View File
@@ -0,0 +1,3 @@
{
"rewrites": [{ "source": "/(.*)", "destination": "/" }]
}
+15
View File
@@ -4,4 +4,19 @@ import TSPaths from "vite-tsconfig-paths";
export default defineConfig({ export default defineConfig({
plugins: [react(), TSPaths()], plugins: [react(), TSPaths()],
define: {
global: 'globalThis',
},
resolve: {
alias: {
buffer: 'buffer/',
}
},
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
}
}); });
+419 -20
View File
@@ -184,7 +184,7 @@
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5"
"@babel/runtime@^7.23.7", "@babel/runtime@^7.23.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2": "@babel/runtime@^7.23.7", "@babel/runtime@^7.23.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.9.2":
version "7.24.0" version "7.24.0"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz"
integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==
@@ -225,10 +225,10 @@
"@babel/helper-validator-identifier" "^7.22.20" "@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@esbuild/darwin-arm64@0.19.12": "@esbuild/win32-x64@0.19.12":
version "0.19.12" version "0.19.12"
resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz" resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz"
integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g== integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0" version "4.4.0"
@@ -361,15 +361,247 @@
resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz" resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@react-dnd/asap@^4.0.0":
version "4.0.1"
resolved "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz"
integrity sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==
"@react-dnd/invariant@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz"
integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==
"@react-dnd/shallowequal@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz"
integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==
"@remix-run/router@1.15.2": "@remix-run/router@1.15.2":
version "1.15.2" version "1.15.2"
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz" resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz"
integrity sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q== integrity sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==
"@rollup/rollup-darwin-arm64@4.12.0": "@rollup/rollup-win32-x64-msvc@4.12.0":
version "4.12.0" version "4.12.0"
resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz" resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz"
integrity sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ== integrity sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==
"@sentry-internal/browser-utils@9.1.0":
version "9.1.0"
resolved "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.1.0.tgz"
integrity sha512-S1uT+kkFlstWpwnaBTIJSwwAID8PS3aA0fIidOjNezeoUE5gOvpsjDATo9q+sl6FbGWynxMz6EnYSrq/5tuaBQ==
dependencies:
"@sentry/core" "9.1.0"
"@sentry-internal/feedback@9.1.0":
version "9.1.0"
resolved "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.1.0.tgz"
integrity sha512-jTDCqkqH3QDC8m9WO4mB06hqnBRsl3p7ozoh0E774UvNB6blOEZjShhSGMMEy5jbbJajPWsOivCofUtFAwbfGw==
dependencies:
"@sentry/core" "9.1.0"
"@sentry-internal/replay-canvas@9.1.0":
version "9.1.0"
resolved "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.1.0.tgz"
integrity sha512-gxredVe+mOgfNqDJ3dTLiRON3FK1rZ8d0LHp7TICK/umLkWFkuso0DbNeyKU+3XCEjCr9VM7ZRqTDMzmY6zyVg==
dependencies:
"@sentry-internal/replay" "9.1.0"
"@sentry/core" "9.1.0"
"@sentry-internal/replay@9.1.0":
version "9.1.0"
resolved "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.1.0.tgz"
integrity sha512-E2xrUoms90qvm0BVOuaZ8QfkMoTUEgoIW/35uOeaqNcL7uOIj8c5cSEQQKit2Dr7CL6W+Ci5c6Khdyd5C0NL5w==
dependencies:
"@sentry-internal/browser-utils" "9.1.0"
"@sentry/core" "9.1.0"
"@sentry/browser@9.1.0":
version "9.1.0"
resolved "https://registry.npmjs.org/@sentry/browser/-/browser-9.1.0.tgz"
integrity sha512-G55e5j77DqRW3LkalJLAjRRfuyKrjHaKTnwIYXa6ycO+Q1+l14pEUxu+eK5Abu2rtSdViwRSb5/G6a/miSUlYA==
dependencies:
"@sentry-internal/browser-utils" "9.1.0"
"@sentry-internal/feedback" "9.1.0"
"@sentry-internal/replay" "9.1.0"
"@sentry-internal/replay-canvas" "9.1.0"
"@sentry/core" "9.1.0"
"@sentry/core@9.1.0":
version "9.1.0"
resolved "https://registry.npmjs.org/@sentry/core/-/core-9.1.0.tgz"
integrity sha512-djWEzSBpMgqdF3GQuxO+kXCUX+Mgq42G4Uah/HSUBvPDHKipMmyWlutGRoFyVPPOnCDgpHu3wCt83wbpEyVmDw==
"@sentry/react@^9.1.0":
version "9.1.0"
resolved "https://registry.npmjs.org/@sentry/react/-/react-9.1.0.tgz"
integrity sha512-aP2sXHH+erbomuzU762ktg340IGDh8zD7ueuqwBwGu98fhCpTYsLXiS85I29tUvPLljwNU9puLPmxbgW4iZ2tQ==
dependencies:
"@sentry/browser" "9.1.0"
"@sentry/core" "9.1.0"
hoist-non-react-statics "^3.3.2"
"@telegram-apps/bridge@^1.9.2":
version "1.9.2"
resolved "https://registry.npmjs.org/@telegram-apps/bridge/-/bridge-1.9.2.tgz"
integrity sha512-SJLcNWLXhbbZr9MiqFH/g2ceuitSJKMxUIZysK4zUNyTUNuonrQG80Q/yrO+XiNbKUj8WdDNM86NBARhuyyinQ==
dependencies:
"@telegram-apps/signals" "^1.1.1"
"@telegram-apps/toolkit" "^1.1.1"
"@telegram-apps/transformers" "^1.2.2"
"@telegram-apps/types" "^1.2.1"
"@telegram-apps/bridge@^2.4.0":
version "2.4.0"
resolved "https://registry.npmjs.org/@telegram-apps/bridge/-/bridge-2.4.0.tgz"
integrity sha512-Lp/vhspF7okK8zXvSWWirunKXOPE6Gr11o9VBne4VmKG/yHRhEW7Pf07ncPtXLLzI6wW8+VYc3khsHPABJymEw==
dependencies:
"@telegram-apps/signals" "^1.1.1"
"@telegram-apps/toolkit" "^2.0.0"
"@telegram-apps/transformers" "^2.2.0"
"@telegram-apps/types" "^2.0.0"
better-promises "^0.4.0"
error-kid "^0.0.4"
mitt "^3.0.1"
valibot "1.0.0-beta.14"
"@telegram-apps/navigation@^1.0.13":
version "1.0.13"
resolved "https://registry.npmjs.org/@telegram-apps/navigation/-/navigation-1.0.13.tgz"
integrity sha512-TsUueB5LQp77GQHoMa93nq26Uw7GJjrFCPbyseMVU7aBBxAc+8CV2IYytRwcVp5sv/q7ThK5X4JaKn2V1yBHDQ==
dependencies:
"@telegram-apps/bridge" "^1.9.2"
"@telegram-apps/signals" "^1.1.1"
"@telegram-apps/toolkit" "^1.1.1"
"@telegram-apps/sdk@^3.5.1":
version "3.5.1"
resolved "https://registry.npmjs.org/@telegram-apps/sdk/-/sdk-3.5.1.tgz"
integrity sha512-m/ynpSozXsqq6Kfb6M9fm8SD6x/+jqvTFT59FuuOBvC+G8eMd0F+fvZdr6Pj0I6IL9M67nrfm92JpTu/aVNEjw==
dependencies:
"@telegram-apps/bridge" "^2.4.0"
"@telegram-apps/navigation" "^1.0.13"
"@telegram-apps/signals" "^1.1.1"
"@telegram-apps/toolkit" "^2.0.0"
"@telegram-apps/transformers" "^2.2.0"
"@telegram-apps/types" "^2.0.0"
better-promises "^0.4.0"
error-kid "^0.0.4"
valibot "1.0.0-beta.14"
"@telegram-apps/signals@^1.1.1":
version "1.1.1"
resolved "https://registry.npmjs.org/@telegram-apps/signals/-/signals-1.1.1.tgz"
integrity sha512-vz37r8lemGpPzDiBRfqpXYBynzmy3SFnY6zfHsTZABTYYt0b0WQZyU5mFDqqqugGhka78Gy11xmr9csgy4YgGA==
"@telegram-apps/toolkit@^1.1.1":
version "1.1.1"
resolved "https://registry.npmjs.org/@telegram-apps/toolkit/-/toolkit-1.1.1.tgz"
integrity sha512-+vhKx6ngfvjyTE6Xagl3z1TPVbfx5s7xAkcYzCdHYUo6T60jLIqLgyZMcI1UPoIAMuMu1pHoO+p8QNCj/+tFmw==
"@telegram-apps/toolkit@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@telegram-apps/toolkit/-/toolkit-2.0.0.tgz"
integrity sha512-1GKTLBNme1Phu/gFvgS9NWPq+LhPfzSIfnwhcF9I/6tCdufrLRcVaSMRiK9R4VDYD6iZUyj+a2l250qWAxxjQQ==
"@telegram-apps/transformers@^1.2.2":
version "1.2.2"
resolved "https://registry.npmjs.org/@telegram-apps/transformers/-/transformers-1.2.2.tgz"
integrity sha512-vvMwXckd1D7Ozc0h66PSUwF5QLrRV9HlGJFFeBuUex8QEk5mSPtsJkLiqB8aBbwuFDa91+TUSM/CxqPZO/e9YQ==
dependencies:
"@telegram-apps/toolkit" "^1.1.1"
"@telegram-apps/types" "^1.2.1"
"@telegram-apps/transformers@^2.2.0":
version "2.2.0"
resolved "https://registry.npmjs.org/@telegram-apps/transformers/-/transformers-2.2.0.tgz"
integrity sha512-wqXXOukhEjZKhzdq5vG1LkxWL11DApbmUKzk+3nA/ki3TLyD3awVTOXbpoNdOwFl2xliIooYcsUOEl4WCyyLGw==
dependencies:
"@telegram-apps/toolkit" "^2.0.0"
"@telegram-apps/types" "^2.0.0"
valibot "1.0.0-beta.14"
"@telegram-apps/types@^1.2.1":
version "1.2.1"
resolved "https://registry.npmjs.org/@telegram-apps/types/-/types-1.2.1.tgz"
integrity sha512-so4HLh7clur0YyMthi9KVIgWoGpZdXlFOuQjk3+Q5NAvJZ11nAheBSwPlGw/Ko92+zwvrSBE/lQyN2+p17RP+w==
"@telegram-apps/types@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@telegram-apps/types/-/types-2.0.0.tgz"
integrity sha512-yF499FJK82a2IDNDAQdrmVH3sgFZl/QFNdVZKgWpgtunIVJ1fres5wi9+4aUBRVIdQwZOZZqB/AOvYYuYXsq3Q==
"@ton/core@^0.59.1":
version "0.59.1"
resolved "https://registry.npmjs.org/@ton/core/-/core-0.59.1.tgz"
integrity sha512-SxFBAvutYJaIllTkv82vbHTJhJI6NxzqUhi499CDEjJEZ9i6i9lHJiK2df4dlLAb/4SiWX6+QUzESkK4DEdnCw==
dependencies:
symbol.inspect "1.0.1"
"@ton/crypto-primitives@2.1.0":
version "2.1.0"
resolved "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz"
integrity sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow==
dependencies:
jssha "3.2.0"
"@ton/crypto@>=3.2.0":
version "3.3.0"
resolved "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz"
integrity sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA==
dependencies:
"@ton/crypto-primitives" "2.1.0"
jssha "3.2.0"
tweetnacl "1.0.3"
"@tonconnect/isomorphic-eventsource@^0.0.2":
version "0.0.2"
resolved "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.2.tgz"
integrity sha512-B4UoIjPi0QkvIzZH5fV3BQLWrqSYABdrzZQSI9sJA9aA+iC0ohOzFwVVGXanlxeDAy1bcvPbb29f6sVUk0UnnQ==
dependencies:
eventsource "^2.0.2"
"@tonconnect/isomorphic-fetch@^0.0.3":
version "0.0.3"
resolved "https://registry.npmjs.org/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.3.tgz"
integrity sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w==
dependencies:
node-fetch "^2.6.9"
"@tonconnect/protocol@^2.2.6":
version "2.2.6"
resolved "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.2.6.tgz"
integrity sha512-kyoDz5EqgsycYP+A+JbVsAUYHNT059BCrK+m0pqxykMODwpziuSAXfwAZmHcg8v7NB9VKYbdFY55xKeXOuEd0w==
dependencies:
tweetnacl "^1.0.3"
tweetnacl-util "^0.15.1"
"@tonconnect/sdk@3.0.6":
version "3.0.6"
resolved "https://registry.npmjs.org/@tonconnect/sdk/-/sdk-3.0.6.tgz"
integrity sha512-dJipe0Cw43p/7o3Pa6Y6h0QMDtY2V2YKzwdCqcYvmyCYadBNmvA+8ScH9QK5GpkngRJnYaWq+321lAaQTFpUwA==
dependencies:
"@tonconnect/isomorphic-eventsource" "^0.0.2"
"@tonconnect/isomorphic-fetch" "^0.0.3"
"@tonconnect/protocol" "^2.2.6"
"@tonconnect/ui-react@^2.0.2":
version "2.0.11"
resolved "https://registry.npmjs.org/@tonconnect/ui-react/-/ui-react-2.0.11.tgz"
integrity sha512-h8E4zlbdNBJCAPgg6+O5ZkVDZh8mvnc82VRmhInSPLFAr6qDZbE+qSjRVm4lkuN1N/m24lhkDXFCFjvJ9CgCow==
dependencies:
"@tonconnect/ui" "2.0.11"
"@tonconnect/ui@2.0.11":
version "2.0.11"
resolved "https://registry.npmjs.org/@tonconnect/ui/-/ui-2.0.11.tgz"
integrity sha512-5TOhfEDeyY8R9oyEGavLU+DRmDW3wSGyxVshWhHisi8597cZIuG39HHNbP05WJBBjmVm/Kh/bhcHtfx7lQp/Ig==
dependencies:
"@tonconnect/sdk" "3.0.6"
classnames "^2.3.2"
csstype "^3.1.1"
deepmerge "^4.2.2"
ua-parser-js "^1.0.35"
"@types/babel__core@^7.20.5": "@types/babel__core@^7.20.5":
version "7.20.5" version "7.20.5"
@@ -419,7 +651,19 @@
resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/node@^18.0.0 || >=20.0.0", "@types/node@^20.11.24": "@types/lodash-es@^4.17.12":
version "4.17.12"
resolved "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz"
integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.17.15"
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz"
integrity sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==
"@types/node@^18.0.0 || >=20.0.0", "@types/node@^20.11.24", "@types/node@>= 12":
version "20.11.24" version "20.11.24"
resolved "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz" resolved "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz"
integrity sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long== integrity sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==
@@ -438,7 +682,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^18.2.56", "@types/react@>=16.8": "@types/react@*", "@types/react@^18.2.56", "@types/react@>= 16", "@types/react@>=16.8":
version "18.2.61" version "18.2.61"
resolved "https://registry.npmjs.org/@types/react/-/react-18.2.61.tgz" resolved "https://registry.npmjs.org/@types/react/-/react-18.2.61.tgz"
integrity sha512-NURTN0qNnJa7O/k4XUkEW2yfygA+NxS0V5h1+kp9jPwhzZy95q3ADoGMP0+JypMhrZBTTgjKAUlTctde1zzeQA== integrity sha512-NURTN0qNnJa7O/k4XUkEW2yfygA+NxS0V5h1+kp9jPwhzZy95q3ADoGMP0+JypMhrZBTTgjKAUlTctde1zzeQA==
@@ -761,6 +1005,18 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
better-promises@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/better-promises/-/better-promises-0.4.0.tgz"
integrity sha512-AcKkTUSd4o1vMf41eBbHW1NkY7vrXeNI6etitGdQE54WFXsF2wkfonrKA06Za7lViRNyT/cMvj5z+DScqhYW8A==
dependencies:
error-kid "^0.0.4"
big-integer@^1.6.16: big-integer@^1.6.16:
version "1.6.52" version "1.6.52"
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz" resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz"
@@ -817,6 +1073,14 @@ browserslist@^4.22.2, browserslist@^4.23.0, "browserslist@>= 4.21.0":
node-releases "^2.0.14" node-releases "^2.0.14"
update-browserslist-db "^1.0.13" update-browserslist-db "^1.0.13"
buffer@^6.0.3:
version "6.0.3"
resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"
call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7" version "1.0.7"
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz"
@@ -875,6 +1139,16 @@ chokidar@^3.5.3:
optionalDependencies: optionalDependencies:
fsevents "~2.3.2" fsevents "~2.3.2"
classnames@^2.3.2:
version "2.5.1"
resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
classnames@~2.3.1:
version "2.3.3"
resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.3.tgz"
integrity sha512-1inzZmicIFcmUya7PGtUQeXtcF7zZpPnxtQoYOrz0uiOBGlLFa4ik4361seYL2JCcRDIyfdFHiwQolESFlw+Og==
clsx@^2.1.0: clsx@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz" resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz"
@@ -940,7 +1214,7 @@ cssesc@^3.0.0:
resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@^3.0.2: csstype@^3.0.2, csstype@^3.1.1:
version "3.1.3" version "3.1.3"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
@@ -964,7 +1238,7 @@ deep-is@^0.1.3:
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
deepmerge@^4.0.0: deepmerge@^4.0.0, deepmerge@^4.2.2:
version "4.3.1" version "4.3.1"
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
@@ -1014,6 +1288,15 @@ dlv@^1.1.3:
resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz" resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
dnd-core@14.0.1:
version "14.0.1"
resolved "https://registry.npmjs.org/dnd-core/-/dnd-core-14.0.1.tgz"
integrity sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A==
dependencies:
"@react-dnd/asap" "^4.0.0"
"@react-dnd/invariant" "^2.0.0"
redux "^4.1.1"
doctrine@^2.1.0: doctrine@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz"
@@ -1048,6 +1331,11 @@ emoji-regex@^9.2.2:
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
error-kid@^0.0.4:
version "0.0.4"
resolved "https://registry.npmjs.org/error-kid/-/error-kid-0.0.4.tgz"
integrity sha512-x+yQhY56SorLMnX6kOf+z3JCv2QBurcWEDcIjgxYtVr4fGeCfAtOdZOCyWttkHHDFPtL2PqnaRUmphbmALJd9w==
es-abstract@^1.22.1, es-abstract@^1.22.3: es-abstract@^1.22.1, es-abstract@^1.22.3:
version "1.22.5" version "1.22.5"
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz" resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz"
@@ -1333,6 +1621,11 @@ esutils@^2.0.2:
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
eventsource@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz"
integrity sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
@@ -1446,11 +1739,6 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2: function-bind@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@@ -1634,6 +1922,18 @@ hasown@^2.0.0, hasown@^2.0.1:
dependencies: dependencies:
function-bind "^1.1.2" function-bind "^1.1.2"
hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0, ignore@^5.2.4: ignore@^5.2.0, ignore@^5.2.4:
version "5.3.1" version "5.3.1"
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz"
@@ -1878,6 +2178,11 @@ json5@^2.2.3:
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jssha@3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz"
integrity sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==
keyv@^4.5.3: keyv@^4.5.3:
version "4.5.4" version "4.5.4"
resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
@@ -1920,6 +2225,11 @@ locate-path@^6.0.0:
dependencies: dependencies:
p-locate "^5.0.0" p-locate "^5.0.0"
lodash-es@^4.17.21:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz"
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
lodash.merge@^4.6.2: lodash.merge@^4.6.2:
version "4.6.2" version "4.6.2"
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
@@ -2032,6 +2342,11 @@ minimist@^1.2.0, minimist@^1.2.6:
resolved "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz" resolved "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz"
integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
mitt@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz"
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
ms@^2.1.1, ms@2.1.2: ms@^2.1.1, ms@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
@@ -2063,6 +2378,13 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
node-fetch@^2.6.9:
version "2.7.0"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
node-releases@^2.0.14: node-releases@^2.0.14:
version "2.0.14" version "2.0.14"
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz"
@@ -2339,7 +2661,25 @@ queue-microtask@^1.2.2:
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
react-dom@^18, react-dom@^18.2.0, react-dom@>=16.8: react-dnd-html5-backend@^14.0.0:
version "14.1.0"
resolved "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-14.1.0.tgz"
integrity sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==
dependencies:
dnd-core "14.0.1"
react-dnd@^14.0.2:
version "14.0.5"
resolved "https://registry.npmjs.org/react-dnd/-/react-dnd-14.0.5.tgz"
integrity sha512-9i1jSgbyVw0ELlEVt/NkCUkxy1hmhJOkePoCH713u75vzHGyXhPDm28oLfc2NMSBjZRM1Y+wRjHXJT3sPrTy+A==
dependencies:
"@react-dnd/invariant" "^2.0.0"
"@react-dnd/shallowequal" "^2.0.0"
dnd-core "14.0.1"
fast-deep-equal "^3.1.3"
hoist-non-react-statics "^3.3.2"
react-dom@^18, react-dom@^18.2.0, react-dom@>=16.8, react-dom@>=17.0.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -2357,7 +2697,7 @@ react-hook-form@^7.0.0, react-hook-form@^7.51.0:
resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.0.tgz" resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.0.tgz"
integrity sha512-BggOy5j58RdhdMzzRUHGOYhSz1oeylFAv6jUSG86OvCIvlAvS7KvnRY7yoAf2pfEiPN7BesnR0xx73nEk3qIiw== integrity sha512-BggOy5j58RdhdMzzRUHGOYhSz1oeylFAv6jUSG86OvCIvlAvS7KvnRY7yoAf2pfEiPN7BesnR0xx73nEk3qIiw==
react-is@^16.13.1: react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -2402,7 +2742,16 @@ react-router@6.22.2:
dependencies: dependencies:
"@remix-run/router" "1.15.2" "@remix-run/router" "1.15.2"
"react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18, react@^18.2.0, react@>=16.6.0, react@>=16.8: react-tag-input@^6.10.3:
version "6.10.3"
resolved "https://registry.npmjs.org/react-tag-input/-/react-tag-input-6.10.3.tgz"
integrity sha512-IVneBnT0/7N8FktHsFUA2UVDvcwLSJoWDTdEm/Ei/r5O6iBABaVw1RMh6SlG+P7oummvzSY6EBjGCXr/Ntq8rQ==
dependencies:
"@types/lodash-es" "^4.17.12"
classnames "~2.3.1"
lodash-es "^4.17.21"
"react@^16.14.0 || 17.x || 18.x || 19.x", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18, react@^18.2.0, "react@>= 16.14", react@>=16.6.0, react@>=16.8, react@>=17.0.0:
version "18.2.0" version "18.2.0"
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -2423,6 +2772,13 @@ readdirp@~3.6.0:
dependencies: dependencies:
picomatch "^2.2.1" picomatch "^2.2.1"
redux@^4.1.1:
version "4.2.1"
resolved "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz"
integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==
dependencies:
"@babel/runtime" "^7.9.2"
regenerator-runtime@^0.14.0: regenerator-runtime@^0.14.0:
version "0.14.1" version "0.14.1"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz"
@@ -2712,6 +3068,11 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
symbol.inspect@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/symbol.inspect/-/symbol.inspect-1.0.1.tgz"
integrity sha512-YQSL4duoHmLhsTD1Pw8RW6TZ5MaTX5rXJnqacJottr2P2LZBF/Yvrc3ku4NUpMOm8aM0KOCqM+UAkMA5HWQCzQ==
synckit@^0.8.6: synckit@^0.8.6:
version "0.8.8" version "0.8.8"
resolved "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz" resolved "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz"
@@ -2786,6 +3147,11 @@ to-regex-range@^5.0.1:
dependencies: dependencies:
is-number "^7.0.0" is-number "^7.0.0"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
ts-api-utils@^1.0.1: ts-api-utils@^1.0.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz" resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz"
@@ -2816,6 +3182,16 @@ tslib@^2.6.2:
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
tweetnacl-util@^0.15.1:
version "0.15.1"
resolved "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz"
integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==
tweetnacl@^1.0.3, tweetnacl@1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz"
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
type-check@^0.4.0, type-check@~0.4.0: type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
@@ -2872,11 +3248,16 @@ typed-array-length@^1.0.5:
is-typed-array "^1.1.13" is-typed-array "^1.1.13"
possible-typed-array-names "^1.0.0" possible-typed-array-names "^1.0.0"
typescript@^5.0.0, typescript@^5.2.2, typescript@>=4.2.0: typescript@^5.0.0, typescript@^5.2.2, typescript@>=4.2.0, typescript@>=5:
version "5.3.3" version "5.3.3"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz" resolved "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz"
integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==
ua-parser-js@^1.0.35:
version "1.0.40"
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz"
integrity sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==
unbox-primitive@^1.0.2: unbox-primitive@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz"
@@ -2925,6 +3306,11 @@ util-deprecate@^1.0.2:
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
valibot@1.0.0-beta.14:
version "1.0.0-beta.14"
resolved "https://registry.npmjs.org/valibot/-/valibot-1.0.0-beta.14.tgz"
integrity sha512-tLyV2rE5QL6U29MFy3xt4AqMrn+/HErcp2ZThASnQvPMwfSozjV1uBGKIGiegtZIGjinJqn0SlBdannf18wENA==
vite-tsconfig-paths@^4.3.1: vite-tsconfig-paths@^4.3.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.1.tgz" resolved "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.1.tgz"
@@ -2945,6 +3331,19 @@ vite@*, "vite@^4.2.0 || ^5.0.0", vite@^5.1.4:
optionalDependencies: optionalDependencies:
fsevents "~2.3.3" fsevents "~2.3.3"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
which-boxed-primitive@^1.0.2: which-boxed-primitive@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz"