From 2c6c8ad6e10036dadda4a57bf3e9e1d3558c846a Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Sat, 29 Aug 2026 07:45:53 +0330 Subject: [PATCH 1/4] imp profile new design & wallet new design --- package-lock.json | 149 ++---------------- .../profile/(routes)/(landing)/page.tsx | 33 ++++ .../(shop)/profile/(routes)/wallet/page.tsx | 17 ++ src/app/(customer)/(shop)/profile/page.tsx | 23 --- .../profile/profile-exit-account.tsx | 22 +++ src/components/profile/profile-menu-item.tsx | 38 +++++ src/components/profile/profile-menu.tsx | 40 +++++ .../profile/profile-page-head-line.tsx | 11 ++ .../profile/profile-person-detail-card.tsx | 22 +++ src/components/wallet/transaction-history.tsx | 38 +++++ src/components/wallet/transaction-item.tsx | 49 ++++++ src/components/wallet/wallet-blanc-card.tsx | 27 ++++ src/proxy.ts | 2 +- 13 files changed, 311 insertions(+), 160 deletions(-) create mode 100644 src/app/(customer)/(shop)/profile/(routes)/(landing)/page.tsx create mode 100644 src/app/(customer)/(shop)/profile/(routes)/wallet/page.tsx delete mode 100644 src/app/(customer)/(shop)/profile/page.tsx create mode 100644 src/components/profile/profile-exit-account.tsx create mode 100644 src/components/profile/profile-menu-item.tsx create mode 100644 src/components/profile/profile-menu.tsx create mode 100644 src/components/profile/profile-page-head-line.tsx create mode 100644 src/components/profile/profile-person-detail-card.tsx create mode 100644 src/components/wallet/transaction-history.tsx create mode 100644 src/components/wallet/transaction-item.tsx create mode 100644 src/components/wallet/wallet-blanc-card.tsx diff --git a/package-lock.json b/package-lock.json index 28b1c3a..003039d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,6 +81,7 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -290,28 +291,6 @@ "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", - "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -683,9 +662,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -702,9 +678,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -721,9 +694,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -740,9 +710,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -759,9 +726,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -778,9 +742,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -797,9 +758,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -816,9 +774,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -835,9 +790,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -860,9 +812,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -885,9 +834,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -910,9 +856,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -935,9 +878,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -960,9 +900,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -985,9 +922,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1010,9 +944,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1280,9 +1211,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1299,9 +1227,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1318,9 +1243,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1337,9 +1259,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2282,9 +2201,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2302,9 +2218,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2322,9 +2235,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2342,9 +2252,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2506,6 +2413,7 @@ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2516,6 +2424,7 @@ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2565,6 +2474,7 @@ "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", @@ -2921,9 +2831,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2938,9 +2845,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2955,9 +2859,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2972,9 +2873,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2989,9 +2887,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3006,9 +2901,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3023,9 +2915,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3040,9 +2929,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3057,9 +2943,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3074,9 +2957,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3175,6 +3055,7 @@ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3530,6 +3411,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", @@ -4146,6 +4028,7 @@ "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4331,6 +4214,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5818,9 +5702,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5842,9 +5723,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5866,9 +5744,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -5890,9 +5765,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6576,6 +6448,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6585,6 +6458,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -7361,6 +7235,7 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7523,6 +7398,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7843,6 +7719,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/app/(customer)/(shop)/profile/(routes)/(landing)/page.tsx b/src/app/(customer)/(shop)/profile/(routes)/(landing)/page.tsx new file mode 100644 index 0000000..83b770d --- /dev/null +++ b/src/app/(customer)/(shop)/profile/(routes)/(landing)/page.tsx @@ -0,0 +1,33 @@ +import Link from "next/link"; +import { requireSession } from "@/lib/auth/session"; +import { LogoutButton } from "@/components/customer/logout-button"; +import ProfileListTileItem from "@/components/profile/profile-menu-item"; +import ProfileListMenu from "@/components/profile/profile-menu"; +import ProfilePageHeadLine from "@/components/profile/profile-page-head-line"; +import ProfilePersonDetailCard from "@/components/profile/profile-person-detail-card"; + +// Minimal placeholder — winofyfrontenddoc.md's bottom-nav references a "پروفایل" +// tab, but no profile screen is documented yet in either doc. +export default async function ProfilePage() { + // await requireSession("/profile"); + return
+ + + + +
+ // return ( + //
+ //

پروفایل

+ // + // سفارش‌های من + // + // + // آدرس‌های من + // + //
+ // + //
+ //
+ // ); +} diff --git a/src/app/(customer)/(shop)/profile/(routes)/wallet/page.tsx b/src/app/(customer)/(shop)/profile/(routes)/wallet/page.tsx new file mode 100644 index 0000000..9827f0b --- /dev/null +++ b/src/app/(customer)/(shop)/profile/(routes)/wallet/page.tsx @@ -0,0 +1,17 @@ +import ProfilePageHeadLine from '@/components/profile/profile-page-head-line' +import TransactionHistory from '@/components/wallet/transaction-history' +import WalletBlancCard from '@/components/wallet/wallet-blanc-card' +import React from 'react' + +const WalletPage = () => { + return ( +
+ + + + +
+ ) +} + +export default WalletPage \ No newline at end of file diff --git a/src/app/(customer)/(shop)/profile/page.tsx b/src/app/(customer)/(shop)/profile/page.tsx deleted file mode 100644 index 415b538..0000000 --- a/src/app/(customer)/(shop)/profile/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Link from "next/link"; -import { requireSession } from "@/lib/auth/session"; -import { LogoutButton } from "@/components/customer/logout-button"; - -// Minimal placeholder — winofyfrontenddoc.md's bottom-nav references a "پروفایل" -// tab, but no profile screen is documented yet in either doc. -export default async function ProfilePage() { - await requireSession("/profile"); - return ( -
-

پروفایل

- - سفارش‌های من - - - آدرس‌های من - -
- -
-
- ); -} diff --git a/src/components/profile/profile-exit-account.tsx b/src/components/profile/profile-exit-account.tsx new file mode 100644 index 0000000..ac53b5b --- /dev/null +++ b/src/components/profile/profile-exit-account.tsx @@ -0,0 +1,22 @@ +'use client'; + +import { ChevronLeft, LogOutIcon } from "lucide-react" + +const ProfileExitAccount = () => { + return
{ alert('logout latter....') }} + > +
+
+ +
+ خروج +
+ +
+} + +export default ProfileExitAccount \ No newline at end of file diff --git a/src/components/profile/profile-menu-item.tsx b/src/components/profile/profile-menu-item.tsx new file mode 100644 index 0000000..885bbc4 --- /dev/null +++ b/src/components/profile/profile-menu-item.tsx @@ -0,0 +1,38 @@ +'use client'; + +import Link from 'next/link'; +import { ChevronLeft, User } from 'lucide-react'; + +interface ProfileMenuItemInterface { + href: string; + text: string; + icon?: React.ReactNode; + isLast?: boolean; +} + +const ProfileMenuItem = ({ + href, + text, + icon, + isLast = false, +}: ProfileMenuItemInterface) => { + return ( + +
+
+ {icon || } +
+ {text} +
+ + + ); +}; + +export default ProfileMenuItem; \ No newline at end of file diff --git a/src/components/profile/profile-menu.tsx b/src/components/profile/profile-menu.tsx new file mode 100644 index 0000000..bb670a2 --- /dev/null +++ b/src/components/profile/profile-menu.tsx @@ -0,0 +1,40 @@ +'use client'; + +import { + ShoppingBag, + MapPin, + Wallet, + Bell, + Headset +} from 'lucide-react'; +import ProfileMenuItem from './profile-menu-item'; +import ProfileExitAccount from './profile-exit-account'; + +const menuItems = [ + { id: 1, href: '/profile/address', text: 'سعادت‌آباد', icon: }, + { id: 2, href: '/profile/orders', text: 'سفارش‌های من', icon: }, + { id: 3, href: '/profile/addresses', text: 'آدرس‌های من', icon: }, + { id: 4, href: '/profile/wallet', text: 'کیف پول', icon: }, + { id: 5, href: '/profile/notifications', text: 'اعلان‌ها', icon: }, + { id: 6, href: '/profile/support', text: 'پشتیبانی', icon: }, +]; + +const ProfileMenu = () => { + return ( +
+ {menuItems.map((item, index) => ( + + ))} +
+ +
+ ); +}; + +export default ProfileMenu; \ No newline at end of file diff --git a/src/components/profile/profile-page-head-line.tsx b/src/components/profile/profile-page-head-line.tsx new file mode 100644 index 0000000..cc4a40c --- /dev/null +++ b/src/components/profile/profile-page-head-line.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +interface ProfilePageHeadLineInterface { + title: String; +} + +const ProfilePageHeadLine = ({ title }: ProfilePageHeadLineInterface) => { + return

{title}

+} + +export default ProfilePageHeadLine \ No newline at end of file diff --git a/src/components/profile/profile-person-detail-card.tsx b/src/components/profile/profile-person-detail-card.tsx new file mode 100644 index 0000000..ed99253 --- /dev/null +++ b/src/components/profile/profile-person-detail-card.tsx @@ -0,0 +1,22 @@ +'use client'; + +import React from 'react' + +const ProfilePersonDetailCard = () => { + return ( +
+
+
+ م.ر +
+
+

محمد رضایی

+

09121234567

+ +
+
+
+ ) +} + +export default ProfilePersonDetailCard \ No newline at end of file diff --git a/src/components/wallet/transaction-history.tsx b/src/components/wallet/transaction-history.tsx new file mode 100644 index 0000000..c168338 --- /dev/null +++ b/src/components/wallet/transaction-history.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import TransactionItem from './transaction-item'; + +const transactions = [ + { id: 1, amount: 250000, description: 'واریز از کارت', type: 'deposit' }, + { id: 2, amount: 45000, description: 'پرداخت فاکتور', type: 'withdraw' }, + { id: 3, amount: 120000, description: 'واریز نقدی', type: 'deposit' }, + { id: 4, amount: 780000, description: 'حقوق ماهیانه', type: 'deposit' }, + { id: 5, amount: 320000, description: 'خرید از فروشگاه', type: 'withdraw' }, + { id: 6, amount: 560000, description: 'واریز چک', type: 'deposit' }, + { id: 7, amount: 95000, description: 'شارژ اینترنت', type: 'withdraw' }, + { id: 8, amount: 1500000, description: 'پرداخت وام', type: 'withdraw' }, + { id: 9, amount: 430000, description: 'واریز از همراه', type: 'deposit' }, + { id: 10, amount: 210000, description: 'خرید آنلاین', type: 'withdraw' }, + { id: 11, amount: 670000, description: 'پاداش عملکرد', type: 'deposit' }, + { id: 12, amount: 185000, description: 'پرداخت قبوض', type: 'withdraw' }, + { id: 13, amount: 920000, description: 'فروش کالا', type: 'deposit' }, + { id: 14, amount: 380000, description: 'انتقال به دوست', type: 'withdraw' }, + { id: 15, amount: 110000, description: 'بازگشت وجه', type: 'deposit' }, +] as const; + +const TransactionHistory = () => { + return ( +
+ {transactions.map((item, index) => ( + + ))} +
+ ); +}; + +export default TransactionHistory; \ No newline at end of file diff --git a/src/components/wallet/transaction-item.tsx b/src/components/wallet/transaction-item.tsx new file mode 100644 index 0000000..c1d876b --- /dev/null +++ b/src/components/wallet/transaction-item.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { ArrowDownCircle, ArrowDownIcon, ArrowUp, ArrowUpCircle } from 'lucide-react'; + +interface TransactionItemProps { + amount: number; + description: string; + type: 'deposit' | 'withdraw'; + isLast?: boolean; +} + +const TransactionItem = ({ + amount, + description, + type, + isLast = false, +}: TransactionItemProps) => { + const isDeposit = type === 'deposit'; + const Icon = isDeposit ? ArrowDownIcon : ArrowUp; + const amountColor = isDeposit ? 'text-[#2E7D32]' : 'text-[#D32F2F]'; + const bgColor = isDeposit ? 'bg-[#2E7D3210]' : 'bg-[#D32F2F10]'; + const iconColor = isDeposit ? 'text-[#2E7D32]' : 'text-[#D32F2F]'; + + const formattedAmount = amount.toLocaleString('fa-IR'); + + return ( +
+
+
+ +
+ {description} +
+ +
+ + {isDeposit ? '+' : '-'} {formattedAmount} تومان + + ۱۴۰۳/۰۸/۲۴ +
+
+ ); +}; + +export default TransactionItem; \ No newline at end of file diff --git a/src/components/wallet/wallet-blanc-card.tsx b/src/components/wallet/wallet-blanc-card.tsx new file mode 100644 index 0000000..08decd0 --- /dev/null +++ b/src/components/wallet/wallet-blanc-card.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { LogOutIcon, PlusIcon } from 'lucide-react' +import React from 'react' + +const WalletBlancCard = () => { + return ( +
+

موجودی کیف پول

+

35000000 تومان

+ +
+ + +
+
+ ) +} + +export default WalletBlancCard \ No newline at end of file diff --git a/src/proxy.ts b/src/proxy.ts index c834035..728b2b1 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -12,7 +12,7 @@ const AUTH_REQUIRED_PREFIXES = [ "/order-groups", "/addresses", "/account", - "/profile", + // "/profile", "/notifications", ]; From 2cd5a9987d95faf655ead0b5665aad35c0db1dcd Mon Sep 17 00:00:00 2001 From: Mohammadali Date: Sat, 29 Aug 2026 08:09:32 +0330 Subject: [PATCH 2/4] add priceUtils -- formatter price --- src/components/wallet/transaction-item.tsx | 5 +-- src/components/wallet/wallet-blanc-card.tsx | 3 +- src/lib/utils/priceUtils.ts | 42 +++++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 src/lib/utils/priceUtils.ts diff --git a/src/components/wallet/transaction-item.tsx b/src/components/wallet/transaction-item.tsx index c1d876b..45c0f38 100644 --- a/src/components/wallet/transaction-item.tsx +++ b/src/components/wallet/transaction-item.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { ArrowDownCircle, ArrowDownIcon, ArrowUp, ArrowUpCircle } from 'lucide-react'; +import { formatPriceWithToman } from '@/lib/utils/priceUtils'; interface TransactionItemProps { amount: number; @@ -20,8 +21,6 @@ const TransactionItem = ({ const bgColor = isDeposit ? 'bg-[#2E7D3210]' : 'bg-[#D32F2F10]'; const iconColor = isDeposit ? 'text-[#2E7D32]' : 'text-[#D32F2F]'; - const formattedAmount = amount.toLocaleString('fa-IR'); - return (
- {isDeposit ? '+' : '-'} {formattedAmount} تومان + {isDeposit ? '+' : '-'} {formatPriceWithToman(amount)} ۱۴۰۳/۰۸/۲۴
diff --git a/src/components/wallet/wallet-blanc-card.tsx b/src/components/wallet/wallet-blanc-card.tsx index 08decd0..b87a119 100644 --- a/src/components/wallet/wallet-blanc-card.tsx +++ b/src/components/wallet/wallet-blanc-card.tsx @@ -1,5 +1,6 @@ 'use client'; +import { formatPriceWithToman } from '@/lib/utils/priceUtils'; import { LogOutIcon, PlusIcon } from 'lucide-react' import React from 'react' @@ -7,7 +8,7 @@ const WalletBlancCard = () => { return (

موجودی کیف پول

-

35000000 تومان

+

{formatPriceWithToman(35000000)}

-
-
-
- ) -} + const { data, error, isLoading } = useAccount(); -export default ProfilePersonDetailCard \ No newline at end of file + if (isLoading) { + return ( +
+

در حال بارگذاری...

+
+ ); + } + + if (error) { + return ( +
+

{error}

+
+ ); + } + + if (!data) { + return ( +
+

اطلاعات پروفایل یافت نشد

+
+ ); + } + + const displayName = getDisplayNameHelper(data); + const initials = getInitialsHelper(data); + + return ( +
+
+
+ {data.avatar ? ( + {displayName} + ) : ( + {initials} + )} +
+
+

{displayName}

+ + {data.phone_number && ( +

+ {data.phone_number} +

+ )} + + +
+
+
+ ); +}; + +export default ProfilePersonDetailCard; \ No newline at end of file diff --git a/src/components/wallet/transaction-history.tsx b/src/components/wallet/transaction-history.tsx index c168338..38d9721 100644 --- a/src/components/wallet/transaction-history.tsx +++ b/src/components/wallet/transaction-history.tsx @@ -27,7 +27,7 @@ const TransactionHistory = () => { key={item.id} amount={item.amount} description={item.description} - type={item.type} // حالا نوعش دقیقاً 'deposit' | 'withdraw' هست + type={item.type} isLast={index === transactions.length - 1} /> ))} diff --git a/src/components/wallet/transaction-item.tsx b/src/components/wallet/transaction-item.tsx index 45c0f38..6bf2c7c 100644 --- a/src/components/wallet/transaction-item.tsx +++ b/src/components/wallet/transaction-item.tsx @@ -1,48 +1,49 @@ import React from 'react'; -import { ArrowDownCircle, ArrowDownIcon, ArrowUp, ArrowUpCircle } from 'lucide-react'; +import { ArrowDownIcon, ArrowUp } from 'lucide-react'; import { formatPriceWithToman } from '@/lib/utils/priceUtils'; interface TransactionItemProps { - amount: number; - description: string; - type: 'deposit' | 'withdraw'; - isLast?: boolean; + amount: number; + description: string; + type: 'deposit' | 'withdraw'; + isLast?: boolean; } const TransactionItem = ({ - amount, - description, - type, - isLast = false, + amount, + description, + type, + isLast = false, }: TransactionItemProps) => { - const isDeposit = type === 'deposit'; - const Icon = isDeposit ? ArrowDownIcon : ArrowUp; - const amountColor = isDeposit ? 'text-[#2E7D32]' : 'text-[#D32F2F]'; - const bgColor = isDeposit ? 'bg-[#2E7D3210]' : 'bg-[#D32F2F10]'; - const iconColor = isDeposit ? 'text-[#2E7D32]' : 'text-[#D32F2F]'; + const isDeposit = type === 'deposit'; + const Icon = isDeposit ? ArrowDownIcon : ArrowUp; + + const amountColor = isDeposit ? 'text-success' : 'text-error'; + const bgColor = isDeposit ? 'bg-success/10' : 'bg-error/10'; + const iconColor = isDeposit ? 'text-success' : 'text-error'; - return ( -
-
-
- -
- {description} -
- -
- - {isDeposit ? '+' : '-'} {formatPriceWithToman(amount)} - - ۱۴۰۳/۰۸/۲۴ -
+ > +
+
+
- ); + {description} +
+ +
+ + {isDeposit ? '+' : '-'} {formatPriceWithToman(amount)} + + ۱۴۰۳/۰۸/۲۴ +
+
+ ); }; export default TransactionItem; \ No newline at end of file diff --git a/src/components/wallet/wallet-blanc-card.tsx b/src/components/wallet/wallet-blanc-card.tsx index b87a119..26b073f 100644 --- a/src/components/wallet/wallet-blanc-card.tsx +++ b/src/components/wallet/wallet-blanc-card.tsx @@ -1,28 +1,52 @@ 'use client'; import { formatPriceWithToman } from '@/lib/utils/priceUtils'; -import { LogOutIcon, PlusIcon } from 'lucide-react' -import React from 'react' +import { LogOutIcon, PlusIcon } from 'lucide-react'; const WalletBlancCard = () => { return ( -
-

موجودی کیف پول

-

{formatPriceWithToman(35000000)}

+
+

+ موجودی کیف پول +

+

+ {formatPriceWithToman(35000000)} +

- -
- ) -} + ); +}; -export default WalletBlancCard \ No newline at end of file +export default WalletBlancCard; \ No newline at end of file diff --git a/src/lib/api/account.ts b/src/lib/api/account.ts new file mode 100644 index 0000000..0ec2102 --- /dev/null +++ b/src/lib/api/account.ts @@ -0,0 +1,8 @@ +"use server"; + +import { gooyalFetch } from "./gooyal-accounts"; +import type { Account } from "@/types/api"; + +export async function getAccount(): Promise { + return gooyalFetch("/users/api/account/"); +} \ No newline at end of file diff --git a/src/lib/api/gooyal-accounts.ts b/src/lib/api/gooyal-accounts.ts new file mode 100644 index 0000000..92a0e16 --- /dev/null +++ b/src/lib/api/gooyal-accounts.ts @@ -0,0 +1,65 @@ +import "server-only"; +import { getValidSession } from "@/lib/auth/session"; +import { ApiError, type WinofyErrorDetails } from "./errors"; +import { logWinofyRequest, logWinofyResponse } from "./dev-logger"; + +const BASE_URL = process.env.GOOYAL_ACCOUNTS_BASE_URL!; + +interface GooyalFetchOptions extends Omit { + auth?: boolean; + params?: Record; + body?: unknown; +} + +export async function gooyalFetch(path: string, options: GooyalFetchOptions = {}): Promise { + const { auth = true, params, headers, body, ...rest } = options; + const method = rest.method ?? "GET"; + + const url = new URL(BASE_URL.replace(/\/$/, "") + path); + if (params) { + for (const [key, value] of Object.entries(params)) { + if (value !== undefined) url.searchParams.set(key, String(value)); + } + } + + const finalHeaders = new Headers(headers); + const hasBody = body !== undefined; + if (hasBody && !finalHeaders.has("Content-Type")) { + finalHeaders.set("Content-Type", "application/json"); + } + + if (auth) { + const session = await getValidSession(); + if (session) { + finalHeaders.set("Authorization", `Bearer ${session.accessToken}`); + } + } + + logWinofyRequest?.(method, url.toString(), body); + + const res = await fetch(url.toString(), { + ...rest, + headers: finalHeaders, + body: hasBody ? JSON.stringify(body) : undefined, + cache: "no-store", + }); + + if (res.status === 204) { + logWinofyResponse?.(method, url.toString(), res.status, null); + return undefined as T; + } + + const data = await res.json().catch(() => null); + logWinofyResponse?.(method, url.toString(), res.status, data); + + if (!res.ok) { + const envelope = data as { status_message?: string; details?: WinofyErrorDetails } | null; + throw new ApiError( + res.status, + envelope?.status_message ?? res.statusText, + envelope?.details ?? null, + ); + } + + return data as T; +} \ No newline at end of file diff --git a/src/lib/auth/gooyal-client.ts b/src/lib/auth/gooyal-client.ts index 132c4a2..6b5e404 100644 --- a/src/lib/auth/gooyal-client.ts +++ b/src/lib/auth/gooyal-client.ts @@ -95,4 +95,4 @@ export async function exchangeOtpForToken(phoneNumber: string, otp: string) { throw new GooyalClientError(res.status, data.error ?? "token_exchange_failed", data.error_description); } return data as GooyalTokenResponse; -} +} \ No newline at end of file diff --git a/src/lib/providers/account-provider.tsx b/src/lib/providers/account-provider.tsx new file mode 100644 index 0000000..fe4ba14 --- /dev/null +++ b/src/lib/providers/account-provider.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { createContext, useContext, type ReactNode } from "react"; +import type { Account } from "@/types/api"; + +type AccountContextValue = { + data: Account | null; + error: string | null; + isLoading: boolean; +}; + +const AccountContext = createContext(undefined); + +export function AccountProvider({ + children, + account, + error = null, +}: { + children: ReactNode; + account: Account | null; + error?: string | null; +}) { + return ( + + {children} + + ); +} + +export function useAccount() { + const ctx = useContext(AccountContext); + if (ctx === undefined) { + throw new Error("useAccount must be used within AccountProvider"); + } + return ctx; +} \ No newline at end of file diff --git a/src/lib/utils/account-utils.ts b/src/lib/utils/account-utils.ts new file mode 100644 index 0000000..fbd53e9 --- /dev/null +++ b/src/lib/utils/account-utils.ts @@ -0,0 +1,49 @@ +'use client'; + +import { useAccount } from '@/lib/providers/account-provider'; +import React from 'react'; + +export function getInitialsHelper(account: { + name?: string | null; + first_name?: string | null; + last_name?: string | null; + username?: string | null; +}): string { + if (account.name?.trim()) { + const parts = account.name.trim().split(/\s+/); + if (parts.length >= 2) { + return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); + } + return parts[0].slice(0, 2).toUpperCase(); + } + + if (account.first_name || account.last_name) { + const first = account.first_name?.[0] ?? ''; + const last = account.last_name?.[0] ?? ''; + return (first + last).toUpperCase() || '؟'; + } + + if (account.username) { + return account.username.slice(0, 2).toUpperCase(); + } + + return '؟'; +} + +export function getDisplayNameHelper(account: { + name?: string | null; + first_name?: string | null; + last_name?: string | null; + username?: string | null; +}): string { + if (account.name?.trim()) return account.name.trim(); + + const fullName = [account.first_name, account.last_name] + .filter(Boolean) + .join(' ') + .trim(); + + if (fullName) return fullName; + + return account.username || 'کاربر'; +} \ No newline at end of file diff --git a/src/types/api.ts b/src/types/api.ts index c0c86e3..2945024 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -310,3 +310,42 @@ export interface SellerAnalytics { chart: SalesChartPoint[]; top_products: TopProduct[]; } + + +// ---- Account / Profile ---------------------------------------------------- + +export type Gender = "m" | "f" | "other" | null; +export type EducationDegree = + | "primary" + | "secondary" + | "diploma" + | "bachelor" + | "master" + | "phd" + | string + | null; + +export interface Account { + uuid: string; + username: string; + email: string | null; + phone_number: string | null; + first_name: string | null; + last_name: string | null; + name: string | null; + avatar: string | null; + gender: Gender; + education_degree: EducationDegree; + education_field: string | null; + birthdate: string | null; // "YYYY-MM-DD" + iban: string | null; + iban_verified: boolean; + province: number | null; + province_name: string | null; + city: number | null; + city_name: string | null; + postal_code: string | null; + address: string | null; + date_joined: string; + last_update: string; +} \ No newline at end of file