diff --git a/apps/user_dashboard/package.json b/apps/user_dashboard/package.json index 8313ec23c..51ef2fc1d 100644 --- a/apps/user_dashboard/package.json +++ b/apps/user_dashboard/package.json @@ -40,6 +40,7 @@ "react": "^19.2.1", "react-dom": "^19.2.1", "react-hook-form": "^7.65.0", + "react-toastify": "^11.0.5", "utility-types": "^3.11.0", "zustand": "^5.0.6" }, diff --git a/apps/user_dashboard/src/app/page.tsx b/apps/user_dashboard/src/app/page.tsx index 709a4044c..fdd7ff2cb 100644 --- a/apps/user_dashboard/src/app/page.tsx +++ b/apps/user_dashboard/src/app/page.tsx @@ -1,3 +1,6 @@ +import { Bounce, ToastContainer } from "react-toastify"; +import { ToastCloseButton } from "@oko-wallet-common-ui/toast/toast"; + import styles from "./page.module.scss"; import { Authorized } from "@oko-wallet-user-dashboard/components/authorized/authorized"; import { DashboardBody } from "@oko-wallet-user-dashboard/components/dashboard_body/dashboard_body"; @@ -23,6 +26,12 @@ export default function Home() { + ); } diff --git a/apps/user_dashboard/src/components/address_chip/address_chip.tsx b/apps/user_dashboard/src/components/address_chip/address_chip.tsx index 90db2fd4a..768c96dc0 100644 --- a/apps/user_dashboard/src/components/address_chip/address_chip.tsx +++ b/apps/user_dashboard/src/components/address_chip/address_chip.tsx @@ -3,6 +3,7 @@ import { Typography } from "@oko-wallet/oko-common-ui/typography"; import { CopyOutlinedIcon } from "@oko-wallet/oko-common-ui/icons/copy_outlined"; import { CheckCircleOutlinedIcon } from "@oko-wallet/oko-common-ui/icons/check_circle_outlined"; +import { displayToast } from "@oko-wallet-user-dashboard/components/toast"; import styles from "./address_chip.module.scss"; interface AddressChipProps { @@ -51,6 +52,11 @@ export const AddressChip: FC = ({ try { await navigator.clipboard.writeText(address); setIsCopied(true); + + displayToast({ + variant: "success", + title: "Copied", + }); setTimeout(() => setIsCopied(false), 1500); } catch (error) { console.error("Failed to copy address:", error); diff --git a/apps/user_dashboard/src/components/my_assets/components/token_item/token_item.tsx b/apps/user_dashboard/src/components/my_assets/components/token_item/token_item.tsx index 1380a0d14..74fed4b52 100644 --- a/apps/user_dashboard/src/components/my_assets/components/token_item/token_item.tsx +++ b/apps/user_dashboard/src/components/my_assets/components/token_item/token_item.tsx @@ -15,6 +15,7 @@ import styles from "./token_item.module.scss"; import type { ViewToken } from "@oko-wallet-user-dashboard/store_legacy/huge-queries"; import { useRootStore } from "@oko-wallet-user-dashboard/state/store"; import { AddressQrModal } from "@oko-wallet-user-dashboard/components/address_qr_modal/address_qr_modal"; +import { displayToast } from "@oko-wallet-user-dashboard/components/toast"; interface TokenItemProps { viewToken: ViewToken; @@ -47,6 +48,11 @@ export const TokenItem: FunctionComponent = observer( try { await navigator.clipboard.writeText(address); setIsCopied(true); + + displayToast({ + variant: "success", + title: "Copied", + }); setTimeout(() => setIsCopied(false), 1500); } catch (error) { console.error("Failed to copy address:", error); diff --git a/apps/user_dashboard/src/components/my_assets/my_assets.tsx b/apps/user_dashboard/src/components/my_assets/my_assets.tsx index c5e5cf8a7..046c0baee 100644 --- a/apps/user_dashboard/src/components/my_assets/my_assets.tsx +++ b/apps/user_dashboard/src/components/my_assets/my_assets.tsx @@ -3,6 +3,7 @@ import { type FC } from "react"; import { Typography } from "@oko-wallet/oko-common-ui/typography"; import { SettingIcon } from "@oko-wallet/oko-common-ui/icons/setting_icon"; +import { Button } from "@oko-wallet/oko-common-ui/button"; import styles from "./my_assets.module.scss"; import { ShowHideChainsModal } from "@oko-wallet-user-dashboard/components/show_hide_chains_modal/show_hide_chains_modal"; @@ -18,12 +19,12 @@ export const MyAssets: FC = () => { ( -
+
+ )} /> diff --git a/apps/user_dashboard/src/components/toast/index.tsx b/apps/user_dashboard/src/components/toast/index.tsx new file mode 100644 index 000000000..99f58b097 --- /dev/null +++ b/apps/user_dashboard/src/components/toast/index.tsx @@ -0,0 +1,33 @@ +import { Toast, type ToastVariant } from "@oko-wallet/oko-common-ui/toast"; +import { type ToastOptions, toast } from "react-toastify"; + +export function displayToast({ + variant, + title, + description, + toastOptions, +}: { + variant: ToastVariant; + title: string; + description?: string; + toastOptions?: Partial; +}) { + toastOptions = { + ...{ + position: "top-right", + autoClose: 5000, + hideProgressBar: true, + closeOnClick: false, + pauseOnHover: true, + draggable: false, + progress: undefined, + pauseOnFocusLoss: false, + }, + ...(toastOptions ?? {}), + }; + + toast( + , + toastOptions, + ); +} diff --git a/ui/oko_common_ui/package.json b/ui/oko_common_ui/package.json index ec6f9ace0..4b0c3b608 100644 --- a/ui/oko_common_ui/package.json +++ b/ui/oko_common_ui/package.json @@ -20,6 +20,7 @@ "./styles/*": "./src/styles/*", "./table": "./src/table/table.tsx", "./theme": "./src/theme/theme_provider.tsx", + "./toast": "./src/toast/toast.tsx", "./toggle": "./src/toggle/toggle.tsx", "./tooltip": "./src/tooltip/tooltip.tsx", "./typography": "./src/typography/typography.tsx", diff --git a/ui/oko_common_ui/src/button/button.module.scss b/ui/oko_common_ui/src/button/button.module.scss index a2ec25ad5..4db3a742a 100644 --- a/ui/oko_common_ui/src/button/button.module.scss +++ b/ui/oko_common_ui/src/button/button.module.scss @@ -44,6 +44,10 @@ pointer-events: none; } + &:hover:not(.disabled) { + background: var(--bg-primary-hover); + } + &.disabled { color: var(--fg-disabled); border: none; diff --git a/ui/oko_common_ui/src/toast/toast.module.scss b/ui/oko_common_ui/src/toast/toast.module.scss new file mode 100644 index 000000000..e4f459646 --- /dev/null +++ b/ui/oko_common_ui/src/toast/toast.module.scss @@ -0,0 +1,58 @@ +:global(.custom-toast) { + display: flex; + border-radius: 12px; + border: 1px solid var(--border-primary); + background-color: var(--white); + box-shadow: var(--shadow-lg); + min-height: fit-content; + padding: 16px; +} + +.successIcon { + color: var(--fg-success-primary); + position: relative; + width: 20px; + height: 20px; + + .successIconOuterRing1 { + position: absolute; + top: -4px; + left: -4px; + width: calc(100% + 8px); + height: calc(100% + 8px); + border-radius: 9999px; + border: 2px solid; + border-color: rgba(#079455, 0.3); + } + + .successIconOuterRing2 { + position: absolute; + top: -9px; + left: -9px; + width: calc(100% + 18px); + height: calc(100% + 18px); + border-radius: 9999px; + border: 2px solid; + border-color: rgba(#079455, 0.1); + } +} + +.toastInner { + display: flex; + align-items: center; + justify-content: flex-start; + + gap: 16px; + width: 100%; +} + +.closeButton { + padding: 0; + margin: 0; + border: none; + background: none; + cursor: pointer; + + width: 20px; + height: 20px; +} diff --git a/ui/oko_common_ui/src/toast/toast.tsx b/ui/oko_common_ui/src/toast/toast.tsx new file mode 100644 index 000000000..fab9ba1da --- /dev/null +++ b/ui/oko_common_ui/src/toast/toast.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { type FC, type MouseEvent } from "react"; + +import { CheckCircleOutlinedIcon } from "@oko-wallet-common-ui/icons/check_circle_outlined"; +import { ErrorIcon } from "@oko-wallet-common-ui/icons/error_icon"; +import { WarningIcon } from "@oko-wallet-common-ui/icons/warning_icon"; +import { InfoCircleIcon } from "@oko-wallet-common-ui/icons/info_circle"; +import { Typography } from "@oko-wallet-common-ui/typography/typography"; +import { XCloseIcon } from "@oko-wallet-common-ui/icons/x_close"; +import styles from "./toast.module.scss"; + +export type ToastVariant = "success" | "error" | "warning" | "info"; +export type ToastItemProps = { + title?: string; + description?: string; + variant: ToastVariant; +}; + +const SuccessToastIcon: FC = () => { + return ( +
+
+
+ +
+ ); +}; + +interface ToastIconProps { + variant: ToastVariant; +} +const ToastIcon: FC = ({ variant }) => { + switch (variant) { + case "success": + return ; + case "error": + return ; + case "warning": + return ; + default: + return ; + } +}; + +export const Toast: FC = ({ title, description, variant }) => { + return ( +
+ + {title && ( + + {title} + + )} + {/* TODO: add description, now there is no description in the design*/} + {/* {description &&
{description}
} */} +
+ ); +}; + +interface ToastCloseButtonProps { + closeToast: (e: MouseEvent) => void; +} +export const ToastCloseButton: FC = ({ closeToast }) => { + return ( + + ); +}; diff --git a/yarn.lock b/yarn.lock index 7cd9aaf03..479928e3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11331,6 +11331,7 @@ __metadata: react: "npm:^19.2.1" react-dom: "npm:^19.2.1" react-hook-form: "npm:^7.65.0" + react-toastify: "npm:^11.0.5" sass: "npm:^1.94.2" tsx: "npm:^4.20.3" typescript: "npm:^5.8.3" @@ -36408,6 +36409,18 @@ __metadata: languageName: node linkType: hard +"react-toastify@npm:^11.0.5": + version: 11.0.5 + resolution: "react-toastify@npm:11.0.5" + dependencies: + clsx: "npm:^2.1.1" + peerDependencies: + react: ^18 || ^19 + react-dom: ^18 || ^19 + checksum: 10c0/50f5b81323ebb1957b2efd0963fac24aa1407155d16ab756ffd6d0f42f8af17e796b3958a9fce13e9d1b945d6c3a5a9ebf13529478474d8a2af4bf1dd0db67d2 + languageName: node + linkType: hard + "react-tooltip@npm:^5.28.0": version: 5.30.0 resolution: "react-tooltip@npm:5.30.0"