From 36a24719c1266d03cb974b86606ad08134390233 Mon Sep 17 00:00:00 2001 From: frasermuller Date: Thu, 4 Dec 2025 03:54:40 -0800 Subject: [PATCH] Nearly all the frontend is done, 2 small features to be added. im going to bed... its 4am.. --- .DS_Store | Bin 6148 -> 0 bytes backend/__pycache__/main.cpython-313.pyc | Bin 1373 -> 1956 bytes backend/data/cart.json | 34 ++ backend/data/penalties.json | 38 +- backend/data/products.json | 52 -- backend/data/refunds.json | 36 ++ backend/data/transactions.json | 318 +++++++++++ backend/data/users.json | 16 + backend/data/wishlist.json | 6 +- backend/models/review_model.py | 3 +- backend/routers/auth_router.py | 16 + backend/routers/export_router.py | 9 +- backend/routers/penalty_router.py | 42 +- backend/routers/product_router.py | 4 +- backend/services/image_scraper_service.py | 118 ---- backend/services/product_service.py | 35 +- frontend/README.md | 36 -- frontend/app/admin/export/page.tsx | 145 +++++ frontend/app/admin/metrics/page.tsx | 398 +++++++++++++ frontend/app/admin/page.tsx | 482 ++++------------ frontend/app/admin/penalties/page.tsx | 222 ++++++++ frontend/app/admin/products/page.tsx | 566 +++++++++++++++++++ frontend/app/admin/promote/page.tsx | 146 +++++ frontend/app/admin/refunds/page.tsx | 232 ++++++++ frontend/app/cart/page.tsx | 252 ++++----- frontend/app/checkout/page.tsx | 183 ++++++ frontend/app/dashboard/page.tsx | 266 +++++---- frontend/app/dashboard/penalties/page.tsx | 201 +++++++ frontend/app/favicon.ico | Bin 25931 -> 0 bytes frontend/app/globals.css | 68 ++- frontend/app/layout.tsx | 26 +- frontend/app/login/page.tsx | 103 ++-- frontend/app/page.tsx | 216 +------ frontend/app/products/[id]/page.tsx | 405 ++++++------- frontend/app/products/page.tsx | 386 ++++++++----- frontend/app/register/page.tsx | 106 ++-- frontend/app/transactions/[id]/page.tsx | 206 ------- frontend/app/transactions/page.tsx | 269 +++++++++ frontend/app/wishlist/page.tsx | 156 +++-- frontend/components/AnomaliesAlert.tsx | 146 ----- frontend/components/CategoryPieChart.tsx | 120 ---- frontend/components/CategorySidebar.tsx | 107 ---- frontend/components/MetricsCharts.tsx | 133 ----- frontend/components/Navbar.tsx | 192 ++++--- frontend/components/ProductCard.tsx | 193 +++++-- frontend/components/ProductCarousel.tsx | 201 ------- frontend/components/ReviewManagement.tsx | 183 ------ frontend/components/UserMetricsDashboard.tsx | 270 --------- frontend/contexts/AuthContext.tsx | 34 +- frontend/contexts/CurrencyContext.tsx | 57 -- frontend/lib/api.ts | 331 +++++------ frontend/lib/backend-check.ts | 22 - frontend/lib/currency.ts | 45 -- frontend/public/file.svg | 1 - frontend/public/globe.svg | 1 - frontend/public/next.svg | 1 - frontend/public/vercel.svg | 1 - frontend/public/window.svg | 1 - frontend/types/index.ts | 61 +- 59 files changed, 4505 insertions(+), 3392 deletions(-) delete mode 100644 .DS_Store delete mode 100644 backend/services/image_scraper_service.py delete mode 100644 frontend/README.md create mode 100644 frontend/app/admin/export/page.tsx create mode 100644 frontend/app/admin/metrics/page.tsx create mode 100644 frontend/app/admin/penalties/page.tsx create mode 100644 frontend/app/admin/products/page.tsx create mode 100644 frontend/app/admin/promote/page.tsx create mode 100644 frontend/app/admin/refunds/page.tsx create mode 100644 frontend/app/checkout/page.tsx create mode 100644 frontend/app/dashboard/penalties/page.tsx delete mode 100644 frontend/app/favicon.ico delete mode 100644 frontend/app/transactions/[id]/page.tsx create mode 100644 frontend/app/transactions/page.tsx delete mode 100644 frontend/components/AnomaliesAlert.tsx delete mode 100644 frontend/components/CategoryPieChart.tsx delete mode 100644 frontend/components/CategorySidebar.tsx delete mode 100644 frontend/components/MetricsCharts.tsx delete mode 100644 frontend/components/ProductCarousel.tsx delete mode 100644 frontend/components/ReviewManagement.tsx delete mode 100644 frontend/components/UserMetricsDashboard.tsx delete mode 100644 frontend/contexts/CurrencyContext.tsx delete mode 100644 frontend/lib/backend-check.ts delete mode 100644 frontend/lib/currency.ts delete mode 100644 frontend/public/file.svg delete mode 100644 frontend/public/globe.svg delete mode 100644 frontend/public/next.svg delete mode 100644 frontend/public/vercel.svg delete mode 100644 frontend/public/window.svg diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index a7d99047c2896bbc861576c014c75c407c59e348..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHLO-=$a6n+&Fi7vpM@h6!JXu=IfGM<0~sQd(o3_}E5xSGVZ>^y^sE2D;$3lkSE zJc4VFq49fdM_OP6H^!L0r2R_!-t>E4nU+FCqBN)#i3&uNLt)IWpqgPk&SlD$bk90a z@H5(tV!0ZH-6>Dk;S_KR{6z)$-ObW2HK;{>>-}wwUd*vD3W7>Is-Q2IJ9~Y)Za(kX z(n2t9<-axyP9X+RZgcFH_Xdzc)CZ~2Fz;xO@?Fd8#S_plJAj-_^zOa?#wNT`EZGILxBR`d8dW|!iEw`%B)lLDYfK%X00p1@x z6h>cTtWa(psN@v@m_@fVwD~T9chz xIX-JWltUCY_KOwD5LEg&mJL3N`F{mz81uLR^fkr`(F1co1f&eEati#a0-w6F>0ST; diff --git a/backend/__pycache__/main.cpython-313.pyc b/backend/__pycache__/main.cpython-313.pyc index f82801fbf93d1440f122213c44cc64c9de43e238..33652e61227534fc3d9e30822884113ff794eaa7 100644 GIT binary patch literal 1956 zcmbVM&uimG6rPc6$v@>?Ym>BT4q~_2jZ6|{6Sify?UpuBsKTZt>aRl}Or^0cDof^R z++=gwb`R|_m;Mp`Z)`Bw2o?%G?JXgcrMJ%5mUT&6uwcIV-qZWu(~KVPx>_wE{(RGT z=(qC-{V9y}Da^=qT}J3Pk(Wj@WS)*q>E00JW{C%?* zdUKPWb)&38?bMIkfgjIHY8}eJcjH<7`Pw06&A>l1Naz{+k>`gk!(kCnV|u_m^I1U8 zm)eYVpIBB9xg=;uF?+JNv$J!4XXb7~6fzom6Yp0EZkV~GN`fFdJ&C|?`C**go|-OD zkA}=AL7YbLFKS2L+-y_gQHT?4x#>?4yTsQ^zOL&W?jb`=DC|f(_+9kA+{C7Qn5Z2Z z$D~Ei-};sYZq%U$iwxGLMw}k#cLZ;X9DJeVJ?rb37qXf__}=LS0R?Mz9b5c5wx-u% zeNWsU_}Q!u@x$%z^CSmR#9)o5xXV|3o5O4L0{u~0>aRTPmv{TQ-M`};_x_w$@u9h# zs7>B4r0WBpt0-BSS=*g?+ibfL#2_R|ZB}i2+CDMxDUCo8pb8K}8j1qMAcwMmih!yB zO+bx9B9m@6(TL}rWMvY~58a^W(X?5VX^}*3@um<5n%oYo3igi3QuwgLn>nCQK@49p zR^#tDUdkBbzZ8VmUZLGrsPRTZ_)L1kBT~FlkgE0X><*N@7xE>K%#Bcefa>F1eXKY7 zo1gaU`{U)+vA%q}L{)Kk! zTJSWOcF~Fd*xipS~9(YA)rnjGh5%vsz-RcuZ|I99~#v^g1x;=Yu zk$Sq!)8(G7a9vH!m3ugfy|(ascG6vUGmszYZQf-Cp2aRNhy=?1ry+FcOZnd&mBO}I zE!C}+jdewXCe$j8vb77fhE;`>ke0T`tLJ77EUD9syr)|@EGb)K5mg+O8*FkaY>NZc zp=2I`IuEi#`=#=6^{kR{o7BNMl6+tj%+^Y#DSy~#(W!>81N{yRD6oBtr)u~~bqNwm zI&0TI6tp#r@fSyUxP{W+1mPU{R(6hjy2z0TJ`K)2^Z5&{t-evYBZlNFkH{BpCRK*8 X>)2)NCUQ0WNid%8Xov*=*x&45SGa1* diff --git a/backend/data/cart.json b/backend/data/cart.json index 886dff9..92ff376 100644 --- a/backend/data/cart.json +++ b/backend/data/cart.json @@ -16,5 +16,39 @@ }, "428391c9-ec68-4a25-9d66-0580cec3ce6a": { "items": [] + }, + "79be980f-d83d-4c63-b3b6-77c13193e1a5": { + "items": [ + { + "product_id": "B08HDJ86NZ", + "product_name": "boAt Deuce USB 300 2 in 1 Type-C & Micro USB Stress Resistant, Tangle-Free, Sturdy Cable with 3A Fast Charging & 480mbps Data Transmission, 10000+ Bends Lifespan and Extended 1.5m Length(Martian Red)", + "img_link": "https://m.media-amazon.com/images/I/41V5FtEWPkL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Deuce-300-Resistant-Tangle-Free-Transmission/dp/B08HDJ86NZ/ref=sr_1_4?qid=1672909124&s=electronics&sr=1-4", + "discounted_price": 329.0, + "quantity": 1 + }, + { + "product_id": "B08Y1TFSP6", + "product_name": "pTron Solero TB301 3A Type-C Data and Fast Charging Cable, Made in India, 480Mbps Data Sync, Strong and Durable 1.5-Meter Nylon Braided USB Cable for Type-C Devices for Charging Adapter (Black)", + "img_link": "https://m.media-amazon.com/images/I/31wOPjcSxlL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Solero-TB301-Charging-480Mbps-1-5-Meter/dp/B08Y1TFSP6/ref=sr_1_6?qid=1672909124&s=electronics&sr=1-6", + "discounted_price": 149.0, + "quantity": 1 + }, + { + "product_id": "B098NS6PVG", + "product_name": "Ambrane Unbreakable 60W / 3A Fast Charging 1.5m Braided Type C Cable for Smartphones, Tablets, Laptops & other Type C devices, PD Technology, 480Mbps Data Sync, Quick Charge 3.0 (RCT15A, Black)", + "img_link": "https://m.media-amazon.com/images/I/41lJ8x1yeIL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Ambrane-Unbreakable-Charging-Braided-Cable/dp/B098NS6PVG/ref=sr_1_2?qid=1672909124&s=electronics&sr=1-2", + "discounted_price": 199.0, + "quantity": 1 + } + ] + }, + "ae993132-12a6-40c6-a91a-3fe889d95edd": { + "items": [] + }, + "1cb86309-e8b2-45e3-8da7-1c2f54c65ae5": { + "items": [] } } \ No newline at end of file diff --git a/backend/data/penalties.json b/backend/data/penalties.json index 0637a08..ab9b5fb 100644 --- a/backend/data/penalties.json +++ b/backend/data/penalties.json @@ -1 +1,37 @@ -[] \ No newline at end of file +[ + { + "penalty_id": "7449b7ee-a743-425c-8e70-7b0bf2833e6a", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "reason": "refunding too many products", + "timestamp": "2025-12-04T09:35:08.730552+00:00", + "status": "resolved" + }, + { + "penalty_id": "00c7296d-00c0-40ee-a31d-710c52403f92", + "user_id": "1cb86309-e8b2-45e3-8da7-1c2f54c65ae5", + "reason": "you are spamming", + "timestamp": "2025-12-04T09:43:45.939387+00:00", + "status": "resolved" + }, + { + "penalty_id": "25e7bb8e-a632-4c95-8867-eae62d624923", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "reason": "youve been bad", + "timestamp": "2025-12-04T09:46:06.251422+00:00", + "status": "resolved" + }, + { + "penalty_id": "dcbd8143-49c9-4ed1-900a-5c00839afb1f", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "reason": "this is a test\n", + "timestamp": "2025-12-04T09:51:32.112239+00:00", + "status": "active" + }, + { + "penalty_id": "fcc4f1f7-b804-47b7-aabd-df60422581e1", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "reason": "this will be resolved\n", + "timestamp": "2025-12-04T09:51:39.294157+00:00", + "status": "resolved" + } +] \ No newline at end of file diff --git a/backend/data/products.json b/backend/data/products.json index e93bb65..807dff3 100644 --- a/backend/data/products.json +++ b/backend/data/products.json @@ -12,19 +12,6 @@ "img_link": "https://m.media-amazon.com/images/I/41lJ8x1yeIL._SY300_SX300_QL70_FMwebp_.jpg", "product_link": "https://www.amazon.in/Ambrane-Unbreakable-Charging-Braided-Cable/dp/B098NS6PVG/ref=sr_1_2?qid=1672909124&s=electronics&sr=1-2" }, - { - "product_id": "B096MSW6CT", - "product_name": "Sounce Fast Phone Charging Cable & Data Sync USB Cable Compatible for iPhone 13, 12,11, X, 8, 7, 6, 5, iPad Air, Pro, Mini & iOS Devices", - "category": "Computers&Accessories|Accessories&Peripherals|Cables&Accessories|Cables|USBCables", - "discounted_price": 199.0, - "actual_price": 1899.0, - "discount_percentage": 90.0, - "rating": 3.9, - "rating_count": 7928, - "about_product": "【 Fast Charger& Data Sync】-With built-in safety proctections and four-core copper wires promote maximum signal quality and strength and enhance charging & data transfer speed with up to 480 mb/s transferring speed.|【 Compatibility】-Compatible with iPhone 13, 12,11, X, 8, 7, 6, 5, iPad Air, Pro, Mini & iOS devices.|【 Sturdy & Durable】-The jacket and enforced connector made of TPE and premium copper, are resistant to repeatedly bending and coiling.|【 Ultra High Quality】: According to the experimental results, the fishbone design can accept at least 20,000 bending and insertion tests for extra protection and durability. Upgraded 3D aluminum connector and exclusive laser welding technology, which to ensure the metal part won't break and also have a tighter connection which fits well even with a protective case on and will never loose connection.|【 Good After Sales Service】-Our friendly and reliable customer service will respond to you within 24 hours ! you can purchase with confidence,and every sale includes a 365-day worry-free Service to prove the importance we set on quality.", - "img_link": "https://m.media-amazon.com/images/W/WEBP_402378-T1/images/I/31IvNJZnmdL._SY445_SX342_QL70_FMwebp_.jpg", - "product_link": "https://www.amazon.in/Sounce-iPhone-Charging-Compatible-Devices/dp/B096MSW6CT/ref=sr_1_3?qid=1672909124&s=electronics&sr=1-3" - }, { "product_id": "B08HDJ86NZ", "product_name": "boAt Deuce USB 300 2 in 1 Type-C & Micro USB Stress Resistant, Tangle-Free, Sturdy Cable with 3A Fast Charging & 480mbps Data Transmission, 10000+ Bends Lifespan and Extended 1.5m Length(Martian Red)", @@ -3834,19 +3821,6 @@ "img_link": "https://m.media-amazon.com/images/I/512qfz0MI0L._SX300_SY300_QL70_FMwebp_.jpg", "product_link": "https://www.amazon.in/Kodak-inches-55CA0909-Digital-Surround/dp/B08XXF5V6G/ref=sr_1_462?qid=1672909147&s=electronics&sr=1-462" }, - { - "product_id": "B09HK9JH4F", - "product_name": "Smashtronics® - Case for Firetv Remote, Fire Stick Remote Cover Case, Silicone Cover for TV Firestick 4K/TV 2nd Gen(3rd Gen) Remote Control - Light Weight/Anti Slip/Shockproof (Black)", - "category": "Electronics|HomeTheater,TV&Video|Accessories|RemoteControls", - "discounted_price": 199.0, - "actual_price": 399.0, - "discount_percentage": 50.0, - "rating": 4.2, - "rating_count": 1335, - "about_product": "【100% Fits】Specially designed for Fire TV Stick (2nd Gen), Fire TV Stick 4K, Fire TV Cube, and Amazon Fire TV (3rd Gen, Pendant Design). Tips:Pls compare with your firestick model fit or not before purchase.|【Full Body Protection】High-quality and eco-friendly silicone material, harmless to your pets, kids and families. Prevent the child from opening the back cover and provides the maximum protection, anti-slip, anti-dust, shock proof and washable.|【Custom Cutting】Accurate hole wide open offers full access to all ports, buttons and functions. Humanized texture design protects your remote from slipping and skidding.Fast heat dissipation and anti-dust that no fingerprints leave.|【Easy to Find/Glowing】Equiped with 4 different colors per set.when you have multiple remotes mixed together, Ability to quickly identify, which saves you a lot of time. The glow green and glow blue which has Luminous colors glow in the dark, Let you find your remote control at night quickly. Don't worry losing your remote control anymore.", - "img_link": "https://m.media-amazon.com/images/W/WEBP_402378-T2/images/I/31dENZ1gQVL._SX300_SY300_QL70_FMwebp_.jpg", - "product_link": "https://www.amazon.in/Smashtronics%C2%AE-Silicone-Firestick-Control-Shockproof/dp/B09HK9JH4F/ref=sr_1_463?qid=1672909147&s=electronics&sr=1-463" - }, { "product_id": "B09MMD1FDN", "product_name": "7SEVEN® Suitable Sony Tv Remote Original Bravia for Smart Android Television Compatible for Any Model of LCD LED OLED UHD 4K Universal Sony Remote Control", @@ -4900,19 +4874,6 @@ "img_link": "https://m.media-amazon.com/images/I/4105IiC5tDL._SY300_SX300_QL70_FMwebp_.jpg", "product_link": "https://www.amazon.in/iQOO-Stellar-Snapdragon-Purchased-Separately/dp/B07WJV6P1R/ref=sr_1_47?qid=1672895755&s=electronics&sr=1-47" }, - { - "product_id": "B096MSW6CT", - "product_name": "Sounce Fast Phone Charging Cable & Data Sync USB Cable Compatible for iPhone 13, 12,11, X, 8, 7, 6, 5, iPad Air, Pro, Mini & iOS Devices", - "category": "Computers&Accessories|Accessories&Peripherals|Cables&Accessories|Cables|USBCables", - "discounted_price": 199.0, - "actual_price": 999.0, - "discount_percentage": 80.0, - "rating": 3.9, - "rating_count": 7928, - "about_product": "【 Fast Charger& Data Sync】-With built-in safety proctections and four-core copper wires promote maximum signal quality and strength and enhance charging & data transfer speed with up to 480 mb/s transferring speed.|【 Sturdy & Durable】-The jacket and enforced connector made of TPE and premium copper, are resistant to repeatedly bending and coiling.|【 Ultra High Quality】: According to the experimental results, the fishbone design can accept at least 20,000 bending and insertion tests for extra protection and durability. Upgraded 3D aluminum connector and exclusive laser welding technology, which to ensure the metal part won't break and also have a tighter connection which fits well even with a protective case on and will never loose connection.|【 Good After Sales Service】-Our friendly and reliable customer service will respond to you within 24 hours ! you can purchase with confidence,and every sale includes a 365-day worry-free Service to prove the importance we set on quality.", - "img_link": "https://m.media-amazon.com/images/I/31IvNJZnmdL._SY445_SX342_QL70_ML2_.jpg", - "product_link": "https://www.amazon.in/Sounce-iPhone-Charging-Compatible-Devices/dp/B096MSW6CT/ref=sr_1_48?qid=1672895755&s=electronics&sr=1-48" - }, { "product_id": "B0BF54LXW6", "product_name": "Fire-Boltt Ninja Call Pro Plus 1.83\" Smart Watch with Bluetooth Calling, AI Voice Assistance, 100 Sports Modes IP67 Rating, 240*280 Pixel High Resolution", @@ -8059,19 +8020,6 @@ "img_link": "https://m.media-amazon.com/images/W/WEBP_402378-T2/images/I/31zOsqQOAOL._SY445_SX342_QL70_FMwebp_.jpg", "product_link": "https://www.amazon.in/Ambrane-Unbreakable-Charging-Braided-Cable/dp/B098NS6PVG/ref=sr_1_41?qid=1672902996&s=computers&sr=1-41" }, - { - "product_id": "B096MSW6CT", - "product_name": "Sounce Fast Phone Charging Cable & Data Sync USB Cable Compatible for iPhone 13, 12,11, X, 8, 7, 6, 5, iPad Air, Pro, Mini & iOS Devices", - "category": "Computers&Accessories|Accessories&Peripherals|Cables&Accessories|Cables|USBCables", - "discounted_price": 199.0, - "actual_price": 999.0, - "discount_percentage": 80.0, - "rating": 3.9, - "rating_count": 7928, - "about_product": "【 Fast Charger& Data Sync】-With built-in safety proctections and four-core copper wires promote maximum signal quality and strength and enhance charging & data transfer speed with up to 480 mb/s transferring speed.|【 Compatibility】-Compatible with iPhone 13, 12,11, X, 8, 7, 6, 5, iPad Air, Pro, Mini & iOS devices.|【 Sturdy & Durable】-The jacket and enforced connector made of TPE and premium copper, are resistant to repeatedly bending and coiling.|【 Ultra High Quality】: According to the experimental results, the fishbone design can accept at least 20,000 bending and insertion tests for extra protection and durability. Upgraded 3D aluminum connector and exclusive laser welding technology, which to ensure the metal part won't break and also have a tighter connection which fits well even with a protective case on and will never loose connection.|【 Good After Sales Service】-Our friendly and reliable customer service will respond to you within 24 hours ! you can purchase with confidence,and every sale includes a 365-day worry-free Service to prove the importance we set on quality.", - "img_link": "https://m.media-amazon.com/images/W/WEBP_402378-T1/images/I/31IvNJZnmdL._SY445_SX342_QL70_FMwebp_.jpg", - "product_link": "https://www.amazon.in/Sounce-iPhone-Charging-Compatible-Devices/dp/B096MSW6CT/ref=sr_1_42?qid=1672902996&s=computers&sr=1-42" - }, { "product_id": "B09ZQK9X8G", "product_name": "Noise ColorFit Pro 4 Advanced Bluetooth Calling Smart Watch with 1.72\" TruView Display, Fully-Functional Digital Crown, 311 PPI, 60Hz Refresh Rate, 500 NITS Brightness (Charcoal Black)", diff --git a/backend/data/refunds.json b/backend/data/refunds.json index 8ccf803..268fb67 100644 --- a/backend/data/refunds.json +++ b/backend/data/refunds.json @@ -7,5 +7,41 @@ "status": "approved", "created_at": "2025-12-04T02:06:23.538296", "updated_at": "2025-12-04T02:06:39.740320" + }, + { + "refund_id": "53a14e4e-79b4-42e2-911f-a2092fd0cca1", + "transaction_id": "29ce33c7-ccc3-48b2-b1ed-e4bc0c88ad82", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "message": "These chargers do no work\n", + "status": "approved", + "created_at": "2025-12-04T08:36:22.238907", + "updated_at": "2025-12-04T08:39:12.578734" + }, + { + "refund_id": "d2ef512f-9992-4e1a-b55e-864688ada23c", + "transaction_id": "9b099db3-fe2c-48b2-afbb-f55ccfd678f3", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "message": "no work\n", + "status": "denied", + "created_at": "2025-12-04T08:52:08.053921", + "updated_at": "2025-12-04T09:21:09.454366" + }, + { + "refund_id": "4f54ec90-d394-4492-aad0-a675a248c6db", + "transaction_id": "a8506de4-375e-44a5-ba48-b65859cbe3cc", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "message": "refund me pls", + "status": "denied", + "created_at": "2025-12-04T09:20:19.147848", + "updated_at": "2025-12-04T09:21:06.675910" + }, + { + "refund_id": "a9542e94-2679-443a-92bd-3d09cb3d570d", + "transaction_id": "e0db5af0-bd0e-476a-b86d-1bb743cb609f", + "user_id": "ae993132-12a6-40c6-a91a-3fe889d95edd", + "message": "me no want\n", + "status": "approved", + "created_at": "2025-12-04T10:12:33.057054", + "updated_at": "2025-12-04T10:12:45.085243" } ] \ No newline at end of file diff --git a/backend/data/transactions.json b/backend/data/transactions.json index c5b1911..608c834 100644 --- a/backend/data/transactions.json +++ b/backend/data/transactions.json @@ -82,5 +82,323 @@ "estimated_delivery": "2025-12-09", "status": "refunded" } + ], + "79be980f-d83d-4c63-b3b6-77c13193e1a5": [ + { + "transaction_id": "d7408dc7-e942-4f6a-86f4-546a6985ade3", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B008IFXQFU", + "product_name": "TP-Link USB WiFi Adapter for PC(TL-WN725N), N150 Wireless Network Adapter for Desktop - Nano Size WiFi Dongle Compatible with Windows 11/10/7/8/8.1/XP/ Mac OS 10.9-10.15 Linux Kernel 2.6.18-4.4.3", + "img_link": "https://m.media-amazon.com/images/I/31dDGr5uhaL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/TP-Link-TL-WN725N-150Mbps-Wireless-Adapter/dp/B008IFXQFU/ref=sr_1_10?qid=1672909124&s=electronics&sr=1-10", + "discounted_price": 499.0, + "quantity": 1 + } + ], + "total_price": 499.0, + "timestamp": "2025-12-04T06:45:30.087624+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "6762713d-37a5-4cf4-a2e9-dda06b41a03a", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B07KSMBL2H", + "product_name": "AmazonBasics Flexible Premium HDMI Cable (Black, 4K@60Hz, 18Gbps), 3-Foot", + "img_link": "https://m.media-amazon.com/images/I/51u78zL8y8L._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/AmazonBasics-Flexible-HDMI-Cable-3-Foot/dp/B07KSMBL2H/ref=sr_1_14?qid=1672909124&s=electronics&sr=1-14", + "discounted_price": 219.0, + "quantity": 2 + } + ], + "total_price": 438.0, + "timestamp": "2025-12-04T08:18:13.684292+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "3b14600a-729d-457f-a8b2-8b303c5ae88a", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B07KSMBL2H", + "product_name": "AmazonBasics Flexible Premium HDMI Cable (Black, 4K@60Hz, 18Gbps), 3-Foot", + "img_link": "https://m.media-amazon.com/images/I/51u78zL8y8L._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/AmazonBasics-Flexible-HDMI-Cable-3-Foot/dp/B07KSMBL2H/ref=sr_1_14?qid=1672909124&s=electronics&sr=1-14", + "discounted_price": 219.0, + "quantity": 1 + } + ], + "total_price": 219.0, + "timestamp": "2025-12-04T08:18:38.597700+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "844e130c-e8e8-460d-aa98-746ec28433e0", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B00NH11KIK", + "product_name": "AmazonBasics USB 2.0 Cable - A-Male to B-Male - for Personal Computer, Printer- 6 Feet (1.8 Meters), Black", + "img_link": "https://m.media-amazon.com/images/I/318RrRPyQcL._SX300_SY300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/AmazonBasics-USB-2-0-Cable-Male/dp/B00NH11KIK/ref=sr_1_44?qid=1672909125&s=electronics&sr=1-44", + "discounted_price": 209.0, + "quantity": 1 + } + ], + "total_price": 209.0, + "timestamp": "2025-12-04T08:22:28.531994+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "29ce33c7-ccc3-48b2-b1ed-e4bc0c88ad82", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B098NS6PVG", + "product_name": "Ambrane Unbreakable 60W / 3A Fast Charging 1.5m Braided Type C Cable for Smartphones, Tablets, Laptops & other Type C devices, PD Technology, 480Mbps Data Sync, Quick Charge 3.0 (RCT15A, Black)", + "img_link": "https://m.media-amazon.com/images/I/41lJ8x1yeIL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Ambrane-Unbreakable-Charging-Braided-Cable/dp/B098NS6PVG/ref=sr_1_2?qid=1672909124&s=electronics&sr=1-2", + "discounted_price": 199.0, + "quantity": 1 + } + ], + "total_price": 199.0, + "timestamp": "2025-12-04T08:36:00.675774+00:00", + "estimated_delivery": "2025-12-09", + "status": "refunded" + }, + { + "transaction_id": "e95d3012-70a3-455a-8964-8346a17e9950", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B0789LZTCJ", + "product_name": "boAt Rugged v3 Extra Tough Unbreakable Braided Micro USB Cable 1.5 Meter (Black)", + "img_link": "https://m.media-amazon.com/images/I/41HLa-Z5PrL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Rugged-Extra-Tough-Unbreakable-Braided/dp/B0789LZTCJ/ref=sr_1_13?qid=1672909124&s=electronics&sr=1-13", + "discounted_price": 299.0, + "quantity": 1 + }, + { + "product_id": "B08HDJ86NZ", + "product_name": "boAt Deuce USB 300 2 in 1 Type-C & Micro USB Stress Resistant, Tangle-Free, Sturdy Cable with 3A Fast Charging & 480mbps Data Transmission, 10000+ Bends Lifespan and Extended 1.5m Length(Martian Red)", + "img_link": "https://m.media-amazon.com/images/I/41V5FtEWPkL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Deuce-300-Resistant-Tangle-Free-Transmission/dp/B08HDJ86NZ/ref=sr_1_4?qid=1672909124&s=electronics&sr=1-4", + "discounted_price": 329.0, + "quantity": 1 + } + ], + "total_price": 628.0, + "timestamp": "2025-12-04T08:39:57.304385+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "9b099db3-fe2c-48b2-afbb-f55ccfd678f3", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B01FSYQ2A4", + "product_name": "boAt Rockerz 400 Bluetooth On Ear Headphones With Mic With Upto 8 Hours Playback & Soft Padded Ear Cushions(Grey/Green)", + "img_link": "https://m.media-amazon.com/images/I/41zejggGzLL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Super-Rockerz-400-Bluetooth-Headphones/dp/B01FSYQ2A4/ref=sr_1_307?qid=1672895835&s=electronics&sr=1-307", + "discounted_price": 1399.0, + "quantity": 3 + }, + { + "product_id": "B09MT84WV5", + "product_name": "Samsung EVO Plus 128GB microSDXC UHS-I U3 130MB/s Full HD & 4K UHD Memory Card with Adapter (MB-MC128KA), Blue", + "img_link": "https://m.media-amazon.com/images/I/31R6RP26dzL._SX300_SY300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Samsung-microSDXC-Memory-Adapter-MB-MC128KA/dp/B09MT84WV5/ref=sr_1_66?qid=1672895762&s=electronics&sr=1-66", + "discounted_price": 1149.0, + "quantity": 2 + } + ], + "total_price": 6495.0, + "timestamp": "2025-12-04T08:51:56.742027+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "036e3b9d-14d6-4738-a2d9-dc327174dbbc", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B098NS6PVG", + "product_name": "Ambrane Unbreakable 60W / 3A Fast Charging 1.5m Braided Type C Cable for Smartphones, Tablets, Laptops & other Type C devices, PD Technology, 480Mbps Data Sync, Quick Charge 3.0 (RCT15A, Black)", + "img_link": "https://m.media-amazon.com/images/I/41lJ8x1yeIL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Ambrane-Unbreakable-Charging-Braided-Cable/dp/B098NS6PVG/ref=sr_1_2?qid=1672909124&s=electronics&sr=1-2", + "discounted_price": 199.0, + "quantity": 1 + } + ], + "total_price": 199.0, + "timestamp": "2025-12-04T08:53:13.578543+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "d60b3546-ee75-4966-adcb-45f8b4f23a75", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B098NS6PVG", + "product_name": "Ambrane Unbreakable 60W / 3A Fast Charging 1.5m Braided Type C Cable for Smartphones, Tablets, Laptops & other Type C devices, PD Technology, 480Mbps Data Sync, Quick Charge 3.0 (RCT15A, Black)", + "img_link": "https://m.media-amazon.com/images/I/41lJ8x1yeIL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Ambrane-Unbreakable-Charging-Braided-Cable/dp/B098NS6PVG/ref=sr_1_2?qid=1672909124&s=electronics&sr=1-2", + "discounted_price": 199.0, + "quantity": 1 + } + ], + "total_price": 199.0, + "timestamp": "2025-12-04T09:11:05.709958+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "61d848cf-3a0e-438b-bb2c-3441329c2a81", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B08HDJ86NZ", + "product_name": "boAt Deuce USB 300 2 in 1 Type-C & Micro USB Stress Resistant, Tangle-Free, Sturdy Cable with 3A Fast Charging & 480mbps Data Transmission, 10000+ Bends Lifespan and Extended 1.5m Length(Martian Red)", + "img_link": "https://m.media-amazon.com/images/I/41V5FtEWPkL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Deuce-300-Resistant-Tangle-Free-Transmission/dp/B08HDJ86NZ/ref=sr_1_4?qid=1672909124&s=electronics&sr=1-4", + "discounted_price": 329.0, + "quantity": 1 + } + ], + "total_price": 329.0, + "timestamp": "2025-12-04T09:12:29.348110+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + }, + { + "transaction_id": "a8506de4-375e-44a5-ba48-b65859cbe3cc", + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "customer_name": "Fraser Muller", + "customer_email": "frasermuller2005@gmail.com", + "items": [ + { + "product_id": "B098NS6PVG", + "product_name": "Ambrane Unbreakable 60W / 3A Fast Charging 1.5m Braided Type C Cable for Smartphones, Tablets, Laptops & other Type C devices, PD Technology, 480Mbps Data Sync, Quick Charge 3.0 (RCT15A, Black)", + "img_link": "https://m.media-amazon.com/images/I/41lJ8x1yeIL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Ambrane-Unbreakable-Charging-Braided-Cable/dp/B098NS6PVG/ref=sr_1_2?qid=1672909124&s=electronics&sr=1-2", + "discounted_price": 199.0, + "quantity": 1 + } + ], + "total_price": 199.0, + "timestamp": "2025-12-04T09:19:58.266513+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + } + ], + "ae993132-12a6-40c6-a91a-3fe889d95edd": [ + { + "transaction_id": "e0db5af0-bd0e-476a-b86d-1bb743cb609f", + "user_id": "ae993132-12a6-40c6-a91a-3fe889d95edd", + "customer_name": "Admin", + "customer_email": "admin@example.com", + "items": [ + { + "product_id": "B08HDJ86NZ", + "product_name": "boAt Deuce USB 300 2 in 1 Type-C & Micro USB Stress Resistant, Tangle-Free, Sturdy Cable with 3A Fast Charging & 480mbps Data Transmission, 10000+ Bends Lifespan and Extended 1.5m Length(Martian Red)", + "img_link": "https://m.media-amazon.com/images/I/41V5FtEWPkL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Deuce-300-Resistant-Tangle-Free-Transmission/dp/B08HDJ86NZ/ref=sr_1_4?qid=1672909124&s=electronics&sr=1-4", + "discounted_price": 329.0, + "quantity": 1 + } + ], + "total_price": 329.0, + "timestamp": "2025-12-04T09:20:52.049183+00:00", + "estimated_delivery": "2025-12-09", + "status": "refunded" + }, + { + "transaction_id": "aed414f4-702d-46bd-ad55-c59d74d4db80", + "user_id": "ae993132-12a6-40c6-a91a-3fe889d95edd", + "customer_name": "Admin", + "customer_email": "admin@example.com", + "items": [ + { + "product_id": "B098NS6PVG", + "product_name": "Ambrane Unbreakable 60W / 3A Fast Charging 1.5m Braided Type C Cable for Smartphones, Tablets, Laptops & other Type C devices, PD Technology, 480Mbps Data Sync, Quick Charge 3.0 (RCT15A, Black)", + "img_link": "https://m.media-amazon.com/images/I/41lJ8x1yeIL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Ambrane-Unbreakable-Charging-Braided-Cable/dp/B098NS6PVG/ref=sr_1_2?qid=1672909124&s=electronics&sr=1-2", + "discounted_price": 199.0, + "quantity": 1 + }, + { + "product_id": "B08CF3B7N1", + "product_name": "Portronics Konnect L 1.2M Fast Charging 3A 8 Pin USB Cable with Charge & Sync Function for iPhone, iPad (Grey)", + "img_link": "https://m.media-amazon.com/images/I/41XfpyH4-JL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Portronics-Konnect-POR-1080-Charging-Function/dp/B08CF3B7N1/ref=sr_1_5?qid=1672909124&s=electronics&sr=1-5", + "discounted_price": 154.0, + "quantity": 1 + }, + { + "product_id": "B08HDJ86NZ", + "product_name": "boAt Deuce USB 300 2 in 1 Type-C & Micro USB Stress Resistant, Tangle-Free, Sturdy Cable with 3A Fast Charging & 480mbps Data Transmission, 10000+ Bends Lifespan and Extended 1.5m Length(Martian Red)", + "img_link": "https://m.media-amazon.com/images/I/41V5FtEWPkL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Deuce-300-Resistant-Tangle-Free-Transmission/dp/B08HDJ86NZ/ref=sr_1_4?qid=1672909124&s=electronics&sr=1-4", + "discounted_price": 329.0, + "quantity": 1 + } + ], + "total_price": 682.0, + "timestamp": "2025-12-04T11:50:41.535383+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + } + ], + "1cb86309-e8b2-45e3-8da7-1c2f54c65ae5": [ + { + "transaction_id": "beb1b95f-b910-41b2-9c41-1cc14ee16ae4", + "user_id": "1cb86309-e8b2-45e3-8da7-1c2f54c65ae5", + "customer_name": "promote me", + "customer_email": "promoteme@gmail.com", + "items": [ + { + "product_id": "B08HDJ86NZ", + "product_name": "boAt Deuce USB 300 2 in 1 Type-C & Micro USB Stress Resistant, Tangle-Free, Sturdy Cable with 3A Fast Charging & 480mbps Data Transmission, 10000+ Bends Lifespan and Extended 1.5m Length(Martian Red)", + "img_link": "https://m.media-amazon.com/images/I/41V5FtEWPkL._SY300_SX300_QL70_FMwebp_.jpg", + "product_link": "https://www.amazon.in/Deuce-300-Resistant-Tangle-Free-Transmission/dp/B08HDJ86NZ/ref=sr_1_4?qid=1672909124&s=electronics&sr=1-4", + "discounted_price": 329.0, + "quantity": 1 + } + ], + "total_price": 329.0, + "timestamp": "2025-12-04T09:43:00.145261+00:00", + "estimated_delivery": "2025-12-09", + "status": "completed" + } ] } \ No newline at end of file diff --git a/backend/data/users.json b/backend/data/users.json index 8e76523..2cec180 100644 --- a/backend/data/users.json +++ b/backend/data/users.json @@ -22,5 +22,21 @@ "password_hash": "$2b$12$U/RCnvijhaHtapuhY4GaIuXy.eWyCtFGU.8.pnQl6QhGoP78c6DAK", "role": "customer", "user_token": "Cwb2TLiTujfZMAyhDUuo2teVnB02" + }, + { + "user_id": "79be980f-d83d-4c63-b3b6-77c13193e1a5", + "name": "Fraser Muller", + "email": "frasermuller2005@gmail.com", + "password_hash": "$2b$12$0WBVrzELlsKw6Vez3bEViO1pdYXNeeNGzYHu0ZiR8oLKW/EEpdPT.", + "role": "customer", + "user_token": "7xf3lzPO8xV2D6UUfNquoWifPK1N" + }, + { + "user_id": "1cb86309-e8b2-45e3-8da7-1c2f54c65ae5", + "name": "promote me", + "email": "promoteme@gmail.com", + "password_hash": "$2b$12$AeRCEl0GyH3aWZ0E8zDnZebtkk04jQIcr/hu/vqYp9E7IrGpYqlvO", + "role": "customer", + "user_token": "VoooH4Eb0DBF9BOJwQvfW2dM0heB" } ] \ No newline at end of file diff --git a/backend/data/wishlist.json b/backend/data/wishlist.json index 576bfe0..1c30bb1 100644 --- a/backend/data/wishlist.json +++ b/backend/data/wishlist.json @@ -8,5 +8,9 @@ ], "428391c9-ec68-4a25-9d66-0580cec3ce6a": [ "B08HDJ86NZ" - ] + ], + "79be980f-d83d-4c63-b3b6-77c13193e1a5": [ + "B08HDJ86NZ" + ], + "ae993132-12a6-40c6-a91a-3fe889d95edd": [] } \ No newline at end of file diff --git a/backend/models/review_model.py b/backend/models/review_model.py index 661b9fc..0dfe776 100644 --- a/backend/models/review_model.py +++ b/backend/models/review_model.py @@ -26,5 +26,4 @@ class AddReviewRequest(BaseModel): user_id: str user_name: str review_title: str - review_content: str - # rating: float # Optional rating + review_content: str diff --git a/backend/routers/auth_router.py b/backend/routers/auth_router.py index 6b6f288..671308c 100644 --- a/backend/routers/auth_router.py +++ b/backend/routers/auth_router.py @@ -134,6 +134,22 @@ async def read_current_user(current_user: User = Depends(get_current_user_dep)): } +@router.get("/users") +async def get_all_users(current_user: User = Depends(admin_required_dep)): + """Admin-only: Get list of all users""" + users = auth_service._load_all_users() + return [ + { + "user_id": u.user_id, + "name": u.name, + "email": u.email, + "user_token": u.user_token, + "role": u.role + } + for u in users + ] + + @router.get("/admin-only") async def admin_only_route(current_user: User = Depends(admin_required_dep)): """ Endpoint protected by admin_required dependency.""" diff --git a/backend/routers/export_router.py b/backend/routers/export_router.py index f897ba5..bb3fff7 100644 --- a/backend/routers/export_router.py +++ b/backend/routers/export_router.py @@ -70,10 +70,7 @@ async def get_available_exports( Admin-only: Get list of available files that can be exported. Returns: - List of file keys that can be used in the /export endpoint + List of filenames that can be downloaded """ - available = export_service.get_available_files() - return { - "available_files": available, - "total": len(available) - } + # Return list of actual filenames (e.g., users.json, products.json) + return [export_service.ALLOWED_FILES[key] for key in export_service.get_available_files()] diff --git a/backend/routers/penalty_router.py b/backend/routers/penalty_router.py index 2a04919..cffc615 100644 --- a/backend/routers/penalty_router.py +++ b/backend/routers/penalty_router.py @@ -5,7 +5,7 @@ from backend.models.penalty_model import ApplyPenaltyRequest, PenaltyResponse, Penalty from backend.models.user_model import User from backend.services.penalty_service import PenaltyService -from backend.services.auth_service import admin_required_dep, AuthService +from backend.services.auth_service import admin_required_dep, get_current_user_dep, AuthService # Create router with prefix /penalties and tag "penalties" # All endpoints in this router will be accessible at /penalties/* @@ -16,6 +16,46 @@ auth_service = AuthService() +# GET /penalties/my-penalties - Get penalties for the authenticated user (Customer-accessible) +@router.get("/my-penalties", response_model=List[Penalty]) +async def get_my_penalties( + status: Optional[str] = Query( + None, + description="Optional status filter: 'active' or 'resolved'. If omitted, returns all.", + ), + current_user: User = Depends(get_current_user_dep), +): + """ + Customer-accessible: list penalties for the authenticated user. + + - IN (optional): query param status = 'active' | 'resolved' + - OUT: JSON array of Penalty objects for the current user, sorted newest first + """ + try: + # Validate status parameter if provided + if status is not None: + normalized_status = status.lower() + if normalized_status not in {"active", "resolved"}: + raise HTTPException( + status_code=400, + detail=f"Invalid status value: '{status}'. Must be 'active' or 'resolved'.", + ) + + # Use the service to load and optionally filter penalties by status + penalties = penalty_service.get_user_penalties(user_id=current_user.user_id, status=status) + + return penalties + except HTTPException: + # Re-raise HTTPException (404, etc.) without wrapping + raise + except Exception as e: + # Generic error handler (e.g., file read issues) + raise HTTPException( + status_code=500, + detail=f"Failed to retrieve penalties: {str(e)}", + ) + + # POST /penalties/apply - Apply a penalty to a user (Admin-only) # This endpoint allows admins to create penalty records for users @router.post("/apply", response_model=PenaltyResponse, dependencies=[Depends(admin_required_dep)]) diff --git a/backend/routers/product_router.py b/backend/routers/product_router.py index 921ce6f..d2cfcf8 100644 --- a/backend/routers/product_router.py +++ b/backend/routers/product_router.py @@ -22,7 +22,7 @@ async def get_all_products(sort: Optional[str] = None): products = product_service.get_all_products() # Return all products - #sort if requested + # Only sort if explicitly requested (don't apply default sorting) if sort: products = product_service.sort_products(products, sort) @@ -34,7 +34,7 @@ async def search_products(keyword: str, sort: Optional[str] = None): # call product_service's method to search products by keyword products = product_service.get_product_by_keyword(keyword) - # sort if requested + # Only sort if explicitly requested (don't default to popularity for search) if sort: products = product_service.sort_products(products, sort) diff --git a/backend/services/image_scraper_service.py b/backend/services/image_scraper_service.py deleted file mode 100644 index ef32a28..0000000 --- a/backend/services/image_scraper_service.py +++ /dev/null @@ -1,118 +0,0 @@ -# Image Scraper Service: Scrape Amazon product images - -import re -from typing import Optional -from urllib.parse import urlparse, urljoin -import httpx -from bs4 import BeautifulSoup - - -class ImageScraperService: - """Service for scraping Amazon product images from product pages""" - - def __init__(self): - # Amazon headers to mimic a real browser request - self.headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'en-US,en;q=0.5', - 'Accept-Encoding': 'gzip, deflate', - 'Connection': 'keep-alive', - 'Upgrade-Insecure-Requests': '1', - } - self.timeout = 30.0 # 30 second timeout - - def fetch_image_url(self, product_link: str) -> Optional[str]: - """ - Fetch the main product image URL from an Amazon product page. - - Args: - product_link: URL to the Amazon product page - - Returns: - Clean, stable image URL or None if not found/error - """ - try: - # Use httpx to fetch the page - with httpx.Client(headers=self.headers, timeout=self.timeout, follow_redirects=True) as client: - response = client.get(product_link) - response.raise_for_status() - - # Parse HTML with BeautifulSoup - soup = BeautifulSoup(response.text, 'html.parser') - - # Try to find the landingImage element - landing_img = soup.find('img', {'id': 'landingImage'}) - - if landing_img: - # Get the src attribute - img_url = landing_img.get('src') or landing_img.get('data-src') - - if img_url: - # Clean the URL - remove query parameters that might cause issues - cleaned_url = self._clean_image_url(img_url) - return cleaned_url - - # Fallback: Try to find other common image selectors - # Amazon sometimes uses different structures - img_selectors = [ - ('img', {'id': 'landingImage'}), - ('img', {'id': 'main-image'}), - ('div', {'id': 'main-image-container'}), - ('img', {'class': re.compile(r'product-image', re.I)}), - ] - - for selector, attrs in img_selectors: - element = soup.find(selector, attrs) - if element: - if selector == 'img': - img_url = element.get('src') or element.get('data-src') - else: - img_tag = element.find('img') - if img_tag: - img_url = img_tag.get('src') or img_tag.get('data-src') - else: - continue - - if img_url: - cleaned_url = self._clean_image_url(img_url) - return cleaned_url - - return None - - except httpx.HTTPError as e: - # Network or HTTP errors - print(f"HTTP error fetching image from {product_link}: {e}") - return None - except Exception as e: - # Any other errors (parsing, etc.) - print(f"Error fetching image from {product_link}: {e}") - return None - - def _clean_image_url(self, url: str) -> str: - """ - Clean and normalize the image URL. - Removes unnecessary query parameters and ensures proper format. - """ - if not url: - return url - - # Parse the URL - parsed = urlparse(url) - - # For Amazon images, we want to keep the core path but clean query params - # Amazon image URLs typically look like: - # https://m.media-amazon.com/images/I/[IMAGE_ID]._AC_SL1500_.jpg - - # Remove common problematic query parameters but keep essential ones - # Build clean URL - clean_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" - - # If it's a relative URL, try to make it absolute - if not parsed.netloc: - clean_url = urljoin("https://www.amazon.in", url) - parsed = urlparse(clean_url) - clean_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" - - return clean_url - diff --git a/backend/services/product_service.py b/backend/services/product_service.py index 26abcc6..93c74c9 100644 --- a/backend/services/product_service.py +++ b/backend/services/product_service.py @@ -5,7 +5,6 @@ import random from typing import List, Optional from backend.models.product_model import Product -from backend.services.image_scraper_service import ImageScraperService from backend.repositories.product_repository import ProductRepository @@ -16,7 +15,6 @@ def __init__(self): # Create our own ProductRepository internally # ProductRepository is locked to products.json (or products_test.json in tests) self.repository = ProductRepository() - self.image_scraper = ImageScraperService() # Load all products from repository def _repo_load(self) -> List[dict]: @@ -233,10 +231,12 @@ def get_product_by_keyword(self, keyword: str) -> List[Product]: # Load all products using helper method products = self._load_all_products() - # checks if keyword is in product name or category (case insensitive) and adds to the list of matching products then returns all matches (the list) + # Search only in product name (case insensitive) for exact matches + # Returns products where the keyword appears in the product name matching_products = [] + keyword_lower = keyword.lower() for product in products: - if keyword.lower() in product.product_name.lower() or keyword.lower() in product.category.lower(): + if keyword_lower in product.product_name.lower(): matching_products.append(product) return matching_products @@ -245,14 +245,21 @@ def get_product_by_keyword(self, keyword: str) -> List[Product]: def sort_products(self, products: List[Product], sort_by: str) -> List[Product]: # Sort a list of products by the specified field. # sort_by options: + # - 'name_asc': Name A-Z + # - 'name_desc': Name Z-A # - 'price_asc': Price low to high # - 'price_desc': Price high to low # - 'rating_asc': Rating low to high # - 'rating_desc': Rating high to low # - 'discount_asc': Discount low to high # - 'discount_desc': Discount high to low + # If invalid option provided, return products unsorted (natural order from JSON) - if sort_by == "price_asc": + if sort_by == "name_asc": + return sorted(products, key=lambda p: p.product_name.lower()) + elif sort_by == "name_desc": + return sorted(products, key=lambda p: p.product_name.lower(), reverse=True) + elif sort_by == "price_asc": return sorted(products, key=lambda p: p.discounted_price) elif sort_by == "price_desc": return sorted(products, key=lambda p: p.discounted_price, reverse=True) @@ -265,7 +272,8 @@ def sort_products(self, products: List[Product], sort_by: str) -> List[Product]: elif sort_by == "discount_desc": return sorted(products, key=lambda p: p.discount_percentage, reverse=True) else: - return products # no sorting if invalid option + # Return unsorted if invalid option (natural order from products.json) + return products def fetch_and_update_image(self, product_id: str) -> Product: """ @@ -285,19 +293,8 @@ def fetch_and_update_image(self, product_id: str) -> Product: if product is None: raise ValueError(f"Product with ID {product_id} not found") - # Fetch the image URL from Amazon - new_image_url = self.image_scraper.fetch_image_url(product.product_link) - - if not new_image_url: - raise ValueError(f"Failed to fetch image from product link: {product.product_link}") - - # Update the product with the new image URL - updated_product = self.update_product( - product_id=product_id, - img_link=new_image_url - ) - - return updated_product + # Image scraping functionality has been removed + raise NotImplementedError("Image fetching functionality is not available") def fetch_all_images(self) -> dict: """ diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index e215bc4..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/frontend/app/admin/export/page.tsx b/frontend/app/admin/export/page.tsx new file mode 100644 index 0000000..fccd2f3 --- /dev/null +++ b/frontend/app/admin/export/page.tsx @@ -0,0 +1,145 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { Download } from 'lucide-react'; +import { exportAPI } from '@/lib/api'; +import { useAuth } from '@/contexts/AuthContext'; + +export default function AdminExportPage() { + const router = useRouter(); + const { user, isAdmin, loading } = useAuth(); + const [availableFiles, setAvailableFiles] = useState([]); + const [loadingData, setLoadingData] = useState(true); + const [downloading, setDownloading] = useState(null); + + useEffect(() => { + if (!loading) { + if (!user) { + router.push('/login'); + } else if (!isAdmin) { + router.push('/dashboard'); + } else { + fetchAvailableFiles(); + } + } + }, [user, isAdmin, loading]); + + const fetchAvailableFiles = async () => { + setLoadingData(true); + try { + const files = await exportAPI.getAvailableFiles(); + setAvailableFiles(files); + } catch (error) { + console.error('Failed to fetch available files:', error); + } finally { + setLoadingData(false); + } + }; + + const handleDownload = async (filename: string) => { + setDownloading(filename); + try { + const data = await exportAPI.exportFile(filename); + + // The backend returns an object with { file_key, filename, data, exported_at, record_count } + // We want to download just the actual data + const jsonData = data.data || data; + + // Create a blob and download + const blob = new Blob([JSON.stringify(jsonData, null, 2)], { type: 'application/json' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + } catch (error: any) { + console.error('Failed to download file:', error); + alert(error.response?.data?.detail || 'Failed to download file'); + } finally { + setDownloading(null); + } + }; + + const getFileDescription = (filename: string) => { + const descriptions: { [key: string]: string } = { + 'users.json': 'User accounts and profiles', + 'products.json': 'Product catalog with details', + 'cart.json': 'Shopping cart data', + 'transactions.json': 'Purchase history and orders', + 'reviews.json': 'Product reviews and ratings', + 'penalties.json': 'User penalties and violations', + 'refunds.json': 'Refund requests and status', + 'wishlist.json': 'Customer wishlist data', + }; + return descriptions[filename] || 'System data file'; + }; + + const getFileIcon = (filename: string) => { + // Icons removed for professional appearance + return null; + }; + + if (loading || loadingData) { + return ( +
+

Loading export options...

+
+ ); + } + + if (!isAdmin) { + return null; + } + + return ( +
+
+
+

Export Data

+

Download system data in JSON format

+
+ + {availableFiles.length === 0 ? ( +
+

No files available for export

+
+ ) : ( +
+ {availableFiles.map((filename) => ( +
+
+
+

{filename}

+

{getFileDescription(filename)}

+ +
+
+
+ ))} +
+ )} + +
+

ℹ️ Export Information

+
    +
  • • Files are exported in JSON format for easy processing
  • +
  • • Data reflects the current state of the system
  • +
  • • Use exported data for backups, analysis, or migration
  • +
  • • Ensure proper handling of sensitive user information
  • +
+
+
+
+ ); +} diff --git a/frontend/app/admin/metrics/page.tsx b/frontend/app/admin/metrics/page.tsx new file mode 100644 index 0000000..46c12a9 --- /dev/null +++ b/frontend/app/admin/metrics/page.tsx @@ -0,0 +1,398 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { BarChart3, TrendingUp, Users, DollarSign, Package, AlertTriangle, ShoppingBag } from 'lucide-react'; +import { metricsAPI } from '@/lib/api'; +import { useAuth } from '@/contexts/AuthContext'; + +type CategoryMetrics = { + summary: { + total_revenue: number; + total_transactions: number; + total_users_with_transactions: number; + returning_users: number; + }; + categories: { + [key: string]: { + total_revenue: number; + transaction_count: number; + most_purchased_products: Array<{ + product_id: string; + product_name: string; + purchase_count: number; + }>; + }; + }; + most_purchased_products: Array<{ + product_id: string; + product_name: string; + purchase_count: number; + }>; +}; + +type ChartData = { + top_products_by_sales: Array<{ + product_name: string; + sales: number; + }>; + category_distribution: Array<{ + category: string; + revenue: number; + percentage: number; + }>; + new_vs_returning_users: Array<{ + user_type: string; + count: number; + percentage: number; + }>; +}; + +type Anomalies = { + penalty_spike: { + message: string; + recent_count: number; + historical_average_per_day: number; + threshold: number; + } | null; + review_anomalies: Array<{ + product_id: string; + product_name: string; + review_count: number; + average_reviews: number; + threshold: number; + message: string; + }>; +}; + +export default function AdminMetricsPage() { + const router = useRouter(); + const { user, isAdmin, loading } = useAuth(); + const [categoryMetrics, setCategoryMetrics] = useState(null); + const [chartData, setChartData] = useState(null); + const [anomalies, setAnomalies] = useState(null); + const [loadingData, setLoadingData] = useState(true); + + useEffect(() => { + if (!loading) { + if (!user) { + router.push('/login'); + } else if (!isAdmin) { + router.push('/dashboard'); + } else { + fetchMetrics(); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [user, isAdmin, loading]); + + const fetchMetrics = async () => { + setLoadingData(true); + try { + const [category, charts, anomalyData] = await Promise.all([ + metricsAPI.getProductsByCategory(), + metricsAPI.getProductCharts(), + metricsAPI.getAnomalies(), + ]); + + setCategoryMetrics(category); + setChartData(charts); + setAnomalies(anomalyData); + } catch (error) { + console.error('Failed to fetch metrics:', error); + } finally { + setLoadingData(false); + } + }; + + if (loading || loadingData) { + return ( +
+

Loading metrics...

+
+ ); + } + + if (!isAdmin) { + return null; + } + + return ( +
+
+
+

Business Metrics

+

Analytics and insights for your e-commerce platform

+
+ + {/* Anomaly Alerts */} + {anomalies && (anomalies.penalty_spike || anomalies.review_anomalies.length > 0) && ( +
+ {anomalies.penalty_spike && ( +
+
+ +
+

⚠️ Penalty Spike Detected

+

{anomalies.penalty_spike.message}

+
+ Recent: {anomalies.penalty_spike.recent_count} | + Historical Avg: {anomalies.penalty_spike.historical_average_per_day}/day | + Threshold: {anomalies.penalty_spike.threshold} +
+
+
+
+ )} + + {anomalies.review_anomalies.length > 0 && ( +
+
+ +
+

Review Anomalies Detected

+
+ {anomalies.review_anomalies.map((anomaly) => ( +
+ {anomaly.product_name}: {anomaly.message} +
+ ))} +
+
+
+
+ )} +
+ )} + + {/* Summary Cards */} + {categoryMetrics && ( +
+
+
+
+ +
+ + ₹{categoryMetrics.summary.total_revenue.toLocaleString()} + +
+

Total Revenue

+
+ +
+
+
+ +
+ + {categoryMetrics.summary.total_transactions} + +
+

Total Transactions

+
+ +
+
+
+ +
+ + {categoryMetrics.summary.total_users_with_transactions} + +
+

Active Customers

+
+ +
+
+
+ +
+ + {categoryMetrics.summary.returning_users} + +
+

Returning Customers

+
+
+ )} + + {/* Charts Section */} + {chartData && ( +
+ {/* Top Products */} +
+

Top 10 Products by Sales

+
+ {chartData.top_products_by_sales.map((product, index) => ( +
+
+ {index + 1} +
+
+

{product.product_name}

+

{product.sales} units sold

+
+
+
+
+
+
+
+ ))} +
+
+ + {/* Category Distribution */} +
+

Revenue by Category

+
+ {chartData.category_distribution.map((category, index) => ( +
+
+ {category.category} + ₹{category.revenue.toLocaleString()} ({category.percentage}%) +
+
+
+
+
+ ))} +
+
+
+ )} + + {/* New vs Returning Users */} + {chartData && chartData.new_vs_returning_users.length > 0 && ( +
+

Customer Retention

+
+ {chartData.new_vs_returning_users.map((userType, index) => ( +
+
+
{userType.percentage}%
+
+

{userType.user_type}

+

{userType.count} users

+
+ ))} +
+
+ )} + + {/* Category Breakdown */} + {categoryMetrics && Object.keys(categoryMetrics.categories).length > 0 && ( +
+

Category Performance

+
+ + + + + + + + + + + {Object.entries(categoryMetrics.categories).map(([category, data]) => ( + + + + + + + ))} + +
+ Category + + Revenue + + Transactions + + Top Products +
+ {category} + + ₹{data.total_revenue.toLocaleString()} + + {data.transaction_count} + +
+ {data.most_purchased_products.slice(0, 3).map((product) => ( +
+ {product.product_name} + ×{product.purchase_count} +
+ ))} +
+
+
+
+ )} + + {/* Overall Top Products */} + {categoryMetrics && categoryMetrics.most_purchased_products.length > 0 && ( +
+

Most Purchased Products (All Categories)

+
+ + + + + + + + + + {categoryMetrics.most_purchased_products.map((product, index) => ( + + + + + + ))} + +
+ Rank + + Product + + Purchase Count +
+ #{index + 1} + + {product.product_name} + + {product.purchase_count} units +
+
+
+ )} + + {/* Note about unimplemented feature */} +
+
+ +
+

+ Note: User engagement metrics (average transactions per user, + average spending, top customers by spending) are not yet available. The backend endpoint + GET /admin/metrics/users needs to be implemented. +

+
+
+
+
+
+ ); +} diff --git a/frontend/app/admin/page.tsx b/frontend/app/admin/page.tsx index ea261fc..b5d2db2 100644 --- a/frontend/app/admin/page.tsx +++ b/frontend/app/admin/page.tsx @@ -1,402 +1,152 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { useAuth } from '@/contexts/AuthContext'; -import { adminAPI, productsAPI, refundsAPI } from '@/lib/api'; -import type { Product, Refund } from '@/types'; -import Navbar from '@/components/Navbar'; +import { useEffect } from 'react'; import { useRouter } from 'next/navigation'; -import { Package, DollarSign, Users, TrendingUp, Plus, Edit, Trash2, Check, X } from 'lucide-react'; import Link from 'next/link'; -import MetricsCharts from '@/components/MetricsCharts'; -import AnomaliesAlert from '@/components/AnomaliesAlert'; -import ReviewManagement from '@/components/ReviewManagement'; -import CategoryPieChart from '@/components/CategoryPieChart'; +import { FileText, TrendingUp, Download, AlertTriangle, UserPlus, Package, BarChart3, AlertCircle } from 'lucide-react'; +import { useAuth } from '@/contexts/AuthContext'; -export default function AdminDashboard() { - const { user, isAdmin, loading: authLoading } = useAuth(); +export default function AdminDashboardPage() { const router = useRouter(); - const [metrics, setMetrics] = useState(null); - const [chartData, setChartData] = useState(null); - const [anomalies, setAnomalies] = useState(null); - const [products, setProducts] = useState([]); - const [refunds, setRefunds] = useState([]); - const [loading, setLoading] = useState(true); - const [activeTab, setActiveTab] = useState<'overview' | 'products' | 'refunds' | 'reviews'>('overview'); + const { user, isAdmin, loading } = useAuth(); useEffect(() => { - if (authLoading) { - return; // Wait for auth to finish loading - } - if (!user) { - router.push('/login'); - return; - } - if (!isAdmin) { - router.push('/dashboard'); - return; - } - loadData(); - }, [user, isAdmin, authLoading]); - - const loadData = async () => { - if (!user) return; - try { - const [metricsData, chartDataResult, anomaliesData, productsData, refundsData] = await Promise.all([ - adminAPI.getMetrics(user.user_token).catch(() => null), - adminAPI.getChartData(user.user_token).catch(() => null), - adminAPI.getAnomalies(user.user_token).catch(() => null), - productsAPI.getAll().catch(() => []), - refundsAPI.getAll(user.user_token).catch(() => []), - ]); - setMetrics(metricsData); - setChartData(chartDataResult); - setAnomalies(anomaliesData); - setProducts(productsData); - setRefunds(refundsData); - } catch (error) { - console.error('Failed to load data:', error); - } finally { - setLoading(false); - } - }; - - - const handleDeleteProduct = async (productId: string) => { - if (!user || !confirm('Are you sure you want to delete this product?')) return; - try { - await productsAPI.delete(productId, user.user_token); - loadData(); - } catch (error) { - alert('Failed to delete product'); + if (!loading) { + if (!user) { + router.push('/login'); + } else if (!isAdmin) { + router.push('/dashboard'); + } } - }; + }, [user, isAdmin, loading, router]); - const handleApproveRefund = async (refundId: string) => { - if (!user) return; - try { - await refundsAPI.approve(refundId, user.user_token); - loadData(); - } catch (error) { - alert('Failed to approve refund'); - } - }; - - const handleDenyRefund = async (refundId: string) => { - if (!user) return; - try { - await refundsAPI.deny(refundId, user.user_token); - loadData(); - } catch (error) { - alert('Failed to deny refund'); - } - }; - - if (authLoading || loading) { + if (loading) { return ( -
- -
Loading...
+
+

Loading...

); } + if (!isAdmin) { + return null; + } + return (
-
-

Admin Dashboard

- -
- - - - +
+

Admin Dashboard

+

Manage your e-commerce platform

- {activeTab === 'overview' && ( -
- {/* Summary Cards */} - {metrics && ( -
-
-
-
-

Total Revenue

-

- ₹{metrics.summary?.total_revenue?.toLocaleString() || 0} -

-

All time revenue

-
- -
+
+ {/* My Orders */} + +
+
+
+
-
-
-
-

Total Products

-

{products.length}

-

Products in catalog

-
- -
+

My Orders

+
+

+ View your personal purchase history and order status as an admin user. +

+
+ + + {/* Manage Refunds */} + +
+
+
+
-
-
-
-

Active Users

-

- {metrics.summary?.total_users_with_transactions || metrics.summary?.total_users || 0} -

-

- {metrics.summary?.returning_users || 0} returning -

-
- -
+

Manage Refunds

+
+

+ Review and process customer refund requests. Approve or deny refunds based on policies. +

+
+ + + {/* Manage Products */} + +
+
+
+
-
-
-
-

Transactions

-

- {metrics.summary?.total_transactions || 0} -

-

Total orders

-
- -
+

Manage Products

+
+

+ Add new products, update existing listings, or remove products from the catalog. +

+
+ + + {/* View Metrics */} + +
+
+
+
+

View Metrics

- )} - - {/* Anomalies Alert */} - - - {/* Category Pie Chart */} - {chartData?.category_distribution && ( - - )} - - {/* Other Charts */} - - - {/* Category Breakdown */} - {metrics?.categories && Object.keys(metrics.categories).length > 0 && ( -
-

Category Revenue Breakdown

-
- {Object.entries(metrics.categories).map(([category, data]: [string, any]) => ( -
-
-

{category}

-
-

- ₹{(data.total_revenue || data.revenue || 0).toLocaleString()} -

-
-
-
- {data.transaction_count || 0} transactions - {data.most_purchased_products && data.most_purchased_products.length > 0 && ( - <> - - - Top: {data.most_purchased_products[0]?.product_name || 'N/A'} - - - )} -
-
- ))} +

+ Analyze sales data, product performance, and customer behavior with detailed charts and reports. +

+
+ + + {/* Export Data */} + +
+
+
+
+

Export Data

- )} -
- )} - - {activeTab === 'products' && ( -
-
-

Products

- - - Add Product - +

+ Download system data including users, products, transactions, and more in JSON format. +

-
-
- - - - - - - - - - - {products.map((product) => ( - - - - - - - ))} - -
- Product - - Category - - Price - - Actions -
-
{product.product_name}
-
-
{product.category}
-
-
₹{((product.discounted_price || 0)).toLocaleString()}
-
- - - - -
+ + + {/* Apply Penalties */} + +
+
+
+ +
+

Apply Penalties

+

+ Manage user penalties for policy violations. Apply fines or sanctions as needed. +

-
- )} - - {activeTab === 'refunds' && ( -
-

Refund Requests

-
-
- - - - - - - - - - - {refunds.map((refund) => ( - - - - - - - ))} - -
- Transaction ID - - Reason - - Status - - Actions -
-
- {refund.transaction_id.slice(0, 8)}... -
-
-
{refund.message}
-
- - {refund.status} - - - {refund.status === 'pending' && ( - <> - - - - )} -
+ + + {/* Promote Users */} + +
+
+
+ +
+

Promote Users

+

+ Upgrade customer accounts to admin status. Grant administrative privileges to trusted users. +

-
- )} - - {activeTab === 'reviews' && user && ( -
- -
- )} + +
); } - diff --git a/frontend/app/admin/penalties/page.tsx b/frontend/app/admin/penalties/page.tsx new file mode 100644 index 0000000..bec6c38 --- /dev/null +++ b/frontend/app/admin/penalties/page.tsx @@ -0,0 +1,222 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { authAPI, penaltyAPI } from '@/lib/api'; +import { User, Penalty } from '@/types'; +import { useAuth } from '@/contexts/AuthContext'; + +export default function AdminPenaltiesPage() { + const router = useRouter(); + const { user, isAdmin, loading } = useAuth(); + const [users, setUsers] = useState([]); + const [userPenalties, setUserPenalties] = useState<{ [key: string]: Penalty[] }>({}); + const [loadingData, setLoadingData] = useState(true); + + // Apply penalty modal state + const [showPenaltyModal, setShowPenaltyModal] = useState(false); + const [selectedUser, setSelectedUser] = useState(null); + const [penaltyReason, setPenaltyReason] = useState(''); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (!loading) { + if (!user) { + router.push('/login'); + } else if (!isAdmin) { + router.push('/dashboard'); + } else { + fetchUsers(); + } + } + }, [user, isAdmin, loading]); + + const fetchUsers = async () => { + setLoadingData(true); + try { + const usersData = await authAPI.getAllUsers(); + setUsers(usersData); + + // Fetch penalties for each user + const penaltyPromises = usersData.map((u: User) => + penaltyAPI.getUserPenalties(u.user_id).catch(() => []) + ); + const penaltiesData = await Promise.all(penaltyPromises); + + // Create a map of user_id -> penalties + const penaltyMap: { [key: string]: Penalty[] } = {}; + usersData.forEach((u: User, index: number) => { + penaltyMap[u.user_id] = penaltiesData[index]; + }); + setUserPenalties(penaltyMap); + } catch (error) { + console.error('Failed to fetch users:', error); + } finally { + setLoadingData(false); + } + }; + + const handleOpenPenaltyModal = (user: User) => { + setSelectedUser(user); + setShowPenaltyModal(true); + }; + + const handleClosePenaltyModal = () => { + setShowPenaltyModal(false); + setSelectedUser(null); + setPenaltyReason(''); + }; + + const handleSubmitPenalty = async (e: React.FormEvent) => { + e.preventDefault(); + if (!selectedUser) return; + setSubmitting(true); + try { + await penaltyAPI.applyPenalty(selectedUser.user_id, penaltyReason); + alert('Penalty applied successfully!'); + handleClosePenaltyModal(); + fetchUsers(); // Refresh + } catch (error: any) { + console.error('Failed to apply penalty:', error); + alert(error.response?.data?.detail || 'Failed to apply penalty'); + } finally { + setSubmitting(false); + } + }; + + const handleResolvePenalty = async (penaltyId: string) => { + try { + await penaltyAPI.resolvePenalty(penaltyId); + alert('Penalty resolved!'); + fetchUsers(); // Refresh + } catch (error: any) { + console.error('Failed to resolve penalty:', error); + alert(error.response?.data?.detail || 'Failed to resolve penalty'); + } + }; + + if (loading || loadingData) { + return ( +
+

Loading users...

+
+ ); + } + + if (!isAdmin) { + return null; + } + + return ( +
+
+
+

Manage Penalties

+

Apply and resolve user penalties

+
+ +
+ {users.map((u) => { + const penalties = userPenalties[u.user_id] || []; + const activePenalties = penalties.filter((p) => p.status === 'active'); + + return ( +
+
+
+
+

{u.name}

+ + {u.role} + +
+

{u.email}

+
+ +
+ + {/* Active Penalties */} + {activePenalties.length > 0 && ( +
+

Active Penalties:

+
+ {activePenalties.map((penalty) => ( +
+
+

{penalty.reason}

+

+ {new Date(penalty.timestamp).toLocaleString()} +

+
+ +
+ ))} +
+
+ )} +
+ ); + })} +
+ + {/* Apply Penalty Modal */} + {showPenaltyModal && selectedUser && ( +
+
+

Apply Penalty

+
+
+

+ Applying penalty to: {selectedUser.name} ({selectedUser.email}) +

+ +