Compare commits
96 Commits
61878c20ba
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5adb1e807b | |||
| 0b2425f506 | |||
| 8487ac5ca0 | |||
| 517059d53e | |||
| d1e6529950 | |||
| d296032e90 | |||
| da55c61edd | |||
| f4af2fd137 | |||
| fd66ca9c9b | |||
| a85f9aabd5 | |||
| d812b8b44a | |||
| 3892a459e5 | |||
| 27f125cbe8 | |||
| 0e92966a5d | |||
| 6a399ea7ca | |||
| 5b915bbc22 | |||
| 0409d63874 | |||
| b86f3209f5 | |||
| 895bec2a50 | |||
| 9b1d6ffb5d | |||
| bdc8bd3d93 | |||
| efbb94b43d | |||
| 8dfd48fe52 | |||
| 71b799dbf3 | |||
| 2026230ff6 | |||
| 2e6ed487fd | |||
| acd30dbb13 | |||
| b3831273d6 | |||
| 91282ba908 | |||
| 5e71e8b7c9 | |||
| 433c275f40 | |||
| 96ea3788d5 | |||
| aa25c6dec5 | |||
| 61c50eecc1 | |||
| f425cef139 | |||
| 52a0b7f3c7 | |||
| fac5e2ea5e | |||
| e9d1b733a5 | |||
| 8a5ff01619 | |||
| 39897c5bfe | |||
| c1cdf835af | |||
| 1ec70fcab8 | |||
| 29fbd71d8f | |||
| fad50a1b1b | |||
| 7907ff0def | |||
| 370141c83b | |||
| 36196a882f | |||
| 22ceb8e2b4 | |||
| 2a48d4a4c5 | |||
| 01d72f4885 | |||
| 2d1c1654f9 | |||
| ed5c7ea79d | |||
| b0dc637b8f | |||
| b5791a0871 | |||
| bd3d747ede | |||
| 73d1fd9135 | |||
| 253407abc3 | |||
| 3387769578 | |||
| b0a9b3c12a | |||
| 272d99a2bd | |||
| 3437c6259b | |||
| 6fc9f38182 | |||
| 7c8e812d4b | |||
| 0d114e12c2 | |||
| 26a7315ea1 | |||
| 51d0b8b3fc | |||
| 7038542f14 | |||
| c05fa1eaa5 | |||
| dc470bf74b | |||
| b2fc051cbb | |||
| cdbd9318cf | |||
| 0668ecccf3 | |||
| 789d99aa12 | |||
| ca3dd78783 | |||
| c1472d5363 | |||
| c2a1ca3ee5 | |||
| 1e5f792854 | |||
| 43cd51aa13 | |||
| 2d58658420 | |||
| e837351a6e | |||
| fcfdac87b4 | |||
| 2de30fbde6 | |||
| 22bb446309 | |||
| 168dedcdc5 | |||
| 5e5fe02bb7 | |||
| 226371e0f6 | |||
| b4502027fa | |||
| 926cd5dd06 | |||
| d641036327 | |||
| 8d5dc3a5d1 | |||
| 4913765584 | |||
| 9c3fbbdc4b | |||
| f5562871c0 | |||
| 020bf86404 | |||
| 79f1ee371b | |||
| 0f88a68c59 |
1
dist/assets/index-3J8Zo9Sf.css
vendored
1
dist/assets/index-3J8Zo9Sf.css
vendored
File diff suppressed because one or more lines are too long
161
dist/assets/index-CF-a3AIG.js
vendored
Normal file
161
dist/assets/index-CF-a3AIG.js
vendored
Normal file
File diff suppressed because one or more lines are too long
60
dist/assets/index-Hdj79d7b.js
vendored
60
dist/assets/index-Hdj79d7b.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-f9EVcxOv.css
vendored
Normal file
1
dist/assets/index-f9EVcxOv.css
vendored
Normal file
File diff suppressed because one or more lines are too long
97
dist/assets/popcat-DOGy5LFs.svg
vendored
Normal file
97
dist/assets/popcat-DOGy5LFs.svg
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 994.3 1000.5" style="enable-background:new 0 0 994.3 1000.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F7F5F8;}
|
||||
.st1{fill:#DCC4D4;}
|
||||
.st2{fill:#EFAD93;}
|
||||
.st3{fill:#D68C72;}
|
||||
.st4{fill:#964C49;}
|
||||
.st5{fill:#3A0101;}
|
||||
.st6{fill:#894544;}
|
||||
.st7{fill:#E3746D;}
|
||||
.st8{fill:#FFFFFF;}
|
||||
.st9{fill:#623538;}
|
||||
.st10{fill:#C95755;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M27.4,444.9l471.8-254l124.2-2.5c0,0,129.7,15.8,232.7,116.6c0.1,0.1,0.2,0.1,0.2,0.2
|
||||
c28.2,27.7,54.4,62.5,75.1,106.3c1.4,2.9,2.8,5.9,4.1,8.9c0,0,151.2,244-41.3,456.4c-192.5,212.3-651.4,110.6-789.3-112.5
|
||||
c-1-1.5-2-3.1-3-4.7c-21.2-34.1-37-67.9-48.6-99.2c-14.7-39.6-22.9-75.1-27.4-101.6C20.8,528.5,27.5,444.9,27.4,444.9z"/>
|
||||
<path class="st1" d="M218.8,420.3c-31.3,88.9,7.8,189.9,85.9,99.6c111.2-153,321.4-183.3,405.1-172.5
|
||||
c102.5,19.8,182.5,123.4,207.9,125.4c25.4,2,13.7-61.3,13.7-61.3c1.4,2.9,2.8,5.9,4.1,8.9c0,0,151.2,244-41.3,456.4
|
||||
C701.7,1089.1,242.8,987.4,105,764.2c-1-1.5-2-3.1-3-4.7c-21.2-34.1-37-67.9-48.6-99.2C106.8,572.1,243.9,348.5,218.8,420.3z"/>
|
||||
<path class="st2" d="M680.7,183.7c49.4-48.5,129.9-117.2,190.6-152.4c29.2-17,53.8-26.2,68.1-21.7c2.8,0.8,5.1,2.2,7.1,4.1
|
||||
c23.7,23.2,14.4,169.4,3.6,279.9c-7,70.6-14.6,126.7-14.6,126.7c-15.5-33.2-32-65.7-81.8-118c-20.7-20.3-41.8-37.4-63.2-51.7
|
||||
c-36-24-72.5-40.1-108.3-50.3c-4.8-1.4-9.6-2.6-14.4-3.8C671.8,192.5,676.1,188.2,680.7,183.7z"/>
|
||||
<path class="st3" d="M871.3,31.4c-10.1,73.5-12.2,200.9,78.8,262.3c-7,70.6-14.6,126.7-14.6,126.7c-15.5-33.2-32-65.7-81.8-118
|
||||
c-20.7-20.3-41.8-37.4-63.2-51.7c-36-24-72.5-40.1-108.3-50.3l-1.6-16.6C730.1,135.2,810.6,66.6,871.3,31.4z"/>
|
||||
<path class="st4" d="M51.1,14.9c49.7-15.8,213.2,136.5,315.7,216.5c0,0,141.2-74,301.1-34.9c7.5,1.8,15.1,3.9,22.8,6.3
|
||||
c-46.6-1.7-115.4-3.1-144.9,2.2C499,213.5,329,264,254.7,383.2c-74.3,119.1-71.3,258.8-69.5,320.2c1.4,48.9-34.4,79.5-88.3,48
|
||||
c-44.5-74.3-63.2-146.8-71-192.6c-1.1-10.1-2.2-20.5-3.5-31c-0.8-6-1.4-12-2.1-17.9C-12.3,223.4,2.5,30.3,51.1,14.9z"/>
|
||||
<path class="st4" d="M939.4,9.6c-41.2,32.2-128.1,113.5-148.9,241c-40.8-27.2-82.3-44.2-122.7-54.1C736,126.7,892.6-5,939.4,9.6z"
|
||||
/>
|
||||
<path class="st5" d="M667.3,196.7c18-21.7,38.7-41.3,59-60.8c30.9-28.7,63.1-56.1,97.5-80.5c23.1-16.3,47-31.5,73.2-42.8
|
||||
c13.8-5.3,28.2-11.2,43.7-6.9c3.2,0.8,4.1,5.5,1.3,7.4c-48,35.5-89.8,81.2-115,136l-7.3,16.4l-6.3,16.8c-8.3,23.1-13.5,47.2-19,71
|
||||
c-1.4,2.2-4.4,2.7-6.5,1.3c-9.3-6.2-18.7-12.1-28.3-17.7C730.6,220,699.1,207.3,667.3,196.7L667.3,196.7z M668.3,196.4
|
||||
c33.5,5.2,65.5,17.4,95.4,33c10,5.2,19.8,11.1,29.3,17.3l-7.2,3.2c11.1-73.1,51.8-137.4,99-192.5c15.9-18.4,33.2-35.6,52-51.1
|
||||
l1.3,7.4c-34.7-7-111.7,54.4-141.3,75.9C755.4,121.4,714.9,155,676,189.9C673.8,191.8,670.7,194.7,668.3,196.4L668.3,196.4z"/>
|
||||
<path class="st5" d="M667,196.8c9.8-12.7,21.5-23.8,32.5-35.3c56.3-55.7,115.3-109,185.4-147.2C903.6,5.5,934-9.3,952.6,8
|
||||
c8.6,9.3,11,23.4,12.9,34.8c7.6,63.5,0.5,127.1-5.9,190.2c-7.1,62.7-14.3,125.2-20.2,188c-0.1,1.2-0.9,2.5-2.1,3.1
|
||||
c-1.9,1-4.3,0.2-5.3-1.7c-21.3-43.7-51.6-81.8-85.2-116.5c-29-28.5-61.5-53.6-97.1-73.3C723.7,217.7,695.1,206.6,667,196.8
|
||||
L667,196.8z M668.6,196.3c102.9,16.6,200.3,90.3,251,181.1c7.4,13.4,13.7,27.4,19.6,41.3l-7.6,1.2c15.8-93,21.6-187.6,22.5-281.8
|
||||
c0.1-30.9,0.3-62.6-4.6-92.8c-1.5-8.2-3.1-16-6.7-22.5c-2.2-3.9-5.1-5.3-10-5.6c-13.2-0.3-28,6.5-40.3,12.5
|
||||
c-13.6,6.6-26.9,14.5-39.9,22.9c-62,40.5-119.8,87.7-175.4,136.6C674.9,191.3,671.1,194.7,668.6,196.3L668.6,196.3z"/>
|
||||
<path class="st5" d="M368.1,222.4c0,0-27.7,15.2-48.3,39.2c32.8-26.4,66.9-39.2,66.9-39.2H368.1z"/>
|
||||
<path class="st5" d="M51.4,15.6C-13.1,43.9,7.5,316.1,13.6,383c3.6,42.3,8.6,84.5,13.7,126.7c0.9,16.6,3.4,33.8,6.5,50.3
|
||||
c15.8,83.2,49,164.3,101,231.4c162.6,201.4,596.3,285.2,772.8,64.1c93.2-112.3,103.3-255.1,47.5-387.3c-6.8-15.7-14.3-31.3-23-45.7
|
||||
c-9.8-21.3-21.3-41.7-34.7-60.8C858,307,809.5,265.5,746.8,233.1c-117.3-59-260-50.3-377.9,2.6c-1.6,0.8-3.7,0.7-5.2-0.5
|
||||
c-50.3-40.8-98.5-83.9-148.5-125C183.7,85.2,89.5,5.3,51.4,15.6L51.4,15.6z M50.9,14.1c38.2-10.9,133.1,68.2,166.8,93
|
||||
c51.1,39.6,100.6,81.4,152,120.5l-5.2-0.5c10.1-5.3,20.2-9.7,30.6-13.9c113.6-45.9,247.7-50.1,357.9,7.6
|
||||
c38.5,19.3,80,51.8,106.2,79.6c28.3,26.3,63.5,77.6,79.6,117.9c37.5,68.6,57.7,147.2,55.3,225.5c-2,78.7-32.2,155.1-80.9,216.3
|
||||
c-144.1,184.7-435.8,164.2-626.6,69.8c-70.3-34.9-136.1-83.3-181-148.9c-52.9-79.3-87.3-177.1-92-270.3
|
||||
c-12-126.6-19.9-254.6-7-381.4C10.8,98.9,18.7,25.8,50.9,14.1L50.9,14.1z"/>
|
||||
<path class="st2" d="M56.5,68.9c10.8,4.5,48,42.6,88.9,92c33.1,40,68.6,87.5,94.4,130.5C256.3,318.9,134.9,522,113,560.3
|
||||
c-20.1,35.1-64.5-92-76.1-226c-1.1-12-1.8-23.9-2.3-35.9C28.8,152.3,36.9,60.8,56.5,68.9z"/>
|
||||
<path class="st6" d="M327.6,643.2c7.1-85.1,52.7-166.4,117.6-206.6c75-42,139.1-61.4,202.6-61.1l0,0c15.2,0.1,30.5,1.3,45.8,3.6
|
||||
c15,2.3,30.1,5.6,45.5,9.9c66,18.5,208.6,88.2,186.5,282.8c-0.7,5.8-1.5,11.5-2.5,17.2C891,873.9,688.2,995.2,467.4,887.4
|
||||
C358,834,319.8,736.3,327.6,643.2z"/>
|
||||
<path class="st7" d="M636.5,336.4c9.7,2.9,32.2,11.3,56.1-1c23.9-12.3,33.2,15.1,20.5,22.1c-14.4,7.9-18.4,17-19.6,21.7
|
||||
c-15.3-2.3-30.5-3.5-45.8-3.6l0,0c-4.1-7.4-25.4-8.4-33.2-21.1C606.7,341.7,626.8,333.4,636.5,336.4z"/>
|
||||
<path class="st8" d="M386.7,314c6.2-5.8,12.6-10.9,19.1-15.3c58-40,121.3-28.3,130.8-3.7c10.6,27.3-35.8,36.7-64.2,48.6
|
||||
c-25.9,10.8-56.1,30.6-78.7,43.5c-16.6,9.5-29.3,15.3-33.1,11.1C351.5,388.3,342.5,354.9,386.7,314z"/>
|
||||
<path class="st8" d="M773.8,272.5c33.4,12.4,58.6,33.3,75,50.7c11.4,12.1,18.4,22.5,20.9,27.2c6.1,11.4-12.7,14-36.2,5.3
|
||||
c-1-0.4-2.1-0.8-3.2-1.2c-25.3-9-84.3-25.6-101.1-33C705.1,310.9,717.2,251.5,773.8,272.5z"/>
|
||||
<path class="st5" d="M405.8,298.7c58-40,121.3-28.3,130.8-3.7c10.6,27.3-35.8,36.7-64.2,48.6c-25.9,10.8-56.1,30.6-78.7,43.5
|
||||
C370.3,352.9,386.3,320.8,405.8,298.7z"/>
|
||||
<path class="st5" d="M773.8,272.5c33.4,12.4,58.6,33.3,75,50.7c0.6,10.9-2.7,23.7-18.5,31.3c-25.3-9-84.3-25.6-101.1-33
|
||||
C705.1,310.9,717.2,251.5,773.8,272.5z"/>
|
||||
<path class="st5" d="M504.4,274.3c-72.3-19-136.4,35.7-149,74s-0.5,51.9,0,40.6s0-28.9,18-52.9c18.1-24,76-50.5,76-50.5
|
||||
S477.1,292.5,504.4,274.3z"/>
|
||||
<path class="st5" d="M869.5,341.4c-20.4-25.1-54.2-47.2-54.2-47.2s5.1,17,18.1,25.7c13,8.8,37.3,35.8,37.3,35.8
|
||||
S876.6,352.4,869.5,341.4z"/>
|
||||
<path class="st8" d="M504.4,294.2c-8.5,0.9-74.6,10.1-65.2-3.7s34.7-12.3,55.5-10.7C517.1,281.5,512.9,293.4,504.4,294.2z"/>
|
||||
<path class="st8" d="M803.6,314.5c-12.8-6.2-34.7-17.4-31.5-26s27.8,6.4,42.5,14C833.1,312,816.4,320.7,803.6,314.5z"/>
|
||||
<path class="st9" d="M445.2,436.6c75-42,139.1-61.4,202.6-61.1l0,0c15.2,0.1,30.5,1.3,45.8,3.6c15,2.3,30.1,5.6,45.5,9.9
|
||||
c66,18.5,208.6,88.2,186.5,282.7c-0.7,5.8-1.5,11.5-2.5,17.2C859.5,755.1,757.8,798,643,797.9c-139.3,0-259.6-63.2-315.5-154.7
|
||||
C334.6,558.1,380.2,476.8,445.2,436.6z"/>
|
||||
<path class="st5" d="M467,888.1c-4.1-2.1-15-8-19-10.1c-4.3-2.5-13.8-8.5-18.2-11.3c-5.7-3.9-11.5-8.7-17.3-12.6
|
||||
c-12.8-10.7-25.3-22-35.9-35c-51-59.2-67.4-143.5-50-219.2c14.9-66.8,52.5-130.4,110.4-168.2c74.4-43.3,162.1-73,249-60.2
|
||||
c66.6,9.1,131.6,37.9,178.2,87.3c47.5,48.6,68.8,117.8,66.7,185c-0.4,38.6-7.7,77.3-22.3,113.1C871.9,848.2,785.9,914.7,689,928
|
||||
C613.2,939.7,535.3,921.3,467,888.1L467,888.1z M467.7,886.6c103.1,51,224,52.6,322.3-10.2c62.5-40,109.6-104.2,126.4-177.1
|
||||
c12.6-54.7,12.2-114.2-9.7-166.5C878,462.3,811,414,739.3,394.7C633,365.6,539,391.2,446.1,446.3
|
||||
C361.9,503,321.5,614.9,336.4,714.1c5.5,34.6,19.1,68.2,39.4,96.7c3.2,4,7.6,9.9,10.8,13.9c6.6,7.1,14.9,16.4,22.4,22.6l5.2,4.8
|
||||
l5.6,4.3c6.3,5.3,16.2,11.9,23.2,16.3C449.7,877.2,460.6,882.6,467.7,886.6L467.7,886.6z"/>
|
||||
<path class="st10" d="M624.5,349.4c9.9-5,28,14.1,28,14.1C630,361.9,614.5,354.5,624.5,349.4z"/>
|
||||
<path class="st10" d="M709.7,347.4c-8.3-4.5-19.1,16.1-19.1,16.1C701.2,361.2,718,351.9,709.7,347.4z"/>
|
||||
<path class="st3" d="M145.4,160.9c33.1,40,68.6,87.5,94.4,130.5c16.5,27.5-104.9,230.6-126.9,268.9c-20.1,35.1-64.5-92-76.1-226
|
||||
C134.4,390.3,148.1,261.2,145.4,160.9z"/>
|
||||
<path class="st5" d="M239.3,291.7C203,231.8,156.6,179,109.9,127.3C94.3,110.6,78.5,93.1,61,78.7c-2.4-1.8-4.8-3.7-6.8-4.5
|
||||
c-2.9,2.5-5.2,9.1-6.4,13.8c-7.9,33.4-8.7,68.6-9.8,103.1c-0.4,23.2-0.5,46.6-0.3,69.9c-0.2,70,5.5,140.1,23.6,207.9
|
||||
c5.3,20,24.9,86.9,43.7,93.3c1.6-0.1,2.6-1.2,3.6-2.7l0.9-1.5c3.4-7.1,73.1-128.3,78.8-138.8C201.1,395.1,248.5,315.1,239.3,291.7
|
||||
L239.3,291.7z M240.3,291.1c10.1,18.9-35.6,108.8-46,131.3c-23,47.3-49.2,93.1-76.6,137.9c-2,4.1-7.1,10.6-13.3,9.8
|
||||
c-30.8-4.3-55.4-138.4-61-168.9C30.8,332,27.6,261.4,27.8,191.1c0.8-28.9-0.2-103.5,17.4-124.3c7.7-8.1,16.3-2.5,22.8,3
|
||||
c9.4,7.7,17.6,16.1,25.8,24.6C150,154,197.5,221.4,240.3,291.1L240.3,291.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.6 KiB |
52
dist/assets/uni-C5oaqT41.svg
vendored
Normal file
52
dist/assets/uni-C5oaqT41.svg
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 400 432.7" style="enable-background:new 0 0 400 432.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F50DB4;}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#F50DB4;}
|
||||
</style>
|
||||
<path class="st0" d="M325.1,65.4c0.6-10.2,2-17,4.8-23.1c1.1-2.4,2.1-4.4,2.3-4.4s-0.3,1.8-1.1,4c-2,6-2.4,14.2-1,23.7
|
||||
c1.8,12.1,2.8,13.8,15.6,26.9c6,6.1,13,13.8,15.5,17.1l4.6,6l-4.6-4.3c-5.6-5.3-18.6-15.5-21.4-17c-1.9-1-2.2-1-3.4,0.2
|
||||
c-1.1,1.1-1.3,2.7-1.5,10.4c-0.2,12-1.9,19.7-5.8,27.3c-2.1,4.2-2.5,3.3-0.5-1.4c1.4-3.5,1.6-5,1.6-16.6c0-23.3-2.8-28.9-19.1-38.5
|
||||
c-4.1-2.4-10.9-5.9-15.1-7.8c-4.2-1.9-7.5-3.5-7.4-3.6c0.5-0.5,16.3,4.2,22.7,6.6c9.5,3.6,11.1,4.1,12.2,3.7
|
||||
C324.4,74.2,324.8,72,325.1,65.4z"/>
|
||||
<path class="st0" d="M124.4,31.5c-5.6-0.9-5.9-1-3.2-1.4c5.1-0.8,17.1,0.3,25.4,2.2c19.3,4.6,36.9,16.3,55.6,37l5,5.5l7.1-1.1
|
||||
c30-4.8,60.6-1,86.1,10.8c7,3.2,18.1,9.7,19.5,11.3c0.4,0.5,1.3,3.9,1.8,7.5c1.9,12.5,0.9,22.1-2.9,29.3c-2.1,3.9-2.2,5.1-0.8,8.5
|
||||
c1.1,2.7,4.3,4.6,7.4,4.6c6.3,0,13.2-10.2,16.3-24.4l1.3-5.6l2.5,2.8c13.7,15.4,24.4,36.4,26.2,51.3l0.5,3.9l-2.3-3.5
|
||||
c-4-6.1-7.9-10.2-13-13.6c-9.2-6-18.9-8.1-44.5-9.4c-23.2-1.2-36.3-3.2-49.3-7.4c-22.1-7.2-33.3-16.7-59.6-51.1
|
||||
c-11.7-15.2-18.9-23.7-26.1-30.5C161,42.8,145,34.7,124.4,31.5z"/>
|
||||
<path class="st0" d="M135.4,105.3c-11.4-15.7-18.5-39.7-17-57.6l0.5-5.6l2.6,0.5c4.9,0.9,13.3,4,17.3,6.4
|
||||
c10.8,6.5,15.5,15.2,20.3,37.3c1.4,6.5,3.2,13.8,4.1,16.3c1.4,4,6.5,13.3,10.7,19.4c3,4.4,1,6.4-5.6,5.8
|
||||
C158,126.9,144.2,117.5,135.4,105.3z"/>
|
||||
<path class="st0" d="M311.3,221.9c-53.5-21.4-72.3-40-72.3-71.4c0-4.6,0.2-8.4,0.4-8.4c0.2,0,2.3,1.5,4.6,3.4
|
||||
c10.8,8.6,23,12.3,56.6,17.2c19.8,2.9,30.9,5.2,41.2,8.6c32.6,10.8,52.8,32.6,57.6,62.4c1.4,8.6,0.6,24.9-1.7,33.4
|
||||
c-1.8,6.7-7.3,18.9-8.7,19.4c-0.4,0.1-0.8-1.4-0.9-3.5c-0.6-11.2-6.2-22-15.8-30.2C361.4,243.6,346.9,236.2,311.3,221.9z"/>
|
||||
<path class="st0" d="M273.7,230.8c-0.7-4-1.8-9-2.6-11.3l-1.4-4l2.5,2.8c3.5,3.9,6.3,8.9,8.6,15.6c1.8,5.1,2,6.6,2,14.9
|
||||
c0,8.1-0.2,9.8-1.9,14.4c-2.6,7.2-5.8,12.4-11.3,17.9c-9.8,9.9-22.3,15.4-40.4,17.6c-3.1,0.4-12.3,1.1-20.4,1.5
|
||||
c-20.3,1.1-33.7,3.2-45.8,7.4c-1.7,0.6-3.3,1-3.4,0.8c-0.5-0.5,7.7-5.3,14.5-8.6c9.5-4.6,19-7.1,40.3-10.6
|
||||
c10.5-1.7,21.4-3.9,24.1-4.7C264.6,276.7,278,256.2,273.7,230.8z"/>
|
||||
<path class="st0" d="M298.2,274.1c-7.1-15.2-8.7-29.9-4.8-43.5c0.4-1.5,1.1-2.7,1.5-2.7c0.4,0,2.1,0.9,3.7,2
|
||||
c3.3,2.2,9.8,5.9,27.3,15.4c21.8,11.8,34.3,21,42.7,31.5c7.4,9.2,12,19.6,14.2,32.3c1.3,7.2,0.5,24.6-1.3,31.8
|
||||
c-5.9,22.9-19.5,40.9-39,51.4c-2.9,1.5-5.4,2.8-5.7,2.8c-0.3,0,0.8-2.6,2.3-5.8c6.5-13.6,7.3-26.8,2.3-41.6c-3-9-9.2-20-21.7-38.6
|
||||
C305.4,287.5,301.8,281.7,298.2,274.1z"/>
|
||||
<path class="st0" d="M97.4,356.1c19.8-16.7,44.5-28.5,67-32.1c9.7-1.6,25.8-0.9,34.8,1.3c14.4,3.7,27.3,11.9,33.9,21.6
|
||||
c6.5,9.5,9.3,17.9,12.3,36.4c1.2,7.3,2.4,14.6,2.8,16.3c2.2,9.6,6.5,17.3,11.8,21.1c8.4,6.1,22.9,6.5,37.1,1
|
||||
c2.4-0.9,4.5-1.6,4.7-1.4c0.5,0.5-6.7,5.3-11.7,7.8c-6.8,3.4-12.2,4.7-19.4,4.7c-13,0-23.9-6.6-32.9-20c-1.8-2.6-5.8-10.6-8.9-17.6
|
||||
c-9.5-21.6-14.2-28.2-25.3-35.4c-9.6-6.3-22.1-7.4-31.4-2.8c-12.3,6-15.7,21.6-6.9,31.5c3.5,3.9,10,7.3,15.3,8
|
||||
c10,1.2,18.5-6.3,18.5-16.3c0-6.5-2.5-10.2-8.8-13.1c-8.6-3.9-17.9,0.7-17.9,8.7c0,3.4,1.5,5.6,5,7.2c2.2,1,2.3,1.1,0.5,0.7
|
||||
c-7.9-1.6-9.8-11.1-3.4-17.4c7.7-7.6,23.5-4.2,28.9,6.1c2.3,4.3,2.5,13,0.6,18.2c-4.5,11.7-17.4,17.8-30.6,14.5
|
||||
c-9-2.3-12.6-4.7-23.4-15.8c-18.8-19.3-26.1-23-53.2-27.2l-5.2-0.8L97.4,356.1z"/>
|
||||
<path class="st1" d="M9.2,11.5c62.8,75.7,106,107,110.8,113.6c4,5.5,2.5,10.4-4.3,14.2c-3.8,2.1-11.5,4.3-15.4,4.3
|
||||
c-4.4,0-5.9-1.7-5.9-1.7c-2.6-2.4-4-2-17.1-25.1C59.1,88.8,43.9,65.5,43.5,65.1c-1-0.9-0.9-0.9,32,57.7c5.3,12.2,1.1,16.7,1.1,18.4
|
||||
c0,3.5-1,5.4-5.4,10.3c-7.3,8.1-10.6,17.2-12.9,36.1c-2.6,21.1-10.1,36.1-30.7,61.6c-12.1,15-14,17.7-17.1,23.7
|
||||
c-3.8,7.6-4.9,11.9-5.3,21.4c-0.5,10.1,0.4,16.7,3.5,26.4c2.7,8.5,5.6,14.1,12.9,25.3c6.3,9.7,9.9,16.9,9.9,19.7
|
||||
c0,2.2,0.4,2.2,10.2,0.1c23.3-5.2,42.2-14.4,52.8-25.7c6.6-7,8.1-10.8,8.2-20.4c0-6.3-0.2-7.6-1.9-11.2c-2.8-5.9-7.8-10.7-18.9-18.3
|
||||
c-14.5-9.9-20.8-17.9-22.5-28.8c-1.4-9,0.2-15.3,8.3-32.1c8.3-17.4,10.4-24.8,11.8-42.3c0.9-11.3,2.2-15.8,5.4-19.3
|
||||
c3.4-3.7,6.5-5,14.9-6.1c13.7-1.9,22.5-5.4,29.7-12c6.2-5.7,8.9-11.2,9.3-19.5l0.3-6.3l-3.5-4C122.8,105.1,0.8,0,0,0
|
||||
C-0.2,0,4,5.2,9.2,11.5z M38.5,305.9c2.9-5,1.3-11.5-3.4-14.7c-4.5-3-11.5-1.6-11.5,2.3c0,1.2,0.7,2.1,2.1,2.8
|
||||
c2.5,1.3,2.7,2.7,0.7,5.7c-2,3-1.8,5.6,0.5,7.4C30.5,312.3,35.7,310.7,38.5,305.9z"/>
|
||||
<path class="st1" d="M147.6,164.9c-6.5,2-12.7,8.8-14.7,15.9c-1.2,4.4-0.5,12,1.3,14.3c2.9,3.8,5.6,4.8,13.1,4.8
|
||||
c14.7-0.1,27.5-6.4,29-14.2c1.2-6.4-4.4-15.3-12.1-19.2C160.2,164.4,151.7,163.6,147.6,164.9z M164.8,178.3c2.3-3.2,1.3-6.7-2.6-9
|
||||
c-7.3-4.5-18.4-0.8-18.4,6.1c0,3.4,5.8,7.2,11.1,7.2C158.4,182.6,163.2,180.5,164.8,178.3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
4
dist/index.html
vendored
4
dist/index.html
vendored
@@ -5,8 +5,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ЭКСА — Ваш мост в мир цифровых активов</title>
|
||||
<script type="module" crossorigin src="/assets/index-Hdj79d7b.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-3J8Zo9Sf.css">
|
||||
<script type="module" crossorigin src="/assets/index-CF-a3AIG.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-f9EVcxOv.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
28
eslint.config.js
Normal file
28
eslint.config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
37
package-lock.json
generated
37
package-lock.json
generated
@@ -11,8 +11,10 @@
|
||||
"@reduxjs/toolkit": "^2.5.1",
|
||||
"@tanstack/react-query": "^5.100.9",
|
||||
"axios": "^1.7.9",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-easy-crop": "^5.5.7",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.1.5",
|
||||
"zod": "^3.24.1"
|
||||
@@ -3055,6 +3057,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-wheel": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
|
||||
"integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -3216,6 +3224,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode.react": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz",
|
||||
"integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.5",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
||||
@@ -3237,6 +3254,20 @@
|
||||
"react": "^19.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/react-easy-crop": {
|
||||
"version": "5.5.7",
|
||||
"resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.5.7.tgz",
|
||||
"integrity": "sha512-kYo4NtMeXFQB7h1U+h5yhUkE46WQbQdq7if54uDlbMdZHdRgNehfvaFrXnFw5NR1PNoUOJIfTwLnWmEx/MaZnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"tslib": "^2.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.4.0",
|
||||
"react-dom": ">=16.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
@@ -3516,6 +3547,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
"@reduxjs/toolkit": "^2.5.1",
|
||||
"@tanstack/react-query": "^5.100.9",
|
||||
"axios": "^1.7.9",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-easy-crop": "^5.5.7",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.1.5",
|
||||
"zod": "^3.24.1"
|
||||
|
||||
255
politika-cookie.txt
Normal file
255
politika-cookie.txt
Normal file
@@ -0,0 +1,255 @@
|
||||
ПОЛИТИКА ИСПОЛЬЗОВАНИЯ ФАЙЛОВ COOKIE
|
||||
====================================
|
||||
|
||||
Общие положения и терминология
|
||||
------------------------------
|
||||
|
||||
Настоящая Политика использования файлов cookie (далее — Политика) устанавливает порядок обработки файлов cookie и содержащихся в них персональных данных ООО «БИТФОРС» (далее — Оператор, мы) при использовании пользователями (далее — Субъекты персональных данных, вы) интернет-ресурса https://bitforce-foundation.ru.
|
||||
|
||||
Файлы cookie — это текстовые файлы небольшого размера, которые устанавливаются на пользовательское устройство (телефон, компьютер, планшет) при посещении интернет-ресурса или совершении на нем определенных действий. Файлы cookie остаются сохраненными на устройстве даже после покидания ресурса, что позволяет «узнавать» пользователя при последующих посещениях.
|
||||
|
||||
К персональным данным относится не сам файл cookie, а его содержимое — уникальные идентификаторы, IP-адреса, информация о предпочтениях пользователя и другие данные, позволяющие прямо или косвенно идентифицировать физическое лицо.
|
||||
|
||||
|
||||
Оператор персональных данных
|
||||
----------------------------
|
||||
|
||||
Оператором персональных данных, содержащихся в файлах cookie, является:
|
||||
|
||||
ООО «БИТФОРС»
|
||||
ИНН: 9810001062
|
||||
ОГРН: 1257800060990
|
||||
Юридический адрес: 196246, город Санкт-Петербург, Московское ш, д. 25 к. 1 литера В, помещ. 3-н
|
||||
|
||||
Оператор определяет цели обработки персональных данных, их состав, а также действия (операции) с персональными данными, включая случаи использования сторонних файлов cookie (third-party cookies), формат и механика работы которых определены третьими лицами (например, Яндекс.Метрика).
|
||||
|
||||
|
||||
Категории файлов cookie и их назначение
|
||||
---------------------------------------
|
||||
|
||||
На нашем интернет-ресурсе используются следующие категории файлов cookie:
|
||||
|
||||
|
||||
1. Строго необходимые (технические) файлы cookie
|
||||
------------------------------------------------
|
||||
|
||||
Данные файлы обеспечивают работу интернет-ресурса и предоставление необходимого уровня сервиса: авторизацию, навигацию, отображение контента в соответствии с параметрами устройства, обеспечение безопасности.
|
||||
|
||||
Обработка таких файлов cookie осуществляется на основании п. 5 ч. 1 ст. 6 ФЗ № 152 (заключение и исполнение договора) — Пользовательского соглашения, устанавливающего правила использования интернет-ресурса. Согласие на использование строго необходимых файлов cookie не требуется.
|
||||
|
||||
Примеры: файлы сессий (PHPSESSID), настройки безопасности, файлы аутентификации.
|
||||
|
||||
|
||||
2. Функциональные файлы cookie
|
||||
------------------------------
|
||||
|
||||
Используются для запоминания пользовательских предпочтений и персонализации взаимодействия с сайтом: сохранение выбранного языка, региона, настроек отображения, размера шрифта.
|
||||
|
||||
Обработка осуществляется на основании согласия субъекта персональных данных (п. 1 ч. 1 ст. 6 ФЗ № 152), поскольку данная обработка не является строго необходимой для функционирования сайта.
|
||||
|
||||
Примеры: настройки языка интерфейса, предпочтения отображения, настройки доступности.
|
||||
|
||||
|
||||
3. Аналитические файлы cookie
|
||||
-----------------------------
|
||||
|
||||
Собирают информацию о взаимодействии пользователей с интернет-ресурсом для анализа его использования, выявления популярных разделов, обнаружения ошибок и улучшения пользовательского опыта. Могут содержать персональные данные, включая IP-адреса пользователей.
|
||||
|
||||
Обработка осуществляется на основании согласия субъекта персональных данных (п. 1 ч. 1 ст. 6 ФЗ № 152), так как данная обработка используется Оператором для получения конкурентного преимущества и не связана напрямую с предоставлением пользователю сервиса.
|
||||
|
||||
|
||||
4. Маркетинговые файлы cookie
|
||||
-----------------------------
|
||||
|
||||
Используются для отслеживания пользователей в целях персонализированной рекламы, анализа эффективности рекламных кампаний, ретаргетинга. Обрабатывают персональные данные пользователей.
|
||||
|
||||
Обработка осуществляется исключительно на основании согласия субъекта персональных данных (п. 1 ч. 1 ст. 6 ФЗ № 152).
|
||||
|
||||
Примеры: пиксели социальных сетей, рекламные идентификаторы, файлы ретаргетинга.
|
||||
|
||||
|
||||
Правовые основания обработки персональных данных
|
||||
------------------------------------------------
|
||||
|
||||
Обработка персональных данных, содержащихся в файлах cookie, осуществляется на следующих правовых основаниях в соответствии со ст. 6 ФЗ № 152:
|
||||
|
||||
1. Согласие субъекта персональных данных (п. 1 ч. 1 ст. 6) — для функциональных, аналитических и маркетинговых файлов cookie. Согласие должно удовлетворять критериям конкретности, предметности, информированности, сознательности и однозначности в соответствии с ч. 1 ст. 9 ФЗ № 152.
|
||||
|
||||
2. Заключение и исполнение договора (п. 5 ч. 1 ст. 6) — для строго необходимых файлов cookie, обеспечивающих работу интернет-ресурса и исполнение Пользовательского соглашения.
|
||||
|
||||
3. Законные интересы оператора (п. 7 ч. 1 ст. 6) — в исключительных случаях, когда отсутствуют иные основания и при условии, что не нарушаются права и свободы субъекта персональных данных.
|
||||
|
||||
Важно: В соответствии с п. 5 ч. 1 ст. 6 ФЗ № 152 запрещается включать в договор положения, допускающие бездействие субъекта персональных данных в качестве формы согласия.
|
||||
|
||||
|
||||
Порядок получения согласия
|
||||
--------------------------
|
||||
|
||||
Согласие на обработку персональных данных, содержащихся в файлах cookie, получается в соответствии с требованиями ФЗ № 152 и практикой Роскомнадзора:
|
||||
|
||||
Принципы получения согласия:
|
||||
• Согласие должно быть получено до начала обработки персональных данных;
|
||||
• Информация об использовании файлов cookie размещается на первом уровне интернет-ресурса (всплывающее уведомление);
|
||||
• Предоставляется возможность выбора категорий файлов cookie;
|
||||
• Используются активные формулировки ("используя сайт", "продолжая пользоваться сайтом");
|
||||
• Исключаются пассивные формулировки ("оставаясь на сайте", "находясь на сайте").
|
||||
|
||||
Критерии действительного согласия:
|
||||
• Добровольность — согласие дается по свободной воле субъекта;
|
||||
• Конкретность — четко определены цели обработки;
|
||||
• Информированность — предоставлена полная информация об обработке;
|
||||
• Однозначность — согласие выражено в недвусмысленной форме.
|
||||
|
||||
|
||||
Сторонние файлы cookie
|
||||
----------------------
|
||||
|
||||
Использование сторонних сервисов:
|
||||
Наш интернет-ресурс использует файлы cookie сторонних сервисов, включая:
|
||||
• Яндекс.Метрика (ООО «ЯНДЕКС», Россия);
|
||||
• Социальные сети и сервисы интеграции.
|
||||
|
||||
Обеспечение защиты:
|
||||
• Получено согласие на передачу;
|
||||
• Применяются дополнительные меры защиты данных;
|
||||
• Контролируется соблюдение принципов обработки персональных данных получателями.
|
||||
|
||||
Ответственность за сторонние файлы cookie:
|
||||
Оператор несет ответственность за использование сторонних файлов cookie в соответствии с законодательством о персональных данных, поскольку определяет цели и средства их обработки на своем интернет-ресурсе.
|
||||
|
||||
|
||||
Сроки обработки и хранения
|
||||
--------------------------
|
||||
|
||||
Сроки обработки персональных данных, содержащихся в файлах cookie, определяются целями обработки и требованиями законодательства:
|
||||
|
||||
Категории по срокам хранения:
|
||||
• Сеансовые cookie — удаляются автоматически при закрытии браузера;
|
||||
• Постоянные cookie — хранятся установленный период или до удаления пользователем.
|
||||
|
||||
Конкретные сроки:
|
||||
• Необходимые файлы cookie — до 12 месяцев;
|
||||
• Функциональные файлы cookie — до 12 месяцев;
|
||||
• Аналитические файлы cookie — до 24 месяцев;
|
||||
• Маркетинговые файлы cookie — до 24 месяцев.
|
||||
|
||||
Автоматическое удаление:
|
||||
По истечении установленных сроков файлы cookie удаляются автоматически. Пользователь может удалить файлы cookie досрочно через настройки браузера или отозвать согласие на их обработку.
|
||||
|
||||
|
||||
Права субъектов персональных данных
|
||||
-----------------------------------
|
||||
|
||||
В соответствии с ФЗ № 152 субъекты персональных данных имеют следующие права:
|
||||
|
||||
Право на информацию (ст. 14 ФЗ № 152):
|
||||
• Получение информации о обработке персональных данных;
|
||||
• Сведения о правовых основаниях и целях обработки;
|
||||
• Информация о сроках обработки и составе данных.
|
||||
|
||||
Право на доступ (ст. 14 ФЗ № 152):
|
||||
• Получение подтверждения факта обработки;
|
||||
• Ознакомление с обрабатываемыми персональными данными;
|
||||
• Получение информации об источниках персональных данных.
|
||||
|
||||
Право на уточнение, блокирование, удаление (ст. 14 ФЗ № 152):
|
||||
• Требование уточнения неточных данных;
|
||||
• Блокирование недостоверных данных;
|
||||
• Удаление незаконно полученных данных.
|
||||
|
||||
Право на отзыв согласия:
|
||||
• Отзыв согласия в любое время;
|
||||
• Прекращение обработки после отзыва согласия;
|
||||
• Сохранение права на обжалование действий оператора.
|
||||
|
||||
Право на обжалование:
|
||||
• Обращение в Роскомнадзор или его территориальные органы;
|
||||
• Обращение в суд для защиты нарушенных прав;
|
||||
• Требование возмещения ущерба.
|
||||
|
||||
|
||||
Способы управления файлами cookie
|
||||
---------------------------------
|
||||
|
||||
Управление через настройки сайта:
|
||||
• Использование баннера согласия на файлы cookie;
|
||||
• Изменение настроек в любое время через интерфейс сайта;
|
||||
• Отзыв согласия на использование отдельных категорий файлов cookie.
|
||||
|
||||
Управление через браузер:
|
||||
Большинство браузеров позволяют контролировать файлы cookie:
|
||||
• Блокировка — запрет установки новых файлов cookie;
|
||||
• Удаление — очистка существующих файлов cookie;
|
||||
• Уведомления — получение предупреждений при установке файлов cookie;
|
||||
• Селективная настройка — разрешение файлов cookie только для определенных сайтов.
|
||||
|
||||
Инструкции для популярных браузеров:
|
||||
• Google Chrome: Настройки → Конфиденциальность и безопасность → Файлы cookie
|
||||
• Mozilla Firefox: Настройки → Приватность и Защита → Файлы cookie
|
||||
• Safari: Настройки → Конфиденциальность → Файлы cookie
|
||||
• Microsoft Edge: Настройки → Файлы cookie и разрешения сайтов
|
||||
|
||||
Важное замечание: Блокировка необходимых файлов cookie может привести к ограничению функциональности интернет-ресурса.
|
||||
|
||||
|
||||
Меры безопасности
|
||||
-----------------
|
||||
|
||||
Оператор применяет правовые, организационные и технические меры для защиты персональных данных в соответствии с требованиями ФЗ № 152 и постановления Правительства РФ № 1119:
|
||||
|
||||
Правовые меры:
|
||||
• Назначение ответственного за организацию обработки персональных данных;
|
||||
• Ознакомление сотрудников с требованиями законодательства;
|
||||
• Заключение соглашений о неразглашении персональных данных.
|
||||
|
||||
Организационные меры:
|
||||
• Определение перечня лиц, допущенных к обработке персональных данных;
|
||||
• Установление правил доступа к персональным данным;
|
||||
• Контроль за соблюдением требований по защите персональных данных.
|
||||
|
||||
Технические меры:
|
||||
• Использование средств защиты информации;
|
||||
• Применение криптографических средств защиты;
|
||||
• Обеспечение целостности и доступности персональных данных;
|
||||
• Регулярное обновление систем защиты информации.
|
||||
|
||||
|
||||
Обновления политики
|
||||
-------------------
|
||||
|
||||
Настоящая Политика может изменяться в связи с:
|
||||
• Изменениями в законодательстве Российской Федерации;
|
||||
• Изменениями в практике Роскомнадзора;
|
||||
• Развитием технологий обработки данных;
|
||||
• Изменениями в бизнес-процессах Оператора.
|
||||
|
||||
Процедура уведомления об изменениях:
|
||||
• Размещение обновленной Политики на интернет-ресурсе;
|
||||
• Указание даты последнего обновления;
|
||||
• Уведомление пользователей о существенных изменениях через интерфейс сайта;
|
||||
• При необходимости — получение нового согласия на обработку.
|
||||
|
||||
Рекомендуем регулярно проверять данную страницу для ознакомления с актуальной информацией.
|
||||
|
||||
|
||||
Контактная информация и обращения
|
||||
---------------------------------
|
||||
|
||||
Для реализации прав субъекта персональных данных обращайтесь к нам:
|
||||
|
||||
ООО «БИТФОРС»
|
||||
ИНН: 9810001062
|
||||
ОГРН: 1257800060990
|
||||
Юридический адрес: 196246, город Санкт-Петербург, Московское ш, д. 25 к. 1 литера В, помещ. 3-н
|
||||
|
||||
Контакты:
|
||||
• Email компании: company@bitforcefoundation.ru
|
||||
|
||||
Порядок рассмотрения обращений:
|
||||
• Срок рассмотрения обращений — до 30 дней с момента получения;
|
||||
• Обращения рассматриваются в письменной форме;
|
||||
• Ответ направляется способом, указанным в обращении;
|
||||
• При отказе в удовлетворении требований указываются мотивированные основания.
|
||||
|
||||
Обращения в контролирующие органы:
|
||||
Федеральная служба по надзору в сфере связи, информационных технологий и массовых коммуникаций (Роскомнадзор)
|
||||
549
politika-obrabotki-personalnyh-dannyh.txt
Normal file
549
politika-obrabotki-personalnyh-dannyh.txt
Normal file
@@ -0,0 +1,549 @@
|
||||
ПОЛИТИКА ОБРАБОТКИ ПЕРСОНАЛЬНЫХ ДАННЫХ
|
||||
======================================
|
||||
|
||||
ООО «БИТФОРС»
|
||||
|
||||
1. Общие положения
|
||||
------------------
|
||||
|
||||
1.1. Настоящая Политика обработки персональных данных (далее — Политика) разработана в соответствии с Федеральным законом от 27.07.2006 № 152-ФЗ «О персональных данных» (далее — Закон о персональных данных) и определяет порядок обработки персональных данных и меры по обеспечению безопасности персональных данных, предпринимаемые ООО «БИТФОРС» (далее — Оператор).
|
||||
|
||||
1.2. Оператор ставит своей важнейшей целью и условием осуществления своей деятельности соблюдение прав и свобод человека и гражданина при обработке его персональных данных, в том числе защиты права на неприкосновенность частной жизни, личную и семейную тайну.
|
||||
|
||||
1.3. Настоящая Политика действует в отношении всех персональных данных, которые обрабатываются Оператором с использованием средств автоматизации и без использования таких средств, при условии, что обработка персональных данных без использования средств автоматизации соответствует характеру действий (операций), совершаемых с персональными данными с использованием средств автоматизации.
|
||||
|
||||
|
||||
1.4. Основные понятия
|
||||
---------------------
|
||||
|
||||
Автоматизированная обработка персональных данных — обработка персональных данных с помощью средств вычислительной техники.
|
||||
|
||||
Блокирование персональных данных — временное прекращение обработки персональных данных (за исключением случаев, если обработка необходима для уточнения персональных данных).
|
||||
|
||||
Веб-сайт — совокупность графических и информационных материалов, а также программ для ЭВМ и баз данных, обеспечивающих их доступность в сети интернет по сетевому адресу https://bitforce-foundation.ru.
|
||||
|
||||
Информационная система персональных данных — совокупность содержащихся в базах данных персональных данных и обеспечивающих их обработку информационных технологий и технических средств.
|
||||
|
||||
Обезличивание персональных данных — действия, в результате которых невозможно определить без использования дополнительной информации принадлежность персональных данных конкретному субъекту персональных данных.
|
||||
|
||||
Обработка персональных данных — любое действие (операция) или совокупность действий (операций), совершаемых с использованием средств автоматизации или без использования таких средств с персональными данными, включая сбор, запись, систематизацию, накопление, хранение, уточнение (обновление, изменение), извлечение, использование, передачу (распространение, предоставление, доступ), обезличивание, блокирование, удаление, уничтожение персональных данных.
|
||||
|
||||
Оператор — государственный орган, муниципальный орган, юридическое или физическое лицо, самостоятельно или совместно с другими лицами организующие и/или осуществляющие обработку персональных данных, а также определяющие цели обработки персональных данных, состав персональных данных, подлежащих обработке, действия (операции), совершаемые с персональными данными.
|
||||
|
||||
Персональные данные — любая информация, относящаяся к прямо или косвенно определенному или определяемому физическому лицу (субъекту персональных данных).
|
||||
|
||||
Персональные данные, разрешенные субъектом персональных данных для распространения — персональные данные, доступ неограниченного круга лиц к которым предоставлен субъектом персональных данных путем дачи согласия на обработку персональных данных, разрешенных субъектом персональных данных для распространения.
|
||||
|
||||
Пользователь — любой посетитель веб-сайта https://bitforce-foundation.ru.
|
||||
|
||||
Предоставление персональных данных — действия, направленные на раскрытие персональных данных определенному лицу или определенному кругу лиц.
|
||||
|
||||
Распространение персональных данных — действия, направленные на раскрытие персональных данных неопределенному кругу лиц (передача персональных данных) или на ознакомление с персональными данными неограниченного круга лиц, в том числе обнародование персональных данных в средствах массовой информации, размещение в информационно-телекоммуникационных сетях или предоставление доступа к персональным данным каким-либо иным способом.
|
||||
|
||||
Уничтожение персональных данных — действия, в результате которых невозможно восстановить содержание персональных данных в информационной системе персональных данных и/или результате которых уничтожаются материальные носители персональных данных.
|
||||
|
||||
|
||||
2. Сведения об операторе
|
||||
------------------------
|
||||
|
||||
2.1. Полное наименование оператора: Общество с ограниченной ответственностью «БИТФОРС»
|
||||
|
||||
2.2. Сокращенное наименование: ООО «БИТФОРС»
|
||||
|
||||
2.3. ИНН: 9810001062
|
||||
|
||||
2.4. ОГРН: 1257800060990
|
||||
|
||||
2.5. Реестр ОПД: Посмотреть в реестре Роскомнадзора (https://pd.rkn.gov.ru/operators-registry/operators-list/?act=search&name_full=%D0%91%D0%B8%D1%82%D1%84%D0%BE%D1%80%D1%81&inn=9810001062®n=)
|
||||
|
||||
2.6. Юридический адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н
|
||||
|
||||
2.7. Почтовый адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н
|
||||
|
||||
2.8. Контактная информация:
|
||||
• Электронная почта: company@bitforcefoundation.ru
|
||||
• Веб-сайт: https://bitforce-foundation.ru
|
||||
|
||||
2.9. Ответственный за организацию обработки персональных данных: назначается приказом генерального директора
|
||||
|
||||
2.10. Контакты ответственного лица по вопросам обработки персональных данных:
|
||||
• Электронная почта: company@bitforcefoundation.ru
|
||||
|
||||
|
||||
3. Общие цели обработки персональных данных
|
||||
-------------------------------------------
|
||||
|
||||
3.1. Оператор обрабатывает персональные данных для достижения следующих общих целей:
|
||||
|
||||
3.1.1. Основная деятельность:
|
||||
• Предоставление услуг по конвертации иного имущества;
|
||||
• Осуществление операций на криптовалютных рынках;
|
||||
• Предоставление услуг в области блокчейн технологий;
|
||||
• Обеспечение функционирования интернет-платформы и мобильных приложений.
|
||||
|
||||
3.1.2. Обеспечение безопасности:
|
||||
• Предотвращение мошенничества и отмывания денежных средств;
|
||||
• Обеспечение безопасности платежных операций;
|
||||
• Выполнение требований по противодействию легализации доходов, полученных преступным путем;
|
||||
• Идентификация и верификация клиентов.
|
||||
|
||||
3.1.3. Соблюдение законодательства:
|
||||
• Исполнение требований российского и международного законодательства;
|
||||
• Взаимодействие с контролирующими и правоохранительными органами;
|
||||
• Ведение обязательной отчетности и документооборота;
|
||||
• Соблюдение налогового законодательства.
|
||||
|
||||
3.1.4. Развитие бизнеса:
|
||||
• Анализ и улучшение качества предоставляемых услуг;
|
||||
• Разработка новых продуктов и сервисов;
|
||||
• Проведение маркетинговых исследований;
|
||||
• Осуществление клиентской поддержки.
|
||||
|
||||
|
||||
4. Цели сбора персональных данных
|
||||
---------------------------------
|
||||
|
||||
4.1. Регистрация и идентификация пользователей:
|
||||
• Создание учетной записи на веб-сайте;
|
||||
• Верификация личности в соответствии с требованиями законодательства;
|
||||
• Подтверждение права на осуществление операций;
|
||||
• Обеспечение доступа к персональному кабинету.
|
||||
|
||||
4.2. Обработка платежей и финансовых операций:
|
||||
• Осуществление операций по конвертации криптовалют;
|
||||
• Проведение расчетов и переводов денежных средств;
|
||||
• Ведение учета и истории транзакций;
|
||||
• Расчет комиссий и сборов.
|
||||
|
||||
4.3. Обеспечение безопасности и соблюдение требований:
|
||||
• Проверка на причастность к запрещенной деятельности;
|
||||
• Мониторинг подозрительных операций;
|
||||
• Выполнение процедур по противодействию легализации доходов;
|
||||
• Архивирование данных для правоохранительных органов.
|
||||
|
||||
4.4. Коммуникация с клиентами:
|
||||
• Предоставление технической поддержки;
|
||||
• Уведомления о состоянии операций и счетов;
|
||||
• Информирование об изменениях в условиях предоставления услуг;
|
||||
• Рассылка маркетинговых материалов (при наличии согласия).
|
||||
|
||||
4.5. Аналитика и улучшение сервиса:
|
||||
• Анализ пользовательского поведения для улучшения интерфейса;
|
||||
• Создание аналитических отчетов;
|
||||
• Исследование рынка и потребностей клиентов;
|
||||
• Разработка персонализированных предложений.
|
||||
|
||||
4.6. Исполнение договорных обязательств:
|
||||
• Выполнение условий пользовательского соглашения;
|
||||
• Предоставление заказанных услуг;
|
||||
• Рассмотрение жалоб и претензий;
|
||||
• Урегулирование споров.
|
||||
|
||||
|
||||
5. Правовые основания обработки персональных данных
|
||||
---------------------------------------------------
|
||||
|
||||
5.1. Правовыми основаниями обработки персональных данных Оператором являются:
|
||||
|
||||
5.1.1. Согласие субъекта персональных данных (п. 1 ч. 1 ст. 6 Закона о персональных данных):
|
||||
• Обработка персональных данных в маркетинговых целях;
|
||||
• Использование файлов cookie и метрик;
|
||||
• Персонализация сервисов и предложений;
|
||||
• Проведение опросов и исследований.
|
||||
|
||||
5.1.2. Необходимость исполнения договора (п. 5 ч. 1 ст. 6 Закона о персональных данных):
|
||||
• Регистрация и ведение учетных записей пользователей;
|
||||
• Осуществление финансовых операций и переводов;
|
||||
• Предоставление доступа к платформе и сервисам;
|
||||
• Техническая поддержка и обслуживание клиентов.
|
||||
|
||||
5.1.3. Соблюдение правовой обязанности (п. 2 ч. 1 ст. 6 Закона о персональных данных):
|
||||
• Выполнение требований валютного законодательства;
|
||||
• Противодействие легализации доходов, полученных преступным путем;
|
||||
• Соблюдение требований по налоговому учету и отчетности;
|
||||
• Взаимодействие с правоохранительными органами.
|
||||
|
||||
5.1.4. Защита жизненно важных интересов (п. 3 ч. 1 ст. 6 Закона о персональных данных):
|
||||
• Предотвращение мошенничества и финансовых преступлений;
|
||||
• Обеспечение безопасности платежных систем;
|
||||
• Защита от кибератак и несанкционированного доступа.
|
||||
|
||||
5.1.5. Законные интересы оператора (п. 7 ч. 1 ст. 6 Закона о персональных данных):
|
||||
• Обеспечение информационной безопасности;
|
||||
• Аналитика для улучшения качества услуг;
|
||||
• Защита прав и законных интересов Оператора;
|
||||
• Взыскание задолженности.
|
||||
|
||||
5.2. При обработке персональных данных на основании согласия субъекта, такое согласие оформляется в соответствии с требованиями ст. 9 Закона о персональных данных.
|
||||
|
||||
5.3. При обработке персональных данных на основании законных интересов Оператор обеспечивает баланс между своими интересами и правами субъектов персональных данных.
|
||||
|
||||
|
||||
6. Объем и категории обрабатываемых персональных данных
|
||||
-------------------------------------------------------
|
||||
|
||||
6.1. Категории субъектов персональных данных:
|
||||
|
||||
6.1.1. Пользователи веб-сайта и мобильного приложения:
|
||||
• Зарегистрированные пользователи;
|
||||
• Посетители сайта без регистрации;
|
||||
• Потенциальные клиенты;
|
||||
• Бывшие клиенты.
|
||||
|
||||
6.1.2. Клиенты:
|
||||
• Физические лица, пользующиеся услугами Оператора;
|
||||
• Индивидуальные предприниматели;
|
||||
• Представители юридических лиц;
|
||||
• Выгодоприобретатели.
|
||||
|
||||
6.1.3. Сотрудники и партнеры:
|
||||
• Сотрудники Оператора;
|
||||
• Кандидаты на трудоустройство;
|
||||
• Представители контрагентов;
|
||||
• Консультанты и советники.
|
||||
|
||||
6.2. Категории и состав обрабатываемых персональных данных:
|
||||
|
||||
6.2.1. Идентификационные данные:
|
||||
• Фамилия, имя, отчество;
|
||||
• Дата рождения;
|
||||
• Место рождения;
|
||||
• Гражданство;
|
||||
• Пол.
|
||||
|
||||
6.2.2. Паспортные данные:
|
||||
• Серия и номер паспорта;
|
||||
• Дата выдачи паспорта;
|
||||
• Код подразделения;
|
||||
• Кем выдан паспорт;
|
||||
• Адрес регистрации;
|
||||
|
||||
6.2.3. Контактная информация:
|
||||
• Номера телефонов (мобильный, домашний, рабочий);
|
||||
• Адреса электронной почты;
|
||||
|
||||
6.2.4. Финансовая информация:
|
||||
• Номера банковских счетов и карт;
|
||||
• Реквизиты кошельков криптовалют;
|
||||
• История операций и транзакций;
|
||||
• Данные о доходах и источниках средств;
|
||||
• Налоговая информация.
|
||||
|
||||
6.2.5. Техническая информация:
|
||||
• IP-адреса устройств;
|
||||
• Данные о браузере и операционной системе;
|
||||
• Файлы cookie и локальное хранилище;
|
||||
• Логи действий на сайте;
|
||||
• Геолокационные данные.
|
||||
|
||||
6.2.6. Дополнительные данные:
|
||||
• Фотографии для верификации;
|
||||
• Биометрические данные (при использовании);
|
||||
• Видеозаписи видеоидентификации;
|
||||
• Данные о семейном положении и составе семьи;
|
||||
• Профессиональная информация.
|
||||
|
||||
6.3. Источники получения персональных данных:
|
||||
• Непосредственно от субъектов персональных данных;
|
||||
• Из общедоступных источников;
|
||||
• От третьих лиц с согласия субъекта;
|
||||
• Из государственных информационных систем;
|
||||
• От партнеров и контрагентов.
|
||||
|
||||
|
||||
7. Порядок и условия обработки персональных данных
|
||||
--------------------------------------------------
|
||||
|
||||
7.1. Принципы обработки персональных данных:
|
||||
• Обработка персональных данных осуществляется на законной и справедливой основе;
|
||||
• Обработка ограничивается достижением конкретных, заранее определенных и законных целей;
|
||||
• Не допускается обработка персональных данных, несовместимая с целями сбора;
|
||||
• Содержание и объем обрабатываемых персональных данных соответствуют заявленным целям;
|
||||
• Обрабатываемые персональные данные являются точными и актуальными.
|
||||
|
||||
7.2. Способы обработки персональных данных:
|
||||
|
||||
7.2.1. Автоматизированная обработка:
|
||||
• Сбор данных через веб-формы и мобильные приложения;
|
||||
• Автоматическая обработка платежей и транзакций;
|
||||
• Автоматизированный анализ и мониторинг операций;
|
||||
• Генерация отчетов и статистики.
|
||||
|
||||
7.2.2. Неавтоматизированная обработка:
|
||||
• Ручная верификация документов;
|
||||
• Обработка обращений службы поддержки;
|
||||
• Подготовка документов для регулирующих органов;
|
||||
• Архивирование документов на электронных носителях (при необходимости на бумажных носителях).
|
||||
|
||||
7.3. Условия обработки персональных данных:
|
||||
|
||||
7.3.1. Получение согласия:
|
||||
• Согласие получается в письменной форме или путем совершения конклюдентных действий;
|
||||
• Субъект информируется о целях, способах и сроках обработки;
|
||||
• Предоставляется возможность отозвать согласие;
|
||||
• Ведется учет полученных согласий.
|
||||
|
||||
7.3.2. Обработка без согласия:
|
||||
• Осуществляется только в случаях, предусмотренных законодательством;
|
||||
• Документируются правовые основания обработки;
|
||||
• Обеспечивается соответствие объема обработки заявленным целям;
|
||||
• Ведется учет случаев обработки без согласия.
|
||||
|
||||
7.4. Сроки обработки персональных данных:
|
||||
• Персональные данные обрабатываются в течение времени, необходимого для достижения целей обработки;
|
||||
• После достижения целей обработки персональные данные подлежат уничтожению или обезличиванию;
|
||||
• Сроки хранения определяются требованиями законодательства и внутренними регламентами;
|
||||
• Ведется учет сроков обработки для каждой категории данных.
|
||||
|
||||
7.5. Места обработки персональных данных:
|
||||
• Основные серверы и хранилища данных расположены на территории Российской Федерации;
|
||||
• Резервные копии могут храниться в дата-центрах на территории РФ;
|
||||
• Обработка может осуществляться удаленно с соблюдением требований безопасности;
|
||||
|
||||
|
||||
8. Актуализация, исправление, удаление и уничтожение персональных данных
|
||||
------------------------------------------------------------
|
||||
|
||||
8.1. Актуализация персональных данных:
|
||||
|
||||
8.1.1. Обязанности субъекта:
|
||||
• Предоставление актуальных и достоверных персональных данных;
|
||||
• Незамедлительное уведомление об изменении персональных данных;
|
||||
• Подтверждение изменений документально при необходимости.
|
||||
|
||||
8.1.2. Процедуры актуализации:
|
||||
• Регулярная проверка актуальности данных;
|
||||
• Запросы на подтверждение данных;
|
||||
• Автоматическое обновление при получении новой информации;
|
||||
• Верификация изменений через дополнительные каналы связи.
|
||||
|
||||
8.2. Исправление персональных данных:
|
||||
|
||||
8.2.1. Основания для исправления:
|
||||
• Обнаружение неточностей в персональных данных;
|
||||
• Получение запроса от субъекта персональных данных;
|
||||
• Выявление ошибок при автоматизированной обработке;
|
||||
• Получение уточняющей информации из достоверных источников.
|
||||
|
||||
8.2.2. Процедура исправления:
|
||||
• Рассмотрение запроса в течение 30 дней;
|
||||
• Проверка обоснованности требования об исправлении;
|
||||
• Внесение изменений во все информационные системы;
|
||||
• Уведомление субъекта о проведенных исправлениях;
|
||||
• Уведомление третьих лиц, которым передавались неточные данные.
|
||||
|
||||
8.3. Удаление персональных данных:
|
||||
|
||||
8.3.1. Основания для удаления:
|
||||
• Отзыв согласия субъектом персональных данных;
|
||||
• Достижение целей обработки;
|
||||
• Истечение сроков обработки;
|
||||
• Выявление незаконности обработки;
|
||||
• Требование субъекта при наличии оснований.
|
||||
|
||||
8.3.2. Процедура удаления:
|
||||
• Проверка наличия законных оснований для продолжения обработки;
|
||||
• Удаление из всех информационных систем и баз данных;
|
||||
• Удаление резервных копий (кроме архивных);
|
||||
• Уведомление субъекта о выполненном удалении;
|
||||
• Документирование факта удаления.
|
||||
|
||||
8.4. Уничтожение персональных данных:
|
||||
|
||||
8.4.1. Случаи уничтожения:
|
||||
• Истечение сроков хранения;
|
||||
• Ликвидация организации;
|
||||
• Прекращение обработки по решению суда;
|
||||
• Техническая необходимость (замена оборудования).
|
||||
|
||||
8.4.2. Способы уничтожения:
|
||||
• Физическое уничтожение носителей информации;
|
||||
• Криптографическое уничтожение (удаление ключей шифрования);
|
||||
• Перезапись информации на носителях;
|
||||
• Размагничивание магнитных носителей;
|
||||
• Использование специализированного программного обеспечения.
|
||||
|
||||
8.4.3. Документирование уничтожения:
|
||||
• Составление актов уничтожения;
|
||||
• Ведение журналов уничтожения;
|
||||
• Фиксация времени, способа и ответственных лиц;
|
||||
• Хранение документов об уничтожении в течение установленного срока.
|
||||
|
||||
|
||||
9. Ответы на запросы субъектов персональных данных
|
||||
--------------------------------------------------
|
||||
|
||||
9.1. Права субъектов персональных данных:
|
||||
|
||||
9.1.1. Право на информацию (ст. 14 Закона о персональных данных):
|
||||
• Подтверждение факта обработки персональных данных;
|
||||
• Правовые основания и цели обработки;
|
||||
• Применяемые способы обработки;
|
||||
• Наименование и местонахождение оператора;
|
||||
• Лица, имеющие доступ к персональным данным;
|
||||
• Обрабатываемые персональные данные;
|
||||
• Источник получения персональных данных;
|
||||
• Сроки обработки персональных данных;
|
||||
• Порядок осуществления прав субъекта;
|
||||
|
||||
9.1.2. Право на доступ:
|
||||
• Получение копий обрабатываемых персональных данных;
|
||||
• Ознакомление с историей обработки;
|
||||
• Получение информации о передаче данных третьим лицам;
|
||||
• Доступ к автоматизированным решениям.
|
||||
|
||||
9.1.3. Право на исправление:
|
||||
• Требование исправления неточных данных;
|
||||
• Дополнение неполных данных;
|
||||
• Актуализация устаревших данных.
|
||||
|
||||
9.1.4. Право на удаление ("право на забвение"):
|
||||
• Требование удаления персональных данных;
|
||||
• Отзыв согласия на обработку;
|
||||
• Возражение против обработки.
|
||||
|
||||
9.1.5. Право на ограничение обработки:
|
||||
• Блокирование обработки на время проверки точности;
|
||||
• Ограничение способов обработки;
|
||||
• Приостановление передачи третьим лицам.
|
||||
|
||||
9.2. Процедура рассмотрения запросов:
|
||||
|
||||
9.2.1. Подача запроса:
|
||||
• Запрос может быть подан лично, по почте или электронно;
|
||||
• Запрос должен содержать сведения, указанные в ч. 3 ст. 14 Закона;
|
||||
• При подаче запроса в электронной форме требуется подтверждение личности;
|
||||
• Запрос может быть подан через представителя при наличии доверенности.
|
||||
|
||||
9.2.2. Сроки рассмотрения:
|
||||
• Срок рассмотрения запроса составляет 30 дней с момента получения;
|
||||
• Срок может быть продлен на 30 дней при большом объеме информации;
|
||||
• О продлении срока субъект уведомляется в течение 30 дней;
|
||||
• Безотлагательно — в случаях, указанных в законе.
|
||||
|
||||
9.2.3. Содержание ответа:
|
||||
• Подтверждение получения запроса;
|
||||
• Запрашиваемая информация в доступной форме;
|
||||
• Разъяснение порядка обжалования;
|
||||
• Мотивированный отказ (при наличии оснований).
|
||||
|
||||
9.2.4. Способы предоставления ответа:
|
||||
• В письменной форме по почте;
|
||||
• В электронной форме (при согласии субъекта);
|
||||
• Через личный кабинет на сайте;
|
||||
• При личном обращении в офис Оператора.
|
||||
|
||||
9.3. Основания для отказа в удовлетворении запроса:
|
||||
• Обработка необходима для защиты жизни, здоровья субъекта;
|
||||
• Обработка необходима для выполнения функций государства;
|
||||
• Персональные данные получены в рамках оперативно-розыскной деятельности;
|
||||
• Обработка необходима для защиты прав и законных интересов третьих лиц;
|
||||
• Исполнение судебного акта или иного акта государственного органа.
|
||||
|
||||
9.4. Плата за предоставление информации:
|
||||
• Первый запрос в течение года обрабатывается бесплатно;
|
||||
• За повторные запросы может взиматься плата в размере расходов;
|
||||
• Размер платы определяется в соответствии с законодательством;
|
||||
• Субъект уведомляется о размере платы до предоставления информации.
|
||||
|
||||
|
||||
10. Обеспечение безопасности персональных данных
|
||||
------------------------------------------------
|
||||
|
||||
10.1. Правовые меры:
|
||||
• Назначение ответственного за организацию обработки персональных данных;
|
||||
• Принятие локальных актов по вопросам обработки персональных данных;
|
||||
• Ознакомление работников с требованиями законодательства и локальными актами;
|
||||
• Применение мер ответственности за нарушение требований законодательства.
|
||||
|
||||
10.2. Организационные меры:
|
||||
• Определение перечня лиц, допущенных к обработке персональных данных;
|
||||
• Установление правил доступа к персональным данным;
|
||||
• Применение средств защиты, прошедших процедуру оценки соответствия;
|
||||
• Оценка вреда, который может быть причинен субъектам персональных данных;
|
||||
• Учет машинных носителей персональных данных;
|
||||
• Обнаружение фактов несанкционированного доступа;
|
||||
• Восстановление персональных данных.
|
||||
|
||||
10.3. Технические меры:
|
||||
• Предотвращение несанкционированного доступа к персональным данным;
|
||||
• Своевременное обнаружение фактов несанкционированного доступа;
|
||||
• Предотвращение воздействия на технические средства обработки;
|
||||
• Возможность незамедлительного восстановления персональных данных;
|
||||
• Постоянный контроль за обеспечением уровня защищенности.
|
||||
|
||||
10.4. Конкретные технические решения:
|
||||
• Использование сертифицированных средств защиты информации;
|
||||
• Шифрование персональных данных при передаче и хранении;
|
||||
• Применение межсетевых экранов и систем обнаружения вторжений;
|
||||
• Резервное копирование и обеспечение отказоустойчивости;
|
||||
• Аудит и мониторинг доступа к информационным системам;
|
||||
• Антивирусная защита и обновление программного обеспечения.
|
||||
|
||||
10.5. Контроль доступа:
|
||||
• Идентификация и аутентификация пользователей;
|
||||
• Разграничение доступа по ролям и полномочиям;
|
||||
• Протоколирование действий пользователей;
|
||||
• Регулярный пересмотр прав доступа;
|
||||
• Блокирование учетных записей при увольнении сотрудников.
|
||||
|
||||
|
||||
11. Сведения о реализуемых требованиях к защите персональных данных
|
||||
------------------------------------------------------------
|
||||
|
||||
11.1. Уровни защищенности:
|
||||
Оператор определяет уровни защищенности персональных данных в соответствии с Постановлением Правительства РФ от 01.11.2012 № 1119 и обеспечивает соответствующие им требования безопасности.
|
||||
|
||||
11.2. 1-й уровень защищенности (общедоступные персональные данные):
|
||||
• Базовые средства защиты от несанкционированного доступа;
|
||||
• Парольная защита доступа к информационным системам;
|
||||
• Антивирусная защита рабочих станций и серверов.
|
||||
|
||||
11.3. 2-й уровень защищенности (иные персональные данные, кроме специальных и биометрических):
|
||||
• Идентификация и аутентификация субъектов доступа;
|
||||
• Управление доступом субъектов доступа к ресурсам;
|
||||
• Ограничение программной среды;
|
||||
• Защита машинных носителей информации;
|
||||
• Регистрация событий безопасности;
|
||||
• Антивирусная защита;
|
||||
• Обнаружение вторжений;
|
||||
• Контроль целостности информационной системы и информации.
|
||||
|
||||
11.4. 3-й уровень защищенности (специальные категории персональных данных):
|
||||
Дополнительно к требованиям 2-го уровня:
|
||||
• Обеспечение целостности информационной системы и информации;
|
||||
• Обеспечение доступности информационной системы и информации;
|
||||
• Защита технических средств;
|
||||
• Защита информационной системы, ее средств, систем связи и передачи данных.
|
||||
|
||||
11.5. 4-й уровень защищенности (биометрические персональные данные):
|
||||
Дополнительно к требованиям 3-го уровня:
|
||||
• Предотвращение внедрения в информационную систему программных закладок;
|
||||
• Анализ защищенности информационной системы.
|
||||
|
||||
|
||||
12. Заключительные положения
|
||||
----------------------------
|
||||
|
||||
12.1. Внесение изменений в Политику:
|
||||
• Политика может изменяться в связи с изменениями в законодательстве;
|
||||
• Существенные изменения доводятся до сведения субъектов персональных данных;
|
||||
• Действующая версия Политики размещается на официальном сайте;
|
||||
• Дата последнего обновления указывается в Политике.
|
||||
|
||||
12.2. Жалобы и обращения:
|
||||
• Субъекты персональных данных могут обратиться к Оператору по вопросам обработки;
|
||||
• Жалобы рассматриваются в установленном законом порядке;
|
||||
• При неурегулировании разногласий возможно обращение в Роскомнадзор или суд.
|
||||
|
||||
12.3. Применимое право:
|
||||
• Политика регулируется законодательством Российской Федерации;
|
||||
• Споры рассматриваются в российских судах;
|
||||
• При противоречии переводов приоритет имеет русскоязычная версия.
|
||||
|
||||
12.4. Контактная информация для обращений:
|
||||
• Почтовый адрес: 196246, г. Санкт-Петербург, Московское ш., д. 25, к. 1, лит. В, пом. 3-н
|
||||
• Электронная почта: company@bitforcefoundation.ru
|
||||
|
||||
12.5. Вступление в силу:
|
||||
• Настоящая Политика вступает в силу с момента ее утверждения и размещения на официальном сайте Оператора.
|
||||
235
publichnaya-oferta.txt
Normal file
235
publichnaya-oferta.txt
Normal file
@@ -0,0 +1,235 @@
|
||||
ПУБЛИЧНЫЙ ДОГОВОР ОФЕРТЫ
|
||||
========================
|
||||
|
||||
ООО БИТФОРС
|
||||
|
||||
Агентский договор
|
||||
-----------------
|
||||
|
||||
Настоящая оферта на заключение агентского договора
|
||||
(далее – Оферта, Договор) является публичным предложением
|
||||
Общества с ограниченной ответственностью «БИТФОРС», заключить договор на условиях и в порядке, определенных настоящей Офертой.
|
||||
|
||||
Акцепт оферты производится в соответствии с пунктом 2 статьи 437 Гражданского кодекса Российской Федерации и равносилен заключению агентского договора в письменной форме.
|
||||
|
||||
|
||||
Основные понятия и определения действующего договора
|
||||
----------------------------------------------------
|
||||
|
||||
Агент – юридическое лицо или индивидуальный предприниматель, зарегистрированный на территории Российской Федерации, в установленном действующим законодательством порядке.
|
||||
|
||||
Принципал – сторона агентского договора, по поручению которой агент осуществляет юридические и иные действия от своего имени, но за счет принципала либо от имени и за счет принципала.
|
||||
|
||||
Агентский договор – соглашение, по которому агент обязуется за вознаграждение совершать по поручению принципала юридические и иные действия от своего имени, но за счет принципала либо от имени и за счет принципала в соответствии с п. 1 ст. 1005 Гражданского Кодекса Российской Федерации.
|
||||
|
||||
Личный кабинета Агента – ресурс, размещенный на сайте Принципала, предназначенный для взаимодействия Агента и Принципала.
|
||||
|
||||
Отчетный период – период для взаиморасчетов с Агентом, равный
|
||||
одному календарному кварталу с даты активации любой из услуг, предоставляемой Принципалу.
|
||||
|
||||
Отчет о сумме начислений (Отчет) – отчет, формируемый в Личном кабинете Агента на основании данных систем учета Принципала.
|
||||
|
||||
Оферта (Договор) – настоящий документ, который отражает предложение и намерение ООО «БИТФОРС» считать заключенным договор с лицом, которым будет принято предложение на условиях, изложенных ниже.
|
||||
|
||||
|
||||
1. Акцепт оферты и заключение агентского договора
|
||||
-------------------------------------------------
|
||||
|
||||
1.1. Акцепт настоящей Оферты и заключение Агентского договора осуществляется Принципалом в процессе регистрации в Личном кабинете Принципала (на сайте Агента), при прочтении текста настоящей Оферты, путем проставления специальной отметки (галочки) напротив фразы
|
||||
«Я ознакомился с Офертой и принимаю ее условия» и нажатия кнопки «Подписать».
|
||||
|
||||
1.2. Особый порядок принятия условий Оферты путем проставления специальной отметки (галочки) определяется интерфейсом Личного кабинета Принципала. Принципал не может зарегистрироваться в Личном кабинете и получить к нему доступ без подтверждения принятия условий Оферты.
|
||||
|
||||
1.3. Принимая Оферту, Принципал подтверждает, что прочел и полностью согласен с документами, размещенными на сайте в разделе, предназначенном для Принципала, которые являются неотъемлемой частью настоящей Оферты (Договора) и обязательны для исполнения Сторонами.
|
||||
|
||||
|
||||
2. Общие положения
|
||||
------------------
|
||||
|
||||
2.1. Публикуемые на сайте Агента (ссылка на сайт) документы
|
||||
(формы, требования, правила и т.п.), устанавливающие порядок и условия выполнения действий, предусмотренных настоящим Договором, являются неотъемлемой частью настоящего Договора и обязательны для исполнения Сторонами. Принципал обязан использовать формы документов, утвержденных Агентом, и не вправе вносить в них какие-либо изменения или дополнения.
|
||||
|
||||
2.2. Агент обязуется уведомлять Принципала обо всех изменениях в документах, связанных с исполнением настоящего Договора, путем направления электронных сообщений (через Личный кабинет или на электронную почту Принципала) или размещением уведомлений об изменениях на сайте Агентов в разделе, предназначенном для размещения объявлений. Такие сообщения и уведомления приравниваются к сообщениям и уведомлениям, исполненным в простой письменной форме, направляемым на почтовые адреса Принципала.
|
||||
|
||||
2.3. К правам и обязанностям сторон, возникшим на основании настоящего Договора, применяются положения действующей редакции Договора и Приложений, опубликованных на сайте Агента, если иное не установлено Договором.
|
||||
|
||||
|
||||
3. Предмет договора
|
||||
-------------------
|
||||
|
||||
3.1. По настоящему Договору Принципал поручает, а Агент принимает на себя обязательство совершать от имени и за счет Принципала указанные в п. 3.2 настоящего Договора действия, а Принципал обязуется выплатить Агенту вознаграждение за совершенные действия.
|
||||
|
||||
3.2. По настоящему Договору Агент совершает следующие действия:
|
||||
|
||||
– Консультирование Принципала об услугах Агента, включая, помимо прочего, порядок активации и оказания услуг, работу в Личном кабинете Принципала и иные дополнительные услуги, оказываемые Агентом;
|
||||
|
||||
– Совершение сделок и иных юридических действий Агентом от своего имени, но за счёт Принципала.
|
||||
|
||||
3.3. Настоящий Договор действует на территории Российской Федерации и иного иностранного государства.
|
||||
|
||||
3.4. Права и обязанности по сделкам, совершенным Агентом во исполнение настоящего Договора, возникают непосредственно у Принципала.
|
||||
|
||||
3.5. Агент гарантирует отсутствие договорных и иных отношений с лицами, которые могли бы оказать влияние на исполнение настоящего Договора. Агент гарантирует свою независимость и объективность в ходе исполнения настоящего Договора.
|
||||
|
||||
|
||||
4. Права и обязанности сторон
|
||||
-----------------------------
|
||||
|
||||
4.1. Агент обязуется:
|
||||
|
||||
4.1.1. Совершать действия, составляющие предмет настоящего Договора, в соответствии с законными интересами Принципала.
|
||||
|
||||
4.1.2. Сообщать Принципалу по его требованию все сведения о ходе исполнения настоящего Договора.
|
||||
|
||||
4.1.3. Передавать Принципалу в течение 7 (семи) рабочих дней имущество, полученное по сделкам, совершенным во исполнение настоящего Договора.
|
||||
|
||||
4.1.4. Не позднее последнего дня месяца, следующего за отчетным, представлять Принципалу отчет об исполнении поручения с приложением доказательств расходов, произведенных Агентом за счет Принципала.
|
||||
|
||||
4.1.5. Выполнять другие обязанности, которые в соответствии с настоящим Договором или законом возлагаются на Агента.
|
||||
|
||||
4.2. Агент несет ответственность за сохранность документов и персональных данных, переданных ему Принципалом для исполнения настоящего Договора.
|
||||
|
||||
4.3. Агент вправе:
|
||||
|
||||
4.3.1. Отступить от указаний Принципала, если по обстоятельствам дела это необходимо в интересах Принципала и Агент не мог предварительно запросить Принципала либо не получил в течение 3 (трех) рабочих дней ответа на свой запрос. Агент обязан уведомить Принципала о допущенных отступлениях, как только уведомление станет возможным.
|
||||
|
||||
4.3.2. Удержать причитающиеся ему по настоящему Договору суммы вознаграждения из всех сумм, поступивших к нему за счет Принципала.
|
||||
|
||||
4.3.3. Агент вправе заключить субагентский договор с другим лицом. В случае заключения субагентского договора ответственным за действия субагента перед Принципалом остается Агент.
|
||||
|
||||
4.4. Принципал обязан:
|
||||
|
||||
4.4.1. Без промедления принять отчет Агента, все предоставленные им документы и все исполненное им в соответствии с Договором. Принципал, имеющий возражения по отчету Агента, должен сообщить о них Агенту в течение 7 (семи) рабочих дней со дня получения отчета. В противном случае отчет считается принятым Принципалом.
|
||||
|
||||
4.4.2. Обеспечить Агента документами и материалами, необходимыми для выполнения настоящего Договора.
|
||||
|
||||
4.4.3. Возместить Агенту понесенные в связи с исполнением настоящего Договора расходы.
|
||||
|
||||
4.4.4. Выплатить Агенту обусловленное настоящим Договором агентское вознаграждение.
|
||||
|
||||
4.5. Принципал вправе:
|
||||
|
||||
4.5.1. Давать Агенту рекомендации об исполнении настоящего Договора. Указания Принципала должны быть правомерными, осуществимыми и конкретными.
|
||||
|
||||
4.5.2. Получать от Агента сведения о ходе выполнения поручения по требованию.
|
||||
|
||||
4.5.3. Требовать от Агента представления отчета о проделанной работе во исполнение настоящего Договора.
|
||||
|
||||
|
||||
5. Агентское вознаграждение и порядок оплаты
|
||||
--------------------------------------------
|
||||
|
||||
5.1. Сумма вознаграждения Агента по настоящему Договору составляет
|
||||
|
||||
- 8% от 5 000 до 30 000 рублей (вычет процента производится с учетом всех операционных расходов, необходимых для исполнения поручения от своего имени, но за счет Принципала Агентом).
|
||||
|
||||
- 6% от 30 000 до 100 000 рублей (вычет процента производится с учетом всех операционных расходов, необходимых для исполнения поручения от своего имени, но за счет Принципала Агентом).
|
||||
|
||||
- 4% от 100 000 до 600 000 рублей (вычет процента производится с учетом всех операционных расходов, необходимых для исполнения поручения от своего имени, но за счет Принципала Агентом).
|
||||
|
||||
5.2. Вознаграждение выплачивается Агенту в следующем порядке и сроки:
|
||||
|
||||
- Принципал выплачивает Агенту вознаграждение с момента подписания настоящего Договора об исполнении поручения Агентом от своего имени, но за счет Принципала.
|
||||
|
||||
5.3. Принципал возмещает следующие расходы Агента:
|
||||
|
||||
5.3.1. Расходы на оплату банковских услуг и иных комиссий в сумме не более 30 000 рублей.
|
||||
|
||||
5.4. Расходы, указанные в п. 5.3 настоящего Договора, возмещаются Принципалом в следующем порядке:
|
||||
|
||||
- Возмещение расходов Агенту осуществляется в момент подписания настоящего Договора об исполнении поручения Агентом от своего имени, но за счет Принципала.
|
||||
|
||||
5.5. Безналичные расчеты по настоящему Договору производятся Сторонами путем перечисления денежных средств на расчетный счет Стороны по реквизитам, указанным в настоящем Договоре. Размер агентского вознаграждения устанавливается договором между сторонами и выплачивается Агенту за совершение юридических и (или) фактических действий в интересах Принципала.
|
||||
|
||||
|
||||
6. Ответственность сторон
|
||||
-------------------------
|
||||
|
||||
6.1. В случае нарушения Агентом срока, установленного п. 4.1.3 настоящего Договора для передачи Принципалу полученного по настоящему Договору, Принципал вправе предъявить Агенту требование об уплате неустойки в размере 0,1% от непереданной денежной суммы (либо от стоимости непереданного имущества) за каждый день просрочки.
|
||||
|
||||
6.2. В случае нарушения Принципалом срока уплаты вознаграждения, установленного п. 5.2 настоящего Договора, или срока возмещения расходов, установленного п. 5.4 настоящего Договора, Агент вправе предъявить Принципалу требование об уплате неустойки в размере 0,1% от не уплаченной в срок суммы за каждый день просрочки.
|
||||
|
||||
6.3. Ответственность Сторон за неисполнение или ненадлежащее исполнение иных обязательств по настоящему Договору определяется в соответствии с нормами действующего законодательства Российской Федерации.
|
||||
|
||||
|
||||
7. Форс-мажор
|
||||
-------------
|
||||
|
||||
7.1. Стороны освобождаются от ответственности за частичное или полное неисполнение обязательств по настоящему Договору, если это неисполнение явилось следствием возникших после заключения настоящего Договора обстоятельств непреодолимой силы, которые Стороны не могли предвидеть или предотвратить.
|
||||
|
||||
7.2. При наступлении обстоятельств, указанных в п. 7.1 настоящего Договора, каждая Сторона должна без промедления известить о них в письменном виде другую Сторону. Извещение должно содержать данные о характере обстоятельств, а также официальные документы, удостоверяющие наличие этих обстоятельств и, по возможности, дающие оценку их влияния на исполнение Стороной своих обязательств по настоящему Договору.
|
||||
|
||||
7.3. Если Сторона не направит или несвоевременно направит извещение, предусмотренное в п. 7.2 настоящего Договора, то она обязана возместить второй Стороне понесенные ею убытки.
|
||||
|
||||
7.4. В случаях наступления обстоятельств, предусмотренных в п. 7.1 настоящего Договора, срок выполнения Стороной обязательств по настоящему Договору отодвигается соразмерно времени, в течение которого действуют эти обстоятельства и их последствия.
|
||||
|
||||
7.5. Если обстоятельства, указанные в п. 7.1 настоящего Договора, и их последствия продолжают действовать более трех недель:
|
||||
|
||||
- Стороны проводят дополнительные переговоры для выявления приемлемых альтернативных способов исполнения настоящего Договора.
|
||||
|
||||
- Сторона, не затронутая ее действием, вправе расторгнуть Договор в одностороннем порядке, направив другой Стороне соответствующее извещение и не возмещая каких-либо убытков, вызванных расторжением Договора.
|
||||
|
||||
|
||||
8. Конфиденциальность
|
||||
---------------------
|
||||
|
||||
8.1. Стороны принимают все необходимые меры для того, чтобы их сотрудники, агенты, правопреемники без предварительного согласия другой Стороны не информировали третьих лиц о конфиденциальной информации и персональных данных Сторон настоящего Договора.
|
||||
|
||||
|
||||
9. Изменение и прекращение договора
|
||||
-----------------------------------
|
||||
|
||||
9.1. Настоящий договор вступает в силу с момента его подписания и действует до момента исполнения сторонами своих обязательств по настоящему договору.
|
||||
|
||||
9.2. Настоящий Договор может быть изменен или прекращен по письменному соглашению Сторон, а также в других случаях, предусмотренных законодательством Российской Федерации.
|
||||
|
||||
9.3. Принципал вправе в любое время отказаться от исполнения настоящего Договора путем направления письменного уведомления Агенту за 3 (три) рабочих дня.
|
||||
|
||||
9.4. В случае отказа от настоящего Договора Принципал обязан незамедлительно после направления уведомления Агенту распорядиться своим имуществом, находящимся в ведении Агента, и не позднее 7 (семи) рабочих дней произвести выплату причитающегося Агенту вознаграждения за действия, совершенные им до прекращения Договора.
|
||||
|
||||
9.5. Агент вправе отказаться от исполнения настоящего Договора путем направления письменного уведомления Принципалу во всякое время.
|
||||
|
||||
9.6. Агент обязан принять меры, необходимые для обеспечения сохранности имущества Принципала. Принципал должен незамедлительно распорядиться своим находящимся в ведении Агента имуществом.
|
||||
|
||||
9.7. Агент, отказавшийся от настоящего Договора, сохраняет право на вознаграждение за действия, выполненные им до прекращения Договора.
|
||||
|
||||
|
||||
10. Заключительные положения
|
||||
----------------------------
|
||||
|
||||
10.1. Ни одна из сторон не вправе передавать свои права и обязанности по настоящему договору третьим лицам без согласия другой стороны.
|
||||
|
||||
10.2. Если иное не предусмотрено Договором, извещения, уведомления, требования и иные юридически значимые сообщения (далее - сообщения) Стороны могут направлять по факсу, электронной почте или другим способом связи при условии, что он позволяет достоверно установить, от кого исходило сообщение и кому оно адресовано.
|
||||
|
||||
10.3. Сообщения влекут гражданско-правовые последствия для Стороны, которой направлены, с момента их доставки указанной Стороне или ее представителю. Такие последствия возникают и в случае, когда сообщение не было вручено адресату по зависящим от него обстоятельствам
|
||||
(п. 1 ст. 165.1 Гражданский Кодекс Российской Федерации).
|
||||
|
||||
10.4. Во всем остальном, что не предусмотрено настоящим Договором, Стороны руководствуются действующим законодательством Российской Федерации.
|
||||
|
||||
10.5. Споры, вытекающие из настоящего Договора, разрешаются в досудебном порядке. При неурегулировании возникших разногласий спор разрешается в Арбитражном суде г. Санкт–Петербурга и Ленинградской области с обязательным соблюдением претензионного порядка. Срок ответа на претензию составляет 14 (четырнадцать) рабочих дней.
|
||||
|
||||
|
||||
Реквизиты сторон
|
||||
----------------
|
||||
|
||||
Общество с ограниченной ответственностью «БИТФОРС»
|
||||
|
||||
196246, г. Санкт-Петербург, Московский р-н, Московское шоссе, д.25к1 литера в, помещ. 3-Н
|
||||
|
||||
ИНН / КПП 9810001062 / 781001001
|
||||
|
||||
ОГРН 1257800060990
|
||||
|
||||
ОКПО / ОКАТО / ОКТМО 68342261 / 40284000000 / 40377000000
|
||||
|
||||
Руководитель: Кленин Михаил Васильевич
|
||||
|
||||
Электронная почта: company@bitforcefoundation.ru
|
||||
|
||||
Наименование банка: ФИЛИАЛ "САНКТ-ПЕТЕРБУРГСКИЙ" АО "АЛЬФА-БАНК"
|
||||
|
||||
Корреспондентский счет 30101810600000000786
|
||||
|
||||
БИК 044030786
|
||||
|
||||
Расчетный счет 40702810632250004861
|
||||
4
reestr-operatora-pd-rkn.txt
Normal file
4
reestr-operatora-pd-rkn.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Реестр операторов персональных данных (Роскомнадзор)
|
||||
ООО «БИТФОРС»
|
||||
|
||||
https://pd.rkn.gov.ru/operators-registry/operators-list/?act=search&name_full=%D0%91%D0%B8%D1%82%D1%84%D0%BE%D1%80%D1%81&inn=9810001062®n=
|
||||
337
soglasie-na-obrabotku-personalnyh-dannyh.txt
Normal file
337
soglasie-na-obrabotku-personalnyh-dannyh.txt
Normal file
@@ -0,0 +1,337 @@
|
||||
СОГЛАСИЕ НА ОБРАБОТКУ ПЕРСОНАЛЬНЫХ ДАННЫХ
|
||||
=========================================
|
||||
|
||||
ООО «БИТФОРС»
|
||||
|
||||
Преамбула
|
||||
---------
|
||||
|
||||
Я, субъект персональных данных, действуя своей волей и в своем интересе, в соответствии с требованиями Федерального закона от 27.07.2006 № 152-ФЗ «О персональных данных» (далее — Закон), предоставляю ООО «БИТФОРС» (далее — Оператор, Общество) согласие на обработку моих персональных данных на условиях и для целей, определенных настоящим Согласием.
|
||||
|
||||
|
||||
1. Сведения об операторе
|
||||
------------------------
|
||||
|
||||
Полное наименование: Общество с ограниченной ответственностью «БИТФОРС»
|
||||
|
||||
ИНН: 9810001062
|
||||
|
||||
ОГРН: 1257800060990
|
||||
|
||||
Реестр ОПД: Посмотреть в реестре Роскомнадзора (https://pd.rkn.gov.ru/operators-registry/operators-list/?act=search&name_full=%D0%91%D0%B8%D1%82%D1%84%D0%BE%D1%80%D1%81&inn=9810001062®n=)
|
||||
|
||||
Юридический адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н
|
||||
|
||||
Контактная информация:
|
||||
• Электронная почта: company@bitforcefoundation.ru
|
||||
• Веб-сайт: https://bitforce-foundation.ru
|
||||
|
||||
|
||||
2. Правовые основания обработки
|
||||
-------------------------------
|
||||
|
||||
2.1. Настоящее согласие предоставляется на основании пункта 1 части 1 статьи 6 Федерального закона «О персональных данных» и является правовым основанием для обработки персональных данных Оператором.
|
||||
|
||||
2.2. Согласие дается добровольно, своей волей и в своих интересах.
|
||||
|
||||
2.3. Субъект персональных данных понимает последствия предоставления согласия, включая возможные риски, связанные с обработкой персональных данных.
|
||||
|
||||
|
||||
3. Цели обработки персональных данных
|
||||
-------------------------------------
|
||||
|
||||
Согласие предоставляется для обработки персональных данных в следующих целях:
|
||||
|
||||
3.1. Основные цели:
|
||||
• Регистрация и ведение учетной записи на веб-сайте https://bitforce-foundation.ru и в мобильном приложении;
|
||||
• Идентификация и верификация личности в соответствии с требованиями законодательства Российской Федерации;
|
||||
• Предоставление услуг по обмену криптовалют и электронных денежных средств;
|
||||
• Проведение финансовых операций, переводов и расчетов;
|
||||
• Ведение учета и истории операций.
|
||||
|
||||
3.2. Дополнительные цели:
|
||||
• Обеспечение безопасности операций и предотвращение мошенничества;
|
||||
• Выполнение требований по противодействию легализации доходов, полученных преступным путем, и финансированию терроризма;
|
||||
• Соблюдение требований валютного, налогового и иного применимого законодательства;
|
||||
• Предоставление технической поддержки и клиентского сервиса;
|
||||
• Рассылка уведомлений о состоянии операций и изменениях в условиях предоставления услуг.
|
||||
|
||||
3.3. Маркетинговые цели (при дополнительном согласии):
|
||||
• Направление информационных и рекламных материалов;
|
||||
• Проведение маркетинговых исследований и опросов;
|
||||
• Персонализация предложений и услуг;
|
||||
• Анализ предпочтений и поведения для улучшения сервисов.
|
||||
|
||||
3.4. Аналитические цели:
|
||||
• Анализ использования веб-сайта и мобильного приложения;
|
||||
• Улучшение качества предоставляемых услуг;
|
||||
• Разработка новых продуктов и сервисов;
|
||||
• Создание статистических отчетов в обезличенном виде.
|
||||
|
||||
|
||||
4. Перечень персональных данных, на обработку которых дается согласие
|
||||
------------------------------------------------------------
|
||||
|
||||
4.1. Идентификационные данные:
|
||||
• Фамилия, имя, отчество;
|
||||
• Дата рождения;
|
||||
• Место рождения;
|
||||
• Гражданство;
|
||||
• Пол.
|
||||
|
||||
4.2. Документы, удостоверяющие личность:
|
||||
• Серия и номер паспорта гражданина Российской Федерации;
|
||||
• Дата выдачи и код подразделения;
|
||||
• Наименование органа, выдавшего документ;
|
||||
• Адрес регистрации по месту жительства;
|
||||
• Цифровые копии (сканы) документов.
|
||||
|
||||
4.3. Контактная информация:
|
||||
• Номера телефонов (мобильный, домашний, рабочий);
|
||||
• Адреса электронной почты;
|
||||
• Почтовые адреса (фактического проживания, для корреспонденции);
|
||||
• Данные мессенджеров и социальных сетей (при предоставлении).
|
||||
|
||||
4.4. Финансовая информация:
|
||||
• Номера банковских счетов и реквизиты банковских карт;
|
||||
• Реквизиты криптовалютных кошельков и криптовалютных адресов;
|
||||
• Информация о доходах и источниках происхождения денежных средств;
|
||||
• История финансовых операций и транзакций;
|
||||
• Данные о налоговом статусе и резидентстве.
|
||||
|
||||
4.5. Техническая информация:
|
||||
• IP-адреса устройств, с которых осуществляется доступ к сервисам;
|
||||
• Информация о браузере, операционной системе и устройстве;
|
||||
• Файлы cookie и данные локального хранилища;
|
||||
• Логи действий и история использования сервисов;
|
||||
• Геолокационные данные (при включенной функции).
|
||||
|
||||
4.6. Дополнительная информация:
|
||||
• Фотографии для процедур верификации;
|
||||
• Видеозаписи процедур видеоидентификации;
|
||||
• Биометрические данные (при использовании соответствующих технологий);
|
||||
• Информация о семейном положении и близких родственниках (при необходимости);
|
||||
• Сведения о профессиональной деятельности и должности;
|
||||
• Любая иная информация, предоставленная субъектом добровольно.
|
||||
|
||||
4.7. Специальные категории персональных данных:
|
||||
При необходимости и при наличии отдельного письменного согласия:
|
||||
• Биометрические персональные данные;
|
||||
• Данные о состоянии здоровья (при оформлении страхования);
|
||||
• Данные о судимости (при проверках безопасности).
|
||||
|
||||
|
||||
5. Перечень действий с персональными данными
|
||||
--------------------------------------------
|
||||
|
||||
Согласие распространяется на следующие действия (операции) с персональными данными:
|
||||
|
||||
5.1. Действия по получению и первичной обработке:
|
||||
• Сбор персональных данных;
|
||||
• Запись на материальные и электронные носители;
|
||||
• Первичная систематизация и структурирование;
|
||||
• Проверка достоверности и полноты данных.
|
||||
|
||||
5.2. Действия по хранению и систематизации:
|
||||
• Накопление персональных данных в базах данных;
|
||||
• Систематизация и каталогизация;
|
||||
• Создание резервных копий;
|
||||
• Архивирование данных.
|
||||
|
||||
5.3. Действия по использованию и анализу:
|
||||
• Извлечение персональных данных из баз данных;
|
||||
• Использование для достижения заявленных целей;
|
||||
• Анализ и обработка для получения аналитической информации;
|
||||
• Сопоставление с данными из других источников.
|
||||
|
||||
5.4. Действия по изменению и актуализации:
|
||||
• Уточнение (обновление, изменение) персональных данных;
|
||||
• Дополнение новой информацией;
|
||||
• Исправление выявленных неточностей;
|
||||
• Актуализация устаревших данных.
|
||||
|
||||
5.5. Действия по передаче:
|
||||
• Передача персональных данных третьим лицам;
|
||||
• Предоставление доступа уполномоченным лицам;
|
||||
|
||||
5.6. Действия по обезличиванию и уничтожению:
|
||||
• Обезличивание персональных данных;
|
||||
• Блокирование доступа к персональным данным;
|
||||
• Удаление персональных данных;
|
||||
• Уничтожение носителей персональных данных.
|
||||
|
||||
5.7. Автоматизированная обработка:
|
||||
• Автоматический сбор данных через веб-сайт и приложения;
|
||||
• Автоматизированный анализ и принятие решений;
|
||||
• Машинное обучение и использование алгоритмов;
|
||||
• Профилирование и сегментация.
|
||||
|
||||
|
||||
6. Лица, которым могут быть переданы персональные данные
|
||||
--------------------------------------------------------
|
||||
|
||||
6.1. Сотрудники Оператора:
|
||||
• Уполномоченные сотрудники, непосредственно участвующие в обработке персональных данных;
|
||||
• Сотрудники службы безопасности и комплаенса;
|
||||
• Сотрудники технической поддержки;
|
||||
• Руководящий состав в рамках их полномочий.
|
||||
|
||||
6.2. Государственные и муниципальные органы:
|
||||
• Федеральная служба по финансовому мониторингу (Росфинмониторинг);
|
||||
• Федеральная налоговая служба;
|
||||
• Правоохранительные органы (при наличии законных требований);
|
||||
• Суды и органы исполнения судебных решений;
|
||||
• Иные государственные органы в рамках их компетенции.
|
||||
|
||||
6.3. Партнеры и контрагенты:
|
||||
• Банки;
|
||||
• Платежные системы;
|
||||
• Операторы электронных денежных средств;
|
||||
• Поставщики технологических решений;
|
||||
• Аудиторские и консалтинговые организации.
|
||||
|
||||
6.4. Третьи лица для специальных целей:
|
||||
• Службы доставки и курьерские службы;
|
||||
• Телекоммуникационные операторы;
|
||||
• Маркетинговые агентства (при согласии на маркетинг);
|
||||
• Сервисы аналитики и веб-метрики;
|
||||
• Облачные провайдеры и хостинг-провайдеры;
|
||||
• Архивные организации.
|
||||
|
||||
6.5. Условия передачи:
|
||||
• Передача осуществляется только для целей, указанных в настоящем согласии;
|
||||
• Получатели обязуются обеспечить конфиденциальность и безопасность данных;
|
||||
• Заключаются соответствующие соглашения о защите персональных данных.
|
||||
|
||||
|
||||
7. Сроки обработки персональных данных
|
||||
--------------------------------------
|
||||
|
||||
7.1. Общие принципы определения сроков:
|
||||
• Персональные данные обрабатываются в течение времени, необходимого для достижения целей обработки;
|
||||
• Сроки определяются требованиями законодательства и характером отношений с Оператором;
|
||||
• После достижения целей обработки данные подлежат уничтожению или обезличиванию.
|
||||
|
||||
7.2. Конкретные сроки обработки:
|
||||
|
||||
Данные активных клиентов:
|
||||
• В течение всего периода действия отношений с Оператором;
|
||||
• Плюс 5 лет после прекращения отношений (в соответствии с требованиями валютного законодательства);
|
||||
• Плюс дополнительные сроки архивного хранения согласно законодательству.
|
||||
|
||||
Данные для идентификации и верификации:
|
||||
• 5 лет с момента прекращения отношений с клиентом;
|
||||
• 5 лет с даты проведения последней операции;
|
||||
• До истечения сроков исковой давности по спорным операциям.
|
||||
|
||||
Финансовая информация и история операций:
|
||||
• 5 лет с даты совершения операции (требования по ПОД/ФТ);
|
||||
• 5 лет для налогового учета;
|
||||
• До завершения всех расследований и судебных процедур.
|
||||
|
||||
Маркетинговые данные:
|
||||
• До отзыва согласия на маркетинговые коммуникации;
|
||||
• Не более 3 лет с момента последнего взаимодействия;
|
||||
• Немедленное удаление при отказе от рассылок.
|
||||
|
||||
Техническая информация (логи, IP-адреса):
|
||||
• 1 год для обеспечения информационной безопасности;
|
||||
• 6 месяцев для технических логов;
|
||||
• Постоянно в обезличенном виде для статистики.
|
||||
|
||||
7.3. Досрочное прекращение обработки:
|
||||
• При отзыве согласия субъектом персональных данных;
|
||||
• При выявлении незаконности получения или обработки;
|
||||
• По требованию уполномоченных органов;
|
||||
• При ликвидации Оператора.
|
||||
|
||||
|
||||
8. Права субъекта персональных данных
|
||||
-------------------------------------
|
||||
|
||||
8.1. Основные права:
|
||||
|
||||
Право на информацию:
|
||||
• Получение подтверждения факта обработки персональных данных;
|
||||
• Получение информации о целях, правовых основаниях и способах обработки;
|
||||
• Получение сведений о сроках обработки и составе данных;
|
||||
• Информация о третьих лицах, которым передаются данные.
|
||||
|
||||
Право на доступ:
|
||||
• Получение копий обрабатываемых персональных данных;
|
||||
• Ознакомление с историей обработки и изменений;
|
||||
• Получение информации об источниках персональных данных;
|
||||
• Доступ к автоматизированным решениям, принятым на основе данных.
|
||||
|
||||
Право на исправление:
|
||||
• Требование исправления неточных или неполных данных;
|
||||
• Дополнение недостающей информации;
|
||||
• Актуализация устаревших данных;
|
||||
• Получение подтверждения о внесенных изменениях.
|
||||
|
||||
Право на удаление ("право на забвение"):
|
||||
• Требование удаления персональных данных при наличии оснований;
|
||||
• Удаление данных после отзыва согласия;
|
||||
• Удаление при прекращении правовых оснований для обработки;
|
||||
• Получение подтверждения об удалении.
|
||||
|
||||
Право на ограничение обработки:
|
||||
• Требование блокирования обработки на время проверки точности данных;
|
||||
• Ограничение способов обработки;
|
||||
• Приостановление передачи третьим лицам;
|
||||
• Сохранение данных без их активного использования.
|
||||
|
||||
8.2. Право на отзыв согласия:
|
||||
• Согласие может быть отозвано в любое время;
|
||||
• Отзыв не влияет на законность обработки до момента отзыва;
|
||||
• Отзыв оформляется в письменной форме;
|
||||
• После отзыва обработка прекращается в разумные сроки.
|
||||
|
||||
8.3. Право на обжалование:
|
||||
• Обращение к Оператору с жалобами на действия по обработке данных;
|
||||
• Обращение в Роскомнадзор или его территориальные органы;
|
||||
• Обращение в суд для защиты нарушенных прав;
|
||||
• Требование возмещения морального и материального вреда.
|
||||
|
||||
8.4. Порядок реализации прав:
|
||||
• Обращения направляются на адрес: company@bitforcefoundation.ru;
|
||||
• Обращения рассматриваются в течение 30 дней;
|
||||
• При необходимости срок может быть продлен на 30 дней;
|
||||
• Ответ предоставляется в письменной или электронной форме по выбору субъекта.
|
||||
|
||||
|
||||
9. Заключительные положения
|
||||
---------------------------
|
||||
|
||||
9.1. Действие согласия:
|
||||
• Согласие действует с момента его предоставления;
|
||||
• Согласие действует до его отзыва или до достижения целей обработки;
|
||||
• Согласие может быть изменено по взаимному соглашению сторон;
|
||||
• При существенных изменениях целей требуется новое согласие.
|
||||
|
||||
9.2. Форма предоставления согласия:
|
||||
• Согласие может быть предоставлено в письменной форме;
|
||||
• Согласие может быть предоставлено в электронной форме;
|
||||
• Согласие может выражаться путем совершения конклюдентных действий;
|
||||
• Согласие фиксируется и сохраняется Оператором.
|
||||
|
||||
9.3. Последствия непредоставления согласия:
|
||||
• Отказ в предоставлении согласия может повлечь невозможность регистрации;
|
||||
• Отказ может ограничить доступ к отдельным услугам;
|
||||
• Отказ в согласии на маркетинг не влияет на основные услуги;
|
||||
• Субъект вправе предоставить частичное согласие.
|
||||
|
||||
9.4. Контактная информация:
|
||||
Для реализации прав и направления обращений:
|
||||
• Почтовый адрес: 196246, г. Санкт-Петербург, Московское ш., д. 25, к. 1, лит. В, пом. 3-н
|
||||
• Электронная почта: company@bitforcefoundation.ru
|
||||
• Ответственное лицо: Кленин Михаил Васильевич
|
||||
• Официальный сайт: https://bitforce-foundation.ru
|
||||
|
||||
9.5. Подтверждение понимания:
|
||||
Предоставляя настоящее согласие, я подтверждаю, что:
|
||||
• Ознакомлен с содержанием согласия и понимаю его значение;
|
||||
• Понимаю цели и способы обработки моих персональных данных;
|
||||
• Знаю о своих правах и способах их реализации;
|
||||
• Согласие предоставляется добровольно и осознанно;
|
||||
• Имею возможность отозвать согласие в любое время.
|
||||
@@ -1,175 +0,0 @@
|
||||
<!DOCTYPE html><html lang="ru"><head>
|
||||
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ЭКСА — Сид Фраза</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
background: #0A0B2E;
|
||||
color: #FFFFFF;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
min-height: 100vh;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.nav{display:flex;align-items:center;justify-content:space-between;padding:0 32px;height:60px;border-bottom:1px solid rgba(255,255,255,0.06);flex-shrink:0}
|
||||
.nav-logo{display:flex;align-items:center}
|
||||
.nav-logo img{height:32px}
|
||||
.ticker{display:flex;gap:24px;font-size:13px;font-family:var(--mono)}
|
||||
.tick{display:flex;align-items:center;gap:6px;color:#B5B0CC}
|
||||
.tick b{color:#fff}
|
||||
.tick .up{color:#00C48C}.tick .dn{color:#FF4D4D}
|
||||
.nav-account{display:flex;align-items:center;gap:10px}
|
||||
.avatar{width:34px;height:34px;border-radius:50%;background:#3D2A8E}
|
||||
.nav-account span{color:#B5B0CC;font-size:14px;font-weight:500}
|
||||
|
||||
/* Content */
|
||||
.content {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 32px 60px;
|
||||
}
|
||||
|
||||
/* Title row */
|
||||
.title-row {
|
||||
display: flex; align-items: flex-start; justify-content: space-between;
|
||||
}
|
||||
.title-row h1 {
|
||||
font-size: 20px; font-weight: 700; letter-spacing: 0.04em;
|
||||
}
|
||||
.title-buttons {
|
||||
display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
|
||||
}
|
||||
.btn-outline {
|
||||
background: rgba(255,255,255,0.08);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
width: 160px;
|
||||
padding: 10px 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
text-align: center;
|
||||
}
|
||||
.btn-outline:hover {
|
||||
background: rgba(255,255,255,0.12);
|
||||
border-color: rgba(255,255,255,0.18);
|
||||
}
|
||||
|
||||
/* Subtitle */
|
||||
.subtitle {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
color: #B5B0CC;
|
||||
font-variant: all-small-caps;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.subtitle .countdown { color: #4A6DFF; font-weight: 700; }
|
||||
|
||||
/* Grid */
|
||||
.seed-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
.seed-card {
|
||||
background: rgba(255,255,255,0.04);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 14px;
|
||||
height: 52px;
|
||||
display: flex; align-items: center;
|
||||
padding: 0 18px;
|
||||
gap: 10px;
|
||||
transition: border-color 0.25s, box-shadow 0.25s;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
.seed-card:hover {
|
||||
border-color: rgba(74,109,255,0.4);
|
||||
box-shadow: 0 0 12px rgba(74,109,255,0.15);
|
||||
}
|
||||
.seed-num {
|
||||
color: #B5B0CC;
|
||||
font-size: 13px;
|
||||
min-width: 22px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.seed-word {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Warning */
|
||||
.warning {
|
||||
margin-top: 32px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.warning p {
|
||||
max-width: 480px;
|
||||
font-size: 13px;
|
||||
color: #B5B0CC;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.warning .icon { color: #FF4D4D; font-size: 18px; margin-bottom: 8px; }
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>:root{--mono:'JetBrains Mono',monospace}</style></head>
|
||||
<body data-cc-id="cc-4" style="cursor: crosshair; font-family: Manrope;">
|
||||
|
||||
<nav class="nav">
|
||||
<div class="nav-logo">
|
||||
<img src="logo-full-white.png" alt="ЭКСА">
|
||||
</div>
|
||||
<div class="ticker">
|
||||
<div class="tick"><b>BTC</b> $66,916.00 <span class="up">+0.12%</span></div>
|
||||
<div class="tick"><b>ETH</b> $2,053.97 <span class="dn">−0.12%</span></div>
|
||||
<div class="tick"><b>SOL</b> $163.84 <span class="dn">−1.57%</span></div>
|
||||
</div>
|
||||
<div class="nav-account">
|
||||
<div class="avatar"></div>
|
||||
<span>Account 1</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="content" style="font-family: Manrope" data-cc-id="cc-5">
|
||||
<div class="title-row" style="height: 70px; width: 896px" data-cc-id="cc-16">
|
||||
<h1 style="width: 250px; font-size: 32px; font-family: Manrope;" data-cc-id="cc-17">СИД ФРАЗА</h1>
|
||||
<div class="title-buttons">
|
||||
<button class="btn-outline">СКРЫТЬ</button>
|
||||
<button class="btn-outline" style="font-size: 13px">КОПИРОВАТЬ</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="subtitle" style="font-size: 14px" data-cc-id="cc-15">АВТОМАТИЧЕСКОЕ СКРЫТИЕ ЧЕРЕЗ <span class="countdown" id="countdown">14</span>С</div>
|
||||
|
||||
<div class="seed-grid" id="seedGrid" data-cc-id="cc-9"><div class="seed-card"><span class="seed-num">1.</span><span class="seed-word">egg</span></div><div class="seed-card" data-cc-id="cc-13"><span class="seed-num">2.</span><span class="seed-word" data-cc-id="cc-14">phone</span></div><div class="seed-card"><span class="seed-num">3.</span><span class="seed-word">long</span></div><div class="seed-card"><span class="seed-num">4.</span><span class="seed-word">vibe</span></div><div class="seed-card" data-cc-id="cc-11"><span class="seed-num">5.</span><span class="seed-word" data-cc-id="cc-12">potato</span></div><div class="seed-card"><span class="seed-num">6.</span><span class="seed-word">soup</span></div><div class="seed-card" data-cc-id="cc-7"><span class="seed-num">7.</span><span class="seed-word" data-cc-id="cc-8">skirt</span></div><div class="seed-card" data-cc-id="cc-10"><span class="seed-num">8.</span><span class="seed-word">black</span></div><div class="seed-card"><span class="seed-num">9.</span><span class="seed-word">phase</span></div><div class="seed-card" data-cc-id="cc-6"><span class="seed-num">10.</span><span class="seed-word">word</span></div><div class="seed-card"><span class="seed-num">11.</span><span class="seed-word">num</span></div><div class="seed-card"><span class="seed-num">12.</span><span class="seed-word">cucumber</span></div></div>
|
||||
|
||||
<div class="warning" style="justify-content: center; flex-direction: row; align-items: flex-start">
|
||||
<div class="icon" style="padding: 16px">⚠️</div>
|
||||
<p>Никогда не передавайте сид-фразу третьим лицам. Тот, кто знает фразу — владеет кошельком.</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
let sec = 52;
|
||||
const el = document.getElementById('countdown');
|
||||
setInterval(() => {
|
||||
if (sec > 0) { sec--; el.textContent = sec; }
|
||||
}, 1000);
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Navigate, Outlet } from 'react-router-dom'
|
||||
import { Navigate, Outlet, useLocation } from 'react-router-dom'
|
||||
import { useIsAuthenticated } from '@features/auth'
|
||||
import { ROUTES } from '@shared/config/routes'
|
||||
|
||||
export function GuestRoute() {
|
||||
const { isAuthenticated, isLoading } = useIsAuthenticated()
|
||||
const location = useLocation()
|
||||
const from = (location.state as { from?: { pathname: string } })?.from?.pathname ?? ROUTES.WALLET
|
||||
|
||||
if (isLoading) return null
|
||||
if (isAuthenticated) return <Navigate to={ROUTES.WALLET} replace />
|
||||
if (isAuthenticated) return <Navigate to={from} replace />
|
||||
return <Outlet />
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
})
|
||||
|
||||
export function QueryProvider({ children }: { children: ReactNode }) {
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
|
||||
@@ -2,12 +2,25 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'
|
||||
import { HomePage } from '@pages/home'
|
||||
import { WalletPage } from '@pages/wallet'
|
||||
import { SwapPage } from '@pages/swap'
|
||||
import { BridgePage } from '@pages/bridge'
|
||||
import { ProfilePage } from '@pages/profile'
|
||||
import { LoginPage } from '@pages/login'
|
||||
import { RegisterPage } from '@pages/register'
|
||||
import { RegisterTestPage } from '@pages/register-test'
|
||||
import { ConverterTestPage } from '@pages/converter-test'
|
||||
import { ConverterPage } from '@pages/converter'
|
||||
import { SeedPhrasePage } from '@pages/seed-phrase'
|
||||
import { KycPage } from '@pages/kyc'
|
||||
import { RestorePasswordPage } from '@pages/restore-password'
|
||||
import { PublichnayaOfertaPage } from '@pages/publichnaya-oferta'
|
||||
import { PolitikaPage } from '@pages/politika-personalnyh-dannyh'
|
||||
import { PolitikaCookiePage } from '@pages/politika-cookie'
|
||||
import { SoglasiePage } from '@pages/soglasie-personalnyh-dannyh'
|
||||
import { ReestryPage } from '@pages/reestr-pd-rkn'
|
||||
import { TransactionsPage } from '@pages/transactions'
|
||||
import { AdminPage } from '@pages/admin'
|
||||
import { AdminOrganizationPage } from '@pages/admin-organization'
|
||||
import { WalletLayout } from '@widgets/wallet-layout'
|
||||
import { ROUTES } from '@shared/config/routes'
|
||||
import { ScrollToTop } from './ScrollToTop'
|
||||
import { ProtectedRoute } from './ProtectedRoute'
|
||||
@@ -19,21 +32,41 @@ export function RouterProvider() {
|
||||
<ScrollToTop />
|
||||
<Routes>
|
||||
<Route path={ROUTES.HOME} element={<HomePage />} />
|
||||
<Route path={ROUTES.PUBLICHNAYA_OFERTA} element={<PublichnayaOfertaPage />} />
|
||||
<Route path={ROUTES.POLITIKA_PERSONALNYH_DANNYH} element={<PolitikaPage />} />
|
||||
<Route path={ROUTES.POLITIKA_COOKIE} element={<PolitikaCookiePage />} />
|
||||
<Route path={ROUTES.SOGLASIE_PERSONALNYH_DANNYH} element={<SoglasiePage />} />
|
||||
<Route path={ROUTES.REESTR_PD_RKN} element={<ReestryPage />} />
|
||||
<Route path={ROUTES.REGISTER_TEST} element={<RegisterTestPage />} />
|
||||
<Route path={ROUTES.CONVERTER_TEST} element={<ConverterTestPage />} />
|
||||
|
||||
{/* Admin panel — own auth gate, independent of the user auth system */}
|
||||
<Route path={ROUTES.ADMIN} element={<AdminPage />} />
|
||||
<Route path={ROUTES.ADMIN_ORGANIZATION} element={<AdminOrganizationPage />} />
|
||||
|
||||
<Route element={<GuestRoute />}>
|
||||
<Route path={ROUTES.LOGIN} element={<LoginPage />} />
|
||||
<Route path={ROUTES.REGISTER} element={<RegisterPage />} />
|
||||
<Route path={ROUTES.RESTORE_PASSWORD} element={<RestorePasswordPage />} />
|
||||
</Route>
|
||||
|
||||
<Route path={ROUTES.CONVERTER} element={<ConverterPage />} />
|
||||
|
||||
<Route element={<ProtectedRoute />}>
|
||||
<Route element={<WalletLayout footer center />}>
|
||||
<Route path={ROUTES.CONVERTER} element={<ConverterPage />} />
|
||||
</Route>
|
||||
|
||||
<Route element={<WalletLayout footer />}>
|
||||
<Route path={ROUTES.SWAP} element={<SwapPage />} />
|
||||
<Route path={ROUTES.BRIDGE} element={<BridgePage />} />
|
||||
<Route path={ROUTES.TRANSACTIONS} element={<TransactionsPage />} />
|
||||
</Route>
|
||||
|
||||
<Route path={ROUTES.WALLET} element={<WalletPage />} />
|
||||
<Route path={ROUTES.SWAP} element={<SwapPage />} />
|
||||
<Route path={ROUTES.WALLET_CHAIN} element={<WalletPage />} />
|
||||
<Route path={ROUTES.PROFILE} element={<ProfilePage />} />
|
||||
<Route path={ROUTES.SEED_PHRASE} element={<SeedPhrasePage />} />
|
||||
<Route path={ROUTES.KYC} element={<KycPage />} />
|
||||
</Route>
|
||||
<Route path={ROUTES.KYC} element={<KycPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
)
|
||||
|
||||
@@ -5,8 +5,28 @@ body {
|
||||
overflow-x: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
scrollbar-color: var(--grad-center) var(--bg-mid);
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bg-mid);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--grad-center);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--highlight);
|
||||
}
|
||||
|
||||
3
src/entities/commission/index.ts
Normal file
3
src/entities/commission/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { TIERS, TIER_MIN, TIER_MAX, findTier, progressPercent } from './model/tiers'
|
||||
export type { Tier } from './model/tiers'
|
||||
export { CommissionTable } from './ui/CommissionTable'
|
||||
@@ -6,8 +6,8 @@ export interface Tier {
|
||||
|
||||
export const TIERS: readonly Tier[] = [
|
||||
{ min: 5_000, max: 30_000, pct: 8 },
|
||||
{ min: 30_000, max: 100_000, pct: 6 },
|
||||
{ min: 100_000, max: 600_000, pct: 4 },
|
||||
{ min: 30_001, max: 100_000, pct: 6 },
|
||||
{ min: 100_001, max: 600_000, pct: 4 },
|
||||
] as const
|
||||
|
||||
export const TIER_MIN = TIERS[0].min
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TIERS } from '@widgets/currency-converter'
|
||||
import styles from './CommissionPanel.module.css'
|
||||
import { TIERS } from '../model/tiers'
|
||||
import styles from './CommissionTable.module.css'
|
||||
|
||||
const ru = (n: number) => n.toLocaleString('ru-RU')
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
effectiveRate: number
|
||||
}
|
||||
|
||||
export function CommissionPanel({ amount, progress, commission, effectiveRate }: Props) {
|
||||
export function CommissionTable({ amount, progress, commission, effectiveRate }: Props) {
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.title}>КОМИССИЯ СЕРВИСА</div>
|
||||
179
src/features/admin/api/adminApi.ts
Normal file
179
src/features/admin/api/adminApi.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import type {
|
||||
AdminLoginRequest,
|
||||
AdminLoginResponse,
|
||||
AdminMeResponse,
|
||||
CreateOrganizationRequest,
|
||||
DocumentResponse,
|
||||
Organization,
|
||||
OrganizationListResponse,
|
||||
PurchaseRequestListResponse,
|
||||
UpdateOrganizationRequest,
|
||||
WalletResponse,
|
||||
} from '../model/types'
|
||||
|
||||
const ADMIN_API_URL = 'https://app.admin.elcsa.ru'
|
||||
|
||||
// In-memory admin access token — deliberately separate from the user `tokenStore`
|
||||
// so the two independent auth systems never collide. No CSRF on the admin API.
|
||||
let adminToken: string | null = null
|
||||
|
||||
export const adminTokenStore = {
|
||||
get: () => adminToken,
|
||||
set: (token: string) => { adminToken = token },
|
||||
clear: () => { adminToken = null },
|
||||
}
|
||||
|
||||
async function doAdminRequest<T>(
|
||||
path: string,
|
||||
options: RequestInit,
|
||||
allowRetry: boolean,
|
||||
): Promise<T> {
|
||||
const bearer = adminTokenStore.get()
|
||||
// For multipart uploads we must NOT set Content-Type — the browser adds the
|
||||
// boundary itself. Detect FormData bodies and skip the JSON header.
|
||||
const isFormData = options.body instanceof FormData
|
||||
|
||||
const res = await fetch(`${ADMIN_API_URL}${path}`, {
|
||||
...options,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
||||
...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
|
||||
if (res.status === 401 && allowRetry) {
|
||||
try {
|
||||
await refreshAdminToken()
|
||||
return doAdminRequest<T>(path, options, false)
|
||||
} catch {
|
||||
adminTokenStore.clear()
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
|
||||
const data = await res.json().catch(() => null)
|
||||
if (!res.ok) throw data
|
||||
return data as T
|
||||
}
|
||||
|
||||
// Refresh by analogy with the main auth service: HttpOnly refresh cookie -> fresh access token.
|
||||
// Exact path is isolated here — adjust if the backend differs (e.g. /v1/jwt/refresh).
|
||||
export async function refreshAdminToken(): Promise<string> {
|
||||
const res = await fetch(`${ADMIN_API_URL}/v1/auth/refresh`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
})
|
||||
if (!res.ok) throw new Error('Unauthorized')
|
||||
const data = await res.json()
|
||||
if (data.access_token) adminTokenStore.set(data.access_token)
|
||||
return (data.access_token ?? true) as string
|
||||
}
|
||||
|
||||
export async function adminLogin(payload: AdminLoginRequest): Promise<AdminLoginResponse> {
|
||||
const data = await doAdminRequest<AdminLoginResponse>(
|
||||
'/v1/auth/login',
|
||||
{ method: 'POST', body: JSON.stringify(payload) },
|
||||
false,
|
||||
)
|
||||
if (data.access_token) adminTokenStore.set(data.access_token)
|
||||
return data
|
||||
}
|
||||
|
||||
export function getAdminMe(): Promise<AdminMeResponse> {
|
||||
return doAdminRequest<AdminMeResponse>('/v1/auth/me', {}, true)
|
||||
}
|
||||
|
||||
export async function adminLogout(): Promise<void> {
|
||||
try {
|
||||
await doAdminRequest<unknown>('/v1/auth/logout', { method: 'POST' }, false)
|
||||
} finally {
|
||||
adminTokenStore.clear()
|
||||
}
|
||||
}
|
||||
|
||||
export function getOrganizations(limit = 50, offset = 0): Promise<OrganizationListResponse> {
|
||||
return doAdminRequest<OrganizationListResponse>(
|
||||
`/v1/organizations?limit=${limit}&offset=${offset}`,
|
||||
{},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
export function createOrganization(payload: CreateOrganizationRequest): Promise<Organization> {
|
||||
return doAdminRequest<Organization>(
|
||||
'/v1/organizations',
|
||||
{ method: 'POST', body: JSON.stringify(payload) },
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
export function getOrganization(id: string): Promise<Organization> {
|
||||
return doAdminRequest<Organization>(`/v1/organizations/${id}`, {}, true)
|
||||
}
|
||||
|
||||
export function createOrganizationWallets(id: string): Promise<WalletResponse[]> {
|
||||
return doAdminRequest<WalletResponse[]>(
|
||||
`/v1/organizations/${id}/wallets/create`,
|
||||
{ method: 'POST' },
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
export function getDocuments(orgId: string): Promise<DocumentResponse[]> {
|
||||
return doAdminRequest<DocumentResponse[]>(
|
||||
`/v1/organizations/${orgId}/documents`,
|
||||
{},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
export function uploadDocument(
|
||||
orgId: string,
|
||||
documentType: string,
|
||||
file: File,
|
||||
): Promise<DocumentResponse> {
|
||||
const body = new FormData()
|
||||
body.append('document_type', documentType)
|
||||
body.append('file', file)
|
||||
|
||||
return doAdminRequest<DocumentResponse>(
|
||||
`/v1/organizations/${orgId}/documents`,
|
||||
{ method: 'POST', body },
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
export async function getPurchaseRequests(params: {
|
||||
organizationId?: string
|
||||
status?: string
|
||||
limit?: number
|
||||
offset?: number
|
||||
}): Promise<PurchaseRequestListResponse> {
|
||||
const query = new URLSearchParams()
|
||||
if (params.organizationId) query.set('organization_id', params.organizationId)
|
||||
if (params.status) query.set('status', params.status)
|
||||
query.set('limit', String(params.limit ?? 50))
|
||||
query.set('offset', String(params.offset ?? 0))
|
||||
|
||||
const data = await doAdminRequest<PurchaseRequestListResponse>(
|
||||
`/v1/purchase-requests?${query.toString()}`,
|
||||
{},
|
||||
true,
|
||||
)
|
||||
// TEMP: inspect real backend shape — especially which `status` values appear.
|
||||
console.log('[purchase-requests] list response:', data)
|
||||
return data
|
||||
}
|
||||
|
||||
export function updateOrganization(
|
||||
id: string,
|
||||
payload: UpdateOrganizationRequest,
|
||||
): Promise<Organization> {
|
||||
return doAdminRequest<Organization>(
|
||||
`/v1/organizations/${id}`,
|
||||
{ method: 'PATCH', body: JSON.stringify(payload) },
|
||||
true,
|
||||
)
|
||||
}
|
||||
16
src/features/admin/hooks/useAdminAuth.ts
Normal file
16
src/features/admin/hooks/useAdminAuth.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { refreshAdminToken } from '../api/adminApi'
|
||||
|
||||
export const ADMIN_AUTH_QUERY_KEY = ['admin-auth']
|
||||
|
||||
export function useAdminAuth(): { isAuthenticated: boolean; isLoading: boolean } {
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: ADMIN_AUTH_QUERY_KEY,
|
||||
queryFn: refreshAdminToken,
|
||||
retry: false,
|
||||
staleTime: Infinity,
|
||||
gcTime: Infinity,
|
||||
refetchOnWindowFocus: false,
|
||||
})
|
||||
return { isAuthenticated: !!data && !isError, isLoading }
|
||||
}
|
||||
15
src/features/admin/hooks/useAdminLogin.ts
Normal file
15
src/features/admin/hooks/useAdminLogin.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { adminLogin } from '../api/adminApi'
|
||||
import { ADMIN_AUTH_QUERY_KEY } from './useAdminAuth'
|
||||
|
||||
export function useAdminLogin() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: adminLogin,
|
||||
onSuccess: (data) => {
|
||||
// The token is already stored by adminLogin; write it straight into the
|
||||
// gate's query cache so we flip to "authenticated" without re-hitting /refresh.
|
||||
queryClient.setQueryData(ADMIN_AUTH_QUERY_KEY, data.access_token)
|
||||
},
|
||||
})
|
||||
}
|
||||
14
src/features/admin/hooks/useAdminLogout.ts
Normal file
14
src/features/admin/hooks/useAdminLogout.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { adminLogout } from '../api/adminApi'
|
||||
import { ADMIN_AUTH_QUERY_KEY } from './useAdminAuth'
|
||||
|
||||
export function useAdminLogout() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: adminLogout,
|
||||
onSuccess: () => {
|
||||
// Flip the gate back to "not authenticated" without triggering a /refresh refetch.
|
||||
queryClient.setQueryData(ADMIN_AUTH_QUERY_KEY, null)
|
||||
},
|
||||
})
|
||||
}
|
||||
13
src/features/admin/hooks/useCreateOrganization.ts
Normal file
13
src/features/admin/hooks/useCreateOrganization.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { createOrganization } from '../api/adminApi'
|
||||
import { ORGANIZATIONS_QUERY_KEY } from './useOrganizations'
|
||||
|
||||
export function useCreateOrganization() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: createOrganization,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ORGANIZATIONS_QUERY_KEY })
|
||||
},
|
||||
})
|
||||
}
|
||||
16
src/features/admin/hooks/useCreateOrganizationWallets.ts
Normal file
16
src/features/admin/hooks/useCreateOrganizationWallets.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { createOrganizationWallets } from '../api/adminApi'
|
||||
import { ORGANIZATIONS_QUERY_KEY } from './useOrganizations'
|
||||
import { ORGANIZATION_QUERY_KEY } from './useOrganization'
|
||||
|
||||
export function useCreateOrganizationWallets() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (organizationId: string) => createOrganizationWallets(organizationId),
|
||||
onSuccess: (_wallets, organizationId) => {
|
||||
// `has_wallets` flips to true server-side — refresh list and detail view.
|
||||
queryClient.invalidateQueries({ queryKey: ORGANIZATIONS_QUERY_KEY })
|
||||
queryClient.invalidateQueries({ queryKey: ORGANIZATION_QUERY_KEY(organizationId) })
|
||||
},
|
||||
})
|
||||
}
|
||||
12
src/features/admin/hooks/useDocuments.ts
Normal file
12
src/features/admin/hooks/useDocuments.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getDocuments } from '../api/adminApi'
|
||||
|
||||
export const DOCUMENTS_QUERY_KEY = (orgId: string) => ['admin-documents', orgId]
|
||||
|
||||
export function useDocuments(orgId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: DOCUMENTS_QUERY_KEY(orgId ?? ''),
|
||||
queryFn: () => getDocuments(orgId as string),
|
||||
enabled: !!orgId,
|
||||
})
|
||||
}
|
||||
12
src/features/admin/hooks/useOrganization.ts
Normal file
12
src/features/admin/hooks/useOrganization.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getOrganization } from '../api/adminApi'
|
||||
|
||||
export const ORGANIZATION_QUERY_KEY = (id: string) => ['admin-organization', id]
|
||||
|
||||
export function useOrganization(id: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ORGANIZATION_QUERY_KEY(id ?? ''),
|
||||
queryFn: () => getOrganization(id as string),
|
||||
enabled: !!id,
|
||||
})
|
||||
}
|
||||
11
src/features/admin/hooks/useOrganizations.ts
Normal file
11
src/features/admin/hooks/useOrganizations.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getOrganizations } from '../api/adminApi'
|
||||
|
||||
export const ORGANIZATIONS_QUERY_KEY = ['admin-organizations']
|
||||
|
||||
export function useOrganizations() {
|
||||
return useQuery({
|
||||
queryKey: ORGANIZATIONS_QUERY_KEY,
|
||||
queryFn: () => getOrganizations(),
|
||||
})
|
||||
}
|
||||
15
src/features/admin/hooks/usePurchaseRequests.ts
Normal file
15
src/features/admin/hooks/usePurchaseRequests.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getPurchaseRequests } from '../api/adminApi'
|
||||
|
||||
export const PURCHASE_REQUESTS_QUERY_KEY = (orgId: string) => [
|
||||
'admin-purchase-requests',
|
||||
orgId,
|
||||
]
|
||||
|
||||
export function usePurchaseRequests(orgId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: PURCHASE_REQUESTS_QUERY_KEY(orgId ?? ''),
|
||||
queryFn: () => getPurchaseRequests({ organizationId: orgId }),
|
||||
enabled: !!orgId,
|
||||
})
|
||||
}
|
||||
16
src/features/admin/hooks/useUpdateOrganization.ts
Normal file
16
src/features/admin/hooks/useUpdateOrganization.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { updateOrganization } from '../api/adminApi'
|
||||
import type { UpdateOrganizationRequest } from '../model/types'
|
||||
import { ORGANIZATIONS_QUERY_KEY } from './useOrganizations'
|
||||
import { ORGANIZATION_QUERY_KEY } from './useOrganization'
|
||||
|
||||
export function useUpdateOrganization(id: string) {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (payload: UpdateOrganizationRequest) => updateOrganization(id, payload),
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData(ORGANIZATION_QUERY_KEY(id), data)
|
||||
queryClient.invalidateQueries({ queryKey: ORGANIZATIONS_QUERY_KEY })
|
||||
},
|
||||
})
|
||||
}
|
||||
19
src/features/admin/hooks/useUploadDocument.ts
Normal file
19
src/features/admin/hooks/useUploadDocument.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { uploadDocument } from '../api/adminApi'
|
||||
import { DOCUMENTS_QUERY_KEY } from './useDocuments'
|
||||
|
||||
interface UploadArgs {
|
||||
documentType: string
|
||||
file: File
|
||||
}
|
||||
|
||||
export function useUploadDocument(orgId: string) {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ documentType, file }: UploadArgs) =>
|
||||
uploadDocument(orgId, documentType, file),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: DOCUMENTS_QUERY_KEY(orgId) })
|
||||
},
|
||||
})
|
||||
}
|
||||
40
src/features/admin/index.ts
Normal file
40
src/features/admin/index.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
export {
|
||||
adminLogin,
|
||||
adminLogout,
|
||||
getAdminMe,
|
||||
getOrganizations,
|
||||
getOrganization,
|
||||
createOrganization,
|
||||
createOrganizationWallets,
|
||||
updateOrganization,
|
||||
getDocuments,
|
||||
uploadDocument,
|
||||
getPurchaseRequests,
|
||||
refreshAdminToken,
|
||||
adminTokenStore,
|
||||
} from './api/adminApi'
|
||||
export type {
|
||||
AdminLoginRequest,
|
||||
AdminLoginResponse,
|
||||
AdminMeResponse,
|
||||
Organization,
|
||||
OrganizationListResponse,
|
||||
CreateOrganizationRequest,
|
||||
UpdateOrganizationRequest,
|
||||
WalletResponse,
|
||||
DocumentResponse,
|
||||
PurchaseRequestResponse,
|
||||
PurchaseRequestListResponse,
|
||||
BankDetails,
|
||||
} from './model/types'
|
||||
export { useAdminAuth, ADMIN_AUTH_QUERY_KEY } from './hooks/useAdminAuth'
|
||||
export { useAdminLogin } from './hooks/useAdminLogin'
|
||||
export { useAdminLogout } from './hooks/useAdminLogout'
|
||||
export { useOrganizations, ORGANIZATIONS_QUERY_KEY } from './hooks/useOrganizations'
|
||||
export { useOrganization, ORGANIZATION_QUERY_KEY } from './hooks/useOrganization'
|
||||
export { useCreateOrganization } from './hooks/useCreateOrganization'
|
||||
export { useCreateOrganizationWallets } from './hooks/useCreateOrganizationWallets'
|
||||
export { useUpdateOrganization } from './hooks/useUpdateOrganization'
|
||||
export { useDocuments, DOCUMENTS_QUERY_KEY } from './hooks/useDocuments'
|
||||
export { useUploadDocument } from './hooks/useUploadDocument'
|
||||
export { usePurchaseRequests, PURCHASE_REQUESTS_QUERY_KEY } from './hooks/usePurchaseRequests'
|
||||
125
src/features/admin/model/types.ts
Normal file
125
src/features/admin/model/types.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
export interface AdminLoginRequest {
|
||||
login: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface AdminLoginResponse {
|
||||
access_token: string
|
||||
token_type: string
|
||||
id: string
|
||||
login: string
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
role: string
|
||||
}
|
||||
|
||||
export interface AdminMeResponse {
|
||||
id: string
|
||||
login: string
|
||||
first_name: string | null
|
||||
last_name: string | null
|
||||
role: string
|
||||
}
|
||||
|
||||
export type BankDetails = Record<string, unknown>
|
||||
|
||||
export interface Organization {
|
||||
id: string
|
||||
user_id: string
|
||||
name: string
|
||||
short_name: string | null
|
||||
inn: string
|
||||
ogrn: string | null
|
||||
kpp: string | null
|
||||
legal_address: string | null
|
||||
actual_address: string | null
|
||||
bank_details: BankDetails | null
|
||||
contact_person: string | null
|
||||
contact_phone: string | null
|
||||
status: string
|
||||
kyc_verified: boolean
|
||||
kyc_verified_at: string | null
|
||||
has_wallets: boolean
|
||||
created_by: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
}
|
||||
|
||||
export interface OrganizationListResponse {
|
||||
items: Organization[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface WalletResponse {
|
||||
id: string
|
||||
chain: string
|
||||
address: string
|
||||
derivation_path: string
|
||||
created_at: string | null
|
||||
}
|
||||
|
||||
export interface DocumentResponse {
|
||||
id: string
|
||||
organization_id: string
|
||||
document_type: string
|
||||
file_name: string
|
||||
content_type: string
|
||||
file_size_bytes: number
|
||||
uploaded_by: string | null
|
||||
created_at: string | null
|
||||
download_url: string | null
|
||||
}
|
||||
|
||||
// Monetary fields are strings to preserve decimal precision — do not coerce to number.
|
||||
export interface PurchaseRequestResponse {
|
||||
id: string
|
||||
organization_id: string
|
||||
status: string
|
||||
usdt_amount: string
|
||||
rub_amount: string | null
|
||||
exchange_rate: string | null
|
||||
service_fee_percent: string | null
|
||||
comment: string | null
|
||||
admin_comment: string | null
|
||||
target_wallet_chain: string | null
|
||||
target_wallet_address: string | null
|
||||
tx_hash: string | null
|
||||
assigned_to: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
completed_at: string | null
|
||||
}
|
||||
|
||||
export interface PurchaseRequestListResponse {
|
||||
items: PurchaseRequestResponse[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface UpdateOrganizationRequest {
|
||||
name?: string | null
|
||||
short_name?: string | null
|
||||
ogrn?: string | null
|
||||
kpp?: string | null
|
||||
legal_address?: string | null
|
||||
actual_address?: string | null
|
||||
bank_details?: BankDetails | null
|
||||
contact_person?: string | null
|
||||
contact_phone?: string | null
|
||||
status?: string | null
|
||||
}
|
||||
|
||||
export interface CreateOrganizationRequest {
|
||||
email: string
|
||||
password: string
|
||||
name: string
|
||||
inn: string
|
||||
short_name?: string | null
|
||||
ogrn?: string | null
|
||||
kpp?: string | null
|
||||
legal_address?: string | null
|
||||
actual_address?: string | null
|
||||
bank_details?: BankDetails | null
|
||||
contact_person?: string | null
|
||||
contact_phone?: string | null
|
||||
status?: string
|
||||
}
|
||||
@@ -1,37 +1,159 @@
|
||||
import { getCsrfToken } from '@shared/api/csrf'
|
||||
import { tokenStore } from '@shared/api/tokenStore'
|
||||
|
||||
const USERS_API_URL = 'https://app.users.elcsa.ru'
|
||||
|
||||
export type AccountType = 'individual' | 'legal_entity'
|
||||
|
||||
// Nested organization payload — present only on legal_entity accounts.
|
||||
export interface LegalEntityInfo {
|
||||
id: string
|
||||
name: string
|
||||
inn: string
|
||||
status: string
|
||||
short_name: string | null
|
||||
ogrn: string | null
|
||||
kpp: string | null
|
||||
legal_address: string | null
|
||||
actual_address: string | null
|
||||
bank_details: Record<string, unknown> | null
|
||||
contact_person: string | null
|
||||
contact_phone: string | null
|
||||
kyc_verified: boolean
|
||||
kyc_verified_at: string | null
|
||||
}
|
||||
|
||||
export interface MeResponse {
|
||||
id: string
|
||||
email: string
|
||||
first_name: string
|
||||
middle_name: string
|
||||
last_name: string
|
||||
birth_date: string
|
||||
crypto_wallet: string | null
|
||||
phone: string
|
||||
// Person fields are null on legal_entity accounts.
|
||||
first_name: string | null
|
||||
middle_name: string | null
|
||||
last_name: string | null
|
||||
birth_date: string | null
|
||||
encrypted_mnemonic: string | null
|
||||
phone: string | null
|
||||
passport_data: string | null
|
||||
inn: string | null
|
||||
erc20: string | null
|
||||
avatar_link: string | null
|
||||
kyc_verified: boolean
|
||||
is_deleted: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
kyc_verified_at: string | null
|
||||
webp_size_bytes?: number
|
||||
// "individual" -> физлицо, "legal_entity" -> аккаунт юр.лица.
|
||||
account_type: AccountType
|
||||
// Populated only for legal_entity accounts.
|
||||
legal_entity?: LegalEntityInfo | null
|
||||
}
|
||||
|
||||
export interface UploadAvatarPayload {
|
||||
photo_base64: string
|
||||
decoded_bytes: string
|
||||
}
|
||||
|
||||
async function authedHeaders(): Promise<HeadersInit> {
|
||||
const csrf = await getCsrfToken()
|
||||
const bearer = tokenStore.get()
|
||||
return {
|
||||
'X-CSRF-Token': csrf,
|
||||
...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMe(): Promise<MeResponse> {
|
||||
const csrf = await getCsrfToken()
|
||||
const headers = await authedHeaders()
|
||||
|
||||
const res = await fetch(`${USERS_API_URL}/me/`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrf,
|
||||
},
|
||||
headers,
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw data
|
||||
return data
|
||||
}
|
||||
|
||||
export async function uploadAvatar(payload: UploadAvatarPayload): Promise<MeResponse> {
|
||||
const headers = await authedHeaders()
|
||||
|
||||
const res = await fetch(`${USERS_API_URL}/me/settings/avatar`, {
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw data
|
||||
return data
|
||||
}
|
||||
|
||||
export interface PasswordResetStartPayload {
|
||||
email: string
|
||||
}
|
||||
|
||||
export async function passwordResetStart(payload: PasswordResetStartPayload): Promise<void> {
|
||||
const csrf = await getCsrfToken()
|
||||
const res = await fetch(`${USERS_API_URL}/me/settings/password/forgot/start`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrf,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
throw data
|
||||
}
|
||||
}
|
||||
|
||||
export async function updatePhone(phone: string): Promise<void> {
|
||||
const headers = await authedHeaders()
|
||||
|
||||
const res = await fetch(`${USERS_API_URL}/me/settings/phone`, {
|
||||
method: 'PATCH',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers,
|
||||
},
|
||||
body: JSON.stringify({ phone }),
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
throw data
|
||||
}
|
||||
}
|
||||
|
||||
export interface PasswordResetCompletePayload {
|
||||
email: string
|
||||
code: string
|
||||
new_password: string
|
||||
confirm_password: string
|
||||
}
|
||||
|
||||
export async function passwordResetComplete(payload: PasswordResetCompletePayload): Promise<void> {
|
||||
const csrf = await getCsrfToken()
|
||||
const res = await fetch(`${USERS_API_URL}/me/settings/password/forgot/complete`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrf,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}))
|
||||
throw data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { UseQueryOptions } from '@tanstack/react-query'
|
||||
import { getMe } from '../api/profileApi'
|
||||
import type { MeResponse } from '../api/profileApi'
|
||||
|
||||
export function useMe() {
|
||||
type MeOptions = Pick<UseQueryOptions<MeResponse>, 'refetchInterval' | 'enabled'>
|
||||
|
||||
export function useMe(options?: MeOptions) {
|
||||
return useQuery<MeResponse>({
|
||||
queryKey: ['me'],
|
||||
queryFn: getMe,
|
||||
staleTime: Infinity,
|
||||
gcTime: Infinity,
|
||||
retry: false,
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
12
src/features/auth/hooks/useUpdatePhone.ts
Normal file
12
src/features/auth/hooks/useUpdatePhone.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { updatePhone } from '../api/profileApi'
|
||||
|
||||
export function useUpdatePhone() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<void, unknown, string>({
|
||||
mutationFn: updatePhone,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['me'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
13
src/features/auth/hooks/useUploadAvatar.ts
Normal file
13
src/features/auth/hooks/useUploadAvatar.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { uploadAvatar } from '../api/profileApi'
|
||||
import type { MeResponse, UploadAvatarPayload } from '../api/profileApi'
|
||||
|
||||
export function useUploadAvatar() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation<MeResponse, unknown, UploadAvatarPayload>({
|
||||
mutationFn: uploadAvatar,
|
||||
onSuccess: (data) => {
|
||||
queryClient.setQueryData(['me'], data)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
export { registrationStart, registrationComplete, loginStart, loginComplete } from './api/registrationApi'
|
||||
export { getMe } from './api/profileApi'
|
||||
export type { MeResponse } from './api/profileApi'
|
||||
export { getMe, uploadAvatar, updatePhone } from './api/profileApi'
|
||||
export type { MeResponse, UploadAvatarPayload } from './api/profileApi'
|
||||
export { useMe } from './hooks/useMe'
|
||||
export { useUploadAvatar } from './hooks/useUploadAvatar'
|
||||
export { useUpdatePhone } from './hooks/useUpdatePhone'
|
||||
export type { RegistrationStartPayload, RegistrationCompletePayload, LoginStartPayload, LoginCompletePayload, AuthResponse } from './api/registrationApi'
|
||||
export { useIsAuthenticated } from './hooks/useIsAuthenticated'
|
||||
export { useAuth, AUTH_QUERY_KEY } from './hooks/useAuth'
|
||||
|
||||
@@ -62,6 +62,10 @@ export function getPaymentQuote(usdtAmount: number): Promise<PaymentQuote> {
|
||||
return doPaymentRequest(`/payment/quote?usdt_amount=${usdtAmount}`, {}, true)
|
||||
}
|
||||
|
||||
export function getPaymentQuoteByRub(rubAmount: number): Promise<PaymentQuote> {
|
||||
return doPaymentRequest(`/payment/quote/rub?total_rub=${rubAmount}`, {}, true)
|
||||
}
|
||||
|
||||
export interface CreateOrderPayload {
|
||||
usdt_amount: number
|
||||
usdt_exchange_rate: number
|
||||
@@ -103,3 +107,71 @@ export function createOrder(payload: CreateOrderPayload): Promise<OrderResult> {
|
||||
body: JSON.stringify(payload),
|
||||
}, true)
|
||||
}
|
||||
|
||||
export type OrderStatus = 'pending' | 'rejected' | 'completed' | 'cancelled' | 'error'
|
||||
|
||||
export type PaymentStatus =
|
||||
| 'pending'
|
||||
| 'money_accepted'
|
||||
| 'web3_processing'
|
||||
| 'web3_hash_error'
|
||||
| 'web3_balance_problem'
|
||||
| 'receipt_error'
|
||||
| 'completed'
|
||||
| 'usdt_delivered'
|
||||
|
||||
export interface Order {
|
||||
id: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
user_id: string
|
||||
usdt_amount: string
|
||||
usdt_exchange_rate: string
|
||||
gas_fee: string
|
||||
total_price: string
|
||||
service_fee: string
|
||||
status: OrderStatus
|
||||
client_payment_id: string
|
||||
itpay_payment_qr_url_desktop: string
|
||||
itpay_payment_qr_url_android: string
|
||||
itpay_payment_qr_url_ios: string
|
||||
itpay_payment_qr_image_desktop: string
|
||||
itpay_payment_qr_image_android: string
|
||||
itpay_payment_qr_image_ios: string
|
||||
itpay_id: string
|
||||
itpay_qr_id: string
|
||||
itpay_amount: string
|
||||
itpay_created_at: string
|
||||
}
|
||||
|
||||
export interface Payment {
|
||||
id: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
user_id: string
|
||||
order_id: string
|
||||
status: PaymentStatus
|
||||
receipt_cloudekassir_id: string
|
||||
receipt_cloudekassir_link: string
|
||||
itpay_payment_id: string
|
||||
itpay_paid_amount: string
|
||||
transaction_id: string
|
||||
web3_transaction_hash: string
|
||||
paid_at: string
|
||||
expired_date: string
|
||||
}
|
||||
|
||||
export interface OrderWithPayment {
|
||||
order: Order
|
||||
payment: Payment | null
|
||||
}
|
||||
|
||||
export interface OrdersResponse {
|
||||
orders: OrderWithPayment[]
|
||||
}
|
||||
|
||||
export const ORDERS_LIMIT = 20
|
||||
|
||||
export function getOrders(offset: number, limit: number = ORDERS_LIMIT): Promise<OrdersResponse> {
|
||||
return doPaymentRequest(`/payment/orders?offset=${offset}&limit=${limit}`, {}, true)
|
||||
}
|
||||
|
||||
15
src/features/payment/hooks/useOrders.ts
Normal file
15
src/features/payment/hooks/useOrders.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { getOrders, ORDERS_LIMIT } from '../api/paymentApi'
|
||||
|
||||
export function useOrders() {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ['payment', 'orders'],
|
||||
queryFn: ({ pageParam }) => getOrders(pageParam as number),
|
||||
initialPageParam: 0,
|
||||
getNextPageParam: (lastPage, allPages) => {
|
||||
if (lastPage.orders.length < ORDERS_LIMIT) return undefined
|
||||
return allPages.length * ORDERS_LIMIT
|
||||
},
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
13
src/features/payment/hooks/usePaymentQuoteByRub.ts
Normal file
13
src/features/payment/hooks/usePaymentQuoteByRub.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getPaymentQuoteByRub } from '../api/paymentApi'
|
||||
import type { PaymentQuote } from '../api/paymentApi'
|
||||
|
||||
export function usePaymentQuoteByRub(rubAmount: number) {
|
||||
return useQuery<PaymentQuote>({
|
||||
queryKey: ['payment', 'quote', 'rub', rubAmount],
|
||||
queryFn: () => getPaymentQuoteByRub(rubAmount),
|
||||
enabled: rubAmount > 0,
|
||||
staleTime: 30_000,
|
||||
retry: false,
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
export { usePaymentConfig } from './hooks/usePaymentConfig'
|
||||
export { usePaymentQuote } from './hooks/usePaymentQuote'
|
||||
export { usePaymentQuoteByRub } from './hooks/usePaymentQuoteByRub'
|
||||
export { useCreateOrder } from './hooks/useCreateOrder'
|
||||
export type { PaymentConfig, PaymentQuote, CreateOrderPayload, OrderResult } from './api/paymentApi'
|
||||
export { useOrders } from './hooks/useOrders'
|
||||
export { useCurrencyConversion } from './model/useCurrencyConversion'
|
||||
export type { PaymentConfig, PaymentQuote, CreateOrderPayload, OrderResult, Order, Payment, OrderWithPayment, OrderStatus, PaymentStatus } from './api/paymentApi'
|
||||
|
||||
102
src/features/payment/model/useCurrencyConversion.ts
Normal file
102
src/features/payment/model/useCurrencyConversion.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useState } from 'react'
|
||||
import { useDebounce } from '@shared/lib/hooks/useDebounce'
|
||||
import { progressPercent } from '@entities/commission'
|
||||
import { GAS_PRICE, MIN_RUB_AMOUNT } from '@shared/config/constants'
|
||||
import type { ConvertFieldData } from '@shared/ui'
|
||||
import { usePaymentConfig } from '../hooks/usePaymentConfig'
|
||||
import { usePaymentQuote } from '../hooks/usePaymentQuote'
|
||||
import { usePaymentQuoteByRub } from '../hooks/usePaymentQuoteByRub'
|
||||
|
||||
const TOO_LARGE_ERROR = 'Сумма слишком большая и превышает 600 000 ₽'
|
||||
|
||||
const sanitize = (raw: string) => raw.replace(/[^0-9.]/g, '')
|
||||
|
||||
interface Options {
|
||||
/** Значение курса USDT/RUB до загрузки конфига (пилюля). */
|
||||
rateFallback?: number
|
||||
}
|
||||
|
||||
export function useCurrencyConversion({ rateFallback = 0 }: Options = {}) {
|
||||
const [direction, setDirection] = useState<'usdt_to_rub' | 'rub_to_usdt'>('usdt_to_rub')
|
||||
const [usdtInput, setUsdtInput] = useState('1000')
|
||||
const [rubInput, setRubInput] = useState(String(MIN_RUB_AMOUNT))
|
||||
|
||||
const { data: config } = usePaymentConfig()
|
||||
const configUsdtRate = Number(config?.usdt_exchange_rate) || rateFallback
|
||||
const gasPriceRub = Number(config?.gas_fee) || GAS_PRICE
|
||||
|
||||
const isUsdtToRub = direction === 'usdt_to_rub'
|
||||
|
||||
const numUsdt = Number.parseFloat(usdtInput) || 0
|
||||
const debouncedUsdt = useDebounce(numUsdt, 400)
|
||||
const { data: quoteUsdtToRub, isError: quoteError } = usePaymentQuote(isUsdtToRub ? debouncedUsdt : 0)
|
||||
|
||||
const numRubInput = Number.parseFloat(rubInput) || 0
|
||||
const debouncedRub = useDebounce(numRubInput, 400)
|
||||
const { data: quoteRubToUsdt, isError: quoteRubError } = usePaymentQuoteByRub(!isUsdtToRub ? debouncedRub : 0)
|
||||
|
||||
const rubBelowMin = !isUsdtToRub && numRubInput > 0 && numRubInput < MIN_RUB_AMOUNT
|
||||
|
||||
const rubTotal = quoteUsdtToRub?.total_price ?? ''
|
||||
const rubTotalNum = Number(rubTotal) || 0
|
||||
const usdtFromRub = quoteRubToUsdt?.usdt_amount ?? ''
|
||||
const usdtFromRubNum = Number(usdtFromRub) || 0
|
||||
|
||||
const commission = isUsdtToRub
|
||||
? Number(quoteUsdtToRub?.service_fee) || 0
|
||||
: Number(quoteRubToUsdt?.service_fee) || 0
|
||||
|
||||
const displayRubAmount = isUsdtToRub ? rubTotalNum : numRubInput
|
||||
const effectiveRate = isUsdtToRub
|
||||
? (numUsdt > 0 ? rubTotalNum / numUsdt : 0)
|
||||
: (usdtFromRubNum > 0 ? numRubInput / usdtFromRubNum : 0)
|
||||
|
||||
function onSwap() {
|
||||
setDirection(d => (d === 'usdt_to_rub' ? 'rub_to_usdt' : 'usdt_to_rub'))
|
||||
}
|
||||
|
||||
const convert: ConvertFieldData = isUsdtToRub
|
||||
? {
|
||||
value: usdtInput,
|
||||
currency: 'USDT',
|
||||
onChange: (raw) => setUsdtInput(sanitize(raw)),
|
||||
error: quoteError ? TOO_LARGE_ERROR : undefined,
|
||||
}
|
||||
: {
|
||||
value: rubInput,
|
||||
currency: 'RUB',
|
||||
onChange: (raw) => setRubInput(sanitize(raw)),
|
||||
error: rubBelowMin
|
||||
? `Минимальная сумма: ${MIN_RUB_AMOUNT.toLocaleString('ru-RU')} ₽`
|
||||
: quoteRubError
|
||||
? TOO_LARGE_ERROR
|
||||
: undefined,
|
||||
}
|
||||
|
||||
const pay: ConvertFieldData = isUsdtToRub
|
||||
? { value: rubTotal, currency: 'RUB' }
|
||||
: { value: usdtFromRub, currency: 'USDT' }
|
||||
|
||||
return {
|
||||
isUsdtToRub,
|
||||
gasPriceRub,
|
||||
configUsdtRate,
|
||||
convert,
|
||||
pay,
|
||||
onSwap,
|
||||
commission: {
|
||||
amount: displayRubAmount,
|
||||
progress: progressPercent(displayRubAmount),
|
||||
commission,
|
||||
effectiveRate,
|
||||
},
|
||||
// сырые значения для создания ордера и валидации в обёртках
|
||||
numUsdt,
|
||||
usdtFromRubNum,
|
||||
rubTotal,
|
||||
rubTotalNum,
|
||||
numRubInput,
|
||||
usdtFromRub,
|
||||
rubBelowMin,
|
||||
}
|
||||
}
|
||||
418
src/features/wallet/api/walletApi.ts
Normal file
418
src/features/wallet/api/walletApi.ts
Normal file
@@ -0,0 +1,418 @@
|
||||
import { getCsrfToken } from '@shared/api/csrf'
|
||||
import { tokenStore, refreshAccessToken } from '@shared/api/tokenStore'
|
||||
|
||||
const WALLET_API_URL = 'https://app.cryptowallet.elcsa.ru'
|
||||
|
||||
export type Chain = 'ETH' | 'BSC' | 'BTC' | 'TRX' | 'SOL'
|
||||
|
||||
export interface FormattedAmount {
|
||||
raw: string
|
||||
formatted: string
|
||||
decimals: number
|
||||
usdPrice: number
|
||||
usdValue: number
|
||||
}
|
||||
|
||||
export interface WalletBalanceData {
|
||||
chain: Chain
|
||||
address: string
|
||||
native: FormattedAmount
|
||||
tokens: Record<string, FormattedAmount>
|
||||
}
|
||||
|
||||
export interface PriceEntry {
|
||||
usd: number
|
||||
}
|
||||
|
||||
export interface SendWalletPayload {
|
||||
to: string
|
||||
amount: string
|
||||
token?: string
|
||||
feeTier?: 'slow' | 'normal' | 'fast'
|
||||
}
|
||||
|
||||
export interface SendWalletResponse {
|
||||
data: { txid: string; chain: Chain }
|
||||
}
|
||||
|
||||
export interface WalletAddress {
|
||||
chain: Chain
|
||||
address: string
|
||||
derivationPath: string
|
||||
}
|
||||
|
||||
export interface PortfolioChain {
|
||||
chain: Chain
|
||||
address: string
|
||||
native: FormattedAmount
|
||||
tokens: Record<string, FormattedAmount>
|
||||
totalUsd: number
|
||||
stale: boolean
|
||||
lastUpdated: number
|
||||
}
|
||||
|
||||
export interface PortfolioData {
|
||||
totalUsd: number
|
||||
hasErrors: boolean
|
||||
perChain: Record<Chain, PortfolioChain>
|
||||
}
|
||||
|
||||
export const CHAINS: Chain[] = ['ETH', 'BSC', 'BTC', 'TRX', 'SOL']
|
||||
|
||||
async function walletGet<T>(path: string, allowRetry: boolean = true): Promise<T> {
|
||||
const csrf = await getCsrfToken()
|
||||
const bearer = tokenStore.get()
|
||||
|
||||
const res = await fetch(`${WALLET_API_URL}${path}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrf,
|
||||
...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),
|
||||
},
|
||||
})
|
||||
|
||||
if (res.status === 401 && allowRetry) {
|
||||
try {
|
||||
await refreshAccessToken()
|
||||
return walletGet<T>(path, false)
|
||||
} catch {
|
||||
tokenStore.clear()
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw data
|
||||
return data as T
|
||||
}
|
||||
|
||||
async function walletPost<T>(
|
||||
path: string,
|
||||
body: unknown,
|
||||
allowRetry: boolean = true,
|
||||
extraHeaders: Record<string, string> = {}
|
||||
): Promise<T> {
|
||||
const csrf = await getCsrfToken()
|
||||
const bearer = tokenStore.get()
|
||||
|
||||
const res = await fetch(`${WALLET_API_URL}${path}`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-Token': csrf,
|
||||
...(bearer ? { Authorization: `Bearer ${bearer}` } : {}),
|
||||
...extraHeaders,
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (res.status === 401 && allowRetry) {
|
||||
try {
|
||||
await refreshAccessToken()
|
||||
return walletPost<T>(path, body, false, extraHeaders)
|
||||
} catch {
|
||||
tokenStore.clear()
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw data
|
||||
return data as T
|
||||
}
|
||||
|
||||
export async function getWalletAddresses(): Promise<WalletAddress[]> {
|
||||
const res = await walletGet<{ success: boolean; data: WalletAddress[] }>('/api/wallets')
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function getWalletBalance(chain: Chain): Promise<WalletBalanceData> {
|
||||
const res = await walletGet<{ success: boolean; data: WalletBalanceData }>(`/api/wallets/${chain}/balance`)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function getPrices(symbols: string[]): Promise<Record<string, PriceEntry>> {
|
||||
const res = await walletGet<{ success: boolean; data: Record<string, PriceEntry> }>(
|
||||
`/api/prices?symbols=${symbols.join(',')}`
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function sendWallet(chain: Chain, payload: SendWalletPayload): Promise<SendWalletResponse> {
|
||||
return walletPost<SendWalletResponse>(`/api/wallets/${chain}/send`, payload)
|
||||
}
|
||||
|
||||
export async function getPortfolio(): Promise<PortfolioData> {
|
||||
const res = await walletGet<{ success: boolean; data: PortfolioData }>('/api/wallets/portfolio')
|
||||
return res.data
|
||||
}
|
||||
|
||||
export interface TokenInfo {
|
||||
chain: string
|
||||
symbol: string
|
||||
name: string
|
||||
contract: string | null
|
||||
}
|
||||
|
||||
export interface RelayQuotePayload {
|
||||
user: string
|
||||
recipient: string
|
||||
originChainId: number
|
||||
destinationChainId: number
|
||||
originCurrency: string
|
||||
destinationCurrency: string
|
||||
amount: string
|
||||
tradeType: 'EXACT_INPUT'
|
||||
}
|
||||
|
||||
export interface RelayQuoteResponse {
|
||||
details: {
|
||||
currencyOut: {
|
||||
amountFormatted: string
|
||||
amountUsd: string
|
||||
}
|
||||
}
|
||||
fees: {
|
||||
gas: {
|
||||
amountUsd: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTokensList(): Promise<TokenInfo[]> {
|
||||
const res = await walletGet<{ success: boolean; data: TokenInfo[] }>('/api/tokens')
|
||||
return res.data
|
||||
}
|
||||
|
||||
export interface JumperToken {
|
||||
address: string
|
||||
chainId: number
|
||||
symbol: string
|
||||
decimals: number
|
||||
name: string
|
||||
coinKey?: string
|
||||
logoURI?: string
|
||||
priceUSD: string
|
||||
}
|
||||
|
||||
export type JumperTokensMap = Record<string, JumperToken[]>
|
||||
|
||||
export async function getJumperTokens(): Promise<JumperTokensMap> {
|
||||
const res = await walletGet<{ tokens?: JumperTokensMap; data?: { tokens: JumperTokensMap } }>(
|
||||
'/api/jumper/tokens?chains=1,56,1151111081099710,728126428,20000000000001'
|
||||
)
|
||||
return res.data?.tokens ?? res.tokens ?? {}
|
||||
}
|
||||
|
||||
export interface JumperQuotePayload {
|
||||
fromChain: string
|
||||
toChain: string
|
||||
fromToken: string
|
||||
toToken: string
|
||||
fromAmount: string
|
||||
fromAddress: string
|
||||
toAddress: string
|
||||
slippage: number
|
||||
}
|
||||
|
||||
export interface JumperQuoteToken {
|
||||
address: string
|
||||
chainId: number
|
||||
symbol: string
|
||||
decimals: number
|
||||
name: string
|
||||
logoURI?: string
|
||||
priceUSD: string
|
||||
}
|
||||
|
||||
export interface JumperFeeCost {
|
||||
name: string
|
||||
description?: string
|
||||
token: JumperQuoteToken
|
||||
amount: string
|
||||
amountUSD: string
|
||||
percentage?: string
|
||||
included?: boolean
|
||||
}
|
||||
|
||||
export interface JumperQuote {
|
||||
type: string
|
||||
id: string
|
||||
tool: string
|
||||
toolDetails: { key: string; name: string; logoURI?: string }
|
||||
action: {
|
||||
fromToken: JumperQuoteToken
|
||||
fromAmount: string
|
||||
toToken: JumperQuoteToken
|
||||
fromChainId: number
|
||||
toChainId: number
|
||||
slippage: number
|
||||
fromAddress: string
|
||||
toAddress: string
|
||||
}
|
||||
estimate: {
|
||||
tool: string
|
||||
approvalAddress?: string
|
||||
toAmountMin: string
|
||||
toAmount: string
|
||||
fromAmount: string
|
||||
feeCosts?: JumperFeeCost[]
|
||||
}
|
||||
}
|
||||
|
||||
export async function getJumperQuote(payload: JumperQuotePayload): Promise<JumperQuote> {
|
||||
const qs = new URLSearchParams({
|
||||
fromChain: payload.fromChain,
|
||||
toChain: payload.toChain,
|
||||
fromToken: payload.fromToken,
|
||||
toToken: payload.toToken,
|
||||
fromAmount: payload.fromAmount,
|
||||
fromAddress: payload.fromAddress,
|
||||
toAddress: payload.toAddress,
|
||||
slippage: String(payload.slippage),
|
||||
}).toString()
|
||||
const res = await walletGet<JumperQuote & { body?: JumperQuote; data?: { body?: JumperQuote } }>(
|
||||
`/api/jumper/quote-best?${qs}`
|
||||
)
|
||||
return (res.data?.body ?? res.body ?? res) as JumperQuote
|
||||
}
|
||||
|
||||
export interface BridgeExecutePayload {
|
||||
provider: string
|
||||
fromChain: number
|
||||
toChain: number
|
||||
fromToken: string
|
||||
toToken: string
|
||||
fromAmount: string
|
||||
fromAddress: string
|
||||
toAddress: string
|
||||
acceptedMinOut?: string
|
||||
}
|
||||
|
||||
export interface BridgeExecuteResult {
|
||||
provider: string
|
||||
fromChain: number
|
||||
toChain: number
|
||||
toolName: string
|
||||
feeTxid?: string
|
||||
feeAmount?: string
|
||||
bridgeTxid: string
|
||||
fromAmount: string
|
||||
toAmountMin: string
|
||||
fromAmountUSD?: string
|
||||
toAmountUSD?: string
|
||||
trackerUrl?: string
|
||||
}
|
||||
|
||||
export async function executeBridge(payload: BridgeExecutePayload): Promise<BridgeExecuteResult> {
|
||||
const res = await walletPost<{ data?: { success: boolean; data: BridgeExecuteResult } }>(
|
||||
'/api/bridge/execute',
|
||||
payload,
|
||||
true,
|
||||
{ 'Idempotency-Key': crypto.randomUUID() }
|
||||
)
|
||||
return (res.data?.data ?? res) as BridgeExecuteResult
|
||||
}
|
||||
|
||||
export async function getRelayQuote(payload: RelayQuotePayload): Promise<RelayQuoteResponse> {
|
||||
return walletPost<RelayQuoteResponse>('/api/relay/quote', payload)
|
||||
}
|
||||
|
||||
export interface RelaySwapStep {
|
||||
id: string
|
||||
action: string
|
||||
description: string
|
||||
kind: string
|
||||
items: Array<{
|
||||
status: string
|
||||
data: {
|
||||
from: string
|
||||
to: string
|
||||
data: string
|
||||
value: string
|
||||
chainId: number
|
||||
gas: string
|
||||
maxFeePerGas: string
|
||||
maxPriorityFeePerGas: string
|
||||
}
|
||||
check: {
|
||||
endpoint: string
|
||||
method: string
|
||||
}
|
||||
}>
|
||||
requestId: string
|
||||
}
|
||||
|
||||
export interface RelaySwapResponse {
|
||||
steps: RelaySwapStep[]
|
||||
fees: RelayQuoteResponse['fees']
|
||||
details: {
|
||||
operation: string
|
||||
sender: string
|
||||
recipient: string
|
||||
currencyIn: { amount: string; amountFormatted: string; amountUsd: string; currency: { symbol: string } }
|
||||
currencyOut: { amount: string; amountFormatted: string; amountUsd: string; currency: { symbol: string } }
|
||||
totalImpact: { usd: string; percent: string }
|
||||
rate: string
|
||||
timeEstimate: number
|
||||
}
|
||||
}
|
||||
|
||||
export async function executeRelaySwap(payload: RelayQuotePayload): Promise<RelaySwapResponse> {
|
||||
return walletPost<RelaySwapResponse>('/api/relay/execute/swap', payload)
|
||||
}
|
||||
|
||||
export async function signRawEvmTx(
|
||||
chain: 'ETH' | 'BSC',
|
||||
txData: RelaySwapStep['items'][0]['data']
|
||||
): Promise<unknown> {
|
||||
const key = `relay-${chain.toLowerCase()}-${Date.now()}`
|
||||
return walletPost(`/api/wallets/${chain}/sign-raw-evm-tx`, txData, true, { 'Idempotency-Key': key })
|
||||
}
|
||||
|
||||
export async function signSolTx(txData: unknown): Promise<unknown> {
|
||||
return walletPost('/api/wallets/SOL/sign-and-broadcast-tx', txData)
|
||||
}
|
||||
|
||||
export interface TrxSwapQuotePayload {
|
||||
from: string
|
||||
to: string
|
||||
amountHuman: string
|
||||
}
|
||||
|
||||
export interface TrxSwapQuoteData {
|
||||
quoteId: string
|
||||
expiresIn: number
|
||||
expectedOutFormatted: string
|
||||
minOutFormatted: string
|
||||
fees: {
|
||||
network: { amountFormatted: string; asset: string; amountUsd: number }
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTrxSwapQuote(payload: TrxSwapQuotePayload): Promise<TrxSwapQuoteData> {
|
||||
const res = await walletPost<{ success: boolean; data: TrxSwapQuoteData }>(
|
||||
'/api/wallets/TRX/swap/quote',
|
||||
payload
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
|
||||
export async function executeTrxSwap(quoteId: string): Promise<unknown> {
|
||||
return walletPost(
|
||||
'/api/wallets/TRX/swap',
|
||||
{ quoteId },
|
||||
true,
|
||||
{ 'Idempotency-Key': `trx-${Date.now()}` }
|
||||
)
|
||||
}
|
||||
|
||||
export async function createWallet(): Promise<void> {
|
||||
await walletPost<unknown>('/api/wallets/create', {})
|
||||
}
|
||||
|
||||
export async function revealMnemonic(): Promise<string> {
|
||||
const res = await walletPost<{ success: boolean; data: { mnemonic: string } }>('/api/wallets/mnemonic/reveal', { confirm: 'I_UNDERSTAND_SEED_IS_SECRET' })
|
||||
return res.data.mnemonic
|
||||
}
|
||||
3
src/features/wallet/index.ts
Normal file
3
src/features/wallet/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { useAllWalletBalances, usePrices, useSendWallet, useWalletAddresses, useWalletBalance, usePortfolio, useTokensList, useRelayQuote, useExecuteRelaySwap, useSignSwap, useTrxSwapQuote, useFetchTrxQuote, useExecuteTrxSwap, useJumperTokens, useJumperQuote, useFetchJumperQuote, useExecuteBridge, useCreateWallet, useRevealMnemonic } from './model/useWalletData'
|
||||
export type { Chain, FormattedAmount, WalletBalanceData, PriceEntry, SendWalletPayload, SendWalletResponse, WalletAddress, PortfolioData, PortfolioChain, TokenInfo, RelayQuotePayload, RelayQuoteResponse, RelaySwapResponse, RelaySwapStep, TrxSwapQuotePayload, TrxSwapQuoteData, JumperToken, JumperTokensMap, JumperQuote, JumperQuotePayload, JumperQuoteToken, JumperFeeCost, BridgeExecutePayload, BridgeExecuteResult } from './api/walletApi'
|
||||
export { CHAINS } from './api/walletApi'
|
||||
145
src/features/wallet/model/useWalletData.ts
Normal file
145
src/features/wallet/model/useWalletData.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { useQuery, useQueries, useMutation } from '@tanstack/react-query'
|
||||
import { getWalletBalance, getPrices, sendWallet, getWalletAddresses, getPortfolio, getTokensList, getRelayQuote, executeRelaySwap, signRawEvmTx, signSolTx, getTrxSwapQuote, executeTrxSwap, getJumperTokens, getJumperQuote, executeBridge, createWallet, revealMnemonic, CHAINS, type Chain, type SendWalletPayload, type RelayQuotePayload, type RelaySwapStep, type TrxSwapQuotePayload, type JumperQuotePayload, type BridgeExecutePayload } from '../api/walletApi'
|
||||
|
||||
export function useWalletBalance(chain: Chain) {
|
||||
return useQuery({
|
||||
queryKey: ['wallet', 'balance', chain],
|
||||
queryFn: () => getWalletBalance(chain),
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useAllWalletBalances() {
|
||||
return useQueries({
|
||||
queries: CHAINS.map(chain => ({
|
||||
queryKey: ['wallet', 'balance', chain],
|
||||
queryFn: () => getWalletBalance(chain),
|
||||
staleTime: 30_000,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
export function usePrices(symbols: string[]) {
|
||||
return useQuery({
|
||||
queryKey: ['wallet', 'prices', symbols.join(',')],
|
||||
queryFn: () => getPrices(symbols),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useSendWallet() {
|
||||
return useMutation({
|
||||
mutationFn: ({ chain, ...payload }: { chain: Chain } & SendWalletPayload) =>
|
||||
sendWallet(chain, payload),
|
||||
})
|
||||
}
|
||||
|
||||
export function useWalletAddresses() {
|
||||
return useQuery({
|
||||
queryKey: ['wallet', 'addresses'],
|
||||
queryFn: getWalletAddresses,
|
||||
staleTime: 10 * 60 * 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export function usePortfolio() {
|
||||
return useQuery({
|
||||
queryKey: ['wallet', 'portfolio'],
|
||||
queryFn: getPortfolio,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useTokensList() {
|
||||
return useQuery({
|
||||
queryKey: ['wallet', 'tokens'],
|
||||
queryFn: getTokensList,
|
||||
staleTime: 10 * 60 * 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useJumperTokens() {
|
||||
return useQuery({
|
||||
queryKey: ['wallet', 'jumper', 'tokens'],
|
||||
queryFn: getJumperTokens,
|
||||
staleTime: 10 * 60 * 1000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useJumperQuote(payload: JumperQuotePayload | null) {
|
||||
return useQuery({
|
||||
queryKey: ['wallet', 'jumper', 'quote',
|
||||
payload?.fromChain, payload?.toChain,
|
||||
payload?.fromToken, payload?.toToken,
|
||||
payload?.fromAmount, payload?.fromAddress, payload?.toAddress,
|
||||
],
|
||||
queryFn: () => getJumperQuote(payload!),
|
||||
enabled: !!payload,
|
||||
staleTime: 10_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useFetchJumperQuote() {
|
||||
return useMutation({ mutationFn: (payload: JumperQuotePayload) => getJumperQuote(payload) })
|
||||
}
|
||||
|
||||
export function useExecuteBridge() {
|
||||
return useMutation({ mutationFn: (payload: BridgeExecutePayload) => executeBridge(payload) })
|
||||
}
|
||||
|
||||
export function useCreateWallet() {
|
||||
return useMutation({ mutationFn: createWallet })
|
||||
}
|
||||
|
||||
export function useRevealMnemonic() {
|
||||
return useQuery({
|
||||
queryKey: ['wallet', 'mnemonic'],
|
||||
queryFn: revealMnemonic,
|
||||
staleTime: Infinity,
|
||||
retry: false,
|
||||
})
|
||||
}
|
||||
|
||||
export function useRelayQuote(payload: RelayQuotePayload | null) {
|
||||
return useQuery({
|
||||
queryKey: ['relay', 'quote',
|
||||
payload?.originChainId, payload?.destinationChainId,
|
||||
payload?.originCurrency, payload?.destinationCurrency, payload?.amount,
|
||||
],
|
||||
queryFn: () => getRelayQuote(payload!),
|
||||
enabled: !!payload,
|
||||
staleTime: 10_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useExecuteRelaySwap() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: RelayQuotePayload) => executeRelaySwap(payload),
|
||||
})
|
||||
}
|
||||
|
||||
export function useSignSwap() {
|
||||
return useMutation({
|
||||
mutationFn: ({ chain, txData }: { chain: Chain; txData: unknown }) => {
|
||||
if (chain === 'SOL') return signSolTx(txData)
|
||||
return signRawEvmTx(chain as 'ETH' | 'BSC', txData as RelaySwapStep['items'][0]['data'])
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useTrxSwapQuote(payload: TrxSwapQuotePayload | null) {
|
||||
return useQuery({
|
||||
queryKey: ['trx', 'quote', payload?.from, payload?.to, payload?.amountHuman],
|
||||
queryFn: () => getTrxSwapQuote(payload!),
|
||||
enabled: !!payload,
|
||||
staleTime: 10_000,
|
||||
})
|
||||
}
|
||||
|
||||
export function useFetchTrxQuote() {
|
||||
return useMutation({ mutationFn: getTrxSwapQuote })
|
||||
}
|
||||
|
||||
export function useExecuteTrxSwap() {
|
||||
return useMutation({ mutationFn: (quoteId: string) => executeTrxSwap(quoteId) })
|
||||
}
|
||||
3106
src/openapi.json
Normal file
3106
src/openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
1
src/pages/admin-organization/index.ts
Normal file
1
src/pages/admin-organization/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { AdminOrganizationPage } from './ui/AdminOrganizationPage'
|
||||
90
src/pages/admin-organization/model/useOrganizationForm.ts
Normal file
90
src/pages/admin-organization/model/useOrganizationForm.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useUpdateOrganization } from '@features/admin'
|
||||
import type { Organization, UpdateOrganizationRequest } from '@features/admin'
|
||||
|
||||
interface FormState {
|
||||
name: string
|
||||
short_name: string
|
||||
ogrn: string
|
||||
kpp: string
|
||||
legal_address: string
|
||||
actual_address: string
|
||||
contact_person: string
|
||||
contact_phone: string
|
||||
status: string
|
||||
}
|
||||
|
||||
function toForm(org: Organization): FormState {
|
||||
return {
|
||||
name: org.name ?? '',
|
||||
short_name: org.short_name ?? '',
|
||||
ogrn: org.ogrn ?? '',
|
||||
kpp: org.kpp ?? '',
|
||||
legal_address: org.legal_address ?? '',
|
||||
actual_address: org.actual_address ?? '',
|
||||
contact_person: org.contact_person ?? '',
|
||||
contact_phone: org.contact_phone ?? '',
|
||||
status: org.status ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
function extractErrorMessage(error: unknown): string {
|
||||
const e = error as { detail?: unknown }
|
||||
if (typeof e?.detail === 'string') return e.detail
|
||||
if (Array.isArray(e?.detail) && (e.detail[0] as { msg?: string })?.msg) {
|
||||
return (e.detail[0] as { msg: string }).msg
|
||||
}
|
||||
return 'Не удалось сохранить изменения'
|
||||
}
|
||||
|
||||
export function useOrganizationForm(
|
||||
org: Organization | undefined,
|
||||
id: string,
|
||||
onSaved?: () => void,
|
||||
) {
|
||||
const [form, setForm] = useState<FormState>(() =>
|
||||
org ? toForm(org) : {
|
||||
name: '', short_name: '', ogrn: '', kpp: '', legal_address: '',
|
||||
actual_address: '', contact_person: '', contact_phone: '', status: '',
|
||||
},
|
||||
)
|
||||
const mutation = useUpdateOrganization(id)
|
||||
|
||||
// Sync local form state once the organization loads / changes.
|
||||
useEffect(() => {
|
||||
if (org) setForm(toForm(org))
|
||||
}, [org])
|
||||
|
||||
const setField = (key: keyof FormState) => (value: string) =>
|
||||
setForm((prev) => ({ ...prev, [key]: value }))
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
const trimmedOrNull = (v: string) => (v.trim() ? v.trim() : null)
|
||||
|
||||
const payload: UpdateOrganizationRequest = {
|
||||
name: form.name.trim(),
|
||||
short_name: trimmedOrNull(form.short_name),
|
||||
ogrn: trimmedOrNull(form.ogrn),
|
||||
kpp: trimmedOrNull(form.kpp),
|
||||
legal_address: trimmedOrNull(form.legal_address),
|
||||
actual_address: trimmedOrNull(form.actual_address),
|
||||
contact_person: trimmedOrNull(form.contact_person),
|
||||
contact_phone: trimmedOrNull(form.contact_phone),
|
||||
status: trimmedOrNull(form.status),
|
||||
}
|
||||
|
||||
mutation.mutate(payload, { onSuccess: () => onSaved?.() })
|
||||
}
|
||||
|
||||
const error = mutation.isError ? extractErrorMessage(mutation.error) : null
|
||||
|
||||
return {
|
||||
form,
|
||||
setField,
|
||||
handleSubmit,
|
||||
isSaving: mutation.isPending,
|
||||
error,
|
||||
}
|
||||
}
|
||||
154
src/pages/admin-organization/ui/AdminOrganizationPage.module.css
Normal file
154
src/pages/admin-organization/ui/AdminOrganizationPage.module.css
Normal file
@@ -0,0 +1,154 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-deep);
|
||||
padding: 40px 48px;
|
||||
}
|
||||
|
||||
.header {
|
||||
max-width: 900px;
|
||||
margin: 0 auto 28px;
|
||||
}
|
||||
|
||||
.back {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary, rgba(255, 255, 255, 0.6));
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-bottom: 14px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.back:hover {
|
||||
color: var(--text-primary, #fff);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: clamp(24px, 3.5vw, 34px);
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #fff);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
max-width: 900px;
|
||||
margin: 0 auto 24px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.tab {
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--text-secondary, rgba(255, 255, 255, 0.6));
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: var(--text-primary, #fff);
|
||||
}
|
||||
|
||||
.tabActive {
|
||||
color: var(--text-primary, #fff);
|
||||
border-bottom-color: var(--interactive, #4a6dff);
|
||||
}
|
||||
|
||||
.tabPanel {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 14px;
|
||||
letter-spacing: 1.5px;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary, rgba(255, 255, 255, 0.5));
|
||||
font-weight: 600;
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.bankLabel {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary, rgba(255, 255, 255, 0.5));
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.06));
|
||||
border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.1));
|
||||
border-radius: 10px;
|
||||
color: var(--text-primary, #fff);
|
||||
font-family: var(--font-mono, monospace);
|
||||
font-size: 13px;
|
||||
padding: 12px 14px;
|
||||
resize: vertical;
|
||||
outline: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.textarea:focus {
|
||||
border-color: var(--interactive, #4a6dff);
|
||||
box-shadow: 0 0 0 3px rgba(74, 109, 255, 0.15);
|
||||
}
|
||||
|
||||
.state {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 16px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, rgba(255, 255, 255, 0.6));
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff5a5a;
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions {
|
||||
max-width: 320px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page {
|
||||
padding: 28px 20px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
142
src/pages/admin-organization/ui/AdminOrganizationPage.tsx
Normal file
142
src/pages/admin-organization/ui/AdminOrganizationPage.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { useAdminAuth, useOrganization } from '@features/admin'
|
||||
import { ROUTES } from '@shared/config/routes'
|
||||
import { FormField, Notification, PrimaryButton } from '@shared/ui'
|
||||
import { AdminLoginForm } from '@widgets/admin-login-form'
|
||||
import { OrganizationDocuments } from '@widgets/organization-documents'
|
||||
import { OrganizationPurchaseRequests } from '@widgets/organization-purchase-requests'
|
||||
import { useOrganizationForm } from '../model/useOrganizationForm'
|
||||
import styles from './AdminOrganizationPage.module.css'
|
||||
|
||||
type Tab = 'info' | 'documents' | 'requests'
|
||||
|
||||
const TABS: { id: Tab; label: string }[] = [
|
||||
{ id: 'info', label: 'Общая информация' },
|
||||
{ id: 'documents', label: 'Документы' },
|
||||
{ id: 'requests', label: 'Заявки' },
|
||||
]
|
||||
|
||||
function formatDateTime(value: string | null): string {
|
||||
if (!value) return '—'
|
||||
const d = new Date(value)
|
||||
if (Number.isNaN(d.getTime())) return '—'
|
||||
return d.toLocaleString('ru-RU')
|
||||
}
|
||||
|
||||
export function AdminOrganizationPage() {
|
||||
const { isAuthenticated, isLoading: isAuthLoading } = useAdminAuth()
|
||||
const { organizationId } = useParams<{ organizationId: string }>()
|
||||
const navigate = useNavigate()
|
||||
const { data: org, isLoading, isError } = useOrganization(organizationId)
|
||||
const [notice, setNotice] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState<Tab>('info')
|
||||
const { form, setField, handleSubmit, isSaving, error } = useOrganizationForm(
|
||||
org,
|
||||
organizationId ?? '',
|
||||
() => setNotice(true),
|
||||
)
|
||||
|
||||
if (isAuthLoading) return null
|
||||
if (!isAuthenticated) return <AdminLoginForm />
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<header className={styles.header}>
|
||||
<button className={styles.back} type="button" onClick={() => navigate(ROUTES.ADMIN)}>
|
||||
← Назад к списку
|
||||
</button>
|
||||
<h1 className={styles.title}>{org ? org.name : 'Юридическое лицо'}</h1>
|
||||
</header>
|
||||
|
||||
{isLoading && <div className={styles.state}>Загрузка...</div>}
|
||||
{isError && <div className={styles.state}>Не удалось загрузить организацию</div>}
|
||||
|
||||
{org && (
|
||||
<div className={styles.tabs}>
|
||||
{TABS.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
type="button"
|
||||
className={`${styles.tab} ${activeTab === tab.id ? styles.tabActive : ''}`}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{org && activeTab === 'info' && (
|
||||
<form className={styles.form} onSubmit={handleSubmit}>
|
||||
<section className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>Реквизиты</h2>
|
||||
<div className={styles.grid}>
|
||||
<FormField label="Наименование" value={form.name} onChange={setField('name')} placeholder="ООО «Ромашка»" required />
|
||||
<FormField label="Краткое наименование" value={form.short_name} onChange={setField('short_name')} placeholder="Ромашка" />
|
||||
<FormField label="ИНН" value={org.inn} readOnly icon="lock" />
|
||||
<FormField label="ОГРН" value={form.ogrn} onChange={setField('ogrn')} placeholder="1027700132195" />
|
||||
<FormField label="КПП" value={form.kpp} onChange={setField('kpp')} placeholder="770801001" />
|
||||
<FormField label="Статус" value={form.status} onChange={setField('status')} placeholder="active" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>Адреса</h2>
|
||||
<div className={styles.grid}>
|
||||
<FormField label="Юридический адрес" value={form.legal_address} onChange={setField('legal_address')} placeholder="г. Москва, ул. Тверская, д. 1" />
|
||||
<FormField label="Фактический адрес" value={form.actual_address} onChange={setField('actual_address')} placeholder="г. Москва, ул. Тверская, д. 1" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>Контакты</h2>
|
||||
<div className={styles.grid}>
|
||||
<FormField label="Контактное лицо" value={form.contact_person} onChange={setField('contact_person')} placeholder="Иванов Иван Иванович" />
|
||||
<FormField label="Контактный телефон" type="tel" value={form.contact_phone} onChange={setField('contact_phone')} placeholder="+7 (999) 000-00-00" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h2 className={styles.sectionTitle}>Системная информация</h2>
|
||||
<div className={styles.grid}>
|
||||
<FormField label="ID организации" value={org.id} readOnly icon="lock" />
|
||||
<FormField label="ID пользователя" value={org.user_id} readOnly icon="lock" />
|
||||
<FormField label="KYC" value={org.kyc_verified ? 'Подтверждён' : 'Не подтверждён'} readOnly />
|
||||
<FormField label="Дата KYC" value={formatDateTime(org.kyc_verified_at)} readOnly />
|
||||
<FormField label="Кошельки" value={org.has_wallets ? 'Есть' : 'Нет'} readOnly />
|
||||
<FormField label="Создано" value={formatDateTime(org.created_at)} readOnly />
|
||||
<FormField label="Обновлено" value={formatDateTime(org.updated_at)} readOnly />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{error && <p className={styles.error}>{error}</p>}
|
||||
|
||||
<div className={styles.actions}>
|
||||
<PrimaryButton label={isSaving ? 'Сохранение...' : 'Сохранить изменения'} disabled={isSaving} />
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{org && activeTab === 'documents' && (
|
||||
<div className={styles.tabPanel}>
|
||||
<OrganizationDocuments orgId={org.id} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{org && activeTab === 'requests' && (
|
||||
<div className={styles.tabPanel}>
|
||||
<OrganizationPurchaseRequests orgId={org.id} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{notice && (
|
||||
<Notification
|
||||
status="success"
|
||||
message="Изменения сохранены"
|
||||
onClose={() => setNotice(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
src/pages/admin/index.ts
Normal file
1
src/pages/admin/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { AdminPage } from './ui/AdminPage'
|
||||
84
src/pages/admin/ui/AdminPage.module.css
Normal file
84
src/pages/admin/ui/AdminPage.module.css
Normal file
@@ -0,0 +1,84 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-deep);
|
||||
padding: 40px 48px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto 36px;
|
||||
}
|
||||
|
||||
.greeting {
|
||||
font-size: clamp(28px, 4vw, 40px);
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #fff);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.logout {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.1));
|
||||
color: var(--text-secondary, rgba(255, 255, 255, 0.7));
|
||||
border-radius: 10px;
|
||||
height: 40px;
|
||||
padding: 0 18px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.logout:hover {
|
||||
background: rgba(255, 90, 90, 0.12);
|
||||
border-color: rgba(255, 90, 90, 0.3);
|
||||
color: #ff5a5a;
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary, #fff);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.addBtn {
|
||||
background: linear-gradient(135deg, var(--grad-edge, #4a6dff), var(--grad-center, #6f4aff));
|
||||
border: none;
|
||||
color: #fff;
|
||||
border-radius: 12px;
|
||||
height: 44px;
|
||||
padding: 0 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: filter 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.addBtn:hover {
|
||||
filter: brightness(1.12);
|
||||
box-shadow: 0 6px 20px rgba(74, 109, 255, 0.35);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page {
|
||||
padding: 28px 20px;
|
||||
}
|
||||
}
|
||||
77
src/pages/admin/ui/AdminPage.tsx
Normal file
77
src/pages/admin/ui/AdminPage.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import { useState } from 'react'
|
||||
import { useAdminAuth, useAdminLogout, useCreateOrganizationWallets } from '@features/admin'
|
||||
import type { Organization } from '@features/admin'
|
||||
import { Notification } from '@shared/ui'
|
||||
import { AdminLoginForm } from '@widgets/admin-login-form'
|
||||
import { LegalEntitiesTable } from '@widgets/legal-entities-table'
|
||||
import { AddLegalEntityModal } from '@widgets/add-legal-entity-modal'
|
||||
import styles from './AdminPage.module.css'
|
||||
|
||||
type NotificationState = { message: string; status: 'success' | 'error' | 'warning' }
|
||||
|
||||
export function AdminPage() {
|
||||
const { isAuthenticated, isLoading } = useAdminAuth()
|
||||
const logout = useAdminLogout()
|
||||
const createWallets = useCreateOrganizationWallets()
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [notification, setNotification] = useState<NotificationState | null>(null)
|
||||
|
||||
// After a legal entity is created we immediately provision its wallets.
|
||||
// The page stays mounted (unlike the modal), so these mutate callbacks fire reliably.
|
||||
function handleCreated(organization: Organization) {
|
||||
setNotification({ status: 'success', message: 'Юридическое лицо добавлено' })
|
||||
createWallets.mutate(organization.id, {
|
||||
onSuccess: (wallets) => {
|
||||
setNotification({
|
||||
status: 'success',
|
||||
message: `Кошельки созданы (${wallets.length})`,
|
||||
})
|
||||
},
|
||||
onError: () => {
|
||||
setNotification({
|
||||
status: 'warning',
|
||||
message: 'Юридическое лицо создано, но кошельки создать не удалось',
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (isLoading) return null
|
||||
if (!isAuthenticated) return <AdminLoginForm />
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<header className={styles.header}>
|
||||
<h1 className={styles.greeting}>Привет, Марк!</h1>
|
||||
<button className={styles.logout} type="button" onClick={() => logout.mutate()}>
|
||||
Выйти
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<section className={styles.content}>
|
||||
<div className={styles.toolbar}>
|
||||
<h2 className={styles.sectionTitle}>Юридические лица</h2>
|
||||
<button className={styles.addBtn} type="button" onClick={() => setModalOpen(true)}>
|
||||
+ Добавить юридическое лицо
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<LegalEntitiesTable />
|
||||
</section>
|
||||
|
||||
<AddLegalEntityModal
|
||||
open={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onCreated={handleCreated}
|
||||
/>
|
||||
|
||||
{notification && (
|
||||
<Notification
|
||||
status={notification.status}
|
||||
message={notification.message}
|
||||
onClose={() => setNotification(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
src/pages/bridge/index.ts
Normal file
1
src/pages/bridge/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { BridgePage } from './ui/BridgePage'
|
||||
12
src/pages/bridge/ui/BridgePage.module.css
Normal file
12
src/pages/bridge/ui/BridgePage.module.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
.content {
|
||||
padding: 32px 20px;
|
||||
}
|
||||
}
|
||||
14
src/pages/bridge/ui/BridgePage.tsx
Normal file
14
src/pages/bridge/ui/BridgePage.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { BridgeForm } from '@widgets/bridge-form'
|
||||
import { SwapBridgeTabs } from '@widgets/swap-bridge-tabs'
|
||||
import styles from './BridgePage.module.css'
|
||||
|
||||
export function BridgePage() {
|
||||
return (
|
||||
<>
|
||||
<SwapBridgeTabs active="bridge" />
|
||||
<div className={styles.content}>
|
||||
<BridgeForm />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
1
src/pages/converter-test/index.ts
Normal file
1
src/pages/converter-test/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ConverterTestPage } from './ui/ConverterTestPage'
|
||||
133
src/pages/converter-test/ui/ConverterTestPage.module.css
Normal file
133
src/pages/converter-test/ui/ConverterTestPage.module.css
Normal file
@@ -0,0 +1,133 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 24px;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: clamp(32px, 4vw, 48px);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 12px;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
.formCol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 12px;
|
||||
color: var(--highlight);
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
/* Панель условий / комиссии */
|
||||
.infoCol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.infoTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.infoRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14px 18px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--glass-border);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.infoRow[data-accent] {
|
||||
border-color: var(--grad-center);
|
||||
background: rgba(91, 61, 184, 0.12);
|
||||
}
|
||||
|
||||
.infoLabel {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.infoValue {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.submitBtn {
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
padding: 18px;
|
||||
border-radius: 12px;
|
||||
background: var(--grad-center);
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.submitBtn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wrap {
|
||||
padding: 28px 20px;
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
105
src/pages/converter-test/ui/ConverterTestPage.tsx
Normal file
105
src/pages/converter-test/ui/ConverterTestPage.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { useState } from 'react'
|
||||
import { FormField } from '@shared/ui'
|
||||
import styles from './ConverterTestPage.module.css'
|
||||
|
||||
const MIN_ORDER = 500_000
|
||||
const APPROX_RATE = 0.03 // примерная комиссия 3% для крупных заявок
|
||||
|
||||
const ru = (n: number) => n.toLocaleString('ru-RU', { maximumFractionDigits: 0 })
|
||||
|
||||
export function ConverterTestPage() {
|
||||
const [amount, setAmount] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
const [contact, setContact] = useState('')
|
||||
|
||||
const numAmount = Number(amount.replace(/\D/g, '')) || 0
|
||||
const belowMin = numAmount > 0 && numAmount < MIN_ORDER
|
||||
const commission = numAmount * APPROX_RATE
|
||||
|
||||
const handleAmountChange = (value: string) => {
|
||||
const digits = value.replace(/\D/g, '')
|
||||
setAmount(digits ? ru(Number(digits)) : '')
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
// Тестовая страница — заявка никуда не отправляется.
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<form className={styles.wrap} onSubmit={handleSubmit}>
|
||||
<div className={styles.header}>
|
||||
<h1 className={styles.title}>Оставить заявку</h1>
|
||||
<p className={styles.subtitle}>
|
||||
Конвертация крупных объёмов по индивидуальному курсу. Оставьте заявку —
|
||||
менеджер свяжется с вами, подтвердит актуальный курс и сопроводит сделку.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
<div className={styles.formCol}>
|
||||
<FormField
|
||||
label="Объём заявки, ₽"
|
||||
type="text"
|
||||
value={amount}
|
||||
onChange={handleAmountChange}
|
||||
placeholder="от 500 000"
|
||||
/>
|
||||
{belowMin && (
|
||||
<p className={styles.hint}>
|
||||
Минимальный объём заявки — {ru(MIN_ORDER)} ₽
|
||||
</p>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
label="Как к вам обращаться"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
placeholder="Имя"
|
||||
/>
|
||||
|
||||
<FormField
|
||||
label="Email или телефон для связи"
|
||||
type="text"
|
||||
value={contact}
|
||||
onChange={setContact}
|
||||
placeholder="example@mail.ru / +7 900 000-00-00"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoCol}>
|
||||
<div className={styles.infoTitle}>УСЛОВИЯ</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Минимальный объём</span>
|
||||
<span className={styles.infoValue}>{ru(MIN_ORDER)} ₽</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Примерная комиссия</span>
|
||||
<span className={styles.infoValue}>{(APPROX_RATE * 100).toFixed(0)} %</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow} data-accent>
|
||||
<span className={styles.infoLabel}>Комиссия с объёма</span>
|
||||
<span className={styles.infoValue}>
|
||||
{numAmount > 0 ? `≈ ${ru(commission)} ₽` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className={styles.note}>
|
||||
Итоговая комиссия рассчитывается индивидуально и зависит от объёма,
|
||||
валюты и направления сделки.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" className={styles.submitBtn} disabled={belowMin}>
|
||||
Оставить заявку
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +1,15 @@
|
||||
import { useMe } from '@features/auth'
|
||||
import { Spinner } from '@shared/ui'
|
||||
import { ConverterSection } from '@widgets/converter-page'
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { WalletHeader } from '@widgets/wallet-header'
|
||||
import styles from './ConverterPage.module.css'
|
||||
import { LegalConverterPage } from './LegalConverterPage'
|
||||
|
||||
export function ConverterPage() {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<WalletHeader />
|
||||
<main className={styles.main}>
|
||||
<ConverterSection />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
const { data, isLoading } = useMe()
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner fullscreen size="lg" label="Загрузка данных аккаунта" />
|
||||
}
|
||||
|
||||
const isLegal = !!data && data.account_type !== 'individual'
|
||||
return isLegal ? <LegalConverterPage /> : <ConverterSection />
|
||||
}
|
||||
|
||||
125
src/pages/converter/ui/LegalConverterPage.module.css
Normal file
125
src/pages/converter/ui/LegalConverterPage.module.css
Normal file
@@ -0,0 +1,125 @@
|
||||
.wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 24px;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: clamp(32px, 4vw, 48px);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 12px;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
.formCol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 12px;
|
||||
color: var(--highlight);
|
||||
margin-top: -12px;
|
||||
}
|
||||
|
||||
/* Панель условий / комиссии */
|
||||
.infoCol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.infoTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.infoRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 14px 18px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--glass-border);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.infoRow[data-accent] {
|
||||
border-color: var(--grad-center);
|
||||
background: rgba(91, 61, 184, 0.12);
|
||||
}
|
||||
|
||||
.infoLabel {
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.infoValue {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.submitBtn {
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
padding: 18px;
|
||||
border-radius: 12px;
|
||||
background: var(--grad-center);
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.submitBtn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.wrap {
|
||||
padding: 28px 20px;
|
||||
}
|
||||
|
||||
.body {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
153
src/pages/converter/ui/LegalConverterPage.tsx
Normal file
153
src/pages/converter/ui/LegalConverterPage.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import { useState } from 'react'
|
||||
import { FormField, Select } from '@shared/ui'
|
||||
import styles from './LegalConverterPage.module.css'
|
||||
|
||||
const MIN_ORDER = 500_000
|
||||
|
||||
// Чем дольше пользователь готов ждать, тем ниже комиссия сервиса.
|
||||
const TERM_OPTIONS = [
|
||||
{ days: 3, rate: 0.05 },
|
||||
{ days: 4, rate: 0.04636 },
|
||||
{ days: 5, rate: 0.04273 },
|
||||
{ days: 6, rate: 0.03909 },
|
||||
{ days: 7, rate: 0.03545 },
|
||||
{ days: 8, rate: 0.03182 },
|
||||
{ days: 9, rate: 0.02818 },
|
||||
{ days: 10, rate: 0.02455 },
|
||||
{ days: 11, rate: 0.02091 },
|
||||
{ days: 12, rate: 0.01727 },
|
||||
{ days: 13, rate: 0.01364 },
|
||||
{ days: 14, rate: 0.01 },
|
||||
] as const
|
||||
|
||||
const ru = (n: number) => n.toLocaleString('ru-RU', { maximumFractionDigits: 0 })
|
||||
|
||||
const dayLabel = (days: number) => {
|
||||
const mod10 = days % 10
|
||||
const mod100 = days % 100
|
||||
if (mod10 === 1 && mod100 !== 11) return `${days} день`
|
||||
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 10 || mod100 >= 20)) return `${days} дня`
|
||||
return `${days} дней`
|
||||
}
|
||||
|
||||
export function LegalConverterPage() {
|
||||
const [amount, setAmount] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
const [contact, setContact] = useState('')
|
||||
const [days, setDays] = useState<number>(TERM_OPTIONS[0].days)
|
||||
|
||||
const numAmount = Number(amount.replace(/\D/g, '')) || 0
|
||||
const belowMin = numAmount > 0 && numAmount < MIN_ORDER
|
||||
|
||||
const rate = TERM_OPTIONS.find((o) => o.days === days)?.rate ?? TERM_OPTIONS[0].rate
|
||||
const commission = numAmount * rate
|
||||
const total = numAmount + commission
|
||||
|
||||
const handleAmountChange = (value: string) => {
|
||||
const digits = value.replace(/\D/g, '')
|
||||
setAmount(digits ? ru(Number(digits)) : '')
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
// Бэкенд пока не подключён — заявка никуда не отправляется.
|
||||
}
|
||||
|
||||
return (
|
||||
<form className={styles.wrap} onSubmit={handleSubmit}>
|
||||
<div className={styles.header}>
|
||||
<h1 className={styles.title}>Оставить заявку</h1>
|
||||
<p className={styles.subtitle}>
|
||||
Конвертация крупных объёмов по индивидуальному курсу. Оставьте заявку —
|
||||
менеджер свяжется с вами, подтвердит актуальный курс и сопроводит сделку.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.body}>
|
||||
<div className={styles.formCol}>
|
||||
<FormField
|
||||
label="Объём заявки, ₽"
|
||||
type="text"
|
||||
value={amount}
|
||||
onChange={handleAmountChange}
|
||||
placeholder="от 500 000"
|
||||
/>
|
||||
{belowMin && (
|
||||
<p className={styles.hint}>
|
||||
Минимальный объём заявки — {ru(MIN_ORDER)} ₽
|
||||
</p>
|
||||
)}
|
||||
|
||||
<Select
|
||||
id="term"
|
||||
label="Срок ожидания операции"
|
||||
value={days}
|
||||
onChange={setDays}
|
||||
options={TERM_OPTIONS.map((o) => ({
|
||||
value: o.days,
|
||||
label: `${dayLabel(o.days)} — комиссия ${(o.rate * 100).toFixed(3)} %`,
|
||||
}))}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
label="Как к вам обращаться"
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={setName}
|
||||
placeholder="Имя"
|
||||
/>
|
||||
|
||||
<FormField
|
||||
label="Email или телефон для связи"
|
||||
type="text"
|
||||
value={contact}
|
||||
onChange={setContact}
|
||||
placeholder="example@mail.ru / +7 900 000-00-00"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoCol}>
|
||||
<div className={styles.infoTitle}>УСЛОВИЯ</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Минимальный объём</span>
|
||||
<span className={styles.infoValue}>{ru(MIN_ORDER)} ₽</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Срок ожидания</span>
|
||||
<span className={styles.infoValue}>{dayLabel(days)}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Ставка комиссии</span>
|
||||
<span className={styles.infoValue}>{(rate * 100).toFixed(3)} %</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow}>
|
||||
<span className={styles.infoLabel}>Сумма комиссии</span>
|
||||
<span className={styles.infoValue}>
|
||||
{numAmount > 0 ? `≈ ${ru(commission)} ₽` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.infoRow} data-accent>
|
||||
<span className={styles.infoLabel}>Итого к оплате</span>
|
||||
<span className={styles.infoValue}>
|
||||
{numAmount > 0 ? `≈ ${ru(total)} ₽` : '—'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className={styles.note}>
|
||||
Итоговая комиссия рассчитывается индивидуально и зависит от объёма,
|
||||
валюты и направления сделки.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" className={styles.submitBtn} disabled={belowMin}>
|
||||
Оставить заявку
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
import { Navigate } from 'react-router-dom'
|
||||
import { useMe } from '@features/auth'
|
||||
import { ROUTES } from '@shared/config/routes'
|
||||
import { KycWidget } from '@widgets/kyc-verification'
|
||||
import styles from './KycPage.module.css'
|
||||
|
||||
export function KycPage() {
|
||||
const { data, isLoading } = useMe()
|
||||
|
||||
if (isLoading) return null
|
||||
if (data?.kyc_verified) return <Navigate to={ROUTES.PROFILE} replace />
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<KycWidget />
|
||||
|
||||
1
src/pages/politika-cookie/index.ts
Normal file
1
src/pages/politika-cookie/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { PolitikaCookiePage } from './ui/PolitikaCookiePage'
|
||||
115
src/pages/politika-cookie/ui/PolitikaCookiePage.module.css
Normal file
115
src/pages/politika-cookie/ui/PolitikaCookiePage.module.css
Normal file
@@ -0,0 +1,115 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: disc;
|
||||
margin-left: 20px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.list strong {
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin: 5px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.example {
|
||||
padding: 10px 15px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 3px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
font-style: italic;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.warning {
|
||||
padding: 15px;
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
274
src/pages/politika-cookie/ui/PolitikaCookiePage.tsx
Normal file
274
src/pages/politika-cookie/ui/PolitikaCookiePage.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './PolitikaCookiePage.module.css'
|
||||
|
||||
export function PolitikaCookiePage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>ПОЛИТИКА ИСПОЛЬЗОВАНИЯ ФАЙЛОВ COOKIE</h1>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Общие положения и терминология</h3>
|
||||
<p>
|
||||
Настоящая Политика использования файлов cookie устанавливает порядок обработки файлов cookie и содержащихся в них персональных данных ООО «БИТФОРС» при использовании пользователями интернет-ресурса https://bitforce-foundation.ru.
|
||||
</p>
|
||||
<p>
|
||||
Файлы cookie — это текстовые файлы небольшого размера, которые устанавливаются на пользовательское устройство при посещении интернет-ресурса или совершении на нем определенных действий. Файлы cookie остаются сохраненными на устройстве даже после покидания ресурса, что позволяет «узнавать» пользователя при последующих посещениях.
|
||||
</p>
|
||||
<p>
|
||||
К персональным данным относится не сам файл cookie, а его содержимое — уникальные идентификаторы, IP-адреса, информация о предпочтениях пользователя и другие данные, позволяющие прямо или косвенно идентифицировать физическое лицо.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Оператор персональных данных</h3>
|
||||
<p>Оператором персональных данных, содержащихся в файлах cookie, является:</p>
|
||||
<div className={styles.info}>
|
||||
<p>ООО «БИТФОРС»</p>
|
||||
<p>ИНН: 9810001062</p>
|
||||
<p>ОГРН: 1257800060990</p>
|
||||
<p>Юридический адрес: 196246, город Санкт-Петербург, Московское ш, д. 25 к. 1 литера В, помещ. 3-н</p>
|
||||
</div>
|
||||
<p>
|
||||
Оператор определяет цели обработки персональных данных, их состав, а также действия с персональными данными, включая случаи использования сторонних файлов cookie.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Категории файлов cookie и их назначение</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>1. Строго необходимые (технические) файлы cookie</h4>
|
||||
<p>
|
||||
Данные файлы обеспечивают работу интернет-ресурса и предоставление необходимого уровня сервиса: авторизацию, навигацию, отображение контента в соответствии с параметрами устройства, обеспечение безопасности.
|
||||
</p>
|
||||
<p>
|
||||
Обработка таких файлов cookie осуществляется на основании п. 5 ч. 1 ст. 6 ФЗ № 152 (заключение и исполнение договора). Согласие на использование строго необходимых файлов cookie не требуется.
|
||||
</p>
|
||||
<p className={styles.example}>Примеры: файлы сессий (PHPSESSID), настройки безопасности, файлы аутентификации.</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>2. Функциональные файлы cookie</h4>
|
||||
<p>
|
||||
Используются для запоминания пользовательских предпочтений и персонализации взаимодействия с сайтом: сохранение выбранного языка, региона, настроек отображения, размера шрифта.
|
||||
</p>
|
||||
<p>
|
||||
Обработка осуществляется на основании согласия субъекта персональных данных, поскольку данная обработка не является строго необходимой для функционирования сайта.
|
||||
</p>
|
||||
<p className={styles.example}>Примеры: настройки языка интерфейса, предпочтения отображения, настройки доступности.</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3. Аналитические файлы cookie</h4>
|
||||
<p>
|
||||
Собирают информацию о взаимодействии пользователей с интернет-ресурсом для анализа его использования, выявления популярных разделов, обнаружения ошибок и улучшения пользовательского опыта. Могут содержать персональные данные, включая IP-адреса пользователей.
|
||||
</p>
|
||||
<p>
|
||||
Обработка осуществляется на основании согласия субъекта персональных данных.
|
||||
</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4. Маркетинговые файлы cookie</h4>
|
||||
<p>
|
||||
Используются для отслеживания пользователей в целях персонализированной рекламы, анализа эффективности рекламных кампаний, ретаргетинга.
|
||||
</p>
|
||||
<p>
|
||||
Обработка осуществляется исключительно на основании согласия субъекта персональных данных.
|
||||
</p>
|
||||
<p className={styles.example}>Примеры: пиксели социальных сетей, рекламные идентификаторы, файлы ретаргетинга.</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Правовые основания обработки персональных данных</h3>
|
||||
<p>Обработка персональных данных, содержащихся в файлах cookie, осуществляется на следующих правовых основаниях:</p>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<strong>Согласие субъекта персональных данных</strong> — для функциональных, аналитических и маркетинговых файлов cookie
|
||||
</li>
|
||||
<li>
|
||||
<strong>Заключение и исполнение договора</strong> — для строго необходимых файлов cookie, обеспечивающих работу интернет-ресурса
|
||||
</li>
|
||||
<li>
|
||||
<strong>Законные интересы оператора</strong> — в исключительных случаях, когда отсутствуют иные основания
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Порядок получения согласия</h3>
|
||||
<h4 className={styles.subSectionTitle}>Принципы получения согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Согласие должно быть получено до начала обработки персональных данных</li>
|
||||
<li>Информация об использовании файлов cookie размещается на первом уровне интернет-ресурса</li>
|
||||
<li>Предоставляется возможность выбора категорий файлов cookie</li>
|
||||
<li>Используются активные формулировки вместо пассивных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Критерии действительного согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<strong>Добровольность</strong> — согласие дается по свободной воле субъекта
|
||||
</li>
|
||||
<li>
|
||||
<strong>Конкретность</strong> — четко определены цели обработки
|
||||
</li>
|
||||
<li>
|
||||
<strong>Информированность</strong> — предоставлена полная информация об обработке
|
||||
</li>
|
||||
<li>
|
||||
<strong>Однозначность</strong> — согласие выражено в недвусмысленной форме
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Сторонние файлы cookie</h3>
|
||||
<h4 className={styles.subSectionTitle}>Использование сторонних сервисов:</h4>
|
||||
<p>Наш интернет-ресурс использует файлы cookie сторонних сервисов, включая:</p>
|
||||
<ul className={styles.list}>
|
||||
<li>Яндекс.Метрика (ООО «ЯНДЕКС», Россия)</li>
|
||||
<li>Социальные сети и сервисы интеграции</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Обеспечение защиты:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получено согласие на передачу</li>
|
||||
<li>Применяются дополнительные меры защиты данных</li>
|
||||
<li>Контролируется соблюдение принципов обработки персональных данных получателями</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Сроки обработки и хранения</h3>
|
||||
<h4 className={styles.subSectionTitle}>Категории по срокам хранения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Сеансовые cookie — удаляются автоматически при закрытии браузера</li>
|
||||
<li>Постоянные cookie — хранятся установленный период или до удаления пользователем</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Конкретные сроки:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Необходимые файлы cookie — до 12 месяцев</li>
|
||||
<li>Функциональные файлы cookie — до 12 месяцев</li>
|
||||
<li>Аналитические файлы cookie — до 24 месяцев</li>
|
||||
<li>Маркетинговые файлы cookie — до 24 месяцев</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
По истечении установленных сроков файлы cookie удаляются автоматически. Пользователь может удалить файлы cookie досрочно через настройки браузера или отозвать согласие на их обработку.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Права субъектов персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>Право на информацию:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получение информации о обработке персональных данных</li>
|
||||
<li>Сведения о правовых основаниях и целях обработки</li>
|
||||
<li>Информация о сроках обработки и составе данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Право на доступ:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получение подтверждения факта обработки</li>
|
||||
<li>Ознакомление с обрабатываемыми персональными данными</li>
|
||||
<li>Получение информации об источниках персональных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Право на уточнение, блокирование, удаление:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Требование уточнения неточных данных</li>
|
||||
<li>Блокирование недостоверных данных</li>
|
||||
<li>Удаление незаконно полученных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Право на отзыв согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Отзыв согласия в любое время</li>
|
||||
<li>Прекращение обработки после отзыва согласия</li>
|
||||
<li>Сохранение права на обжалование действий оператора</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Способы управления файлами cookie</h3>
|
||||
<h4 className={styles.subSectionTitle}>Управление через настройки сайта:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Использование баннера согласия на файлы cookie</li>
|
||||
<li>Изменение настроек в любое время через интерфейс сайта</li>
|
||||
<li>Отзыв согласия на использование отдельных категорий файлов cookie</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Управление через браузер:</h4>
|
||||
<p>Большинство браузеров позволяют контролировать файлы cookie:</p>
|
||||
<ul className={styles.list}>
|
||||
<li>Блокировка — запрет установки новых файлов cookie</li>
|
||||
<li>Удаление — очистка существующих файлов cookie</li>
|
||||
<li>Уведомления — получение предупреждений при установке файлов cookie</li>
|
||||
<li>Селективная настройка — разрешение файлов cookie только для определенных сайтов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Инструкции для популярных браузеров:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Google Chrome: Настройки → Конфиденциальность и безопасность → Файлы cookie</li>
|
||||
<li>Mozilla Firefox: Настройки → Приватность и Защита → Файлы cookie</li>
|
||||
<li>Safari: Настройки → Конфиденциальность → Файлы cookie</li>
|
||||
<li>Microsoft Edge: Настройки → Файлы cookie и разрешения сайтов</li>
|
||||
</ul>
|
||||
|
||||
<p className={styles.warning}>
|
||||
Блокировка необходимых файлов cookie может привести к ограничению функциональности интернет-ресурса.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Меры безопасности</h3>
|
||||
<p>
|
||||
Оператор применяет правовые, организационные и технические меры для защиты персональных данных:
|
||||
</p>
|
||||
<h4 className={styles.subSectionTitle}>Правовые меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Назначение ответственного за организацию обработки персональных данных</li>
|
||||
<li>Ознакомление сотрудников с требованиями законодательства</li>
|
||||
<li>Заключение соглашений о неразглашении персональных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Организационные меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Определение перечня лиц, допущенных к обработке персональных данных</li>
|
||||
<li>Установление правил доступа к персональным данным</li>
|
||||
<li>Контроль за соблюдением требований по защите персональных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Технические меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Использование средств защиты информации</li>
|
||||
<li>Применение криптографических средств защиты</li>
|
||||
<li>Обеспечение целостности и доступности персональных данных</li>
|
||||
<li>Регулярное обновление систем защиты информации</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Контактная информация и обращения</h3>
|
||||
<p>Для реализации прав субъекта персональных данных обращайтесь к нам:</p>
|
||||
<div className={styles.info}>
|
||||
<p>ООО «БИТФОРС»</p>
|
||||
<p>ИНН: 9810001062</p>
|
||||
<p>ОГРН: 1257800060990</p>
|
||||
<p>Юридический адрес: 196246, город Санкт-Петербург, Московское ш, д. 25 к. 1 литера В, помещ. 3-н</p>
|
||||
<p>Email компании: company@bitforcefoundation.ru</p>
|
||||
</div>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>Порядок рассмотрения обращений:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Срок рассмотрения обращений — до 30 дней с момента получения</li>
|
||||
<li>Обращения рассматриваются в письменной форме</li>
|
||||
<li>Ответ направляется способом, указанным в обращении</li>
|
||||
<li>При отказе в удовлетворении требований указываются мотивированные основания</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
1
src/pages/politika-personalnyh-dannyh/index.ts
Normal file
1
src/pages/politika-personalnyh-dannyh/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { PolitikaPage } from './ui/PolitikaPage'
|
||||
138
src/pages/politika-personalnyh-dannyh/ui/PolitikaPage.module.css
Normal file
138
src/pages/politika-personalnyh-dannyh/ui/PolitikaPage.module.css
Normal file
@@ -0,0 +1,138 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, #b5b0cc);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.definitions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.definition {
|
||||
padding: 15px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: disc;
|
||||
margin-left: 20px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.goalsList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.goal {
|
||||
padding: 15px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.goal strong {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.goal ul {
|
||||
list-style: disc;
|
||||
margin-left: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.goal li {
|
||||
margin-bottom: 6px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.contacts {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.08));
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
300
src/pages/politika-personalnyh-dannyh/ui/PolitikaPage.tsx
Normal file
300
src/pages/politika-personalnyh-dannyh/ui/PolitikaPage.tsx
Normal file
@@ -0,0 +1,300 @@
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './PolitikaPage.module.css'
|
||||
|
||||
export function PolitikaPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>ПОЛИТИКА ОБРАБОТКИ ПЕРСОНАЛЬНЫХ ДАННЫХ</h1>
|
||||
<h2 className={styles.subtitle}>ООО «БИТФОРС»</h2>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>1. Общие положения</h3>
|
||||
<p>
|
||||
Настоящая Политика обработки персональных данных разработана в соответствии с Федеральным законом от 27.07.2006 № 152-ФЗ «О персональных данных» и определяет порядок обработки персональных данных и меры по обеспечению безопасности персональных данных, предпринимаемые ООО «БИТФОРС».
|
||||
</p>
|
||||
<p>
|
||||
Оператор ставит своей важнейшей целью и условием осуществления своей деятельности соблюдение прав и свобод человека и гражданина при обработке его персональных данных, в том числе защиты права на неприкосновенность частной жизни, личную и семейную тайну.
|
||||
</p>
|
||||
<p>
|
||||
Настоящая Политика действует в отношении всех персональных данных, которые обрабатываются Оператором с использованием средств автоматизации и без использования таких средств.
|
||||
</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>1.4. Основные понятия</h4>
|
||||
<div className={styles.definitions}>
|
||||
<div className={styles.definition}>
|
||||
<strong>Автоматизированная обработка персональных данных</strong> — обработка персональных данных с помощью средств вычислительной техники.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Обработка персональных данных</strong> — любое действие или совокупность действий, совершаемых с использованием средств автоматизации или без использования таких средств с персональными данными, включая сбор, запись, систематизацию, накопление, хранение, уточнение, извлечение, использование, передачу, обезличивание, блокирование, удаление, уничтожение.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Оператор</strong> — юридическое или физическое лицо, организующие и осуществляющие обработку персональных данных.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Персональные данные</strong> — любая информация, относящаяся к прямо или косвенно определенному или определяемому физическому лицу.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Пользователь</strong> — любой посетитель веб-сайта https://bitforce-foundation.ru.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>2. Сведения об операторе</h3>
|
||||
<ul className={styles.list}>
|
||||
<li>Полное наименование: Общество с ограниченной ответственностью «БИТФОРС»</li>
|
||||
<li>Сокращенное наименование: ООО «БИТФОРС»</li>
|
||||
<li>ИНН: 9810001062</li>
|
||||
<li>ОГРН: 1257800060990</li>
|
||||
<li>Юридический адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н</li>
|
||||
<li>Электронная почта: company@bitforcefoundation.ru</li>
|
||||
<li>Веб-сайт: https://bitforce-foundation.ru</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>3. Общие цели обработки персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>3.1.1. Основная деятельность:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Предоставление услуг по конвертации иного имущества</li>
|
||||
<li>Осуществление операций на криптовалютных рынках</li>
|
||||
<li>Предоставление услуг в области блокчейн технологий</li>
|
||||
<li>Обеспечение функционирования интернет-платформы и мобильных приложений</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.1.2. Обеспечение безопасности:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Предотвращение мошенничества и отмывания денежных средств</li>
|
||||
<li>Обеспечение безопасности платежных операций</li>
|
||||
<li>Выполнение требований по противодействию легализации доходов</li>
|
||||
<li>Идентификация и верификация клиентов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.1.3. Соблюдение законодательства:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Исполнение требований российского и международного законодательства</li>
|
||||
<li>Взаимодействие с контролирующими и правоохранительными органами</li>
|
||||
<li>Ведение обязательной отчетности и документооборота</li>
|
||||
<li>Соблюдение налогового законодательства</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>4. Цели сбора персональных данных</h3>
|
||||
<div className={styles.goalsList}>
|
||||
<div className={styles.goal}>
|
||||
<strong>Регистрация и идентификация пользователей:</strong>
|
||||
<ul>
|
||||
<li>Создание учетной записи на веб-сайте</li>
|
||||
<li>Верификация личности в соответствии с требованиями законодательства</li>
|
||||
<li>Подтверждение права на осуществление операций</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={styles.goal}>
|
||||
<strong>Обработка платежей и финансовых операций:</strong>
|
||||
<ul>
|
||||
<li>Осуществление операций по конвертации криптовалют</li>
|
||||
<li>Проведение расчетов и переводов денежных средств</li>
|
||||
<li>Ведение учета и истории транзакций</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className={styles.goal}>
|
||||
<strong>Коммуникация с клиентами:</strong>
|
||||
<ul>
|
||||
<li>Предоставление технической поддержки</li>
|
||||
<li>Уведомления о состоянии операций и счетов</li>
|
||||
<li>Информирование об изменениях в условиях предоставления услуг</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>5. Правовые основания обработки персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>5.1.1. Согласие субъекта персональных данных:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обработка персональных данных в маркетинговых целях</li>
|
||||
<li>Использование файлов cookie и метрик</li>
|
||||
<li>Персонализация сервисов и предложений</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>5.1.2. Необходимость исполнения договора:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Регистрация и ведение учетных записей пользователей</li>
|
||||
<li>Осуществление финансовых операций и переводов</li>
|
||||
<li>Предоставление доступа к платформе и сервисам</li>
|
||||
<li>Техническая поддержка и обслуживание клиентов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>5.1.3. Соблюдение правовой обязанности:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Выполнение требований валютного законодательства</li>
|
||||
<li>Противодействие легализации доходов, полученных преступным путем</li>
|
||||
<li>Соблюдение требований по налоговому учету и отчетности</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>6. Объем и категории обрабатываемых персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>6.1.1. Пользователи веб-сайта и мобильного приложения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Зарегистрированные пользователи</li>
|
||||
<li>Посетители сайта без регистрации</li>
|
||||
<li>Потенциальные клиенты</li>
|
||||
<li>Бывшие клиенты</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2.1. Идентификационные данные:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Фамилия, имя, отчество</li>
|
||||
<li>Дата рождения</li>
|
||||
<li>Гражданство</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2.3. Контактная информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Номера телефонов (мобильный, домашний, рабочий)</li>
|
||||
<li>Адреса электронной почты</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2.4. Финансовая информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Номера банковских счетов и карт</li>
|
||||
<li>Реквизиты кошельков криптовалют</li>
|
||||
<li>История операций и транзакций</li>
|
||||
<li>Данные о доходах и источниках средств</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2.5. Техническая информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>IP-адреса устройств</li>
|
||||
<li>Данные о браузере и операционной системе</li>
|
||||
<li>Файлы cookie и локальное хранилище</li>
|
||||
<li>Логи действий на сайте</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>7. Порядок и условия обработки персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>7.1. Принципы обработки персональных данных:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обработка осуществляется на законной и справедливой основе</li>
|
||||
<li>Обработка ограничивается достижением конкретных, заранее определенных целей</li>
|
||||
<li>Содержание и объем данных соответствуют заявленным целям</li>
|
||||
<li>Обрабатываемые персональные данные являются точными и актуальными</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>7.4. Сроки обработки персональных данных:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Персональные данные обрабатываются в течение времени, необходимого для достижения целей</li>
|
||||
<li>После достижения целей персональные данные подлежат уничтожению или обезличиванию</li>
|
||||
<li>Сроки хранения определяются требованиями законодательства</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>7.5. Места обработки персональных данных:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Основные серверы и хранилища данных расположены на территории Российской Федерации</li>
|
||||
<li>Резервные копии могут храниться в дата-центрах на территории РФ</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>8. Актуализация, исправление, удаление и уничтожение персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>8.2.2. Процедура исправления:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Рассмотрение запроса в течение 30 дней</li>
|
||||
<li>Проверка обоснованности требования об исправлении</li>
|
||||
<li>Внесение изменений во все информационные системы</li>
|
||||
<li>Уведомление субъекта о проведенных исправлениях</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.3.2. Процедура удаления:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Проверка наличия законных оснований для продолжения обработки</li>
|
||||
<li>Удаление из всех информационных систем и баз данных</li>
|
||||
<li>Удаление резервных копий (кроме архивных)</li>
|
||||
<li>Уведомление субъекта о выполненном удалении</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>9. Ответы на запросы субъектов персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>9.1.1. Право на информацию:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Подтверждение факта обработки персональных данных</li>
|
||||
<li>Правовые основания и цели обработки</li>
|
||||
<li>Применяемые способы обработки</li>
|
||||
<li>Наименование и местонахождение оператора</li>
|
||||
<li>Лица, имеющие доступ к персональным данным</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.2.2. Сроки рассмотрения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Срок рассмотрения запроса составляет 30 дней с момента получения</li>
|
||||
<li>Срок может быть продлен на 30 дней при большом объеме информации</li>
|
||||
<li>О продлении срока субъект уведомляется в течение 30 дней</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.4. Плата за предоставление информации:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Первый запрос в течение года обрабатывается бесплатно</li>
|
||||
<li>За повторные запросы может взиматься плата в размере расходов</li>
|
||||
<li>Субъект уведомляется о размере платы до предоставления информации</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>10. Обеспечение безопасности персональных данных</h3>
|
||||
<h4 className={styles.subSectionTitle}>10.1. Правовые меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Назначение ответственного за организацию обработки персональных данных</li>
|
||||
<li>Принятие локальных актов по вопросам обработки персональных данных</li>
|
||||
<li>Ознакомление работников с требованиями законодательства</li>
|
||||
<li>Применение мер ответственности за нарушение требований</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>10.3. Технические меры:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Предотвращение несанкционированного доступа к персональным данным</li>
|
||||
<li>Своевременное обнаружение фактов несанкционированного доступа</li>
|
||||
<li>Возможность незамедлительного восстановления персональных данных</li>
|
||||
<li>Постоянный контроль за обеспечением уровня защищенности</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>10.4. Конкретные технические решения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Использование сертифицированных средств защиты информации</li>
|
||||
<li>Шифрование персональных данных при передаче и хранении</li>
|
||||
<li>Применение межсетевых экранов и систем обнаружения вторжений</li>
|
||||
<li>Резервное копирование и обеспечение отказоустойчивости</li>
|
||||
<li>Антивирусная защита и обновление программного обеспечения</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>12. Заключительные положения</h3>
|
||||
<h4 className={styles.subSectionTitle}>12.2. Жалобы и обращения:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Субъекты персональных данных могут обратиться к Оператору по вопросам обработки</li>
|
||||
<li>Жалобы рассматриваются в установленном законом порядке</li>
|
||||
<li>При неурегулировании разногласий возможно обращение в Роскомнадзор или суд</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>12.4. Контактная информация для обращений:</h4>
|
||||
<p className={styles.contacts}>
|
||||
Почтовый адрес: 196246, г. Санкт-Петербург, Московское ш., д. 25, к. 1, лит. В, пом. 3-н<br />
|
||||
Электронная почта: company@bitforcefoundation.ru
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
34
src/pages/profile/ui/IndividualFields.tsx
Normal file
34
src/pages/profile/ui/IndividualFields.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { MeResponse } from '@features/auth'
|
||||
import { FormField } from '@shared/ui'
|
||||
import { ProfileSection } from '@widgets/profile'
|
||||
import styles from './ProfilePage.module.css'
|
||||
|
||||
interface Props {
|
||||
data: MeResponse
|
||||
fullName: string
|
||||
phone: string
|
||||
onPhoneChange: (value: string) => void
|
||||
onPhoneBlur: () => void
|
||||
}
|
||||
|
||||
export function IndividualFields({ data, fullName, phone, onPhoneChange, onPhoneBlur }: Props) {
|
||||
return (
|
||||
<>
|
||||
<ProfileSection title="Личные данные">
|
||||
<div className={styles.grid2}>
|
||||
<FormField label="Полное ФИО" value={fullName} placeholder="Например: Иванов Иван Иванович" readOnly />
|
||||
<FormField label="Адрес электронной почты" value={data.email ?? ''} type="email" icon="check" placeholder="example@mail.ru" readOnly />
|
||||
<FormField label="Серия и номер паспорта" value={data.passport_data ?? ''} placeholder="0000 000000" readOnly />
|
||||
<FormField label="Номер телефона" value={phone} onChange={onPhoneChange} onBlur={onPhoneBlur} type="tel" placeholder="+7 (999) 000-00-00" />
|
||||
</div>
|
||||
</ProfileSection>
|
||||
|
||||
<ProfileSection title="Верификация">
|
||||
<div className={styles.grid2}>
|
||||
<FormField label="ИНН" value={data.inn ?? ''} readOnly icon="lock" placeholder="000000000000" />
|
||||
<FormField label="ID аккаунта" value={data.id ?? ''} readOnly icon="lock" placeholder="ECSA-00000000" />
|
||||
</div>
|
||||
</ProfileSection>
|
||||
</>
|
||||
)
|
||||
}
|
||||
47
src/pages/profile/ui/LegalEntityFields.tsx
Normal file
47
src/pages/profile/ui/LegalEntityFields.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { MeResponse } from '@features/auth'
|
||||
import { FormField } from '@shared/ui'
|
||||
import { ProfileSection } from '@widgets/profile'
|
||||
import styles from './ProfilePage.module.css'
|
||||
|
||||
interface Props {
|
||||
data: MeResponse
|
||||
}
|
||||
|
||||
// Legal-account fields. Organization data lives in the nested `legal_entity`
|
||||
// object; person-level fields are null on these accounts.
|
||||
// All read-only — organization data is managed admin-side, not by the user.
|
||||
export function LegalEntityFields({ data }: Props) {
|
||||
const le = data.legal_entity
|
||||
if (!le) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<ProfileSection title="Данные организации">
|
||||
<div className={styles.grid2}>
|
||||
<FormField label="Наименование" value={le.name ?? ''} placeholder="ООО «Ромашка»" readOnly />
|
||||
<FormField label="Краткое наименование" value={le.short_name ?? ''} placeholder="Ромашка" readOnly />
|
||||
<FormField label="ИНН" value={le.inn ?? ''} readOnly icon="lock" placeholder="000000000000" />
|
||||
<FormField label="ОГРН" value={le.ogrn ?? ''} placeholder="1027700132195" readOnly />
|
||||
<FormField label="КПП" value={le.kpp ?? ''} placeholder="770801001" readOnly />
|
||||
<FormField label="Адрес электронной почты" value={data.email ?? ''} type="email" icon="check" placeholder="org@mail.ru" readOnly />
|
||||
</div>
|
||||
</ProfileSection>
|
||||
|
||||
<ProfileSection title="Адреса">
|
||||
<div className={styles.grid2}>
|
||||
<FormField label="Юридический адрес" value={le.legal_address ?? ''} placeholder="г. Москва, ул. Тверская, д. 1" readOnly />
|
||||
<FormField label="Фактический адрес" value={le.actual_address ?? ''} placeholder="г. Москва, ул. Тверская, д. 1" readOnly />
|
||||
</div>
|
||||
</ProfileSection>
|
||||
|
||||
<ProfileSection title="Контакты и верификация">
|
||||
<div className={styles.grid2}>
|
||||
<FormField label="Контактное лицо" value={le.contact_person ?? ''} placeholder="Иванов Иван Иванович" readOnly />
|
||||
<FormField label="Контактный телефон" value={le.contact_phone ?? ''} type="tel" placeholder="+7 (999) 000-00-00" readOnly />
|
||||
<FormField label="Статус" value={le.status ?? ''} placeholder="active" readOnly />
|
||||
<FormField label="ID аккаунта" value={data.id ?? ''} readOnly icon="lock" placeholder="ECSA-00000000" />
|
||||
</div>
|
||||
</ProfileSection>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +1,64 @@
|
||||
import { useMe } from '@features/auth'
|
||||
import { Button, FormField } from '@shared/ui'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useMe, useUpdatePhone } from '@features/auth'
|
||||
import { usePortfolio, useWalletAddresses } from '@features/wallet'
|
||||
import { ROUTES } from '@shared/config/routes'
|
||||
import { Button, FormField, Notification } from '@shared/ui'
|
||||
import { WalletHeader } from '@widgets/wallet-header'
|
||||
import { ProfileAvatar, ProfileSection } from '@widgets/profile'
|
||||
import { IndividualFields } from './IndividualFields'
|
||||
import { LegalEntityFields } from './LegalEntityFields'
|
||||
import styles from './ProfilePage.module.css'
|
||||
|
||||
export function ProfilePage() {
|
||||
const { data } = useMe()
|
||||
const { data: portfolio, isLoading: isPortfolioLoading } = usePortfolio()
|
||||
const { data: walletAddresses } = useWalletAddresses()
|
||||
const updatePhone = useUpdatePhone()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [phone, setPhone] = useState('')
|
||||
const [savedPhone, setSavedPhone] = useState('')
|
||||
const [notification, setNotification] = useState<{ message: string; status: 'success' | 'error' } | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.phone != null) {
|
||||
setPhone(data.phone)
|
||||
setSavedPhone(data.phone)
|
||||
}
|
||||
}, [data?.phone])
|
||||
|
||||
function handlePhoneChange(value: string) {
|
||||
setPhone(value.replace(/[^\d+\s()-]/g, ''))
|
||||
}
|
||||
|
||||
function handlePhoneBlur() {
|
||||
const next = phone.trim()
|
||||
if (next === savedPhone || updatePhone.isPending) return
|
||||
updatePhone.mutate(next, {
|
||||
onSuccess: () => {
|
||||
setSavedPhone(next)
|
||||
setNotification({ status: 'success', message: 'Номер телефона обновлён' })
|
||||
},
|
||||
onError: () => {
|
||||
setNotification({ status: 'error', message: 'Не удалось обновить номер телефона' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const capitalize = (s: string | null) => (s ? s[0].toUpperCase() + s.slice(1).toLowerCase() : '')
|
||||
const fullName = data
|
||||
? [data.last_name, data.first_name, data.middle_name].filter(Boolean).join(' ')
|
||||
? [data.last_name, data.first_name, data.middle_name].filter(Boolean).map(capitalize).join(' ')
|
||||
: ''
|
||||
|
||||
const isLegal = !!data && data.account_type !== 'individual'
|
||||
const displayName = isLegal ? (data?.legal_entity?.name ?? '') : fullName
|
||||
|
||||
const userBalance =
|
||||
isPortfolioLoading || !portfolio || portfolio.totalUsd == null
|
||||
? '$—'
|
||||
: `$${portfolio.totalUsd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<WalletHeader />
|
||||
@@ -23,28 +71,24 @@ export function ProfilePage() {
|
||||
<div className={styles.profileTop}>
|
||||
<ProfileAvatar />
|
||||
<div className={styles.userInfo}>
|
||||
<span className={styles.userName}>{fullName}</span>
|
||||
<span className={styles.userBalance}>$245.00</span>
|
||||
<span className={styles.userBalanceRub}>≈ 22 340,50 ₽</span>
|
||||
<span className={styles.userName}>{displayName}</span>
|
||||
<span className={styles.userBalance}>{userBalance}</span>
|
||||
{/* <span className={styles.userBalanceRub}>≈ 22 340,50 ₽</span> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.sections}>
|
||||
<ProfileSection title="Личные данные">
|
||||
<div className={styles.grid2}>
|
||||
<FormField label="Полное ФИО" value={fullName} placeholder="Например: Иванов Иван Иванович" />
|
||||
<FormField label="Адрес электронной почты" value={data?.email ?? ''} type="email" icon="check" placeholder="example@mail.ru" readOnly />
|
||||
<FormField label="Серия и номер паспорта" value={data?.passport_data ?? ''} placeholder="0000 000000" readOnly />
|
||||
<FormField label="Номер телефона" value={data?.phone ?? ''} type="tel" icon="check" placeholder="+7 (999) 000-00-00" readOnly />
|
||||
</div>
|
||||
</ProfileSection>
|
||||
|
||||
<ProfileSection title="Верификация">
|
||||
<div className={styles.grid2}>
|
||||
<FormField label="ИНН" value={data?.inn ?? ''} readOnly icon="lock" placeholder="000000000000" />
|
||||
<FormField label="ID аккаунта" value={data?.id ?? ''} readOnly icon="lock" placeholder="ECSA-00000000" />
|
||||
</div>
|
||||
</ProfileSection>
|
||||
{data && (isLegal ? (
|
||||
<LegalEntityFields data={data} />
|
||||
) : (
|
||||
<IndividualFields
|
||||
data={data}
|
||||
fullName={fullName}
|
||||
phone={phone}
|
||||
onPhoneChange={handlePhoneChange}
|
||||
onPhoneBlur={handlePhoneBlur}
|
||||
/>
|
||||
))}
|
||||
|
||||
<ProfileSection
|
||||
title="Безопасность"
|
||||
@@ -56,7 +100,16 @@ export function ProfilePage() {
|
||||
}
|
||||
>
|
||||
<div className={styles.grid1}>
|
||||
<FormField label="Адрес ERC-20" readOnly icon="lock" value={data?.erc20 ?? ''} placeholder="0x0000000000000000000000000000000000000000" />
|
||||
{walletAddresses?.map(({ chain, address }) => (
|
||||
<FormField
|
||||
key={chain}
|
||||
label={`Адрес ${chain}`}
|
||||
readOnly
|
||||
icon="lock"
|
||||
value={address}
|
||||
placeholder="—"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ProfileSection>
|
||||
|
||||
@@ -66,11 +119,18 @@ export function ProfilePage() {
|
||||
<span className={styles.mnemonicIcon}>🔑</span>
|
||||
<span className={styles.mnemonicText}>Сид-фраза из 12 слов для восстановления кошелька</span>
|
||||
</div>
|
||||
<Button variant="danger">⚠ Показать мнемонику</Button>
|
||||
<Button variant="danger" onClick={() => navigate(ROUTES.SEED_PHRASE)}>⚠ Показать мнемонику</Button>
|
||||
</div>
|
||||
</ProfileSection>
|
||||
</div>
|
||||
</main>
|
||||
{notification && (
|
||||
<Notification
|
||||
status={notification.status}
|
||||
message={notification.message}
|
||||
onClose={() => setNotification(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
1
src/pages/publichnaya-oferta/index.ts
Normal file
1
src/pages/publichnaya-oferta/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { PublichnayaOfertaPage } from './ui/PublichnayaOfertaPage'
|
||||
@@ -0,0 +1,89 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, #b5b0cc);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.definitions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.definition {
|
||||
padding: 15px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.requisites {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.08));
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.requisites p {
|
||||
margin: 5px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.requisites {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
200
src/pages/publichnaya-oferta/ui/PublichnayaOfertaPage.tsx
Normal file
200
src/pages/publichnaya-oferta/ui/PublichnayaOfertaPage.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './PublichnayaOfertaPage.module.css'
|
||||
|
||||
export function PublichnayaOfertaPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>ПУБЛИЧНЫЙ ДОГОВОР ОФЕРТЫ</h1>
|
||||
|
||||
<h2 className={styles.subtitle}>ООО БИТФОРС</h2>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Агентский договор</h3>
|
||||
<p>
|
||||
Настоящая оферта на заключение агентского договора (далее – Оферта, Договор) является публичным предложением Общества с ограниченной ответственностью «БИТФОРС», заключить договор на условиях и в порядке, определенных настоящей Офертой.
|
||||
</p>
|
||||
<p>
|
||||
Акцепт оферты производится в соответствии с пунктом 2 статьи 437 Гражданского кодекса Российской Федерации и равносилен заключению агентского договора в письменной форме.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Основные понятия и определения действующего договора</h3>
|
||||
<div className={styles.definitions}>
|
||||
<div className={styles.definition}>
|
||||
<strong>Агент</strong> – юридическое лицо или индивидуальный предприниматель, зарегистрированный на территории Российской Федерации, в установленном действующим законодательством порядке.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Принципал</strong> – сторона агентского договора, по поручению которой агент осуществляет юридические и иные действия от своего имени, но за счет принципала либо от имени и за счет принципала.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Агентский договор</strong> – соглашение, по которому агент обязуется за вознаграждение совершать по поручению принципала юридические и иные действия от своего имени, но за счет принципала либо от имени и за счет принципала в соответствии с п. 1 ст. 1005 Гражданского Кодекса Российской Федерации.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Личный кабинета Агента</strong> – ресурс, размещенный на сайте Принципала, предназначенный для взаимодействия Агента и Принципала.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Отчетный период</strong> – период для взаиморасчетов с Агентом, равный одному календарному кварталу с даты активации любой из услуг, предоставляемой Принципалу.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Отчет о сумме начислений (Отчет)</strong> – отчет, формируемый в Личном кабинете Агента на основании данных систем учета Принципала.
|
||||
</div>
|
||||
<div className={styles.definition}>
|
||||
<strong>Оферта (Договор)</strong> – настоящий документ, который отражает предложение и намерение ООО «БИТФОРС» считать заключенным договор с лицом, которым будет принято предложение на условиях, изложенных ниже.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>1. Акцепт оферты и заключение агентского договора</h3>
|
||||
<p>
|
||||
Акцепт настоящей Оферты и заключение Агентского договора осуществляется Принципалом в процессе регистрации в Личном кабинете Принципала (на сайте Агента), при прочтении текста настоящей Оферты, путем проставления специальной отметки (галочки) напротив фразы «Я ознакомился с Офертой и принимаю ее условия» и нажатия кнопки «Подписать».
|
||||
</p>
|
||||
<p>
|
||||
Особый порядок принятия условий Оферты путем проставления специальной отметки (галочки) определяется интерфейсом Личного кабинета Принципала. Принципал не может зарегистрироваться в Личном кабинете и получить к нему доступ без подтверждения принятия условий Оферты.
|
||||
</p>
|
||||
<p>
|
||||
Принимая Оферту, Принципал подтверждает, что прочел и полностью согласен с документами, размещенными на сайте в разделе, предназначенном для Принципала, которые являются неотъемлемой частью настоящей Оферты (Договора) и обязательны для исполнения Сторонами.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>2. Общие положения</h3>
|
||||
<p>
|
||||
Публикуемые на сайте Агента документы (формы, требования, правила и т.п.), устанавливающие порядок и условия выполнения действий, предусмотренных настоящим Договором, являются неотъемлемой частью настоящего Договора и обязательны для исполнения Сторонами. Принципал обязан использовать формы документов, утвержденных Агентом, и не вправе вносить в них какие-либо изменения или дополнения.
|
||||
</p>
|
||||
<p>
|
||||
Агент обязуется уведомлять Принципала обо всех изменениях в документах, связанных с исполнением настоящего Договора, путем направления электронных сообщений (через Личный кабинет или на электронную почту Принципала) или размещением уведомлений об изменениях на сайте Агентов в разделе, предназначенном для размещения объявлений.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>3. Предмет договора</h3>
|
||||
<p>
|
||||
По настоящему Договору Принципал поручает, а Агент принимает на себя обязательство совершать от имени и за счет Принципала указанные действия, а Принципал обязуется выплатить Агенту вознаграждение за совершенные действия.
|
||||
</p>
|
||||
<p>
|
||||
По настоящему Договору Агент совершает следующие действия:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Консультирование Принципала об услугах Агента, включая, помимо прочего, порядок активации и оказания услуг, работу в Личном кабинете Принципала и иные дополнительные услуги, оказываемые Агентом;</li>
|
||||
<li>Совершение сделок и иных юридических действий Агентом от своего имени, но за счёт Принципала.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Настоящий Договор действует на территории Российской Федерации и иного иностранного государства.
|
||||
</p>
|
||||
<p>
|
||||
Права и обязанности по сделкам, совершенным Агентом во исполнение настоящего Договора, возникают непосредственно у Принципала.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>4. Права и обязанности сторон</h3>
|
||||
<p>
|
||||
Агент обязуется совершать действия, составляющие предмет настоящего Договора, в соответствии с законными интересами Принципала, сообщать Принципалу по его требованию все сведения о ходе исполнения настоящего Договора, передавать Принципалу в течение 7 рабочих дней имущество, полученное по сделкам.
|
||||
</p>
|
||||
<p>
|
||||
Агент несет ответственность за сохранность документов и персональных данных, переданных ему Принципалом для исполнения настоящего Договора.
|
||||
</p>
|
||||
<p>
|
||||
Принципал обязан без промедления принять отчет Агента, все предоставленные им документы, обеспечить Агента документами и материалами, необходимыми для выполнения настоящего Договора, возместить Агенту понесенные расходы и выплатить обусловленное Договором агентское вознаграждение.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>5. Агентское вознаграждение и порядок оплаты</h3>
|
||||
<p>
|
||||
Сумма вознаграждения Агента по настоящему Договору составляет:
|
||||
</p>
|
||||
<ul>
|
||||
<li>8% от 5 000 до 30 000 рублей</li>
|
||||
<li>6% от 30 000 до 100 000 рублей</li>
|
||||
<li>4% от 100 000 до 600 000 рублей</li>
|
||||
</ul>
|
||||
<p>
|
||||
Вознаграждение выплачивается Агенту с момента подписания настоящего Договора об исполнении поручения Агентом от своего имени, но за счет Принципала.
|
||||
</p>
|
||||
<p>
|
||||
Принципал возмещает следующие расходы Агента в сумме не более 30 000 рублей на оплату банковских услуг и иных комиссий.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>6. Ответственность сторон</h3>
|
||||
<p>
|
||||
В случае нарушения Агентом сроков, установленных Договором для передачи Принципалу полученного имущества, Принципал вправе предъявить требование об уплате неустойки в размере 0,1% от непереданной суммы за каждый день просрочки.
|
||||
</p>
|
||||
<p>
|
||||
В случае нарушения Принципалом сроков уплаты вознаграждения или возмещения расходов, Агент вправе предъявить требование об уплате неустойки в размере 0,1% от не уплаченной в срок суммы за каждый день просрочки.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>7. Форс-мажор</h3>
|
||||
<p>
|
||||
Стороны освобождаются от ответственности за частичное или полное неисполнение обязательств по настоящему Договору, если это неисполнение явилось следствием возникших после заключения настоящего Договора обстоятельств непреодолимой силы.
|
||||
</p>
|
||||
<p>
|
||||
При наступлении форс-мажорных обстоятельств каждая Сторона должна без промедления известить о них в письменном виде другую Сторону с указанием характера обстоятельств и их влияния на исполнение обязательств.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>8. Конфиденциальность</h3>
|
||||
<p>
|
||||
Стороны принимают все необходимые меры для того, чтобы их сотрудники, агенты, правопреемники без предварительного согласия другой Стороны не информировали третьих лиц о конфиденциальной информации и персональных данных Сторон настоящего Договора.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>9. Изменение и прекращение договора</h3>
|
||||
<p>
|
||||
Настоящий договор вступает в силу с момента его подписания и действует до момента исполнения сторонами своих обязательств по настоящему договору.
|
||||
</p>
|
||||
<p>
|
||||
Настоящий Договор может быть изменен или прекращен по письменному соглашению Сторон, а также в других случаях, предусмотренных законодательством Российской Федерации.
|
||||
</p>
|
||||
<p>
|
||||
Принципал вправе в любое время отказаться от исполнения настоящего Договора путем направления письменного уведомления Агенту за 3 рабочих дня.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>10. Заключительные положения</h3>
|
||||
<p>
|
||||
Ни одна из сторон не вправе передавать свои права и обязанности по настоящему договору третьим лицам без согласия другой стороны.
|
||||
</p>
|
||||
<p>
|
||||
Сообщения Стороны могут направлять по факсу, электронной почте или другим способом связи при условии, что он позволяет достоверно установить, от кого исходило сообщение и кому оно адресовано.
|
||||
</p>
|
||||
<p>
|
||||
Споры, вытекающие из настоящего Договора, разрешаются в досудебном порядке. При неурегулировании возникших разногласий спор разрешается в Арбитражном суде г. Санкт–Петербурга и Ленинградской области с обязательным соблюдением претензионного порядка.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Реквизиты сторон</h3>
|
||||
<div className={styles.requisites}>
|
||||
<p>Общество с ограниченной ответственностью «БИТФОРС»</p>
|
||||
<p>196246, г. Санкт-Петербург, Московский р-н, Московское шоссе, д.25к1 литера в, помещ. 3-Н</p>
|
||||
<p>ИНН / КПП: 9810001062 / 781001001</p>
|
||||
<p>ОГРН: 1257800060990</p>
|
||||
<p>ОКПО / ОКАТО / ОКТМО: 68342261 / 40284000000 / 40377000000</p>
|
||||
<p>Руководитель: Кленин Михаил Васильевич</p>
|
||||
<p>Электронная почта: company@bitforcefoundation.ru</p>
|
||||
<p>Наименование банка: ФИЛИАЛ "САНКТ-ПЕТЕРБУРГСКИЙ" АО "АЛЬФА-БАНК"</p>
|
||||
<p>Корреспондентский счет: 30101810600000000786</p>
|
||||
<p>БИК: 044030786</p>
|
||||
<p>Расчетный счет: 40702810632250004861</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
1
src/pages/reestr-pd-rkn/index.ts
Normal file
1
src/pages/reestr-pd-rkn/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { ReestryPage } from './ui/ReestryPage'
|
||||
115
src/pages/reestr-pd-rkn/ui/ReestryPage.module.css
Normal file
115
src/pages/reestr-pd-rkn/ui/ReestryPage.module.css
Normal file
@@ -0,0 +1,115 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, #b5b0cc);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.linkBlock {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-radius: 8px;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 15px 40px;
|
||||
background: var(--interactive, #4a6dff);
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid var(--interactive, #4a6dff);
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: transparent;
|
||||
color: var(--interactive, #4a6dff);
|
||||
}
|
||||
|
||||
.operatorInfo {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.operatorInfo p {
|
||||
margin: 10px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.linkBlock {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.button {
|
||||
padding: 12px 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
74
src/pages/reestr-pd-rkn/ui/ReestryPage.tsx
Normal file
74
src/pages/reestr-pd-rkn/ui/ReestryPage.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './ReestryPage.module.css'
|
||||
|
||||
export function ReestryPage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>Реестр операторов персональных данных</h1>
|
||||
<h2 className={styles.subtitle}>ООО «БИТФОРС»</h2>
|
||||
|
||||
<section className={styles.section}>
|
||||
<p className={styles.description}>
|
||||
Информация об операторе персональных данных размещена в реестре операторов персональных данных Федеральной службы по надзору в сфере связи, информационных технологий и массовых коммуникаций (Роскомнадзор).
|
||||
</p>
|
||||
|
||||
<p className={styles.info}>
|
||||
Вы можете просмотреть информацию об операторе в реестре Роскомнадзора, перейдя по ссылке ниже:
|
||||
</p>
|
||||
|
||||
<div className={styles.linkBlock}>
|
||||
<a
|
||||
href="https://pd.rkn.gov.ru/operators-registry/operators-list/?act=search&name_full=%D0%91%D0%B8%D1%82%D1%84%D0%BE%D1%80%D1%81&inn=9810001062®n="
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.button}
|
||||
>
|
||||
Открыть реестр Роскомнадзора
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p className={styles.info}>
|
||||
Реестр содержит информацию об операторах персональных данных, включая сведения о целях и методах обработки персональных данных, а также меры по обеспечению безопасности персональных данных.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Информация об операторе</h3>
|
||||
<div className={styles.operatorInfo}>
|
||||
<p>
|
||||
<strong>Наименование:</strong> ООО «БИТФОРС»
|
||||
</p>
|
||||
<p>
|
||||
<strong>ИНН:</strong> 9810001062
|
||||
</p>
|
||||
<p>
|
||||
<strong>ОГРН:</strong> 1257800060990
|
||||
</p>
|
||||
<p>
|
||||
<strong>Юридический адрес:</strong> 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н
|
||||
</p>
|
||||
<p>
|
||||
<strong>Контактная информация:</strong> company@bitforcefoundation.ru
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>О Роскомнадзоре</h3>
|
||||
<p>
|
||||
Федеральная служба по надзору в сфере связи, информационных технологий и массовых коммуникаций (Роскомнадзор) — это федеральный орган исполнительной власти, осуществляющий функции по контролю и надзору в области персональных данных.
|
||||
</p>
|
||||
<p>
|
||||
Роскомнадзор ведет реестр операторов персональных данных в соответствии с требованиями Федерального закона «О персональных данных». Реестр является открытой информационной системой и доступен всем заинтересованным лицам.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
1
src/pages/register-test/index.ts
Normal file
1
src/pages/register-test/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { RegisterTestPage } from './ui/RegisterTestPage'
|
||||
227
src/pages/register-test/ui/RegisterTestPage.module.css
Normal file
227
src/pages/register-test/ui/RegisterTestPage.module.css
Normal file
@@ -0,0 +1,227 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--glass-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 24px;
|
||||
padding: 32px;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 24px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* Переключатель типа регистрации */
|
||||
.typeSwitch {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 14px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.typeOption {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: 12px 16px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: background 0.18s ease, color 0.18s ease;
|
||||
}
|
||||
|
||||
.typeOption:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.typeOptionActive {
|
||||
background: var(--interactive);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.twoCol {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px 24px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.leftCol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.rightCol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.codeHint {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Кнопка возврата на шаг ввода данных */
|
||||
.backButton {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
padding: 0;
|
||||
margin-bottom: 16px;
|
||||
cursor: pointer;
|
||||
transition: color 0.18s ease;
|
||||
}
|
||||
|
||||
.backButton:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Документы для юридического лица */
|
||||
.documents {
|
||||
margin-top: 24px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.documentsTitle {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.documentsSubtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.documentsList {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.documentItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.documentName {
|
||||
font-size: 13px;
|
||||
color: var(--text-primary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.attachButton {
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--interactive);
|
||||
padding: 8px 14px;
|
||||
border: 1px solid var(--interactive);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background 0.18s ease, color 0.18s ease;
|
||||
}
|
||||
|
||||
.attachButton:hover {
|
||||
background: var(--interactive);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.fileInput {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #ff5a5a;
|
||||
font-size: 13px;
|
||||
margin-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submitWrapper {
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.legal {
|
||||
text-align: center;
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.legal a {
|
||||
color: var(--interactive);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.legal a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.card {
|
||||
padding: 32px 20px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.twoCol {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.documentItem {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
155
src/pages/register-test/ui/RegisterTestPage.tsx
Normal file
155
src/pages/register-test/ui/RegisterTestPage.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import { useState } from 'react'
|
||||
import { FormField, PrimaryButton, Button } from '@shared/ui'
|
||||
import logo from '@shared/assets/logo-full-white.png'
|
||||
import styles from './RegisterTestPage.module.css'
|
||||
|
||||
type AccountType = 'individual' | 'legal'
|
||||
type Step = 'info' | 'documents'
|
||||
|
||||
const LEGAL_DOCUMENTS = [
|
||||
'Свидетельство о государственной регистрации (ОГРН)',
|
||||
'Свидетельство о постановке на учёт в налоговом органе (ИНН)',
|
||||
'Устав организации (действующая редакция)',
|
||||
'Решение/протокол о назначении руководителя',
|
||||
'Документ, подтверждающий полномочия лица, открывающего счёт',
|
||||
'Карточка с образцами подписей и оттиска печати',
|
||||
]
|
||||
|
||||
export function RegisterTestPage() {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [confirmPassword, setConfirmPassword] = useState('')
|
||||
const [verificationCode, setVerificationCode] = useState('')
|
||||
|
||||
const [accountType, setAccountType] = useState<AccountType>('individual')
|
||||
const [step, setStep] = useState<Step>('info')
|
||||
const isLegal = accountType === 'legal'
|
||||
|
||||
// Тестовая страница — без проверок и запросов. «Создать» просто ведёт на шаг 2.
|
||||
const handleInfoSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setStep('documents')
|
||||
}
|
||||
|
||||
const handleDocumentsSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
// Тестовая страница — отправки нет.
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
{step === 'info' ? (
|
||||
<form className={styles.card} onSubmit={handleInfoSubmit}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo} alt="ЭКСА" />
|
||||
</div>
|
||||
<h1 className={styles.title}>Создать кошелёк ЭКСА</h1>
|
||||
|
||||
{/* Выбор типа регистрации — перед всеми полями */}
|
||||
<div className={styles.typeSwitch} role="tablist" aria-label="Тип регистрации">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={!isLegal}
|
||||
className={`${styles.typeOption} ${!isLegal ? styles.typeOptionActive : ''}`}
|
||||
onClick={() => setAccountType('individual')}
|
||||
>
|
||||
Физическое лицо
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={isLegal}
|
||||
className={`${styles.typeOption} ${isLegal ? styles.typeOptionActive : ''}`}
|
||||
onClick={() => setAccountType('legal')}
|
||||
>
|
||||
Юридическое лицо
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={styles.twoCol}>
|
||||
<div className={styles.leftCol}>
|
||||
<FormField
|
||||
label={isLegal ? 'Введите корпоративный email' : 'Введите адрес электронной почты'}
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={setEmail}
|
||||
placeholder={isLegal ? 'name@company.ru' : 'example@mail.ru'}
|
||||
/>
|
||||
<FormField
|
||||
label="Придумайте пароль"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={setPassword}
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
<FormField
|
||||
label="Повторите пароль"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={setConfirmPassword}
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.rightCol}>
|
||||
<Button variant="ghost" type="button">
|
||||
Получить проверочный код
|
||||
</Button>
|
||||
<span className={styles.codeHint}>Код не пришёл</span>
|
||||
<FormField
|
||||
label="Ввести код"
|
||||
type="text"
|
||||
value={verificationCode}
|
||||
onChange={setVerificationCode}
|
||||
placeholder="000 000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.submitWrapper}>
|
||||
<PrimaryButton label="Создать" />
|
||||
</div>
|
||||
|
||||
<p className={styles.legal}>
|
||||
Нажимая «Создать», вы принимаете<br />
|
||||
<a href="#">Пользовательское соглашение</a> и <a href="#">Политику конфиденциальности</a>
|
||||
</p>
|
||||
</form>
|
||||
) : (
|
||||
/* Шаг 2: прикрепление документов (только юр. лицо) */
|
||||
<form className={styles.card} onSubmit={handleDocumentsSubmit}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo} alt="ЭКСА" />
|
||||
</div>
|
||||
|
||||
<button type="button" className={styles.backButton} onClick={() => setStep('info')}>
|
||||
← Назад к данным
|
||||
</button>
|
||||
|
||||
<h1 className={styles.title}>Прикрепите документы</h1>
|
||||
<p className={styles.documentsSubtitle}>
|
||||
Для открытия счёта юридическому лицу прикрепите сканы или фотографии
|
||||
следующих документов:
|
||||
</p>
|
||||
|
||||
<ul className={styles.documentsList}>
|
||||
{LEGAL_DOCUMENTS.map((doc) => (
|
||||
<li key={doc} className={styles.documentItem}>
|
||||
<span className={styles.documentName}>{doc}</span>
|
||||
<label className={styles.attachButton}>
|
||||
Прикрепить
|
||||
<input type="file" className={styles.fileInput} multiple />
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className={styles.submitWrapper}>
|
||||
<PrimaryButton label="Создать аккаунт" />
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
1
src/pages/restore-password/index.ts
Normal file
1
src/pages/restore-password/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { RestorePasswordPage } from './ui/RestorePasswordPage'
|
||||
@@ -0,0 +1,7 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
10
src/pages/restore-password/ui/RestorePasswordPage.tsx
Normal file
10
src/pages/restore-password/ui/RestorePasswordPage.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { RestorePasswordForm } from '@widgets/restore-password-form'
|
||||
import styles from './RestorePasswordPage.module.css'
|
||||
|
||||
export function RestorePasswordPage() {
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<RestorePasswordForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
import { WalletHeader } from '@widgets/wallet-header'
|
||||
import { SeedPhraseWidget } from '@widgets/seed-phrase'
|
||||
import { useRevealMnemonic } from '@features/wallet'
|
||||
import styles from './SeedPhrasePage.module.css'
|
||||
|
||||
const MOCK_WORDS = ['egg', 'phone', 'long', 'vibe', 'potato', 'soup', 'skirt', 'black', 'phase', 'word', 'num', 'cucumber']
|
||||
|
||||
export function SeedPhrasePage() {
|
||||
const { data: mnemonic, isLoading } = useRevealMnemonic()
|
||||
const words = mnemonic ? mnemonic.split(' ') : []
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<WalletHeader />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.glow} />
|
||||
<SeedPhraseWidget words={MOCK_WORDS} />
|
||||
{!isLoading && <SeedPhraseWidget words={words} />}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
||||
1
src/pages/soglasie-personalnyh-dannyh/index.ts
Normal file
1
src/pages/soglasie-personalnyh-dannyh/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { SoglasiePage } from './ui/SoglasiePage'
|
||||
127
src/pages/soglasie-personalnyh-dannyh/ui/SoglasiePage.module.css
Normal file
127
src/pages/soglasie-personalnyh-dannyh/ui/SoglasiePage.module.css
Normal file
@@ -0,0 +1,127 @@
|
||||
.main {
|
||||
padding: 40px 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: var(--bg-mid, #1b1547);
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary, #b5b0cc);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
border-bottom: 2px solid var(--interactive, #4a6dff);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style: disc;
|
||||
margin-left: 20px;
|
||||
line-height: 1.8;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.list strong {
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.info {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
margin: 15px 0;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.info p {
|
||||
margin: 8px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.contacts {
|
||||
padding: 20px;
|
||||
background: var(--glass-bg, rgba(255, 255, 255, 0.04));
|
||||
border-left: 4px solid var(--interactive, #4a6dff);
|
||||
border-radius: 4px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.contacts p {
|
||||
margin: 8px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.confirmation {
|
||||
padding: 10px 0;
|
||||
color: var(--text-primary, #ffffff);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subSectionTitle {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.info,
|
||||
.contacts {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
300
src/pages/soglasie-personalnyh-dannyh/ui/SoglasiePage.tsx
Normal file
300
src/pages/soglasie-personalnyh-dannyh/ui/SoglasiePage.tsx
Normal file
@@ -0,0 +1,300 @@
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { Header } from '@widgets/header'
|
||||
import styles from './SoglasiePage.module.css'
|
||||
|
||||
export function SoglasiePage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className={styles.main}>
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.title}>СОГЛАСИЕ НА ОБРАБОТКУ ПЕРСОНАЛЬНЫХ ДАННЫХ</h1>
|
||||
<h2 className={styles.subtitle}>ООО «БИТФОРС»</h2>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>Преамбула</h3>
|
||||
<p>
|
||||
Я, субъект персональных данных, действуя своей волей и в своем интересе, в соответствии с требованиями Федерального закона от 27.07.2006 № 152-ФЗ «О персональных данных», предоставляю ООО «БИТФОРС» согласие на обработку моих персональных данных на условиях и для целей, определенных настоящим Согласием.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>1. Сведения об операторе</h3>
|
||||
<div className={styles.info}>
|
||||
<p>Полное наименование: Общество с ограниченной ответственностью «БИТФОРС»</p>
|
||||
<p>ИНН: 9810001062</p>
|
||||
<p>ОГРН: 1257800060990</p>
|
||||
<p>Юридический адрес: 196246, город Санкт-Петербург, Московское шоссе, дом 25, корпус 1, литера В, помещение 3-н</p>
|
||||
<p>Электронная почта: company@bitforcefoundation.ru</p>
|
||||
<p>Веб-сайт: https://bitforce-foundation.ru</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>2. Правовые основания обработки</h3>
|
||||
<p>
|
||||
Настоящее согласие предоставляется на основании пункта 1 части 1 статьи 6 Федерального закона «О персональных данных» и является правовым основанием для обработки персональных данных Оператором.
|
||||
</p>
|
||||
<p>Согласие дается добровольно, своей волей и в своих интересах.</p>
|
||||
<p>
|
||||
Субъект персональных данных понимает последствия предоставления согласия, включая возможные риски, связанные с обработкой персональных данных.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>3. Цели обработки персональных данных</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.1. Основные цели:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Регистрация и ведение учетной записи на веб-сайте и в мобильном приложении</li>
|
||||
<li>Идентификация и верификация личности в соответствии с требованиями законодательства</li>
|
||||
<li>Предоставление услуг по обмену криптовалют и электронных денежных средств</li>
|
||||
<li>Проведение финансовых операций, переводов и расчетов</li>
|
||||
<li>Ведение учета и истории операций</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.2. Дополнительные цели:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обеспечение безопасности операций и предотвращение мошенничества</li>
|
||||
<li>Выполнение требований по противодействию легализации доходов</li>
|
||||
<li>Соблюдение требований валютного, налогового и иного применимого законодательства</li>
|
||||
<li>Предоставление технической поддержки и клиентского сервиса</li>
|
||||
<li>Рассылка уведомлений о состоянии операций и изменениях в условиях</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.3. Маркетинговые цели (при дополнительном согласии):</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Направление информационных и рекламных материалов</li>
|
||||
<li>Проведение маркетинговых исследований и опросов</li>
|
||||
<li>Персонализация предложений и услуг</li>
|
||||
<li>Анализ предпочтений и поведения для улучшения сервисов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>3.4. Аналитические цели:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Анализ использования веб-сайта и мобильного приложения</li>
|
||||
<li>Улучшение качества предоставляемых услуг</li>
|
||||
<li>Разработка новых продуктов и сервисов</li>
|
||||
<li>Создание статистических отчетов в обезличенном виде</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>4. Перечень персональных данных</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.1. Идентификационные данные:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Фамилия, имя, отчество</li>
|
||||
<li>Дата рождения</li>
|
||||
<li>Гражданство</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.2. Документы, удостоверяющие личность:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Серия и номер паспорта гражданина Российской Федерации</li>
|
||||
<li>Дата выдачи и код подразделения</li>
|
||||
<li>Адрес регистрации по месту жительства</li>
|
||||
<li>Цифровые копии (сканы) документов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.3. Контактная информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Номера телефонов (мобильный, домашний, рабочий)</li>
|
||||
<li>Адреса электронной почты</li>
|
||||
<li>Почтовые адреса (фактического проживания, для корреспонденции)</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.4. Финансовая информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Номера банковских счетов и реквизиты банковских карт</li>
|
||||
<li>Реквизиты криптовалютных кошельков и адресов</li>
|
||||
<li>Информация о доходах и источниках происхождения денежных средств</li>
|
||||
<li>История финансовых операций и транзакций</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.5. Техническая информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>IP-адреса устройств, с которых осуществляется доступ к сервисам</li>
|
||||
<li>Информация о браузере, операционной системе и устройстве</li>
|
||||
<li>Файлы cookie и данные локального хранилища</li>
|
||||
<li>Логи действий и история использования сервисов</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>4.6. Дополнительная информация:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Фотографии для процедур верификации</li>
|
||||
<li>Видеозаписи процедур видеоидентификации</li>
|
||||
<li>Биометрические данные (при использовании соответствующих технологий)</li>
|
||||
<li>Информация о семейном положении и профессиональной деятельности</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>5. Перечень действий с персональными данными</h3>
|
||||
<p>Согласие распространяется на следующие действия (операции) с персональными данными:</p>
|
||||
<ul className={styles.list}>
|
||||
<li>Сбор, запись и первичная обработка персональных данных</li>
|
||||
<li>Накопление и систематизация в базах данных</li>
|
||||
<li>Создание резервных копий и архивирование</li>
|
||||
<li>Извлечение, использование и анализ данных</li>
|
||||
<li>Уточнение, обновление и актуализация информации</li>
|
||||
<li>Передача данных третьим лицам</li>
|
||||
<li>Обезличивание и удаление данных</li>
|
||||
<li>Автоматизированная обработка и профилирование</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>6. Лица, которым могут быть переданы персональные данные</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.1. Сотрудники Оператора:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Уполномоченные сотрудники, непосредственно участвующие в обработке</li>
|
||||
<li>Сотрудники службы безопасности и комплаенса</li>
|
||||
<li>Сотрудники технической поддержки</li>
|
||||
<li>Руководящий состав в рамках их полномочий</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.2. Государственные и муниципальные органы:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Федеральная служба по финансовому мониторингу</li>
|
||||
<li>Федеральная налоговая служба</li>
|
||||
<li>Правоохранительные органы (при наличии законных требований)</li>
|
||||
<li>Суды и органы исполнения судебных решений</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>6.3. Партнеры и контрагенты:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Банки и платежные системы</li>
|
||||
<li>Операторы электронных денежных средств</li>
|
||||
<li>Поставщики технологических решений</li>
|
||||
<li>Аудиторские и консалтинговые организации</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>7. Сроки обработки персональных данных</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>7.1. Общие принципы:</h4>
|
||||
<p>
|
||||
Персональные данные обрабатываются в течение времени, необходимого для достижения целей обработки. После достижения целей данные подлежат уничтожению или обезличиванию.
|
||||
</p>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>7.2. Конкретные сроки обработки:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<strong>Данные активных клиентов:</strong> в течение всего периода отношений плюс 5 лет после прекращения
|
||||
</li>
|
||||
<li>
|
||||
<strong>Данные для идентификации:</strong> 5 лет с момента прекращения отношений
|
||||
</li>
|
||||
<li>
|
||||
<strong>Финансовая информация:</strong> 5 лет с даты совершения операции
|
||||
</li>
|
||||
<li>
|
||||
<strong>Маркетинговые данные:</strong> до отзыва согласия, но не более 3 лет
|
||||
</li>
|
||||
<li>
|
||||
<strong>Техническая информация:</strong> 1 год для безопасности, 6 месяцев для логов
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>8. Права субъекта персональных данных</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.1. Право на информацию:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получение подтверждения факта обработки персональных данных</li>
|
||||
<li>Получение информации о целях и способах обработки</li>
|
||||
<li>Информация о сроках обработки и составе данных</li>
|
||||
<li>Сведения о лицах, которым передаются данные</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.2. Право на доступ:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Получение копий обрабатываемых персональных данных</li>
|
||||
<li>Ознакомление с историей обработки и изменений</li>
|
||||
<li>Получение информации об источниках персональных данных</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.3. Право на исправление и удаление:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Требование исправления неточных или неполных данных</li>
|
||||
<li>Требование удаления персональных данных при наличии оснований</li>
|
||||
<li>Удаление данных после отзыва согласия</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.4. Право на отзыв согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Согласие может быть отозвано в любое время</li>
|
||||
<li>Отзыв оформляется в письменной форме</li>
|
||||
<li>После отзыва обработка прекращается в разумные сроки</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.5. Право на обжалование:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обращение к Оператору с жалобами на действия по обработке данных</li>
|
||||
<li>Обращение в Роскомнадзор или его территориальные органы</li>
|
||||
<li>Обращение в суд для защиты нарушенных прав</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>8.6. Порядок реализации прав:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Обращения направляются на адрес: company@bitforcefoundation.ru</li>
|
||||
<li>Обращения рассматриваются в течение 30 дней</li>
|
||||
<li>При необходимости срок может быть продлен на 30 дней</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h3 className={styles.sectionTitle}>9. Заключительные положения</h3>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.1. Действие согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Согласие действует с момента его предоставления</li>
|
||||
<li>Согласие действует до его отзыва или до достижения целей обработки</li>
|
||||
<li>При существенных изменениях целей требуется новое согласие</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.2. Форма предоставления согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Согласие может быть предоставлено в письменной форме</li>
|
||||
<li>Согласие может быть предоставлено в электронной форме</li>
|
||||
<li>Согласие может выражаться путем совершения конклюдентных действий</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.3. Последствия непредоставления согласия:</h4>
|
||||
<ul className={styles.list}>
|
||||
<li>Отказ в предоставлении согласия может повлечь невозможность регистрации</li>
|
||||
<li>Отказ может ограничить доступ к отдельным услугам</li>
|
||||
<li>Отказ в согласии на маркетинг не влияет на основные услуги</li>
|
||||
<li>Субъект вправе предоставить частичное согласие</li>
|
||||
</ul>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.4. Контактная информация:</h4>
|
||||
<div className={styles.contacts}>
|
||||
<p>Почтовый адрес: 196246, г. Санкт-Петербург, Московское ш., д. 25, к. 1, лит. В, пом. 3-н</p>
|
||||
<p>Электронная почта: company@bitforcefoundation.ru</p>
|
||||
<p>Ответственное лицо: Кленин Михаил Васильевич</p>
|
||||
<p>Официальный сайт: https://bitforce-foundation.ru</p>
|
||||
</div>
|
||||
|
||||
<h4 className={styles.subSectionTitle}>9.5. Подтверждение понимания:</h4>
|
||||
<p className={styles.confirmation}>
|
||||
Предоставляя настоящее согласие, я подтверждаю, что:
|
||||
</p>
|
||||
<ul className={styles.list}>
|
||||
<li>Ознакомлен с содержанием согласия и понимаю его значение</li>
|
||||
<li>Понимаю цели и способы обработки моих персональных данных</li>
|
||||
<li>Знаю о своих правах и способах их реализации</li>
|
||||
<li>Согласие предоставляется добровольно и осознанно</li>
|
||||
<li>Имею возможность отозвать согласие в любое время</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,52 +1,12 @@
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: var(--bg-deep);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 24px 28px 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 24px;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-family: var(--font-sans);
|
||||
letter-spacing: 0.5px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.active {
|
||||
background: linear-gradient(135deg, var(--grad-edge), var(--grad-center));
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.inactive {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.inactive:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32px 20px 48px;
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
|
||||
@media (max-width: 650px) {
|
||||
.main {
|
||||
padding: 32px 20px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding: 32px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,14 @@
|
||||
import { useState } from 'react'
|
||||
import { Footer } from '@widgets/footer'
|
||||
import { SwapForm } from '@widgets/swap-form'
|
||||
import { WalletHeader } from '@widgets/wallet-header'
|
||||
import { SwapBridgeTabs } from '@widgets/swap-bridge-tabs'
|
||||
import styles from './SwapPage.module.css'
|
||||
|
||||
type Tab = 'swap' | 'bridge'
|
||||
|
||||
export function SwapPage() {
|
||||
const [tab, setTab] = useState<Tab>('swap')
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<WalletHeader />
|
||||
|
||||
<div className={styles.tabs}>
|
||||
<button
|
||||
className={`${styles.tab} ${tab === 'swap' ? styles.active : styles.inactive}`}
|
||||
onClick={() => setTab('swap')}
|
||||
>
|
||||
СВОП
|
||||
</button>
|
||||
<button
|
||||
className={`${styles.tab} ${tab === 'bridge' ? styles.active : styles.inactive}`}
|
||||
onClick={() => setTab('bridge')}
|
||||
>
|
||||
БРИДЖ
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<main className={styles.main}>
|
||||
<>
|
||||
<SwapBridgeTabs active="swap" />
|
||||
<div className={styles.content}>
|
||||
<SwapForm />
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
1
src/pages/transactions/index.ts
Normal file
1
src/pages/transactions/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { TransactionsPage } from './ui/TransactionsPage'
|
||||
39
src/pages/transactions/ui/TransactionsPage.module.css
Normal file
39
src/pages/transactions/ui/TransactionsPage.module.css
Normal file
@@ -0,0 +1,39 @@
|
||||
.inner {
|
||||
padding: 28px 32px 40px;
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.glow {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 600px;
|
||||
height: 320px;
|
||||
background: radial-gradient(ellipse, rgba(61, 42, 142, 0.15), transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 24px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.inner {
|
||||
padding: 20px 16px 32px;
|
||||
}
|
||||
|
||||
.glow {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
12
src/pages/transactions/ui/TransactionsPage.tsx
Normal file
12
src/pages/transactions/ui/TransactionsPage.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TransactionsList } from '@widgets/transactions-list'
|
||||
import styles from './TransactionsPage.module.css'
|
||||
|
||||
export function TransactionsPage() {
|
||||
return (
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.glow} />
|
||||
<h1 className={styles.title}>Транзакции</h1>
|
||||
<TransactionsList />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -35,6 +35,20 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.noWallet {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
min-height: 300px;
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.glow {
|
||||
width: auto;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user