diff --git a/components/dashboard/modals/withdraw-success-modal.tsx b/components/dashboard/modals/withdraw-success-modal.tsx
new file mode 100644
index 0000000..74bd379
--- /dev/null
+++ b/components/dashboard/modals/withdraw-success-modal.tsx
@@ -0,0 +1,42 @@
+import { useWithdrawalStore } from "@/store/withdrawalStore";
+import Image from "next/image";
+
+export default function WithdrawalSuccessModal() {
+ const { closeSuccessModal } = useWithdrawalStore();
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
+
+ Withdrawal Successful
+
+
+
+ -100,000
+ NGN
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/dashboard/modals/withdrawal-modal.tsx b/components/dashboard/modals/withdrawal-modal.tsx
new file mode 100644
index 0000000..6a76559
--- /dev/null
+++ b/components/dashboard/modals/withdrawal-modal.tsx
@@ -0,0 +1,222 @@
+import { AlertTriangle, XIcon } from "lucide-react";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import Image from "next/image";
+import { useFormik } from "formik";
+import { useIsMobile } from "@/hooks/useIsMobile";
+import { cn } from "@/lib/utils";
+import { useWithdrawalStore } from "@/store/withdrawalStore";
+import { withdrawalValidationSchema } from "@/utils/withdrawalSchema";
+
+interface WithdrawModalData {
+ walletAddressOrUsername: string;
+ asset: string;
+ amount: number;
+}
+
+const assets = [
+ {
+ name: "USDC",
+ icon: "/usdc.svg",
+ },
+ {
+ name: "ETH",
+ icon: "/eth.svg",
+ },
+ {
+ name: "BNB",
+ icon: "/bnb.svg",
+ },
+];
+
+export default function WithdrawalModal() {
+ const { isWithdrawModalOpen, closeWithdrawModal, openSuccessModal } =
+ useWithdrawalStore();
+ const {
+ values,
+ handleChange,
+ handleSubmit,
+ handleBlur,
+ isValid,
+ setFieldValue,
+ touched,
+ errors,
+ isSubmitting,
+ } = useFormik
({
+ initialValues: {
+ walletAddressOrUsername: "",
+ asset: "USDC",
+ amount: 0,
+ },
+ validationSchema: withdrawalValidationSchema,
+ validateOnMount: true,
+ onSubmit: async (values) => {
+ console.log("Submitting...", values);
+
+ // Simulate network request
+ await new Promise((resolve) => setTimeout(resolve, 1500));
+
+ console.log("Submission successful");
+ closeWithdrawModal();
+ openSuccessModal();
+ },
+ });
+ const isMobile = useIsMobile();
+
+ return (
+ closeWithdrawModal()}
+ >
+
e.stopPropagation()}
+ >
+
+
+ Withdraw to another wallet
+
+ closeWithdrawModal()} />
+
+
+
+
+
+ );
+}
diff --git a/components/dashboard/withdrawal/withdraw-options-mobile-screen.tsx b/components/dashboard/withdrawal/withdraw-options-mobile-screen.tsx
new file mode 100644
index 0000000..81768cb
--- /dev/null
+++ b/components/dashboard/withdrawal/withdraw-options-mobile-screen.tsx
@@ -0,0 +1,82 @@
+"use client";
+
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { useWithdrawalStore } from "@/store/withdrawalStore";
+import {
+ ArrowLeftRight,
+ FolderSymlink,
+ SquareArrowOutUpRight,
+ XIcon,
+} from "lucide-react";
+
+export default function WithdrawOptionsMobileScreen() {
+ const {
+ isMobileWithdrawOptionsOpen,
+ closeMobileWithdrawOptions,
+ openWithdrawModal,
+ } = useWithdrawalStore();
+
+ const handleShowWithdrawModal = () => {
+ closeMobileWithdrawOptions();
+ openWithdrawModal();
+ };
+ return (
+
+ );
+}
diff --git a/hooks/useIsMobile.ts b/hooks/useIsMobile.ts
new file mode 100644
index 0000000..6858f1d
--- /dev/null
+++ b/hooks/useIsMobile.ts
@@ -0,0 +1,15 @@
+import { useEffect, useState } from "react";
+
+export function useIsMobile(breakpoint = 768) {
+ const [isMobile, setIsMobile] = useState(false);
+
+ useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
+ const onChange = (e: MediaQueryListEvent) => setIsMobile(e.matches);
+ setIsMobile(mql.matches);
+ mql.addEventListener("change", onChange);
+ return () => mql.removeEventListener("change", onChange);
+ }, [breakpoint]);
+
+ return isMobile;
+}
diff --git a/public/green-tick.svg b/public/green-tick.svg
new file mode 100644
index 0000000..807a307
--- /dev/null
+++ b/public/green-tick.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/usdc.svg b/public/usdc.svg
new file mode 100644
index 0000000..8f15be1
--- /dev/null
+++ b/public/usdc.svg
@@ -0,0 +1,9 @@
+
diff --git a/store/withdrawalStore.ts b/store/withdrawalStore.ts
new file mode 100644
index 0000000..9f98c04
--- /dev/null
+++ b/store/withdrawalStore.ts
@@ -0,0 +1,28 @@
+import { create } from "zustand";
+
+interface WithdrawalState {
+ isMobileWithdrawOptionsOpen: boolean;
+ isWithdrawModalOpen: boolean;
+ isSuccessModalOpen: boolean;
+ openWithdrawModal: () => void;
+ closeWithdrawModal: () => void;
+ openMobileWithdrawOptions: () => void;
+ closeMobileWithdrawOptions: () => void;
+ openSuccessModal: () => void;
+ closeSuccessModal: () => void;
+ toggleWithdrawModal: () => void;
+}
+
+export const useWithdrawalStore = create((set) => ({
+ isMobileWithdrawOptionsOpen: false,
+ isWithdrawModalOpen: false,
+ isSuccessModalOpen: false,
+ openWithdrawModal: () => set({ isWithdrawModalOpen: true }),
+ closeWithdrawModal: () => set({ isWithdrawModalOpen: false }),
+ openMobileWithdrawOptions: () => set({ isMobileWithdrawOptionsOpen: true }),
+ closeMobileWithdrawOptions: () => set({ isMobileWithdrawOptionsOpen: false }),
+ openSuccessModal: () => set({ isSuccessModalOpen: true }),
+ closeSuccessModal: () => set({ isSuccessModalOpen: false }),
+ toggleWithdrawModal: () =>
+ set((state) => ({ isWithdrawModalOpen: !state.isWithdrawModalOpen })),
+}));
diff --git a/utils/withdrawalSchema.ts b/utils/withdrawalSchema.ts
new file mode 100644
index 0000000..b08cdf7
--- /dev/null
+++ b/utils/withdrawalSchema.ts
@@ -0,0 +1,32 @@
+import * as Yup from "yup";
+
+export const withdrawalValidationSchema = Yup.object({
+ walletAddressOrUsername: Yup.string()
+ .required("Wallet address or username is required")
+ .min(3, "Must be at least 3 characters")
+ .test(
+ "valid-format",
+ "Invalid wallet address or username format",
+ (value) => {
+ if (!value) return false;
+
+ // Check for Ethereum address pattern (0x followed by 40 hex characters)
+ const ethAddressPattern = /^0x[a-fA-F0-9]{40}$/;
+ // Check for username pattern (alphanumeric with underscores/hyphens)
+ const usernamePattern = /^[a-zA-Z0-9_-]{3,30}$/;
+
+ return ethAddressPattern.test(value) || usernamePattern.test(value);
+ }
+ ),
+
+ amount: Yup.number()
+ .required("Amount is required")
+ .positive("Amount must be positive")
+ .min(0.01, "Minimum amount is 0.01")
+ .max(326447, "Amount exceeds available balance")
+ .test("decimal-places", "Maximum 6 decimal places allowed", (value) => {
+ if (!value) return true;
+ const decimalPlaces = (value.toString().split(".")[1] || "").length;
+ return decimalPlaces <= 6;
+ }),
+});