diff --git a/public/promo-banners/crumbsup-tokenomics-desktop2x.png b/public/promo-banners/crumbsup-tokenomics-desktop2x.png new file mode 100644 index 00000000..ccf73547 Binary files /dev/null and b/public/promo-banners/crumbsup-tokenomics-desktop2x.png differ diff --git a/public/promo-banners/crumbsup-tokenomics-mobile2x.png b/public/promo-banners/crumbsup-tokenomics-mobile2x.png new file mode 100644 index 00000000..2fd43b58 Binary files /dev/null and b/public/promo-banners/crumbsup-tokenomics-mobile2x.png differ diff --git a/src/app/components/NavBar.tsx b/src/app/components/NavBar.tsx index d35ee3c1..2b11ced8 100644 --- a/src/app/components/NavBar.tsx +++ b/src/app/components/NavBar.tsx @@ -3,7 +3,12 @@ import Image from "next/image"; import Link from "next/link"; import { useSelector } from "react-redux"; -import { useAppDispatch, useAppSelector, useHydrationErrorFix } from "hooks"; +import { + useAppDispatch, + useAppSelector, + useHydrationErrorFix, + useTranslations, +} from "hooks"; import { getSupportedLanguagesAsString } from "../state/i18nSlice"; import { i18nSlice } from "../state/i18nSlice"; @@ -32,11 +37,11 @@ interface NavbarItemMobileProps extends NavbarItemProps { const NavItems: { path: string; title: string }[] = [ { path: "/trade", - title: "Trade", + title: "trade", }, { path: "/rewards", - title: "Rewards", + title: "rewards", }, ]; @@ -219,13 +224,14 @@ function Logo() { } function NavbarItemsDesktop() { + const t = useTranslations(); return ( <>
{NavItems.map((navItem, indx) => { return ( diff --git a/src/app/components/OrderInput.tsx b/src/app/components/OrderInput.tsx index cfa90766..57e2ddd5 100644 --- a/src/app/components/OrderInput.tsx +++ b/src/app/components/OrderInput.tsx @@ -96,17 +96,9 @@ export function OrderInput() { const dispatch = useAppDispatch(); const pairAddress = useAppSelector((state) => state.pairSelector.address); const { walletData } = useAppSelector((state) => state.radix); - const { - type, - side, - token1, - token2, - price, - specifiedToken, - validationPrice, - validationToken1, - validationToken2, - } = useAppSelector((state) => state.orderInput); + const { type, side, token1, token2, price, specifiedToken } = useAppSelector( + (state) => state.orderInput + ); // for better readibility const isMarketOrder = type === "MARKET"; @@ -126,7 +118,6 @@ export function OrderInput() { useEffect(() => { if ( - noValidationErrors(validationPrice, validationToken1, validationToken2) && pairAddressIsSet(pairAddress) && priceIsValid(price, type) && tokenIsSpecified(specifiedToken) @@ -141,9 +132,6 @@ export function OrderInput() { price, side, type, - validationPrice, - validationToken1, - validationToken2, pairAddress, ]); @@ -375,14 +363,27 @@ function SubmitButton() { const isClient = useHydrationErrorFix(); // to fix HydrationError const t = useTranslations(); const dispatch = useAppDispatch(); - const { side, type, token1, quote, quoteDescription, quoteError } = - useAppSelector((state) => state.orderInput); + const { + side, + type, + token1, + quote, + quoteDescription, + quoteError, + validationPrice, + validationToken1, + validationToken2, + } = useAppSelector((state) => state.orderInput); const { isConnected } = useAppSelector((state) => state.radix); const hasQuote = quote !== undefined; const hasQuoteError = quoteError !== undefined; const isLimitOrder = type === OrderType.LIMIT; const isBuyOrder = side === OrderSide.BUY; - const disabled = !hasQuote || hasQuoteError || !isConnected; + const disabled = + !hasQuote || + hasQuoteError || + !isConnected || + !noValidationErrors(validationPrice, validationToken1, validationToken2); const buttonText = !isConnected ? t("connect_wallet_to_trade") : t("market_action_token") diff --git a/src/app/components/PromoBannerCarousel.tsx b/src/app/components/PromoBannerCarousel.tsx index 95b924a6..4c18e868 100644 --- a/src/app/components/PromoBannerCarousel.tsx +++ b/src/app/components/PromoBannerCarousel.tsx @@ -9,6 +9,8 @@ export interface PromoBannerProps { redirectUrl: string; // target redirect address when banner is clicked redirectOpensInSameTab?: boolean; // redirection should not be "_blank" but samepage backgroundColor?: string; // background color, in the format of bg-[#fff] + startDate?: Date; // banner won't be shown before this date is reached + expirationDate?: Date; // banner won't be shown after this date is reached } interface PromoBannerCarouselProps { @@ -24,28 +26,39 @@ export function PromoBannerCarousel({ items, interval = 10000, }: PromoBannerCarouselProps) { + // Filter items based on startDate and expirationDate + const validItems = useMemo(() => { + return items.filter((item) => { + const currentDate = new Date(); + const startDateValid = !item.startDate || item.startDate <= currentDate; + const expirationDateValid = + !item.expirationDate || item.expirationDate >= currentDate; + return startDateValid && expirationDateValid; + }); + }, [items]); + // Use null initially to not show any image and prevent hydration error const [currentImageSrc, setCurrentImageSrc] = useState(null); const [activeIndex, setActiveIndex] = useState(0); const [backgroundColor, setBackgroundColor] = useState( - items[0].backgroundColor || DEFAULT_GRADIENT_BACKGROUND + validItems[0]?.backgroundColor || DEFAULT_GRADIENT_BACKGROUND ); const [fade, setFade] = useState(true); - const hasRedirectUrl = items[activeIndex].redirectUrl !== ""; + const hasRedirectUrl = validItems[activeIndex].redirectUrl !== ""; const redirectOpensInSameTab = - items[activeIndex].redirectOpensInSameTab || false; + validItems[activeIndex].redirectOpensInSameTab || false; const { imageUrlMobile, imageUrl, redirectUrl } = useMemo( - () => items[activeIndex], - [items, activeIndex] + () => validItems[activeIndex], + [validItems, activeIndex] ); const moveToNextSlide = useCallback(() => { setActiveIndex((prevIndex) => - prevIndex === items.length - 1 ? 0 : prevIndex + 1 + prevIndex === validItems.length - 1 ? 0 : prevIndex + 1 ); - }, [items.length]); + }, [validItems.length]); const handleRedirect = () => { if (hasRedirectUrl && typeof window !== "undefined") { @@ -65,13 +78,13 @@ export function PromoBannerCarousel({ }; useEffect(() => { - const selectedBg = items[activeIndex].backgroundColor; + const selectedBg = validItems[activeIndex].backgroundColor; if (selectedBg) { setBackgroundColor(selectedBg); } else { setBackgroundColor(DEFAULT_GRADIENT_BACKGROUND); } - }, [activeIndex, items]); + }, [activeIndex, validItems]); useEffect(() => { // Determine which image to show based on the client's screen size @@ -96,18 +109,15 @@ export function PromoBannerCarousel({ return () => clearInterval(intervalId); }, [moveToNextSlide, interval]); - // const handleDotClick = useCallback((idx: number) => { - // setFade(false); - // setTimeout(() => { - // setActiveIndex(idx); - // setFade(true); - // }, 500); // Duration of the fade out - // }, []); - if (currentImageSrc === null || currentImageSrc === "") { return null; // Return null if no image should be shown } + // Return null if no valid items are available + if (validItems.length === 0) { + return null; + } + return (
diff --git a/src/app/state/locales/pt/rewards.json b/src/app/state/locales/pt/rewards.json index 5a34cd46..0eb0ee95 100644 --- a/src/app/state/locales/pt/rewards.json +++ b/src/app/state/locales/pt/rewards.json @@ -5,7 +5,7 @@ "next_distribution": "Próxima distribuição", "total_rewards": "Total de recompensas", "claim_all_rewards": "Reivindicar todas", - "learn_more_about_rewards": "Saiba mais sobre a recompensas", + "learn_more_about_rewards": "Saiba mais sobre recompensas", "claiming_rewards_loading": "Reivindicando recompensas...", "claiming_rewards_success": "Recompensas reivindicadas", "claiming_rewards_fail": "Erro ao reivindicar recompensas", @@ -13,6 +13,6 @@ "rewards_claimed": "Recompensas resgatadas", "continue_trading_to_earn_more": "Continue negociando ou fazendo staking para ganhar mais e volte mais tarde.", "go_back": "Voltar", - "no_rewards_to_claim": "Sem recompensas para reivindica", + "no_rewards_to_claim": "Sem recompensas para reivindicar", "loading...": "Carregamento..." } diff --git a/src/app/state/locales/pt/trade.json b/src/app/state/locales/pt/trade.json index 3f370d43..991c4428 100644 --- a/src/app/state/locales/pt/trade.json +++ b/src/app/state/locales/pt/trade.json @@ -49,7 +49,7 @@ "order_price": "Preço", "filled_qty": "Qtd preenchida", "completed_perc": "Concluído (%)", - "action": "Açâo", + "action": "Ação", "status": "Status", "pending": "Pendente", "completed": "Completo", @@ -62,8 +62,8 @@ "transaction_in_progress": "Transação em andamento...", "best_buy": "Melhor compra", "best_sell": "Melhor venda", - "no_order_history": "Sem histórico de pedidos", - "no_active_orders": "Sem pedidos ativos", + "no_order_history": "Sem histórico de ordens", + "no_active_orders": "Sem ordens ativas", "no_trade_history": "Sem histórico de negociações", "no_trades_have_occured_yet": "Nenhuma transação foi realizada ainda", "market_action_token": "<$SIDE> <$TOKEN_SYMBOL> a <$ORDER_TYPE>", diff --git a/src/app/state/orderInputSlice.ts b/src/app/state/orderInputSlice.ts index ec0e84da..9d0239f4 100644 --- a/src/app/state/orderInputSlice.ts +++ b/src/app/state/orderInputSlice.ts @@ -248,13 +248,7 @@ export const fetchQuote = createAsyncThunk< { state: RootState } >("orderInput/fetchQuote", async (_arg, thunkAPI) => { const state = thunkAPI.getState(); - if ( - !noValidationErrors( - state.orderInput.validationPrice, - state.orderInput.validationToken1, - state.orderInput.validationToken2 - ) - ) { + if (!state.orderInput.validationPrice.valid) { throw new Error("Validation errors found"); } if (!pairAddressIsSet(state.pairSelector.address)) { diff --git a/src/app/trade/page.tsx b/src/app/trade/page.tsx index 9ee854ec..546d12fc 100644 --- a/src/app/trade/page.tsx +++ b/src/app/trade/page.tsx @@ -79,13 +79,14 @@ export default function Trade() {