diff --git a/apps/business/src/App.tsx b/apps/business/src/App.tsx index d02a813..7187b5c 100644 --- a/apps/business/src/App.tsx +++ b/apps/business/src/App.tsx @@ -79,11 +79,11 @@ function App() { return ( + - }> @@ -170,10 +170,10 @@ function App() { - + ) diff --git a/apps/business/src/components/BackgroundOrbs.module.css b/apps/business/src/components/BackgroundOrbs.module.css index 04d33bd..157ded3 100644 --- a/apps/business/src/components/BackgroundOrbs.module.css +++ b/apps/business/src/components/BackgroundOrbs.module.css @@ -132,3 +132,7 @@ animation: none; } } + +:global(html[data-theme='dark']) .bgOrbs { + display: none; +} diff --git a/apps/business/src/components/BlogCard.module.css b/apps/business/src/components/BlogCard.module.css index a743a45..2781332 100644 --- a/apps/business/src/components/BlogCard.module.css +++ b/apps/business/src/components/BlogCard.module.css @@ -67,7 +67,7 @@ text-transform: uppercase; border-radius: 50px; border: 1px solid var(--glass-border); - background: rgba(255, 255, 255, 0.82); + background: var(--surface); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); box-shadow: 0 2px 10px rgba(15, 23, 42, 0.12); diff --git a/apps/business/src/components/BlogCommentsSection.module.css b/apps/business/src/components/BlogCommentsSection.module.css index cca46b3..8b35975 100644 --- a/apps/business/src/components/BlogCommentsSection.module.css +++ b/apps/business/src/components/BlogCommentsSection.module.css @@ -48,7 +48,7 @@ .comment { padding: 14px 16px; - background: rgba(255, 255, 255, 0.6); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.2); border-radius: var(--radius-sm); } diff --git a/apps/business/src/components/BrandModal.module.css b/apps/business/src/components/BrandModal.module.css index 27bff75..92f5fc5 100644 --- a/apps/business/src/components/BrandModal.module.css +++ b/apps/business/src/components/BrandModal.module.css @@ -14,7 +14,7 @@ .modal { width: 100%; max-width: 520px; - background: rgba(255, 255, 255, 0.85); + background: var(--surface); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border: 1px solid var(--glass-border); @@ -76,7 +76,7 @@ font-size: var(--field-font-size); line-height: 1.4; color: var(--text-primary); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; diff --git a/apps/business/src/components/BrandRow.module.css b/apps/business/src/components/BrandRow.module.css index 80991e0..f76aece 100644 --- a/apps/business/src/components/BrandRow.module.css +++ b/apps/business/src/components/BrandRow.module.css @@ -4,7 +4,7 @@ flex-shrink: 0; object-fit: contain; border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.6); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.2); } diff --git a/apps/business/src/components/CategoryModal.module.css b/apps/business/src/components/CategoryModal.module.css index 3e7b432..80e9345 100644 --- a/apps/business/src/components/CategoryModal.module.css +++ b/apps/business/src/components/CategoryModal.module.css @@ -45,7 +45,7 @@ .modal { width: 100%; max-width: 480px; - background: rgba(255, 255, 255, 0.92); + background: var(--elevated-surface); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border: 1px solid var(--glass-border); @@ -114,7 +114,7 @@ font-size: var(--field-font-size); line-height: 1.4; color: var(--text-primary); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; @@ -135,7 +135,7 @@ color: var(--text-primary); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right var(--select-arrow-offset) center; diff --git a/apps/business/src/components/CategoryTreeSelect.module.css b/apps/business/src/components/CategoryTreeSelect.module.css index 71c3f88..c5a0ec9 100644 --- a/apps/business/src/components/CategoryTreeSelect.module.css +++ b/apps/business/src/components/CategoryTreeSelect.module.css @@ -11,7 +11,7 @@ gap: 6px; min-height: var(--field-height); padding: 0 var(--field-padding-x); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; @@ -84,7 +84,7 @@ overflow-y: auto; margin-top: 0; padding: 4px; - background: rgba(255, 255, 255, 0.98); + background: var(--elevated-surface); border: 1px solid var(--glass-border); border-radius: var(--radius-sm); box-shadow: 0 10px 28px rgba(31, 38, 135, 0.14); diff --git a/apps/business/src/components/ChangeUserAccessModal.module.css b/apps/business/src/components/ChangeUserAccessModal.module.css index 944d3ae..9eea659 100644 --- a/apps/business/src/components/ChangeUserAccessModal.module.css +++ b/apps/business/src/components/ChangeUserAccessModal.module.css @@ -11,7 +11,7 @@ padding: 10px 12px; border-radius: 10px; border: 1px solid rgba(15, 23, 42, 0.1); - background: rgba(255, 255, 255, 0.45); + background: var(--surface); cursor: pointer; font-family: var(--font-ui); } diff --git a/apps/business/src/components/ConfirmBulkAddStoreItemsModal.module.css b/apps/business/src/components/ConfirmBulkAddStoreItemsModal.module.css index 26b0259..58e493b 100644 --- a/apps/business/src/components/ConfirmBulkAddStoreItemsModal.module.css +++ b/apps/business/src/components/ConfirmBulkAddStoreItemsModal.module.css @@ -17,7 +17,7 @@ max-width: 440px; padding: 32px 28px 24px; text-align: start; - background: rgba(255, 255, 255, 0.94); + background: var(--elevated-surface); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border: 1px solid rgba(245, 158, 11, 0.28); diff --git a/apps/business/src/components/ConfirmDeleteModal.module.css b/apps/business/src/components/ConfirmDeleteModal.module.css index 2110a15..3ac8366 100644 --- a/apps/business/src/components/ConfirmDeleteModal.module.css +++ b/apps/business/src/components/ConfirmDeleteModal.module.css @@ -17,7 +17,7 @@ max-width: 400px; padding: 32px 28px 24px; text-align: center; - background: rgba(255, 255, 255, 0.92); + background: var(--elevated-surface); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border: 1px solid rgba(239, 68, 68, 0.25); diff --git a/apps/business/src/components/CreateStoreItemsModal.module.css b/apps/business/src/components/CreateStoreItemsModal.module.css index dd96797..c91f6e5 100644 --- a/apps/business/src/components/CreateStoreItemsModal.module.css +++ b/apps/business/src/components/CreateStoreItemsModal.module.css @@ -10,7 +10,7 @@ gap: 8px; align-items: center; padding: 10px 12px; - background: rgba(255, 255, 255, 0.6); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.25); border-radius: var(--radius-sm); } @@ -39,7 +39,7 @@ line-height: 1.4; font-family: inherit; color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; @@ -235,7 +235,7 @@ .discountRow { padding: 10px 12px; - background: rgba(255, 255, 255, 0.6); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.25); border-radius: var(--radius-sm); } diff --git a/apps/business/src/components/DailyActivityChart.module.css b/apps/business/src/components/DailyActivityChart.module.css index df9cbc9..cc39e62 100644 --- a/apps/business/src/components/DailyActivityChart.module.css +++ b/apps/business/src/components/DailyActivityChart.module.css @@ -218,7 +218,7 @@ opacity: 0; visibility: hidden; transition: opacity 0.12s ease, visibility 0.12s; - background: rgba(255, 255, 255, 0.98); + background: var(--elevated-surface); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid var(--glass-border); diff --git a/apps/business/src/components/GenerateWebsiteLinkModal.module.css b/apps/business/src/components/GenerateWebsiteLinkModal.module.css index ce003f9..2351dff 100644 --- a/apps/business/src/components/GenerateWebsiteLinkModal.module.css +++ b/apps/business/src/components/GenerateWebsiteLinkModal.module.css @@ -45,7 +45,7 @@ font-size: 12px; font-family: var(--font-ui); color: var(--text-secondary); - background: rgba(255, 255, 255, 0.45); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.28); border-radius: var(--radius-sm); word-break: break-all; diff --git a/apps/business/src/components/Header.module.css b/apps/business/src/components/Header.module.css index ae43abc..3fe5308 100644 --- a/apps/business/src/components/Header.module.css +++ b/apps/business/src/components/Header.module.css @@ -89,7 +89,7 @@ gap: 8px; padding: 8px 14px; border-radius: 50px; - background: rgba(255, 255, 255, 0.5); + background: var(--surface); border: 1px solid var(--glass-border); cursor: pointer; transition: box-shadow 0.2s, border-color 0.2s; @@ -143,7 +143,7 @@ inset-inline-end: 0; min-width: 180px; padding: 6px; - background: rgba(255, 255, 255, 0.95); + background: var(--elevated-surface); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid var(--glass-border); diff --git a/apps/business/src/components/ImageCropper.module.css b/apps/business/src/components/ImageCropper.module.css index ff92d90..f14cc13 100644 --- a/apps/business/src/components/ImageCropper.module.css +++ b/apps/business/src/components/ImageCropper.module.css @@ -15,7 +15,7 @@ min-height: 0; border: 2px dashed rgba(148, 163, 184, 0.4); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.4); + background: var(--surface); color: var(--text-secondary); font-size: 14px; cursor: pointer; @@ -89,7 +89,7 @@ border: 1px solid var(--glass-border); border-radius: var(--radius-sm); overflow: hidden; - background: rgba(255, 255, 255, 0.5); + background: var(--surface); } .cropArea { diff --git a/apps/business/src/components/ImageLightbox.module.css b/apps/business/src/components/ImageLightbox.module.css index d94e0a7..3dea762 100644 --- a/apps/business/src/components/ImageLightbox.module.css +++ b/apps/business/src/components/ImageLightbox.module.css @@ -55,7 +55,7 @@ max-height: calc(100vh - 180px); object-fit: contain; border-radius: 12px; - background: #ffffff; + background: var(--card-media-bg); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); } @@ -107,7 +107,7 @@ border-radius: 8px; overflow: hidden; border: 2px solid transparent; - background: #fff; + background: var(--card-media-bg); opacity: 0.72; transition: opacity 0.2s, border-color 0.2s, transform 0.2s; } diff --git a/apps/business/src/components/ImageUploader.module.css b/apps/business/src/components/ImageUploader.module.css index a54174d..11bf5ca 100644 --- a/apps/business/src/components/ImageUploader.module.css +++ b/apps/business/src/components/ImageUploader.module.css @@ -15,7 +15,7 @@ border-radius: var(--radius-sm); overflow: hidden; border: 1px solid var(--glass-border); - background: #fff; + background: var(--card-media-bg); display: flex; align-items: center; justify-content: center; @@ -58,7 +58,7 @@ gap: 6px; border: 2px dashed rgba(148, 163, 184, 0.4); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.4); + background: var(--surface); color: var(--text-secondary); font-size: 12px; transition: border-color 0.2s, color 0.2s, background 0.2s; diff --git a/apps/business/src/components/InvoiceDraftFields.module.css b/apps/business/src/components/InvoiceDraftFields.module.css index 30d917e..97ba33a 100644 --- a/apps/business/src/components/InvoiceDraftFields.module.css +++ b/apps/business/src/components/InvoiceDraftFields.module.css @@ -12,7 +12,7 @@ padding: 12px; border: 1px solid rgba(148, 163, 184, 0.28); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.45); + background: var(--surface); } .itemCardHeader { @@ -100,7 +100,7 @@ font-size: 12px; font-family: var(--font-ui); color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); appearance: none; diff --git a/apps/business/src/components/InvoiceForm.module.css b/apps/business/src/components/InvoiceForm.module.css index b9d72c1..fac9a23 100644 --- a/apps/business/src/components/InvoiceForm.module.css +++ b/apps/business/src/components/InvoiceForm.module.css @@ -22,7 +22,7 @@ line-height: 1.4; font-family: var(--font-ui); color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; diff --git a/apps/business/src/components/JalaliDatePicker.module.css b/apps/business/src/components/JalaliDatePicker.module.css index b768dad..c09bd2f 100644 --- a/apps/business/src/components/JalaliDatePicker.module.css +++ b/apps/business/src/components/JalaliDatePicker.module.css @@ -8,7 +8,7 @@ padding: var(--field-padding-y) var(--field-padding-x); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.85); + background: var(--surface); font-size: var(--field-font-size); font-family: var(--font-ui); color: var(--text-primary); @@ -39,7 +39,7 @@ .calendar :global(.rmdp-wrapper) { border: 1px solid var(--glass-border); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.98); + background: var(--elevated-surface); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); } diff --git a/apps/business/src/components/ListFiltersPanel.module.css b/apps/business/src/components/ListFiltersPanel.module.css index 7176f09..0cb9f0e 100644 --- a/apps/business/src/components/ListFiltersPanel.module.css +++ b/apps/business/src/components/ListFiltersPanel.module.css @@ -104,7 +104,7 @@ padding: var(--field-padding-y) var(--field-padding-x); font-size: var(--field-font-size); color: var(--text-primary); - background: rgba(255, 255, 255, 0.65); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; @@ -117,7 +117,7 @@ padding: var(--field-padding-y) var(--select-padding-end) var(--field-padding-y) var(--field-padding-x); appearance: none; -webkit-appearance: none; - background-color: rgba(255, 255, 255, 0.65); + background-color: var(--surface); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right var(--select-arrow-offset) center; diff --git a/apps/business/src/components/Modal.module.css b/apps/business/src/components/Modal.module.css index bb6b330..e21d08a 100644 --- a/apps/business/src/components/Modal.module.css +++ b/apps/business/src/components/Modal.module.css @@ -17,7 +17,7 @@ max-height: calc(100vh - 40px); display: flex; flex-direction: column; - background: rgba(255, 255, 255, 0.9); + background: var(--elevated-surface); backdrop-filter: blur(28px); -webkit-backdrop-filter: blur(28px); border: 1px solid var(--glass-border); diff --git a/apps/business/src/components/MultiSelectDropdown.module.css b/apps/business/src/components/MultiSelectDropdown.module.css index 674ad2e..8250137 100644 --- a/apps/business/src/components/MultiSelectDropdown.module.css +++ b/apps/business/src/components/MultiSelectDropdown.module.css @@ -14,7 +14,7 @@ font-family: inherit; color: var(--text-primary); text-align: left; - background: rgba(255, 255, 255, 0.65); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; @@ -71,7 +71,7 @@ flex-direction: column; max-height: none; overflow: visible; - background: rgba(255, 255, 255, 0.95); + background: var(--elevated-surface); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid var(--glass-border); @@ -148,7 +148,7 @@ border: 1.5px solid rgba(148, 163, 184, 0.55); border-radius: 4px; color: white; - background: white; + background: var(--card-media-bg); } .checkSelected { diff --git a/apps/business/src/components/OrderItemsModal.module.css b/apps/business/src/components/OrderItemsModal.module.css index ec263a8..6d288b7 100644 --- a/apps/business/src/components/OrderItemsModal.module.css +++ b/apps/business/src/components/OrderItemsModal.module.css @@ -48,7 +48,7 @@ padding: 10px 12px; border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.18); - background: rgba(255, 255, 255, 0.55); + background: var(--surface); } .itemThumb { @@ -57,7 +57,7 @@ flex-shrink: 0; border-radius: 8px; overflow: hidden; - background: #fff; + background: var(--card-media-bg); border: 1px solid rgba(148, 163, 184, 0.15); } diff --git a/apps/business/src/components/OrderStepModal.module.css b/apps/business/src/components/OrderStepModal.module.css index c797c73..d8fb243 100644 --- a/apps/business/src/components/OrderStepModal.module.css +++ b/apps/business/src/components/OrderStepModal.module.css @@ -14,7 +14,7 @@ text-align: start; border: 1px solid rgba(148, 163, 184, 0.3); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.45); + background: var(--surface); transition: border-color 0.2s, background 0.2s, box-shadow 0.2s; } diff --git a/apps/business/src/components/OrderTransactionsModal.module.css b/apps/business/src/components/OrderTransactionsModal.module.css index d3607ca..2d5c847 100644 --- a/apps/business/src/components/OrderTransactionsModal.module.css +++ b/apps/business/src/components/OrderTransactionsModal.module.css @@ -22,7 +22,7 @@ padding: 12px 14px; border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.18); - background: rgba(255, 255, 255, 0.55); + background: var(--surface); } .txHeader { diff --git a/apps/business/src/components/PickStoreVariantModal.module.css b/apps/business/src/components/PickStoreVariantModal.module.css index c1a7635..7702bdc 100644 --- a/apps/business/src/components/PickStoreVariantModal.module.css +++ b/apps/business/src/components/PickStoreVariantModal.module.css @@ -18,7 +18,7 @@ padding: 12px 14px; border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.2); - background: rgba(255, 255, 255, 0.65); + background: var(--surface); text-align: left; transition: border-color 0.2s, background 0.2s, transform 0.15s; } diff --git a/apps/business/src/components/ProductActivityChart.module.css b/apps/business/src/components/ProductActivityChart.module.css index caf21df..b8ba766 100644 --- a/apps/business/src/components/ProductActivityChart.module.css +++ b/apps/business/src/components/ProductActivityChart.module.css @@ -189,7 +189,7 @@ opacity: 0; visibility: hidden; transition: opacity 0.12s ease, visibility 0.12s; - background: rgba(255, 255, 255, 0.98); + background: var(--elevated-surface); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid var(--glass-border); diff --git a/apps/business/src/components/ProductCard.module.css b/apps/business/src/components/ProductCard.module.css index 3fff9d3..2292921 100644 --- a/apps/business/src/components/ProductCard.module.css +++ b/apps/business/src/components/ProductCard.module.css @@ -7,6 +7,7 @@ border: 1px solid var(--glass-border); border-radius: var(--radius); box-shadow: var(--glass-shadow); + overflow: hidden; transition: transform 0.2s, box-shadow 0.2s; } @@ -34,16 +35,15 @@ width: 100%; aspect-ratio: 1 / 1; overflow: hidden; - background: #ffffff; + background: var(--card-media-bg); border-bottom: 1px solid rgba(148, 163, 184, 0.12); - border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0; } .image { width: 100%; height: 100%; - object-fit: contain; - padding: 10px; + object-fit: cover; + display: block; } .imagePlaceholder { diff --git a/apps/business/src/components/ProductCommentsModal.module.css b/apps/business/src/components/ProductCommentsModal.module.css index 373bbdb..048ef17 100644 --- a/apps/business/src/components/ProductCommentsModal.module.css +++ b/apps/business/src/components/ProductCommentsModal.module.css @@ -17,7 +17,7 @@ max-height: 90vh; display: flex; flex-direction: column; - background: rgba(255, 255, 255, 0.92); + background: var(--elevated-surface); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border: 1px solid var(--glass-border); @@ -94,7 +94,7 @@ .comment { padding: 14px 16px; - background: rgba(255, 255, 255, 0.6); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.25); border-radius: var(--radius-sm); } diff --git a/apps/business/src/components/ProductDetailsTabs.module.css b/apps/business/src/components/ProductDetailsTabs.module.css index dab250c..d6fdfaf 100644 --- a/apps/business/src/components/ProductDetailsTabs.module.css +++ b/apps/business/src/components/ProductDetailsTabs.module.css @@ -37,7 +37,7 @@ .tabActive { color: var(--primary); - background: rgba(255, 255, 255, 0.75); + background: var(--surface); box-shadow: inset 0 -2px 0 var(--primary); } @@ -118,7 +118,7 @@ .comment, .review { padding: 14px 16px; - background: rgba(255, 255, 255, 0.65); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.2); border-radius: var(--radius-sm); } diff --git a/apps/business/src/components/SearchableSelect.module.css b/apps/business/src/components/SearchableSelect.module.css index 2c64464..0b560b3 100644 --- a/apps/business/src/components/SearchableSelect.module.css +++ b/apps/business/src/components/SearchableSelect.module.css @@ -8,7 +8,7 @@ gap: 6px; min-height: var(--field-height); padding: 0 var(--field-padding-x); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; @@ -74,7 +74,7 @@ list-style: none; margin: 0; padding: 6px; - background: rgba(255, 255, 255, 0.98); + background: var(--elevated-surface); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid var(--glass-border); diff --git a/apps/business/src/components/SectionCard.module.css b/apps/business/src/components/SectionCard.module.css index 9c62f5e..790b0b3 100644 --- a/apps/business/src/components/SectionCard.module.css +++ b/apps/business/src/components/SectionCard.module.css @@ -25,7 +25,8 @@ display: flex; align-items: center; justify-content: center; - background: linear-gradient(135deg, var(--primary-light) 0%, rgba(219, 234, 254, 0.5) 100%); + background: linear-gradient(135deg, var(--icon-bg) 0%, var(--icon-bg-end) 100%); + border: 1px solid rgba(var(--primary-rgb) / 0.22); border-radius: var(--radius-sm); color: var(--primary); margin-bottom: 20px; diff --git a/apps/business/src/components/ShoppingCartModal.module.css b/apps/business/src/components/ShoppingCartModal.module.css index 8d72543..0425d72 100644 --- a/apps/business/src/components/ShoppingCartModal.module.css +++ b/apps/business/src/components/ShoppingCartModal.module.css @@ -56,7 +56,7 @@ padding: 6px 10px; border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.18); - background: rgba(255, 255, 255, 0.55); + background: var(--surface); min-width: 0; } @@ -66,7 +66,7 @@ flex-shrink: 0; border-radius: 10px; overflow: hidden; - background: #fff; + background: var(--card-media-bg); border: 1px solid rgba(148, 163, 184, 0.15); } @@ -136,7 +136,7 @@ border: 1px solid rgba(148, 163, 184, 0.2); border-radius: 8px; padding: 1px; - background: rgba(255, 255, 255, 0.7); + background: var(--surface); } .qtyBtn { @@ -211,7 +211,7 @@ font-size: var(--field-font-size); border: 1px solid rgba(148, 163, 184, 0.25); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.75); + background: var(--surface); color: var(--text-primary); } @@ -258,7 +258,7 @@ list-style: none; border: 1px solid rgba(148, 163, 184, 0.2); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.85); + background: var(--surface); max-height: 180px; overflow-y: auto; overflow-x: hidden; @@ -356,7 +356,7 @@ padding: 12px 14px; border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.2); - background: rgba(255, 255, 255, 0.55); + background: var(--surface); font-size: 13px; color: var(--text-secondary); } @@ -450,7 +450,7 @@ padding: var(--field-padding-y) var(--field-padding-x); font-size: var(--field-font-size); color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.75); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; diff --git a/apps/business/src/components/Sidebar.module.css b/apps/business/src/components/Sidebar.module.css index 01977f6..fc584e8 100644 --- a/apps/business/src/components/Sidebar.module.css +++ b/apps/business/src/components/Sidebar.module.css @@ -35,7 +35,7 @@ .brandLogoUploaded { border-radius: 8px; object-fit: cover; - background: rgba(255, 255, 255, 0.75); + background: var(--card-media-bg); } .brandEditBtn { diff --git a/apps/business/src/components/Sidebar.tsx b/apps/business/src/components/Sidebar.tsx index 24c8fab..67a271c 100644 --- a/apps/business/src/components/Sidebar.tsx +++ b/apps/business/src/components/Sidebar.tsx @@ -91,10 +91,9 @@ export function Sidebar({ open = false }: SidebarProps) { const navigate = useNavigate() const { user, logout } = useAuth() const { locale } = useLocale() - const { enabledModules } = useTenantBranding() + const { enabledModules, logoUrl: brandLogoUrl } = useTenantBranding() const t = useT() const [openGroups, setOpenGroups] = useState>({}) - const [brandLogoUrl, setBrandLogoUrl] = useState(null) const [nameEn, setNameEn] = useState('') const [nameFa, setNameFa] = useState('') @@ -242,12 +241,10 @@ export function Sidebar({ open = false }: SidebarProps) { async function loadBranding() { try { const data = await getBusinessProfile(controller.signal) - setBrandLogoUrl(data.profile.logoUrl) setNameEn(data.profile.nameEn.trim()) setNameFa(data.profile.nameFa.trim()) } catch (err) { if (isAbortError(err)) return - setBrandLogoUrl(null) setNameEn('') setNameFa('') } diff --git a/apps/business/src/components/StoreItemCard.module.css b/apps/business/src/components/StoreItemCard.module.css index f8f93b5..7363d6a 100644 --- a/apps/business/src/components/StoreItemCard.module.css +++ b/apps/business/src/components/StoreItemCard.module.css @@ -38,7 +38,7 @@ width: 100%; aspect-ratio: 1 / 1; overflow: hidden; - background: #ffffff; + background: var(--card-media-bg); border-bottom: 1px solid rgba(148, 163, 184, 0.12); } @@ -73,7 +73,7 @@ letter-spacing: 0.03em; border-radius: 50px; border: 1px solid var(--glass-border); - background: rgba(255, 255, 255, 0.82); + background: var(--surface); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); box-shadow: 0 2px 10px rgba(15, 23, 42, 0.12); diff --git a/apps/business/src/components/StoreItemMultiSearchSelect.module.css b/apps/business/src/components/StoreItemMultiSearchSelect.module.css index c89b044..06fde6e 100644 --- a/apps/business/src/components/StoreItemMultiSearchSelect.module.css +++ b/apps/business/src/components/StoreItemMultiSearchSelect.module.css @@ -37,7 +37,7 @@ font-size: 11px; font-weight: 700; color: var(--primary); - background: #fff; + background: var(--card-media-bg); } .optionSelected .checkbox { @@ -51,7 +51,7 @@ flex-shrink: 0; border-radius: 6px; overflow: hidden; - background: #fff; + background: var(--card-media-bg); border: 1px solid rgba(148, 163, 184, 0.15); display: flex; align-items: center; @@ -132,7 +132,7 @@ padding: 8px 10px; border: 1px solid rgba(148, 163, 184, 0.2); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.55); + background: var(--surface); } .removeSelectedBtn { diff --git a/apps/business/src/components/Switch.module.css b/apps/business/src/components/Switch.module.css index 643e3ca..a6f61fa 100644 --- a/apps/business/src/components/Switch.module.css +++ b/apps/business/src/components/Switch.module.css @@ -5,13 +5,43 @@ flex-shrink: 0; border-radius: 50px; background: rgba(148, 163, 184, 0.45); + overflow: hidden; transition: background 0.2s ease; } +.switch::before, +.switch::after { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-family: var(--font-en), var(--font-ui); + font-size: 10px; + font-weight: 700; + line-height: 1; + pointer-events: none; + z-index: 0; + color: var(--text-primary); +} + +.switch::before { + content: '1'; + left: 7px; +} + +.switch::after { + content: '0'; + right: 7px; +} + .switch.on { background: var(--primary); } +.switch.on::before, +.switch.on::after { + color: #fff; +} + .switch:disabled { opacity: 0.55; cursor: not-allowed; @@ -27,6 +57,7 @@ background: #fff; box-shadow: 0 1px 3px rgba(15, 23, 42, 0.2); transition: transform 0.2s ease; + z-index: 1; } .switch.on .thumb { diff --git a/apps/business/src/components/TagInput.module.css b/apps/business/src/components/TagInput.module.css index 0463c22..28c93a0 100644 --- a/apps/business/src/components/TagInput.module.css +++ b/apps/business/src/components/TagInput.module.css @@ -1,7 +1,7 @@ .wrapper { min-height: var(--field-height); padding: 6px 10px; - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; diff --git a/apps/business/src/components/TechnicalFormModal.module.css b/apps/business/src/components/TechnicalFormModal.module.css index 5101e45..040f5aa 100644 --- a/apps/business/src/components/TechnicalFormModal.module.css +++ b/apps/business/src/components/TechnicalFormModal.module.css @@ -11,7 +11,7 @@ .fieldCard { padding: 10px 12px; - background: rgba(255, 255, 255, 0.6); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.25); border-radius: var(--radius-sm); } @@ -30,7 +30,7 @@ line-height: 1.4; font-family: inherit; color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; @@ -115,7 +115,7 @@ line-height: 1.4; font-family: inherit; color: var(--text-primary); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; @@ -137,7 +137,7 @@ line-height: 1.5; font-family: inherit; color: var(--text-primary); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; diff --git a/apps/business/src/components/Toast.module.css b/apps/business/src/components/Toast.module.css index 7d71701..49d77d1 100644 --- a/apps/business/src/components/Toast.module.css +++ b/apps/business/src/components/Toast.module.css @@ -19,7 +19,7 @@ font-weight: 500; line-height: 1.4; color: var(--text-primary); - background: var(--toast-bg, rgba(255, 255, 255, 0.94)); + background: var(--toast-bg, var(--elevated-surface)); backdrop-filter: blur(var(--blur-glass)); -webkit-backdrop-filter: blur(var(--blur-glass)); border: 1px solid var(--glass-border); diff --git a/apps/business/src/components/ToggleSwitch.module.css b/apps/business/src/components/ToggleSwitch.module.css index 1585f30..6ef039a 100644 --- a/apps/business/src/components/ToggleSwitch.module.css +++ b/apps/business/src/components/ToggleSwitch.module.css @@ -4,12 +4,42 @@ height: 22px; border-radius: 999px; background: rgba(148, 163, 184, 0.45); + overflow: hidden; transition: background 0.2s; flex-shrink: 0; } +.switch::before, +.switch::after { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-family: var(--font-en), var(--font-ui); + font-size: 9px; + font-weight: 700; + line-height: 1; + pointer-events: none; + z-index: 0; + color: var(--text-primary); +} + +.switch::before { + content: '1'; + left: 6px; +} + +.switch::after { + content: '0'; + right: 6px; +} + .switch.on { - background: #16a34a; + background: var(--primary); +} + +.switch.on::before, +.switch.on::after { + color: #fff; } .thumb { @@ -19,9 +49,10 @@ width: 18px; height: 18px; border-radius: 50%; - background: white; + background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); transition: transform 0.2s; + z-index: 1; } .switch.on .thumb { @@ -33,6 +64,19 @@ height: 18px; } +.compact::before, +.compact::after { + font-size: 8px; +} + +.compact::before { + left: 5px; +} + +.compact::after { + right: 5px; +} + .compact .thumb { width: 14px; height: 14px; diff --git a/apps/business/src/components/Tooltip.module.css b/apps/business/src/components/Tooltip.module.css index 9e65282..68d0af9 100644 --- a/apps/business/src/components/Tooltip.module.css +++ b/apps/business/src/components/Tooltip.module.css @@ -21,7 +21,7 @@ opacity: 0; visibility: hidden; transition: opacity 0.15s ease, visibility 0.15s; - background: rgba(255, 255, 255, 0.95); + background: var(--elevated-surface); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid var(--glass-border); @@ -44,10 +44,10 @@ .tipTop::after { top: 100%; - border-top-color: rgba(255, 255, 255, 0.95); + border-top-color: var(--elevated-surface); } .tipBottom::after { bottom: 100%; - border-bottom-color: rgba(255, 255, 255, 0.95); + border-bottom-color: var(--elevated-surface); } diff --git a/apps/business/src/components/UserProductStatusModal.module.css b/apps/business/src/components/UserProductStatusModal.module.css index 4a46454..3491906 100644 --- a/apps/business/src/components/UserProductStatusModal.module.css +++ b/apps/business/src/components/UserProductStatusModal.module.css @@ -18,7 +18,7 @@ padding: var(--field-padding-y) var(--field-padding-x); border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.22); - background: rgba(255, 255, 255, 0.55); + background: var(--surface); cursor: pointer; font-size: var(--field-font-size); font-family: var(--font-ui); diff --git a/apps/business/src/components/VariationsModal.module.css b/apps/business/src/components/VariationsModal.module.css index 4b63d91..aaea6c7 100644 --- a/apps/business/src/components/VariationsModal.module.css +++ b/apps/business/src/components/VariationsModal.module.css @@ -28,7 +28,7 @@ max-width: 480px; max-height: 90vh; overflow-y: auto; - background: rgba(255, 255, 255, 0.92); + background: var(--elevated-surface); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border: 1px solid var(--glass-border); @@ -117,7 +117,7 @@ align-items: center; gap: 12px; padding: 12px 14px; - background: rgba(255, 255, 255, 0.6); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.25); border-radius: var(--radius-sm); } @@ -208,7 +208,7 @@ line-height: 1.4; font-family: inherit; color: var(--text-primary); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; @@ -236,7 +236,7 @@ line-height: 1.5; font-family: inherit; color: var(--text-primary); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; @@ -262,7 +262,7 @@ font-size: 13px; font-weight: 500; color: var(--text-secondary); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: 50px; transition: all 0.15s; @@ -300,7 +300,7 @@ font-size: var(--field-font-size); font-family: var(--font-ui); color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; diff --git a/apps/business/src/components/WebsiteSliderSlideCard.module.css b/apps/business/src/components/WebsiteSliderSlideCard.module.css index 8a2419a..1b8b69c 100644 --- a/apps/business/src/components/WebsiteSliderSlideCard.module.css +++ b/apps/business/src/components/WebsiteSliderSlideCard.module.css @@ -21,7 +21,7 @@ width: 100%; aspect-ratio: 9 / 4; overflow: hidden; - background: #fff; + background: var(--card-media-bg); border-bottom: 1px solid rgba(148, 163, 184, 0.12); } diff --git a/apps/business/src/components/WebsiteStaticImageGallery.module.css b/apps/business/src/components/WebsiteStaticImageGallery.module.css index 3dd0f4a..dd8c462 100644 --- a/apps/business/src/components/WebsiteStaticImageGallery.module.css +++ b/apps/business/src/components/WebsiteStaticImageGallery.module.css @@ -61,7 +61,7 @@ width: 100%; padding: 0; overflow: hidden; - background: #fff; + background: var(--card-media-bg); cursor: pointer; } diff --git a/apps/business/src/context/BusinessThemeContext.tsx b/apps/business/src/context/BusinessThemeContext.tsx index eceed5c..2387d40 100644 --- a/apps/business/src/context/BusinessThemeContext.tsx +++ b/apps/business/src/context/BusinessThemeContext.tsx @@ -2,12 +2,13 @@ import { useEffect, type ReactNode } from 'react' import { isAbortError } from '../lib/api' import { getBusinessDomain } from '../lib/config' import { resolveTenantByDomain } from '../services/tenantService' -import { applyBusinessPrimaryColor } from '../utils/applyBusinessTheme' +import { applyBusinessPrimaryColor, applyThemeMode } from '../utils/applyBusinessTheme' import { normalizeBusinessPrimaryColorId } from '../utils/businessPrimaryColors' /** - * Applies the tenant primary color. Does not reset to blue on unmount — - * Strict Mode / HMR remounts were wiping the brand color back to the default. + * Applies the tenant primary color and dashboard theme mode. + * Does not reset to blue on unmount — Strict Mode / HMR remounts were + * wiping the brand color back to the default. */ export function BusinessThemeProvider({ children }: { children: ReactNode }) { useEffect(() => { @@ -21,9 +22,11 @@ export function BusinessThemeProvider({ children }: { children: ReactNode }) { applyBusinessPrimaryColor( normalizeBusinessPrimaryColorId(tenant.primaryColor), ) + applyThemeMode(tenant.themeMode) } catch (err) { if (isAbortError(err) || controller.signal.aborted) return applyBusinessPrimaryColor(undefined) + applyThemeMode('light') } } diff --git a/apps/business/src/context/TenantBrandingContext.tsx b/apps/business/src/context/TenantBrandingContext.tsx index 6b342a5..6d2af83 100644 --- a/apps/business/src/context/TenantBrandingContext.tsx +++ b/apps/business/src/context/TenantBrandingContext.tsx @@ -15,7 +15,12 @@ import { getBusinessDomain } from '../lib/config' import { BUSINESS_PROFILE_UPDATED_EVENT } from '../lib/businessContext' import { getBusinessProfile } from '../services/businessProfileService' import { resolveTenantByDomain } from '../services/tenantService' -import { applyBusinessPrimaryColor } from '../utils/applyBusinessTheme' +import { + applyBusinessPrimaryColor, + applyThemeMode, + pickThemedLogoUrl, + type DashboardThemeMode, +} from '../utils/applyBusinessTheme' import { normalizeBusinessPrimaryColorId } from '../utils/businessPrimaryColors' import { DEFAULT_ENABLED_BUSINESS_MODULES, @@ -31,6 +36,7 @@ interface TenantBrandingContextValue { businessNameEn: string logoUrl: string | null faviconUrl: string | null + themeMode: DashboardThemeMode enabledModules: BusinessModuleId[] homeCharts: [HomeChartId, HomeChartId] refreshBranding: () => void @@ -54,6 +60,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) { const [nameFa, setNameFa] = useState('') const [logoUrl, setLogoUrl] = useState(null) const [faviconUrl, setFaviconUrl] = useState(null) + const [themeMode, setThemeMode] = useState('light') const [enabledModules, setEnabledModules] = useState( DEFAULT_ENABLED_BUSINESS_MODULES, ) @@ -78,7 +85,13 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) { let nextNameEn = pickBusinessName(tenant.name, domain) let nextNameFa = pickBusinessName(tenant.nameFa, tenant.name, domain) - let nextLogo = tenant.logoUrl?.trim() || null + const nextThemeMode: DashboardThemeMode = + tenant.themeMode === 'dark' ? 'dark' : 'light' + let nextLogo = pickThemedLogoUrl( + tenant.logoUrl, + tenant.logoDarkUrl, + nextThemeMode, + ) let nextFavicon = tenant.faviconUrl?.trim() || tenant.logoUrl?.trim() || null @@ -96,7 +109,12 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) { nextNameFa, domain, ) - nextLogo = profile.profile.logoUrl?.trim() || nextLogo + nextLogo = + pickThemedLogoUrl( + profile.profile.logoUrl, + profile.profile.logoDarkUrl, + nextThemeMode, + ) || nextLogo nextFavicon = profile.profile.faviconUrl?.trim() || profile.profile.logoUrl?.trim() || @@ -113,12 +131,14 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) { setNameFa(nextNameFa || nextNameEn || domain) setLogoUrl(nextLogo) setFaviconUrl(nextFavicon) + setThemeMode(nextThemeMode) setEnabledModules(normalizeEnabledBusinessModules(tenant.enabledModules)) setHomeCharts(normalizeHomeCharts(tenant.homeCharts)) applyDocumentFavicon(nextFavicon) applyBusinessPrimaryColor( normalizeBusinessPrimaryColorId(tenant.primaryColor), ) + applyThemeMode(nextThemeMode) if (!defaultLocaleAppliedRef.current) { defaultLocaleAppliedRef.current = true if (tenant.defaultLocale === 'en' || tenant.defaultLocale === 'fa') { @@ -135,10 +155,12 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) { setNameFa(domain) setLogoUrl(null) setFaviconUrl(null) + setThemeMode('light') setEnabledModules([...DEFAULT_ENABLED_BUSINESS_MODULES]) setHomeCharts([...DEFAULT_HOME_CHARTS]) applyDocumentFavicon(null) applyBusinessPrimaryColor(undefined) + applyThemeMode('light') } } @@ -173,6 +195,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) { businessNameEn, logoUrl, faviconUrl, + themeMode, enabledModules, homeCharts, refreshBranding, @@ -182,6 +205,7 @@ export function TenantBrandingProvider({ children }: { children: ReactNode }) { businessNameEn, logoUrl, faviconUrl, + themeMode, enabledModules, homeCharts, refreshBranding, diff --git a/apps/business/src/index.css b/apps/business/src/index.css index 6280b3b..455de27 100644 --- a/apps/business/src/index.css +++ b/apps/business/src/index.css @@ -44,6 +44,16 @@ --text-primary: #1e293b; --text-secondary: #64748b; --text-muted: #94a3b8; + --border-color: rgba(148, 163, 184, 0.35); + --surface: rgba(255, 255, 255, 0.82); + --card-media-bg: #ffffff; + --elevated-surface: rgba(255, 255, 255, 0.96); + --icon-bg: color-mix(in srgb, var(--primary) 14%, #ffffff); + --icon-bg-end: color-mix(in srgb, var(--primary) 8%, #f1f5f9); + --card-hover-lift: -4px; + --card-hover-shadow: 0 16px 48px rgba(var(--primary-rgb) / 0.14); + --card-hover-transition: transform 0.25s ease, box-shadow 0.25s ease; + --table-header-bg: rgba(255, 255, 255, 0.35); --sidebar-width: 260px; --radius: 16px; --radius-sm: 12px; @@ -61,6 +71,57 @@ sans-serif; } +/* + * Dark mode: neutral dark gray backgrounds. + * Brand/theme color (--primary) is for accents only — not page/card backgrounds. + */ +html[data-theme='dark'] { + color-scheme: dark; + + --bg-gradient-start: #2a2d34; + --bg-gradient-mid: #22252b; + --bg-gradient-end: #1a1d23; + + --glass-bg: rgba(45, 49, 57, 0.9); + --header-bg: color-mix(in srgb, #2a2d34 92%, transparent); + --glass-border: rgba(255, 255, 255, 0.1); + --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.45); + --modal-overlay-bg: rgba(0, 0, 0, 0.6); + + --text-primary: #f1f5f9; + --text-secondary: #cbd5e1; + --text-muted: #94a3b8; + --border-color: rgba(255, 255, 255, 0.12); + --surface: rgba(55, 59, 68, 0.95); + --card-media-bg: #32363f; + --elevated-surface: #3a3e48; + --toast-bg: rgba(58, 62, 72, 0.96); + --toast-shadow: 0 12px 40px rgba(0, 0, 0, 0.45); + + --card-hover-shadow: 0 16px 48px rgba(0, 0, 0, 0.45); + + /* Table header: one step above the row, still dark */ + --table-header-bg: rgba(255, 255, 255, 0.07); + + /* Icon chip: dark gray tile, slight primary tint — not a light wash */ + --icon-bg: color-mix(in srgb, var(--primary) 22%, #2f333c); + --icon-bg-end: color-mix(in srgb, var(--primary) 12%, #25282f); +} + +html[data-theme='dark'] select { + background-color: var(--surface); + color: var(--text-primary); + border-color: var(--border-color); +} + +html[data-theme='dark'] + input:not([type='checkbox']):not([type='radio']):not([type='file']):not([type='range']):not([type='hidden']), +html[data-theme='dark'] textarea { + background-color: var(--surface); + color: var(--text-primary); + border-color: var(--border-color); +} + html { min-height: 100%; } @@ -91,6 +152,29 @@ body { overflow-x: hidden; } +html[data-theme='dark'] body { + background: linear-gradient( + 135deg, + var(--bg-gradient-start) 0%, + var(--bg-gradient-mid) 45%, + var(--bg-gradient-end) 100% + ); + background-attachment: fixed; +} + +/* Soft neutral aura — no theme-color wash on the page */ +html[data-theme='dark'] body::before { + content: ''; + position: fixed; + inset: -30%; + z-index: -1; + pointer-events: none; + display: block; + background: + radial-gradient(ellipse 520px 520px at 72% 18%, rgba(255, 255, 255, 0.04), transparent 72%), + radial-gradient(ellipse 420px 420px at 22% 82%, rgba(255, 255, 255, 0.03), transparent 72%); +} + /* Portaled dialogs sit on body; blur the app behind them. */ body:has([aria-modal='true']) #root { filter: blur(var(--modal-overlay-blur, 16px)); @@ -117,8 +201,8 @@ select { font-size: var(--field-font-size); line-height: 1.4; color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); - border: 1px solid rgba(148, 163, 184, 0.35); + background-color: var(--surface); + border: 1px solid var(--border-color); border-radius: var(--radius-sm); min-height: var(--field-height); padding: var(--field-padding-y) var(--select-padding-end) var(--field-padding-y) var(--field-padding-x); diff --git a/apps/business/src/pages/AddNewProductPage.module.css b/apps/business/src/pages/AddNewProductPage.module.css index 4af8a07..e27dc9b 100644 --- a/apps/business/src/pages/AddNewProductPage.module.css +++ b/apps/business/src/pages/AddNewProductPage.module.css @@ -158,7 +158,7 @@ font-size: var(--field-font-size); line-height: 1.4; color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; @@ -261,7 +261,7 @@ font-weight: 500; line-height: 1.2; color: var(--text-secondary); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: 50px; transition: border-color 0.15s, color 0.15s, background 0.15s; @@ -318,7 +318,7 @@ font-size: var(--field-font-size); line-height: 1.4; color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); cursor: pointer; @@ -363,7 +363,7 @@ font-size: var(--field-font-size); line-height: 1.4; color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; diff --git a/apps/business/src/pages/BusinessProfilePage.module.css b/apps/business/src/pages/BusinessProfilePage.module.css index ab7e980..98ee964 100644 --- a/apps/business/src/pages/BusinessProfilePage.module.css +++ b/apps/business/src/pages/BusinessProfilePage.module.css @@ -128,7 +128,7 @@ font-size: var(--field-font-size); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); } .selectField { diff --git a/apps/business/src/pages/CustomerProductDetailsPage.module.css b/apps/business/src/pages/CustomerProductDetailsPage.module.css index 9786c87..c3b25ce 100644 --- a/apps/business/src/pages/CustomerProductDetailsPage.module.css +++ b/apps/business/src/pages/CustomerProductDetailsPage.module.css @@ -72,7 +72,7 @@ border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.15); overflow: hidden; - background: #ffffff; + background: var(--card-media-bg); } .galleryThumbs { @@ -86,7 +86,7 @@ border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.15); overflow: hidden; - background: #ffffff; + background: var(--card-media-bg); } .galleryThumb img { diff --git a/apps/business/src/pages/CustomersPage.module.css b/apps/business/src/pages/CustomersPage.module.css index b16ab3a..15d0552 100644 --- a/apps/business/src/pages/CustomersPage.module.css +++ b/apps/business/src/pages/CustomersPage.module.css @@ -76,7 +76,7 @@ font-size: 12px; font-weight: 700; color: var(--text-secondary); - background: rgba(255, 255, 255, 0.35); + background: var(--table-header-bg); } .td { diff --git a/apps/business/src/pages/InvoicesPage.module.css b/apps/business/src/pages/InvoicesPage.module.css index 67b4901..149b691 100644 --- a/apps/business/src/pages/InvoicesPage.module.css +++ b/apps/business/src/pages/InvoicesPage.module.css @@ -76,7 +76,7 @@ font-size: 12px; font-weight: 700; color: var(--text-secondary); - background: rgba(255, 255, 255, 0.35); + background: var(--table-header-bg); } .td { @@ -433,7 +433,7 @@ padding: 12px; border: 1px solid rgba(148, 163, 184, 0.22); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.4); + background: var(--surface); } .detailItemTop { diff --git a/apps/business/src/pages/LoginPage.module.css b/apps/business/src/pages/LoginPage.module.css index dfc8ec0..789e728 100644 --- a/apps/business/src/pages/LoginPage.module.css +++ b/apps/business/src/pages/LoginPage.module.css @@ -15,12 +15,12 @@ width: 100%; max-width: 420px; padding: 24px 32px 28px; - background: rgba(255, 255, 255, 0.75); + background: var(--glass-bg); 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.12); + box-shadow: var(--glass-shadow); } .brand { @@ -176,8 +176,8 @@ line-height: 1.4; font-family: inherit; color: var(--text-primary); - background: rgba(255, 255, 255, 0.8); - border: 1px solid rgba(148, 163, 184, 0.35); + background: var(--surface); + border: 1px solid var(--border-color); border-radius: var(--radius-sm); outline: none; transition: border-color 0.2s, box-shadow 0.2s; @@ -325,13 +325,20 @@ button.submitBtn { .error { font-size: 13px; - color: #b91c1c; - background: rgba(239, 68, 68, 0.08); - border: 1px solid rgba(239, 68, 68, 0.25); + line-height: 1.55; + color: #fecaca; + background: rgba(239, 68, 68, 0.16); + border: 1px solid rgba(248, 113, 113, 0.45); border-radius: var(--radius-sm); padding: 10px 12px; } +:global(html:not([data-theme='dark'])) .error { + color: #b91c1c; + background: rgba(239, 68, 68, 0.08); + border-color: rgba(239, 68, 68, 0.25); +} + .info { font-size: 13px; color: var(--text-secondary); diff --git a/apps/business/src/pages/OrdersPage.module.css b/apps/business/src/pages/OrdersPage.module.css index a466da0..fe455db 100644 --- a/apps/business/src/pages/OrdersPage.module.css +++ b/apps/business/src/pages/OrdersPage.module.css @@ -84,7 +84,7 @@ font-size: 12px; font-weight: 700; color: var(--text-secondary); - background: rgba(255, 255, 255, 0.35); + background: var(--table-header-bg); } .td { diff --git a/apps/business/src/pages/PortfolioDetailsPage.module.css b/apps/business/src/pages/PortfolioDetailsPage.module.css index 8bd2f78..d7c7b19 100644 --- a/apps/business/src/pages/PortfolioDetailsPage.module.css +++ b/apps/business/src/pages/PortfolioDetailsPage.module.css @@ -163,7 +163,7 @@ border: 1px solid rgba(148, 163, 184, 0.2); border-radius: var(--radius); overflow: hidden; - background: rgba(255, 255, 255, 0.55); + background: var(--surface); cursor: pointer; transition: transform 0.2s, box-shadow 0.2s; } diff --git a/apps/business/src/pages/ProductDetailsPage.module.css b/apps/business/src/pages/ProductDetailsPage.module.css index c801ed6..4f13f28 100644 --- a/apps/business/src/pages/ProductDetailsPage.module.css +++ b/apps/business/src/pages/ProductDetailsPage.module.css @@ -23,7 +23,7 @@ width: 100%; max-width: 320px; aspect-ratio: 1 / 1; - background: #ffffff; + background: var(--card-media-bg); border-radius: var(--radius-sm); border: 1px solid rgba(148, 163, 184, 0.15); overflow: hidden; @@ -70,7 +70,7 @@ .thumb { width: 52px; height: 52px; - background: #fff; + background: var(--card-media-bg); border: 2px solid rgba(148, 163, 184, 0.2); border-radius: 8px; overflow: hidden; diff --git a/apps/business/src/pages/ProfilePage.module.css b/apps/business/src/pages/ProfilePage.module.css index ce18e45..a9da27f 100644 --- a/apps/business/src/pages/ProfilePage.module.css +++ b/apps/business/src/pages/ProfilePage.module.css @@ -70,7 +70,7 @@ font-family: var(--font-ui); line-height: 1.4; color: var(--text-primary); - background: rgba(255, 255, 255, 0.8); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; diff --git a/apps/business/src/pages/SettingsPage.module.css b/apps/business/src/pages/SettingsPage.module.css index 5e37864..0aa7495 100644 --- a/apps/business/src/pages/SettingsPage.module.css +++ b/apps/business/src/pages/SettingsPage.module.css @@ -6,7 +6,7 @@ .gatewayCard, .comingSoonCard { - background: rgba(255, 255, 255, 0.45); + background: var(--glass-bg); border: 1px solid rgba(148, 163, 184, 0.22); border-radius: var(--radius); padding: 20px; @@ -99,7 +99,7 @@ line-height: 1.4; font-family: var(--font-ui); color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; diff --git a/apps/business/src/pages/StoreItemsPage.module.css b/apps/business/src/pages/StoreItemsPage.module.css index f406558..312975c 100644 --- a/apps/business/src/pages/StoreItemsPage.module.css +++ b/apps/business/src/pages/StoreItemsPage.module.css @@ -85,7 +85,7 @@ padding: 0 36px; border-radius: 50px; border: 1px solid rgba(var(--primary-rgb) / 0.3); - background: rgba(255, 255, 255, 0.55); + background: var(--surface); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); box-shadow: 0 8px 28px rgba(31, 38, 135, 0.12); diff --git a/apps/business/src/pages/StoreSettingsPage.module.css b/apps/business/src/pages/StoreSettingsPage.module.css index f7f839c..a87d4cc 100644 --- a/apps/business/src/pages/StoreSettingsPage.module.css +++ b/apps/business/src/pages/StoreSettingsPage.module.css @@ -49,7 +49,7 @@ line-height: 1.4; font-family: inherit; color: var(--text-primary); - background: rgba(255, 255, 255, 0.6); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); transition: border-color 0.2s, box-shadow 0.2s; diff --git a/apps/business/src/pages/WebsiteContactPage.module.css b/apps/business/src/pages/WebsiteContactPage.module.css index a6e3294..98515cd 100644 --- a/apps/business/src/pages/WebsiteContactPage.module.css +++ b/apps/business/src/pages/WebsiteContactPage.module.css @@ -56,7 +56,7 @@ font-size: 12px; font-weight: 700; color: var(--text-secondary); - background: rgba(255, 255, 255, 0.35); + background: var(--table-header-bg); } .td { @@ -133,7 +133,7 @@ font-size: 12px; font-weight: 600; color: var(--text-secondary); - background: rgba(255, 255, 255, 0.55); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.25); transition: background 0.15s, color 0.15s, border-color 0.15s; } diff --git a/apps/business/src/pages/WebsiteSettingsPage.module.css b/apps/business/src/pages/WebsiteSettingsPage.module.css index 4d651d1..41ed023 100644 --- a/apps/business/src/pages/WebsiteSettingsPage.module.css +++ b/apps/business/src/pages/WebsiteSettingsPage.module.css @@ -17,7 +17,7 @@ font-size: var(--field-font-size); font-family: var(--font-ui); color: var(--text-primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right var(--select-arrow-offset) center; @@ -60,7 +60,7 @@ font-family: var(--font-ui); font-weight: 600; color: var(--primary); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); border: 1px solid rgba(var(--primary-rgb) / 0.35); border-radius: var(--radius-sm); cursor: pointer; diff --git a/apps/business/src/pages/WebsiteStaticImagesPage.module.css b/apps/business/src/pages/WebsiteStaticImagesPage.module.css index 0529c92..25016a1 100644 --- a/apps/business/src/pages/WebsiteStaticImagesPage.module.css +++ b/apps/business/src/pages/WebsiteStaticImagesPage.module.css @@ -71,7 +71,7 @@ appearance: none; -webkit-appearance: none; padding-right: var(--select-padding-end); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right var(--select-arrow-offset) center; diff --git a/apps/business/src/services/tenantService.ts b/apps/business/src/services/tenantService.ts index 2ad143b..59e8abc 100644 --- a/apps/business/src/services/tenantService.ts +++ b/apps/business/src/services/tenantService.ts @@ -1,6 +1,7 @@ import { apiRequest } from '../lib/api' import type { DashboardLocale } from '@meshkee/dashboard-core' import type { BusinessPrimaryColorId } from '../utils/businessPrimaryColors' +import type { DashboardThemeMode } from '../utils/applyBusinessTheme' import type { BusinessModuleId, HomeChartId } from '../utils/businessModules' export interface ResolvedTenant { @@ -11,10 +12,12 @@ export interface ResolvedTenant { domain: string primaryColor: BusinessPrimaryColorId defaultLocale?: DashboardLocale + themeMode?: DashboardThemeMode enabledModules?: BusinessModuleId[] homeCharts?: [HomeChartId, HomeChartId] specialProductsSource?: 'product' | 'store_item' logoUrl?: string | null + logoDarkUrl?: string | null faviconUrl?: string | null } diff --git a/apps/business/src/styles/ai.module.css b/apps/business/src/styles/ai.module.css index c12383c..b604f29 100644 --- a/apps/business/src/styles/ai.module.css +++ b/apps/business/src/styles/ai.module.css @@ -89,7 +89,7 @@ textarea.aiPromptTextarea:focus { border: 2px solid transparent; background-image: - linear-gradient(rgba(255, 255, 255, 0.94), rgba(255, 255, 255, 0.94)), + linear-gradient(var(--elevated-surface), var(--elevated-surface)), linear-gradient( 90deg, #ff375f 0%, diff --git a/apps/business/src/utils/applyBusinessTheme.ts b/apps/business/src/utils/applyBusinessTheme.ts index 86c3841..bea9d58 100644 --- a/apps/business/src/utils/applyBusinessTheme.ts +++ b/apps/business/src/utils/applyBusinessTheme.ts @@ -4,6 +4,25 @@ import { type BusinessPrimaryColorId, } from './businessPrimaryColors' +export type DashboardThemeMode = 'light' | 'dark' + +export function applyThemeMode(mode: 'light' | 'dark' | undefined) { + document.documentElement.setAttribute( + 'data-theme', + mode === 'dark' ? 'dark' : 'light', + ) +} + +export function pickThemedLogoUrl( + lightUrl: string | null | undefined, + darkUrl: string | null | undefined, + mode: DashboardThemeMode, +): string | null { + const light = lightUrl?.trim() || null + const dark = darkUrl?.trim() || null + return mode === 'dark' ? dark || light : light +} + const CSS_VAR_DEFAULTS: Record = { '--primary': '#3b82f6', '--primary-glow': '#3b82f6', diff --git a/apps/customer/src/index.css b/apps/customer/src/index.css index dd32b0f..b78daef 100644 --- a/apps/customer/src/index.css +++ b/apps/customer/src/index.css @@ -60,9 +60,9 @@ html[data-theme='dark'] { --card-hover-shadow: 0 16px 48px rgba(0, 0, 0, 0.45); - /* Icon chip: clear primary tint on dark gray (not muddy page wash) */ - --icon-bg: rgba(var(--primary-rgb) / 0.22); - --icon-bg-end: rgba(var(--primary-rgb) / 0.12); + /* Icon chip: dark gray tile, slight primary tint — not a light wash */ + --icon-bg: color-mix(in srgb, var(--primary) 22%, #2f333c); + --icon-bg-end: color-mix(in srgb, var(--primary) 12%, #25282f); } html[data-theme='dark'] select { diff --git a/apps/super-admin/src/components/ConfirmDeleteModal.tsx b/apps/super-admin/src/components/ConfirmDeleteModal.tsx index daa3385..873359c 100644 --- a/apps/super-admin/src/components/ConfirmDeleteModal.tsx +++ b/apps/super-admin/src/components/ConfirmDeleteModal.tsx @@ -1,6 +1,7 @@ -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { AlertTriangle, X } from 'lucide-react' +import { lockBodyScroll, unlockBodyScroll } from '../utils/lockBodyScroll' import styles from './ConfirmDeleteModal.module.css' interface ConfirmDeleteModalProps { @@ -22,6 +23,8 @@ export function ConfirmDeleteModal({ }: ConfirmDeleteModalProps) { const [mounted, setMounted] = useState(open) const [closing, setClosing] = useState(false) + const onCancelRef = useRef(onCancel) + onCancelRef.current = onCancel useEffect(() => { if (open) { @@ -40,16 +43,15 @@ export function ConfirmDeleteModal({ useEffect(() => { if (!mounted || closing) return const onKey = (e: KeyboardEvent) => { - if (e.key === 'Escape') onCancel() + if (e.key === 'Escape') onCancelRef.current() } - const prevOverflow = document.body.style.overflow - document.body.style.overflow = 'hidden' + lockBodyScroll() document.addEventListener('keydown', onKey) return () => { - document.body.style.overflow = prevOverflow + unlockBodyScroll() document.removeEventListener('keydown', onKey) } - }, [mounted, closing, onCancel]) + }, [mounted, closing]) if (!mounted) return null diff --git a/apps/super-admin/src/components/Modal.tsx b/apps/super-admin/src/components/Modal.tsx index c024b80..321bc1d 100644 --- a/apps/super-admin/src/components/Modal.tsx +++ b/apps/super-admin/src/components/Modal.tsx @@ -1,6 +1,7 @@ -import { useEffect } from 'react' +import { useEffect, useRef } from 'react' import { createPortal } from 'react-dom' import { X } from 'lucide-react' +import { lockBodyScroll, unlockBodyScroll } from '../utils/lockBodyScroll' import styles from './Modal.module.css' interface ModalProps { @@ -14,19 +15,21 @@ interface ModalProps { } export function Modal({ open, title, children, onClose, wide, xl }: ModalProps) { + const onCloseRef = useRef(onClose) + onCloseRef.current = onClose + useEffect(() => { if (!open) return const onKey = (e: KeyboardEvent) => { - if (e.key === 'Escape') onClose() + if (e.key === 'Escape') onCloseRef.current() } - const prevOverflow = document.body.style.overflow - document.body.style.overflow = 'hidden' + lockBodyScroll() document.addEventListener('keydown', onKey) return () => { - document.body.style.overflow = prevOverflow + unlockBodyScroll() document.removeEventListener('keydown', onKey) } - }, [open, onClose]) + }, [open]) if (!open) return null diff --git a/apps/super-admin/src/components/ToggleSwitch.module.css b/apps/super-admin/src/components/ToggleSwitch.module.css index d114a82..453866d 100644 --- a/apps/super-admin/src/components/ToggleSwitch.module.css +++ b/apps/super-admin/src/components/ToggleSwitch.module.css @@ -4,12 +4,42 @@ height: 22px; border-radius: 999px; background: rgba(148, 163, 184, 0.45); + overflow: hidden; transition: background 0.2s; flex-shrink: 0; } +.switch::before, +.switch::after { + position: absolute; + top: 50%; + transform: translateY(-50%); + font-family: var(--font-en), var(--font-ui); + font-size: 9px; + font-weight: 700; + line-height: 1; + pointer-events: none; + z-index: 0; + color: var(--text-primary); +} + +.switch::before { + content: '1'; + left: 6px; +} + +.switch::after { + content: '0'; + right: 6px; +} + .switch.on { - background: #16a34a; + background: var(--primary); +} + +.switch.on::before, +.switch.on::after { + color: #fff; } .thumb { @@ -19,9 +49,10 @@ width: 18px; height: 18px; border-radius: 50%; - background: white; + background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); transition: transform 0.2s; + z-index: 1; } .switch.on .thumb { diff --git a/apps/super-admin/src/pages/BusinessesPage.module.css b/apps/super-admin/src/pages/BusinessesPage.module.css index 546c361..ae8ab3a 100644 --- a/apps/super-admin/src/pages/BusinessesPage.module.css +++ b/apps/super-admin/src/pages/BusinessesPage.module.css @@ -576,6 +576,44 @@ line-height: 1.3; } +.renewalDaysRow { + display: inline-flex; + align-items: center; + gap: 4px; +} + +.renewalRefreshBtn { + width: 18px; + height: 18px; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + border-radius: 5px; + color: var(--text-muted); + transition: background 0.2s, color 0.2s; +} + +.renewalRefreshBtn:hover:not(:disabled) { + background: rgba(var(--primary-rgb) / 0.1); + color: var(--primary); +} + +.renewalRefreshBtn:disabled { + opacity: 0.55; + cursor: not-allowed; +} + +.renewalRefreshSpin { + animation: renewalRefreshSpin 0.8s linear infinite; +} + +@keyframes renewalRefreshSpin { + to { + transform: rotate(360deg); + } +} + .renewalOk { color: #15803d; } diff --git a/apps/super-admin/src/pages/BusinessesPage.tsx b/apps/super-admin/src/pages/BusinessesPage.tsx index 2417e51..5e6c774 100644 --- a/apps/super-admin/src/pages/BusinessesPage.tsx +++ b/apps/super-admin/src/pages/BusinessesPage.tsx @@ -9,6 +9,7 @@ import { Lock, Pencil, Plus, + RefreshCw, RotateCcw, Search, Trash2, @@ -112,28 +113,43 @@ function emptyModuleSelection( ) as Record } -/** Next anniversary of the contract date (renews every year). */ +function startOfLocalDay(d: Date) { + return new Date(d.getFullYear(), d.getMonth(), d.getDate()) +} + +/** Anniversary of the contract date. Does not roll a missed date into next year. */ function nextAnnualRenewal(contractAt: string, now = new Date()) { const contract = new Date(contractAt) if (Number.isNaN(contract.getTime())) return null - const startToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()) - let next = new Date( - startToday.getFullYear(), - contract.getMonth(), - contract.getDate(), - ) - if (next.getTime() < startToday.getTime()) { - next = new Date( - startToday.getFullYear() + 1, - contract.getMonth(), - contract.getDate(), - ) - } + const startToday = startOfLocalDay(now) + const contractDay = startOfLocalDay(contract) + + const due = + contractDay.getTime() > startToday.getTime() + ? contractDay + : new Date( + startToday.getFullYear(), + contract.getMonth(), + contract.getDate(), + ) + const daysRemaining = Math.round( - (next.getTime() - startToday.getTime()) / (24 * 60 * 60 * 1000), + (due.getTime() - startToday.getTime()) / (24 * 60 * 60 * 1000), ) - return { next, year: next.getFullYear(), daysRemaining } + return { next: due, year: due.getFullYear(), daysRemaining } +} + +function oneYearFromTodayIso(from = new Date()) { + const next = new Date( + from.getFullYear() + 1, + from.getMonth(), + from.getDate(), + 12, + 0, + 0, + ) + return next.toISOString() } function toDateInputValue(iso: string | null | undefined): string { @@ -165,12 +181,18 @@ function formatRenewalCell( if (!next) { return { dateLabel: '—', daysLabel: null, tone: null } } + const overdue = next.daysRemaining < 0 + const absDays = Math.abs(next.daysRemaining) const daysLabel = next.daysRemaining === 0 ? 'Renews today' - : next.daysRemaining === 1 - ? '1 day left' - : `${next.daysRemaining} days left` + : overdue + ? absDays === 1 + ? '1 day past' + : `${absDays} days past` + : next.daysRemaining === 1 + ? '1 day left' + : `${next.daysRemaining} days left` return { dateLabel: next.next.toLocaleDateString('en-US', { month: 'short', @@ -178,7 +200,7 @@ function formatRenewalCell( year: 'numeric', }), daysLabel, - tone: renewalTone(next.daysRemaining), + tone: overdue ? ('danger' as const) : renewalTone(next.daysRemaining), } } @@ -360,6 +382,7 @@ export function BusinessesPage() { const [removeTarget, setRemoveTarget] = useState(null) const [togglingId, setTogglingId] = useState(null) const [savingColorId, setSavingColorId] = useState(null) + const [savingRenewalId, setSavingRenewalId] = useState(null) const [savingLocaleId, setSavingLocaleId] = useState(null) const [modulesOpen, setModulesOpen] = useState(false) @@ -861,14 +884,42 @@ export function BusinessesPage() { setEditOpen(false) setEditBusiness(null) showToast('Business updated.', 'success') - await fetchList() } catch (err) { - setEditError(err instanceof ApiError ? err.message : 'Unable to update business.') + setEditError( + err instanceof ApiError ? err.message : 'Unable to update business.', + ) } finally { setEditSubmitting(false) } } + async function restartRenewalCountdown(b: BusinessListItem) { + const annualRenewalAt = oneYearFromTodayIso() + setSavingRenewalId(b.id) + try { + await updateBusiness(b.id, { annualRenewalAt }) + setData((prev) => { + if (!prev) return prev + return { + ...prev, + items: prev.items.map((item) => + item.id === b.id ? { ...item, annualRenewalAt } : item, + ), + } + }) + showToast(`Renewal countdown restarted for "${b.name}".`, 'success') + } catch (err) { + showToast( + err instanceof ApiError + ? err.message + : 'Unable to restart renewal countdown.', + 'error', + ) + } finally { + setSavingRenewalId(null) + } + } + function openDomainEdit(b: BusinessListItem) { setDomainBusiness(b) setDomainId(b.domainId) @@ -1457,17 +1508,37 @@ export function BusinessesPage() {
{renewal.dateLabel} {renewal.daysLabel ? ( - - {renewal.daysLabel} - +
+ + {renewal.daysLabel} + + +
) : null}
diff --git a/apps/super-admin/src/utils/lockBodyScroll.ts b/apps/super-admin/src/utils/lockBodyScroll.ts new file mode 100644 index 0000000..02dc90c --- /dev/null +++ b/apps/super-admin/src/utils/lockBodyScroll.ts @@ -0,0 +1,19 @@ +/** Nested-modal-safe body scroll lock. Restoring a captured overflow value + * leaves `overflow: hidden` on body after overlapping dialogs close. */ +let lockCount = 0 + +export function lockBodyScroll() { + lockCount += 1 + if (lockCount === 1) { + document.documentElement.style.overflow = 'hidden' + document.body.style.overflow = 'hidden' + } +} + +export function unlockBodyScroll() { + lockCount = Math.max(0, lockCount - 1) + if (lockCount === 0) { + document.documentElement.style.overflow = '' + document.body.style.overflow = '' + } +} diff --git a/docs/PROJECT_CONTEXT.md b/docs/PROJECT_CONTEXT.md index 2acbe0d..60d290b 100644 --- a/docs/PROJECT_CONTEXT.md +++ b/docs/PROJECT_CONTEXT.md @@ -3,7 +3,7 @@ > **For AI agents:** Read this file at the start of a new chat before making changes. > Update this document when a major feature is completed or architecture changes. -Last updated: August 21, 2026 +Last updated: August 31, 2026 --- @@ -471,7 +471,7 @@ Match **Super Admin** compact field sizing across all dashboards — see `.curso Global input sizing lives in `packages/dashboard-core/src/styles/tokens.css` (customer) and each app’s `index.css` (business / super-admin). Change password → shared `PasswordResetModal` from `@meshkee/dashboard-ui`. - **Farsi text:** Customer app → Yekan Bakh (`src/fonts/yekanbakh.css`); business/super-admin → IranYekan (`iranyekan.css`). `--font-en` / `--font-fa` are named faces only; `--font-ui` appends system fallbacks. Self-host files in `public/fonts/` — see `.cursor/rules/ui-farsi-fonts.mdc` -- **Per-business theme:** Super Admin sets `branding.primaryColor`; business app applies via `BusinessThemeProvider` +- **Per-business theme:** Super Admin sets `branding.primaryColor` and `branding.themeMode` (`light` \| `dark`); business + customer dashboards apply both via their theme providers (`data-theme` on ``). Dark mode uses neutral gray surfaces; `--primary` stays for accents only. - **Per-business modules:** Super Admin **Modules** column on Businesses list; stored in `settings.modules.enabled` (business + customer sections); business Home + Sidebar hide disabled business modules; `customer_products` gates customer My Products (`/my-products`, `/my-products/new`) which call `businesses/:id/my-user-products` - **RTL:** Farsi inputs use `dir="rtl"` and the app’s `--font-fa` - **IRT prices:** comma-separated thousands + `IRT` suffix — see `.cursor/rules/ui-irt-price.mdc` diff --git a/packages/dashboard-ui/src/components/AddressListEditor.module.css b/packages/dashboard-ui/src/components/AddressListEditor.module.css index b912f50..7b6a0b2 100644 --- a/packages/dashboard-ui/src/components/AddressListEditor.module.css +++ b/packages/dashboard-ui/src/components/AddressListEditor.module.css @@ -53,7 +53,7 @@ font-size: var(--field-font-size); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); - background-color: rgba(255, 255, 255, 0.7); + background-color: var(--surface); } .selectField { diff --git a/packages/dashboard-ui/src/components/PasswordResetModal.module.css b/packages/dashboard-ui/src/components/PasswordResetModal.module.css index 43d5ba3..930dde0 100644 --- a/packages/dashboard-ui/src/components/PasswordResetModal.module.css +++ b/packages/dashboard-ui/src/components/PasswordResetModal.module.css @@ -16,7 +16,7 @@ width: 100%; max-width: 440px; padding: 28px 28px 24px; - background: rgba(255, 255, 255, 0.88); + background: var(--elevated-surface); backdrop-filter: blur(28px); -webkit-backdrop-filter: blur(28px); border: 1px solid var(--glass-border); @@ -65,7 +65,7 @@ font-family: var(--font-ui); line-height: 1.4; color: var(--text-primary); - background: rgba(255, 255, 255, 0.75); + background: var(--surface); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); outline: none; @@ -101,12 +101,24 @@ color: #b91c1c; } +:global(html[data-theme='dark']) .error { + color: #fecaca; + background: rgba(239, 68, 68, 0.16); + border-color: rgba(248, 113, 113, 0.45); +} + .success { background: rgba(34, 197, 94, 0.08); border: 1px solid rgba(34, 197, 94, 0.25); color: #15803d; } +:global(html[data-theme='dark']) .success { + color: #bbf7d0; + background: rgba(34, 197, 94, 0.16); + border-color: rgba(74, 222, 128, 0.4); +} + .actions { display: flex; gap: 10px; diff --git a/packages/dashboard-ui/src/components/RichTextEditor.module.css b/packages/dashboard-ui/src/components/RichTextEditor.module.css index 86d0c6a..d1b085c 100644 --- a/packages/dashboard-ui/src/components/RichTextEditor.module.css +++ b/packages/dashboard-ui/src/components/RichTextEditor.module.css @@ -4,7 +4,7 @@ min-width: 0; border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.7); + background: var(--surface); overflow: visible; transition: border-color 0.2s, box-shadow 0.2s; box-sizing: border-box; @@ -22,7 +22,7 @@ gap: 2px; padding: 4px 6px; border-bottom: 1px solid rgba(148, 163, 184, 0.2); - background: rgba(248, 250, 252, 0.8); + background: var(--elevated-surface); position: relative; z-index: 2; } @@ -110,7 +110,7 @@ padding: 10px; border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.98); + background: var(--elevated-surface); box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12); } @@ -142,7 +142,7 @@ padding: var(--field-padding-y) var(--field-padding-x); border: 1px solid rgba(148, 163, 184, 0.35); border-radius: var(--radius-sm); - background: rgba(255, 255, 255, 0.9); + background: var(--elevated-surface); font-size: var(--field-font-size); font-family: var(--font-ui); color: var(--text-primary); diff --git a/packages/dashboard-ui/src/context/Toast.module.css b/packages/dashboard-ui/src/context/Toast.module.css index 7d71701..49d77d1 100644 --- a/packages/dashboard-ui/src/context/Toast.module.css +++ b/packages/dashboard-ui/src/context/Toast.module.css @@ -19,7 +19,7 @@ font-weight: 500; line-height: 1.4; color: var(--text-primary); - background: var(--toast-bg, rgba(255, 255, 255, 0.94)); + background: var(--toast-bg, var(--elevated-surface)); backdrop-filter: blur(var(--blur-glass)); -webkit-backdrop-filter: blur(var(--blur-glass)); border: 1px solid var(--glass-border);