diff --git a/crowdin/messages.json b/crowdin/messages.json index 17dfb91cc51..42d7f5919f5 100644 --- a/crowdin/messages.json +++ b/crowdin/messages.json @@ -2,6 +2,7 @@ "3670321": "<0>IVV.US The iShares Core S&P 500 ETF tracks the performance of an index of large-capitalisation US equities.", "3965745": "Your payout can potentially grow by 1% or 5% on average per tick.", "4506932": "How do I add a Deriv X account?", + "5629598": "Company website/social media URL*", "5681962": "EUR/USD DFX10 Index", "6312314": "Deriv (V) Ltd (Company No. 014556), incorporated on the 17th February 2016, is registered in the Republic of Vanuatu with its registered office located at 1276, Govant Building, Kumul Highway, Port Vila, Republic of Vanuatu. Deriv (V) Ltd is licensed by the Vanuatu Financial Services Commission <0>(view licence) and is a member of the <1>Financial Markets Association.", "7128051": "How can I adjust or remove my self-exclusion limits?", @@ -126,6 +127,7 @@ "115911783": "Of course, this list isn’t complete. Every day, impersonators come up with new ways to try to steal your information and money.", "117174749": "Can I close an open position before expiry?", "117273069": "<0>Wall Street 30 follows the stock performance of the top 30 listed companies in the US.", + "117318539": "Password should have lower and uppercase English letters with numbers.", "117920584": "As per <0>our terms, we only allow one account per client, which you can open in the currency of your choice (either fiat or crypto). If you want to trade with other currencies, you can add multiple cryptocurrency accounts to your profile.", "118164751": "This plan is available exclusively for EU-based clients. <0>Please note that according to regulations, you cannot have clients who reside in Portugal or Spain.", "119888059": "Who can apply", @@ -133,6 +135,7 @@ "120180174": "0–19.999%", "121961018": "If you're residing in the EU or UK, <0>contact us via live chat, and we'll help you with it.", "123005346": "No artificial barriers to customer withdrawals", + "127307725": "A politically exposed person (PEP) is someone appointed with a prominent public position. Close associates and family members of a PEP are also considered to be PEPs.", "128120244": "Since 1999, the Group has served traders around the world with integrity and reliability. We always hold ourselves to the highest ethical standards and regulatory requirements.", "131637194": "For <0>metals, there is a fixed commission of USD 4 per lot. A deal for 1 lot of XAU/USD will pay out USD 4 in commission based on the following formula:", "132319176": " take profit level", @@ -178,9 +181,11 @@ "176773798": "0.5%", "176823932": "We may decline your verification documents if they are insufficiently clear, invalid, expired, or have cropped edges.", "176972763": "<0>GLD.US The SPDR Gold Shares ETF tracks the price of gold bullion in the over-the-counter (OTC) market.", + "177720589": "If you hit <0>Yes, the details you entered will be lost, and you’ll need to restart the registration process.", "179737767": "Our legacy options trading platform.", "180412186": "Predict whether the exit spot will be strictly higher or lower than the entry spot at the end of the contract period.", "181754600": "How can I identify emails from impersonators of Deriv customer support?", + "182234515": "Choose the type of partner account you want to register", "182438100": "Frequently asked questions - DBot", "183032954": "No, the High/Low Ticks options are only available on the SmartTrader and Deriv Bot trading platforms, and are available to trade on synthetics.", "186459036": "Your net profit will depend on how much the final price is above the predetermined strike price, with the maximum potential gains growing if the price of the underlying asset rises significantly. Your losses are limited to the initial stake required to purchase the call option.", @@ -198,6 +203,7 @@ "200347746": "You’ll be brought back to the <0>Cashier page. Click Review pending and select the transaction that you'd like to cancel.", "201096446": "UNG.US", "202712649": "Fast and secure deposit and withdrawal options", + "203271702": "Try again", "209516313": "The MT5 trading signals service allows you to copy the trades of more experienced traders to your MT5 account. Once you’ve subscribed to a signal, the provider’s deals will be automatically replicated on your Deriv MT5 trading account each time they place a trade.", "209799068": "A contract for difference (CFD) allows you to trade on the price movement of an asset, without buying the underlying asset.", "210989591": "Add a Deriv X real account.", @@ -318,6 +324,7 @@ "316920917": "Oops, sorry...", "317551740": "$ 8.00", "317744607": "Discover DBot now", + "317753588": "Second website/social media URL", "318239044": "Pfizer", "318433321": "Instruments available for options trading", "318815297": "Trade the way you like", @@ -435,6 +442,7 @@ "431284069": "Your net profit will depend on how much the spot price falls below the barrier, with the maximum potential gains growing if the underlying price falls significantly. Your losses are limited to the initial stake required to purchase the short turbo option.", "432518896": "Client trust is our highest priority, and that’s why millions of users choose us. Here are some of the things that make us a leading online trading service provider.", "432775041": "Get Trading", + "433348384": "Real accounts are not available to politically exposed persons (PEPs).", "434113887": "Frequently asked questions - Deriv MT5", "434254071": "rc rp", "434669878": "Best forex spread award", @@ -641,6 +649,7 @@ "618098617": "Your IB commissions earned from MT5 and Deriv EZ are credited directly into your Deriv MT5 account daily. You can transfer the funds to your Deriv account and withdraw them via your preferred payment method.", "618453683": "On Deriv, you can trade CFDs with high leverage, enabling you to pay just a fraction of the contract’s value. It will amplify your potential gain and also increase your potential loss.", "619354706": "Max(6.83, -10) ", + "619481945": "Mobile number is required", "619610610": "Close your account at any time you want.", "621291584": "0.75", "622253642": "Save time – no need to open, monitor, and close trades.", @@ -726,6 +735,7 @@ "699722384": "1. You must have an MQL5 community account to subscribe to trading signals. If you don't have an account yet, please go to <0>MQL5.com to register.", "700128691": "We’re steadfast in our commitment to high ethical standards. Find more reasons to know why Deriv is the choice of online traders.", "700971061": "You can find your Deriv MT5 server name on your <0>Deriv MT5 dashboard. Click Trade on your preferred MT5 account and look for the server below the broker name.", + "701236336": "Website url", "701775446": "Volatility 200 Index", "701840176": "Commission per USD 100k turnover", "702074152": "PARTNERS", @@ -891,10 +901,12 @@ "856928872": "Yes, as long as you pass our checks. Initially, you'll start with a 500 USD limit for buy and sell orders.", "860052562": "Airbnb", "861659702": "Easy-to-use platforms, superior charts", + "862283602": "Phone number*", "862480295": "Our swap calculator helps you to estimate the swap charges required to keep your positions open overnight on Deriv MT5.", "866130068": "product department", "866196404": "What are the major differences between DTrader, Deriv MT5 and Deriv X?", "867182964": "6,000 + 2,250 = USD 8,250", + "867244595": "We’re unable to process your sign-up request at this time. Please try again.", "867458310": "No, the trade barrier can only be set before you open a contract. Once your turbo options contract is opened, the barrier cannot be changed.", "867582421": "Help centre | Frequently asked questions | Account | Deriv", "868154618": "Accounts", @@ -940,6 +952,7 @@ "897732167": "Deriv MT5 logo", "898646134": "To see your account’s trading limits, go to Settings > Security and safety > <0>Account limits.", "899411431": "Once you’re shown options to select from, choose “Complaints”. You can also type \"complaint\".", + "899612897": "You should enter 8-13 numbers.", "901158881": "This feature allows you to set the level of profit that you are comfortable with when the market moves in your favour. Once the amount is reached, your position will be closed automatically and your earnings will be deposited into your Deriv account.", "902057285": "Click Yes to confirm the cancellation. Your funds will be returned to your Deriv account, and your account balance will be updated accordingly.", "903422317": "Get instant access to your trades wherever you are whenever you want.", @@ -975,6 +988,7 @@ "934685291": "This helps you to set the stop loss and take profit level when the asset price moves in Up or Down direction.", "935167918": "3.75", "935645176": "Additional terms and restrictions for Deriv clients in certain countries", + "937831119": "Last name*", "938314093": "ARRK.US", "938692453": "Swaps charged upon rollover for short (sell) positions.", "938792466": "Customise your contracts to suit your style and risk appetite using innovative features like stop loss, take profit, and deal cancellation.", @@ -986,6 +1000,7 @@ "943716510": "Introduction to cryptocurrencies", "946654704": "Client trades with a stake of USD 10.", "947541466": "Trade 24/7 on our proprietary synthetics that simulate real-world market movements. These indices are unaffected by regular
market hours, global events, or market and liquidity risks. Manage your exposure by selecting the volatility level to suit your risk appetite.", + "947758334": "City is required", "948545552": "150+", "949495445": "even/odd option trade", "949617971": "Your Deriv X password is linked to the standalone Deriv X trading platform while your Deriv password gives you access to platforms hosted on our website such as DTrader and DBot.", @@ -1062,6 +1077,7 @@ "1025032938": "Do I have to pay any fees to become a payment agent for Deriv?", "1026160856": "Predict whether the last digit of the last tick of a contract will be an even number or an odd number.", "1026845997": "Endpoint", + "1027349186": "First Name", "1029179269": "Set profit level", "1030898603": "What is the maximum I can lose when I trade turbo options?", "1032907147": "AUD/NZD", @@ -1077,6 +1093,7 @@ "1040447467": "|(1.1750", "1040702244": "Turbo options", "1041405933": "Read <0>this article to learn more about automated trading on Deriv.", + "1042007913": " (${id} of ${steps.length})", "1045309798": "Swap rates (overnight funding)", "1045757354": "Estimate the level and amount of the stop loss and take profit for your contract to mitigate your risk in case the market price moves against your prediction.", "1046578159": "GBP/SGD", @@ -1107,6 +1124,7 @@ "1070284669": "<0>Stop loss and take profit level in the Down direction = asset price × {(-stop loss OR take profit amount - commission) ÷ (stake × multiplier) + 1}", "1070403135": "weekend trading", "1070720669": "Enjoy trading asset prices derived from
simulated markets.", + "1072012627": "Country is required", "1072128975": "Once your identity has been verified, you can use Deriv P2P on desktop or mobile.", "1072571985": "If you created your Deriv account using Apple/Google/Facebook, try resetting your Apple/Google/Facebook password. After that, you should be able to log in to Deriv as usual.", "1076548216": "Leverage Deriv’s technology to launch your own trading app. Deliver an enhanced trading experience to your clients and earn from every trade executed on your app.", @@ -1197,6 +1215,7 @@ "1169889619": "Already have an account? <0>Log in", "1170266535": "<0>Deriv Trader offers multipliers trading on a range of underlying assets such as forex, cryptocurrencies, and derived. This platform allows you to open multipliers trades that offer the opportunity to multiply potential profit without risking more than your stake. You may find this platform more intuitive if you're new to the trading world.", "1171083830": "Hit <0>Get next to <0>Deriv account.", + "1171332705": "Username*", "1171765024": "Step 3", "1172611503": "If you select ‘Asian Fall’, you will win the payout if the last tick is lower than the average of the ticks.", "1172806588": "Make instant deposits using your local e-wallet.", @@ -1350,9 +1369,11 @@ "1312819271": "Let’s say you want to trade 2 lots of EUR/USD.", "1313291854": "If you select 'Reset Call', you receive the payout if the exit price is higher than either the entry price or the price at reset time.", "1316697520": "Markets available for multipliers trading", + "1319030942": "Signup failed", "1320418221": "Estimate the margin you need to hold your positions. The result depends on leverage, volume lot, and your Deriv MT5 account balance.", "1323941798": "Short", "1324376913": "Our values are the fabric of our culture", + "1325181938": "Street*", "1326142154": "high-low option trade", "1326235397": "These indices correspond to simulated markets with constant volatilities of 10%, 25%, 50%, 75%, and 100%.", "1327067520": "Deriv P2P makes withdrawals and deposits simple, it’s the best user-friendly app.", @@ -1395,6 +1416,7 @@ "1364972610": "How do High/Low Ticks contracts work?", "1365926355": "Deriv X trading paltform", "1367358043": "<0>USD 200 Volume", + "1367856592": "Choose which plan you would like to subscribe.", "1367990698": "Volatility 10 Index", "1370484308": "A chart illustrating a losing High Ticks trade type contract", "1370655673": "Affiliate and IB programmes | Partnership programmes | Deriv", @@ -1408,6 +1430,7 @@ "1376955476": "Swap charge", "1377692190": "1. Go to your Deriv X dashboard.", "1380047398": "Scan to download", + "1380676466": " is required", "1384321002": "If you are not satisfied with the outcome, you can escalate your complaint, provided that the <0>complaints policy associated with your account states that escalation is possible.", "1384941966": "Citigroup", "1385878133": "No money", @@ -1453,6 +1476,7 @@ "1413295628": "DIA.US", "1413567719": "What is payout per point?", "1413977299": "You can trade Digits contracts on the Deriv Trader, SmartTrader, and Deriv Bot trading platforms, on synthetics.", + "1414394793": "Country*", "1415101574": "We have a diverse suite of 7 trading platforms: <0>Deriv MT5, <1>Deriv X, <2>Deriv Trader, <3>Deriv Bot, <4>Deriv GO, <5>SmartTrader, and <6>Binary Bot. Each of these platforms is designed to fit any trading style, regardless of your trading experience.", "1416184764": "How can I identify Deriv customer support impersonators on Telegram?", "1416248668": "Choose the best rates", @@ -1477,6 +1501,7 @@ "1435321289": "Process all deposits and withdrawals quickly and accurately", "1435793182": "Predict whether the exit spot will be higher or lower than either the entry spot or the spot at reset time.", "1437505451": "Pip Calculator", + "1438632095": "Please enter a valid company registration number.", "1442199408": "Deriv MT5 trading platform", "1446857185": "
  1. Select the market and asset you want to trade.
  2. Choose which Digits contract to open (Matches/Differs; Even/Odd; Over/Under).
  3. Set your preferred tick duration and last digit prediction.
  4. Enter your stake or desired potential payout amount.
  5. Open your Digits trade.
", "1446914689": "Guernsey alt img", @@ -1559,6 +1584,7 @@ "1516021730": "20 - 1,000", "1516676261": "Deposit", "1518575473": "Uptime", + "1519951161": "Space not available", "1523687535": "We don’t pay commission, but you can set your own commission rate per transaction within reasonable thresholds. When you sign up, our team will be in touch to work out the details with you.", "1524248407": "Trade on financial markets plus our proprietary synthetics that are available 24/7.", "1524725659": "When will the commission be paid out?", @@ -1571,6 +1597,7 @@ "1529762880": "You must have a minimum balance in your Deriv account while your application is under review. The amount of this balance depends on your country of residence. You only need to maintain the minimum balance until your application is successful.", "1529770577": "Derived indices consist of asset prices generated from real-world and simulated markets and indices, with little to no influence from real-world events. You can trade from a variety of derived indices, including synthetic indices, derived FX indices, and baskets.", "1533284111": "What is a floating exchange rate?", + "1533452395": "Earn based on each contract's payout probability or client's trade. <0>Learn more", "1535175777": "Make deposits and withdrawals through your local bank in real-time.", "1536955851": "Options trading | Trading types | Deriv", "1537212469": "Spot price", @@ -1620,6 +1647,7 @@ "1573231398": "Why trade cryptocurrencies on Deriv", "1573429525": "Call/Put", "1573589662": "Do keep in mind that we do not allow unauthorised incentives, gifts, and payments to encourage client signups. Payment will be withheld if any suspicions or activity of this conduct is discovered.", + "1574365902": "Please enter a valid url", "1574476135": "Let’s say you predict that the market will go up.", "1575069722": "Here are some of the ways we ensure that Deriv P2P is as secure as possible:", "1575147178": "Only ups/Only downs | Digital options contract | Deriv", @@ -1650,6 +1678,7 @@ "1597478217": "No, you cannot close an Asians contract before it expires.", "1598664071": "List of directors", "1599891822": "Stop loss amount in Down direction", + "1602016750": "Company registration number*", "1602627054": "So your pip value is <0>2 USD.", "1602716515": "When you join our IB programme,", "1602800752": "Save your strategies", @@ -1671,6 +1700,7 @@ "1614573899": "Open a digital options contract with an Asians trade type on Deriv’s trading platforms and earn payouts with accurate market predictions.", "1616071522": "<0>France 40 tracks the performance of the 40 most traded stocks among the top 100 listed companies in France.", "1616502762": "<0>US Tech 100 follows the stock performance of the 100 largest non-financial companies in the US.", + "1617759657": "Already have a Deriv affiliate account? ", "1618280107": "Offer competitive prices on all our products", "1619070150": "You are being redirected to an external website.", "1619579199": "Why is my Deriv P2P balance different from my Deriv account balance?", @@ -1758,6 +1788,7 @@ "1686378613": "DBot is a web-based strategy builder for trading digital options. It’s a platform where you can build your own trading bot using drag-and-drop 'blocks'.", "1688330506": "<0>221.5961", "1688556728": "European indices", + "1690730424": "Only Latin and Alphabet characters", "1691701175": "Stock & indices", "1691844143": " Based on the number of ticks and the accumulator value, your potential profit may exceed your set amount multiple times.", "1692056155": "Unsubscribe | Emails | Deriv", @@ -1767,6 +1798,7 @@ "1694777943": "A user-friendly trading platform", "1695892307": "Can I enter multiple lookbacks contracts simultaneously?", "1696396625": "google", + "1698515050": "Create partner account", "1700291683": "It’s completely free of charge to join the IB programme.", "1701577545": "The maximum duration for every contract is limited and differs according to the chosen accumulator value. When the maximum duration is reached, the contract will be automatically closed.", "1702295087": "Make easy deposits and withdrawals using Vietnam's famous banks.", @@ -1819,6 +1851,7 @@ "1748626120": "No hidden costs", "1748670878": "Can I use my Binance wallet to make the cryptocurrency deposit?", "1748925202": "How can I reactivate my Deriv MT5 account?", + "1752444975": "Company second website/social media URL", "1752586208": "What payment methods can I use to exchange with other traders?", "1752611984": "Open a digital options contract with an Up/Down trade type on Deriv’s trading platforms and earn payouts with accurate market predictions.", "1753559268": "We'll pay your IB commission into your MT5 Synthetic account daily.", @@ -1937,6 +1970,7 @@ "1862283751": "Trade forex, synthetic indices, and cryptocurrencies wherever, whenever you want and maximise your potential profit with multipliers on Deriv GO.", "1862328242": "success", "1867740000": "GBP/USD DFX10 Index", + "1868644151": "Partner with us as an affiliate. Earn commission from the total net revenue of your referred clients’ from option trading platforms.", "1868860566": "EUR/SEK", "1869230144": "<0>-7.23", "1869497149": "Enjoy a smooth trading experience with easy-to-use charts and a pleasant dark theme.", @@ -1945,6 +1979,7 @@ "1872216697": "Yes, you can choose the duration of your Asians contract anywhere between 5 and 10 ticks.", "1872568231": "GBP/TRY", "1872607463": "banner", + "1873174451": "Postcode is required", "1873182175": "Apple account sign in page", "1873188024": "A deal for 1 lot of the Volatility Index 75 for a price of USD 500,000 per USD 100,000 turnover will pay out a commission of USD 5. The minimum volume required to receive the minimum commission of USD 0.01 is determined using this formula:", "1874424045": "I lost my phone. How can I disable two-factor authentication (2FA)?", @@ -2155,6 +2190,7 @@ "2045764916": "0.0006631", "2046788283": "What markets can I trade on DTrader?", "2048307729": "Help Deriv clients to make multiple deposits and withdrawals daily.", + "2049136184": "Username is required", "2049247194": "20-22 November 2023", "2049759467": "Can I customise the duration for an Only Ups/Only Downs contract?", "2051986867": "So you will require a swap charge of <0>0.24 USD to keep the position open for one night.", @@ -2185,6 +2221,8 @@ "2065278286": "Spread", "2067757832": "Derived Indices", "2068595952": "What is derived?", + "2069455291": "Date of Birth*", + "2069660515": "Postal/Zip code*", "2070356006": "Start earning based on your chosen commission plan –– up to 45% of the total net revenue generated by your referred clients.", "2070897729": "Trade CFDs on forex, commodities, cryptocurrencies, stocks, stock indices, and derived indices.", "2071458958": "The minimum volume for micro forex pairs is 0.1 lot.", @@ -2207,6 +2245,7 @@ "2089087110": "Basket indices", "2089257874": "internal audit department", "2092654058": "To onboard with Deriv Prime’s liquidity solution and trading services, you will need to follow a streamlined process:", + "2094921906": "Street is required", "2095230169": "Reliable", "2097023111": "A low-cost way to trade diverse asset groups", "2097310605": "Why trade synthetics on Deriv", @@ -2222,6 +2261,8 @@ "2108799022": "<0>Rise/Fall: Predict whether the exit spot will be strictly higher or lower than the entry spot at the end of the contract period.", "2109279238": "derived", "2109387636": "How many trading platforms do you offer?", + "2109960759": "Company name is required", + "2110013669": "Password*", "2112006652": "FIL/USD", "2112510770": "Check if your browser and device are updated. If they're not, compatibility issues may prevent the email from reaching you.", "2115934251": "When you open a turbo options contract, you’ll pay an initial stake amount. This is the cost of entering the turbo options trade. You’ll also need to determine a trade barrier. The spot price must not touch or breach this barrier throughout your contract duration, otherwise your contract will be terminated early without any gains.", @@ -4155,6 +4196,59 @@ "-880201385": "info", "-771133354": "For more details on our products and the risks involved in online trading, read our <0>key information documents (KIDs) on forex, stocks, stock indices, commodities, synthetic indices, and cryptocurrencies.", "-466084206": "Read our guidelines on secure and responsible trading. Understand the risks involved in online trading and how you can manage them.", + "-333013269": "Personal address", + "-794179340": "Company address", + "-136976514": "Country of residence*", + "-1474274516": "State/province*", + "-1253349870": "Town/city*", + "-1120954663": "First name*", + "-1929304521": "Website/social media URL*", + "-1113902570": "Details", + "-303056592": "Affiliate account details", + "-1441847447": "Earn based on the monthly net revenue generated by your client. <0>Learn more", + "-1674476166": "Earn based on each successful referral. <0>Learn more", + "-63489872": "<0>Note: This plan is available exclusively for EU-based clients only.", + "-2068229627": "I am not a PEP, and I have not been a PEP in the last 12 months.", + "-1841395447": "I have read and accepted <0>Deriv’s terms and conditions", + "-412683223": "I have read and accepted <0>Deriv’s general terms of use and affiliates and introducing brokers’ terms and conditions", + "-1805422644": "I consent to receive promotional materials and notifications regarding your partnership program.", + "-925958201": "Individual", + "-1468933628": "Register for an individual account.", + "-2147340077": "Company", + "-1412113272": "Register for a company account.", + "-1808676023": "Deriv Affiliate", + "-1918023753": "Our introducing broker programme is available to all Deriv affiliates. Earn commission from your clients' trades on Deriv's CFD trading platforms.", + "-498923246": "Want to sign up as a trader?", + "-1760596315": "Create a Deriv account", + "-1986150937": "Thank you for signing up", + "-1598032031": "Your application has been received. We’re processing your application. You can expect to hear back from us within 3 to 5 business days.", + "-2017825013": "Got it", + "-1588894283": "Are you sure you want to cancel your registration?", + "-1057336268": "No, continue", + "-1830091000": "Username already exists. Please enter another:", + "-573454478": "User name", + "-260364285": "Change username", + "-830224473": "Your website is not a valid entry. Please enter another:", + "-1129605560": "Change website url", + "-1085862265": "Subscription plan", + "-730447440": "Address details", + "-1823540512": "Personal details", + "-204765990": "Terms of use", + "-1501151867": "Step ${id} : ", + "-1541554430": "Next", + "-26610045": "Add an affiliate account", + "-435672026": "Wrong email", + "-265889213": "Empty input not available", + "-1787546919": "You should enter ${min_digit}-${max_digit} characters.", + "-442533603": "Incorrect company name", + "-840841662": "Only Latin characters", + "-1430656728": "Password is required", + "-1049534775": "Please enter a valid postcode with Latin characters.", + "-420140433": "Company registration number is required", + "-2146980219": "Please enter a valid state", + "-1857429287": "Last Name", + "-378415317": "State is required", + "-1478320346": "Currency is required", "-706927392": "Important Guidelines", "-2086585870": "Partners Guide", "-776458472": "General terms of use", diff --git a/package-lock.json b/package-lock.json index 36afb983d4b..c2e1c732f7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@artsy/fresnel": "^6.1.0", "@builder.io/partytown": "^0.8.1", - "@deriv/analytics": "^1.3.4", + "@deriv/analytics": "^1.4.4", "@deriv/deriv-api": "^1.0.11", "@hookform/resolvers": "^3.0.1", "@livechat/customer-sdk": "^3.1.0", @@ -62,6 +62,7 @@ "prompt-sync": "^4.2.0", "prop-types": "^15.8.1", "react": "^18.2.0", + "react-date-picker": "8.4.0", "react-device-detect": "^2.2.2", "react-dom": "^18.2.0", "react-hook-form": "^7.43.9", @@ -2953,9 +2954,9 @@ } }, "node_modules/@deriv/analytics": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@deriv/analytics/-/analytics-1.3.4.tgz", - "integrity": "sha512-Tz0q6UK0shReMAoB9Zt4HPRMcY2v5OZbImCPvtSOtlgoGhWmmwTJjUDbBcXTTs/g4VJj0Nv5rzCONDNy6XunYA==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@deriv/analytics/-/analytics-1.4.4.tgz", + "integrity": "sha512-brMCtAel5z4GOnDA32dM93buNu9obtqz6+SlzQuXnKdsonF/YaY+XjNC+rmURnV4NqTUq5nRJyIxY6h9kitsrw==", "dependencies": { "@growthbook/growthbook": "^0.29.0", "rudder-sdk-js": "^2.35.0" @@ -16988,6 +16989,14 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-calendar": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@types/react-calendar/-/react-calendar-3.9.0.tgz", + "integrity": "sha512-KpAu1MKAGFw5hNwlDnWsHWqI9i/igAB+8jH97YV7QpC2v7rlwNEU5i6VMFb73lGRacuejM/Zd2LklnEzkFV3XA==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-dom": { "version": "18.2.14", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", @@ -17970,6 +17979,14 @@ "tslib": "^2.3.1" } }, + "node_modules/@wojtekmaj/date-utils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz", + "integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==", + "funding": { + "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -23379,6 +23396,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/detect-element-overflow": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/detect-element-overflow/-/detect-element-overflow-1.4.2.tgz", + "integrity": "sha512-4m6cVOtvm/GJLjo7WFkPfwXoEIIbM7GQwIh4WEa4g7IsNi1YzwUsGL5ApNLrrHL29bHeNeQ+/iZhw+YHqgE2Fw==", + "funding": { + "url": "https://github.com/wojtekmaj/detect-element-overflow?sponsor=1" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -29489,6 +29514,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-user-locale": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-1.5.1.tgz", + "integrity": "sha512-WiNpoFRcHn1qxP9VabQljzGwkAQDrcpqUtaP0rNBEkFxJdh4f3tik6MfZsMYZc+UgQJdGCxWEjL9wnCUlRQXag==", + "dependencies": { + "lodash.memoize": "^4.1.1" + }, + "funding": { + "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1" + } + }, "node_modules/get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -37170,6 +37206,14 @@ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, + "node_modules/make-event-props": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.6.2.tgz", + "integrity": "sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==", + "funding": { + "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" + } + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -37464,11 +37508,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-class-names": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/merge-class-names/-/merge-class-names-1.4.2.tgz", + "integrity": "sha512-bOl98VzwCGi25Gcn3xKxnR5p/WrhWFQB59MS/aGENcmUc6iSm96yrFDF0XSNurX9qN4LbJm0R9kfvsQ17i8zCw==", + "funding": { + "url": "https://github.com/wojtekmaj/merge-class-names?sponsor=1" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, + "node_modules/merge-refs": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.2.2.tgz", + "integrity": "sha512-RwcT7GsQR3KbuLw1rRuodq4Nt547BKEBkliZ0qqsrpyNne9bGTFtsFIsIpx82huWhcl3kOlOlH4H0xkPk/DqVw==", + "funding": { + "url": "https://github.com/wojtekmaj/merge-refs?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -44080,6 +44148,48 @@ "node": ">=0.10.0" } }, + "node_modules/react-calendar": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/react-calendar/-/react-calendar-3.9.0.tgz", + "integrity": "sha512-g6RJCEaPovHTiV2bMhBUfm0a1YoMj4bOUpL8hQSLmR1Glhc7lgRLtZBd4mcC4jkoGsb+hv9uA/QH4pZcm5l9lQ==", + "dependencies": { + "@wojtekmaj/date-utils": "^1.0.2", + "get-user-locale": "^1.2.0", + "merge-class-names": "^1.1.1", + "prop-types": "^15.6.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-calendar?sponsor=1" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-date-picker": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/react-date-picker/-/react-date-picker-8.4.0.tgz", + "integrity": "sha512-zocntugDUyiHmV2Nq1qnsk4kDQuhBLUsDTz7akfIEJ0jVX925w0K5Ai5oZzWFNQOzXL/ITxafmDMuSbzlpBt/A==", + "dependencies": { + "@types/react-calendar": "^3.0.0", + "@wojtekmaj/date-utils": "^1.0.3", + "get-user-locale": "^1.2.0", + "make-event-props": "^1.1.0", + "merge-class-names": "^1.1.1", + "merge-refs": "^1.0.0", + "prop-types": "^15.6.0", + "react-calendar": "^3.3.1", + "react-fit": "^1.4.0", + "update-input-width": "^1.2.2" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-date-picker?sponsor=1" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -44270,6 +44380,33 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, + "node_modules/react-fit": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/react-fit/-/react-fit-1.7.1.tgz", + "integrity": "sha512-y/TYovCCBzfIwRJsbLj0rH4Es40wPQhU5GPPq9GlbdF09b0OdzTdMSkBza0QixSlgFzTm6dkM7oTFzaVvaBx+w==", + "dependencies": { + "detect-element-overflow": "^1.4.0", + "prop-types": "^15.6.0", + "tiny-warning": "^1.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-fit?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/react-hook-form": { "version": "7.47.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.47.0.tgz", @@ -49566,6 +49703,14 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, + "node_modules/update-input-width": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/update-input-width/-/update-input-width-1.4.2.tgz", + "integrity": "sha512-/p0XLhrQQQ4bMWD7bL9duYObwYCO1qGr8R19xcMmoMSmXuQ7/1//veUnCObQ7/iW6E2pGS6rFkS4TfH4ur7e/g==", + "funding": { + "url": "https://github.com/wojtekmaj/update-input-width?sponsor=1" + } + }, "node_modules/upper-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", diff --git a/package.json b/package.json index 0033f4a57ba..4fbdbab34fa 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dependencies": { "@artsy/fresnel": "^6.1.0", "@builder.io/partytown": "^0.8.1", - "@deriv/analytics": "^1.3.4", + "@deriv/analytics": "^1.4.4", "@deriv/deriv-api": "^1.0.11", "@hookform/resolvers": "^3.0.1", "@livechat/customer-sdk": "^3.1.0", @@ -57,6 +57,7 @@ "prompt-sync": "^4.2.0", "prop-types": "^15.8.1", "react": "^18.2.0", + "react-date-picker": "8.4.0", "react-device-detect": "^2.2.2", "react-dom": "^18.2.0", "react-hook-form": "^7.43.9", diff --git a/src/components/custom/signup.tsx b/src/components/custom/signup.tsx index 05b190c063a..995909e9788 100644 --- a/src/components/custom/signup.tsx +++ b/src/components/custom/signup.tsx @@ -1,9 +1,9 @@ import React, { useState } from 'react' -import { graphql, useStaticQuery, navigate } from 'gatsby' +import { graphql, navigate, useStaticQuery } from 'gatsby' import styled from 'styled-components' import Cookies from 'js-cookie' import { getLanguage } from '../../common/utility' -import { getCookiesObject, getCookiesFields, getDataObjFromCookies } from 'common/cookies' +import { getCookiesFields, getCookiesObject, getDataObjFromCookies } from 'common/cookies' import { Flex } from 'components/containers' import Login, { TSocialProvider } from 'common/login' import validation from 'common/validation' diff --git a/src/components/elements/dropdown-search.tsx b/src/components/elements/dropdown-search.tsx index ae672a8a6d9..2fd3ed7d693 100644 --- a/src/components/elements/dropdown-search.tsx +++ b/src/components/elements/dropdown-search.tsx @@ -92,6 +92,10 @@ const DropdownSearch = ({ } }, [selected_item]) + useEffect(() => { + setDropdownItems([...items]) + }, [items]) + const handleInputChange = (e) => { setInputValue(e.target.value) toggleListVisibility(e) diff --git a/src/components/elements/dropdown.tsx b/src/components/elements/dropdown.tsx index f6df0bcf1c9..0d3b9a611ae 100644 --- a/src/components/elements/dropdown.tsx +++ b/src/components/elements/dropdown.tsx @@ -247,7 +247,6 @@ const UnorderedList = styled.ul` position: absolute; left: 0; top: 0.8rem; - border-radius: 4px; box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.16); transition: opacity 0.1s cubic-bezier(0, 0, 0.38, 0.9), max-height 0.25s cubic-bezier(0, 0, 0.38, 0.9); @@ -313,7 +312,7 @@ export const StyledLabel = styled.label` color: var(--color-green); transform: translate(-0.6rem, -2.2rem) scale(0.7); @media ${device.tabletL} { - top: 9px; + top: 15px; } ` }} diff --git a/src/components/form/input.tsx b/src/components/form/input.tsx index 479d74a42a0..7de2314369f 100644 --- a/src/components/form/input.tsx +++ b/src/components/form/input.tsx @@ -22,7 +22,7 @@ interface ReactInput extends React.ComponentPropsWithoutRef<'input'> { ) => void } -type InputProps = ReactInput & InputWrapperProps & StyledInputProps & StyledLabelProps +export type InputProps = ReactInput & InputWrapperProps & StyledInputProps & StyledLabelProps type InputWrapperProps = { border?: string @@ -53,10 +53,10 @@ type StyledLabelProps = { htmlFor?: string } -const RelativeWrapper = styled.div` +export const RelativeWrapper = styled.div` position: relative; ` -const InputWrapper = styled.div` +export const InputWrapper = styled.div` /* prettier-ignore */ width: 100%; border: ${(props) => props.border || '1px solid var(--color-grey-2)'}; @@ -96,7 +96,7 @@ const InputWrapper = styled.div` `} ` -const StyledError = styled.img` +export const StyledError = styled.img` position: absolute; right: 0.8rem; top: 1.2rem; @@ -112,7 +112,7 @@ const StyledError = styled.img` } ` -const StyledInput = styled.input` +export const StyledInput = styled.input` color: ${({ inputColor }) => inputColor ? `var(--color-${inputColor})` : 'var(--color-black)'}; font-size: var(--text-size-s); @@ -130,7 +130,7 @@ const StyledInput = styled.input` & ~ label { font-size: 1.75rem; - top: 1.95rem; + top: 15px; } } @media ${device.mobileL} { @@ -154,7 +154,7 @@ const StyledInput = styled.input` background-color: ${({ background }) => background ? `var(--color-${background})` : 'var(--color-grey-1)'}; - @media ${device.mobileL} { + @media ${device.tablet} { transform: translate(-0.6rem, -20px) scale(0.7); } } @@ -179,21 +179,21 @@ const StyledInput = styled.input` background ? `var(--color-${background})` : 'var(--color-grey-1)'}; @media ${device.tabletL} { - top: 9px; + top: 15px; } } `} } ` -const ErrorMessages = styled(Text)` +export const ErrorMessages = styled(Text)` padding: 6px; font-size: 1.2rem; min-height: 16px; color: var(--color-red-1); ` -const StyledLabel = styled.label` +export const StyledLabel = styled.label` color: ${({ label_color }) => label_color ? `var(--color-${label_color})` : 'var(--color-grey)'}; font-size: var(--text-size-xs); diff --git a/src/components/layout/layout.tsx b/src/components/layout/layout.tsx index fd5cf15bfcd..8f6f04dc515 100644 --- a/src/components/layout/layout.tsx +++ b/src/components/layout/layout.tsx @@ -15,6 +15,7 @@ import BugBountyNav from 'features/components/templates/navigation/bug-bounty-na import CareerNav from 'features/components/templates/navigation/career-nav' import MarketNav from 'features/components/templates/navigation/market-nav' import PpcProvider from 'features/contexts/ppc-campaign/ppc.provider' +import AffiliateNav from 'features/components/templates/navigation/affiliates' import BannerAlert from 'components/custom/_banner-alert' import { bannerTypes } from 'common/constants' @@ -80,6 +81,7 @@ const Navs = { careers: , 'payment-methods': , + affiliates: , } const Layout = ({ diff --git a/src/features/components/templates/navigation/affiliates/index.tsx b/src/features/components/templates/navigation/affiliates/index.tsx new file mode 100644 index 00000000000..118e8a537d9 --- /dev/null +++ b/src/features/components/templates/navigation/affiliates/index.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import Link from 'features/components/atoms/link' +import Image from 'features/components/atoms/image' +import NavTemplate from 'features/components/templates/navigation/template' +import LanguageSwitcher from 'features/components/molecules/language-switcher' +import { partners_nav_logo } from 'features/components/templates/navigation/payment-agent-nav/payment-agent-nav.module.scss' +import PartnerNavLogo from 'images/svg/partner-nav-logo.svg' + +const AffiliateNav = () => { + return ( + ( + + {'nav + + )} + has_centered_items + > + + + ) +} + +export default AffiliateNav diff --git a/src/features/hooks/use-residence-list/index.tsx b/src/features/hooks/use-residence-list/index.tsx index e942aeefb8e..1d8cd5eef1d 100644 --- a/src/features/hooks/use-residence-list/index.tsx +++ b/src/features/hooks/use-residence-list/index.tsx @@ -1,29 +1,46 @@ import { useEffect, useMemo } from 'react' -import useWS from '../../../components/hooks/useWS' +import useWS from 'components/hooks/useWS' export type ResidenceType = { text: string value: string } -const formatResidenceList = (residences) => { +const formatResidenceList = ({ + residences, + restricted, +}: { + residences: any[] + restricted: string[] +}) => { if (!residences.length) { return [] } - return residences?.map(({ text: display_name, text: name, value: symbol }) => ({ - name, - display_name, - symbol, - })) + return residences + .filter(({ text: name }) => !restricted.includes(name)) + .map(({ text: display_name, text: name, value: symbol, phone_idd: prefix }) => { + return { + name, + display_name, + symbol, + prefix, + } + }) } -export const useResidenceList = () => { +export const useResidenceList = ({ + restricted_countries = [], +}: { + restricted_countries?: string[] +} = {}) => { const { send, data } = useWS('residence_list') - useEffect(() => { send() }, [send]) - const residence_list = useMemo(() => formatResidenceList(data || []), [data]) + const residence_list = useMemo( + () => formatResidenceList({ residences: data || [], restricted: restricted_countries }), + [data], + ) return [residence_list] } diff --git a/src/features/hooks/use-states-list/index.tsx b/src/features/hooks/use-states-list/index.tsx new file mode 100644 index 00000000000..4e51087e79a --- /dev/null +++ b/src/features/hooks/use-states-list/index.tsx @@ -0,0 +1,32 @@ +import { useEffect, useMemo } from 'react' +import useWS from '../../../components/hooks/useWS' + +export type ResidenceType = { + text: string + value: string +} + +const formatStatesList = (states) => { + if (!states.length) { + return [] + } + + return states?.map( + ({ text }) => + typeof text !== undefined && { + name: text, + display_name: text, + }, + ) +} + +export const useStatesList = (country_code) => { + const { send, data } = useWS('states_list') + + useEffect(() => { + send({ states_list: country_code }) + }, [country_code]) + + const states_list = useMemo(() => formatStatesList(data || []), [data]) + return [states_list] +} diff --git a/src/images/svg/signup-affiliates/aud.svg b/src/images/svg/signup-affiliates/aud.svg new file mode 100644 index 00000000000..bbbae46732c --- /dev/null +++ b/src/images/svg/signup-affiliates/aud.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/images/svg/signup-affiliates/calendar.svg b/src/images/svg/signup-affiliates/calendar.svg new file mode 100644 index 00000000000..c42a342baf2 --- /dev/null +++ b/src/images/svg/signup-affiliates/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/svg/signup-affiliates/captcha.svg b/src/images/svg/signup-affiliates/captcha.svg new file mode 100644 index 00000000000..65f3ef23080 --- /dev/null +++ b/src/images/svg/signup-affiliates/captcha.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/images/svg/signup-affiliates/closed-eye.svg b/src/images/svg/signup-affiliates/closed-eye.svg new file mode 100644 index 00000000000..bffa58b6d0e --- /dev/null +++ b/src/images/svg/signup-affiliates/closed-eye.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/images/svg/signup-affiliates/code.svg b/src/images/svg/signup-affiliates/code.svg new file mode 100644 index 00000000000..9ca0608abdb --- /dev/null +++ b/src/images/svg/signup-affiliates/code.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/images/svg/signup-affiliates/company.svg b/src/images/svg/signup-affiliates/company.svg new file mode 100644 index 00000000000..f55887a7ddc --- /dev/null +++ b/src/images/svg/signup-affiliates/company.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/images/svg/signup-affiliates/cpa.svg b/src/images/svg/signup-affiliates/cpa.svg new file mode 100644 index 00000000000..3ef95956a05 --- /dev/null +++ b/src/images/svg/signup-affiliates/cpa.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/images/svg/signup-affiliates/eur.svg b/src/images/svg/signup-affiliates/eur.svg new file mode 100644 index 00000000000..4f6ba32ee2d --- /dev/null +++ b/src/images/svg/signup-affiliates/eur.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/images/svg/signup-affiliates/failed.svg b/src/images/svg/signup-affiliates/failed.svg new file mode 100644 index 00000000000..89263f19085 --- /dev/null +++ b/src/images/svg/signup-affiliates/failed.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/images/svg/signup-affiliates/gbp.svg b/src/images/svg/signup-affiliates/gbp.svg new file mode 100644 index 00000000000..596539469f9 --- /dev/null +++ b/src/images/svg/signup-affiliates/gbp.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/images/svg/signup-affiliates/individual.svg b/src/images/svg/signup-affiliates/individual.svg new file mode 100644 index 00000000000..8f1e51ec3d3 --- /dev/null +++ b/src/images/svg/signup-affiliates/individual.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/images/svg/signup-affiliates/map.svg b/src/images/svg/signup-affiliates/map.svg new file mode 100644 index 00000000000..8503e1bf224 --- /dev/null +++ b/src/images/svg/signup-affiliates/map.svg @@ -0,0 +1,3187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/svg/signup-affiliates/opened-eye.svg b/src/images/svg/signup-affiliates/opened-eye.svg new file mode 100644 index 00000000000..92b93baa099 --- /dev/null +++ b/src/images/svg/signup-affiliates/opened-eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/svg/signup-affiliates/revenue.svg b/src/images/svg/signup-affiliates/revenue.svg new file mode 100644 index 00000000000..f3b20d1323b --- /dev/null +++ b/src/images/svg/signup-affiliates/revenue.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/images/svg/signup-affiliates/selected.svg b/src/images/svg/signup-affiliates/selected.svg new file mode 100644 index 00000000000..0e428be4e23 --- /dev/null +++ b/src/images/svg/signup-affiliates/selected.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/images/svg/signup-affiliates/success.svg b/src/images/svg/signup-affiliates/success.svg new file mode 100644 index 00000000000..13fa53e0383 --- /dev/null +++ b/src/images/svg/signup-affiliates/success.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/images/svg/signup-affiliates/turnover.svg b/src/images/svg/signup-affiliates/turnover.svg new file mode 100644 index 00000000000..4ae8aa32b78 --- /dev/null +++ b/src/images/svg/signup-affiliates/turnover.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/images/svg/signup-affiliates/usd.svg b/src/images/svg/signup-affiliates/usd.svg new file mode 100644 index 00000000000..081082dec3f --- /dev/null +++ b/src/images/svg/signup-affiliates/usd.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/endpoint/index.tsx b/src/pages/endpoint/index.tsx index ab690eb09c8..740a4e28d4e 100644 --- a/src/pages/endpoint/index.tsx +++ b/src/pages/endpoint/index.tsx @@ -1,12 +1,12 @@ import React from 'react' import styled from 'styled-components' -import { Formik, Form } from 'formik' +import { Form, Formik } from 'formik' import device from 'themes/device' import { WithIntl } from 'components/localization' import Layout from 'components/layout/layout' import { Container, SEO } from 'components/containers' import { Header, Text } from 'components/elements' -import { Input, Button } from 'components/form' +import { Button, Input } from 'components/form' import validation from 'common/validation' import { trimSpaces } from 'common/utility' import { default_server_url } from 'common/constants' diff --git a/src/pages/landing/ebooks/components/_get-ebook.tsx b/src/pages/landing/ebooks/components/_get-ebook.tsx index ec472fb5266..a84feabe25c 100644 --- a/src/pages/landing/ebooks/components/_get-ebook.tsx +++ b/src/pages/landing/ebooks/components/_get-ebook.tsx @@ -2,9 +2,9 @@ import React from 'react' import styled from 'styled-components' import Cookies from 'js-cookie' import Login, { TSocialProvider } from 'common/login' -import { getCookiesObject, getCookiesFields, getDataObjFromCookies } from 'common/cookies' +import { getCookiesFields, getCookiesObject, getDataObjFromCookies } from 'common/cookies' import validation from 'common/validation' -import { Input, Button } from 'components/form' +import { Button, Input } from 'components/form' import { Header, Text } from 'components/elements' import { Localize, localize } from 'components/localization' import { Flex } from 'components/containers' diff --git a/src/pages/reset-password/index.tsx b/src/pages/reset-password/index.tsx index b16173adc75..7edda039e55 100644 --- a/src/pages/reset-password/index.tsx +++ b/src/pages/reset-password/index.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react' import styled from 'styled-components' -import { Formik, Form } from 'formik' +import { Form, Formik } from 'formik' import Layout from 'components/layout/layout' import { Localize, localize, WithIntl } from 'components/localization' import { Container, SEO } from 'components/containers' import { Header, Text } from 'components/elements' -import { Input, Button } from 'components/form' +import { Button, Input } from 'components/form' import validation from 'common/validation' import { trimSpaces } from 'common/utility' import Login from 'common/login' diff --git a/src/pages/signup-affiliates/_types.ts b/src/pages/signup-affiliates/_types.ts new file mode 100644 index 00000000000..389efcbd2b6 --- /dev/null +++ b/src/pages/signup-affiliates/_types.ts @@ -0,0 +1,136 @@ +import { Dispatch, ReactElement, SetStateAction } from 'react' + +type CountryType = { name?: string; display_name?: string; prefix?: string; symbol?: string } + +type AffiliateAccountTypes = { + email: string + account_type: number + account_plan: number + address_details: { + country: CountryType + state: { name?: string; display_name?: string } + city: string + street: string + postal_code: string + } + personal_details: { + first_name: string + last_name: string + date_birth: Date + phone: string + prefix: string + website_url: string + second_website_url: string + company_name: string + company_registration_number: string + username: string + password: string + } + terms_of_use: { + non_pep_declaration_accepted: boolean + tnc_accepted: boolean + general_terms_accepted: boolean + is_partner_checked: boolean + } +} +type AffiliateSendTypes = { + address_city: string + address_postcode: string + address_state: string + address_street: string + commission_plan: number + country: string + date_of_birth: string + email: string + first_name: string + last_name: string + non_pep_declaration: number + over_18_declaration: number + phone: string + phone_code: number + tnc_accepted: number + tnc_affiliate_accepted: number + type_of_account: number + user_name: string + website_url: string + whatsapp_number: string + whatsapp_number_phoneCode: number + company_name?: string + company_registration_number?: number +} + +type setAffiliateAccountTypes = Dispatch> + +type AffiliateAccountProps = { + affiliate_account?: AffiliateAccountTypes + setAffiliateAccount?: setAffiliateAccountTypes + onSubmit?: () => void +} + +export type SignUpStatusTypes = + | 'Username not available' + | 'lost connection' + | 'success' + | 'loading' + | 'Your website is not a valid entry' + | 'wizard opened' + | 'closing wizard' + | '' +export type setSignUpStatusTypes = Dispatch> + +export type SignUpStatusProps = { + signup_status: SignUpStatusTypes + setSignupStatus: setSignUpStatusTypes +} & AffiliateAccountProps + +type SubmitTypes = { + is_online: boolean + affiliate_account: AffiliateAccountTypes + + setSignupStatus: setSignUpStatusTypes + // once our API register this call we can remove type below + affiliateSend: (data?: AffiliateSendTypes) => Promise +} + +type SetShowWizardType = Dispatch> + +type WizardProps = { + show_wizard: boolean + setSignupStatus: setSignUpStatusTypes +} & AffiliateAccountProps + +type SignUpFormProps = { + setShowWizard: SetShowWizardType +} & AffiliateAccountProps + +type WizardComponentTypes = { + children?: ReactElement[] + next_btn_enabled: boolean + setNextBtnEnabled: Dispatch> + step: number + setStep: Dispatch> + max_step?: number +} & Pick + +type WizardComponentsProps = WizardComponentTypes & WizardProps + +type AffiliateData = keyof AffiliateAccountTypes + +type WizardStepProps = { + affiliate_account: AffiliateAccountTypes + updateData: (value: AffiliateAccountTypes[AffiliateData]) => void + onValidate: (value: boolean) => void + residence_list?: CountryType[] + is_individual?: boolean +} + +export type { + AffiliateAccountTypes, + setAffiliateAccountTypes, + WizardStepProps, + WizardComponentsProps, + WizardComponentTypes, + WizardProps, + SignUpFormProps, + SubmitTypes, +} diff --git a/src/pages/signup-affiliates/components/_account-address.tsx b/src/pages/signup-affiliates/components/_account-address.tsx new file mode 100644 index 00000000000..477b2768a07 --- /dev/null +++ b/src/pages/signup-affiliates/components/_account-address.tsx @@ -0,0 +1,216 @@ +import React, { useEffect, useState } from 'react' +import { WizardStepProps } from '../_types' +import AffiliateInput from '../utils/_affiliate-input' +import { customSlugify } from '../index' +import AffiliatesHeader, { InputGroup, InputWrapper } from '../utils/_affiliate-header' +import affiliate_validation from '../validations/_affilaite_validation' +import { useStatesList } from 'features/hooks/use-states-list' +import { DropdownSearch } from 'components/elements' +import { localize } from 'components/localization' +import { TString } from 'types/generics' + +const AccountAddress = ({ + affiliate_account, + is_individual, + updateData, + onValidate, + residence_list, +}: WizardStepProps) => { + const affiliate_data = affiliate_account.address_details + const [country, setCountry] = useState(affiliate_data.country) + const [state, setState] = useState(affiliate_data.state) + const [city, setCity] = useState(affiliate_data.city) + const [street, setStreet] = useState(affiliate_data.street) + const [postal_code, setPostCode] = useState(affiliate_data.postal_code) + const [country_error_msg, setCountryErrorMsg] = useState('') + const [state_error_msg, setStateErrorMsg] = useState('') + const [city_error_msg, setCityErrorMsg] = useState('') + const [street_error_msg, setStreetErrorMsg] = useState('') + const [postcode_error_msg, setPostCodeErrorMsg] = useState('') + + const [states_list] = useStatesList(affiliate_data.country?.symbol) + + const header_text: TString = is_individual ? '_t_Personal address_t_' : '_t_Company address_t_' + + console.log('chosen state: ', state?.name) + console.log('state to send: ', customSlugify(state?.name ?? 'abc')) + + useEffect(() => { + updateData({ + ...affiliate_data, + country, + state, + street, + city, + postal_code, + }) + }, [country, state, street, city, postal_code]) + + const validate = + country?.name && + state?.name && + city && + street && + postal_code && + !country_error_msg && + !state_error_msg && + !city_error_msg && + !street_error_msg && + !postcode_error_msg + + useEffect(() => { + onValidate(validate) + }, [onValidate, validate]) + + const handleCountry = (changed_country) => { + if (country?.name && state?.name && changed_country !== country) { + setCountryErrorMsg('State is not valid for this country') + } + setCountry(changed_country) + } + const handleState = (changed_state) => { + if (country_error_msg) { + setCountryErrorMsg('') + setState(changed_state) + } + setState(changed_state) + } + + const form_inputs = [ + { + id: 'dm-country-select', + name: 'country', + type: 'select', + label: is_individual + ? localize('_t_Country of residence*_t_') + : localize('_t_Country*_t_'), + required: true, + error: country_error_msg, + value: country, + list: residence_list, + value_set: handleCountry, + }, + { + id: 'dm-state', + name: 'state', + type: 'select', + label: localize('_t_State/province*_t_'), + required: true, + error: state_error_msg, + value: state, + list: states_list, + value_set: handleState, + }, + { + id: 'dm-town', + name: 'city', + type: 'text', + label: localize('_t_Town/city*_t_'), + placeholder: 'Town/city*', + extra_info: ' ', + required: true, + error: city_error_msg, + value: city, + value_set: setCity, + error_set: setCityErrorMsg, + }, + { + id: 'dm-street', + name: 'street', + type: 'text', + label: localize('_t_Street*_t_'), + placeholder: 'Street*', + extra_info: ' ', + required: true, + error: street_error_msg, + value: street, + value_set: setStreet, + error_set: setStreetErrorMsg, + }, + { + id: 'dm-postal-code', + name: 'postal_code', + type: 'text', + label: localize('_t_Postal/Zip code*_t_'), + placeholder: 'Postal/Zip code*', + extra_info: ' ', + required: true, + error: postcode_error_msg, + value: postal_code, + value_set: setPostCode, + error_set: setPostCodeErrorMsg, + }, + ] + const handleInput = (e) => { + const { name, value } = e.target + switch (name) { + case 'country': { + setCountry(value) + return setCountryErrorMsg(affiliate_validation.country(value)) + } + case 'state': { + setState(value) + return setStateErrorMsg(affiliate_validation.address_state(value)) + } + case 'city': { + setCity(value) + return setCityErrorMsg(affiliate_validation.address_city(value)) + } + case 'street': { + setStreet(value) + return setStreetErrorMsg(affiliate_validation.address_street(value)) + } + case 'postal_code': { + setPostCode(value) + return setPostCodeErrorMsg(affiliate_validation.address_postal_code(value)) + } + } + } + + return ( + + + + {form_inputs.map((item) => { + if (item.name === 'country' || item.name === 'state') { + return ( + item.value_set(value)} + style={{ marginTop: '16px' }} + /> + ) + } else { + return ( + { + item?.value_set('') + item?.error_set('') + }} + /> + ) + } + })} + + + ) +} + +export default AccountAddress diff --git a/src/pages/signup-affiliates/components/_account-details.tsx b/src/pages/signup-affiliates/components/_account-details.tsx new file mode 100644 index 00000000000..6d79523b60d --- /dev/null +++ b/src/pages/signup-affiliates/components/_account-details.tsx @@ -0,0 +1,352 @@ +import React, { useEffect, useState } from 'react' +import { WizardStepProps } from '../_types' +import affiliate_validation from '../validations/_affilaite_validation' +import BirthForm from '../utils/_birth-form' +import AffiliateInput from '../utils/_affiliate-input' +import AffiliatesHeader, { InputGroup, InputWrapper } from '../utils/_affiliate-header' +import { localize } from 'components/localization' + +const AccountDetails = ({ + is_individual, + affiliate_account, + updateData, + onValidate, +}: WizardStepProps) => { + const affiliate_data = affiliate_account.personal_details + const [username, setUsername] = useState(affiliate_data.username) + const [first_name, setFirstName] = useState(affiliate_data.first_name) + const [last_name, setLastName] = useState(affiliate_data.last_name) + const [date_birth, setDateBirth] = useState(affiliate_data.date_birth) + const [prefix, setPrefix] = useState(affiliate_data.prefix) + const [phone, setPhone] = useState( + affiliate_data.phone ? affiliate_data.phone : affiliate_data.prefix, + ) + const [company_name, setCompanyName] = useState(affiliate_data.company_name) + const [company_registration_number, setCompanyRegistrationNumber] = useState( + affiliate_data.company_registration_number, + ) + const [website_url, setWebsiteUrl] = useState(affiliate_data.website_url) + const [second_website_url, setSecondWebsiteUrl] = useState(affiliate_data.second_website_url) + const [password, setPassword] = useState(affiliate_data.password) + + const [user_name_error_msg, setUserNameErrorMsg] = useState() + const [first_name_error_msg, setFirstNameErrorMsg] = useState() + const [last_name_error_msg, setLastNameErrorMsg] = useState() + const [phone_error_msg, setPhoneErrorMsg] = useState() + const [company_name_error_msg, setCompanyNameErrorMsg] = useState() + const [company_registration_error_msg, setCompanyRegistrationErrorMsg] = useState() + const [website_url_error_msg, setWebsiteUrlErrorMsg] = useState() + const [second_website_url_error_msg, setSecondWebsiteUrlErrorMsg] = useState() + const [password_error_msg, setPasswordErrorMsg] = useState() + + const form_inputs = [ + { + id: 'first_name', + name: 'first_name', + type: 'text', + label: localize('_t_First name*_t_'), + error: first_name_error_msg, + value: first_name, + required: true, + value_set: setFirstName, + error_set: setFirstNameErrorMsg, + }, + { + id: 'last_name', + name: 'last_name', + type: 'text', + label: localize('_t_Last name*_t_'), + error: last_name_error_msg, + value: last_name, + required: true, + value_set: setLastName, + error_set: setLastNameErrorMsg, + }, + { + id: 'date_birth', + name: 'date_birth', + type: 'date', + label: localize('_t_Date of Birth*_t_'), + value: date_birth, + required: false, + value_set: setDateBirth, + }, + { + id: 'phone', + name: 'phone', + type: 'number', + label: localize('_t_Phone number*_t_'), + error: phone_error_msg, + value: phone, + required: false, + value_set: setPhone, + error_set: setPhoneErrorMsg, + }, + { + id: 'company_name', + name: 'company_name', + type: 'text', + label: localize('_t_Company name*_t_'), + error: company_name_error_msg, + value: company_name, + required: true, + value_set: setCompanyName, + error_set: setCompanyNameErrorMsg, + }, + { + id: 'company_registration_number', + name: 'company_registration_number', + type: 'number', + label: localize('_t_Company registration number*_t_'), + error: company_registration_error_msg, + value: company_registration_number, + required: true, + value_set: setCompanyRegistrationNumber, + error_set: setCompanyRegistrationErrorMsg, + }, + { + id: 'website_url', + name: 'website_url', + type: 'text', + label: is_individual + ? localize('_t_Website/social media URL*_t_') + : localize('_t_Company website/social media URL*_t_'), + error: website_url_error_msg, + value: website_url, + required: true, + value_set: setWebsiteUrl, + error_set: setWebsiteUrlErrorMsg, + }, + { + id: 'second_website_url', + name: 'second_website_url', + type: 'text', + label: is_individual + ? localize('_t_Second website/social media URL_t_') + : localize('_t_Company second website/social media URL_t_'), + error: second_website_url_error_msg, + value: second_website_url, + required: false, + value_set: setSecondWebsiteUrl, + error_set: setSecondWebsiteUrlErrorMsg, + }, + { + id: 'username', + name: 'username', + type: 'text', + label: localize('_t_Username*_t_'), + error: user_name_error_msg, + value: username, + required: true, + value_set: setUsername, + error_set: setUserNameErrorMsg, + }, + { + id: 'dm-password', + name: 'password', + type: 'password', + label: localize('_t_Password*_t_'), + error: password_error_msg, + value: password, + required: true, + value_set: setPassword, + error_set: setPasswordErrorMsg, + }, + ] + // prevent these characters from the input type='number' tag + const input_id = document.getElementById('company_registration_number') + const phone_id = document.getElementById('phone') + input_id?.addEventListener('keydown', function (e) { + if (['.', ',', '-', '+', 'e'].includes(e.key)) { + e.preventDefault() + } + }) + phone_id?.addEventListener('keydown', function (e) { + if (['.', ',', '-', '+', 'e'].includes(e.key)) { + e.preventDefault() + } + }) + + const validate = is_individual + ? username && + first_name && + last_name && + date_birth && + phone && + password && + !first_name_error_msg && + !last_name_error_msg && + !password_error_msg + : first_name && + last_name && + date_birth && + password && + company_name && + company_registration_number && + !first_name_error_msg && + !last_name_error_msg && + !phone_error_msg && + !password_error_msg && + !company_name_error_msg && + !company_registration_error_msg + + useEffect(() => { + updateData({ + ...affiliate_data, + first_name, + last_name, + date_birth, + phone, + prefix, + company_name, + company_registration_number, + website_url, + second_website_url, + username, + password, + }) + }, [ + username, + first_name, + last_name, + date_birth, + phone, + company_name, + company_registration_number, + website_url, + second_website_url, + password, + ]) + + useEffect(() => { + onValidate(validate) + }, [onValidate, validate]) + const getFormFields = () => { + return form_inputs.filter((item) => { + if (is_individual) { + const company_details = ['company_name', 'company_registration_number'] + + return !company_details.includes(item.name) + } + return true + }) + } + const handleInput = (e) => { + const { name, value } = e.target + switch (name) { + case 'first_name': { + setFirstName(value) + return setFirstNameErrorMsg(affiliate_validation.first_name(value)) + } + case 'last_name': { + setLastName(value) + return setLastNameErrorMsg(affiliate_validation.last_name(value)) + } + case 'date_birth': { + setDateBirth(value) + break + } + case 'phone': { + setPhone(value) + return setPhoneErrorMsg(affiliate_validation.phone(value)) + } + case 'company_name': { + setCompanyName(value) + return setCompanyNameErrorMsg(affiliate_validation.company_name(value)) + } + case 'company_registration_number': { + setCompanyRegistrationNumber(value) + return setCompanyRegistrationErrorMsg( + affiliate_validation.company_registration_number(value), + ) + } + case 'website_url': { + setWebsiteUrl(value) + return setWebsiteUrlErrorMsg(affiliate_validation.website_url(value)) + } + case 'second_website_url': { + setSecondWebsiteUrl(value) + return setSecondWebsiteUrlErrorMsg(affiliate_validation.second_website_url(value)) + } + case 'username': { + setUsername(value) + return setUserNameErrorMsg(affiliate_validation.username(value)) + } + case 'password': { + setPassword(value) + return setPasswordErrorMsg(affiliate_validation.password(value)) + } + } + } + + return ( + + + + {getFormFields().map((item) => { + if (item.name === 'date_birth') { + return ( + + ) + } else if (item.name === 'username') { + return ( + <> + + { + item?.value_set('') + item?.error_set('') + }} + /> + + ) + } else { + return ( + { + item?.value_set('') + item?.error_set('') + }} + /> + ) + } + })} + + + ) +} +export default AccountDetails diff --git a/src/pages/signup-affiliates/components/_account-plan.tsx b/src/pages/signup-affiliates/components/_account-plan.tsx new file mode 100644 index 00000000000..84ab2a1af79 --- /dev/null +++ b/src/pages/signup-affiliates/components/_account-plan.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from 'react' +import styled from 'styled-components' +import { WizardStepProps } from '../_types' +import AccountCard, { AccountCardProps, MainWrapper } from '../utils/_account-card' +import { Localize } from 'components/localization' +import { Header } from 'components/elements' +import Revenue from 'images/svg/signup-affiliates/revenue.svg' +import Turnover from 'images/svg/signup-affiliates/turnover.svg' +import CPA from 'images/svg/signup-affiliates/cpa.svg' +import device from 'themes/device' + +const StyledHeader = styled(Header)` + @media ${device.tabletL} { + text-align: left; + inline-size: 328px; + margin: 0 auto; + } +` + +const plans: AccountCardProps[] = [ + { + value: 2, + icon: Revenue, + title: '_t_Revenue share_t_', + description: + '_t_Earn based on the monthly net revenue generated by your client. <0>Learn more_t_', + }, + { + value: 4, + icon: Turnover, + title: '_t_Turnover_t_', + description: + "_t_Earn based on each contract's payout probability or client's trade. <0>Learn more_t_", + }, + { + value: 6, + icon: CPA, + title: '_t_CPA (EU only)_t_', + description: '_t_Earn based on each successful referral. <0>Learn more_t_', + note: '_t_<0>Note: This plan is available exclusively for EU-based clients only._t_', + }, +] +const AccountPlan = ({ updateData, affiliate_account, onValidate }: WizardStepProps) => { + const [account_plan, setAccountPlan] = useState(affiliate_account.account_plan) + + useEffect(() => { + onValidate(!!account_plan) + }, [onValidate, account_plan]) + + useEffect(() => { + updateData(account_plan) + }, [account_plan]) + + return ( + + + + + {plans.map(({ value, icon, title, description, note }) => { + return ( + { + setAccountPlan(value) + }} + /> + ) + })} + + ) +} +export default AccountPlan diff --git a/src/pages/signup-affiliates/components/_account-terms.tsx b/src/pages/signup-affiliates/components/_account-terms.tsx new file mode 100644 index 00000000000..ee717668234 --- /dev/null +++ b/src/pages/signup-affiliates/components/_account-terms.tsx @@ -0,0 +1,139 @@ +import React, { useEffect, useState } from 'react' +import styled from 'styled-components' +import { WizardStepProps } from '../_types' +import { Localize } from 'components/localization' +import { Header, LocalizedLinkText } from 'components/elements' +import Flex from 'features/components/atoms/flex-box' +import Typography from 'features/components/atoms/typography' +import { TString } from 'types/generics' +import device from 'themes/device' + +type AgreementDataType = { + link_text: TString + name: string +} +const StyledHeader = styled(Header)` + @media ${device.tabletL} { + text-align: left; + margin: 0 auto; + } +` +const MainWrapper = styled.div` + margin: 0 80px; + display: flex; + flex-direction: column; + + @media ${device.tabletL} { + margin: 0 auto; + inline-size: 95%; + } +` +const Line = styled.div` + width: 100%; + height: 1px; + background-color: var(--color-grey-8); + margin-bottom: 16px; +` +const CheckBox = styled.input` + accent-color: var(--color-red); + min-block-size: 16px; + min-inline-size: 16px; + margin-inline-end: 8px; +` + +const AccountTerms = ({ affiliate_account, updateData, onValidate }: WizardStepProps) => { + const affiliate_data = affiliate_account.terms_of_use + const [terms_of_use, setTermsOfUse] = useState(affiliate_data) + + const AgreementData: AgreementDataType[] = [ + { + link_text: '_t_I am not a PEP, and I have not been a PEP in the last 12 months._t_', + name: 'non_pep_declaration_accepted', + }, + { + link_text: '_t_I have read and accepted <0>Deriv’s terms and conditions_t_', + name: 'tnc_accepted', + }, + { + link_text: + '_t_I have read and accepted <0>Deriv’s general terms of use and affiliates and introducing brokers’ terms and conditions_t_', + name: 'general_terms_accepted', + }, + { + link_text: + '_t_I consent to receive promotional materials and notifications regarding your partnership program._t_', + name: 'is_partner_checked', + }, + ] + + const validate = + terms_of_use['non_pep_declaration_accepted'] && + terms_of_use['tnc_accepted'] && + terms_of_use['general_terms_accepted'] + + useEffect(() => { + onValidate(validate) + }, [onValidate, validate]) + + useEffect(() => { + updateData({ + ...terms_of_use, + }) + }, [terms_of_use]) + + return ( + + + + +
+ +
+ + {AgreementData.map(({ link_text, name }, index) => { + return ( + <> + + + setTermsOfUse({ + ...terms_of_use, + [name]: !affiliate_data[name], + }) + } + /> + + + , + ]} + /> + + + + {index !== AgreementData.length - 1 && } + + ) + })} +
+ ) +} +export default AccountTerms diff --git a/src/pages/signup-affiliates/components/_account-type.tsx b/src/pages/signup-affiliates/components/_account-type.tsx new file mode 100644 index 00000000000..5b67fa517d5 --- /dev/null +++ b/src/pages/signup-affiliates/components/_account-type.tsx @@ -0,0 +1,68 @@ +import React, { useEffect, useState } from 'react' +import styled from 'styled-components' +import { WizardStepProps } from '../_types' +import AccountCard, { AccountCardProps, MainWrapper } from '../utils/_account-card' +import { Header } from 'components/elements' +import { Localize } from 'components/localization' +import IndividualIcon from 'images/svg/signup-affiliates/individual.svg' +import BusinessIcon from 'images/svg/signup-affiliates/company.svg' +import device from 'themes/device' + +const StyledHeader = styled(Header)` + @media ${device.tabletL} { + text-align: left; + inline-size: 328px; + margin: 0 auto; + } +` + +const types: AccountCardProps[] = [ + { + value: 1, + icon: IndividualIcon, + title: '_t_Individual_t_', + description: '_t_Register for an individual account._t_', + }, + { + value: 2, + icon: BusinessIcon, + title: '_t_Company_t_', + description: '_t_Register for a company account._t_', + }, +] +const AccountType = ({ updateData, affiliate_account, onValidate }: WizardStepProps) => { + const [account_type, setAccountType] = useState(affiliate_account.account_type) + + useEffect(() => { + onValidate(!!account_type) + }, [onValidate, account_type]) + + useEffect(() => { + updateData(account_type) + }, [account_type]) + + return ( + + + + + {types.map(({ value, icon, title, description }) => { + return ( + { + setAccountType(value) + }} + /> + ) + })} + + ) +} +export default AccountType diff --git a/src/pages/signup-affiliates/components/_signup-form.tsx b/src/pages/signup-affiliates/components/_signup-form.tsx new file mode 100644 index 00000000000..8ba5ffbd64e --- /dev/null +++ b/src/pages/signup-affiliates/components/_signup-form.tsx @@ -0,0 +1,201 @@ +import React, { useState } from 'react' +import styled from 'styled-components' +import affiliate_validation from '../validations/_affilaite_validation' +import AffiliateInput from '../utils/_affiliate-input' +import { SignUpFormProps } from '../_types' +import { localize, Localize } from 'components/localization' +import { Container } from 'components/containers' +import { Button } from 'components/form' +import { Header, LinkText } from 'components/elements' +import device from 'themes/device' +import Link from 'features/components/atoms/link' +import Typography from 'features/components/atoms/typography' + +const StyledNote = styled.div` + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: flex-start; + padding: 8px 0; + margin: 24px 16px; + block-size: 40px; + inline-size: 406px; + border-radius: 8px; + background-color: #f8f8f9; + + @media ${device.tabletL} { + inline-size: 100%; + } +` +const StyledLinkText = styled(LinkText)` + margin-left: 3px; + font-size: 14px; + line-height: 20px; + height: 20px; + + @media ${device.tabletL} { + font-size: 12px; + } +` +const InputGroup = styled.div` + inline-size: 100%; +` +const EmailButton = styled(Button)` + width: 100%; + font-size: 1.4rem; + margin-block-end: 24px; + + @media ${device.mobileL} { + font-size: 1.75rem; + } +` +const LoginContainer = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: space-around; + flex-direction: row; + align-items: center; +` +const StyledFlex = styled(Container)` + display: flex; + flex-direction: column; + block-size: 510px; + inline-size: 100%; + max-inline-size: 486px; + margin: 0; + + @media ${device.tabletL} { + justify-content: flex-start; + max-inline-size: 328px; + block-size: 100%; + padding-block: 24px; + } +` +export const SignUpWrapper = styled(Container)` + display: flex; + flex-direction: column; + padding: 24px 34px; + margin: 0; + inline-size: 100%; + max-inline-size: 486px; + background: var(--color-white); + border-radius: 6px; + box-shadow: 0 12px 16px -4px #0e0e0e14; + + @media ${device.tabletS} { + box-shadow: unset; + justify-content: flex-end; + padding: 0; + } +` +const AffiliateSignupForm = ({ + affiliate_account, + setAffiliateAccount, + setShowWizard, +}: SignUpFormProps) => { + const [email_error_msg, setEmailErrorMsg] = useState('') + + const handleInput = (e) => { + const { value } = e.target + setAffiliateAccount({ ...affiliate_account, email: value }) + setEmailErrorMsg(affiliate_validation.email(value)) + } + + return ( + <> + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ + + + +
+
+ + setAffiliateAccount({ ...affiliate_account, email: '' })} + /> + + { + window.scrollTo(0, 0) + setShowWizard(true) + }} + disabled={!(affiliate_account.email && !email_error_msg)} + > + + + + + + + + + + + + +
+ + ) +} + +export default AffiliateSignupForm diff --git a/src/pages/signup-affiliates/components/_signup-status.tsx b/src/pages/signup-affiliates/components/_signup-status.tsx new file mode 100644 index 00000000000..53a177525e7 --- /dev/null +++ b/src/pages/signup-affiliates/components/_signup-status.tsx @@ -0,0 +1,346 @@ +import React, { useState } from 'react' +import styled from 'styled-components' +import { Analytics } from '@deriv/analytics' +import affiliate_validation from '../validations/_affilaite_validation' +import AffiliateInput from '../utils/_affiliate-input' +import { SignUpStatusProps } from '../_types' +import Image from 'features/components/atoms/image' +import { localize, Localize } from 'components/localization' +import { Button } from 'components/form' +import { Background } from 'pages/signup-affiliates/components/wizard-component' +import { Header } from 'components/elements' +import device from 'themes/device' +import Success from 'images/svg/signup-affiliates/success.svg' +import Failed from 'images/svg/signup-affiliates/failed.svg' +import CloseSVG from 'images/svg/custom/close-2.svg' + +const StyledButton = styled(Button)` + inline-size: fit-content; + gap: 8px; + margin-block-start: 12px; +` +const ButtonComposition = styled.div` + display: flex; + flex-direction: row; +` +const ProgressModal = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 100; +` +const Modal = styled.div<{ ai?: string }>` + z-index: 101; + opacity: 1; + display: flex; + flex-direction: column; + align-items: ${({ ai }) => ai || 'center'}; + justify-content: center; + inline-size: 100%; + max-inline-size: 486px; + border-radius: 8px; + padding: 24px; + gap: 24px; + transform: translate(-50%, -50%); + position: fixed; + top: 50%; + left: 50%; + background-color: white; + box-shadow: 0 20px 24px -4px #0e0e0e14; + + @media ${device.tabletL} { + padding: 16px; + gap: 16px; + } + @media ${device.tabletS} { + max-inline-size: 328px; + } +` +const StyledSpinner = styled.svg` + animation: rotate 1s linear infinite; + margin: 40px; + width: 200px; + height: 200px; + z-index: 105; + + & .path { + stroke: var(--color-red-5); + stroke-linecap: round; + animation: dash 1.5s ease-in-out infinite; + } + + @keyframes rotate { + 100% { + transform: rotate(360deg); + } + } + @keyframes dash { + 0% { + stroke-dasharray: 1, 150; + stroke-dashoffset: 0; + } + 50% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -35; + } + 100% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -124; + } + } +` +const CloseButton = styled.img` + z-index: 102; + &:hover { + cursor: pointer; + } +` + +const AffiliateSignupStatus = ({ + signup_status, + setSignupStatus, + affiliate_account, + setAffiliateAccount, + onSubmit, +}: SignUpStatusProps) => { + const [username_error, setUsernameError] = useState() + const [website_url_error, setWebsiteUrlError] = useState() + const analyticsData: Parameters[1] = { + form_name: 'ce_partner_account_signup_form', + } + + return ( + <> + {signup_status == 'success' && ( + + + email +
+ +
+
+ +
+ { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'success_popup_cta', + ...analyticsData, + }) + window.location.href = 'https://deriv.com/partners/' + }} + > + + +
+ +
+ )} + {signup_status === 'closing wizard' && ( + + +
+ +
+
+ ]} + /> +
+ + { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'close_wizard', + ...analyticsData, + }) + window.location.href = 'https://deriv.com/partners/' + }} + > + + + setSignupStatus('')}> + + + +
+ +
+ )} + {signup_status == 'loading' && ( + + + { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'failed_popup_cta', + ...analyticsData, + }) + setSignupStatus('') + }} + /> + + + + + + + )} + {signup_status == 'lost connection' && ( + + + email +
+ +
+
+ +
+ { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'failed_popup_cta', + ...analyticsData, + }) + setSignupStatus('') + }} + > + + +
+ +
+ )} + {signup_status == 'Username not available' && ( + + + email +
+ +
+
+ +
+ { + setUsernameError(affiliate_validation.username(e.target.value)) + setAffiliateAccount({ + ...affiliate_account, + personal_details: { + ...affiliate_account.personal_details, + username: e.target.value, + }, + }) + }} + handleError={() => { + setAffiliateAccount({ + ...affiliate_account, + personal_details: { + ...affiliate_account.personal_details, + username: '', + }, + }) + }} + required + /> + { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'try_submit', + ...analyticsData, + }) + setSignupStatus('') + onSubmit() + }} + > + + +
+ +
+ )} + {signup_status == 'Your website is not a valid entry' && ( + + + email +
+ +
+
+ +
+ { + setWebsiteUrlError(affiliate_validation.website_url(e.target.value)) + setAffiliateAccount({ + ...affiliate_account, + personal_details: { + ...affiliate_account.personal_details, + website_url: e.target.value, + }, + }) + }} + handleError={() => { + setAffiliateAccount({ + ...affiliate_account, + personal_details: { + ...affiliate_account.personal_details, + website_url: '', + }, + }) + }} + required + /> + { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'try_submit', + ...analyticsData, + }) + setSignupStatus('') + onSubmit() + }} + > + + +
+ +
+ )} + + ) +} + +export default AffiliateSignupStatus diff --git a/src/pages/signup-affiliates/components/_wizard.tsx b/src/pages/signup-affiliates/components/_wizard.tsx new file mode 100644 index 00000000000..04ce52a84a9 --- /dev/null +++ b/src/pages/signup-affiliates/components/_wizard.tsx @@ -0,0 +1,181 @@ +import React, { useEffect, useState } from 'react' +import styled from 'styled-components' +import { Analytics } from '@deriv/analytics' +import { WizardProps } from '../_types' +import AccountType from './_account-type' +import AccountPlan from './_account-plan' +import AccountAddress from './_account-address' +import AccountTerms from './_account-terms' +import AccountDetails from './_account-details' +import WizardComponent from './wizard-component' +import { useResidenceList } from 'features/hooks/use-residence-list' +import { Container } from 'components/containers' +import { isBrowser } from 'common/utility' + +export const SignUpWrapper = styled(Container)` + display: flex; + flex-direction: column; + padding: 40px; + margin: 0; + height: 510px; + width: 100%; + max-width: 486px; + background: var(--color-white); + border-radius: 6px; + box-shadow: 0 12px 16px -4px #0e0e0e14; +` + +const Wizard = ({ + show_wizard, + setSignupStatus, + affiliate_account, + setAffiliateAccount, + onSubmit, +}: WizardProps) => { + const [step, setStep] = useState(1) + const [next_btn_enabled, setNextBtnEnabled] = useState(false) + const restricted_countries = ['Iran', 'North Korea', 'Myanmar (Burma)', 'Syria', 'Cuba'] + const [residence_list] = useResidenceList({ restricted_countries }) + + const is_individual = affiliate_account?.account_type == 1 + + const analyticsData: Parameters[1] = { + form_source: isBrowser() && window?.location.hostname, + form_name: 'default_diel_deriv', + } + + useEffect(() => { + show_wizard && + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'open_wizard', + ...analyticsData, + }) + return () => { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'close_wizard', + ...analyticsData, + }) + } + }, [show_wizard]) + + const updateAffiliateValues = (value, type) => { + switch (type) { + case 'account-type': + setAffiliateAccount({ + ...affiliate_account, + account_type: value, + }) + break + case 'account-plan': + setAffiliateAccount({ + ...affiliate_account, + account_plan: value, + }) + break + case 'account-details': + setAffiliateAccount({ + ...affiliate_account, + address_details: { + country: value.country, + state: value.state, + city: value.city, + street: value.street, + postal_code: value.postal_code, + }, + }) + break + case 'personal-details': + setAffiliateAccount({ + ...affiliate_account, + personal_details: { + first_name: value.first_name, + last_name: value.last_name, + date_birth: value.date_birth, + phone: value.phone, + prefix: value.prefix, + website_url: value.website_url, + second_website_url: value.second_website_url, + company_name: value.company_name, + company_registration_number: value.company_registration_number, + username: value.username, + password: value.password, + }, + }) + break + case 'terms-of-use': + setAffiliateAccount({ + ...affiliate_account, + terms_of_use: { + non_pep_declaration_accepted: value.non_pep_declaration_accepted, + tnc_accepted: value.tnc_accepted, + general_terms_accepted: value.general_terms_accepted, + is_partner_checked: value.is_partner_checked, + }, + }) + break + } + } + + return ( + + { + updateAffiliateValues(value, 'account-type') + }} + onValidate={(valid) => { + setNextBtnEnabled(valid) + }} + /> + { + updateAffiliateValues(value, 'account-plan') + }} + onValidate={(valid) => { + setNextBtnEnabled(valid) + }} + /> + { + updateAffiliateValues(value, 'account-details') + }} + onValidate={(valid) => { + setNextBtnEnabled(valid) + }} + residence_list={residence_list} + /> + { + updateAffiliateValues(value, 'personal-details') + }} + onValidate={(valid) => { + setNextBtnEnabled(valid) + }} + /> + { + updateAffiliateValues(value, 'terms-of-use') + }} + onValidate={(valid) => { + setNextBtnEnabled(valid) + }} + /> + + ) +} + +export default Wizard diff --git a/src/pages/signup-affiliates/components/wizard-component/index.tsx b/src/pages/signup-affiliates/components/wizard-component/index.tsx new file mode 100644 index 00000000000..b29eab9bfc9 --- /dev/null +++ b/src/pages/signup-affiliates/components/wizard-component/index.tsx @@ -0,0 +1,106 @@ +import React, { useEffect } from 'react' +import styled from 'styled-components' +import WizardFooter from './wizard-footer' +import WizardHeader from './wizard-header' +import Stepper from './stepper' +import { WizardComponentsProps } from 'pages/signup-affiliates/_types' +import { useDebouncedEffect } from 'components/hooks/use-debounced-effect' +import device from 'themes/device' + +export const Background = styled.div` + position: absolute; + width: 100%; + block-size: 100vh; + top: 0; + left: 0; + background-color: var(--color-black); + opacity: 0.4; + z-index: 10; + + @media ${device.tabletL} { + block-size: 100%; + opacity: 0; + z-index: unset; + } +` +export const Modal = styled.div` + display: flex; + flex-direction: column; + position: fixed; + justify-content: space-between; + top: 50%; + left: 50%; + block-size: 80%; + min-inline-size: 70%; + border-radius: 8px; + background-color: white; + transform: translate(-50%, -50%); + z-index: 100; + overflow: hidden; + + @media ${device.tabletL} { + position: unset; + transform: unset; + border-radius: unset; + min-inline-size: unset; + inline-size: 100vw; + block-size: 100vh; + overflow: scroll; + padding-block-end: 150px; + } +` +const Container = styled.div` + overflow: scroll; +` +const Content = styled.div` + block-size: fit-content; + overflow-x: auto; +` +const WizardComponent = ({ + children, + show_wizard, + setSignupStatus, + onSubmit, + step, + setStep, + next_btn_enabled, + setNextBtnEnabled, +}: WizardComponentsProps) => { + useEffect(() => { + setNextBtnEnabled(next_btn_enabled) + }, [next_btn_enabled]) + + useDebouncedEffect( + () => (document.body.style.overflow = show_wizard ? 'hidden' : 'unset'), + [show_wizard], + 1, + ) + + if (!show_wizard) return <> + return ( + <> + + + + + + {React.Children.map(children, (child, idx) => ( +
{step === idx + 1 && child}
+ ))} +
+
+ +
+ + + ) +} + +export default WizardComponent diff --git a/src/pages/signup-affiliates/components/wizard-component/stepper.tsx b/src/pages/signup-affiliates/components/wizard-component/stepper.tsx new file mode 100644 index 00000000000..63be4362bef --- /dev/null +++ b/src/pages/signup-affiliates/components/wizard-component/stepper.tsx @@ -0,0 +1,162 @@ +import React from 'react' +import styled from 'styled-components' +import Typography from 'features/components/atoms/typography' +import useBreakpoints from 'components/hooks/use-breakpoints' +import { Localize } from 'components/localization' +import { TString } from 'types/generics' +import device from 'themes/device' + +const StepperWrapper = styled.div` + padding: 24px 0; + display: flex; + justify-content: space-between; + inline-size: 700px; + margin: 0 auto; + + @media ${device.tabletL} { + justify-content: center; + inline-size: 100%; + padding-block: 20px 16px; + } +` +const StepView = styled.div<{ first_two_steps?: boolean }>` + display: flex; + flex-direction: column; + justify-content: flex-start; + text-align: ${({ first_two_steps }) => (first_two_steps ? 'center' : 'left')}; + margin: 0 auto; + inline-size: 95%; +` +const StepperItem = styled.div<{ active: boolean }>` + position: relative; + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + + @media (max-width: 768px) { + font-size: 12px; + } + + &::before { + position: absolute; + content: ''; + border-bottom: 2px solid #999999; + width: 100%; + top: 13px; + left: -50%; + z-index: 2; + } + &::after { + position: absolute; + content: ''; + border-bottom: ${(props) => (props.active ? '2px solid #FF444F' : '2px solid #999999')}; + width: 100%; + top: 13px; + left: 50%; + z-index: ${(props) => (props.active ? 3 : 2)}; + } + &:first-child::before { + content: none; + } + &:last-child::after { + content: none; + } +` +const StepCounter = styled.div<{ active: boolean }>` + color: #ffffff; + font-weight: 700; + font-size: 14px; + position: relative; + z-index: 5; + display: flex; + justify-content: center; + align-items: center; + width: 26px; + height: 26px; + border-radius: 50%; + background: ${(props) => (props.active ? `#FF444F` : `#999999`)}; + margin-bottom: 6px; + + &.active { + font-weight: bold; + } +` +const Description = styled.div` + position: fixed; + top: 20%; + left: 25%; +` +const Label = styled.h4<{ active: boolean }>` + text-align: center; + padding-top: 10px; + font-size: 12px; + font-weight: ${(props) => (props.active ? 700 : 400)}; + color: ${(props) => (props.active ? `#FF444F` : `#999999`)}; +` + +const steps: { id: number; step_name: TString; step_description?: TString }[] = [ + { + id: 1, + step_name: '_t_Account type_t_', + step_description: '_t_Choose the type of partner account you want to register_t_', + }, + { + id: 2, + step_name: '_t_Subscription plan_t_', + step_description: '_t_Choose which plan you would like to subscribe._t_', + }, + { + id: 3, + step_name: '_t_Address details_t_', + step_description: '_t_Personal address_t_', + }, + { + id: 4, + step_name: '_t_Personal details_t_', + step_description: '_t_Details_t_', + }, + { + id: 5, + step_name: '_t_Terms of use_t_', + step_description: + '_t_Real accounts are not available to politically exposed persons (PEPs)._t_', + }, +] +const Stepper = ({ step }: { step: number }) => { + const { is_mobile_or_tablet } = useBreakpoints() + return ( + + {is_mobile_or_tablet + ? steps.map(({ id, step_name }) => ( + + {step === id && ( + + + + + + + + )} + + )) + : steps.map(({ id, step_name }) => ( + + {id} + + + ))} + + ) +} + +export default Stepper diff --git a/src/pages/signup-affiliates/components/wizard-component/wizard-footer.tsx b/src/pages/signup-affiliates/components/wizard-component/wizard-footer.tsx new file mode 100644 index 00000000000..72849041862 --- /dev/null +++ b/src/pages/signup-affiliates/components/wizard-component/wizard-footer.tsx @@ -0,0 +1,114 @@ +import React from 'react' +import styled from 'styled-components' +import { Analytics } from '@deriv/analytics' +import Button from 'components/form/button' +import { Localize } from 'components/localization' +import { WizardComponentTypes } from 'pages/signup-affiliates/_types' +import { isBrowser } from 'common/utility' +import device from 'themes/device' + +const StyledFooter = styled.div` + display: flex; + justify-content: flex-end; + align-items: center; + padding: 16px; + gap: 8px; + position: absolute; + right: 0; + bottom: 0; + border-top: 1px solid var(--color-grey-43); + inline-size: 100%; + background-color: var(--color-white); + z-index: 2; + + @media ${device.tabletL} { + position: fixed; + justify-content: center; + } +` +const PartnersButton = styled(Button)` + @media ${device.tabletL} { + inline-size: 100%; + max-inline-size: 160px; + } +` +const enum ButtonType { + Previous = 'PREVIOUS', + Next = 'NEXT', +} +const getCodeName = (num: number) => { + switch (num) { + case 1: + return 'Account type' + case 2: + return 'Account plan' + case 3: + return 'Address' + case 4: + return 'Details' + case 5: + return 'Terms of use' + } +} +const WizardFooter = ({ + step, + setStep, + onSubmit, + max_step, + setNextBtnEnabled, + next_btn_enabled, +}: WizardComponentTypes) => { + const analyticsData: Parameters[1] = { + form_source: isBrowser() && window?.location.hostname, + form_name: 'default_diel_deriv', + } + + const buttonHandler = React.useCallback( + (button_type: ButtonType): void => { + if (button_type === ButtonType.Previous) { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'step_back', + step_num: step, + step_codename: getCodeName(step), + ...analyticsData, + }) + step > 1 && setStep(step - 1) + setNextBtnEnabled(true) + } + if (button_type === ButtonType.Next) { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'step_passed', + step_num: step, + step_codename: getCodeName(step), + ...analyticsData, + }) + step < max_step && setStep(step + 1) + setNextBtnEnabled(false) + } + }, + [max_step, setNextBtnEnabled, setStep, step], + ) + + return ( + + {step > 1 && ( + buttonHandler(ButtonType.Previous)}> + + + )} + (max_step === step ? onSubmit() : buttonHandler(ButtonType.Next))} + > + {max_step === step ? ( + + ) : ( + + )} + + + ) +} + +export default WizardFooter diff --git a/src/pages/signup-affiliates/components/wizard-component/wizard-header.tsx b/src/pages/signup-affiliates/components/wizard-component/wizard-header.tsx new file mode 100644 index 00000000000..3c75f0638bb --- /dev/null +++ b/src/pages/signup-affiliates/components/wizard-component/wizard-header.tsx @@ -0,0 +1,39 @@ +import React from 'react' +import styled from 'styled-components' +import { setSignUpStatusTypes } from '../../_types' +import { Localize } from 'components/localization' +import CloseSVG from 'images/svg/custom/close-2.svg' + +const StyledHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + height: 56px; + padding: 24px; + border-bottom: 1px solid var(--color-grey-43); + gap: 10px; +` +const CloseButton = styled.img` + &:hover { + cursor: pointer; + } +` +const Title = styled.h2` + font-size: 16px; + font-weight: 700; + padding-top: 4px; + color: var(--color-black-3); +` + +const WizardHeader = ({ setSignupStatus }: { setSignupStatus: setSignUpStatusTypes }) => { + return ( + + + <Localize translate_text="_t_Add an affiliate account_t_" /> + + setSignupStatus('closing wizard')} /> + + ) +} + +export default WizardHeader diff --git a/src/pages/signup-affiliates/index.tsx b/src/pages/signup-affiliates/index.tsx new file mode 100644 index 00000000000..301dc0c5cfb --- /dev/null +++ b/src/pages/signup-affiliates/index.tsx @@ -0,0 +1,285 @@ +import React, { useEffect, useState } from 'react' +import styled from 'styled-components' +import Loadable from '@loadable/component' +import { Analytics } from '@deriv/analytics' +import AffiliateSignupForm from './components/_signup-form' +import { AffiliateAccountTypes, SignUpStatusTypes, SubmitTypes } from './_types' +import { isBrowser } from 'common/utility' +import { WithIntl } from 'components/localization' +import { Container } from 'components/containers' +import Layout from 'components/layout/layout' +import useWS from 'components/hooks/useWS' +import AtomicContainer from 'features/components/atoms/container' +import device from 'themes/device' +import Map from 'images/svg/signup-affiliates/map.svg' + +const AffiliateSignupStatus = Loadable(() => import('./components/_signup-status')) +const Wizard = Loadable(() => import('./components/_wizard')) + +export const customSlugify = (text: string): string => { + const charMap: { [key: string]: string } = { + ə: 'e', + // Add other special characters and their mappings here if needed + } + + return text + .toString() + .split('') + .map((char) => charMap[char] || char) + .join('') + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .trim() + .replace(/'/g, '') + .replace(/--+/g, '-') +} +const Submit = ({ is_online, affiliate_account, setSignupStatus, affiliateSend }: SubmitTypes) => { + if (!is_online) { + setSignupStatus('lost connection') + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'partners_signup_error', + partner_signup_error_message: 'lost connection', + form_source: document.referrer, + form_name: 'ce_partner_account_signup_form', + }) + } else + affiliateSend({ + address_city: affiliate_account.address_details.city, + address_postcode: affiliate_account.address_details.postal_code, + address_state: customSlugify(affiliate_account.address_details.state.name), + address_street: affiliate_account.address_details.street, + commission_plan: affiliate_account.account_plan, + country: affiliate_account.address_details.country.symbol, + date_of_birth: affiliate_account.personal_details.date_birth + ?.toISOString() + .slice(0, 10), + email: affiliate_account.email, + first_name: affiliate_account.personal_details.first_name, + last_name: affiliate_account.personal_details.last_name, + non_pep_declaration: affiliate_account.terms_of_use.non_pep_declaration_accepted && 1, + over_18_declaration: 1, + phone: `+${ + affiliate_account.personal_details.prefix + affiliate_account.personal_details.phone + }`, + phone_code: Number(affiliate_account.personal_details.prefix.substring(0, 4)), + tnc_accepted: affiliate_account.terms_of_use.tnc_accepted && 1, + tnc_affiliate_accepted: affiliate_account.terms_of_use.is_partner_checked && 1, + type_of_account: affiliate_account.account_type, + user_name: affiliate_account.personal_details.username, + website_url: affiliate_account.personal_details?.website_url.includes('www.') + ? affiliate_account.personal_details?.website_url + : `www.${affiliate_account.personal_details?.website_url}`, + whatsapp_number: `+${ + affiliate_account.personal_details.prefix + affiliate_account.personal_details.phone + }`, + whatsapp_number_phoneCode: Number( + affiliate_account.personal_details.prefix.substring(0, 4), + ), + ...(affiliate_account.personal_details?.company_name !== '' && { + company_name: affiliate_account.personal_details?.company_name, + }), + ...(affiliate_account.personal_details?.company_registration_number !== '' && { + company_registration_number: Number( + affiliate_account.personal_details?.company_registration_number, + ), + }), + }) +} + +const ParentWrapper = styled.div` + block-size: 100vh; + background-image: url(${Map}); + background-repeat: no-repeat; + background-position: bottom; +` +const StyledContainer = styled(Container)` + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-around; + padding-top: 120px; + inline-size: 100%; + + @media ${device.tabletL} { + flex-direction: column-reverse; + justify-content: center; + align-items: center; + padding-top: unset; + } +` + +const AffiliateSignup = () => { + const [show_wizard, setShowWizard] = useState(false) + const [is_online, setIsOnline] = useState(isBrowser() && navigator.onLine) + const [signup_status, setSignupStatus] = useState('') + + const analyticsData: Parameters[1] = { + form_name: 'ce_partner_account_signup_form', + } + + useEffect(() => { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'open', + ...analyticsData, + }) + + return () => { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'close', + ...analyticsData, + }) + } + }, []) + + const [affiliate_account, setAffiliateAccount] = useState({ + email: 'test@gmail.com', + account_type: 1, + account_plan: 2, + address_details: { + country: {}, + state: {}, + city: 'test', + street: 'test', + postal_code: 'test1234', + }, + personal_details: { + first_name: 'test', + last_name: 'test', + date_birth: null, + phone: '123123123', + prefix: '', + website_url: 'www.test.com', + second_website_url: '', + company_name: 'testers', + company_registration_number: '123345678', + username: 'Test', + password: 'Test12 ', + }, + terms_of_use: { + non_pep_declaration_accepted: true, + tnc_accepted: true, + general_terms_accepted: true, + is_partner_checked: true, + }, + }) + const { + data: affiliate_api_data, + error: affiliate_api_error, + send: affiliateSend, + } = useWS('affiliate_register_person') + + useEffect(() => { + const handleStatusChange = () => { + if (!navigator.onLine) { + Analytics?.trackEvent('ce_partner_account_signup_form', { action: 'other_error' }) + } + setIsOnline(navigator.onLine) + } + window.addEventListener('online', handleStatusChange) + window.addEventListener('offline', handleStatusChange) + return () => { + window.removeEventListener('online', handleStatusChange) + window.removeEventListener('offline', handleStatusChange) + } + }, [is_online]) + useEffect(() => { + const partner_signup_error_message = affiliate_api_error?.error.message + if (partner_signup_error_message == 'Username not available') { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'partners_signup_error', + partner_signup_error_message, + ...analyticsData, + }) + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'failed_popup_opened', + ...analyticsData, + }) + setSignupStatus(partner_signup_error_message) + } else if ( + partner_signup_error_message == 'Your website is not a valid entry' || + partner_signup_error_message == "String does not match '^[0-9A-Za-z.-]{5,250}$'" || + partner_signup_error_message == 'Input validation failed: website_url' + ) { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'partners_signup_error', + partner_signup_error_message, + ...analyticsData, + }) + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'failed_popup_opened', + ...analyticsData, + }) + setSignupStatus('Your website is not a valid entry') + } else if (partner_signup_error_message) { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'other_error', + partner_signup_error_message, + ...analyticsData, + }) + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'failed_popup_opened', + ...analyticsData, + }) + } + if (affiliate_api_data) { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'success_popup_opened', + user_choice: JSON.stringify(affiliate_api_error?.echo_req), + ...analyticsData, + }) + setSignupStatus('success') + } + }, [affiliate_api_data, affiliate_api_error, affiliateSend]) + useEffect(() => { + setAffiliateAccount({ + ...affiliate_account, + personal_details: { + ...affiliate_account.personal_details, + prefix: affiliate_account.address_details.country?.prefix, + }, + }) + }, [affiliate_account.address_details.country]) + const onSubmit = () => { + Analytics?.trackEvent('ce_partner_account_signup_form', { + action: 'try_submit', + ...analyticsData, + }) + setSignupStatus('loading') + Submit({ is_online, affiliate_account, setSignupStatus, affiliateSend }) + } + + return ( + + + + + {show_wizard ? ( + + ) : ( + + )} + + + + + + ) +} + +export default WithIntl()(AffiliateSignup) diff --git a/src/pages/signup-affiliates/utils/_account-card.tsx b/src/pages/signup-affiliates/utils/_account-card.tsx new file mode 100644 index 00000000000..c9ce7d5854c --- /dev/null +++ b/src/pages/signup-affiliates/utils/_account-card.tsx @@ -0,0 +1,137 @@ +import React, { useEffect, useState } from 'react' +import styled from 'styled-components' +import { Localize, LocalizedLink } from 'components/localization' +import Typography from 'features/components/atoms/typography' +import { TString } from 'types/generics' +import device from 'themes/device' +import Selected from 'images/svg/signup-affiliates/selected.svg' + +export type AccountCardProps = { + icon?: string + title?: TString + description?: TString + selected?: boolean + note?: TString + value?: number + onClick?: (e) => void +} +export const MainWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` +const StyledCard = styled.div<{ selected: boolean }>` + display: flex; + align-items: center; + inline-size: 400px; + padding: 16px; + margin-block: 8px; + position: relative; + background: #f3f7f7; + border: ${({ selected }) => (selected ? '1px solid #aad2d8' : '1px solid #ffffff')}; + border-radius: 8px; + cursor: pointer; + + @media ${device.tabletL} { + inline-size: 328px; + } +` +const Icon = styled.img` + width: 48px; + height: 48px; +` +const Content = styled.div` + display: flex; + flex-direction: column; + padding-left: 12px; +` + +const Title = styled.h3` + font-size: 16px; + font-weight: 700; + line-height: 24px; + + @media ${device.tabletL} { + font-size: 14px; + } +` +const Description = styled.p` + font-size: 12px; + line-height: 18px; + + @media ${device.tabletL} { + font-size: 10px; + } +` +const SelectedIcon = styled.img` + position: absolute; + top: 16px; + right: 16px; + width: 16px; + height: 16px; +` +export const StyledLink = styled(LocalizedLink)` + color: var(--color-red); + text-decoration: none; + z-index: 10; +` +const AdditionalNote = styled.div` + display: flex; + flex-direction: row; + margin-block-start: 12px; +` + +const AccountCard = ({ + icon, + title, + description, + selected, + note, + value, + onClick, +}: AccountCardProps) => { + const [is_selected, setIsSelected] = useState(false) + const clickHandler = () => { + setIsSelected(true) + onClick && onClick(value) + } + + useEffect(() => { + setIsSelected(selected) + }, [selected]) + + return ( + + + + + <Localize translate_text={title} /> + + + , + ]} + /> + {note && ( + + + ]} /> + + + )} + + + {is_selected ? : <>} + + ) +} + +export default AccountCard diff --git a/src/pages/signup-affiliates/utils/_affiliate-header.tsx b/src/pages/signup-affiliates/utils/_affiliate-header.tsx new file mode 100644 index 00000000000..6fcdcbef26b --- /dev/null +++ b/src/pages/signup-affiliates/utils/_affiliate-header.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import styled from 'styled-components' +import { Localize } from 'components/localization' +import { Header } from 'components/elements' +import device from 'themes/device' +import { TString } from 'types/generics' + +const StyledWrapper = styled.div<{ sized?: boolean }>` + display: flex; + flex-direction: row; + flex-wrap: wrap; + inline-size: ${({ sized }) => (sized ? '100%' : '50%')}; + + @media ${device.tabletL} { + inline-size: ${({ sized }) => (sized ? '100%' : '95%')}; + } +` +const StyledHeader = styled(Header)` + inline-size: fit-content; + z-index: 1; + padding-inline-end: 8px; + text-align: left; + background-color: var(--color-white); +` +const CenteredDivider = styled.div` + border-bottom: 1px solid var(--color-grey-8); + inline-size: 100%; + block-size: 1px; + margin-block-start: -18px; +` +export const InputGroup = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + inline-size: 100%; + block-size: 100%; + + p { + min-height: 30px !important; + } +` +export const InputWrapper = styled.div` + inline-size: 50%; + line-height: 10px; + margin-bottom: 80px; + + @media ${device.tabletL} { + inline-size: 95%; + margin-bottom: 0; + } +` + +const AffiliatesHeader = ({ + translate_text, + sized, +}: { + translate_text: TString + sized?: boolean +}) => { + return ( + + + + + + + ) +} + +export default AffiliatesHeader diff --git a/src/pages/signup-affiliates/utils/_affiliate-input.tsx b/src/pages/signup-affiliates/utils/_affiliate-input.tsx new file mode 100644 index 00000000000..b0f0c255d28 --- /dev/null +++ b/src/pages/signup-affiliates/utils/_affiliate-input.tsx @@ -0,0 +1,148 @@ +import React, { useRef, useState } from 'react' +import styled, { css } from 'styled-components' +import device from 'themes/device' +import { + InputProps, + InputWrapper, + RelativeWrapper, + StyledError, + StyledInput, + StyledLabel, +} from 'components/form/input' +import OpenedEye from 'images/svg/signup-affiliates/opened-eye.svg' +import ClosedEye from 'images/svg/signup-affiliates/closed-eye.svg' +import CrossIcon from 'images/svg/help/cross.svg' + +type AffiliateInputProps = { + password_icon?: boolean + extra_info?: string + extra_info_size?: string +} & InputProps + +export const StyledRelativeWrapper = styled(RelativeWrapper)` + margin-block: 16px 36px; +` +export const ErrorMessage = styled.div<{ error?: boolean; extra_info_size?: string }>` + position: absolute; + font-size: 12px; + color: ${({ error }) => (error ? 'var(--color-red-1)' : 'var(--color-grey-5)')}; + padding: 6px 0; +` +const StyledIcon = styled.img<{ password_icon?: boolean }>` + position: absolute; + right: ${({ password_icon }) => (password_icon ? '2.8rem' : '0.8rem')}; + top: 1.5rem; + height: 1rem; + width: 1.5rem; + cursor: pointer; + + @media ${device.tablet} { + right: ${({ password_icon }) => (password_icon ? '4rem' : '2rem')}; + top: 1.6rem; + } + @media ${device.desktopL} { + top: 1rem; + } +` +export const AffiliateLabel = styled(StyledLabel)` + top: 1.5rem; + color: var(--color-grey-5); +` +export const StyledInputWrapper = styled(InputWrapper)<{ + password_length?: number + is_password?: boolean +}>` + border-radius: 4px; + border: solid 1px var(--color-grey-7); + ${({ password_length, is_password }) => { + if (is_password && password_length == 0) + return css` + border-bottom: solid 4px var(--color-grey-5); + ` + else if (is_password && password_length >= 1) + return css` + border-bottom: solid 4px var(--color-blue-7); + &:hover { + border-color: var(--color-blue-7); + } + ` + else + return css` + border-bottom: solid 1px var(--color-grey-7); + &:hover { + border-color: var(--color-grey-5); + + & > label { + color: var(--color-grey-5); + } + } + ` + }} + + @media ${device.tabletL} { + height: unset; + border-radius: 4px; + } +` + +const AffiliateInput = ({ + label = '', + extra_info, + extra_info_size, + id = '', + error = '', + handleError, + password_icon, + ...props +}: AffiliateInputProps) => { + const current_input = useRef(null) + const [is_password_visible, setPasswordVisible] = useState(false) + + return ( + + + current_input} + type={is_password_visible ? 'text' : props.type} + /> + {label && ( + + {label} + + )} + + {password_icon && ( + setPasswordVisible(!is_password_visible)} + /> + )} + {error && ( + <> + {error} + { + handleError(current_input) + }} + /> + + )} + + ) +} + +export default AffiliateInput diff --git a/src/pages/signup-affiliates/utils/_birth-form.tsx b/src/pages/signup-affiliates/utils/_birth-form.tsx new file mode 100644 index 00000000000..15830541169 --- /dev/null +++ b/src/pages/signup-affiliates/utils/_birth-form.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import BirthPicker from './_birth-picker' +import { StyledInputWrapper, StyledRelativeWrapper } from './_affiliate-input' + +const BirthForm = (props) => { + return ( + + + + + + ) +} + +export default BirthForm diff --git a/src/pages/signup-affiliates/utils/_birth-picker.tsx b/src/pages/signup-affiliates/utils/_birth-picker.tsx new file mode 100644 index 00000000000..98c4e683d4d --- /dev/null +++ b/src/pages/signup-affiliates/utils/_birth-picker.tsx @@ -0,0 +1,137 @@ +import React, { useState } from 'react' +import styled, { css } from 'styled-components' +import DatePicker from 'react-date-picker' +import { AffiliateLabel } from './_affiliate-input' +import Calendar from 'images/svg/signup-affiliates/calendar.svg' + +export type SetStateDate = + | React.Dispatch> + | React.Dispatch> + +type BirthPickerWrapperProps = { + is_date_field: boolean + value: Date + error: string +} +type BirthPickerProps = { + id: string + error: string + value: Date + setFieldValue: SetStateDate + label?: string + label_color?: string + label_focus_color?: string +} + +const BirthPickerWrapper = styled.div` + width: 100%; + height: 100%; + align-items: flex-start; + + .react-date-picker { + display: block; + font-size: 14px; + padding: 0.8rem; + height: 40px; + } + .react-date-picker__wrapper { + border: none; + border-radius: 5px; + } + .react-date-picker__calendar { + width: 320px; + } + .react-calendar__month-view__weekdays__weekday { + font-size: 15px; + font-weight: initial; + } + .react-date-picker__inputGroup { + padding: 0 2px 2px; + } + .react-date-picker__inputGroup__input { + outline: none; + caret-color: transparent; + } + .react-date-picker__inputGroup__input:invalid { + background: none; + } + .react-calendar { + margin-top: 5px; + border: none; + border-radius: 5px; + box-shadow: 0 1.6rem 2rem 0 rgba(0, 0, 0, 0.1); + } + .react-calendar__navigation { + border-bottom: 1px solid #f3f4f5; + } + .react-calendar__navigation__arrow { + font-size: 30px; + font-weight: 100; + } + .react-calendar__navigation__arrow:hover { + background-color: none; + } + .react-calendar__tile--active, + .react-calendar__tile--active:hover, + .react-calendar__tile--hasActive, + .react-calendar__tile--hasActive:hover { + background-color: red; + border-radius: 5px; + } + label { + background-color: var(--color-white); + color: 'green'; + transform: translate(-0.6rem, -2rem) scale(0.7); + padding: 5px 4px; + margin: -5px 0; + ${({ is_date_field, value }) => { + return is_date_field || value + ? css` + transform: translate(-0.6rem, -2.2rem) scale(0.7); + ` + : css` + transform: translate(0rem, 0rem) scale(1); + color: var(--color-${({ error }) => (error ? 'red-1' : 'grey')}); + ` + }} + } +` + +const BirthPicker = ({ id, error, value, setFieldValue, label, label_color }: BirthPickerProps) => { + const [is_date_field, selectDateField] = useState(false) + + const onDateChange = (date) => { + setFieldValue(date) + } + const subtractYears = (numOfYears, date = new Date()) => { + date.setFullYear(date.getFullYear() - numOfYears) + return date + } + const max_date = subtractYears(18) + + return ( + selectDateField(true)} + onKeyDown={(e) => e.preventDefault()} + > + } + clearIcon={null} + /> + + {label} + + + ) +} + +export default BirthPicker diff --git a/src/pages/signup-affiliates/validations/_affilaite_validation.tsx b/src/pages/signup-affiliates/validations/_affilaite_validation.tsx new file mode 100644 index 00000000000..3900e5a9d14 --- /dev/null +++ b/src/pages/signup-affiliates/validations/_affilaite_validation.tsx @@ -0,0 +1,199 @@ +import { localize } from 'components/localization' +/* eslint-disable */ + +export const affiliate_validation_regex = { + email: /^[a-z0-9]+@[a-z0-9]+[.][a-z]{2,}/, + latin: /[^a-zA-Za 0-9-]/, + name: /^[^a-zA-Z-]/, + user_name: /[^a-zA-Za 0-9!"?¨'_.,-]/, + password: /^(?=.*[a-z])(?=.*\d)(?=.*[A-Z])[ -~]*$/, + address: /^[a-zA-Z 0-9/_.,-]*$/, + postal_code: /^[a-zA-Z 0-9_.-]{5,10}$/, + url: /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/, + non_empty_string: /^[^ ]+ *.*$/, +} + +const validation_is_exceed_number = (input, max_digit) => { + const max_digit_value = max_digit || 15 + return input.length <= max_digit_value +} + +const validation_is_lack_number = (input, min_digit) => input.length + 1 > min_digit + +const emailValidation = (input) => { + if (!input) { + return localize('_t_Email is required_t_') + } else if (!affiliate_validation_regex.email.test(input)) { + return localize('_t_Wrong email_t_') + } +} +const userNameValidation = (input) => { + if (!input) { + return localize('_t_Username is required_t_') + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Empty input not available_t_') + } +} +const nameValidation = (input, text, min_digit, max_digit) => { + if (!input) { + return text + localize('_t_ is required_t_') + } else if ( + !validation_is_exceed_number(input, max_digit) || + !validation_is_lack_number(input, min_digit) + ) { + return localize(`_t_You should enter ${min_digit}-${max_digit} characters._t_`) + } else if ( + affiliate_validation_regex.latin.test(input) || + affiliate_validation_regex.name.test(input) + ) { + return localize('_t_Only Latin and Alphabet characters_t_') + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Empty input not available_t_') + } +} +const companyNameValidation = (input, min_digit, max_digit) => { + const length_error = localize(`_t_You should enter ${min_digit}-${max_digit} characters._t_`) + if (!input) { + return localize('_t_Company name is required_t_') + } else if (!/^[a-zA-Z0-9 ]+$/.test(input)) { + return localize('_t_Incorrect company name_t_') + } else if (!validation_is_exceed_number(input, max_digit)) { + return length_error + } else if (!validation_is_lack_number(input, min_digit)) { + return length_error + } else if (affiliate_validation_regex.latin.test(input)) { + return localize('_t_Only Latin characters_t_') + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Empty input not available_t_') + } +} +const phoneValidation = (input) => { + if (!input) { + return localize('_t_Mobile number is required_t_') + } else if (!validation_is_exceed_number(input, 13) || !validation_is_lack_number(input, 8)) { + return localize(`_t_You should enter 8-13 numbers._t_`) + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Space not available_t_') + } +} +const passwordValidation = (input, min_digit, max_digit) => { + if (!input) { + return localize('_t_Password is required_t_') + } else if ( + !validation_is_exceed_number(input, max_digit) || + !validation_is_lack_number(input, min_digit) + ) { + return localize(`_t_You should enter ${min_digit}-${max_digit} characters._t_`) + } else if (!affiliate_validation_regex.password.test(input)) { + return localize( + `_t_Password should have lower and uppercase English letters with numbers._t_`, + ) + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Empty input not available_t_') + } +} +const postcodeValidation = (input, min_digit, max_digit) => { + if (!input) { + return localize('_t_Postcode is required_t_') + } else if ( + !validation_is_exceed_number(input, max_digit) || + !validation_is_lack_number(input, min_digit) + ) { + return localize(`_t_You should enter ${min_digit}-${max_digit} characters._t_`) + } else if (!affiliate_validation_regex.postal_code.test(input)) { + return localize(`_t_Please enter a valid postcode with Latin characters._t_`) + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Empty input not available_t_') + } +} +const registrationNumberValidation = (input, min_digit, max_digit) => { + if (!input) { + return localize('_t_Company registration number is required_t_') + } else if ( + !validation_is_exceed_number(input, max_digit) || + !validation_is_lack_number(input, min_digit) + ) { + return localize(`_t_You should enter ${min_digit}-${max_digit} characters._t_`) + } else if (!affiliate_validation_regex.postal_code.test(input)) { + return localize(`_t_Please enter a valid company registration number._t_`) + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Empty input not available_t_') + } +} +const addressValidation = (input, text, min_digit, max_digit) => { + if (!input) { + return text + } + if (affiliate_validation_regex.latin.test(input)) { + return localize('_t_Only Latin characters_t_') + } else if ( + !validation_is_exceed_number(input, max_digit) || + !validation_is_lack_number(input, min_digit) + ) { + return localize(`_t_You should enter ${min_digit}-${max_digit} characters._t_`) + } else if (!affiliate_validation_regex.address.test(input)) { + return localize('_t_Please enter a valid state_t_') + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Empty input not available_t_') + } +} +const urlValidation = (input) => { + if (!affiliate_validation_regex.url.test(input)) { + return localize(`_t_Please enter a valid url_t_`) + } else if (!affiliate_validation_regex.non_empty_string.test(input)) { + return localize('_t_Empty input not available_t_') + } +} + +const affiliate_validation = { + email: (input) => { + return emailValidation(input) + }, + username: (input) => { + return userNameValidation(input) + }, + first_name: (input) => { + return nameValidation(input, localize('_t_First Name_t_'), 2, 50) + }, + last_name: (input) => { + return nameValidation(input, localize('_t_Last Name_t_'), 2, 50) + }, + country: (input) => { + if (!input || input.display_name === '') { + return localize('_t_Country is required_t_') + } + return null + }, + address_city: (input) => { + return addressValidation(input, localize('_t_City is required_t_'), 2, 256) + }, + address_state: (input) => { + return addressValidation(input, localize('_t_State is required_t_'), 2, 256) + }, + address_street: (input) => { + return addressValidation(input, localize('_t_Street is required_t_'), 2, 256) + }, + address_postal_code: (input) => { + return postcodeValidation(input, 5, 10) + }, + phone: (input) => { + return phoneValidation(input) + }, + password: (input) => { + return passwordValidation(input, 6, 50) + }, + currency: (input) => (input ? null : localize('_t_Currency is required_t_')), + company_name: (input) => { + return companyNameValidation(input, 2, 70) + }, + company_registration_number: (input) => { + return registrationNumberValidation(input, 2, 20) + }, + website_url: (input) => { + return urlValidation(input) + }, + second_website_url: (input) => { + return urlValidation(input) + }, +} +export default affiliate_validation diff --git a/src/translations/ach.json b/src/translations/ach.json index 87de6891873..8e55e66fb0f 100644 --- a/src/translations/ach.json +++ b/src/translations/ach.json @@ -4545,4 +4545,4 @@ "-1165835520": "crwdns3556684:0crwdne3556684:0", "-651384976": "crwdns3556686:0crwdne3556686:0", "-1033881248": "crwdns3556688:0crwdne3556688:0" -} \ No newline at end of file +} diff --git a/src/translations/ar.json b/src/translations/ar.json index c2931a2e5d2..454ca983515 100644 --- a/src/translations/ar.json +++ b/src/translations/ar.json @@ -4545,4 +4545,4 @@ "-1165835520": "موظف", "-651384976": "الجنسيات", "-1033881248": "شاهد صفقاتنا المفتوحة" -} \ No newline at end of file +} diff --git a/src/translations/bn.json b/src/translations/bn.json index c0197c08220..1e4850b67a7 100644 --- a/src/translations/bn.json +++ b/src/translations/bn.json @@ -4545,4 +4545,4 @@ "-1165835520": "কর্মচারী", "-651384976": "জাতীয়তা", "-1033881248": "আমাদের ওপেন পজিশন দেখুন" -} \ No newline at end of file +} diff --git a/src/translations/de.json b/src/translations/de.json index 000ad7dc1e8..8e5dd20459b 100644 --- a/src/translations/de.json +++ b/src/translations/de.json @@ -4545,4 +4545,4 @@ "-1165835520": "Mitarbeiter", "-651384976": "Nationalitäten", "-1033881248": "Sehen Sie unsere offenen Stellen" -} \ No newline at end of file +} diff --git a/src/translations/es.json b/src/translations/es.json index f46631cebe6..5597edc9c2a 100644 --- a/src/translations/es.json +++ b/src/translations/es.json @@ -4545,4 +4545,4 @@ "-1165835520": "empleados", "-651384976": "nacionalidades", "-1033881248": "Vea nuestros puestos disponibles" -} \ No newline at end of file +} diff --git a/src/translations/fr.json b/src/translations/fr.json index 041dff74831..4c0e60f6f89 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -4545,4 +4545,4 @@ "-1165835520": "employés", "-651384976": "nationalités", "-1033881248": "Voir nos postes vacants" -} \ No newline at end of file +} diff --git a/src/translations/it.json b/src/translations/it.json index e70b0349a91..74610902c25 100644 --- a/src/translations/it.json +++ b/src/translations/it.json @@ -4545,4 +4545,4 @@ "-1165835520": "dipendenti", "-651384976": "nazionalità", "-1033881248": "Vedi le posizioni aperte" -} \ No newline at end of file +} diff --git a/src/translations/ko.json b/src/translations/ko.json index 966f9c1625e..046669696ff 100644 --- a/src/translations/ko.json +++ b/src/translations/ko.json @@ -4545,4 +4545,4 @@ "-1165835520": "직원", "-651384976": "출신 국적", "-1033881248": "채용 정보 보기" -} \ No newline at end of file +} diff --git a/src/translations/pl.json b/src/translations/pl.json index 8a7b298df07..ec0fb1e88f4 100644 --- a/src/translations/pl.json +++ b/src/translations/pl.json @@ -4545,4 +4545,4 @@ "-1165835520": "pracowników", "-651384976": "narodowości", "-1033881248": "Zobacz nasze otwarte pozycje" -} \ No newline at end of file +} diff --git a/src/translations/pt.json b/src/translations/pt.json index 6a121aeb747..907b1468041 100644 --- a/src/translations/pt.json +++ b/src/translations/pt.json @@ -4545,4 +4545,4 @@ "-1165835520": "funcionários", "-651384976": "nacionalidades", "-1033881248": "Consulte as nossas vagas" -} \ No newline at end of file +} diff --git a/src/translations/ru.json b/src/translations/ru.json index d6320b4cda4..f055d6b536a 100644 --- a/src/translations/ru.json +++ b/src/translations/ru.json @@ -4545,4 +4545,4 @@ "-1165835520": "сотрудников", "-651384976": "национальностей", "-1033881248": "Смотреть вакансии" -} \ No newline at end of file +} diff --git a/src/translations/si.json b/src/translations/si.json index 443bf92f12b..5321d0ec55e 100644 --- a/src/translations/si.json +++ b/src/translations/si.json @@ -4545,4 +4545,4 @@ "-1165835520": "සේවකයින්", "-651384976": "ජාතීන්", "-1033881248": "අපගේ විවෘත ස්ථාන බලන්න" -} \ No newline at end of file +} diff --git a/src/translations/sw.json b/src/translations/sw.json index 26b621a28fc..da462b37165 100644 --- a/src/translations/sw.json +++ b/src/translations/sw.json @@ -4545,4 +4545,4 @@ "-1165835520": "wafanyakazi", "-651384976": "mataifa", "-1033881248": "Tazama nafasi zetu zilizo wazi" -} \ No newline at end of file +} diff --git a/src/translations/th.json b/src/translations/th.json index 81fbc519c73..6be9e62800b 100644 --- a/src/translations/th.json +++ b/src/translations/th.json @@ -4545,4 +4545,4 @@ "-1165835520": "พนักงาน", "-651384976": "สัญชาติ", "-1033881248": "ดูตำแหน่งงานที่เปิดรับ" -} \ No newline at end of file +} diff --git a/src/translations/tr.json b/src/translations/tr.json index 72d11beb73a..20091e60ef7 100644 --- a/src/translations/tr.json +++ b/src/translations/tr.json @@ -4545,4 +4545,4 @@ "-1165835520": "çalışanlar", "-651384976": "milliyetler", "-1033881248": "Açık pozisyonlarımızı görün" -} \ No newline at end of file +} diff --git a/src/translations/vi.json b/src/translations/vi.json index 2e5c8283d63..33a657dec58 100644 --- a/src/translations/vi.json +++ b/src/translations/vi.json @@ -4545,4 +4545,4 @@ "-1165835520": "nhân viên", "-651384976": "dân tộc", "-1033881248": "Xem các vị trí đang tuyển" -} \ No newline at end of file +} diff --git a/src/translations/zh_cn.json b/src/translations/zh_cn.json index 8f64af2d900..4171d219526 100644 --- a/src/translations/zh_cn.json +++ b/src/translations/zh_cn.json @@ -4545,4 +4545,4 @@ "-1165835520": "雇员", "-651384976": "国籍", "-1033881248": "查看空缺职位" -} \ No newline at end of file +} diff --git a/src/translations/zh_tw.json b/src/translations/zh_tw.json index 81a5a4b01ae..cd81daecaca 100644 --- a/src/translations/zh_tw.json +++ b/src/translations/zh_tw.json @@ -4545,4 +4545,4 @@ "-1165835520": "僱員", "-651384976": "國籍", "-1033881248": "查看空缺職位" -} \ No newline at end of file +}