commit
ee5ad268c8
23 changed files with 841 additions and 249 deletions
140
package-lock.json
generated
140
package-lock.json
generated
|
|
@ -27,6 +27,7 @@
|
|||
"react-apexcharts": "^1.5.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-intersection-observer": "^9.10.3",
|
||||
"react-joyride": "^2.9.3",
|
||||
"sequelize": "^6.37.3",
|
||||
"sha256": "^0.2.0",
|
||||
"socket.io": "^4.6.1",
|
||||
|
|
@ -2401,6 +2402,12 @@
|
|||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@gilbarbara/deep-equal": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz",
|
||||
"integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||
|
|
@ -4223,7 +4230,6 @@
|
|||
"version": "15.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
|
|
@ -4244,7 +4250,6 @@
|
|||
"version": "18.2.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.16.tgz",
|
||||
"integrity": "sha512-LLFWr12ZhBJ4YVw7neWLe6Pk7Ey5R9OCydfuMsz1L8bZxzaawJj2p06Q8/EFEHDeTBQNFLF62X+CG7B2zIyu0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
|
|
@ -4266,7 +4271,6 @@
|
|||
"version": "0.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz",
|
||||
"integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
|
|
@ -6315,6 +6319,12 @@
|
|||
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deep-diff": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz",
|
||||
"integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
|
|
@ -6325,7 +6335,6 @@
|
|||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
|
@ -8701,6 +8710,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-lite": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz",
|
||||
"integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-map": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
|
||||
|
|
@ -10344,6 +10359,17 @@
|
|||
"node": ">=12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/popper.js": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
|
||||
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
|
|
@ -11246,6 +11272,55 @@
|
|||
"react": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-floater": {
|
||||
"version": "0.7.9",
|
||||
"resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz",
|
||||
"integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deepmerge": "^4.3.1",
|
||||
"is-lite": "^0.8.2",
|
||||
"popper.js": "^1.16.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"tree-changes": "^0.9.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "15 - 18",
|
||||
"react-dom": "15 - 18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-floater/node_modules/@gilbarbara/deep-equal": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz",
|
||||
"integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-floater/node_modules/is-lite": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz",
|
||||
"integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-floater/node_modules/tree-changes": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz",
|
||||
"integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@gilbarbara/deep-equal": "^0.1.1",
|
||||
"is-lite": "^0.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-innertext": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz",
|
||||
"integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=0.0.0 <=99",
|
||||
"react": ">=0.0.0 <=99"
|
||||
}
|
||||
},
|
||||
"node_modules/react-intersection-observer": {
|
||||
"version": "9.16.0",
|
||||
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.16.0.tgz",
|
||||
|
|
@ -11267,6 +11342,41 @@
|
|||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-joyride": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.9.3.tgz",
|
||||
"integrity": "sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@gilbarbara/deep-equal": "^0.3.1",
|
||||
"deep-diff": "^1.0.2",
|
||||
"deepmerge": "^4.3.1",
|
||||
"is-lite": "^1.2.1",
|
||||
"react-floater": "^0.7.9",
|
||||
"react-innertext": "^1.1.5",
|
||||
"react-is": "^16.13.1",
|
||||
"scroll": "^3.0.1",
|
||||
"scrollparent": "^2.1.0",
|
||||
"tree-changes": "^0.11.2",
|
||||
"type-fest": "^4.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "15 - 18",
|
||||
"react-dom": "15 - 18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-joyride/node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
|
|
@ -11739,6 +11849,12 @@
|
|||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/scroll": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz",
|
||||
"integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scroll-into-view-if-needed": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
|
||||
|
|
@ -11748,6 +11864,12 @@
|
|||
"compute-scroll-into-view": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/scrollparent": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz",
|
||||
"integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
|
|
@ -13015,6 +13137,16 @@
|
|||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tree-changes": {
|
||||
"version": "0.11.3",
|
||||
"resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.3.tgz",
|
||||
"integrity": "sha512-r14mvDZ6tqz8PRQmlFKjhUVngu4VZ9d92ON3tp0EGpFBE6PAHOq8Bx8m8ahbNoGE3uI/npjYcJiqVydyOiYXag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@gilbarbara/deep-equal": "^0.3.1",
|
||||
"is-lite": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"react-apexcharts": "^1.5.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-intersection-observer": "^9.10.3",
|
||||
"react-joyride": "^2.9.3",
|
||||
"sequelize": "^6.37.3",
|
||||
"sha256": "^0.2.0",
|
||||
"socket.io": "^4.6.1",
|
||||
|
|
|
|||
12
src/components/UI/GuideContent/index.tsx
Normal file
12
src/components/UI/GuideContent/index.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { useEffect } from 'react';
|
||||
import { GuideContentProps } from './types';
|
||||
|
||||
function GuideContent({ onEnter, text }: GuideContentProps) {
|
||||
useEffect(() => {
|
||||
if (onEnter) onEnter();
|
||||
}, [onEnter]);
|
||||
|
||||
return <p>{text}</p>;
|
||||
}
|
||||
|
||||
export default GuideContent;
|
||||
4
src/components/UI/GuideContent/types.ts
Normal file
4
src/components/UI/GuideContent/types.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export interface GuideContentProps {
|
||||
text: string;
|
||||
onEnter?: () => void;
|
||||
}
|
||||
37
src/components/UI/GuideTooltip/index.tsx
Normal file
37
src/components/UI/GuideTooltip/index.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import type { TooltipRenderProps } from 'react-joyride';
|
||||
import styles from './styles.module.scss';
|
||||
import Button from '../Button/Button';
|
||||
|
||||
export default function GuideTooltip({
|
||||
index,
|
||||
size,
|
||||
step,
|
||||
primaryProps,
|
||||
skipProps,
|
||||
tooltipProps,
|
||||
isLastStep,
|
||||
}: TooltipRenderProps) {
|
||||
return (
|
||||
<div {...tooltipProps} className={styles.tooltip}>
|
||||
<div className={styles.tooltip__body}>
|
||||
<div className={styles.tooltip__index}>
|
||||
{index + 1}/{size}
|
||||
</div>
|
||||
<div className={styles.tooltip__content}>
|
||||
{typeof step.content === 'string' ? <p>{step.content}</p> : step.content}
|
||||
</div>
|
||||
|
||||
<Button {...(primaryProps as object)} className={styles.tooltip__btn}>
|
||||
{isLastStep ? 'Close' : 'Continue'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{!isLastStep && (
|
||||
<button {...skipProps} className={styles.tooltip__skip}>
|
||||
Skip guide
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
77
src/components/UI/GuideTooltip/styles.module.scss
Normal file
77
src/components/UI/GuideTooltip/styles.module.scss
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
.tooltip {
|
||||
transition: none !important;
|
||||
width: 300px;
|
||||
|
||||
&__body {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__index {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #0c0c3a;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
&__content,
|
||||
&__content p {
|
||||
color: #0c0c3a;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
padding: 13px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
&__skip {
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 140%;
|
||||
color: var(--guide-skip-btn);
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width:620px) {
|
||||
.tooltip {
|
||||
width: 200px;
|
||||
|
||||
&__body {
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&__content,
|
||||
&__content p {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&__btn,
|
||||
&__skip {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
&__index {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/components/default/GuideRegistrator/index.tsx
Normal file
16
src/components/default/GuideRegistrator/index.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { useTour } from '@/store/guide-provider';
|
||||
import { useEffect } from 'react';
|
||||
import type { Step } from 'react-joyride';
|
||||
|
||||
type Props = { name: string; steps: Step[]; autoStartOnceVersion?: string };
|
||||
|
||||
export default function GuideRegistrator({ name, steps, autoStartOnceVersion }: Props) {
|
||||
const { register, startOnce } = useTour(name);
|
||||
|
||||
useEffect(() => {
|
||||
register(name, steps);
|
||||
if (autoStartOnceVersion) startOnce(autoStartOnceVersion);
|
||||
}, [name, steps, register, startOnce, autoStartOnceVersion]);
|
||||
|
||||
return <></>;
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
>p {
|
||||
> p {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
|
|
@ -76,9 +76,9 @@
|
|||
&__currency {
|
||||
padding: 0 8px;
|
||||
|
||||
>p {
|
||||
> p {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,9 +50,13 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
const [hasImmediateMatch, setHasImmediateMatch] = useState(false);
|
||||
const isBuy = buySellState?.code === 'buy';
|
||||
|
||||
function goToSuitableTab() {
|
||||
function goToTab(name?: string) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set('tab', 'matches');
|
||||
if (name) {
|
||||
params.set('tab', name);
|
||||
} else {
|
||||
params.delete('tab');
|
||||
}
|
||||
|
||||
router.replace(`${pathname}?${params.toString()}`, undefined, {
|
||||
shallow: true,
|
||||
|
|
@ -93,6 +97,8 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
if (result.success) {
|
||||
if (result.data?.immediateMatch) {
|
||||
setHasImmediateMatch(true);
|
||||
goToTab();
|
||||
scrollToOrderList();
|
||||
}
|
||||
onAfter();
|
||||
resetForm();
|
||||
|
|
@ -125,7 +131,7 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
const isButtonDisabled = !priceValid || !amountValid || !totalValid || creatingState;
|
||||
|
||||
return (
|
||||
<div className={styles.inputPanel}>
|
||||
<div data-tour="input-panel" className={styles.inputPanel}>
|
||||
{hasImmediateMatch && (
|
||||
<Alert
|
||||
type="custom"
|
||||
|
|
@ -139,7 +145,7 @@ function InputPanelItem(props: InputPanelItemProps) {
|
|||
className={styles.applyAlert__button}
|
||||
onClick={() => {
|
||||
scrollToOrderList();
|
||||
goToSuitableTab();
|
||||
goToTab('matches');
|
||||
setHasImmediateMatch(false);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -144,7 +144,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
.inputPanel {
|
||||
padding: 0;
|
||||
|
|
@ -195,4 +194,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ const OrdersPool = (props: OrdersPoolProps) => {
|
|||
useMouseLeave(ordersInfoRef, () => setOrdersInfoTooltip(null));
|
||||
return (
|
||||
<>
|
||||
<div className={styles.ordersPool}>
|
||||
<div data-tour="orders-pool" className={styles.ordersPool}>
|
||||
<div className={styles.ordersPool__header}>
|
||||
<Tabs value={currentOrder} setValue={setCurrentOrder} data={tabsData} />
|
||||
|
||||
|
|
|
|||
|
|
@ -262,4 +262,4 @@
|
|||
.tooltip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
102
src/components/dex/TradingHeader/components/DexGuide/index.tsx
Normal file
102
src/components/dex/TradingHeader/components/DexGuide/index.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import GuideRegistrator from '@/components/default/GuideRegistrator';
|
||||
import GuideContent from '@/components/UI/GuideContent';
|
||||
import { useMediaQuery } from '@/hook/useMediaQuery';
|
||||
import { usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
import { Placement, Step } from 'react-joyride';
|
||||
|
||||
const DexGuide = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const pathname = usePathname();
|
||||
const isMobile = useMediaQuery(`(max-width: 620px)`);
|
||||
|
||||
const getPlacement = (position: 'auto' | Placement | 'center' | undefined) => {
|
||||
if (isMobile) return 'auto';
|
||||
|
||||
return position;
|
||||
};
|
||||
|
||||
const changeTab = (name?: string) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
if (name) {
|
||||
params.set('tab', name);
|
||||
} else {
|
||||
params.delete('tab');
|
||||
}
|
||||
|
||||
const qs = params.toString();
|
||||
router.replace(qs ? `${pathname}?${qs}` : pathname, undefined, {
|
||||
shallow: true,
|
||||
scroll: false,
|
||||
});
|
||||
};
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
target: '[data-tour="orders-pool"]',
|
||||
placement: getPlacement('right-start'),
|
||||
content: (
|
||||
<GuideContent text="This section displays the current buy and sell orders in the market. You can see the price, quantity, and total value, as well as the order book depth visualized with bars." />
|
||||
),
|
||||
disableBeacon: true,
|
||||
},
|
||||
{
|
||||
target: '[data-tour="input-panel"]',
|
||||
placement: getPlacement('left-start'),
|
||||
content: (
|
||||
<GuideContent text="Here you can place buy or sell orders. Set your price, choose the amount, and review the total cost before creating the order." />
|
||||
),
|
||||
},
|
||||
{
|
||||
target: '[data-tour="user-orders"]',
|
||||
placement: getPlacement('top-start'),
|
||||
content: (
|
||||
<GuideContent
|
||||
onEnter={() => changeTab()}
|
||||
text="This tab shows all of your active orders. You can track price, quantity, and cancel them anytime."
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
target: '[data-tour="user-orders"]',
|
||||
placement: getPlacement('top-start'),
|
||||
content: (
|
||||
<GuideContent
|
||||
onEnter={() => changeTab('matches')}
|
||||
text="Here you see orders that have been successfully matched and executed."
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
target: '[data-tour="user-orders"]',
|
||||
placement: getPlacement('top-start'),
|
||||
content: (
|
||||
<GuideContent
|
||||
onEnter={() => changeTab('requests')}
|
||||
text="This section lists your pending requests that are waiting for approval or fulfillment."
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
target: '[data-tour="user-orders"]',
|
||||
placement: getPlacement('top-start'),
|
||||
content: (
|
||||
<GuideContent
|
||||
onEnter={() => changeTab('offers')}
|
||||
text="In this tab you can find offers you’ve made to other traders that are not yet matched."
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<GuideRegistrator name="dex-onboarding" steps={steps} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DexGuide;
|
||||
|
|
@ -6,11 +6,13 @@ import BackButton from '@/components/default/BackButton/BackButton';
|
|||
import { roundTo, notationToString, classes } from '@/utils/utils';
|
||||
import questionIcon from '@/assets/images/UI/question.svg';
|
||||
import Image from 'next/image';
|
||||
import { useTour } from '@/store/guide-provider';
|
||||
import styles from './styles.module.scss';
|
||||
import StatItem from './components/StatItem';
|
||||
import { TradingHeaderProps } from './types';
|
||||
import CurrencyIcon from './components/CurrencyIcon';
|
||||
import AssetRow from './components/AssetRow';
|
||||
import DexGuide from './components/DexGuide';
|
||||
|
||||
const TradingHeader = ({
|
||||
pairStats,
|
||||
|
|
@ -21,6 +23,8 @@ const TradingHeader = ({
|
|||
secondAssetId,
|
||||
pairData,
|
||||
}: TradingHeaderProps) => {
|
||||
const { start, isRunning } = useTour('dex-onboarding');
|
||||
|
||||
const currencyNames = {
|
||||
firstCurrencyName: pairData?.first_currency?.name || '',
|
||||
secondCurrencyName: pairData?.second_currency?.name || '',
|
||||
|
|
@ -59,76 +63,85 @@ const TradingHeader = ({
|
|||
];
|
||||
|
||||
return (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.header__stats}>
|
||||
<div className={styles.header__currency}>
|
||||
<div className={styles.header__currency_icon}>
|
||||
<CurrencyIcon code={firstAssetId} />
|
||||
</div>
|
||||
<>
|
||||
<DexGuide />
|
||||
<div className={styles.header}>
|
||||
<div className={styles.header__stats}>
|
||||
<div className={styles.header__currency}>
|
||||
<div className={styles.header__currency_icon}>
|
||||
<CurrencyIcon code={firstAssetId} />
|
||||
</div>
|
||||
|
||||
<div className={styles.header__currency_item}>
|
||||
<p className={styles.currencyName}>
|
||||
{!pairData ? (
|
||||
'...'
|
||||
) : (
|
||||
<>
|
||||
{firstCurrencyName}
|
||||
<span>/{secondCurrencyName}</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
|
||||
<div className={styles.price}>
|
||||
<p
|
||||
className={classes(
|
||||
styles.price__secondCurrency,
|
||||
coefficientOutput >= 0 ? styles.green : styles.red,
|
||||
<div className={styles.header__currency_item}>
|
||||
<p className={styles.currencyName}>
|
||||
{!pairData ? (
|
||||
'...'
|
||||
) : (
|
||||
<>
|
||||
{firstCurrencyName}
|
||||
<span>/{secondCurrencyName}</span>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
{roundTo(notationToString(pairStats?.rate || 0, 8))}
|
||||
</p>
|
||||
{pairRateUsd && <p className={styles.price__usd}>~ ${pairRateUsd}</p>}
|
||||
|
||||
<div className={styles.price}>
|
||||
<p
|
||||
className={classes(
|
||||
styles.price__secondCurrency,
|
||||
coefficientOutput >= 0 ? styles.green : styles.red,
|
||||
)}
|
||||
>
|
||||
{roundTo(notationToString(pairStats?.rate || 0, 8))}
|
||||
</p>
|
||||
{pairRateUsd && (
|
||||
<p className={styles.price__usd}>~ ${pairRateUsd}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{pairData && firstAssetLink && secondAssetLink && (
|
||||
<div className={styles.header__stats_assets}>
|
||||
<AssetRow
|
||||
name={firstCurrencyName}
|
||||
link={firstAssetLink}
|
||||
id={firstAssetId || ''}
|
||||
code={firstAssetId}
|
||||
/>
|
||||
<AssetRow
|
||||
name={secondCurrencyName}
|
||||
link={secondAssetLink}
|
||||
id={secondAssetId || ''}
|
||||
code={secondAssetId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{stats.map(({ Img, title, value, coefficient }) => (
|
||||
<StatItem
|
||||
key={title}
|
||||
className={styles.header__stats_item}
|
||||
Img={Img}
|
||||
title={title}
|
||||
value={value}
|
||||
coefficient={coefficient}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{pairData && firstAssetLink && secondAssetLink && (
|
||||
<div className={styles.header__stats_assets}>
|
||||
<AssetRow
|
||||
name={firstCurrencyName}
|
||||
link={firstAssetLink}
|
||||
id={firstAssetId || ''}
|
||||
code={firstAssetId}
|
||||
/>
|
||||
<AssetRow
|
||||
name={secondCurrencyName}
|
||||
link={secondAssetLink}
|
||||
id={secondAssetId || ''}
|
||||
code={secondAssetId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.header__actions}>
|
||||
<button
|
||||
onClick={start}
|
||||
disabled={isRunning}
|
||||
className={styles.header__actions_guide}
|
||||
>
|
||||
<Image src={questionIcon} width={24} height={24} alt="guide" />
|
||||
</button>
|
||||
|
||||
{stats.map(({ Img, title, value, coefficient }) => (
|
||||
<StatItem
|
||||
key={title}
|
||||
className={styles.header__stats_item}
|
||||
Img={Img}
|
||||
title={title}
|
||||
value={value}
|
||||
coefficient={coefficient}
|
||||
/>
|
||||
))}
|
||||
<BackButton className={styles.header__actions_backBtn} isSm />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.header__actions}>
|
||||
<button className={styles.header__actions_guide}>
|
||||
<Image src={questionIcon} width={24} height={24} alt="guide" />
|
||||
</button>
|
||||
|
||||
<BackButton className={styles.header__actions_backBtn} isSm />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
background-color: var(--icon-bg-color);
|
||||
border-radius: 50%;
|
||||
|
||||
>img {
|
||||
> img {
|
||||
width: 24px;
|
||||
height: auto;
|
||||
}
|
||||
|
|
@ -169,4 +169,4 @@
|
|||
gap: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,206 +1,206 @@
|
|||
.cards {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
padding: 6px 10px;
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
padding: 6px 10px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.sell {
|
||||
&::before {
|
||||
background-color: #ff6767;
|
||||
}
|
||||
&.sell {
|
||||
&::before {
|
||||
background-color: #ff6767;
|
||||
}
|
||||
|
||||
.card__type {
|
||||
color: #ff6767;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
.card__type {
|
||||
color: #ff6767;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
|
||||
&.buy {
|
||||
&::before {
|
||||
background-color: #16d1d6;
|
||||
}
|
||||
&.buy {
|
||||
&::before {
|
||||
background-color: #16d1d6;
|
||||
}
|
||||
|
||||
.card__type {
|
||||
color: #16d1d6;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
.card__type {
|
||||
color: #16d1d6;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
|
||||
&__row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
&__row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
|
||||
&.sm {
|
||||
gap: 18px;
|
||||
&.sm {
|
||||
gap: 18px;
|
||||
|
||||
.card__col {
|
||||
min-width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.card__col {
|
||||
min-width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
color: var(--table-th-color);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
}
|
||||
&__label {
|
||||
color: var(--table-th-color);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
&__value {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
|
||||
&.primary {
|
||||
color: #1f8feb;
|
||||
}
|
||||
&.primary {
|
||||
color: #1f8feb;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
color: var(--table-th-color);
|
||||
}
|
||||
&.secondary {
|
||||
color: var(--table-th-color);
|
||||
}
|
||||
|
||||
span {
|
||||
color: var(--table-th-color);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
}
|
||||
}
|
||||
span {
|
||||
color: var(--table-th-color);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__col {
|
||||
min-width: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
&__col {
|
||||
min-width: 100px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
&__pair {
|
||||
.card__value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
&__pair {
|
||||
.card__value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card__type {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.card__type {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__flex {
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
&__flex {
|
||||
gap: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
.cards .card {
|
||||
gap: 10px;
|
||||
.cards .card {
|
||||
gap: 10px;
|
||||
|
||||
&__row {
|
||||
gap: 15px;
|
||||
&__row {
|
||||
gap: 15px;
|
||||
|
||||
&.sm {
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
&.sm {
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__col {
|
||||
min-width: 80px;
|
||||
&__col {
|
||||
min-width: 80px;
|
||||
|
||||
&_direction {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&_direction {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 10px;
|
||||
}
|
||||
&__label {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 11px;
|
||||
}
|
||||
&__value {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
&__pair {
|
||||
.card__label {
|
||||
display: none;
|
||||
}
|
||||
&__pair {
|
||||
.card__label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card__value {
|
||||
font-size: 12px;
|
||||
}
|
||||
.card__value {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.card__type {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
.card__type {
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.cards .card {
|
||||
&__row {
|
||||
justify-content: space-between;
|
||||
.cards .card {
|
||||
&__row {
|
||||
justify-content: space-between;
|
||||
|
||||
&.sm {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
&.sm {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
&__col {
|
||||
min-width: auto;
|
||||
}
|
||||
&__col {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
&__flex {
|
||||
flex-direction: column;
|
||||
}
|
||||
&__flex {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__price {
|
||||
display: none;
|
||||
&__price {
|
||||
display: none;
|
||||
|
||||
&.mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
&.mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&__offers {
|
||||
display: none;
|
||||
&__offers {
|
||||
display: none;
|
||||
|
||||
&.mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ const UserOrders = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div ref={orderListRef} className={styles.userOrders}>
|
||||
<div data-tour="user-orders" ref={orderListRef} className={styles.userOrders}>
|
||||
<div className={styles.userOrders__header}>
|
||||
<Tabs
|
||||
type={isMobile ? 'button' : 'tab'}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@
|
|||
td {
|
||||
position: relative;
|
||||
|
||||
>p {
|
||||
> p {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
>span {
|
||||
> span {
|
||||
line-height: 1;
|
||||
color: var(--font-dimmed-color);
|
||||
font-size: 11px;
|
||||
|
|
@ -115,4 +115,4 @@
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import useTradeInit from '@/hook/useTradeInit';
|
|||
import useMatrixAddresses from '@/hook/useMatrixAddresses';
|
||||
import takeOrderClick from '@/utils/takeOrderClick';
|
||||
import useUpdateUser from '@/hook/useUpdateUser';
|
||||
import { GuideProvider } from '@/store/guide-provider';
|
||||
|
||||
function Trading() {
|
||||
const { alertState, alertSubtitle, setAlertState } = useAlert();
|
||||
|
|
@ -137,7 +138,7 @@ function Trading() {
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<GuideProvider>
|
||||
<Header isLg={true} />
|
||||
|
||||
<main className={styles.trading}>
|
||||
|
|
@ -233,7 +234,7 @@ function Trading() {
|
|||
)}
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
</GuideProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
191
src/store/guide-provider.tsx
Normal file
191
src/store/guide-provider.tsx
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import GuideTooltip from '@/components/UI/GuideTooltip';
|
||||
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||
import { CallBackProps, EVENTS, STATUS, Step } from 'react-joyride';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const JoyrideNoSSR = dynamic(() => import('react-joyride'), { ssr: false });
|
||||
|
||||
type TourName = string;
|
||||
type Registry = Record<TourName, Step[]>;
|
||||
|
||||
type Ctx = {
|
||||
register: (_name: TourName, _steps: Step[]) => void;
|
||||
start: (_name: TourName) => void;
|
||||
startOnce: (_name: TourName, _version?: string) => void;
|
||||
stop: () => void;
|
||||
isRunning: boolean;
|
||||
active?: TourName;
|
||||
};
|
||||
|
||||
const TourCtx = createContext<Ctx | null>(null);
|
||||
const STORAGE_PREFIX = 'tour.seen:';
|
||||
|
||||
export function GuideProvider({ children }: { children: React.ReactNode }) {
|
||||
const registryRef = useRef<Registry>({});
|
||||
const [run, setRun] = useState(false);
|
||||
const [active, setActive] = useState<TourName | undefined>(undefined);
|
||||
const [stepIndex, setStepIndex] = useState(0);
|
||||
|
||||
const register = useCallback<Ctx['register']>((name, steps) => {
|
||||
registryRef.current[name] = steps;
|
||||
}, []);
|
||||
|
||||
const start = useCallback((name: TourName) => {
|
||||
if (!registryRef.current[name]?.length) return;
|
||||
setActive(name);
|
||||
setStepIndex(0);
|
||||
setRun(true);
|
||||
}, []);
|
||||
|
||||
const startOnce = useCallback(
|
||||
(name: TourName, version = 'v1') => {
|
||||
const key = `${STORAGE_PREFIX}${name}:${version}`;
|
||||
if (!localStorage.getItem(key)) {
|
||||
localStorage.setItem(key, '1');
|
||||
start(name);
|
||||
}
|
||||
},
|
||||
[start],
|
||||
);
|
||||
|
||||
const stop = useCallback(() => {
|
||||
setRun(false);
|
||||
setActive(undefined);
|
||||
setStepIndex(0);
|
||||
}, []);
|
||||
|
||||
const waitForEl = useCallback(
|
||||
(selector: string | Element, { timeout = 2000, interval = 50 } = {}) =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
const startT = performance.now();
|
||||
const tick = () => {
|
||||
let found: Element | null | string = selector;
|
||||
|
||||
if (selector === 'body') {
|
||||
found = document.body;
|
||||
} else if (typeof selector === 'string') {
|
||||
found = document.querySelector(selector);
|
||||
} else {
|
||||
found = selector;
|
||||
}
|
||||
|
||||
if (found) return resolve(true);
|
||||
if (performance.now() - startT > timeout) return resolve(false);
|
||||
setTimeout(tick, interval);
|
||||
};
|
||||
tick();
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const advancingRef = useRef(false);
|
||||
|
||||
const safeAdvanceTo = useCallback(
|
||||
async (nextIndex: number) => {
|
||||
if (advancingRef.current) return;
|
||||
advancingRef.current = true;
|
||||
|
||||
const steps = active ? (registryRef.current[active] ?? []) : [];
|
||||
const next = steps[nextIndex];
|
||||
|
||||
if (!next || next.target === 'body') {
|
||||
setStepIndex(nextIndex);
|
||||
advancingRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const ok = await waitForEl(String(next.target), { timeout: 2000 });
|
||||
if (ok) setStepIndex(nextIndex);
|
||||
|
||||
advancingRef.current = false;
|
||||
},
|
||||
[active, waitForEl],
|
||||
);
|
||||
|
||||
const callback = useCallback(
|
||||
(data: CallBackProps) => {
|
||||
const { status, index, type } = data;
|
||||
|
||||
if (type === EVENTS.STEP_AFTER && typeof index === 'number') {
|
||||
void safeAdvanceTo(index + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === EVENTS.TARGET_NOT_FOUND && typeof index === 'number') {
|
||||
void safeAdvanceTo(index + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status === STATUS.FINISHED || status === STATUS.SKIPPED) {
|
||||
stop();
|
||||
setStepIndex(0);
|
||||
}
|
||||
},
|
||||
[safeAdvanceTo, stop],
|
||||
);
|
||||
|
||||
const steps = active ? (registryRef.current[active] ?? []) : [];
|
||||
|
||||
const value = useMemo<Ctx>(
|
||||
() => ({
|
||||
register,
|
||||
start,
|
||||
startOnce,
|
||||
stop,
|
||||
isRunning: run,
|
||||
active,
|
||||
}),
|
||||
[register, start, startOnce, stop, run, active],
|
||||
);
|
||||
|
||||
return (
|
||||
<TourCtx.Provider value={value}>
|
||||
{children}
|
||||
<JoyrideNoSSR
|
||||
steps={steps}
|
||||
run={run}
|
||||
stepIndex={stepIndex}
|
||||
continuous
|
||||
showProgress
|
||||
showSkipButton
|
||||
hideCloseButton
|
||||
scrollToFirstStep
|
||||
disableScrolling={false}
|
||||
spotlightPadding={0}
|
||||
disableOverlayClose
|
||||
disableCloseOnEsc
|
||||
floaterProps={{
|
||||
disableAnimation: true,
|
||||
styles: {
|
||||
floater: { transition: 'none', position: 'fixed' },
|
||||
},
|
||||
hideArrow: true,
|
||||
}}
|
||||
tooltipComponent={GuideTooltip}
|
||||
styles={{
|
||||
options: { zIndex: 9999, primaryColor: '#1F8FEB' },
|
||||
overlay: { transition: 'none' },
|
||||
tooltipContainer: { transition: 'none' },
|
||||
}}
|
||||
locale={{ next: 'Continue', skip: 'Skip guide', last: 'Finish' }}
|
||||
callback={callback}
|
||||
/>
|
||||
</TourCtx.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTour(name?: TourName) {
|
||||
const ctx = useContext(TourCtx);
|
||||
if (!ctx) throw new Error('useTour must be used within GuideProvider');
|
||||
const { register, start, startOnce, stop, isRunning, active } = ctx;
|
||||
return {
|
||||
register,
|
||||
start: () => (name ? start(name) : stop()),
|
||||
startOnce: (version?: string) => (name ? startOnce(name, version) : undefined),
|
||||
stop,
|
||||
isRunning,
|
||||
active,
|
||||
};
|
||||
}
|
||||
|
||||
export const dataTour = (id: string) => ({ 'data-tour': id }) as const;
|
||||
|
|
@ -68,7 +68,6 @@
|
|||
|
||||
@media screen and (max-width: 1024px) {
|
||||
.trading__top {
|
||||
|
||||
&_trades,
|
||||
&_form {
|
||||
max-width: 100%;
|
||||
|
|
@ -90,4 +89,4 @@
|
|||
.trading__top {
|
||||
height: 370px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,4 +56,5 @@
|
|||
--table-group-header-bg: #0c1940;
|
||||
--tab-bg-color: #1f8feb1a;
|
||||
--selector-bg-color: #0c1940;
|
||||
--guide-skip-btn: #1f8feb;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,4 +56,5 @@
|
|||
--table-group-header-bg: #f2f5f9;
|
||||
--tab-bg-color: #1f8feb;
|
||||
--selector-bg-color: #e7eff8;
|
||||
--guide-skip-btn: #ffffff;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue