From f566387c61f43d272fa20fd2046c932c2d8b1ad9 Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Wed, 22 Jul 2026 13:48:53 +0330 Subject: [PATCH] Initial commit: Meshkee dashboards monorepo. Includes business, customer, and super-admin apps with shared packages and production deploy scripts. Co-authored-by: Cursor --- .gitignore | 11 + README.md | 67 + apps/business/.env.example | 7 + apps/business/.gitignore | 25 + apps/business/.oxlintrc.json | 8 + apps/business/README.md | 32 + apps/business/index.html | 22 + apps/business/package-lock.json | 1453 ++++++++++++++++ apps/business/package.json | 29 + apps/business/public/favicon.svg | 1 + apps/business/public/icons.svg | 24 + apps/business/public/meshkee-logo.png | Bin 0 -> 3070 bytes apps/business/public/product-template.png | Bin 0 -> 44410 bytes apps/business/src/App.tsx | 118 ++ apps/business/src/assets/hero.png | Bin 0 -> 13057 bytes apps/business/src/assets/meshkee-logo.png | Bin 0 -> 3070 bytes apps/business/src/assets/react.svg | 1 + apps/business/src/assets/vite.svg | 1 + .../components/AddCustomerModal.module.css | 22 + .../src/components/AddCustomerModal.tsx | 209 +++ .../components/AddProductByAiModal.module.css | 25 + .../src/components/AddProductByAiModal.tsx | 234 +++ .../src/components/AddVariationModal.tsx | 248 +++ .../AddWebsiteSliderSlideModal.module.css | 3 + .../components/AddWebsiteSliderSlideModal.tsx | 153 ++ .../src/components/BlogCard.module.css | 145 ++ apps/business/src/components/BlogCard.tsx | 120 ++ .../src/components/BlogCategoryRow.tsx | 81 + .../src/components/BlogCategoryTree.tsx | 64 + .../src/components/BlogCommentsModal.tsx | 262 +++ .../components/BlogCommentsSection.module.css | 157 ++ .../src/components/BlogCommentsSection.tsx | 190 ++ .../src/components/BrandModal.module.css | 184 ++ apps/business/src/components/BrandModal.tsx | 171 ++ .../src/components/BrandRow.module.css | 17 + apps/business/src/components/BrandRow.tsx | 56 + .../src/components/Breadcrumbs.module.css | 45 + apps/business/src/components/Breadcrumbs.tsx | 38 + .../components/BusinessDomainGuard.module.css | 42 + .../src/components/BusinessDomainGuard.tsx | 31 + .../CategoryAiPromptModal.module.css | 29 + .../src/components/CategoryAiPromptModal.tsx | 161 ++ .../src/components/CategoryModal.module.css | 189 ++ .../business/src/components/CategoryModal.tsx | 174 ++ .../src/components/CategoryRow.module.css | 173 ++ apps/business/src/components/CategoryRow.tsx | 129 ++ .../src/components/CategoryTree.module.css | 13 + apps/business/src/components/CategoryTree.tsx | 84 + .../components/ConfirmDeleteModal.module.css | 122 ++ .../src/components/ConfirmDeleteModal.tsx | 86 + .../ContactSubmissionDetailModal.module.css | 62 + .../ContactSubmissionDetailModal.tsx | 123 ++ .../components/CreateProductVariantModal.tsx | 188 ++ .../CreateStoreItemsModal.module.css | 170 ++ .../src/components/CreateStoreItemsModal.tsx | 399 +++++ .../src/components/DashboardDocumentTitle.tsx | 18 + .../src/components/EditCustomerModal.tsx | 190 ++ .../src/components/EditStoreItemsModal.tsx | 350 ++++ apps/business/src/components/GuestRoute.tsx | 21 + .../business/src/components/Header.module.css | 203 +++ apps/business/src/components/Header.tsx | 152 ++ .../src/components/ImageCropper.module.css | 159 ++ apps/business/src/components/ImageCropper.tsx | 132 ++ .../src/components/ImageLightbox.module.css | 166 ++ .../business/src/components/ImageLightbox.tsx | 122 ++ .../src/components/ImageUploader.module.css | 82 + .../business/src/components/ImageUploader.tsx | 75 + .../components/ListFiltersPanel.module.css | 197 +++ .../components/MultiSelectDropdown.module.css | 175 ++ .../src/components/MultiSelectDropdown.tsx | 216 +++ .../src/components/OrderItemsModal.module.css | 160 ++ .../src/components/OrderItemsModal.tsx | 152 ++ .../src/components/OrderStepModal.module.css | 74 + .../src/components/OrderStepModal.tsx | 165 ++ .../OrderTransactionsModal.module.css | 123 ++ .../src/components/OrderTransactionsModal.tsx | 192 ++ .../src/components/PageContent.module.css | 120 ++ .../src/components/PageLayout.module.css | 17 + apps/business/src/components/PageLayout.tsx | 20 + .../src/components/Pagination.module.css | 61 + apps/business/src/components/Pagination.tsx | 53 + .../PickStoreSpecialItemsModal.module.css | 62 + .../components/PickStoreSpecialItemsModal.tsx | 179 ++ .../PickStoreVariantModal.module.css | 48 + .../src/components/PickStoreVariantModal.tsx | 116 ++ .../src/components/PickWebsiteBrandsModal.tsx | 174 ++ .../components/PickWebsiteCategoriesModal.tsx | 175 ++ .../src/components/PortfolioCard.module.css | 97 ++ .../business/src/components/PortfolioCard.tsx | 93 + .../src/components/PortfolioCommentsModal.tsx | 262 +++ .../components/PortfolioCommentsSection.tsx | 193 ++ .../ProductActivityChart.module.css | 133 ++ .../src/components/ProductActivityChart.tsx | 113 ++ .../src/components/ProductCard.module.css | 164 ++ apps/business/src/components/ProductCard.tsx | 102 ++ .../ProductCommentsModal.module.css | 219 +++ .../src/components/ProductCommentsModal.tsx | 262 +++ .../components/ProductDetailsTabs.module.css | 289 +++ .../src/components/ProductDetailsTabs.tsx | 446 +++++ .../src/components/ProductSearchSelect.tsx | 145 ++ .../components/ProductTechnicalInfoModal.tsx | 275 +++ .../src/components/ProductVariantsModal.tsx | 206 +++ .../src/components/ProtectedRoute.tsx | 21 + .../src/components/RichTextEditor.module.css | 112 ++ .../src/components/RichTextEditor.tsx | 275 +++ .../src/components/RouteLoader.module.css | 21 + .../components/SearchableSelect.module.css | 142 ++ .../src/components/SearchableSelect.tsx | 119 ++ .../src/components/SectionCard.module.css | 83 + apps/business/src/components/SectionCard.tsx | 37 + .../components/ShoppingCartModal.module.css | 564 ++++++ .../src/components/ShoppingCartModal.tsx | 704 ++++++++ .../src/components/Sidebar.module.css | 159 ++ apps/business/src/components/Sidebar.tsx | 281 +++ .../src/components/StepColorPicker.module.css | 86 + .../src/components/StepColorPicker.tsx | 90 + .../src/components/StoreItemCard.module.css | 200 +++ .../business/src/components/StoreItemCard.tsx | 148 ++ .../src/components/StoreItemDiscountModal.tsx | 200 +++ .../src/components/StoreItemFestivalModal.tsx | 197 +++ .../StoreItemMultiSearchSelect.module.css | 133 ++ .../components/StoreItemMultiSearchSelect.tsx | 168 ++ .../src/components/StoreItemPrice.module.css | 43 + .../src/components/StoreItemPrice.tsx | 28 + .../StoreSpecialCarousel.module.css | 129 ++ .../src/components/StoreSpecialCarousel.tsx | 129 ++ .../src/components/StoreSpecialModal.tsx | 111 ++ .../business/src/components/Switch.module.css | 34 + apps/business/src/components/Switch.tsx | 32 + .../src/components/TagInput.module.css | 65 + apps/business/src/components/TagInput.tsx | 57 + .../components/TechnicalFormModal.module.css | 159 ++ .../src/components/TechnicalFormModal.tsx | 360 ++++ apps/business/src/components/Toast.module.css | 80 + .../src/components/ToggleSwitch.module.css | 48 + apps/business/src/components/ToggleSwitch.tsx | 31 + .../src/components/Tooltip.module.css | 45 + apps/business/src/components/Tooltip.tsx | 18 + .../src/components/VariationsModal.module.css | 425 +++++ .../src/components/VariationsModal.tsx | 158 ++ .../WebsiteGroupCarousel.module.css | 130 ++ .../src/components/WebsiteGroupCarousel.tsx | 115 ++ .../WebsiteGroupItemCard.module.css | 76 + .../src/components/WebsiteGroupItemCard.tsx | 42 + .../src/components/WebsiteSectionShell.tsx | 39 + .../WebsiteSliderGallery.module.css | 129 ++ .../src/components/WebsiteSliderGallery.tsx | 84 + .../WebsiteSliderSlideCard.module.css | 71 + .../src/components/WebsiteSliderSlideCard.tsx | 33 + apps/business/src/context/AuthContext.tsx | 149 ++ .../src/context/BusinessThemeContext.tsx | 42 + .../business/src/context/DraftCartContext.tsx | 167 ++ .../src/context/TenantBrandingContext.tsx | 119 ++ apps/business/src/context/ToastContext.tsx | 98 ++ apps/business/src/data/categories.ts | 84 + apps/business/src/data/productComments.ts | 9 + apps/business/src/data/productVariants.ts | 43 + apps/business/src/data/products.ts | 44 + apps/business/src/fonts/iranyekan.css | 40 + apps/business/src/index.css | 143 ++ apps/business/src/lib/api.ts | 122 ++ apps/business/src/lib/businessContext.ts | 38 + apps/business/src/lib/cellNumber.ts | 30 + apps/business/src/lib/config.ts | 36 + apps/business/src/lib/routeTitles.ts | 47 + apps/business/src/main.tsx | 11 + apps/business/src/pages/AddNewBlogPage.tsx | 287 +++ .../src/pages/AddNewPortfolioPage.tsx | 285 +++ .../src/pages/AddNewProductPage.module.css | 214 +++ apps/business/src/pages/AddNewProductPage.tsx | 311 ++++ .../business/src/pages/BlogCategoriesPage.tsx | 222 +++ .../src/pages/BlogDetailsPage.module.css | 169 ++ apps/business/src/pages/BlogDetailsPage.tsx | 145 ++ apps/business/src/pages/BlogListPage.tsx | 236 +++ apps/business/src/pages/BlogPage.module.css | 50 + apps/business/src/pages/BlogPage.tsx | 62 + apps/business/src/pages/BlogSettingsPage.tsx | 127 ++ apps/business/src/pages/BrandsPage.module.css | 58 + apps/business/src/pages/BrandsPage.tsx | 217 +++ .../src/pages/BusinessProfilePage.module.css | 240 +++ .../src/pages/BusinessProfilePage.tsx | 481 +++++ .../src/pages/CategoriesPage.module.css | 26 + apps/business/src/pages/CategoriesPage.tsx | 557 ++++++ .../src/pages/CustomersPage.module.css | 260 +++ apps/business/src/pages/CustomersPage.tsx | 515 ++++++ apps/business/src/pages/HomePage.tsx | 105 ++ apps/business/src/pages/LoginPage.module.css | 305 ++++ apps/business/src/pages/LoginPage.tsx | 700 ++++++++ .../src/pages/MyProductsPage.module.css | 86 + apps/business/src/pages/MyProductsPage.tsx | 340 ++++ apps/business/src/pages/OrdersPage.module.css | 311 ++++ apps/business/src/pages/OrdersPage.tsx | 618 +++++++ .../src/pages/PortfolioCategoriesPage.tsx | 222 +++ .../src/pages/PortfolioDetailsPage.module.css | 217 +++ .../src/pages/PortfolioDetailsPage.tsx | 188 ++ apps/business/src/pages/PortfolioListPage.tsx | 207 +++ .../src/pages/PortfolioSettingsPage.tsx | 127 ++ apps/business/src/pages/PortfoliosPage.tsx | 62 + .../src/pages/ProductDetailsPage.module.css | 213 +++ .../business/src/pages/ProductDetailsPage.tsx | 167 ++ .../src/pages/ProductSettingsPage.module.css | 89 + .../src/pages/ProductSettingsPage.tsx | 167 ++ apps/business/src/pages/ProductsPage.tsx | 76 + .../src/pages/ShoppingCardsPage.module.css | 56 + apps/business/src/pages/ShoppingCardsPage.tsx | 454 +++++ .../src/pages/StoreItemsPage.module.css | 140 ++ apps/business/src/pages/StoreItemsPage.tsx | 437 +++++ apps/business/src/pages/StorePage.tsx | 75 + .../src/pages/StoreSettingsPage.module.css | 131 ++ apps/business/src/pages/StoreSettingsPage.tsx | 342 ++++ .../src/pages/StoreSpecialsPage.module.css | 25 + apps/business/src/pages/StoreSpecialsPage.tsx | 461 +++++ apps/business/src/pages/WebsiteBadgesPage.tsx | 24 + .../src/pages/WebsiteContactPage.module.css | 138 ++ .../business/src/pages/WebsiteContactPage.tsx | 297 ++++ .../src/pages/WebsiteEPaymentPage.tsx | 24 + apps/business/src/pages/WebsiteFaqPage.tsx | 24 + .../business/src/pages/WebsitePage.module.css | 40 + apps/business/src/pages/WebsitePage.tsx | 107 ++ .../business/src/pages/WebsiteSlidersPage.tsx | 176 ++ .../src/pages/WebsiteSpecialBrandsPage.tsx | 314 ++++ .../pages/WebsiteSpecialCategoriesPage.tsx | 314 ++++ .../src/pages/WebsiteSubscriptionsPage.tsx | 24 + apps/business/src/services/authService.ts | 67 + .../src/services/blogCategoryService.ts | 76 + apps/business/src/services/blogService.ts | 216 +++ apps/business/src/services/brandService.ts | 113 ++ .../businessActivityCategoryService.ts | 23 + .../src/services/businessProfileService.ts | 97 ++ .../services/categoryTechnicalFormService.ts | 82 + .../src/services/categoryVariationsService.ts | 85 + apps/business/src/services/citiesService.ts | 20 + apps/business/src/services/commentService.ts | 115 ++ .../src/services/contactSubmissionService.ts | 42 + apps/business/src/services/customerService.ts | 119 ++ .../src/services/expertReviewService.ts | 98 ++ apps/business/src/services/mediaService.ts | 167 ++ apps/business/src/services/orderService.ts | 179 ++ .../src/services/portfolioCategoryService.ts | 76 + .../business/src/services/portfolioService.ts | 210 +++ .../business/src/services/productAiService.ts | 27 + .../src/services/productCategoryService.ts | 90 + apps/business/src/services/productService.ts | 207 +++ .../services/productTechnicalInfoService.ts | 70 + .../src/services/productVariantService.ts | 80 + .../src/services/productVariationService.ts | 71 + apps/business/src/services/settingsService.ts | 72 + .../src/services/shoppingCardService.ts | 110 ++ .../business/src/services/storeItemService.ts | 161 ++ .../src/services/storeSpecialService.ts | 62 + apps/business/src/services/tenantService.ts | 16 + .../src/services/websiteBrandGroupService.ts | 49 + .../services/websiteCategoryGroupService.ts | 52 + .../src/services/websiteSliderService.ts | 62 + apps/business/src/styles/ai.module.css | 125 ++ apps/business/src/types/auth.ts | 64 + apps/business/src/types/blog.ts | 38 + apps/business/src/types/brand.ts | 53 + apps/business/src/types/category.ts | 58 + apps/business/src/types/comment.ts | 8 + apps/business/src/types/contactSubmission.ts | 16 + apps/business/src/types/draftCart.ts | 99 ++ apps/business/src/types/expertReview.ts | 10 + apps/business/src/types/portfolio.ts | 27 + apps/business/src/types/product.ts | 16 + apps/business/src/types/productVariant.ts | 16 + apps/business/src/types/storeSpecial.ts | 34 + apps/business/src/types/technicalForm.ts | 43 + apps/business/src/types/variation.ts | 17 + apps/business/src/types/websiteBrandGroup.ts | 41 + .../src/types/websiteCategoryGroup.ts | 39 + apps/business/src/types/websiteSlider.ts | 49 + apps/business/src/utils/applyBusinessTheme.ts | 34 + apps/business/src/utils/businessCategories.ts | 22 + .../src/utils/businessPrimaryColors.ts | 111 ++ apps/business/src/utils/categories.ts | 38 + apps/business/src/utils/cropImage.ts | 46 + .../business/src/utils/filterStoreListings.ts | 55 + apps/business/src/utils/htmlImages.ts | 8 + apps/business/src/utils/id.ts | 7 + apps/business/src/utils/imageUpload.ts | 88 + apps/business/src/utils/irtPrice.ts | 39 + apps/business/src/utils/productActivity.ts | 63 + apps/business/src/utils/stepColors.ts | 84 + apps/business/src/utils/storeItemRows.ts | 28 + apps/business/src/utils/storeProductGroups.ts | 98 ++ .../src/utils/storeSpecialListings.ts | 46 + apps/business/tsconfig.app.json | 26 + apps/business/tsconfig.json | 7 + apps/business/tsconfig.node.json | 23 + apps/business/vite.config.ts | 19 + apps/customer/.env.example | 7 + apps/customer/.gitignore | 25 + apps/customer/index.html | 22 + apps/customer/package-lock.json | 1468 ++++++++++++++++ apps/customer/package.json | 29 + apps/customer/public/favicon.svg | 1 + .../fonts/yekanbakh/YekanBakh-Bold.woff2 | Bin 0 -> 30960 bytes .../fonts/yekanbakh/YekanBakh-Light.woff2 | Bin 0 -> 30420 bytes .../fonts/yekanbakh/YekanBakh-Regular.woff2 | Bin 0 -> 30444 bytes apps/customer/src/App.tsx | 71 + apps/customer/src/assets/meshkee-logo.png | Bin 0 -> 3070 bytes apps/customer/src/assets/order-failed.png | Bin 0 -> 264074 bytes apps/customer/src/assets/order-succeed.png | Bin 0 -> 217064 bytes .../src/components/CustomerDomainGuard.tsx | 8 + .../src/components/DashboardDocumentTitle.tsx | 18 + .../FavoriteStoreItemCard.module.css | 180 ++ .../src/components/FavoriteStoreItemCard.tsx | 119 ++ apps/customer/src/components/GuestRoute.tsx | 26 + .../customer/src/components/Header.module.css | 203 +++ apps/customer/src/components/Header.tsx | 135 ++ .../src/components/OrderItemsModal.module.css | 160 ++ .../src/components/OrderItemsModal.tsx | 152 ++ .../src/components/OrderRow.module.css | 105 ++ apps/customer/src/components/OrderRow.tsx | 100 ++ .../src/components/PageContent.module.css | 113 ++ .../src/components/PageLayout.module.css | 17 + apps/customer/src/components/PageLayout.tsx | 17 + .../src/components/ProtectedRoute.tsx | 17 + .../src/components/Sidebar.module.css | 175 ++ apps/customer/src/components/Sidebar.tsx | 108 ++ .../src/components/StoreItemPrice.module.css | 43 + .../src/components/StoreItemPrice.tsx | 28 + apps/customer/src/components/Toast.module.css | 80 + .../src/components/Tooltip.module.css | 45 + apps/customer/src/components/Tooltip.tsx | 18 + .../src/components/VariationsModal.module.css | 425 +++++ .../checkout/CheckoutActionBar.module.css | 65 + .../components/checkout/CheckoutActionBar.tsx | 72 + .../CheckoutAddAddressModal.module.css | 9 + .../checkout/CheckoutAddAddressModal.tsx | 98 ++ .../CheckoutAddAddressPanel.module.css | 163 ++ .../checkout/CheckoutAddAddressPanel.tsx | 244 +++ .../checkout/CheckoutBackButton.module.css | 19 + .../checkout/CheckoutBackButton.tsx | 16 + .../checkout/CheckoutLayout.module.css | 143 ++ .../components/checkout/CheckoutLayout.tsx | 71 + .../checkout/CheckoutStepper.module.css | 96 + .../components/checkout/CheckoutStepper.tsx | 62 + apps/customer/src/context/AuthContext.tsx | 141 ++ apps/customer/src/context/CheckoutContext.tsx | 76 + .../src/context/CustomerThemeContext.tsx | 34 + .../src/context/TenantBrandingContext.tsx | 98 ++ apps/customer/src/fonts/iranyekan.css | 40 + apps/customer/src/fonts/yekanbakh.css | 23 + apps/customer/src/index.css | 6 + apps/customer/src/lib/api.ts | 20 + apps/customer/src/lib/businessContext.ts | 22 + apps/customer/src/lib/cellNumber.ts | 1 + apps/customer/src/lib/config.ts | 33 + apps/customer/src/lib/guestCart.ts | 144 ++ apps/customer/src/lib/routeTitles.ts | 19 + apps/customer/src/main.tsx | 12 + .../src/pages/AddressesPage.module.css | 44 + apps/customer/src/pages/AddressesPage.tsx | 259 +++ .../src/pages/FavoritesPage.module.css | 74 + apps/customer/src/pages/FavoritesPage.tsx | 138 ++ apps/customer/src/pages/HomePage.tsx | 75 + apps/customer/src/pages/LoginPage.module.css | 305 ++++ apps/customer/src/pages/LoginPage.tsx | 720 ++++++++ apps/customer/src/pages/OrdersPage.module.css | 307 ++++ apps/customer/src/pages/OrdersPage.tsx | 201 +++ .../customer/src/pages/ProfilePage.module.css | 120 ++ apps/customer/src/pages/ProfilePage.tsx | 213 +++ .../src/pages/checkout/CheckoutCartStep.tsx | 344 ++++ .../pages/checkout/CheckoutDeliveryStep.tsx | 217 +++ .../src/pages/checkout/CheckoutFailedStep.tsx | 41 + .../src/pages/checkout/CheckoutFlow.tsx | 62 + .../src/pages/checkout/CheckoutLoginStep.tsx | 124 ++ .../pages/checkout/CheckoutPaymentStep.tsx | 330 ++++ .../pages/checkout/CheckoutSteps.module.css | 807 +++++++++ .../pages/checkout/CheckoutSuccessStep.tsx | 67 + apps/customer/src/services/addressService.ts | 49 + apps/customer/src/services/authService.ts | 83 + apps/customer/src/services/cartService.ts | 106 ++ apps/customer/src/services/citiesService.ts | 20 + .../customer/src/services/favoritesService.ts | 53 + apps/customer/src/services/orderService.ts | 105 ++ apps/customer/src/services/syncGuestCart.ts | 47 + apps/customer/src/services/tenantService.ts | 18 + apps/customer/src/services/websiteService.ts | 22 + apps/customer/src/types/auth.ts | 10 + apps/customer/src/utils/applyBusinessTheme.ts | 34 + .../src/utils/businessPrimaryColors.ts | 111 ++ apps/customer/src/utils/irtPrice.ts | 7 + apps/customer/src/utils/orderSteps.ts | 43 + apps/customer/src/utils/stepColors.ts | 53 + apps/customer/src/utils/storeProductGroups.ts | 3 + apps/customer/tsconfig.app.json | 26 + apps/customer/tsconfig.json | 7 + apps/customer/tsconfig.node.json | 23 + apps/customer/vite.config.ts | 18 + apps/super-admin/.env.example | 4 + apps/super-admin/.gitignore | 5 + apps/super-admin/index.html | 23 + apps/super-admin/package-lock.json | 1433 +++++++++++++++ apps/super-admin/package.json | 28 + apps/super-admin/public/favicon.svg | 1 + apps/super-admin/src/App.tsx | 43 + apps/super-admin/src/assets/meshkee-logo.png | Bin 0 -> 3070 bytes .../components/AdminDomainGuard.module.css | 42 + .../src/components/AdminDomainGuard.tsx | 31 + .../components/ConfirmDeleteModal.module.css | 122 ++ .../src/components/ConfirmDeleteModal.tsx | 86 + .../super-admin/src/components/GuestRoute.tsx | 21 + .../src/components/Header.module.css | 198 +++ apps/super-admin/src/components/Header.tsx | 133 ++ .../src/components/Modal.module.css | 64 + apps/super-admin/src/components/Modal.tsx | 39 + .../components/MultiSelectDropdown.module.css | 153 ++ .../src/components/MultiSelectDropdown.tsx | 149 ++ .../src/components/PageContent.module.css | 81 + .../src/components/PageLayout.module.css | 113 ++ .../super-admin/src/components/PageLayout.tsx | 24 + .../components/PrimaryColorPicker.module.css | 58 + .../src/components/PrimaryColorPicker.tsx | 45 + .../PrimaryColorSwatchControl.module.css | 84 + .../components/PrimaryColorSwatchControl.tsx | 154 ++ .../src/components/ProtectedRoute.tsx | 21 + .../src/components/RouteLoader.module.css | 21 + .../src/components/SectionCard.module.css | 73 + .../src/components/SectionCard.tsx | 37 + .../src/components/Sidebar.module.css | 101 ++ apps/super-admin/src/components/Sidebar.tsx | 69 + .../src/components/Toast.module.css | 80 + .../src/components/ToggleSwitch.module.css | 34 + .../src/components/ToggleSwitch.tsx | 24 + .../src/components/UserSearchInput.module.css | 51 + .../src/components/UserSearchInput.tsx | 120 ++ apps/super-admin/src/context/AuthContext.tsx | 96 + apps/super-admin/src/context/ToastContext.tsx | 98 ++ apps/super-admin/src/fonts/iranyekan.css | 40 + apps/super-admin/src/index.css | 158 ++ apps/super-admin/src/lib/api.ts | 122 ++ apps/super-admin/src/lib/cellNumber.ts | 30 + apps/super-admin/src/lib/config.ts | 10 + apps/super-admin/src/main.tsx | 11 + .../src/pages/BusinessesPage.module.css | 520 ++++++ apps/super-admin/src/pages/BusinessesPage.tsx | 977 +++++++++++ apps/super-admin/src/pages/HomePage.tsx | 68 + .../src/pages/LoginPage.module.css | 278 +++ apps/super-admin/src/pages/LoginPage.tsx | 121 ++ .../src/pages/ProfilePage.module.css | 145 ++ apps/super-admin/src/pages/ProfilePage.tsx | 231 +++ .../src/pages/UsersPage.module.css | 86 + apps/super-admin/src/pages/UsersPage.tsx | 1103 ++++++++++++ .../src/pages/WebsitesPage.module.css | 18 + apps/super-admin/src/pages/WebsitesPage.tsx | 527 ++++++ apps/super-admin/src/services/authService.ts | 20 + .../src/services/businessService.ts | 93 + .../src/services/businessSettingsService.ts | 43 + .../src/services/categoryService.ts | 6 + .../super-admin/src/services/domainService.ts | 55 + .../src/services/profileService.ts | 35 + apps/super-admin/src/services/teamService.ts | 35 + apps/super-admin/src/services/userService.ts | 102 ++ apps/super-admin/src/types/auth.ts | 51 + apps/super-admin/src/types/business.ts | 44 + apps/super-admin/src/types/category.ts | 13 + apps/super-admin/src/types/domain.ts | 17 + apps/super-admin/src/types/profile.ts | 36 + apps/super-admin/src/types/user.ts | 43 + .../src/utils/businessPrimaryColors.ts | 95 + apps/super-admin/src/utils/categories.ts | 22 + apps/super-admin/src/vite-env.d.ts | 10 + apps/super-admin/tsconfig.app.json | 22 + apps/super-admin/tsconfig.json | 7 + apps/super-admin/tsconfig.node.json | 19 + apps/super-admin/vite.config.ts | 50 + deploy/nginx-dashboards-ssl.conf | 61 + deploy/nginx-dashboards.conf | 44 + deploy/ssl-sync.sh | 99 ++ docs/DEPLOY.md | 54 + docs/PROJECT_CONTEXT.md | 453 +++++ package-lock.json | 1546 +++++++++++++++++ package.json | 19 + packages/README.md | 70 + packages/dashboard-core/package.json | 17 + .../dashboard-core/src/api/createApiClient.ts | 143 ++ .../dashboard-core/src/domain/subdomain.ts | 57 + packages/dashboard-core/src/index.ts | 43 + packages/dashboard-core/src/styles/tokens.css | 159 ++ packages/dashboard-core/src/types/auth.ts | 65 + .../dashboard-core/src/utils/cellNumber.ts | 30 + .../dashboard-core/src/utils/documentTitle.ts | 54 + packages/dashboard-core/src/utils/favicon.ts | 35 + packages/dashboard-core/src/utils/irtPrice.ts | 36 + packages/dashboard-core/tsconfig.json | 14 + packages/dashboard-ui/package.json | 27 + .../components/AddressListEditor.module.css | 144 ++ .../src/components/AddressListEditor.tsx | 192 ++ .../src/components/Breadcrumbs.module.css | 45 + .../src/components/Breadcrumbs.tsx | 38 + .../src/components/DomainGuard.module.css | 42 + .../src/components/Pagination.module.css | 61 + .../src/components/Pagination.tsx | 59 + .../components/PasswordResetModal.module.css | 262 +++ .../src/components/PasswordResetModal.tsx | 283 +++ .../src/components/RouteLoader.module.css | 21 + .../src/components/RouteLoader.tsx | 9 + .../src/components/SectionCard.module.css | 77 + .../src/components/SectionCard.tsx | 37 + .../src/components/createDomainGuard.tsx | 41 + .../dashboard-ui/src/context/Toast.module.css | 80 + .../dashboard-ui/src/context/ToastContext.tsx | 98 ++ packages/dashboard-ui/src/css.d.ts | 4 + .../src/hooks/useDashboardDocumentTitle.ts | 31 + packages/dashboard-ui/src/index.ts | 22 + packages/dashboard-ui/tsconfig.json | 15 + 509 files changed, 62690 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 apps/business/.env.example create mode 100644 apps/business/.gitignore create mode 100644 apps/business/.oxlintrc.json create mode 100644 apps/business/README.md create mode 100644 apps/business/index.html create mode 100644 apps/business/package-lock.json create mode 100644 apps/business/package.json create mode 100644 apps/business/public/favicon.svg create mode 100644 apps/business/public/icons.svg create mode 100644 apps/business/public/meshkee-logo.png create mode 100644 apps/business/public/product-template.png create mode 100644 apps/business/src/App.tsx create mode 100644 apps/business/src/assets/hero.png create mode 100644 apps/business/src/assets/meshkee-logo.png create mode 100644 apps/business/src/assets/react.svg create mode 100644 apps/business/src/assets/vite.svg create mode 100644 apps/business/src/components/AddCustomerModal.module.css create mode 100644 apps/business/src/components/AddCustomerModal.tsx create mode 100644 apps/business/src/components/AddProductByAiModal.module.css create mode 100644 apps/business/src/components/AddProductByAiModal.tsx create mode 100644 apps/business/src/components/AddVariationModal.tsx create mode 100644 apps/business/src/components/AddWebsiteSliderSlideModal.module.css create mode 100644 apps/business/src/components/AddWebsiteSliderSlideModal.tsx create mode 100644 apps/business/src/components/BlogCard.module.css create mode 100644 apps/business/src/components/BlogCard.tsx create mode 100644 apps/business/src/components/BlogCategoryRow.tsx create mode 100644 apps/business/src/components/BlogCategoryTree.tsx create mode 100644 apps/business/src/components/BlogCommentsModal.tsx create mode 100644 apps/business/src/components/BlogCommentsSection.module.css create mode 100644 apps/business/src/components/BlogCommentsSection.tsx create mode 100644 apps/business/src/components/BrandModal.module.css create mode 100644 apps/business/src/components/BrandModal.tsx create mode 100644 apps/business/src/components/BrandRow.module.css create mode 100644 apps/business/src/components/BrandRow.tsx create mode 100644 apps/business/src/components/Breadcrumbs.module.css create mode 100644 apps/business/src/components/Breadcrumbs.tsx create mode 100644 apps/business/src/components/BusinessDomainGuard.module.css create mode 100644 apps/business/src/components/BusinessDomainGuard.tsx create mode 100644 apps/business/src/components/CategoryAiPromptModal.module.css create mode 100644 apps/business/src/components/CategoryAiPromptModal.tsx create mode 100644 apps/business/src/components/CategoryModal.module.css create mode 100644 apps/business/src/components/CategoryModal.tsx create mode 100644 apps/business/src/components/CategoryRow.module.css create mode 100644 apps/business/src/components/CategoryRow.tsx create mode 100644 apps/business/src/components/CategoryTree.module.css create mode 100644 apps/business/src/components/CategoryTree.tsx create mode 100644 apps/business/src/components/ConfirmDeleteModal.module.css create mode 100644 apps/business/src/components/ConfirmDeleteModal.tsx create mode 100644 apps/business/src/components/ContactSubmissionDetailModal.module.css create mode 100644 apps/business/src/components/ContactSubmissionDetailModal.tsx create mode 100644 apps/business/src/components/CreateProductVariantModal.tsx create mode 100644 apps/business/src/components/CreateStoreItemsModal.module.css create mode 100644 apps/business/src/components/CreateStoreItemsModal.tsx create mode 100644 apps/business/src/components/DashboardDocumentTitle.tsx create mode 100644 apps/business/src/components/EditCustomerModal.tsx create mode 100644 apps/business/src/components/EditStoreItemsModal.tsx create mode 100644 apps/business/src/components/GuestRoute.tsx create mode 100644 apps/business/src/components/Header.module.css create mode 100644 apps/business/src/components/Header.tsx create mode 100644 apps/business/src/components/ImageCropper.module.css create mode 100644 apps/business/src/components/ImageCropper.tsx create mode 100644 apps/business/src/components/ImageLightbox.module.css create mode 100644 apps/business/src/components/ImageLightbox.tsx create mode 100644 apps/business/src/components/ImageUploader.module.css create mode 100644 apps/business/src/components/ImageUploader.tsx create mode 100644 apps/business/src/components/ListFiltersPanel.module.css create mode 100644 apps/business/src/components/MultiSelectDropdown.module.css create mode 100644 apps/business/src/components/MultiSelectDropdown.tsx create mode 100644 apps/business/src/components/OrderItemsModal.module.css create mode 100644 apps/business/src/components/OrderItemsModal.tsx create mode 100644 apps/business/src/components/OrderStepModal.module.css create mode 100644 apps/business/src/components/OrderStepModal.tsx create mode 100644 apps/business/src/components/OrderTransactionsModal.module.css create mode 100644 apps/business/src/components/OrderTransactionsModal.tsx create mode 100644 apps/business/src/components/PageContent.module.css create mode 100644 apps/business/src/components/PageLayout.module.css create mode 100644 apps/business/src/components/PageLayout.tsx create mode 100644 apps/business/src/components/Pagination.module.css create mode 100644 apps/business/src/components/Pagination.tsx create mode 100644 apps/business/src/components/PickStoreSpecialItemsModal.module.css create mode 100644 apps/business/src/components/PickStoreSpecialItemsModal.tsx create mode 100644 apps/business/src/components/PickStoreVariantModal.module.css create mode 100644 apps/business/src/components/PickStoreVariantModal.tsx create mode 100644 apps/business/src/components/PickWebsiteBrandsModal.tsx create mode 100644 apps/business/src/components/PickWebsiteCategoriesModal.tsx create mode 100644 apps/business/src/components/PortfolioCard.module.css create mode 100644 apps/business/src/components/PortfolioCard.tsx create mode 100644 apps/business/src/components/PortfolioCommentsModal.tsx create mode 100644 apps/business/src/components/PortfolioCommentsSection.tsx create mode 100644 apps/business/src/components/ProductActivityChart.module.css create mode 100644 apps/business/src/components/ProductActivityChart.tsx create mode 100644 apps/business/src/components/ProductCard.module.css create mode 100644 apps/business/src/components/ProductCard.tsx create mode 100644 apps/business/src/components/ProductCommentsModal.module.css create mode 100644 apps/business/src/components/ProductCommentsModal.tsx create mode 100644 apps/business/src/components/ProductDetailsTabs.module.css create mode 100644 apps/business/src/components/ProductDetailsTabs.tsx create mode 100644 apps/business/src/components/ProductSearchSelect.tsx create mode 100644 apps/business/src/components/ProductTechnicalInfoModal.tsx create mode 100644 apps/business/src/components/ProductVariantsModal.tsx create mode 100644 apps/business/src/components/ProtectedRoute.tsx create mode 100644 apps/business/src/components/RichTextEditor.module.css create mode 100644 apps/business/src/components/RichTextEditor.tsx create mode 100644 apps/business/src/components/RouteLoader.module.css create mode 100644 apps/business/src/components/SearchableSelect.module.css create mode 100644 apps/business/src/components/SearchableSelect.tsx create mode 100644 apps/business/src/components/SectionCard.module.css create mode 100644 apps/business/src/components/SectionCard.tsx create mode 100644 apps/business/src/components/ShoppingCartModal.module.css create mode 100644 apps/business/src/components/ShoppingCartModal.tsx create mode 100644 apps/business/src/components/Sidebar.module.css create mode 100644 apps/business/src/components/Sidebar.tsx create mode 100644 apps/business/src/components/StepColorPicker.module.css create mode 100644 apps/business/src/components/StepColorPicker.tsx create mode 100644 apps/business/src/components/StoreItemCard.module.css create mode 100644 apps/business/src/components/StoreItemCard.tsx create mode 100644 apps/business/src/components/StoreItemDiscountModal.tsx create mode 100644 apps/business/src/components/StoreItemFestivalModal.tsx create mode 100644 apps/business/src/components/StoreItemMultiSearchSelect.module.css create mode 100644 apps/business/src/components/StoreItemMultiSearchSelect.tsx create mode 100644 apps/business/src/components/StoreItemPrice.module.css create mode 100644 apps/business/src/components/StoreItemPrice.tsx create mode 100644 apps/business/src/components/StoreSpecialCarousel.module.css create mode 100644 apps/business/src/components/StoreSpecialCarousel.tsx create mode 100644 apps/business/src/components/StoreSpecialModal.tsx create mode 100644 apps/business/src/components/Switch.module.css create mode 100644 apps/business/src/components/Switch.tsx create mode 100644 apps/business/src/components/TagInput.module.css create mode 100644 apps/business/src/components/TagInput.tsx create mode 100644 apps/business/src/components/TechnicalFormModal.module.css create mode 100644 apps/business/src/components/TechnicalFormModal.tsx create mode 100644 apps/business/src/components/Toast.module.css create mode 100644 apps/business/src/components/ToggleSwitch.module.css create mode 100644 apps/business/src/components/ToggleSwitch.tsx create mode 100644 apps/business/src/components/Tooltip.module.css create mode 100644 apps/business/src/components/Tooltip.tsx create mode 100644 apps/business/src/components/VariationsModal.module.css create mode 100644 apps/business/src/components/VariationsModal.tsx create mode 100644 apps/business/src/components/WebsiteGroupCarousel.module.css create mode 100644 apps/business/src/components/WebsiteGroupCarousel.tsx create mode 100644 apps/business/src/components/WebsiteGroupItemCard.module.css create mode 100644 apps/business/src/components/WebsiteGroupItemCard.tsx create mode 100644 apps/business/src/components/WebsiteSectionShell.tsx create mode 100644 apps/business/src/components/WebsiteSliderGallery.module.css create mode 100644 apps/business/src/components/WebsiteSliderGallery.tsx create mode 100644 apps/business/src/components/WebsiteSliderSlideCard.module.css create mode 100644 apps/business/src/components/WebsiteSliderSlideCard.tsx create mode 100644 apps/business/src/context/AuthContext.tsx create mode 100644 apps/business/src/context/BusinessThemeContext.tsx create mode 100644 apps/business/src/context/DraftCartContext.tsx create mode 100644 apps/business/src/context/TenantBrandingContext.tsx create mode 100644 apps/business/src/context/ToastContext.tsx create mode 100644 apps/business/src/data/categories.ts create mode 100644 apps/business/src/data/productComments.ts create mode 100644 apps/business/src/data/productVariants.ts create mode 100644 apps/business/src/data/products.ts create mode 100644 apps/business/src/fonts/iranyekan.css create mode 100644 apps/business/src/index.css create mode 100644 apps/business/src/lib/api.ts create mode 100644 apps/business/src/lib/businessContext.ts create mode 100644 apps/business/src/lib/cellNumber.ts create mode 100644 apps/business/src/lib/config.ts create mode 100644 apps/business/src/lib/routeTitles.ts create mode 100644 apps/business/src/main.tsx create mode 100644 apps/business/src/pages/AddNewBlogPage.tsx create mode 100644 apps/business/src/pages/AddNewPortfolioPage.tsx create mode 100644 apps/business/src/pages/AddNewProductPage.module.css create mode 100644 apps/business/src/pages/AddNewProductPage.tsx create mode 100644 apps/business/src/pages/BlogCategoriesPage.tsx create mode 100644 apps/business/src/pages/BlogDetailsPage.module.css create mode 100644 apps/business/src/pages/BlogDetailsPage.tsx create mode 100644 apps/business/src/pages/BlogListPage.tsx create mode 100644 apps/business/src/pages/BlogPage.module.css create mode 100644 apps/business/src/pages/BlogPage.tsx create mode 100644 apps/business/src/pages/BlogSettingsPage.tsx create mode 100644 apps/business/src/pages/BrandsPage.module.css create mode 100644 apps/business/src/pages/BrandsPage.tsx create mode 100644 apps/business/src/pages/BusinessProfilePage.module.css create mode 100644 apps/business/src/pages/BusinessProfilePage.tsx create mode 100644 apps/business/src/pages/CategoriesPage.module.css create mode 100644 apps/business/src/pages/CategoriesPage.tsx create mode 100644 apps/business/src/pages/CustomersPage.module.css create mode 100644 apps/business/src/pages/CustomersPage.tsx create mode 100644 apps/business/src/pages/HomePage.tsx create mode 100644 apps/business/src/pages/LoginPage.module.css create mode 100644 apps/business/src/pages/LoginPage.tsx create mode 100644 apps/business/src/pages/MyProductsPage.module.css create mode 100644 apps/business/src/pages/MyProductsPage.tsx create mode 100644 apps/business/src/pages/OrdersPage.module.css create mode 100644 apps/business/src/pages/OrdersPage.tsx create mode 100644 apps/business/src/pages/PortfolioCategoriesPage.tsx create mode 100644 apps/business/src/pages/PortfolioDetailsPage.module.css create mode 100644 apps/business/src/pages/PortfolioDetailsPage.tsx create mode 100644 apps/business/src/pages/PortfolioListPage.tsx create mode 100644 apps/business/src/pages/PortfolioSettingsPage.tsx create mode 100644 apps/business/src/pages/PortfoliosPage.tsx create mode 100644 apps/business/src/pages/ProductDetailsPage.module.css create mode 100644 apps/business/src/pages/ProductDetailsPage.tsx create mode 100644 apps/business/src/pages/ProductSettingsPage.module.css create mode 100644 apps/business/src/pages/ProductSettingsPage.tsx create mode 100644 apps/business/src/pages/ProductsPage.tsx create mode 100644 apps/business/src/pages/ShoppingCardsPage.module.css create mode 100644 apps/business/src/pages/ShoppingCardsPage.tsx create mode 100644 apps/business/src/pages/StoreItemsPage.module.css create mode 100644 apps/business/src/pages/StoreItemsPage.tsx create mode 100644 apps/business/src/pages/StorePage.tsx create mode 100644 apps/business/src/pages/StoreSettingsPage.module.css create mode 100644 apps/business/src/pages/StoreSettingsPage.tsx create mode 100644 apps/business/src/pages/StoreSpecialsPage.module.css create mode 100644 apps/business/src/pages/StoreSpecialsPage.tsx create mode 100644 apps/business/src/pages/WebsiteBadgesPage.tsx create mode 100644 apps/business/src/pages/WebsiteContactPage.module.css create mode 100644 apps/business/src/pages/WebsiteContactPage.tsx create mode 100644 apps/business/src/pages/WebsiteEPaymentPage.tsx create mode 100644 apps/business/src/pages/WebsiteFaqPage.tsx create mode 100644 apps/business/src/pages/WebsitePage.module.css create mode 100644 apps/business/src/pages/WebsitePage.tsx create mode 100644 apps/business/src/pages/WebsiteSlidersPage.tsx create mode 100644 apps/business/src/pages/WebsiteSpecialBrandsPage.tsx create mode 100644 apps/business/src/pages/WebsiteSpecialCategoriesPage.tsx create mode 100644 apps/business/src/pages/WebsiteSubscriptionsPage.tsx create mode 100644 apps/business/src/services/authService.ts create mode 100644 apps/business/src/services/blogCategoryService.ts create mode 100644 apps/business/src/services/blogService.ts create mode 100644 apps/business/src/services/brandService.ts create mode 100644 apps/business/src/services/businessActivityCategoryService.ts create mode 100644 apps/business/src/services/businessProfileService.ts create mode 100644 apps/business/src/services/categoryTechnicalFormService.ts create mode 100644 apps/business/src/services/categoryVariationsService.ts create mode 100644 apps/business/src/services/citiesService.ts create mode 100644 apps/business/src/services/commentService.ts create mode 100644 apps/business/src/services/contactSubmissionService.ts create mode 100644 apps/business/src/services/customerService.ts create mode 100644 apps/business/src/services/expertReviewService.ts create mode 100644 apps/business/src/services/mediaService.ts create mode 100644 apps/business/src/services/orderService.ts create mode 100644 apps/business/src/services/portfolioCategoryService.ts create mode 100644 apps/business/src/services/portfolioService.ts create mode 100644 apps/business/src/services/productAiService.ts create mode 100644 apps/business/src/services/productCategoryService.ts create mode 100644 apps/business/src/services/productService.ts create mode 100644 apps/business/src/services/productTechnicalInfoService.ts create mode 100644 apps/business/src/services/productVariantService.ts create mode 100644 apps/business/src/services/productVariationService.ts create mode 100644 apps/business/src/services/settingsService.ts create mode 100644 apps/business/src/services/shoppingCardService.ts create mode 100644 apps/business/src/services/storeItemService.ts create mode 100644 apps/business/src/services/storeSpecialService.ts create mode 100644 apps/business/src/services/tenantService.ts create mode 100644 apps/business/src/services/websiteBrandGroupService.ts create mode 100644 apps/business/src/services/websiteCategoryGroupService.ts create mode 100644 apps/business/src/services/websiteSliderService.ts create mode 100644 apps/business/src/styles/ai.module.css create mode 100644 apps/business/src/types/auth.ts create mode 100644 apps/business/src/types/blog.ts create mode 100644 apps/business/src/types/brand.ts create mode 100644 apps/business/src/types/category.ts create mode 100644 apps/business/src/types/comment.ts create mode 100644 apps/business/src/types/contactSubmission.ts create mode 100644 apps/business/src/types/draftCart.ts create mode 100644 apps/business/src/types/expertReview.ts create mode 100644 apps/business/src/types/portfolio.ts create mode 100644 apps/business/src/types/product.ts create mode 100644 apps/business/src/types/productVariant.ts create mode 100644 apps/business/src/types/storeSpecial.ts create mode 100644 apps/business/src/types/technicalForm.ts create mode 100644 apps/business/src/types/variation.ts create mode 100644 apps/business/src/types/websiteBrandGroup.ts create mode 100644 apps/business/src/types/websiteCategoryGroup.ts create mode 100644 apps/business/src/types/websiteSlider.ts create mode 100644 apps/business/src/utils/applyBusinessTheme.ts create mode 100644 apps/business/src/utils/businessCategories.ts create mode 100644 apps/business/src/utils/businessPrimaryColors.ts create mode 100644 apps/business/src/utils/categories.ts create mode 100644 apps/business/src/utils/cropImage.ts create mode 100644 apps/business/src/utils/filterStoreListings.ts create mode 100644 apps/business/src/utils/htmlImages.ts create mode 100644 apps/business/src/utils/id.ts create mode 100644 apps/business/src/utils/imageUpload.ts create mode 100644 apps/business/src/utils/irtPrice.ts create mode 100644 apps/business/src/utils/productActivity.ts create mode 100644 apps/business/src/utils/stepColors.ts create mode 100644 apps/business/src/utils/storeItemRows.ts create mode 100644 apps/business/src/utils/storeProductGroups.ts create mode 100644 apps/business/src/utils/storeSpecialListings.ts create mode 100644 apps/business/tsconfig.app.json create mode 100644 apps/business/tsconfig.json create mode 100644 apps/business/tsconfig.node.json create mode 100644 apps/business/vite.config.ts create mode 100644 apps/customer/.env.example create mode 100644 apps/customer/.gitignore create mode 100644 apps/customer/index.html create mode 100644 apps/customer/package-lock.json create mode 100644 apps/customer/package.json create mode 100644 apps/customer/public/favicon.svg create mode 100644 apps/customer/public/fonts/yekanbakh/YekanBakh-Bold.woff2 create mode 100644 apps/customer/public/fonts/yekanbakh/YekanBakh-Light.woff2 create mode 100644 apps/customer/public/fonts/yekanbakh/YekanBakh-Regular.woff2 create mode 100644 apps/customer/src/App.tsx create mode 100644 apps/customer/src/assets/meshkee-logo.png create mode 100644 apps/customer/src/assets/order-failed.png create mode 100644 apps/customer/src/assets/order-succeed.png create mode 100644 apps/customer/src/components/CustomerDomainGuard.tsx create mode 100644 apps/customer/src/components/DashboardDocumentTitle.tsx create mode 100644 apps/customer/src/components/FavoriteStoreItemCard.module.css create mode 100644 apps/customer/src/components/FavoriteStoreItemCard.tsx create mode 100644 apps/customer/src/components/GuestRoute.tsx create mode 100644 apps/customer/src/components/Header.module.css create mode 100644 apps/customer/src/components/Header.tsx create mode 100644 apps/customer/src/components/OrderItemsModal.module.css create mode 100644 apps/customer/src/components/OrderItemsModal.tsx create mode 100644 apps/customer/src/components/OrderRow.module.css create mode 100644 apps/customer/src/components/OrderRow.tsx create mode 100644 apps/customer/src/components/PageContent.module.css create mode 100644 apps/customer/src/components/PageLayout.module.css create mode 100644 apps/customer/src/components/PageLayout.tsx create mode 100644 apps/customer/src/components/ProtectedRoute.tsx create mode 100644 apps/customer/src/components/Sidebar.module.css create mode 100644 apps/customer/src/components/Sidebar.tsx create mode 100644 apps/customer/src/components/StoreItemPrice.module.css create mode 100644 apps/customer/src/components/StoreItemPrice.tsx create mode 100644 apps/customer/src/components/Toast.module.css create mode 100644 apps/customer/src/components/Tooltip.module.css create mode 100644 apps/customer/src/components/Tooltip.tsx create mode 100644 apps/customer/src/components/VariationsModal.module.css create mode 100644 apps/customer/src/components/checkout/CheckoutActionBar.module.css create mode 100644 apps/customer/src/components/checkout/CheckoutActionBar.tsx create mode 100644 apps/customer/src/components/checkout/CheckoutAddAddressModal.module.css create mode 100644 apps/customer/src/components/checkout/CheckoutAddAddressModal.tsx create mode 100644 apps/customer/src/components/checkout/CheckoutAddAddressPanel.module.css create mode 100644 apps/customer/src/components/checkout/CheckoutAddAddressPanel.tsx create mode 100644 apps/customer/src/components/checkout/CheckoutBackButton.module.css create mode 100644 apps/customer/src/components/checkout/CheckoutBackButton.tsx create mode 100644 apps/customer/src/components/checkout/CheckoutLayout.module.css create mode 100644 apps/customer/src/components/checkout/CheckoutLayout.tsx create mode 100644 apps/customer/src/components/checkout/CheckoutStepper.module.css create mode 100644 apps/customer/src/components/checkout/CheckoutStepper.tsx create mode 100644 apps/customer/src/context/AuthContext.tsx create mode 100644 apps/customer/src/context/CheckoutContext.tsx create mode 100644 apps/customer/src/context/CustomerThemeContext.tsx create mode 100644 apps/customer/src/context/TenantBrandingContext.tsx create mode 100644 apps/customer/src/fonts/iranyekan.css create mode 100644 apps/customer/src/fonts/yekanbakh.css create mode 100644 apps/customer/src/index.css create mode 100644 apps/customer/src/lib/api.ts create mode 100644 apps/customer/src/lib/businessContext.ts create mode 100644 apps/customer/src/lib/cellNumber.ts create mode 100644 apps/customer/src/lib/config.ts create mode 100644 apps/customer/src/lib/guestCart.ts create mode 100644 apps/customer/src/lib/routeTitles.ts create mode 100644 apps/customer/src/main.tsx create mode 100644 apps/customer/src/pages/AddressesPage.module.css create mode 100644 apps/customer/src/pages/AddressesPage.tsx create mode 100644 apps/customer/src/pages/FavoritesPage.module.css create mode 100644 apps/customer/src/pages/FavoritesPage.tsx create mode 100644 apps/customer/src/pages/HomePage.tsx create mode 100644 apps/customer/src/pages/LoginPage.module.css create mode 100644 apps/customer/src/pages/LoginPage.tsx create mode 100644 apps/customer/src/pages/OrdersPage.module.css create mode 100644 apps/customer/src/pages/OrdersPage.tsx create mode 100644 apps/customer/src/pages/ProfilePage.module.css create mode 100644 apps/customer/src/pages/ProfilePage.tsx create mode 100644 apps/customer/src/pages/checkout/CheckoutCartStep.tsx create mode 100644 apps/customer/src/pages/checkout/CheckoutDeliveryStep.tsx create mode 100644 apps/customer/src/pages/checkout/CheckoutFailedStep.tsx create mode 100644 apps/customer/src/pages/checkout/CheckoutFlow.tsx create mode 100644 apps/customer/src/pages/checkout/CheckoutLoginStep.tsx create mode 100644 apps/customer/src/pages/checkout/CheckoutPaymentStep.tsx create mode 100644 apps/customer/src/pages/checkout/CheckoutSteps.module.css create mode 100644 apps/customer/src/pages/checkout/CheckoutSuccessStep.tsx create mode 100644 apps/customer/src/services/addressService.ts create mode 100644 apps/customer/src/services/authService.ts create mode 100644 apps/customer/src/services/cartService.ts create mode 100644 apps/customer/src/services/citiesService.ts create mode 100644 apps/customer/src/services/favoritesService.ts create mode 100644 apps/customer/src/services/orderService.ts create mode 100644 apps/customer/src/services/syncGuestCart.ts create mode 100644 apps/customer/src/services/tenantService.ts create mode 100644 apps/customer/src/services/websiteService.ts create mode 100644 apps/customer/src/types/auth.ts create mode 100644 apps/customer/src/utils/applyBusinessTheme.ts create mode 100644 apps/customer/src/utils/businessPrimaryColors.ts create mode 100644 apps/customer/src/utils/irtPrice.ts create mode 100644 apps/customer/src/utils/orderSteps.ts create mode 100644 apps/customer/src/utils/stepColors.ts create mode 100644 apps/customer/src/utils/storeProductGroups.ts create mode 100644 apps/customer/tsconfig.app.json create mode 100644 apps/customer/tsconfig.json create mode 100644 apps/customer/tsconfig.node.json create mode 100644 apps/customer/vite.config.ts create mode 100644 apps/super-admin/.env.example create mode 100644 apps/super-admin/.gitignore create mode 100644 apps/super-admin/index.html create mode 100644 apps/super-admin/package-lock.json create mode 100644 apps/super-admin/package.json create mode 100644 apps/super-admin/public/favicon.svg create mode 100644 apps/super-admin/src/App.tsx create mode 100644 apps/super-admin/src/assets/meshkee-logo.png create mode 100644 apps/super-admin/src/components/AdminDomainGuard.module.css create mode 100644 apps/super-admin/src/components/AdminDomainGuard.tsx create mode 100644 apps/super-admin/src/components/ConfirmDeleteModal.module.css create mode 100644 apps/super-admin/src/components/ConfirmDeleteModal.tsx create mode 100644 apps/super-admin/src/components/GuestRoute.tsx create mode 100644 apps/super-admin/src/components/Header.module.css create mode 100644 apps/super-admin/src/components/Header.tsx create mode 100644 apps/super-admin/src/components/Modal.module.css create mode 100644 apps/super-admin/src/components/Modal.tsx create mode 100644 apps/super-admin/src/components/MultiSelectDropdown.module.css create mode 100644 apps/super-admin/src/components/MultiSelectDropdown.tsx create mode 100644 apps/super-admin/src/components/PageContent.module.css create mode 100644 apps/super-admin/src/components/PageLayout.module.css create mode 100644 apps/super-admin/src/components/PageLayout.tsx create mode 100644 apps/super-admin/src/components/PrimaryColorPicker.module.css create mode 100644 apps/super-admin/src/components/PrimaryColorPicker.tsx create mode 100644 apps/super-admin/src/components/PrimaryColorSwatchControl.module.css create mode 100644 apps/super-admin/src/components/PrimaryColorSwatchControl.tsx create mode 100644 apps/super-admin/src/components/ProtectedRoute.tsx create mode 100644 apps/super-admin/src/components/RouteLoader.module.css create mode 100644 apps/super-admin/src/components/SectionCard.module.css create mode 100644 apps/super-admin/src/components/SectionCard.tsx create mode 100644 apps/super-admin/src/components/Sidebar.module.css create mode 100644 apps/super-admin/src/components/Sidebar.tsx create mode 100644 apps/super-admin/src/components/Toast.module.css create mode 100644 apps/super-admin/src/components/ToggleSwitch.module.css create mode 100644 apps/super-admin/src/components/ToggleSwitch.tsx create mode 100644 apps/super-admin/src/components/UserSearchInput.module.css create mode 100644 apps/super-admin/src/components/UserSearchInput.tsx create mode 100644 apps/super-admin/src/context/AuthContext.tsx create mode 100644 apps/super-admin/src/context/ToastContext.tsx create mode 100644 apps/super-admin/src/fonts/iranyekan.css create mode 100644 apps/super-admin/src/index.css create mode 100644 apps/super-admin/src/lib/api.ts create mode 100644 apps/super-admin/src/lib/cellNumber.ts create mode 100644 apps/super-admin/src/lib/config.ts create mode 100644 apps/super-admin/src/main.tsx create mode 100644 apps/super-admin/src/pages/BusinessesPage.module.css create mode 100644 apps/super-admin/src/pages/BusinessesPage.tsx create mode 100644 apps/super-admin/src/pages/HomePage.tsx create mode 100644 apps/super-admin/src/pages/LoginPage.module.css create mode 100644 apps/super-admin/src/pages/LoginPage.tsx create mode 100644 apps/super-admin/src/pages/ProfilePage.module.css create mode 100644 apps/super-admin/src/pages/ProfilePage.tsx create mode 100644 apps/super-admin/src/pages/UsersPage.module.css create mode 100644 apps/super-admin/src/pages/UsersPage.tsx create mode 100644 apps/super-admin/src/pages/WebsitesPage.module.css create mode 100644 apps/super-admin/src/pages/WebsitesPage.tsx create mode 100644 apps/super-admin/src/services/authService.ts create mode 100644 apps/super-admin/src/services/businessService.ts create mode 100644 apps/super-admin/src/services/businessSettingsService.ts create mode 100644 apps/super-admin/src/services/categoryService.ts create mode 100644 apps/super-admin/src/services/domainService.ts create mode 100644 apps/super-admin/src/services/profileService.ts create mode 100644 apps/super-admin/src/services/teamService.ts create mode 100644 apps/super-admin/src/services/userService.ts create mode 100644 apps/super-admin/src/types/auth.ts create mode 100644 apps/super-admin/src/types/business.ts create mode 100644 apps/super-admin/src/types/category.ts create mode 100644 apps/super-admin/src/types/domain.ts create mode 100644 apps/super-admin/src/types/profile.ts create mode 100644 apps/super-admin/src/types/user.ts create mode 100644 apps/super-admin/src/utils/businessPrimaryColors.ts create mode 100644 apps/super-admin/src/utils/categories.ts create mode 100644 apps/super-admin/src/vite-env.d.ts create mode 100644 apps/super-admin/tsconfig.app.json create mode 100644 apps/super-admin/tsconfig.json create mode 100644 apps/super-admin/tsconfig.node.json create mode 100644 apps/super-admin/vite.config.ts create mode 100644 deploy/nginx-dashboards-ssl.conf create mode 100644 deploy/nginx-dashboards.conf create mode 100644 deploy/ssl-sync.sh create mode 100644 docs/DEPLOY.md create mode 100644 docs/PROJECT_CONTEXT.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 packages/README.md create mode 100644 packages/dashboard-core/package.json create mode 100644 packages/dashboard-core/src/api/createApiClient.ts create mode 100644 packages/dashboard-core/src/domain/subdomain.ts create mode 100644 packages/dashboard-core/src/index.ts create mode 100644 packages/dashboard-core/src/styles/tokens.css create mode 100644 packages/dashboard-core/src/types/auth.ts create mode 100644 packages/dashboard-core/src/utils/cellNumber.ts create mode 100644 packages/dashboard-core/src/utils/documentTitle.ts create mode 100644 packages/dashboard-core/src/utils/favicon.ts create mode 100644 packages/dashboard-core/src/utils/irtPrice.ts create mode 100644 packages/dashboard-core/tsconfig.json create mode 100644 packages/dashboard-ui/package.json create mode 100644 packages/dashboard-ui/src/components/AddressListEditor.module.css create mode 100644 packages/dashboard-ui/src/components/AddressListEditor.tsx create mode 100644 packages/dashboard-ui/src/components/Breadcrumbs.module.css create mode 100644 packages/dashboard-ui/src/components/Breadcrumbs.tsx create mode 100644 packages/dashboard-ui/src/components/DomainGuard.module.css create mode 100644 packages/dashboard-ui/src/components/Pagination.module.css create mode 100644 packages/dashboard-ui/src/components/Pagination.tsx create mode 100644 packages/dashboard-ui/src/components/PasswordResetModal.module.css create mode 100644 packages/dashboard-ui/src/components/PasswordResetModal.tsx create mode 100644 packages/dashboard-ui/src/components/RouteLoader.module.css create mode 100644 packages/dashboard-ui/src/components/RouteLoader.tsx create mode 100644 packages/dashboard-ui/src/components/SectionCard.module.css create mode 100644 packages/dashboard-ui/src/components/SectionCard.tsx create mode 100644 packages/dashboard-ui/src/components/createDomainGuard.tsx create mode 100644 packages/dashboard-ui/src/context/Toast.module.css create mode 100644 packages/dashboard-ui/src/context/ToastContext.tsx create mode 100644 packages/dashboard-ui/src/css.d.ts create mode 100644 packages/dashboard-ui/src/hooks/useDashboardDocumentTitle.ts create mode 100644 packages/dashboard-ui/src/index.ts create mode 100644 packages/dashboard-ui/tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c838103 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +dist/ +.env +.env.local +*.local +.DS_Store +.certs/ +*.log +.idea/ +.vscode/ +.cursor/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..ecc2d84 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# MeshkeeApp Monorepo + +Dashboard applications and shared packages for the Meshkee platform. + +**Agent context:** see [docs/PROJECT_CONTEXT.md](./docs/PROJECT_CONTEXT.md) and `.cursor/rules/`. + +## Structure + +``` +MeshkeeApp/ + apps/ + super-admin/ # port 5174 — meshkee.app + business/ # port 5173 — business.{domain} + customer/ # port 5175 — customer.{domain} + packages/ + dashboard-core/ # @meshkee/dashboard-core + dashboard-ui/ # @meshkee/dashboard-ui +``` + +## Setup + +```bash +cd MeshkeeApp +npm install +``` + +## Dev servers + +From the monorepo root: + +```bash +npm run dev:customer # http://customer.sanihome.ir:5175 +npm run dev:business # http://business.sanihome.ir:5173 +npm run dev:super-admin # https://meshkee.app:5174 +``` + +Or from an app directory: + +```bash +cd apps/customer && npm run dev +``` + +## Build + +```bash +npm run build # packages + all apps +npm run build:customer +npm run build:business +npm run build:super-admin +``` + +## Shared packages + +See [packages/README.md](./packages/README.md). + +- **dashboard-core** — API client, auth types, utils, design tokens +- **dashboard-ui** — Breadcrumbs, SectionCard, Pagination, Toast, RouteLoader, DomainGuard + +Customer dashboard uses both packages. Business and Super Admin will migrate incrementally. + +## Local hosts + +``` +127.0.0.1 customer.sanihome.ir +127.0.0.1 business.sanihome.ir +127.0.0.1 meshkee.app +``` diff --git a/apps/business/.env.example b/apps/business/.env.example new file mode 100644 index 0000000..45d98f6 --- /dev/null +++ b/apps/business/.env.example @@ -0,0 +1,7 @@ +# Backend API (NestJS) +VITE_API_BASE_URL=http://localhost:3000/api/v1 + +# Optional localhost-only fallback when not using business.{domain} in /etc/hosts. +# Leave unset for multi-tenant dev — tenant is resolved from hostname +# (e.g. business.sanihome.ir → sanihome.ir, business.safeteb.com → safeteb.com). +# VITE_BUSINESS_DOMAIN=sanihome.ir diff --git a/apps/business/.gitignore b/apps/business/.gitignore new file mode 100644 index 0000000..438657a --- /dev/null +++ b/apps/business/.gitignore @@ -0,0 +1,25 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +.env + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/apps/business/.oxlintrc.json b/apps/business/.oxlintrc.json new file mode 100644 index 0000000..6fa991d --- /dev/null +++ b/apps/business/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/apps/business/README.md b/apps/business/README.md new file mode 100644 index 0000000..d6af7e3 --- /dev/null +++ b/apps/business/README.md @@ -0,0 +1,32 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the Oxlint configuration + +If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`: + +```json +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "options": { + "typeAware": true + }, + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} +``` + +See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories. diff --git a/apps/business/index.html b/apps/business/index.html new file mode 100644 index 0000000..b71258d --- /dev/null +++ b/apps/business/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + Business Dashboard + + +
+ + + diff --git a/apps/business/package-lock.json b/apps/business/package-lock.json new file mode 100644 index 0000000..71d9a00 --- /dev/null +++ b/apps/business/package-lock.json @@ -0,0 +1,1453 @@ +{ + "name": "meshkeeapp-gen2", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "meshkeeapp-gen2", + "version": "0.0.0", + "dependencies": { + "lucide-react": "^1.23.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-easy-crop": "^6.2.0", + "react-router-dom": "^7.18.1" + }, + "devDependencies": { + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", + "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.73.0.tgz", + "integrity": "sha512-HZQRN/UMBu+Ut+/9MiAChkbP4qZqrNOWBcNI45vOT40GVhbGR0JgHB87L48D4iAqFQIdVmeQYtV9RF89AjTKkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.73.0.tgz", + "integrity": "sha512-Gp+KJRylv2aW7thRpG5p1KTxZq4ZJFbWowrKzufNq9d3ssl3r3JviYV45/+p+7CN1Nv0zDd1e8Ex0b/HUDq4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.73.0.tgz", + "integrity": "sha512-3de96NdtXhxERMjIz7wsp2HYMY6pMQycGxFWac2mFecAx6VeARF/IqFb1QIaqiCRIdfzBwzTed+pCTCoiS+CYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.73.0.tgz", + "integrity": "sha512-5zx/uPW32TiaOeVY1dQ/H5iOf0K1HOdFKOJhLqGl4o63+i1fpzoqqu/mKtd7OFgFjNCdhlyTGgjVkQTZm1ELcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.73.0.tgz", + "integrity": "sha512-qNe4gKHaGnLuZJ8toUg90JAa0S2vTVvDw+0bRi3q1avXZXDT4u5mMeECf3nD4HYrbdn1O7dXqWut4onY/yx/Xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.73.0.tgz", + "integrity": "sha512-cCehYh5hTbfShm/fxTD6wwrGUWIpvX+N5OxmAMhFhDeTGXvw+BeNj889tpxsFQ9ZLatQ6wImuY8tsKLZ+FMz7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.73.0.tgz", + "integrity": "sha512-d5j5GDU/2dMgjVhw7TQT9ITrsIr1Y02KEXKyVGIXUkD+KiaxE9TP65FS2ZdgTBemQvoRL+gSBdbrIm3cQIeacg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.73.0.tgz", + "integrity": "sha512-Eyf1SrP3+yR1DI3OJgOY2Pvrr9dWP9TK37xPaDYycwTtlGlI45erJAVIfH5/m/xosDt6BupJYEFi47bvbTuuyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.73.0.tgz", + "integrity": "sha512-IlT/OJApEDKaMmCooHuncgJZbbCe7T5QIWmTZBEtYscWvzPQuuEinVcid6kwQRVQOUdb7PUCz4jQHnaYXdfJXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.73.0.tgz", + "integrity": "sha512-L+JYcb/vdg5fmcH08V6o0YYLU28cTH1SPNulwJdvK9NK49aXSkYy6oNpKBmddArVOXYqNepriDGiZ04G54kh1Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.73.0.tgz", + "integrity": "sha512-Qtk0g3bKV6OwWjIm7R8kQN1uOZRKQt/MODK2a8QfkwhTpXBD53ozx5XLVWLGDQAVyp2otLW4D2wB98XfAfMPGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.73.0.tgz", + "integrity": "sha512-wX0NQKZVxltkAOVmzFcpOaMpdaUvsq1Eqpx9tkAfl71UdkTlSo1R4AdAnGccR1Fm2+TzFgZ22CyyGuZ41RDr/A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.73.0.tgz", + "integrity": "sha512-vPe7UGBMWyiLTtnqS4xxgMQFSFGmtQwhwCxuiw6lXygaO6bVt0D8dFVg8Xv05eaiN3ybC0HXXHUAohFMFvqoCQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.73.0.tgz", + "integrity": "sha512-2CwIWr9cemFC/CbRBWZvuk5mffz6ObmfFkfcC/9rTQ7f+icNhYr2kOjf9Rt8lLvugvkdGDOmkoVoFFHh6ClCTw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.73.0.tgz", + "integrity": "sha512-nDadfJgg7NBBxG0N560wOe7LLX5QiYp6qBaI7viuk5EUORFBktU/NfV0MbTqU3gTqQDCh4VyxKdo5VADxk9w8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.73.0.tgz", + "integrity": "sha512-wGjJC+NLH9xP+IKGn9RDW94ojJR/wPbg5WCnQjj/oReaOtCQthr8ws1zICe77JFmo4ouUdeTHHZL/ESGiF6Pmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.73.0.tgz", + "integrity": "sha512-I7X47GPGljw225YUQ5SbC/rb1Kkdrd0yQf0x+hYxeKS6DpfjMbo9ccQPQ6LNY6BoJQ1sHhgDUGuMn5Vg5gHT6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.73.0.tgz", + "integrity": "sha512-5lWj+3h+74Fm1jYOO9qkJA4xkAlZA099DkXppuXsk7UpnpZLttsefrZU469vChGaG6hcSqrkKXQOvMTZtbjeNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.73.0.tgz", + "integrity": "sha512-WaNRvh4f6zY9CvUQk2YoA1O90ieWrIklI84+HXFr9Isjz9CSESrdqo/RtIYt4Dll/cAchqGDMehfaZd0vqEFZw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", + "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", + "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", + "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", + "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", + "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", + "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", + "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", + "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", + "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", + "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", + "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", + "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", + "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", + "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", + "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucide-react": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.23.0.tgz", + "integrity": "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-wheel": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/normalize-wheel/-/normalize-wheel-1.0.1.tgz", + "integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==", + "license": "BSD-3-Clause" + }, + "node_modules/oxlint": { + "version": "1.73.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.73.0.tgz", + "integrity": "sha512-u91G9TJzU6yqKWNZUYprQB07W7YvntZXaRxQ6CkoytepYhLWUXWsr1M8zUJ34VatNPuUAr3Z8GH+O2A331CluQ==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.73.0", + "@oxlint/binding-android-arm64": "1.73.0", + "@oxlint/binding-darwin-arm64": "1.73.0", + "@oxlint/binding-darwin-x64": "1.73.0", + "@oxlint/binding-freebsd-x64": "1.73.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.73.0", + "@oxlint/binding-linux-arm-musleabihf": "1.73.0", + "@oxlint/binding-linux-arm64-gnu": "1.73.0", + "@oxlint/binding-linux-arm64-musl": "1.73.0", + "@oxlint/binding-linux-ppc64-gnu": "1.73.0", + "@oxlint/binding-linux-riscv64-gnu": "1.73.0", + "@oxlint/binding-linux-riscv64-musl": "1.73.0", + "@oxlint/binding-linux-s390x-gnu": "1.73.0", + "@oxlint/binding-linux-x64-gnu": "1.73.0", + "@oxlint/binding-linux-x64-musl": "1.73.0", + "@oxlint/binding-openharmony-arm64": "1.73.0", + "@oxlint/binding-win32-arm64-msvc": "1.73.0", + "@oxlint/binding-win32-ia32-msvc": "1.73.0", + "@oxlint/binding-win32-x64-msvc": "1.73.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.24.0", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-easy-crop": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-6.2.0.tgz", + "integrity": "sha512-8CEes7M8UkNoRPaz/krm70xMTbpar07crz/OrPSJwdGopV6lPcqdEmamDYTXgsqukb/1e2fbFd9/mROi8Qkp+A==", + "license": "MIT", + "dependencies": { + "normalize-wheel": "^1.0.1" + }, + "peerDependencies": { + "react": ">=16.4.0", + "react-dom": ">=16.4.0" + } + }, + "node_modules/react-router": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rolldown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", + "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.138.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.4", + "@rolldown/binding-darwin-arm64": "1.1.4", + "@rolldown/binding-darwin-x64": "1.1.4", + "@rolldown/binding-freebsd-x64": "1.1.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", + "@rolldown/binding-linux-arm64-gnu": "1.1.4", + "@rolldown/binding-linux-arm64-musl": "1.1.4", + "@rolldown/binding-linux-ppc64-gnu": "1.1.4", + "@rolldown/binding-linux-s390x-gnu": "1.1.4", + "@rolldown/binding-linux-x64-gnu": "1.1.4", + "@rolldown/binding-linux-x64-musl": "1.1.4", + "@rolldown/binding-openharmony-arm64": "1.1.4", + "@rolldown/binding-wasm32-wasi": "1.1.4", + "@rolldown/binding-win32-arm64-msvc": "1.1.4", + "@rolldown/binding-win32-x64-msvc": "1.1.4" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", + "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/apps/business/package.json b/apps/business/package.json new file mode 100644 index 0000000..23a7360 --- /dev/null +++ b/apps/business/package.json @@ -0,0 +1,29 @@ +{ + "name": "@meshkee/business-dashboard", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "@meshkee/dashboard-ui": "*", + "lucide-react": "^1.23.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-easy-crop": "^6.2.0", + "react-router-dom": "^7.18.1" + }, + "devDependencies": { + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1" + } +} diff --git a/apps/business/public/favicon.svg b/apps/business/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/apps/business/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/business/public/icons.svg b/apps/business/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/apps/business/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/business/public/meshkee-logo.png b/apps/business/public/meshkee-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9d12a5b27f3d8bf34d79fbd8cbb3317ea56c2620 GIT binary patch literal 3070 zcmeHJX;jl!7JorxQIX9ShNUoY6jQb$VqjQ8HL^`vj0zM1qtgz1!T=Hw5^_e72n7a> zT4YmW0F@;yVI31zBPyvOEE3j02qKWM$FRXj)0=T?b%qr#tG)GYS0pk>)4g`sueUjs+wY zu^nS2!qKk7UV7wenVv5W}-T(j0|ad1O#C*TKWvJfxY}z z4TjOTnO$#HpqBsOX%Q@oHD7KgA9dLFzMY75$riOfn|`_Z)|(Mu&@NB9#H*N0d(lnT z|E``Dw2mcK3k=kAt}!f%ue1u!g-yR350yTIT0_6i?nMbg(WVu3B)YsfOI((_{VtK7e)pVh4=cgIyvl+_mv)~I9kzp z%dLr90TGj?^7I=#RxzcHFA$yP;y2g@+l=BcUfrGjV}!}YLBUYUC*Y+4(9KgN#!H3g zc6KBU?j@rrfcd(&l<~@V;$h8&;EnCot)WW7{jxy|=jKAzTYKxxF_sBEUOi}W3TAt! z=EC=P*SM4zU3ED_&PeKwuFpaK*vJ)zgK}-X3Yd=eg~04%14s~t-!vN%;H37cP@MYQDLO&vgV`n7FItL~qv#&-CEOs@{N=--s)V%pEn&i_;mxqxi zfMor5%|jlD=3oz`CNRAxDRHf#;tM_P>eV|f04U+oD1VBNo^@rIU53$hB*|pHB{sQX z9E_harb)1=5de(qF+F_1`&0-WKf0v$WT_%);!OQDO4tD{ZMk9g={I^gX@AZI4R@re zB8r0M6UFG1z8+Uf_0E_3zMGHSOKQlFUacdr5}xFZY=@ao`IZTI_kr()wGv9b0<}A$ z8?)q}=@ecCHwEH}1t=4JGfBAQD66p)>&Ln(KwQ!}*OuXF-=qPT?6VA958_-&qxN4k zaRwa)Lu=kqGaL9~MH~TdHI&4=0nA!+2RFy#5y*xDwAYnIPWSUg`dMW>Z1@!Xfy)m6 zVv`h~0i(xR&RwVUbR1VAHP~zAQBI;(T@R5CCTk@>8bJn84gz57x6JB@+(9BM7+-ZC zgMk)1bkpDvqvL*F4V0B>aeQEu9tp# z0t_cI&d(J;Z;y{#OoP|=t73lV^%|YU6`2qV(4V=Ys*L2uv5~tX2Z&WjZJd*h_W8{R zyJ7z6a&C5vY+I=okWPf^7P}eG0=j&7X29(IR9j}hM!q{v8Hgvt=iYKlG_jfC z`J%vZkX9wN8!+2Ye9rXVZ5Y|wX|+7VCRrHKd<}Os674mV_>$2m(tP$Nb zbFpqGuQg#dH(;H|+Ix>8MceZWC9F$PP|-X%W=J}VB^`>2bX?&tv~gnDgvYonhZ&Kg zWn9fuRw8>b{W;Y8;DTf_Ol_k110gxC?KPZ7QJAc-UvKA%Gfkj=N;nI--EJ{;O!Zh@ zksCa!gYD;>>9z$Gs+ci-ePz-oN3@X(&qcH!HmNH$?PQ1Rnt{l_q3r|B_NO36p31hE zh)tM3GGYTiAFw_`8Nv?uk6H0)3C`-XrF#G&bpuULRB2c{PiEWmYN0t literal 0 HcmV?d00001 diff --git a/apps/business/public/product-template.png b/apps/business/public/product-template.png new file mode 100644 index 0000000000000000000000000000000000000000..ee4236fdea1dc106c6b1937511863a1850b77956 GIT binary patch literal 44410 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

1wY^-J&SM_0qHTIs_d~+Q1!xdJ7KF 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 c*agu2wgZZ4GW=KYE|FpCfgWrAF@yh=l}o! literal 0 HcmV?d00001 diff --git a/apps/business/src/App.tsx b/apps/business/src/App.tsx new file mode 100644 index 0000000..f0d9720 --- /dev/null +++ b/apps/business/src/App.tsx @@ -0,0 +1,118 @@ +import { BrowserRouter, Routes, Route } from 'react-router-dom' +import { BusinessDomainGuard } from './components/BusinessDomainGuard' +import { DashboardDocumentTitle } from './components/DashboardDocumentTitle' +import { AuthProvider } from './context/AuthContext' +import { BusinessThemeProvider } from './context/BusinessThemeContext' +import { TenantBrandingProvider } from './context/TenantBrandingContext' +import { ToastProvider } from './context/ToastContext' +import { ProtectedRoute } from './components/ProtectedRoute' +import { GuestRoute } from './components/GuestRoute' +import { PageLayout } from './components/PageLayout' +import { HomePage } from './pages/HomePage' +import { ProductsPage } from './pages/ProductsPage' +import { CategoriesPage } from './pages/CategoriesPage' +import { BrandsPage } from './pages/BrandsPage' +import { AddNewProductPage } from './pages/AddNewProductPage' +import { MyProductsPage } from './pages/MyProductsPage' +import { ProductDetailsPage } from './pages/ProductDetailsPage' +import { ProductSettingsPage } from './pages/ProductSettingsPage' +import { BusinessProfilePage } from './pages/BusinessProfilePage' +import { StorePage } from './pages/StorePage' +import { StoreItemsPage } from './pages/StoreItemsPage' +import { StoreSpecialsPage } from './pages/StoreSpecialsPage' +import { CustomersPage } from './pages/CustomersPage' +import { OrdersPage } from './pages/OrdersPage' +import { LoginPage } from './pages/LoginPage' +import { ShoppingCardsPage } from './pages/ShoppingCardsPage' +import { StoreSettingsPage } from './pages/StoreSettingsPage' +import { BlogPage } from './pages/BlogPage' +import { BlogListPage } from './pages/BlogListPage' +import { AddNewBlogPage } from './pages/AddNewBlogPage' +import { BlogCategoriesPage } from './pages/BlogCategoriesPage' +import { BlogSettingsPage } from './pages/BlogSettingsPage' +import { BlogDetailsPage } from './pages/BlogDetailsPage' +import { PortfoliosPage } from './pages/PortfoliosPage' +import { PortfolioListPage } from './pages/PortfolioListPage' +import { AddNewPortfolioPage } from './pages/AddNewPortfolioPage' +import { PortfolioCategoriesPage } from './pages/PortfolioCategoriesPage' +import { PortfolioSettingsPage } from './pages/PortfolioSettingsPage' +import { PortfolioDetailsPage } from './pages/PortfolioDetailsPage' +import { WebsitePage } from './pages/WebsitePage' +import { WebsiteContactPage } from './pages/WebsiteContactPage' +import { WebsiteSubscriptionsPage } from './pages/WebsiteSubscriptionsPage' +import { WebsiteFaqPage } from './pages/WebsiteFaqPage' +import { WebsiteBadgesPage } from './pages/WebsiteBadgesPage' +import { WebsiteEPaymentPage } from './pages/WebsiteEPaymentPage' +import { WebsiteSlidersPage } from './pages/WebsiteSlidersPage' +import { WebsiteSpecialCategoriesPage } from './pages/WebsiteSpecialCategoriesPage' +import { WebsiteSpecialBrandsPage } from './pages/WebsiteSpecialBrandsPage' + +function App() { + return ( + + + + + + + + + }> + } /> + + + }> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + + + + + + ) +} + +export default App diff --git a/apps/business/src/assets/hero.png b/apps/business/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..02251f4b956c55af2d76fd0788124d7eee2b45eb GIT binary patch literal 13057 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 P00000NkvXXu0mjft=yBf literal 0 HcmV?d00001 diff --git a/apps/business/src/assets/meshkee-logo.png b/apps/business/src/assets/meshkee-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9d12a5b27f3d8bf34d79fbd8cbb3317ea56c2620 GIT binary patch literal 3070 zcmeHJX;jl!7JorxQIX9ShNUoY6jQb$VqjQ8HL^`vj0zM1qtgz1!T=Hw5^_e72n7a> zT4YmW0F@;yVI31zBPyvOEE3j02qKWM$FRXj)0=T?b%qr#tG)GYS0pk>)4g`sueUjs+wY zu^nS2!qKk7UV7wenVv5W}-T(j0|ad1O#C*TKWvJfxY}z z4TjOTnO$#HpqBsOX%Q@oHD7KgA9dLFzMY75$riOfn|`_Z)|(Mu&@NB9#H*N0d(lnT z|E``Dw2mcK3k=kAt}!f%ue1u!g-yR350yTIT0_6i?nMbg(WVu3B)YsfOI((_{VtK7e)pVh4=cgIyvl+_mv)~I9kzp z%dLr90TGj?^7I=#RxzcHFA$yP;y2g@+l=BcUfrGjV}!}YLBUYUC*Y+4(9KgN#!H3g zc6KBU?j@rrfcd(&l<~@V;$h8&;EnCot)WW7{jxy|=jKAzTYKxxF_sBEUOi}W3TAt! z=EC=P*SM4zU3ED_&PeKwuFpaK*vJ)zgK}-X3Yd=eg~04%14s~t-!vN%;H37cP@MYQDLO&vgV`n7FItL~qv#&-CEOs@{N=--s)V%pEn&i_;mxqxi zfMor5%|jlD=3oz`CNRAxDRHf#;tM_P>eV|f04U+oD1VBNo^@rIU53$hB*|pHB{sQX z9E_harb)1=5de(qF+F_1`&0-WKf0v$WT_%);!OQDO4tD{ZMk9g={I^gX@AZI4R@re zB8r0M6UFG1z8+Uf_0E_3zMGHSOKQlFUacdr5}xFZY=@ao`IZTI_kr()wGv9b0<}A$ z8?)q}=@ecCHwEH}1t=4JGfBAQD66p)>&Ln(KwQ!}*OuXF-=qPT?6VA958_-&qxN4k zaRwa)Lu=kqGaL9~MH~TdHI&4=0nA!+2RFy#5y*xDwAYnIPWSUg`dMW>Z1@!Xfy)m6 zVv`h~0i(xR&RwVUbR1VAHP~zAQBI;(T@R5CCTk@>8bJn84gz57x6JB@+(9BM7+-ZC zgMk)1bkpDvqvL*F4V0B>aeQEu9tp# z0t_cI&d(J;Z;y{#OoP|=t73lV^%|YU6`2qV(4V=Ys*L2uv5~tX2Z&WjZJd*h_W8{R zyJ7z6a&C5vY+I=okWPf^7P}eG0=j&7X29(IR9j}hM!q{v8Hgvt=iYKlG_jfC z`J%vZkX9wN8!+2Ye9rXVZ5Y|wX|+7VCRrHKd<}Os674mV_>$2m(tP$Nb zbFpqGuQg#dH(;H|+Ix>8MceZWC9F$PP|-X%W=J}VB^`>2bX?&tv~gnDgvYonhZ&Kg zWn9fuRw8>b{W;Y8;DTf_Ol_k110gxC?KPZ7QJAc-UvKA%Gfkj=N;nI--EJ{;O!Zh@ zksCa!gYD;>>9z$Gs+ci-ePz-oN3@X(&qcH!HmNH$?PQ1Rnt{l_q3r|B_NO36p31hE zh)tM3GGYTiAFw_`8Nv?uk6H0)3C`-XrF#G&bpuULRB2c{PiEWmYN0t literal 0 HcmV?d00001 diff --git a/apps/business/src/assets/react.svg b/apps/business/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/apps/business/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/business/src/assets/vite.svg b/apps/business/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/apps/business/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/apps/business/src/components/AddCustomerModal.module.css b/apps/business/src/components/AddCustomerModal.module.css new file mode 100644 index 0000000..fe43099 --- /dev/null +++ b/apps/business/src/components/AddCustomerModal.module.css @@ -0,0 +1,22 @@ +.formGrid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; +} + +.formFull { + grid-column: 1 / -1; +} + +.hint { + margin-bottom: 12px; + font-size: 12px; + line-height: 1.45; + color: var(--text-secondary); +} + +@media (max-width: 520px) { + .formGrid { + grid-template-columns: 1fr; + } +} diff --git a/apps/business/src/components/AddCustomerModal.tsx b/apps/business/src/components/AddCustomerModal.tsx new file mode 100644 index 0000000..0b68894 --- /dev/null +++ b/apps/business/src/components/AddCustomerModal.tsx @@ -0,0 +1,209 @@ +import { useEffect, useState } from 'react' +import { X } from 'lucide-react' +import { ApiError } from '../lib/api' +import { toE164CellNumber } from '../lib/cellNumber' +import { + createCustomer, + type BusinessCustomerListItem, +} from '../services/customerService' +import formStyles from './AddCustomerModal.module.css' +import modalStyles from './VariationsModal.module.css' + +interface AddCustomerModalProps { + open: boolean + onClose: () => void + onCreated: (customer: BusinessCustomerListItem) => void +} + +const ANIMATION_MS = 220 + +export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalProps) { + const [mounted, setMounted] = useState(open) + const [closing, setClosing] = useState(false) + const [firstName, setFirstName] = useState('') + const [lastName, setLastName] = useState('') + const [cellNumber, setCellNumber] = useState('') + const [password, setPassword] = useState('') + const [email, setEmail] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState('') + + useEffect(() => { + if (open) { + setMounted(true) + setClosing(false) + setFirstName('') + setLastName('') + setCellNumber('') + setPassword('') + setEmail('') + setError('') + } else if (mounted) { + setClosing(true) + const timer = setTimeout(() => { + setMounted(false) + setClosing(false) + }, ANIMATION_MS) + return () => clearTimeout(timer) + } + }, [open, mounted]) + + useEffect(() => { + if (!mounted || closing) return + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + return () => document.removeEventListener('keydown', onKey) + }, [mounted, closing, onClose]) + + if (!mounted) return null + + const canSubmit = + firstName.trim().length >= 2 && + lastName.trim().length >= 2 && + toE164CellNumber(cellNumber.trim()).length > 0 + + async function handleSubmit() { + const normalizedCell = toE164CellNumber(cellNumber.trim()) + if (!canSubmit || !normalizedCell) return + + setIsSubmitting(true) + setError('') + try { + const created = await createCustomer({ + cellNumber: normalizedCell, + firstName: firstName.trim(), + lastName: lastName.trim(), + ...(password.trim() ? { password: password.trim() } : {}), + ...(email.trim() ? { email: email.trim() } : {}), + }) + onCreated({ + id: created.id, + cellNumber: created.cellNumber, + firstName: created.firstName, + lastName: created.lastName, + email: created.email, + label: created.label, + createdAt: created.createdAt, + isEnabled: created.isEnabled, + }) + onClose() + } catch (err) { + setError(err instanceof ApiError ? err.message : 'Unable to add customer.') + } finally { + setIsSubmitting(false) + } + } + + return ( +

+
e.stopPropagation()} + role="dialog" + aria-modal="true" + aria-labelledby="add-customer-title" + > +
+
+

+ Add customer +

+

+ Creates a verified customer account or links an existing user to your business. +

+
+ +
+ +
+

+ Password is required only for new accounts. Existing users are added as verified + customers. +

+ + {error &&

{error}

} + +
+
+ + setFirstName(e.target.value)} + disabled={isSubmitting} + /> +
+
+ + setLastName(e.target.value)} + disabled={isSubmitting} + /> +
+
+ + setCellNumber(e.target.value)} + placeholder="0912..." + autoComplete="off" + disabled={isSubmitting} + /> +
+
+ + setPassword(e.target.value)} + placeholder="Min. 8 characters" + disabled={isSubmitting} + /> +
+
+ + setEmail(e.target.value)} + disabled={isSubmitting} + /> +
+
+ +
+ + +
+
+
+
+ ) +} diff --git a/apps/business/src/components/AddProductByAiModal.module.css b/apps/business/src/components/AddProductByAiModal.module.css new file mode 100644 index 0000000..4c9f10a --- /dev/null +++ b/apps/business/src/components/AddProductByAiModal.module.css @@ -0,0 +1,25 @@ +.headerBlock { + flex: 1; + min-width: 0; +} + +.subtitle { + font-size: 13px; + color: var(--text-secondary); + margin-top: 4px; + line-height: 1.45; +} + +.error { + margin: 0; + padding: 10px 12px; + font-size: 13px; + color: #b91c1c; + background: rgba(239, 68, 68, 0.08); + border: 1px solid rgba(239, 68, 68, 0.25); + border-radius: var(--radius-sm); +} + +.header { + align-items: flex-start; +} diff --git a/apps/business/src/components/AddProductByAiModal.tsx b/apps/business/src/components/AddProductByAiModal.tsx new file mode 100644 index 0000000..30ea18b --- /dev/null +++ b/apps/business/src/components/AddProductByAiModal.tsx @@ -0,0 +1,234 @@ +import { useEffect, useMemo, useState } from 'react' +import { Sparkles, X } from 'lucide-react' +import { useToast } from '../context/ToastContext' +import { ApiError } from '../lib/api' +import { listProductCategories, mapProductCategoryToUi } from '../services/productCategoryService' +import { createProductByAi, type ProductAiLanguage } from '../services/productAiService' +import type { Category } from '../types/category' +import { flattenCategories } from '../utils/categories' +import { SearchableSelect } from './SearchableSelect' +import modalStyles from './CategoryModal.module.css' +import aiStyles from '../styles/ai.module.css' +import styles from './AddProductByAiModal.module.css' +import fieldStyles from '../pages/AddNewProductPage.module.css' + +interface AddProductByAiModalProps { + open: boolean + onClose: () => void + onCreated: (productId: string) => void +} + +const ANIMATION_MS = 220 + +export function AddProductByAiModal({ + open, + onClose, + onCreated, +}: AddProductByAiModalProps) { + const { showToast } = useToast() + const [mounted, setMounted] = useState(open) + const [closing, setClosing] = useState(false) + const [categories, setCategories] = useState([]) + const [categoryId, setCategoryId] = useState('') + const [name, setName] = useState('') + const [language, setLanguage] = useState('en') + const [isLoadingCategories, setIsLoadingCategories] = useState(false) + const [isSubmitting, setIsSubmitting] = useState(false) + const [error, setError] = useState('') + + const categoryOptions = useMemo(() => flattenCategories(categories), [categories]) + + useEffect(() => { + if (open) { + setMounted(true) + setClosing(false) + setCategoryId('') + setName('') + setLanguage('en') + setError('') + } else if (mounted) { + setClosing(true) + const timer = setTimeout(() => { + setMounted(false) + setClosing(false) + }, ANIMATION_MS) + return () => clearTimeout(timer) + } + }, [open, mounted]) + + useEffect(() => { + if (!open) return + + const controller = new AbortController() + void loadCategories(controller.signal) + return () => controller.abort() + }, [open]) + + useEffect(() => { + if (!mounted || closing) return + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape' && !isSubmitting) onClose() + } + document.addEventListener('keydown', onKey) + return () => document.removeEventListener('keydown', onKey) + }, [mounted, closing, isSubmitting, onClose]) + + async function loadCategories(signal?: AbortSignal) { + setIsLoadingCategories(true) + setError('') + + try { + const items = await listProductCategories(signal) + setCategories(items.map(mapProductCategoryToUi)) + } catch (err) { + if (err instanceof DOMException && err.name === 'AbortError') return + if (err instanceof ApiError) { + setError(err.message) + } else { + setError('Unable to load categories.') + } + } finally { + setIsLoadingCategories(false) + } + } + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + if (!categoryId || !name.trim()) return + + setIsSubmitting(true) + setError('') + + try { + const result = await createProductByAi({ + categoryId, + name: name.trim(), + language, + }) + showToast(result.message, 'success') + onCreated(result.product.id) + onClose() + } catch (err) { + if (err instanceof ApiError) { + setError(err.message) + } else if (err instanceof Error) { + setError(err.message) + } else { + setError('Unable to create product with AI.') + } + } finally { + setIsSubmitting(false) + } + } + + if (!mounted) return null + + return ( +
e.target === e.currentTarget && !isSubmitting && onClose()} + role="presentation" + > +
+
+
+

+ Add Product by AI +

+

+ AI will generate product content and technical data. You can add images and + variations afterward. +

+
+ +
+ +
void handleSubmit(e)}> +
+ + +
+ +
+ + setName(e.target.value)} + placeholder={ + language === 'fa' ? 'نام محصول را وارد کنید' : 'Enter product name' + } + dir={language === 'fa' ? 'rtl' : 'ltr'} + className={language === 'fa' ? 'faText' : undefined} + disabled={isSubmitting} + required + /> +
+ +
+ + +
+ + {error &&

{error}

} + +
+ + +
+
+
+
+ ) +} diff --git a/apps/business/src/components/AddVariationModal.tsx b/apps/business/src/components/AddVariationModal.tsx new file mode 100644 index 0000000..19323e4 --- /dev/null +++ b/apps/business/src/components/AddVariationModal.tsx @@ -0,0 +1,248 @@ +import { useEffect, useRef, useState } from 'react' +import { Plus, X } from 'lucide-react' +import type { Variation, VariationType } from '../types/variation' +import { COLOR_OPTIONS, SIZE_OPTIONS } from '../types/variation' +import styles from './VariationsModal.module.css' + +interface AddVariationModalProps { + open: boolean + onClose: () => void + onSubmit: (variation: Omit) => void +} + +const ANIMATION_MS = 220 + +export function AddVariationModal({ open, onClose, onSubmit }: AddVariationModalProps) { + const [mounted, setMounted] = useState(open) + const [closing, setClosing] = useState(false) + const [type, setType] = useState('color') + const [customName, setCustomName] = useState('') + const [selectedColors, setSelectedColors] = useState([]) + const [selectedSizes, setSelectedSizes] = useState([]) + const [customValues, setCustomValues] = useState(['']) + const valueInputRefs = useRef<(HTMLInputElement | null)[]>([]) + const pendingFocusIndex = useRef(null) + + useEffect(() => { + if (pendingFocusIndex.current === null) return + valueInputRefs.current[pendingFocusIndex.current]?.focus() + pendingFocusIndex.current = null + }, [customValues.length]) + + useEffect(() => { + if (open) { + setMounted(true) + setClosing(false) + setType('color') + setCustomName('') + setSelectedColors([]) + setSelectedSizes([]) + setCustomValues(['']) + } else if (mounted) { + setClosing(true) + const timer = setTimeout(() => { + setMounted(false) + setClosing(false) + }, ANIMATION_MS) + return () => clearTimeout(timer) + } + }, [open, mounted]) + + useEffect(() => { + if (!mounted || closing) return + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + return () => document.removeEventListener('keydown', onKey) + }, [mounted, closing, onClose]) + + if (!mounted) return null + + function toggleOption(value: string, selected: string[], setSelected: (v: string[]) => void) { + setSelected( + selected.includes(value) + ? selected.filter((v) => v !== value) + : [...selected, value], + ) + } + + function updateCustomValue(index: number, value: string) { + setCustomValues((prev) => prev.map((v, i) => (i === index ? value : v))) + } + + function addCustomRow() { + setCustomValues((prev) => [...prev, '']) + } + + function removeCustomRow(index: number) { + setCustomValues((prev) => prev.filter((_, i) => i !== index)) + } + + function handleValueKeyDown(index: number, e: React.KeyboardEvent) { + if (e.key !== 'Enter') return + e.preventDefault() + + const nextIndex = index + 1 + if (nextIndex < customValues.length) { + valueInputRefs.current[nextIndex]?.focus() + return + } + + pendingFocusIndex.current = nextIndex + setCustomValues((prev) => [...prev, '']) + } + + function handleSubmit(e: React.FormEvent) { + e.preventDefault() + + if (type === 'color') { + onSubmit({ name: 'Color', type: 'color', values: selectedColors }) + } else if (type === 'size') { + onSubmit({ name: 'Size', type: 'size', values: selectedSizes }) + } else { + const name = customName.trim() || 'Custom' + const values = customValues.map((v) => v.trim()).filter(Boolean) + onSubmit({ name, type: 'custom', values }) + } + } + + const canSubmit = + type === 'color' + ? selectedColors.length > 0 + : type === 'size' + ? selectedSizes.length > 0 + : customValues.some((v) => v.trim()) + + return ( +
+
e.stopPropagation()} + role="dialog" + aria-modal="true" + aria-labelledby="add-variation-title" + > +
+

+ Add Variation +

+ +
+ +
+
+ + +
+ + {type === 'color' && ( +
+ +
+ {COLOR_OPTIONS.map((color) => ( + + ))} +
+
+ )} + + {type === 'size' && ( +
+ +
+ {SIZE_OPTIONS.map((size) => ( + + ))} +
+
+ )} + + {type === 'custom' && ( + <> +
+ + setCustomName(e.target.value)} + /> +
+
+ +
+ {customValues.map((value, index) => ( +
+ { + valueInputRefs.current[index] = el + }} + type="text" + placeholder={`Value ${index + 1}`} + value={value} + onChange={(e) => updateCustomValue(index, e.target.value)} + onKeyDown={(e) => handleValueKeyDown(index, e)} + /> + {customValues.length > 1 && ( + + )} +
+ ))} + +
+
+ + )} + +
+ + +
+
+
+
+ ) +} diff --git a/apps/business/src/components/AddWebsiteSliderSlideModal.module.css b/apps/business/src/components/AddWebsiteSliderSlideModal.module.css new file mode 100644 index 0000000..4d84af2 --- /dev/null +++ b/apps/business/src/components/AddWebsiteSliderSlideModal.module.css @@ -0,0 +1,3 @@ +.modalWide { + max-width: 560px; +} diff --git a/apps/business/src/components/AddWebsiteSliderSlideModal.tsx b/apps/business/src/components/AddWebsiteSliderSlideModal.tsx new file mode 100644 index 0000000..d1761d3 --- /dev/null +++ b/apps/business/src/components/AddWebsiteSliderSlideModal.tsx @@ -0,0 +1,153 @@ +import { useEffect, useRef, useState } from 'react' +import { X } from 'lucide-react' +import { ImageCropper } from './ImageCropper' +import modalStyles from './CategoryModal.module.css' +import styles from './AddWebsiteSliderSlideModal.module.css' + +export interface WebsiteSliderSlideFormData { + image: string | null + title: string + linkUrl: string +} + +interface AddWebsiteSliderSlideModalProps { + open: boolean + onClose: () => void + onSubmit: (data: WebsiteSliderSlideFormData) => void + isSubmitting?: boolean +} + +const ANIMATION_MS = 220 + +export function AddWebsiteSliderSlideModal({ + open, + onClose, + onSubmit, + isSubmitting = false, +}: AddWebsiteSliderSlideModalProps) { + const formRef = useRef(null) + const [mounted, setMounted] = useState(open) + const [closing, setClosing] = useState(false) + const [image, setImage] = useState(null) + const [title, setTitle] = useState('') + const [linkUrl, setLinkUrl] = useState('') + + useEffect(() => { + if (open) { + setMounted(true) + setClosing(false) + setImage(null) + setTitle('') + setLinkUrl('') + } else if (mounted) { + setClosing(true) + const timer = setTimeout(() => { + setMounted(false) + setClosing(false) + }, ANIMATION_MS) + return () => clearTimeout(timer) + } + }, [open, mounted]) + + useEffect(() => { + if (!mounted || closing) return + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + return () => document.removeEventListener('keydown', onKey) + }, [mounted, closing, onClose]) + + if (!mounted) return null + + function handleSubmit(e: React.FormEvent) { + e.preventDefault() + if (!image) return + onSubmit({ + image, + title: title.trim(), + linkUrl: linkUrl.trim(), + }) + } + + return ( +
e.target === e.currentTarget && onClose()} + role="presentation" + > +
+
+

+ Add slide +

+ +
+ +
+
+ + +
+ +
+ + setTitle(e.target.value)} + disabled={isSubmitting} + /> +
+ +
+ + setLinkUrl(e.target.value)} + disabled={isSubmitting} + /> +
+ +
+ + +
+
+
+
+ ) +} diff --git a/apps/business/src/components/BlogCard.module.css b/apps/business/src/components/BlogCard.module.css new file mode 100644 index 0000000..7cbea6a --- /dev/null +++ b/apps/business/src/components/BlogCard.module.css @@ -0,0 +1,145 @@ +.card { + display: flex; + flex-direction: column; + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--glass-border); + border-radius: var(--radius); + box-shadow: var(--glass-shadow); + transition: transform 0.2s, box-shadow 0.2s; + overflow: hidden; +} + +.card:hover { + transform: translateY(-3px); + box-shadow: 0 12px 32px rgba(31, 38, 135, 0.1); +} + +.clickable { + display: flex; + flex-direction: column; + flex: 1; + text-decoration: none; + color: inherit; + cursor: pointer; +} + +.clickable:hover .title { + color: var(--primary); +} + +.imageWrap { + position: relative; + width: 100%; + aspect-ratio: 3 / 2; + overflow: hidden; + background: rgba(148, 163, 184, 0.08); + border-bottom: 1px solid rgba(148, 163, 184, 0.12); +} + +.image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.imagePlaceholder { + width: 100%; + height: 100%; + background: linear-gradient( + 135deg, + rgba(148, 163, 184, 0.12) 0%, + rgba(148, 163, 184, 0.04) 100% + ); +} + +.statusBadge { + position: absolute; + top: 8px; + left: 8px; + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.03em; + text-transform: uppercase; + border-radius: 50px; + border: 1px solid var(--glass-border); + background: rgba(255, 255, 255, 0.82); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + box-shadow: 0 2px 10px rgba(15, 23, 42, 0.12); + z-index: 1; +} + +.waitingBadge { + color: #c2410c; +} + +.verifiedBadge { + color: #15803d; +} + +.body { + display: flex; + flex-direction: column; + gap: 8px; + padding: 14px 16px 16px; +} + +.title { + font-size: 15px; + font-weight: 600; + line-height: 1.4; + color: var(--text-primary); + transition: color 0.2s; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.abstract { + margin: 0; + font-size: 13px; + line-height: 1.5; + color: var(--text-secondary); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.meta { + margin: 0; + font-size: 12px; + line-height: 1.4; + color: var(--text-muted); +} + +.metaDot { + margin: 0 6px; +} + +.verifyActive { + color: #15803d !important; + background: rgba(34, 197, 94, 0.12) !important; +} + +.verifyActive:hover { + background: rgba(34, 197, 94, 0.2) !important; + color: #15803d !important; +} + +.verifyWaiting { + color: #c2410c !important; + background: rgba(249, 115, 22, 0.12) !important; +} + +.verifyWaiting:hover { + background: rgba(249, 115, 22, 0.2) !important; + color: #c2410c !important; +} diff --git a/apps/business/src/components/BlogCard.tsx b/apps/business/src/components/BlogCard.tsx new file mode 100644 index 0000000..a98e36a --- /dev/null +++ b/apps/business/src/components/BlogCard.tsx @@ -0,0 +1,120 @@ +import { Link } from 'react-router-dom' +import { Pencil, MessageSquare, Trash2, BadgeCheck, Clock } from 'lucide-react' +import type { Blog } from '../types/blog' +import { + formatBlogAuthor, + formatBlogCardDate, + isBlogVerified, +} from '../services/blogService' +import { Tooltip } from './Tooltip' +import cardStyles from './BlogCard.module.css' +import controlStyles from './ProductCard.module.css' + +interface BlogCardProps { + blog: Blog + commentCount: number + onEdit: (id: string) => void + onComments: (id: string) => void + onToggleVerify: (id: string) => void + onRemove: (id: string) => void + isVerifying?: boolean +} + +export function BlogCard({ + blog, + commentCount, + onEdit, + onComments, + onToggleVerify, + onRemove, + isVerifying = false, +}: BlogCardProps) { + const verified = isBlogVerified(blog) + const publishDate = formatBlogCardDate(blog.publishedAt ?? blog.createdAt) + + return ( +
+ +
+ {blog.titleImageUrl ? ( + {blog.title} + ) : ( + + +
+

{blog.title}

+ {blog.abstract ? ( +

{blog.abstract}

+ ) : ( +

No summary yet.

+ )} +

+ {formatBlogAuthor(blog.author)} + · + {publishDate} +

+
+ + +
+ + + + + + + + + + + + +
+
+ ) +} diff --git a/apps/business/src/components/BlogCategoryRow.tsx b/apps/business/src/components/BlogCategoryRow.tsx new file mode 100644 index 0000000..528b190 --- /dev/null +++ b/apps/business/src/components/BlogCategoryRow.tsx @@ -0,0 +1,81 @@ +import { FolderPlus, Trash2, ChevronRight } from 'lucide-react' +import type { Category } from '../types/category' +import { Tooltip } from './Tooltip' +import styles from './CategoryRow.module.css' + +interface BlogCategoryRowProps { + category: Category + depth: number + hasChildren: boolean + expanded: boolean + onToggle: () => void + onAddSub: (id: string) => void + onRemove: (id: string) => void +} + +export function BlogCategoryRow({ + category, + depth, + hasChildren, + expanded, + onToggle, + onAddSub, + onRemove, +}: BlogCategoryRowProps) { + return ( +
+
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onToggle() + } + } + : undefined + } + role={hasChildren ? 'button' : undefined} + tabIndex={hasChildren ? 0 : undefined} + aria-expanded={hasChildren ? expanded : undefined} + > +
+
+ {category.nameEn} + · + {category.nameFa} + {hasChildren && ( + + + + )} +
+ {category.description &&

{category.description}

} +
+
+ +
+ + + + + + +
+
+ ) +} diff --git a/apps/business/src/components/BlogCategoryTree.tsx b/apps/business/src/components/BlogCategoryTree.tsx new file mode 100644 index 0000000..26cfadb --- /dev/null +++ b/apps/business/src/components/BlogCategoryTree.tsx @@ -0,0 +1,64 @@ +import type { Category } from '../types/category' +import { BlogCategoryRow } from './BlogCategoryRow' +import { getChildren, hasChildren } from '../utils/categories' +import styles from './CategoryTree.module.css' + +interface BlogCategoryTreeProps { + categories: Category[] + expandedIds: Set + onToggle: (id: string) => void + onAddSub: (id: string) => void + onRemove: (id: string) => void + parentId?: string | null + depth?: number +} + +export function BlogCategoryTree({ + categories, + expandedIds, + onToggle, + onAddSub, + onRemove, + parentId = null, + depth = 0, +}: BlogCategoryTreeProps) { + const nodes = getChildren(categories, parentId) + + return ( + <> + {nodes.map((category) => { + const childCount = hasChildren(categories, category.id) + const expanded = expandedIds.has(category.id) + + return ( +
+ onToggle(category.id)} + onAddSub={onAddSub} + onRemove={onRemove} + /> + {childCount && ( +
+
+ +
+
+ )} +
+ ) + })} + + ) +} diff --git a/apps/business/src/components/BlogCommentsModal.tsx b/apps/business/src/components/BlogCommentsModal.tsx new file mode 100644 index 0000000..9a9e946 --- /dev/null +++ b/apps/business/src/components/BlogCommentsModal.tsx @@ -0,0 +1,262 @@ +import { useEffect, useState } from 'react' +import { X, ThumbsUp, Check, XCircle, Trash2 } from 'lucide-react' +import type { ProductComment } from '../types/comment' +import { formatCommentDate } from '../data/productComments' +import { ApiError } from '../lib/api' +import { + COMMENTS_PER_PAGE, + deleteComment, + listBlogComments, + updateCommentApproval, +} from '../services/commentService' +import { Pagination } from './Pagination' +import styles from './ProductCommentsModal.module.css' + +interface BlogCommentsModalProps { + open: boolean + blogId: string + blogTitle: string + onClose: () => void + onCountChange?: (count: number) => void +} + +const ANIMATION_MS = 220 + +export function BlogCommentsModal({ + open, + blogId, + blogTitle, + onClose, + onCountChange, +}: BlogCommentsModalProps) { + const [mounted, setMounted] = useState(open) + const [closing, setClosing] = useState(false) + const [comments, setComments] = useState([]) + const [totalComments, setTotalComments] = useState(0) + const [currentPage, setCurrentPage] = useState(1) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState('') + const [actionId, setActionId] = useState(null) + + const totalPages = Math.max(1, Math.ceil(totalComments / COMMENTS_PER_PAGE)) + + useEffect(() => { + if (open) { + setMounted(true) + setClosing(false) + setCurrentPage(1) + } else if (mounted) { + setClosing(true) + const timer = setTimeout(() => { + setMounted(false) + setClosing(false) + }, ANIMATION_MS) + return () => clearTimeout(timer) + } + }, [open, mounted]) + + useEffect(() => { + if (!open || !blogId) return + + const controller = new AbortController() + void loadComments(currentPage, controller.signal) + return () => controller.abort() + }, [open, blogId, currentPage]) + + async function loadComments(page: number, signal?: AbortSignal) { + setIsLoading(true) + setError('') + + try { + const data = await listBlogComments(blogId, page, COMMENTS_PER_PAGE, signal) + setComments(data.items) + setTotalComments(data.total) + onCountChange?.(data.total) + } catch (err) { + if (err instanceof DOMException && err.name === 'AbortError') return + if (err instanceof ApiError) { + setError(err.message) + } else { + setError('Unable to load comments.') + } + } finally { + setIsLoading(false) + } + } + + useEffect(() => { + if (currentPage > totalPages) { + setCurrentPage(totalPages) + } + }, [currentPage, totalPages]) + + useEffect(() => { + if (!mounted || closing) return + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + return () => document.removeEventListener('keydown', onKey) + }, [mounted, closing, onClose]) + + if (!mounted) return null + + async function toggleApproval(comment: ProductComment) { + setActionId(comment.id) + setError('') + + try { + const updated = await updateCommentApproval(comment.id, !comment.approved) + setComments((prev) => prev.map((c) => (c.id === comment.id ? updated : c))) + } catch (err) { + if (err instanceof ApiError) { + setError(err.message) + } else { + setError('Unable to update comment approval.') + } + } finally { + setActionId(null) + } + } + + async function removeComment(commentId: string) { + setActionId(commentId) + setError('') + + try { + await deleteComment(commentId) + const nextTotal = totalComments - 1 + const nextTotalPages = Math.max(1, Math.ceil(nextTotal / COMMENTS_PER_PAGE)) + const nextPage = currentPage > nextTotalPages ? nextTotalPages : currentPage + setTotalComments(nextTotal) + onCountChange?.(nextTotal) + + if (nextPage !== currentPage) { + setCurrentPage(nextPage) + } else { + setComments((prev) => prev.filter((c) => c.id !== commentId)) + } + } catch (err) { + if (err instanceof ApiError) { + setError(err.message) + } else { + setError('Unable to delete comment.') + } + } finally { + setActionId(null) + } + } + + return ( +
+
e.stopPropagation()} + role="dialog" + aria-modal="true" + aria-labelledby="blog-comments-title" + > +
+
+

+ Comments +

+ {blogTitle && ( +

+ {blogTitle} · {totalComments}{' '} + {totalComments === 1 ? 'comment' : 'comments'} +

+ )} +
+ +
+ +
+ {error && ( +

+ {error} +

+ )} + + {isLoading ? ( +

Loading comments...

+ ) : totalComments === 0 ? ( +

No comments yet for this blog post.

+ ) : ( + <> +
+ {comments.map((comment) => ( +
+
+
+
{comment.author}
+
+ {formatCommentDate(comment.createdAt)} +
+
+ + + {comment.likesCount} + +
+

{comment.text}

+
+ {comment.approved ? ( + + ) : ( + + )} + +
+
+ ))} +
+ + {totalComments > COMMENTS_PER_PAGE && ( +
+ +
+ )} + + )} +
+
+
+ ) +} diff --git a/apps/business/src/components/BlogCommentsSection.module.css b/apps/business/src/components/BlogCommentsSection.module.css new file mode 100644 index 0000000..cca46b3 --- /dev/null +++ b/apps/business/src/components/BlogCommentsSection.module.css @@ -0,0 +1,157 @@ +.section { + background: var(--glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--glass-border); + border-radius: var(--radius); + box-shadow: var(--glass-shadow); + padding: 24px; +} + +.header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin-bottom: 16px; +} + +.title { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); +} + +.count { + font-size: 13px; + color: var(--text-secondary); +} + +.emptyText { + font-size: 14px; + color: var(--text-muted); + text-align: center; + padding: 24px 12px; +} + +.errorText { + font-size: 13px; + color: #dc2626; + margin-bottom: 12px; +} + +.list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.comment { + padding: 14px 16px; + background: rgba(255, 255, 255, 0.6); + border: 1px solid rgba(148, 163, 184, 0.2); + border-radius: var(--radius-sm); +} + +.commentApproved { + border-color: rgba(34, 197, 94, 0.35); + background: rgba(34, 197, 94, 0.04); +} + +.commentHeader { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + margin-bottom: 8px; +} + +.commentMeta { + min-width: 0; +} + +.author { + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} + +.dateTime { + font-size: 12px; + color: var(--text-muted); + margin-top: 2px; +} + +.likes { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--text-secondary); + flex-shrink: 0; +} + +.text { + font-size: 14px; + line-height: 1.55; + color: var(--text-primary); + white-space: pre-wrap; +} + +.commentActions { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; +} + +.approveBtn, +.rejectBtn, +.removeBtn { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 5px 10px; + font-size: 12px; + font-weight: 500; + border-radius: var(--radius-sm); + transition: background 0.2s, color 0.2s; +} + +.approveBtn { + color: #15803d; + background: rgba(34, 197, 94, 0.1); +} + +.approveBtn:hover:not(:disabled) { + background: rgba(34, 197, 94, 0.18); +} + +.rejectBtn { + color: #b45309; + background: rgba(251, 191, 36, 0.12); +} + +.rejectBtn:hover:not(:disabled) { + background: rgba(251, 191, 36, 0.2); +} + +.removeBtn { + color: #dc2626; + background: rgba(239, 68, 68, 0.08); +} + +.removeBtn:hover:not(:disabled) { + background: rgba(239, 68, 68, 0.14); +} + +.approveBtn:disabled, +.rejectBtn:disabled, +.removeBtn:disabled { + opacity: 0.55; + cursor: not-allowed; +} + +.paginationWrap { + margin-top: 16px; +} diff --git a/apps/business/src/components/BlogCommentsSection.tsx b/apps/business/src/components/BlogCommentsSection.tsx new file mode 100644 index 0000000..f52a9a6 --- /dev/null +++ b/apps/business/src/components/BlogCommentsSection.tsx @@ -0,0 +1,190 @@ +import { useEffect, useState } from 'react' +import { ThumbsUp, Check, XCircle, Trash2 } from 'lucide-react' +import type { ProductComment } from '../types/comment' +import { formatCommentDate } from '../data/productComments' +import { ApiError } from '../lib/api' +import { + COMMENTS_PER_PAGE, + deleteComment, + listBlogComments, + updateCommentApproval, +} from '../services/commentService' +import { Pagination } from './Pagination' +import styles from './BlogCommentsSection.module.css' + +interface BlogCommentsSectionProps { + blogId: string + onCountChange?: (count: number) => void +} + +export function BlogCommentsSection({ blogId, onCountChange }: BlogCommentsSectionProps) { + const [comments, setComments] = useState([]) + const [totalComments, setTotalComments] = useState(0) + const [currentPage, setCurrentPage] = useState(1) + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState('') + const [actionId, setActionId] = useState(null) + + const totalPages = Math.max(1, Math.ceil(totalComments / COMMENTS_PER_PAGE)) + + useEffect(() => { + const controller = new AbortController() + void loadComments(currentPage, controller.signal) + return () => controller.abort() + }, [blogId, currentPage]) + + async function loadComments(page: number, signal?: AbortSignal) { + setIsLoading(true) + setError('') + + try { + const data = await listBlogComments(blogId, page, COMMENTS_PER_PAGE, signal) + setComments(data.items) + setTotalComments(data.total) + onCountChange?.(data.total) + } catch (err) { + if (err instanceof DOMException && err.name === 'AbortError') return + if (err instanceof ApiError) { + setError(err.message) + } else { + setError('Unable to load comments.') + } + } finally { + setIsLoading(false) + } + } + + async function toggleApproval(comment: ProductComment) { + setActionId(comment.id) + setError('') + + try { + const updated = await updateCommentApproval(comment.id, !comment.approved) + setComments((prev) => prev.map((c) => (c.id === comment.id ? updated : c))) + } catch (err) { + if (err instanceof ApiError) { + setError(err.message) + } else { + setError('Unable to update comment approval.') + } + } finally { + setActionId(null) + } + } + + async function removeComment(commentId: string) { + setActionId(commentId) + setError('') + + try { + await deleteComment(commentId) + const nextTotal = totalComments - 1 + const nextTotalPages = Math.max(1, Math.ceil(nextTotal / COMMENTS_PER_PAGE)) + const nextPage = currentPage > nextTotalPages ? nextTotalPages : currentPage + setTotalComments(nextTotal) + onCountChange?.(nextTotal) + + if (nextPage !== currentPage) { + setCurrentPage(nextPage) + } else { + setComments((prev) => prev.filter((c) => c.id !== commentId)) + } + } catch (err) { + if (err instanceof ApiError) { + setError(err.message) + } else { + setError('Unable to delete comment.') + } + } finally { + setActionId(null) + } + } + + return ( +
+
+

Comments

+ + {totalComments} {totalComments === 1 ? 'comment' : 'comments'} + +
+ + {error && ( +

+ {error} +

+ )} + + {isLoading ? ( +

Loading comments...

+ ) : totalComments === 0 ? ( +

No comments yet for this blog post.

+ ) : ( + <> +
+ {comments.map((comment) => ( +
+
+
+
{comment.author}
+
{formatCommentDate(comment.createdAt)}
+
+ + + {comment.likesCount} + +
+

{comment.text}

+
+ {comment.approved ? ( + + ) : ( + + )} + +
+
+ ))} +
+ + {totalComments > COMMENTS_PER_PAGE && ( +
+ +
+ )} + + )} +
+ ) +} diff --git a/apps/business/src/components/BrandModal.module.css b/apps/business/src/components/BrandModal.module.css new file mode 100644 index 0000000..d89a231 --- /dev/null +++ b/apps/business/src/components/BrandModal.module.css @@ -0,0 +1,184 @@ +.overlay { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.25); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + display: flex; + align-items: center; + justify-content: center; + z-index: 200; + padding: 20px; +} + +.modal { + width: 100%; + max-width: 520px; + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border: 1px solid var(--glass-border); + border-radius: var(--radius); + box-shadow: 0 20px 60px rgba(31, 38, 135, 0.15); +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24px 24px 0; +} + +.title { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); +} + +.closeBtn { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: var(--text-secondary); + transition: background 0.2s, color 0.2s; +} + +.closeBtn:hover { + background: rgba(var(--primary-rgb) / 0.1); + color: var(--primary); +} + +.form { + padding: 20px 24px 24px; + display: flex; + flex-direction: column; + gap: 14px; +} + +.field { + display: flex; + flex-direction: column; + gap: 4px; +} + +.field label { + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); +} + +.field input, +.field textarea { + padding: var(--field-padding-y) var(--field-padding-x); + font-size: var(--field-font-size); + line-height: 1.4; + color: var(--text-primary); + background: rgba(255, 255, 255, 0.7); + border: 1px solid rgba(148, 163, 184, 0.35); + border-radius: var(--radius-sm); + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; + box-sizing: border-box; +} + +.field input { + min-height: var(--field-height); +} + +.field input:focus, +.field textarea:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(var(--primary-rgb) / 0.12); +} + +.field textarea { + resize: vertical; + min-height: calc(var(--field-height) + 24px); +} + +.actions { + display: flex; + justify-content: flex-end; + gap: 10px; + padding-top: 4px; +} + +.cancelBtn { + padding: 8px 16px; + font-size: 13px; + font-weight: 500; + color: var(--text-secondary); + border-radius: var(--radius-sm); + transition: background 0.2s; +} + +.cancelBtn:hover { + background: rgba(148, 163, 184, 0.15); +} + +.submitBtn { + padding: 8px 18px; + font-size: 13px; + font-weight: 600; + color: white; + background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); + border-radius: var(--radius-sm); + box-shadow: 0 4px 12px rgba(var(--primary-rgb) / 0.3); + transition: transform 0.2s, box-shadow 0.2s; +} + +.submitBtn:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(var(--primary-rgb) / 0.35); +} + +.overlayIn { + animation: overlayFadeIn 0.22s ease forwards; +} + +.overlayOut { + animation: overlayFadeOut 0.22s ease forwards; +} + +.modalIn { + animation: modalFadeIn 0.25s ease forwards; +} + +.modalOut { + animation: modalFadeOut 0.22s ease forwards; +} + +@keyframes overlayFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes overlayFadeOut { + from { opacity: 1; } + to { opacity: 0; } +} + +@keyframes modalFadeIn { + from { + opacity: 0; + transform: translateY(12px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +@keyframes modalFadeOut { + from { + opacity: 1; + transform: translateY(0) scale(1); + } + to { + opacity: 0; + transform: translateY(8px) scale(0.98); + } +} diff --git a/apps/business/src/components/BrandModal.tsx b/apps/business/src/components/BrandModal.tsx new file mode 100644 index 0000000..54a4e2e --- /dev/null +++ b/apps/business/src/components/BrandModal.tsx @@ -0,0 +1,171 @@ +import { useEffect, useRef, useState } from 'react' +import { X } from 'lucide-react' +import { ImageCropper } from './ImageCropper' +import type { Brand, BrandFormData } from '../types/brand' +import styles from './BrandModal.module.css' + +interface BrandModalProps { + open: boolean + onClose: () => void + onSubmit: (data: BrandFormData) => void + editingBrand?: Brand | null + title?: string + isSubmitting?: boolean +} + +const ANIMATION_MS = 220 + +export function BrandModal({ + open, + onClose, + onSubmit, + editingBrand = null, + title = 'Add Brand', + isSubmitting = false, +}: BrandModalProps) { + const formRef = useRef(null) + const [mounted, setMounted] = useState(open) + const [closing, setClosing] = useState(false) + const [nameEn, setNameEn] = useState('') + const [nameFa, setNameFa] = useState('') + const [about, setAbout] = useState('') + const [image, setImage] = useState(null) + const [imageMediaId, setImageMediaId] = useState(null) + + useEffect(() => { + if (open) { + setMounted(true) + setClosing(false) + setNameEn(editingBrand?.nameEn ?? '') + setNameFa(editingBrand?.nameFa ?? '') + setAbout(editingBrand?.about ?? '') + setImage(editingBrand?.imageUrl ?? null) + setImageMediaId(editingBrand?.imageMediaId ?? null) + } else if (mounted) { + setClosing(true) + const timer = setTimeout(() => { + setMounted(false) + setClosing(false) + }, ANIMATION_MS) + return () => clearTimeout(timer) + } + }, [open, editingBrand, mounted]) + + useEffect(() => { + if (!mounted || closing) return + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + document.addEventListener('keydown', onKey) + return () => document.removeEventListener('keydown', onKey) + }, [mounted, closing, onClose]) + + if (!mounted) return null + + function handleSubmit(e: React.FormEvent) { + e.preventDefault() + onSubmit({ + nameFa: nameFa.trim(), + nameEn: nameEn.trim(), + about: about.trim(), + image, + imageMediaId, + }) + } + + return ( +
+
e.stopPropagation()} + role="dialog" + aria-modal="true" + aria-labelledby="brand-modal-title" + > +
+

+ {title} +

+ +
+ +
+
+ + +
+ +
+ + setNameFa(e.target.value)} + disabled={isSubmitting} + /> +
+ +
+ + setNameEn(e.target.value)} + required + disabled={isSubmitting} + /> +
+ +
+ +