From 0b6c5ad15dbf47b1ea71e2e6a4f69e15e0b9fcc6 Mon Sep 17 00:00:00 2001 From: mayuran-deriv Date: Mon, 6 Jan 2025 11:53:35 +0400 Subject: [PATCH 01/11] fix: favicon updated --- index.html | 1 + public/favicon-96.png | Bin 0 -> 7324 bytes 2 files changed, 1 insertion(+) create mode 100644 public/favicon-96.png diff --git a/index.html b/index.html index f48a039..62441a7 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + Trade Rise Fall diff --git a/public/favicon-96.png b/public/favicon-96.png new file mode 100644 index 0000000000000000000000000000000000000000..4e215a5dae55aee3da09ad7883d8270e1e82e051 GIT binary patch literal 7324 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 Date: Mon, 6 Jan 2025 11:56:35 +0400 Subject: [PATCH 02/11] fix: favicon name updated --- index.html | 2 +- public/{favicon-96.png => favicon-deriv.png} | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename public/{favicon-96.png => favicon-deriv.png} (100%) diff --git a/index.html b/index.html index 62441a7..1afe9c0 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + Trade Rise Fall diff --git a/public/favicon-96.png b/public/favicon-deriv.png similarity index 100% rename from public/favicon-96.png rename to public/favicon-deriv.png From c8d1e0fde4181bf08a4af0e6d21e828caa27035e Mon Sep 17 00:00:00 2001 From: mayuran-deriv Date: Mon, 6 Jan 2025 19:20:44 +0400 Subject: [PATCH 03/11] fix: rise proposal --- .../TradingPanel/TradingPanel.tsx | 54 ++++++++++++--- src/hooks/useDebounce.ts | 19 ++++++ src/hooks/usePriceProposal.ts | 66 +++++++++++++++++++ src/stores/TradingPanelStore.ts | 7 +- 4 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 src/hooks/useDebounce.ts create mode 100644 src/hooks/usePriceProposal.ts diff --git a/src/components/TradingComponents/TradingPanel/TradingPanel.tsx b/src/components/TradingComponents/TradingPanel/TradingPanel.tsx index 2292e10..7874ec3 100644 --- a/src/components/TradingComponents/TradingPanel/TradingPanel.tsx +++ b/src/components/TradingComponents/TradingPanel/TradingPanel.tsx @@ -16,6 +16,8 @@ import { import { DurationTabValue, StakeTabValue } from "../types"; import { observer } from "mobx-react-lite"; import { tradingPanelStore } from "../../../stores/TradingPanelStore"; +import { chartStore } from "../../../stores/ChartStore"; +import { usePriceProposal } from "../../../hooks/usePriceProposal"; import "./TradingPanel.scss"; export const TradingPanel = observer(() => { @@ -32,6 +34,13 @@ export const TradingPanel = observer(() => { setSelectedStakeTab, } = tradingPanelStore; + const { proposal, clearProposal } = usePriceProposal( + price, + duration, + selectedStakeTab, + chartStore.symbol + ); + const durationTabs: Array<{ label: string; value: DurationTabValue }> = [ { label: "Duration", value: "duration" }, { label: "End time", value: "endtime" }, @@ -42,6 +51,19 @@ export const TradingPanel = observer(() => { { label: "Payout", value: "payout" }, ]; + const getDisplayAmount = () => { + if (!proposal) return "0.00"; + + if (selectedStakeTab === "stake") { + return proposal.payout?.toFixed(2) ?? "0.00"; + } + return proposal.ask_price?.toFixed(2) ?? "0.00"; + }; + + const getDisplayLabel = () => { + return selectedStakeTab === "stake" ? "Payout" : "Stake"; + }; + return (
@@ -115,13 +137,11 @@ export const TradingPanel = observer(() => { /> {
- Payout + {getDisplayLabel()} - 97.71 USD + {getDisplayAmount()} USD
@@ -173,7 +193,14 @@ export const TradingPanel = observer(() => {
- 95.42% + {proposal + ? ( + ((proposal.payout - proposal.ask_price) / + proposal.ask_price) * + 100 + ).toFixed(2) + : "0.00"} + %
@@ -187,7 +214,14 @@ export const TradingPanel = observer(() => {
- 95.20% + {proposal + ? ( + ((proposal.payout - proposal.ask_price) / + proposal.ask_price) * + 100 + ).toFixed(2) + : "0.00"} + % diff --git a/src/hooks/useDebounce.ts b/src/hooks/useDebounce.ts new file mode 100644 index 0000000..de00a71 --- /dev/null +++ b/src/hooks/useDebounce.ts @@ -0,0 +1,19 @@ +import { useEffect, useState } from "react"; + +function useDebounce(value: T, delay: number = 300): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + + return debouncedValue; +} + +export default useDebounce; diff --git a/src/hooks/usePriceProposal.ts b/src/hooks/usePriceProposal.ts new file mode 100644 index 0000000..c52a4bd --- /dev/null +++ b/src/hooks/usePriceProposal.ts @@ -0,0 +1,66 @@ +import { useEffect, useState } from "react"; +import { getDerivAPI } from "../services/deriv-api.instance"; +import { PriceProposalResponse } from "../types/deriv-api.types"; +import useDebounce from "./useDebounce"; +export const usePriceProposal = ( + price: string, + duration: number, + basis: string, + symbol: string +) => { + const [proposal, setProposal] = useState(); + const debouncedPrice = useDebounce(price, 1000); + const derivAPI = getDerivAPI(); + + const clearProposal = () => { + setProposal(undefined); + }; + + useEffect(() => { + const handleProposal = async () => { + const data = { + price: debouncedPrice, + duration, + basis, + symbol, + }; + + const numPrice = parseFloat(data.price); + if (numPrice <= 0) { + return; + } + + try { + await derivAPI.subscribeStream( + { + proposal: 1, + subscribe: 1, + amount: numPrice, + basis: data.basis, + contract_type: "CALL", + currency: "USD", + duration: data.duration, + duration_unit: "m", + symbol: data.symbol, + }, + (response: any) => { + if (response.error) { + console.error("Proposal error:", response.error); + } else if (response.proposal) { + setProposal(response.proposal); + } + }, + "PROPOSAL" + ); + } catch (err) { + console.error("Subscription error:", err); + } + }; + + if (debouncedPrice && duration && basis && symbol) { + handleProposal(); + } + }, [debouncedPrice, duration, basis, symbol, derivAPI]); + + return { proposal, clearProposal }; +}; diff --git a/src/stores/TradingPanelStore.ts b/src/stores/TradingPanelStore.ts index a48527b..341dd03 100644 --- a/src/stores/TradingPanelStore.ts +++ b/src/stores/TradingPanelStore.ts @@ -2,7 +2,7 @@ import { makeAutoObservable } from "mobx"; export class TradingPanelStore { duration = 1; - price = "0"; + price = "10"; allowEquals = false; selectedDurationTab: "duration" | "endtime" = "duration"; selectedStakeTab: "stake" | "payout" = "stake"; @@ -19,6 +19,11 @@ export class TradingPanelStore { }; setPrice = (value: string) => { + if (!value) { + this.price = ""; + return; + } + const numValue = parseFloat(value); if (!isNaN(numValue)) { this.price = numValue.toString(); From d5e4f89bac9d8cd7532f31bc197476a977d7ecca Mon Sep 17 00:00:00 2001 From: muhammad-ahmed Date: Wed, 8 Jan 2025 16:57:44 +0800 Subject: [PATCH 04/11] Update base URL and add config on deploy.yml file --- .github/workflows/deploy.yml | 3 ++- src/App.tsx | 2 +- src/stores/AuthStore.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a42a4f8..f6e2c10 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,7 +20,8 @@ env: REACT_APP_WS_PORT: ${{ secrets.REACT_APP_WS_PORT }} REACT_APP_WS_URL: ${{ secrets.REACT_APP_WS_URL }} REACT_CURRENT_ENVIRONMENT: ${{ secrets.REACT_CURRENT_ENVIRONMENT }} - + REACT_OAUTH_URL: ${{ secrets.REACT_OAUTH_URL }} + jobs: deploy: environment: diff --git a/src/App.tsx b/src/App.tsx index 95b5403..43b0dbe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -83,7 +83,7 @@ const App: React.FC = observer(() => { return ( - + diff --git a/src/stores/AuthStore.ts b/src/stores/AuthStore.ts index 0802af5..624353e 100644 --- a/src/stores/AuthStore.ts +++ b/src/stores/AuthStore.ts @@ -39,7 +39,7 @@ export class AuthStore { login = () => { const app_id = process.env.REACT_APP_WS_PORT; - const server_url = process.env.OAUTH_URL; + const server_url = process.env.REACT_OAUTH_URL; if (!app_id || !server_url) { console.error('Required environment variables are not set'); From 2a1687aa214fa4b639f9101970c141e94acc6ccc Mon Sep 17 00:00:00 2001 From: mayuran-deriv Date: Wed, 8 Jan 2025 16:21:20 +0400 Subject: [PATCH 05/11] fix: initial api calls --- .../TradingPanel/TradingPanel.scss | 90 +++++++++------- .../TradingPanel/TradingPanel.tsx | 102 +++++++++++------- src/hooks/usePriceProposal.ts | 70 ++++++++++-- src/stores/TradingPanelStore.ts | 40 ++++++- 4 files changed, 212 insertions(+), 90 deletions(-) diff --git a/src/components/TradingComponents/TradingPanel/TradingPanel.scss b/src/components/TradingComponents/TradingPanel/TradingPanel.scss index 66b1bae..75bd3f5 100644 --- a/src/components/TradingComponents/TradingPanel/TradingPanel.scss +++ b/src/components/TradingComponents/TradingPanel/TradingPanel.scss @@ -1,6 +1,6 @@ .trading-panel { width: 360px; - background: var(--system-light-1-primary, #FFF); + background: var(--system-light-1-primary, #fff); border-radius: 8px; padding: 16px; display: flex; @@ -19,7 +19,7 @@ gap: 8px; .learn-link { - color: var(--brand-blue, #85ACB0); + color: var(--brand-blue, #85acb0); cursor: pointer; } } @@ -33,7 +33,8 @@ display: flex; gap: 4px; - .rise-icon, .fall-icon { + .rise-icon, + .fall-icon { width: 16px; height: 16px; display: flex; @@ -43,11 +44,11 @@ } .rise-icon { - background: var(--state-success, #4BB4B3); + background: var(--state-success, #4bb4b3); } .fall-icon { - background: var(--state-danger, #EC3F3F); + background: var(--state-danger, #ec3f3f); } } } @@ -89,18 +90,27 @@ flex-direction: column; gap: 16px; - .payout-info { - background: var(--system-light-2-secondary, #F2F3F4); - padding: 16px; - border-radius: 8px; + .payout-info-row { display: flex; - justify-content: space-between; align-items: center; + gap: 12px; + justify-content: space-between; + min-height: 48px; + min-width: 200px; - .payout-amount { - display: flex; - flex-direction: column; - gap: 4px; + span:first-child { + font-weight: 700; + } + + .info-icon { + width: 16px; + height: 16px; + color: var(--text-less-prominent); + cursor: pointer; + + &:hover { + color: var(--text-general); + } } } @@ -113,49 +123,53 @@ padding: 0; margin-bottom: 8px; - .button-content { + .button-content { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + height: 100%; + padding: 0 16px; + color: var(--text-colored-background, #ffffff); + position: relative; + z-index: 1; + + .left { display: flex; - justify-content: space-between; align-items: center; - width: 100%; - height: 100%; - padding: 0 16px; - color: var(--text-colored-background, #FFFFFF); - position: relative; - z-index: 1; - - .left { - display: flex; - align-items: center; - gap: 12px; - - svg { - width: 20px; - height: 20px; - color: var(--text-colored-background, #FFFFFF); - fill: currentColor; - } + gap: 12px; + + svg { + width: 20px; + height: 20px; + color: var(--text-colored-background, #ffffff); + fill: currentColor; } } + } &::after { - content: ''; + content: ""; position: absolute; top: 0; right: 0; height: 100%; width: 30%; - background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1)); + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.1) + ); clip-path: polygon(0 0, 100% 0, 100% 100%, 30% 100%); } } .rise-button { - background: linear-gradient(90deg, #4BB4B3 0%, #5ECFCE 100%); + background: linear-gradient(90deg, #4bb4b3 0%, #5ecfce 100%); } .fall-button { - background: linear-gradient(90deg, #EC3F3F 0%, #FF5C5C 100%); + background: linear-gradient(90deg, #ec3f3f 0%, #ff5c5c 100%); } } } diff --git a/src/components/TradingComponents/TradingPanel/TradingPanel.tsx b/src/components/TradingComponents/TradingPanel/TradingPanel.tsx index 7874ec3..3d2d06c 100644 --- a/src/components/TradingComponents/TradingPanel/TradingPanel.tsx +++ b/src/components/TradingComponents/TradingPanel/TradingPanel.tsx @@ -32,9 +32,11 @@ export const TradingPanel = observer(() => { setAllowEquals, setSelectedDurationTab, setSelectedStakeTab, + durationError, + priceError, } = tradingPanelStore; - const { proposal, clearProposal } = usePriceProposal( + const { proposal, clearProposal, isLoading } = usePriceProposal( price, duration, selectedStakeTab, @@ -51,19 +53,28 @@ export const TradingPanel = observer(() => { { label: "Payout", value: "payout" }, ]; - const getDisplayAmount = () => { - if (!proposal) return "0.00"; + const getAmount = (type: "rise" | "fall") => { + const currentProposal = proposal[type]; + if (!currentProposal) return "0.00"; if (selectedStakeTab === "stake") { - return proposal.payout?.toFixed(2) ?? "0.00"; + return currentProposal.payout?.toFixed(2) ?? "0.00"; } - return proposal.ask_price?.toFixed(2) ?? "0.00"; + return currentProposal.ask_price?.toFixed(2) ?? "0.00"; }; - const getDisplayLabel = () => { - return selectedStakeTab === "stake" ? "Payout" : "Stake"; + const getPercentage = (type: "rise" | "fall") => { + const currentProposal = proposal[type]; + if (!currentProposal) return "0.00"; + + const { payout, ask_price } = currentProposal; + if (!payout || !ask_price) return "0.00"; + + return (((payout - ask_price) / ask_price) * 100).toFixed(2); }; + const label = selectedStakeTab === "stake" ? "Payout" : "Stake"; + return (
@@ -103,9 +114,20 @@ export const TradingPanel = observer(() => { Minutes setDuration(e.target.value)} className="duration-field" + allowDecimals={false} + allowSign={false} + regex={/[^0-9]|^$/g} + inputMode="numeric" + shouldRound={true} + textAlignment="center" + minusDisabled={Number(duration) - 1 <= 0} + variant="fill" + status={durationError ? "error" : "success"} + message={durationError} /> Range: 1 - 1,440 minutes @@ -149,6 +171,8 @@ export const TradingPanel = observer(() => { onChange={(e) => setPrice(e.target.value)} unitRight="USD" className="stake-field" + status={priceError ? "error" : "success"} + message={priceError} />
@@ -168,22 +192,6 @@ export const TradingPanel = observer(() => {
-
-
- - {getDisplayLabel()} - - - {getDisplayAmount()} USD - -
- - - -
- +
+ {!isLoading.rise && ( + <> + + {label} + + + {getAmount("rise")} USD + + + + + + )} +
+
+ {!isLoading.fall && ( + <> + + {label} + + + {getAmount("fall")} USD + + + + + + )} +
); diff --git a/src/hooks/usePriceProposal.ts b/src/hooks/usePriceProposal.ts index c52a4bd..2ddffb6 100644 --- a/src/hooks/usePriceProposal.ts +++ b/src/hooks/usePriceProposal.ts @@ -2,25 +2,44 @@ import { useEffect, useState } from "react"; import { getDerivAPI } from "../services/deriv-api.instance"; import { PriceProposalResponse } from "../types/deriv-api.types"; import useDebounce from "./useDebounce"; + +interface ProposalState { + rise?: PriceProposalResponse["proposal"]; + fall?: PriceProposalResponse["proposal"]; +} + +interface LoadingState { + rise: boolean; + fall: boolean; +} + export const usePriceProposal = ( price: string, duration: number, basis: string, symbol: string ) => { - const [proposal, setProposal] = useState(); + const [proposal, setProposal] = useState({}); + const [isLoading, setIsLoading] = useState({ + rise: false, + fall: false, + }); const debouncedPrice = useDebounce(price, 1000); + const debouncedDuration = useDebounce(duration, 1000); + const derivAPI = getDerivAPI(); const clearProposal = () => { - setProposal(undefined); + setProposal({}); }; useEffect(() => { const handleProposal = async () => { + setIsLoading({ fall: true, rise: true }); + const data = { price: debouncedPrice, - duration, + duration: debouncedDuration, basis, symbol, }; @@ -44,23 +63,56 @@ export const usePriceProposal = ( symbol: data.symbol, }, (response: any) => { + setIsLoading((prev) => ({ ...prev, rise: false })); if (response.error) { - console.error("Proposal error:", response.error); + console.error("Rise proposal error:", response.error); } else if (response.proposal) { - setProposal(response.proposal); + setProposal((prev) => ({ ...prev, rise: response.proposal })); } }, - "PROPOSAL" + "PROPOSAL_RISE" ); } catch (err) { - console.error("Subscription error:", err); + console.error("Rise subscription error:", err); + setIsLoading((prev) => ({ ...prev, rise: false })); + } + + try { + await derivAPI.subscribeStream( + { + proposal: 1, + subscribe: 1, + amount: numPrice, + basis: data.basis, + contract_type: "PUT", + currency: "USD", + duration: data.duration, + duration_unit: "m", + symbol: data.symbol, + }, + (response: any) => { + setIsLoading((prev) => ({ ...prev, fall: false })); + if (response.error) { + console.error("Fall proposal error:", response.error); + } else if (response.proposal) { + setProposal((prev) => ({ ...prev, fall: response.proposal })); + } + }, + "PROPOSAL_FALL" + ); + } catch (err) { + console.error("Fall subscription error:", err); + setIsLoading((prev) => ({ ...prev, fall: false })); } }; + setProposal({}); + setIsLoading({ rise: false, fall: false }); + if (debouncedPrice && duration && basis && symbol) { handleProposal(); } - }, [debouncedPrice, duration, basis, symbol, derivAPI]); + }, [debouncedPrice, debouncedDuration, basis, symbol, derivAPI]); - return { proposal, clearProposal }; + return { proposal, clearProposal, isLoading }; }; diff --git a/src/stores/TradingPanelStore.ts b/src/stores/TradingPanelStore.ts index 341dd03..b1146d1 100644 --- a/src/stores/TradingPanelStore.ts +++ b/src/stores/TradingPanelStore.ts @@ -6,28 +6,60 @@ export class TradingPanelStore { allowEquals = false; selectedDurationTab: "duration" | "endtime" = "duration"; selectedStakeTab: "stake" | "payout" = "stake"; + durationError: string | null = null; + priceError: string | null = null; constructor() { makeAutoObservable(this); } setDuration = (value: string) => { + if (!value) { + this.duration = 0; + this.durationError = "Duration is required"; + return; + } + const numValue = parseInt(value); - if (!isNaN(numValue)) { - this.duration = Math.min(Math.max(numValue, 1), 1440); + if (isNaN(numValue)) { + this.durationError = "Please enter a valid number"; + return; } + + if (numValue <= 0) { + this.durationError = "Duration must be greater than 0"; + return; + } + + if (numValue > 1440) { + this.durationError = "Maximum duration is 1440 minutes"; + return; + } + + this.duration = numValue; + this.durationError = null; }; setPrice = (value: string) => { if (!value) { this.price = ""; + this.priceError = "Price is required"; return; } const numValue = parseFloat(value); - if (!isNaN(numValue)) { - this.price = numValue.toString(); + if (isNaN(numValue)) { + this.priceError = "Please enter a valid number"; + return; } + + if (numValue <= 0) { + this.priceError = "Price must be greater than 0"; + return; + } + + this.price = numValue.toString(); + this.priceError = null; }; setAllowEquals = (value: boolean) => { From d57b0fec7452f8447a09e136d7aa815db07864a9 Mon Sep 17 00:00:00 2001 From: mayuran-deriv Date: Wed, 8 Jan 2025 19:22:14 +0400 Subject: [PATCH 06/11] fix: fine tune apis --- .../DurationSection/DurationSection.tsx | 80 ++++++++ .../PayoutInfo/PayoutInfo.tsx | 34 ++++ .../StakeSection/StakeSection.tsx | 56 ++++++ .../TradeButton/TradeButton.tsx | 35 ++++ .../TradingPanel/TradingPanel.tsx | 190 ++++-------------- src/hooks/usePriceProposal.ts | 102 +++++----- 6 files changed, 289 insertions(+), 208 deletions(-) create mode 100644 src/components/TradingComponents/DurationSection/DurationSection.tsx create mode 100644 src/components/TradingComponents/PayoutInfo/PayoutInfo.tsx create mode 100644 src/components/TradingComponents/StakeSection/StakeSection.tsx create mode 100644 src/components/TradingComponents/TradeButton/TradeButton.tsx diff --git a/src/components/TradingComponents/DurationSection/DurationSection.tsx b/src/components/TradingComponents/DurationSection/DurationSection.tsx new file mode 100644 index 0000000..47539ee --- /dev/null +++ b/src/components/TradingComponents/DurationSection/DurationSection.tsx @@ -0,0 +1,80 @@ +import { Text, TextField, TextFieldWithSteppers } from "@deriv-com/quill-ui"; +import { Tab } from "../Tab"; +import { DurationTabValue } from "../types"; + +interface DurationSectionProps { + duration: number; + selectedDurationTab: DurationTabValue; + durationError: string; + setDuration: (value: string) => void; + setSelectedDurationTab: (value: DurationTabValue) => void; +} + +interface DurationTab { + label: string; + value: DurationTabValue; +} + +export const DurationSection = ({ + duration, + selectedDurationTab, + durationError, + setDuration, + setSelectedDurationTab, +}: DurationSectionProps) => { + const durationTabs: DurationTab[] = [ + { label: "Duration", value: "duration" }, + { label: "End time", value: "endtime" }, + ]; + + return ( +
+ + {selectedDurationTab === "duration" ? ( +
+ + Minutes + + setDuration(e.target.value)} + className="duration-field" + allowDecimals={false} + allowSign={false} + regex={/[^0-9]|^$/g} + inputMode="numeric" + shouldRound={true} + textAlignment="center" + minusDisabled={Number(duration) - 1 <= 0} + variant="fill" + status={durationError ? "error" : "success"} + message={durationError} + /> + + Range: 1 - 1,440 minutes + +
+ ) : ( +
+ + +
+ )} +
+ ); +}; diff --git a/src/components/TradingComponents/PayoutInfo/PayoutInfo.tsx b/src/components/TradingComponents/PayoutInfo/PayoutInfo.tsx new file mode 100644 index 0000000..26941b2 --- /dev/null +++ b/src/components/TradingComponents/PayoutInfo/PayoutInfo.tsx @@ -0,0 +1,34 @@ +import { Text, Tooltip } from "@deriv-com/quill-ui"; +import { LabelPairedCircleInfoSmBoldIcon } from "@deriv/quill-icons"; + +interface PayoutInfoProps { + type: "rise" | "fall"; + label: string; + amount: string; + isLoading: boolean; +} + +export const PayoutInfo = ({ + type, + label, + amount, + isLoading, +}: PayoutInfoProps) => { + return ( +
+ {!isLoading && ( + <> + + {label} + + + {amount} USD + + + + + + )} +
+ ); +}; diff --git a/src/components/TradingComponents/StakeSection/StakeSection.tsx b/src/components/TradingComponents/StakeSection/StakeSection.tsx new file mode 100644 index 0000000..2a331aa --- /dev/null +++ b/src/components/TradingComponents/StakeSection/StakeSection.tsx @@ -0,0 +1,56 @@ +import { TextFieldWithSteppers } from "@deriv-com/quill-ui"; +import { Tab } from "../Tab"; +import { StakeTabValue } from "../types"; + +interface StakeSectionProps { + price: string; + selectedStakeTab: StakeTabValue; + priceError: string; + setPrice: (value: string) => void; + setSelectedStakeTab: (value: StakeTabValue) => void; +} + +interface StakeTab { + label: string; + value: StakeTabValue; +} + +export const StakeSection = ({ + price, + selectedStakeTab, + priceError, + setPrice, + setSelectedStakeTab, +}: StakeSectionProps) => { + const stakeTabs: StakeTab[] = [ + { label: "Stake", value: "stake" }, + { label: "Payout", value: "payout" }, + ]; + + return ( +
+ + setPrice(e.target.value)} + unitRight="USD" + className="stake-field" + status={priceError ? "error" : "success"} + message={priceError} + /> +
+ ); +}; diff --git a/src/components/TradingComponents/TradeButton/TradeButton.tsx b/src/components/TradingComponents/TradeButton/TradeButton.tsx new file mode 100644 index 0000000..5f208b0 --- /dev/null +++ b/src/components/TradingComponents/TradeButton/TradeButton.tsx @@ -0,0 +1,35 @@ +import { Button, Text } from "@deriv-com/quill-ui"; +import { + TradeTypesUpsAndDownsRiseIcon, + TradeTypesUpsAndDownsFallIcon, +} from "@deriv/quill-icons"; + +interface TradeButtonProps { + type: "rise" | "fall"; + percentage: string; +} + +export const TradeButton = ({ type, percentage }: TradeButtonProps) => { + const Icon = + type === "rise" + ? TradeTypesUpsAndDownsRiseIcon + : TradeTypesUpsAndDownsFallIcon; + const label = type === "rise" ? "Rise" : "Fall"; + const className = `${type}-button`; + + return ( + + ); +}; diff --git a/src/components/TradingComponents/TradingPanel/TradingPanel.tsx b/src/components/TradingComponents/TradingPanel/TradingPanel.tsx index 3d2d06c..4a91a72 100644 --- a/src/components/TradingComponents/TradingPanel/TradingPanel.tsx +++ b/src/components/TradingComponents/TradingPanel/TradingPanel.tsx @@ -1,23 +1,18 @@ -import { - Button, - TextField, - Text, - Tooltip, - Checkbox, - TextFieldWithSteppers, -} from "@deriv-com/quill-ui"; -import { Tab } from "../Tab"; +import { Button, Text, Tooltip, Checkbox } from "@deriv-com/quill-ui"; import { LabelPairedBackwardSmBoldIcon, TradeTypesUpsAndDownsRiseIcon, TradeTypesUpsAndDownsFallIcon, LabelPairedCircleInfoSmBoldIcon, } from "@deriv/quill-icons"; -import { DurationTabValue, StakeTabValue } from "../types"; import { observer } from "mobx-react-lite"; import { tradingPanelStore } from "../../../stores/TradingPanelStore"; import { chartStore } from "../../../stores/ChartStore"; import { usePriceProposal } from "../../../hooks/usePriceProposal"; +import { DurationSection } from "../DurationSection/DurationSection"; +import { StakeSection } from "../StakeSection/StakeSection"; +import { TradeButton } from "../TradeButton/TradeButton"; +import { PayoutInfo } from "../PayoutInfo/PayoutInfo"; import "./TradingPanel.scss"; export const TradingPanel = observer(() => { @@ -40,20 +35,12 @@ export const TradingPanel = observer(() => { price, duration, selectedStakeTab, - chartStore.symbol + chartStore.symbol, + durationError, + priceError ); - const durationTabs: Array<{ label: string; value: DurationTabValue }> = [ - { label: "Duration", value: "duration" }, - { label: "End time", value: "endtime" }, - ]; - - const stakeTabs: Array<{ label: string; value: StakeTabValue }> = [ - { label: "Stake", value: "stake" }, - { label: "Payout", value: "payout" }, - ]; - - const getAmount = (type: "rise" | "fall") => { + const getAmount = (type: "rise" | "fall"): string => { const currentProposal = proposal[type]; if (!currentProposal) return "0.00"; @@ -63,7 +50,7 @@ export const TradingPanel = observer(() => { return currentProposal.ask_price?.toFixed(2) ?? "0.00"; }; - const getPercentage = (type: "rise" | "fall") => { + const getPercentage = (type: "rise" | "fall"): string => { const currentProposal = proposal[type]; if (!currentProposal) return "0.00"; @@ -102,79 +89,20 @@ export const TradingPanel = observer(() => {
-
- - {selectedDurationTab === "duration" ? ( -
- - Minutes - - setDuration(e.target.value)} - className="duration-field" - allowDecimals={false} - allowSign={false} - regex={/[^0-9]|^$/g} - inputMode="numeric" - shouldRound={true} - textAlignment="center" - minusDisabled={Number(duration) - 1 <= 0} - variant="fill" - status={durationError ? "error" : "success"} - message={durationError} - /> - - Range: 1 - 1,440 minutes - -
- ) : ( -
- - -
- )} -
- -
- - setPrice(e.target.value)} - unitRight="USD" - className="stake-field" - status={priceError ? "error" : "success"} - message={priceError} - /> -
+ +
@@ -192,63 +120,21 @@ export const TradingPanel = observer(() => {
- -
- {!isLoading.rise && ( - <> - - {label} - - - {getAmount("rise")} USD - - - - - - )} -
+ + - -
- {!isLoading.fall && ( - <> - - {label} - - - {getAmount("fall")} USD - - - - - - )} -
+ +
); diff --git a/src/hooks/usePriceProposal.ts b/src/hooks/usePriceProposal.ts index 2ddffb6..f8d4249 100644 --- a/src/hooks/usePriceProposal.ts +++ b/src/hooks/usePriceProposal.ts @@ -17,7 +17,9 @@ export const usePriceProposal = ( price: string, duration: number, basis: string, - symbol: string + symbol: string, + durationError: string, + priceError: string ) => { const [proposal, setProposal] = useState({}); const [isLoading, setIsLoading] = useState({ @@ -49,67 +51,55 @@ export const usePriceProposal = ( return; } - try { - await derivAPI.subscribeStream( - { - proposal: 1, - subscribe: 1, - amount: numPrice, - basis: data.basis, - contract_type: "CALL", - currency: "USD", - duration: data.duration, - duration_unit: "m", - symbol: data.symbol, - }, - (response: any) => { - setIsLoading((prev) => ({ ...prev, rise: false })); - if (response.error) { - console.error("Rise proposal error:", response.error); - } else if (response.proposal) { - setProposal((prev) => ({ ...prev, rise: response.proposal })); - } - }, - "PROPOSAL_RISE" - ); - } catch (err) { - console.error("Rise subscription error:", err); - setIsLoading((prev) => ({ ...prev, rise: false })); - } + const subscribeToProposal = async (type: "rise" | "fall") => { + const config = { + proposal: 1, + subscribe: 1, + amount: numPrice, + basis: data.basis, + contract_type: type === "rise" ? "CALL" : "PUT", + currency: "USD", + duration: data.duration, + duration_unit: "m", + symbol: data.symbol, + }; - try { - await derivAPI.subscribeStream( - { - proposal: 1, - subscribe: 1, - amount: numPrice, - basis: data.basis, - contract_type: "PUT", - currency: "USD", - duration: data.duration, - duration_unit: "m", - symbol: data.symbol, - }, - (response: any) => { - setIsLoading((prev) => ({ ...prev, fall: false })); - if (response.error) { - console.error("Fall proposal error:", response.error); - } else if (response.proposal) { - setProposal((prev) => ({ ...prev, fall: response.proposal })); - } - }, - "PROPOSAL_FALL" - ); - } catch (err) { - console.error("Fall subscription error:", err); - setIsLoading((prev) => ({ ...prev, fall: false })); - } + try { + await derivAPI.subscribeStream( + config, + (response: any) => { + setIsLoading((prev) => ({ ...prev, [type]: false })); + if (response.error) { + console.error(`${type} proposal error:`, response.error); + } else if (response.proposal) { + setProposal((prev) => ({ ...prev, [type]: response.proposal })); + } + }, + `PROPOSAL_${type.toUpperCase()}` + ); + } catch (err) { + console.error(`${type} subscription error:`, err); + setIsLoading((prev) => ({ ...prev, [type]: false })); + } + }; + + await Promise.all([ + subscribeToProposal("rise"), + subscribeToProposal("fall"), + ]); }; setProposal({}); setIsLoading({ rise: false, fall: false }); - if (debouncedPrice && duration && basis && symbol) { + if ( + debouncedPrice && + duration && + basis && + symbol && + !durationError && + !priceError + ) { handleProposal(); } }, [debouncedPrice, debouncedDuration, basis, symbol, derivAPI]); From 1512210c9082496ff8ccf9ebc8b019922ffea6ea Mon Sep 17 00:00:00 2001 From: mayuran-deriv Date: Wed, 8 Jan 2025 19:25:13 +0400 Subject: [PATCH 07/11] fix: package lock udate --- package-lock.json | 68 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7d1cfd5..5199fe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "@rsbuild/plugin-basic-ssl": "^1.1.1", "@rsbuild/plugin-sass": "^1.1.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-router-dom": "^7.1.1" }, "devDependencies": { "@rsbuild/core": "^1.1.8", @@ -1010,6 +1011,11 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/lodash": { "version": "4.17.14", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz", @@ -1342,6 +1348,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "peer": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -1616,6 +1623,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "engines": { + "node": ">=18" + } + }, "node_modules/core-js": { "version": "3.39.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", @@ -2823,7 +2838,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "peer": true }, "node_modules/json-stable-stringify": { "version": "1.2.1", @@ -6325,6 +6341,44 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz", + "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.1.tgz", + "integrity": "sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==", + "dependencies": { + "react-router": "7.1.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-swipeable": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-6.2.2.tgz", @@ -7088,6 +7142,11 @@ "node": "*" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -7644,6 +7703,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==" + }, "node_modules/type-detect": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", From 78d5ab703ef32c6420570ad41c327868a9fe171e Mon Sep 17 00:00:00 2001 From: mayuran-deriv Date: Wed, 8 Jan 2025 19:31:44 +0400 Subject: [PATCH 08/11] fix: added slash path --- src/App.tsx | 54 +++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 43b0dbe..dd3e292 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,22 @@ -import React, { Suspense, useEffect } from 'react'; -import { BrowserRouter, Routes, Route, useLocation, useNavigate } from 'react-router-dom'; -import { ThemeProvider } from '@deriv-com/quill-ui'; -import { observer } from 'mobx-react-lite'; -import LoadingSpinner from './components/LoadingSpinner/LoadingSpinner'; -import Header from './components/Header/Header'; -import ProtectedRoute from './components/ProtectedRoute/ProtectedRoute'; -import ErrorBoundary from './components/ErrorBoundary/ErrorBoundary'; -import { authStore } from './stores/AuthStore'; -import { authService } from './services/auth.service'; +import React, { Suspense, useEffect } from "react"; +import { + BrowserRouter, + Routes, + Route, + useLocation, + useNavigate, +} from "react-router-dom"; +import { ThemeProvider } from "@deriv-com/quill-ui"; +import { observer } from "mobx-react-lite"; +import LoadingSpinner from "./components/LoadingSpinner/LoadingSpinner"; +import Header from "./components/Header/Header"; +import ProtectedRoute from "./components/ProtectedRoute/ProtectedRoute"; +import ErrorBoundary from "./components/ErrorBoundary/ErrorBoundary"; +import { authStore } from "./stores/AuthStore"; +import { authService } from "./services/auth.service"; -const Homepage = React.lazy(() => import('./pages/homepage')); -const DerivTrading = React.lazy(() => import('./pages/trading')); +const Homepage = React.lazy(() => import("./pages/homepage")); +const DerivTrading = React.lazy(() => import("./pages/trading")); const AuthHandler: React.FC = observer(() => { const location = useLocation(); @@ -18,18 +24,18 @@ const AuthHandler: React.FC = observer(() => { useEffect(() => { const params = new URLSearchParams(location.search); - const token = params.get('token1'); + const token = params.get("token1"); if (token) { authStore.handleAuthCallback(token).then((success) => { // Clear the token from URL const cleanUrl = window.location.pathname; window.history.replaceState({}, document.title, cleanUrl); - + if (success) { - navigate('/dashboard'); + navigate("/dashboard"); } else { - navigate('/'); + navigate("/"); } }); } @@ -44,12 +50,9 @@ const AppContent: React.FC = () => {
- } - /> - } /> + @@ -82,8 +85,11 @@ const App: React.FC = observer(() => { return ( - - + + + + + From eee3e06ad2b784deb4c5480c2360214be04987ba Mon Sep 17 00:00:00 2001 From: mayuran-deriv Date: Wed, 8 Jan 2025 19:55:03 +0400 Subject: [PATCH 09/11] fix: route issue fix --- src/App.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index dd3e292..b53d0ba 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -89,9 +89,6 @@ const App: React.FC = observer(() => { - - - ); From 9fff22a565e002df4d608301453f136431e791d0 Mon Sep 17 00:00:00 2001 From: mayuran-deriv Date: Wed, 8 Jan 2025 20:24:53 +0400 Subject: [PATCH 10/11] fix: protect routes --- src/common/utils/index.tsx | 5 +++++ .../ProtectedRoute/ProtectedRoute.tsx | 15 ++++++++----- src/pages/homepage/index.tsx | 22 ++++++++++++------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/common/utils/index.tsx b/src/common/utils/index.tsx index 446a3a7..1256f01 100644 --- a/src/common/utils/index.tsx +++ b/src/common/utils/index.tsx @@ -20,6 +20,11 @@ export const getChartUrl = (): string => { return `${baseUrl}/js/smartcharts/`; }; +export const isLogged = (): boolean => { + const token = localStorage.getItem("auth_token"); + return !!token; +}; + export const getUrlBase = (path: string = ""): string => { const l = window.location; diff --git a/src/components/ProtectedRoute/ProtectedRoute.tsx b/src/components/ProtectedRoute/ProtectedRoute.tsx index 51b5b56..73ce978 100644 --- a/src/components/ProtectedRoute/ProtectedRoute.tsx +++ b/src/components/ProtectedRoute/ProtectedRoute.tsx @@ -1,13 +1,16 @@ -import React from 'react'; -import { Navigate, useLocation } from 'react-router-dom'; -import { observer } from 'mobx-react-lite'; -import { authStore } from '../../stores/AuthStore'; +import React from "react"; +import { Navigate, useLocation } from "react-router-dom"; +import { observer } from "mobx-react-lite"; +import { authStore } from "../../stores/AuthStore"; +import { isLogged } from "../../common/utils"; interface ProtectedRouteProps { children: React.ReactNode; } -const ProtectedRoute = observer(function ProtectedRoute({ children }: ProtectedRouteProps) { +const ProtectedRoute = observer(function ProtectedRoute({ + children, +}: ProtectedRouteProps) { const location = useLocation(); const { isAuthenticated, isInitializing } = authStore; @@ -15,7 +18,7 @@ const ProtectedRoute = observer(function ProtectedRoute({ children }: ProtectedR return
Loading...
; } - if (!isAuthenticated) { + if (!isLogged()) { return ; } diff --git a/src/pages/homepage/index.tsx b/src/pages/homepage/index.tsx index 7bca5ac..775f2f8 100644 --- a/src/pages/homepage/index.tsx +++ b/src/pages/homepage/index.tsx @@ -1,23 +1,29 @@ -import React from 'react'; -import { Button, Heading } from '@deriv-com/quill-ui'; -import { authStore } from '../../stores/AuthStore'; -import './homepage.scss'; +import React from "react"; +import { Button, Heading } from "@deriv-com/quill-ui"; +import { authStore } from "../../stores/AuthStore"; +import { isLogged } from "../../common/utils"; +import "./homepage.scss"; const Homepage: React.FC = () => { - return (
Deriv: Option Trading - + Mirror the success of top traders automatically. Set up in minutes - +