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() {