-
Notifications
You must be signed in to change notification settings - Fork 12
Emcd swap #297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staging
Are you sure you want to change the base?
Emcd swap #297
Changes from all commits
c941eb8
c7ada18
76f20fd
7728ec2
194d204
a7cceda
55e3b98
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| /* eslint-disable no-console */ | ||
| import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; | ||
|
|
||
| // store | ||
| import { addMiddleware } from '../../../store'; | ||
|
|
||
| // Define types for the API responses and requests | ||
| interface ICoin { | ||
| title: string; | ||
| icon_url: string; | ||
| networks: Array<Record<string, string>> | ||
| } | ||
|
|
||
| interface ErrorData { | ||
| message?: string; | ||
| [key: string]: any; | ||
| } | ||
|
|
||
| interface EstimateParams { | ||
| coin_from: string; | ||
| coin_to: string; | ||
| amount: number; | ||
| network_from: string; | ||
| network_to: string; | ||
| } | ||
|
|
||
| interface EstimateResponse { | ||
| amount_to: string; | ||
| rate: string; | ||
| // Add other properties as needed | ||
| } | ||
|
|
||
|
|
||
| export const emcdSwapApi = createApi({ | ||
| reducerPath: 'emcdSwapApi', | ||
| baseQuery: fetchBaseQuery({ | ||
| baseUrl: process.env.REACT_APP_EMCD_SWAP_API_URL || 'https://b2b-endpoint.dev-b2b.mytstnv.site', | ||
| prepareHeaders: (headers) => { | ||
| return headers; | ||
| }, | ||
| }), | ||
| endpoints: (builder) => ({ | ||
| getSwapCoins: builder.query<ICoin[], void>({ | ||
| query: () => 'v2/swap/coins', | ||
| transformErrorResponse: (response: { status: number; data: ErrorData }) => { | ||
| console.error('API Error:', response); | ||
| return { | ||
| status: response.status, | ||
| data: response.data, | ||
| message: response.data?.message || 'An error occurred' | ||
| }; | ||
| }, | ||
| }), | ||
| getEstimate: builder.query<any, any>({ | ||
| query: (params) => { | ||
| const searchParams = new URLSearchParams(); | ||
|
|
||
| Object.entries(params).forEach(([key, value]) => { | ||
| if (value !== undefined && value !== null) { | ||
| searchParams.append(key, String(value)); | ||
| } | ||
| }); | ||
|
|
||
| return `swap/estimate?${searchParams.toString()}`; | ||
| }, | ||
| transformErrorResponse: (response: { status: number; data: ErrorData }) => { | ||
| console.error('API Error:', response); | ||
| return { | ||
| status: response.status, | ||
| data: response.data, | ||
| message: response.data?.message || 'An error occurred' | ||
| }; | ||
| }, | ||
| }), | ||
| createSwap: builder.mutation<any, any>({ | ||
| query: (formData) => ({ | ||
| url: 'swap', | ||
| method: 'POST', | ||
| body: formData, | ||
| }), | ||
| transformErrorResponse: (response: { status: number; data: ErrorData }) => { | ||
| console.error('API Error:', response); | ||
| return { | ||
| status: response.status, | ||
| data: response.data, | ||
| message: response.data?.message || 'An error occurred' | ||
| }; | ||
| }, | ||
| }), | ||
| getSwapStatus: builder.query<any, { swapID: string; status: number }>({ | ||
| query: ({ swapID, status }) => `swap/${swapID}/${status}`, | ||
| transformErrorResponse: (response: { status: number; data: ErrorData }) => { | ||
| console.error('API Error:', response); | ||
| return { | ||
| status: response.status, | ||
| data: response.data, | ||
| message: response.data?.message || 'An error occurred' | ||
| }; | ||
| }, | ||
| }), | ||
|
Comment on lines
+90
to
+100
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Define response type for getSwapStatus endpoint Using +interface SwapStatusResponse {
+ status: number;
+ // Add other properties based on the API response
+}
- getSwapStatus: builder.query<any, { swapID: string; status: number }>({
+ getSwapStatus: builder.query<SwapStatusResponse, { swapID: string; status: number }>({🤖 Prompt for AI Agents |
||
| getSwap: builder.query<any, { swapID: string }>({ | ||
| query: ({ swapID }) => `swap/${swapID}`, | ||
| }), | ||
|
Comment on lines
+101
to
+103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling to getSwap endpoint This endpoint is missing error transformation logic that all other endpoints have. getSwap: builder.query<any, { swapID: string }>({
query: ({ swapID }) => `swap/${swapID}`,
+ transformErrorResponse: (response: { status: number; data: ErrorData }) => {
+ console.error('API Error:', response);
+ return {
+ status: response.status,
+ data: response.data,
+ message: response.data?.message || 'An error occurred'
+ };
+ },
}),
🤖 Prompt for AI Agents |
||
| createUser: builder.mutation<any, any>({ | ||
| query: (formData) => ({ | ||
| url: 'swap/user', | ||
| method: 'POST', | ||
| body: formData, | ||
| }), | ||
| transformErrorResponse: (response: { status: number; data: ErrorData }) => { | ||
| console.error('API Error:', response); | ||
| return { | ||
| status: response.status, | ||
| data: response.data, | ||
| message: response.data?.message || 'An error occurred' | ||
| }; | ||
| }, | ||
| }), | ||
|
|
||
| createTicket: builder.mutation<any, any>({ | ||
| query: (formData) => ({ | ||
| url: 'swap/support/message', | ||
| method: 'POST', | ||
| body: formData, | ||
| }), | ||
| transformErrorResponse: (response: { status: number; data: ErrorData }) => { | ||
| console.error('API Error:', response); | ||
| return { | ||
| status: response.status, | ||
| data: response.data, | ||
| message: response.data?.message || 'An error occurred' | ||
| }; | ||
| }, | ||
| }), | ||
| }), | ||
| }); | ||
|
|
||
| addMiddleware(emcdSwapApi); | ||
|
|
||
| export const { useGetSwapCoinsQuery, useLazyGetEstimateQuery, useCreateSwapMutation, useCreateUserMutation, useLazyGetSwapQuery, useCreateTicketMutation, useLazyGetSwapStatusQuery } = emcdSwapApi; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,69 @@ | ||||||||||||||
| import React, { ReactNode } from 'react'; | ||||||||||||||
|
|
||||||||||||||
| interface ButtonProps { | ||||||||||||||
| children: ReactNode; | ||||||||||||||
| type?: 'shade' | 'main' | 'monochrome'; | ||||||||||||||
| size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'; | ||||||||||||||
| className?: string; | ||||||||||||||
| onClick?: () => void; | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve onClick event handler typing The current - onClick?: () => void;
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;🤖 Prompt for AI Agents |
||||||||||||||
| disabled?: boolean | null; | ||||||||||||||
| buttonType?: 'button' | 'submit' | 'reset'; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const Button = ({ children, type, className, onClick, size = 'sm', disabled, buttonType = 'button' }: ButtonProps) => { | ||||||||||||||
|
|
||||||||||||||
| const getSize = () => { | ||||||||||||||
| const classes = [] | ||||||||||||||
|
|
||||||||||||||
| if (size === 'xs') { | ||||||||||||||
| classes.push('p-1') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (size === 'sm') { | ||||||||||||||
| classes.push('px-4 py-2') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (size === 'md') { | ||||||||||||||
| classes.push('p-4') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (size === 'lg') { | ||||||||||||||
| classes.push('px-6 py-3') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (size === 'xl') { | ||||||||||||||
| classes.push('px-8 py-4') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return classes.join(' ') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| const getType = () => { | ||||||||||||||
| const classes = [] | ||||||||||||||
|
|
||||||||||||||
| if (disabled) { | ||||||||||||||
| return '' | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+44
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve disabled state handling When const getType = () => {
const classes = []
if (disabled) {
- return ''
+ return 'bg-gray-400 cursor-not-allowed opacity-60'
}
// rest of the function remains the same📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| if (type === 'shade') { | ||||||||||||||
| classes.push('bg-bg-5') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (type === 'main') { | ||||||||||||||
| classes.push('bg-brand') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (type === 'monochrome') { | ||||||||||||||
| classes.push('bg-transparent text-color-2 border border-color-3') | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| return classes.join(' ') | ||||||||||||||
| } | ||||||||||||||
| return ( | ||||||||||||||
| <button onClick={onClick} type={buttonType} className={`w-full text-color-1 rounded-sm text-sm outline-none border font-medium border-transparent ${getSize()} ${getType()} ${className}`}> | ||||||||||||||
| {children} | ||||||||||||||
| </button> | ||||||||||||||
|
Comment on lines
+63
to
+65
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add accessibility attributes to the button The button component is missing important accessibility attributes that would improve usability for users with disabilities. return (
<button
onClick={onClick}
type={buttonType}
+ disabled={!!disabled}
+ aria-disabled={!!disabled}
className={`w-full text-color-1 rounded-sm text-sm outline-none border font-medium border-transparent ${getSize()} ${getType()} ${className || ''}`}>
{children}
</button>
);🤖 Prompt for AI Agents |
||||||||||||||
| ); | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| export default Button; | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import React, { ReactNode } from 'react'; | ||
|
|
||
| interface CardProps { | ||
| children: ReactNode; | ||
| className?: string; | ||
| valid?: boolean | null; | ||
| } | ||
|
|
||
| const Card = ({ children, className, valid }:CardProps) => { | ||
| return ( | ||
| <div className={`p-5 border w-full bg-bg-7/70 rounded-5 ${valid ? 'border-bg-5' : 'border-error'} ${className} `}> | ||
| { children } | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default Card; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import React, { useEffect, useState } from 'react'; | ||
|
|
||
| interface DefaultInputProps { | ||
| value: string | null; | ||
| onChange: (value: string | null) => void; | ||
| placeholder?: string; | ||
| } | ||
|
|
||
| const DefaultInput:React.FC<DefaultInputProps> = ({ value, onChange, placeholder }) => { | ||
| const [stateValue, setStateValue] = useState(''); | ||
|
|
||
| useEffect(() => { | ||
| if (value) { | ||
| setStateValue(value) | ||
| } | ||
| }, [value]); | ||
|
|
||
| const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
| setStateValue(e.target.value); | ||
| onChange(e.target.value); | ||
| } | ||
|
|
||
| return ( | ||
| <div className='w-full'> | ||
| <input placeholder={placeholder} value={stateValue} aria-label={placeholder || "Default input"} onChange={handleChange} className='w-full bg-bg-6 p-4 text-color-1 border border-bg-2 hover:border-brand-hover focus:border-brand-active text-sm outline-none rounded-sm transition-all' /> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default DefaultInput; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import React from 'react' | ||
|
|
||
| import FAQItem from './components//FAQItem' | ||
|
|
||
| interface FAQItemData { | ||
| question: string; | ||
| answer: string; | ||
| } | ||
|
|
||
| const faqList: FAQItemData[] = [ | ||
| { | ||
| question: 'Что такое ESWAP?', | ||
| answer: 'ESWAP — это сервис для мгновенного обмена криптовалют. Он интегрирован с разными торговыми платформами, чтобы предложить лучший курс для обмена 500 монет без скрытых комиссий. Для ESWAP не нужно создавать аккаунт', | ||
| }, | ||
| { | ||
| question: 'Как быстро проходит обмен?', | ||
| answer: 'Обмен занимает от двух до двадцати минут — это зависит от загруженности блокчейна. Большинство обменов завершаются всего за несколько минут.', | ||
| }, | ||
| { | ||
| question: 'Как получить криптовалютный кошелек?', | ||
| answer: 'Адрес криптовалютного кошелька — это уникальная комбинация цифр и букв длиной от 26 до 35 символов. Обычно он выглядит так: 17bkZPLB4Wn6F347PLZBR34ijhzQDUFZ4ZC. Чтобы получить свой адрес, нужно создать горячий кошелек или купить холодный. Горячий кошелек можно получить бесплатно в EMCD, если создать аккаунт, а холодный купить, например, Ledger', | ||
| }, | ||
| { | ||
| question: 'Что такое мемо или тег?', | ||
| answer: 'Некоторые монеты, например TON, требуют ввода дополнительного идентификатора сделки при отправке или приеме крипты. Этот идентификатор называется мемо или тег. Если не ввести мемо или тег там, где он требуется, отправленные средства исчезнут', | ||
| }, | ||
| { | ||
| question: 'Что такое адрес кошелька получателя?', | ||
| answer: 'Если ты хочешь купить криптовалюту, тебе нужно отправить ее на определенный криптокошелек. У каждой монеты он свой. Адрес получателя — это твой кошелек, на который переводится криптовалюта после обмена', | ||
| }, | ||
| { | ||
| question: 'Как отменить транзакцию?', | ||
| answer: 'Транзакцию в блокчейне нельзя отменить, поэтому нужно тщательно проверять адрес кошелька, мемо или тег и другие данные перед отправкой криптовалюты.', | ||
| }, | ||
| { | ||
| question: 'Почему финальная сумма обмена отличается от первоначальной?', | ||
| answer: 'Обработка транзакций занимает некоторое время. Из-за высокой волатильности криптовалюты финальный курс обмена может отличаться как в положительную, так и в отрицательную сторону', | ||
| }, | ||
| ] | ||
|
|
||
| const FAQList: React.FC = () => { | ||
| return ( | ||
| <div className="faq-container max-w-4xl mx-auto p-4 mt-5"> | ||
| {faqList.map((item, index) => ( | ||
| <FAQItem key={index} question={item.question} answer={item.answer} /> | ||
| ))} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default FAQList; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import React, { useState } from 'react'; | ||
|
|
||
| interface FAQItemProps { | ||
| question: string; | ||
| answer: string; | ||
| } | ||
|
|
||
| const FAQItem: React.FC<FAQItemProps> = ({ question, answer }) => { | ||
| const [isOpen, setIsOpen] = useState(false); | ||
|
|
||
| const toggle = () => { | ||
| setIsOpen(!isOpen); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="faq-item mb-4 border-b border-bg-35 pb-4"> | ||
| <button | ||
| onClick={toggle} | ||
| className="flex justify-between items-center cursor-pointer" | ||
| > | ||
| <div className='text-sm text-color-1'> | ||
| {question} | ||
| </div> | ||
|
|
||
| <div className="ml-2"> | ||
| <svg | ||
| className={`w-4 h-4 transition-transform ${isOpen ? 'transform rotate-180' : ''}`} | ||
| fill="none" | ||
| stroke="currentColor" | ||
| viewBox="0 0 24 24" | ||
| > | ||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> | ||
| </svg> | ||
| </div> | ||
|
|
||
| </button> | ||
|
|
||
| <div className={`text-xs text-color-3 transition-all overflow-hidden ${isOpen ? 'h-auto' : 'h-0 opacity-0'}`}> | ||
| {answer} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default FAQItem; |
Uh oh!
There was an error while loading. Please reload this page.