diff --git a/src/actions/userManagement.js b/src/actions/userManagement.js index 86d59e420c..31b21892bc 100644 --- a/src/actions/userManagement.js +++ b/src/actions/userManagement.js @@ -19,6 +19,9 @@ import { ENDPOINTS } from '~/utils/URL'; import { UserStatus, UserStatusOperations, InactiveReason } from '~/utils/enums'; import { COMPANY_TZ } from '~/utils/formatDate'; +/** Used when callers request all basic profiles without a named source (e.g. LB/BM dashboards). */ +const DEFAULT_USER_PROFILE_BASIC_INFO_SOURCE = 'UserManagement'; + /** * Set a flag that fetching user profiles */ @@ -368,12 +371,17 @@ export const updateUserFinalDayStatusIsSet = (user, status, finalDayDate, isSet) export const getUserProfileBasicInfo = ({ userId, source } = {}) => { // API request to fetch basic user profile information let userProfileBasicInfoPromise; - if (userId) - userProfileBasicInfoPromise = axios.get(`${ENDPOINTS.USER_PROFILE_BASIC_INFO}?userId=${userId}`); - else if (source) + if (userId) { + userProfileBasicInfoPromise = axios.get( + `${ENDPOINTS.APIEndpoint()}/userProfile/basicInfo?userId=${userId}`, + ); + } else if (source) { userProfileBasicInfoPromise = axios.get(ENDPOINTS.USER_PROFILE_BASIC_INFO(source)); - else - userProfileBasicInfoPromise = axios.get(ENDPOINTS.USER_PROFILE_BASIC_INFO); + } else { + userProfileBasicInfoPromise = axios.get( + ENDPOINTS.USER_PROFILE_BASIC_INFO(DEFAULT_USER_PROFILE_BASIC_INFO_SOURCE), + ); + } return async dispatch => { // Dispatch action indicating the start of the fetch process diff --git a/src/components/BMDashboard/ItemList/ItemListView.module.css b/src/components/BMDashboard/ItemList/ItemListView.module.css index da43cce100..f6c50e2894 100644 --- a/src/components/BMDashboard/ItemList/ItemListView.module.css +++ b/src/components/BMDashboard/ItemList/ItemListView.module.css @@ -49,7 +49,7 @@ :global(.dark-mode) .items_list_container { background-color: #0d1117 !important; - color: #ffffff !important; + color: #fff !important; min-height: 100vh; padding-bottom: 2rem; } @@ -60,19 +60,19 @@ :global(.dark-mode) .items_list_container table { background-color: #161b22; - color: #ffffff; + color: #fff; border: 1px solid #30363d; } :global(.dark-mode) .items_list_container th { background-color: #1f2937; - color: #ffffff; + color: #fff; border-bottom: 1px solid #30363d; } :global(.dark-mode) .items_list_container td { border-bottom: 1px solid #30363d; - color: #ffffff; + color: #fff; } :global(.dark-mode) .items_list_container tr:hover { @@ -80,29 +80,29 @@ } :global(.dark-mode) .items_cell svg { - color: #ffffff; + color: #fff; } :global(.dark-mode) .items_cell svg:hover { - color: #ffffff; + color: #fff; } :global(.dark-mode) .select_input { background-color: #1e2632; - color: #ffffff; + color: #fff; border: 1px solid #30363d; } :global(.dark-mode) select, :global(.dark-mode) input { background-color: #1e2632; - color: #ffffff; + color: #fff; border: 1px solid #30363d; } :global(.dark-mode) option { background-color: #111827; - color: #ffffff; + color: #fff; } :global(.dark-mode) body, @@ -113,13 +113,13 @@ .items_list_container.dark_mode { background-color: #1b1f27 !important; - color: #ffffff !important; + color: #fff !important; border-color: #2a2f3a !important; } .dark-mode .items_table_container .table thead th { background-color: #1c2541 !important; - color: #ffffff !important; + color: #fff !important; border-color: #555 !important; } diff --git a/src/components/BMDashboard/ItemList/RecordsModal.module.css b/src/components/BMDashboard/ItemList/RecordsModal.module.css index c4bc05a405..afafaf2670 100644 --- a/src/components/BMDashboard/ItemList/RecordsModal.module.css +++ b/src/components/BMDashboard/ItemList/RecordsModal.module.css @@ -10,7 +10,7 @@ :global(.dark-mode) .records_modal_table_container, :global(.dark-mode) .recordsModalTableContainer { background-color: #3a506b; - color: #ffffff; + color: #fff; } :global(.dark-mode) .records_modal_table_container a, @@ -27,24 +27,24 @@ :global(.dark-mode) .records_modal_table_container thead tr th, :global(.dark-mode) .recordsModalTableContainer thead tr th { background-color: #1c2541 !important; - color: #ffffff !important; + color: #fff !important; border-color: #3a506b !important; } :global(.dark-mode) .records_modal_table_container tbody tr td, :global(.dark-mode) .recordsModalTableContainer tbody tr td { background-color: #3a506b !important; - color: #ffffff !important; + color: #fff !important; border-color: #1c2541 !important; } :global(.dark-mode) .records_modal_table_container tbody tr:hover td, :global(.dark-mode) .recordsModalTableContainer tbody tr:hover td { background-color: #1c2541 !important; - color: #ffffff !important; + color: #fff !important; } -@media (min-width: 1200px) { +@media (width >= 1200px) { .records_modal_table_container, .recordsModalTableContainer { font-size: medium; @@ -88,7 +88,7 @@ :global(.dark-mode) .reject_button, :global(.dark-mode) .rejectButton { background-color: #1c2541; - color: #ffffff; + color: #fff; border: 1px solid #3a506b; } diff --git a/src/components/BMDashboard/Lesson/LessonForm.module.css b/src/components/BMDashboard/Lesson/LessonForm.module.css index cbe454527b..2d38700aa2 100644 --- a/src/components/BMDashboard/Lesson/LessonForm.module.css +++ b/src/components/BMDashboard/Lesson/LessonForm.module.css @@ -3,11 +3,12 @@ color: #e9ecef !important; } } + .masterContainer { display: flex; flex-direction: column; height: 100%; - padding: 5% 10% 10% 10%; + padding: 5% 10% 10%; background-color: #E8F4F9; } @@ -22,17 +23,18 @@ .darkModeForm { background-color: #1C2541 !important; /* Space Cadet */ - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); + box-shadow: 0 4px 8px rgb(0 0 0 / 50%); } .formControl { width: 25%; flex: 0 0 25%; + max-width: unset !important; padding: 0.375rem 0.75rem; font-size: 1rem; line-height: 1.5; color: #212529; - background-color: #ffffff; + background-color: #fff; border: 1px solid #ced4da; border-radius: 0.375rem; outline: none; @@ -47,17 +49,6 @@ box-shadow: none; } -.masterContainer{ - display: flex; - flex-direction: column; - height: 100%; - padding-left: 10%; - padding-right: 10%; - padding-top: 5%; - padding-bottom: 10%; - background-color: #E8F4F9; -} - /* Dark Mode Input Fields (Yinmn Blue) */ .darkModeInput { background-color: #3A506B !important; /* Yinmn Blue */ @@ -69,10 +60,6 @@ color: #ADB5BD !important; } -.formControl { - max-width: unset !important; -} - .lessonLabel { font-size: larger; font-weight: bold; @@ -99,7 +86,7 @@ } .dragAndDropStyle { - border: 2px dashed #cccccc; + border: 2px dashed #ccc; padding: 0.5%; text-align: center; cursor: pointer; @@ -137,7 +124,7 @@ .lessonFormButtonCancel { width: 25%; height: 50px; - background-color: #FFFFFF !important; + background-color: #FFF !important; color: #2E5061 !important; border-color: #2E5061 !important; } @@ -152,7 +139,7 @@ width: 25%; height: 50px; background-color: #2E5061 !important; - color: #FFFFFF !important; + color: #FFF !important; border-color: #2E5061 !important; } @@ -241,18 +228,21 @@ width: 100%; } -@media (max-width: 570px) { +@media (width <= 570px) { .formSelectContainer { align-items: center; flex-direction: column; } + .singleFormSelect { width: 90%; } + .lessonFormButtonSubmit, .lessonFormButtonCancel { width: 45%; } + .masterContainer { padding-bottom: 20%; @@ -272,23 +262,24 @@ color: black; } -.darkFormContainer{ - background-color: '#1e2433'; - /* color: '#f5f7fa' */ +.darkFormContainer { + background-color: #1e2433; } /* ===================== */ + /* Dark Mode Styles */ + /* ===================== */ .masterContainerDark { background-color: #1B2A41; - color: #ffffff; + color: #fff; } .formContainerDark { background-color: #1C2541; - color: #ffffff; + color: #fff; } .suppressInitialFocus :global(:focus) { @@ -311,20 +302,22 @@ .noFocusShadow :global(input[type='text']), .noFocusShadow :global(textarea) { box-shadow: none !important; - -webkit-box-shadow: none !important; } .noFocusShadow :global(input.form-control:focus), .noFocusShadow :global(textarea.form-control:focus), .noFocusShadow :global(input[type='text']:focus), .noFocusShadow :global(textarea:focus) { - -webkit-box-shadow: 0 0 0 1000px #1C2541 inset !important; box-shadow: 0 0 0 1000px #1C2541 inset !important; } +/* Dark-mode input cascade: specificity order is intentional for Bootstrap overrides */ + +/* stylelint-disable no-descending-specificity */ + /* Force no shadow unless focused */ -.formContainerDark .lessonTitleInputDark:not(:focus):not(:active):not(:focus-visible), -.formContainerDark .lessonPlaceholderTextDark:not(:focus):not(:active):not(:focus-visible), +.formContainerDark .lessonTitleInputDark:not(:focus, :active, :focus-visible), +.formContainerDark .lessonPlaceholderTextDark:not(:focus, :active, :focus-visible), :global(body.dark-mode) .formContainerDark :global(input.form-control:not(:focus)), :global(body.bm-dashboard-dark) .formContainerDark :global(input.form-control:not(:focus)), :global(body.dark-mode) .formContainerDark :global(textarea.form-control:not(:focus)), @@ -334,19 +327,18 @@ :global(body.dark-mode) .formContainerDark :global(textarea:not(:focus)), :global(body.bm-dashboard-dark) .formContainerDark :global(textarea:not(:focus)) { box-shadow: none !important; - -webkit-box-shadow: none !important; } .lessonLabelDark { - color: #ffffff; + color: #fff; } .lessonPlaceholderTextDark { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border: 1px solid #404040 !important; color-scheme: dark; - -webkit-text-fill-color: #ffffff; + -webkit-text-fill-color: #fff; box-shadow: none !important; } @@ -356,10 +348,9 @@ .lessonTitleInputDark { color-scheme: dark; - caret-color: #ffffff; + caret-color: #fff; appearance: none; - -webkit-appearance: none; - -webkit-text-fill-color: #ffffff; + -webkit-text-fill-color: #fff; box-shadow: none !important; } @@ -368,7 +359,6 @@ .lessonTitleInputDark:focus-visible { background-color: #1C2541 !important; border-color: #2563eb !important; - -webkit-box-shadow: 0 0 0 1000px #1C2541 inset !important; background-clip: padding-box !important; box-shadow: 0 0 0 1000px #1C2541 inset !important; outline: none !important; @@ -377,9 +367,8 @@ .formContainerDark .lessonTitleInputDark, .formContainerDark .lessonPlaceholderTextDark { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border-color: #404040 !important; - -webkit-box-shadow: none !important; background-clip: padding-box !important; box-shadow: none !important; outline: none !important; @@ -393,9 +382,8 @@ .lessonPlaceholderTextDark:global(.form-control):active, .lessonPlaceholderTextDark:global(.form-control):focus-visible { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border-color: #2563eb !important; - -webkit-box-shadow: 0 0 0 1000px #1C2541 inset !important; background-clip: padding-box !important; box-shadow: 0 0 0 1000px #1C2541 inset !important; outline: none !important; @@ -405,7 +393,6 @@ .lessonPlaceholderTextDark:active { border-color: #2563eb !important; background-color: #1C2541 !important; - -webkit-box-shadow: 0 0 0 1000px #1C2541 inset !important; background-clip: padding-box !important; box-shadow: 0 0 0 1000px #1C2541 inset !important; outline: none !important; @@ -417,26 +404,25 @@ :global(body.dark-mode) .formContainerDark .lessonPlaceholderTextDark, :global(body.bm-dashboard-dark) .formContainerDark .lessonPlaceholderTextDark { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border-color: #404040 !important; - -webkit-box-shadow: none !important; box-shadow: none !important; } .formSelectContainerDark { - color: #ffffff; + color: #fff; } .singleFormSelectDark { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border: 1px solid #444; } .singleFormSelectDark:focus, .singleFormSelectDark:active { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border-color: #2563eb !important; box-shadow: none !important; outline: none !important; @@ -445,7 +431,7 @@ .dragAndDropStyleDark { border: 2px dashed #555; background-color: #1f1f1f; - color: #ffffff; + color: #fff; } .dragandDropTextDark { @@ -453,49 +439,49 @@ } .fileSelectedDark { - border-color: #ffffff; + border-color: #fff; } .lessonFormButtonCancelDark { background-color: #2a2a2a !important; - color: #ffffff !important; + color: #fff !important; border-color: #666 !important; } .lessonFormButtonSubmitDark { background-color: #2563eb !important; - color: #ffffff !important; + color: #fff !important; border-color: #2563eb !important; } /* Tags - Dark Mode */ .tagsDivDark { - color: #ffffff; + color: #fff; } .tagDark { background-color: #2a2a2a; - color: #ffffff; + color: #fff; border: 1px solid #555; } .removeTagBTNDark { - color: #ffffff; + color: #fff; } .tagDropdownDark { background-color: #1e1e1e; - color: #ffffff !important; + color: #fff !important; border: 1px solid #444; } .tagDropdownDark * { - color: #ffffff !important; + color: #fff !important; } .tagOptionDark { - color: #ffffff !important; + color: #fff !important; background-color: transparent; } @@ -503,14 +489,14 @@ .tagOptionDark:focus, .tagOptionDark:active { background-color: #2d3b66; - color: #ffffff !important; + color: #fff !important; outline: none; } .tagOptionDark:hover *, .tagOptionDark:focus *, .tagOptionDark:active * { - color: #ffffff !important; + color: #fff !important; background-color: transparent !important; } @@ -520,7 +506,7 @@ .formControlDark { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border: 1px solid #404040 !important; box-shadow: none !important; } @@ -529,7 +515,7 @@ .formControlDark:active, .formControlDark:focus-visible { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border-color: #2563eb !important; box-shadow: none !important; outline: none !important; @@ -542,6 +528,7 @@ .deleteTagBtnDark:hover { color: #ff3b3b; } + /* Remove default arrow in all browsers */ .formSelectDark { background-color: #1C2541; @@ -550,35 +537,27 @@ /* Remove default arrows */ appearance: none; - -webkit-appearance: none; - -moz-appearance: none; /* Add custom arrow spacing */ padding-right: 32px; - background-image: url("data:image/svg+xml;utf8,"); background-repeat: no-repeat; background-position: right 10px center; background-size: 18px; } + /* Fix selected text background in dark mode */ .lessonPlaceholderTextDark::selection, -.lessonPlaceholderTextDark::-moz-selection, .lessonPlaceholderTextDark.form-control::selection, -.lessonPlaceholderTextDark.form-control::-moz-selection, .lessonTitleInput::selection, -.lessonTitleInput::-moz-selection, -.lessonTitleInputDark::selection, -.lessonTitleInputDark::-moz-selection { +.lessonTitleInputDark::selection { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; } /* Hide selection when the field is not focused */ .lessonTitleInputDark:not(:focus)::selection, -.lessonTitleInputDark:not(:focus)::-moz-selection, -.lessonPlaceholderTextDark:not(:focus)::selection, -.lessonPlaceholderTextDark:not(:focus)::-moz-selection { +.lessonPlaceholderTextDark:not(:focus)::selection { background-color: #1C2541 !important; color: #1C2541 !important; } @@ -593,7 +572,7 @@ :global(body.dark-mode) .formContainerDark :global(textarea)::selection, :global(body.bm-dashboard-dark) .formContainerDark :global(textarea)::selection { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; } :global(body.dark-mode) .formContainerDark :global(input.form-control:not(:focus))::selection, @@ -613,8 +592,8 @@ .formControlDark:-webkit-autofill:hover, .formControlDark:-webkit-autofill:focus, .formControlDark:-webkit-autofill:active { - -webkit-box-shadow: 0 0 0 1000px #1C2541 inset !important; - -webkit-text-fill-color: #ffffff !important; + box-shadow: 0 0 0 1000px #1C2541 inset !important; + -webkit-text-fill-color: #fff !important; transition: background-color 9999s ease-in-out 0s; } @@ -623,18 +602,18 @@ .lessonTitleInputDark:-webkit-autofill:hover, .lessonTitleInputDark:-webkit-autofill:focus, .lessonTitleInputDark:-webkit-autofill:active { - -webkit-box-shadow: 0 0 0 1000px #1C2541 inset !important; - -webkit-text-fill-color: #ffffff !important; - caret-color: #ffffff !important; + box-shadow: 0 0 0 1000px #1C2541 inset !important; + -webkit-text-fill-color: #fff !important; + caret-color: #fff !important; } :global(body.dark-mode) .formContainerDark :global(input.form-control:-webkit-autofill), :global(body.bm-dashboard-dark) .formContainerDark :global(input.form-control:-webkit-autofill), :global(body.dark-mode) .formContainerDark :global(input[type='text']:-webkit-autofill), :global(body.bm-dashboard-dark) .formContainerDark :global(input[type='text']:-webkit-autofill) { - -webkit-box-shadow: 0 0 0 1000px #1C2541 inset !important; - -webkit-text-fill-color: #ffffff !important; - caret-color: #ffffff !important; + box-shadow: 0 0 0 1000px #1C2541 inset !important; + -webkit-text-fill-color: #fff !important; + caret-color: #fff !important; } @@ -647,8 +626,10 @@ .lessonPlaceholderTextDark.form-control:active, .lessonPlaceholderTextDark.form-control:focus-visible { background-color: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border: 1px solid #404040 !important; box-shadow: none !important; outline: none !important; } + +/* stylelint-enable no-descending-specificity */ diff --git a/src/components/BMDashboard/RentalChart/ReturnedLateChart.module.css b/src/components/BMDashboard/RentalChart/ReturnedLateChart.module.css index 2b6da5de3a..f242c24135 100644 --- a/src/components/BMDashboard/RentalChart/ReturnedLateChart.module.css +++ b/src/components/BMDashboard/RentalChart/ReturnedLateChart.module.css @@ -161,6 +161,7 @@ background-color: #2a3f5f; border: 1px solid #555; } + .background-red { background-color: #ff4d4f; } @@ -168,6 +169,7 @@ :global(.dark.rmsc ) { --rmsc-border: none; --rmsc-bg: none; + border:transparent; } @@ -186,6 +188,7 @@ :global(.dark.rmsc .select-item.selected) { background-color: #253246; } + :global(.rmsc.dark .options) { background-color: #2a3f5f; color: #fff; @@ -196,6 +199,7 @@ background-color: #2a3f5f; color: #fff; } + :global(.rmsc.dark .search input:focus) { background-color: #223653; } diff --git a/src/components/BMDashboard/UpdateMaterials/UpdateMaterial.module.css b/src/components/BMDashboard/UpdateMaterials/UpdateMaterial.module.css index 86244bf231..79f2d7196c 100644 --- a/src/components/BMDashboard/UpdateMaterials/UpdateMaterial.module.css +++ b/src/components/BMDashboard/UpdateMaterials/UpdateMaterial.module.css @@ -7,7 +7,7 @@ background-color: #E8F4F9; width: 100%; height: auto; - margin: 0px; + margin: 0; padding: 1rem 2rem; font-size: 15px; } @@ -102,13 +102,13 @@ /* Page background */ :global(.dark-mode) .updateMaterialPage { background-color: #3A506B; /* Yinmn Blue */ - color: #ffffff; + color: #fff; } /* Card container */ :global(.dark-mode) .updateMaterial { background-color: #1B2A41; /* Muted Oxford Blue */ - color: #ffffff; + color: #fff; border: 1px solid #2a2f3a; } @@ -119,32 +119,32 @@ /* Values */ :global(.dark-mode) .materialFormValue { - color: #ffffff; + color: #fff; } /* Buttons */ :global(.dark-mode) .materialButtonOutline { - color: #ffffff !important; - border-color: #ffffff !important; + color: #fff !important; + border-color: #fff !important; } :global(.dark-mode) .materialButtonOutline:hover { background-color: #1C2541 !important; /* Space Cadet */ - color: #ffffff !important; + color: #fff !important; } :global(.dark-mode) .materialButtonBg { background-color: #1C2541 !important; /* Space Cadet */ - color: #ffffff !important; + color: #fff !important; } :global(.dark-mode) .materialButtonBg:hover { background-color: #3A506B !important; /* Yinmn Blue */ - color: #ffffff !important; + color: #fff !important; } /* Modal container */ :global(.dark-mode) .updateModalContainer { background-color: #1B2A41; /* Muted Oxford Blue */ - color: #ffffff; + color: #fff; } \ No newline at end of file diff --git a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css index de8ed50350..ed4647b5a3 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css +++ b/src/components/BMDashboard/WeeklyProjectSummary/ProjectRiskProfileOverview.module.css @@ -1,5 +1,5 @@ .chartCard { - background-color: #ffffff; + background-color: #fff; color: #232323; border-radius: 8px; box-shadow: 0 2px 8px #eee; @@ -62,10 +62,6 @@ padding: 0; } -.darkMode .dropdownButton { - color: #e5e5e5; -} - .dropdownMenu { position: absolute; left: 0; @@ -146,24 +142,20 @@ background: #2b2b2b; border: 1px solid #555; } + .darkMode .dropdownButton:hover { background: #3a3a3a; } .darkMode .dropdownMenu { - background: #2c2c2c; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); + background-color: #1c2541; + box-shadow: 2px 2px 4px 1px black; } .darkMode .heading { color: #eee; } -.darkMode .dropdownMenu { - background-color: #1c2541; /* --color-space-cadet */ - box-shadow: 2px 2px 4px 1px black; -} - .chartContainer { width: 100%; margin: 0 -24px; @@ -217,7 +209,7 @@ } .trendTitle { - margin: 0 0 12px 0; + margin: 0 0 12px; font-size: 16px; font-weight: 600; color: #1a1a1a; @@ -256,20 +248,42 @@ color: #eee; } -.trendRow { - display: flex; - align-items: center; - gap: 8px; - font-size: 13px; +.trendRow { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; margin-bottom: 8px; } -.trendRow span:first-child { flex: 1; color: #666; font-weight: 500; } -.darkMode .trendRow span:first-child { color: #aaa; } -.trendRow span:last-child { font-weight: 600; color: #1a1a1a; min-width: 50px; text-align: right; } -.darkMode .trendRow span:last-child { color: #ddd; } +.trendRow span:last-child { + font-weight: 600; + color: #1a1a1a; + min-width: 50px; + text-align: right; +} + +.trendRow span:first-child { + flex: 1; + color: #666; + font-weight: 500; +} + +.darkMode .trendRow span:first-child { + color: #aaa; +} -.trendIndicator { font-size: 16px; font-weight: 700; display: inline-block; width: 24px; text-align: center; } +.darkMode .trendRow span:last-child { + color: #ddd; +} + +.trendIndicator { + font-size: 16px; + font-weight: 700; + display: inline-block; + width: 24px; + text-align: center; +} .attributeRow { display: flex; @@ -311,6 +325,16 @@ color: #ddd; } -.timelineSection { margin-top: 28px; padding: 18px; background: #fafafa; border-radius: 8px; border: 1px solid #e8e8e8; } -.darkMode .timelineSection { background: #252525; border-color: #404040; } +.timelineSection { + margin-top: 28px; + padding: 18px; + background: #fafafa; + border-radius: 8px; + border: 1px solid #e8e8e8; +} + +.darkMode .timelineSection { + background: #252525; + border-color: #404040; +} diff --git a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css index 4b0e5eb7b4..53c201e3b6 100644 --- a/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css +++ b/src/components/BMDashboard/WeeklyProjectSummary/WeeklyProjectSummary.module.css @@ -42,7 +42,7 @@ max-width: 1280px; padding: 12px 16px; background: var(--section-bg); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgb(0 0 0 / 10%); border-radius: 8px; gap: 12px; } @@ -109,6 +109,7 @@ } /* Share Button — override Bootstrap .btn so it lines up with .form-control selects */ + /* Bootstrap .btn defaults to flex-shrink: 1; when the row is tight the button shrinks and the label overflows outside the painted background (overlapping the last dropdown). */ .weeklySummaryShareBtn { @@ -141,15 +142,15 @@ vertical-align: middle; } -.weeklySummaryShareBtn:hover:not(:disabled) { - background-color: var(--button-hover) !important; -} - .weeklySummaryShareBtn:disabled { opacity: 0.7; cursor: not-allowed; } +.weeklySummaryShareBtn:hover:not(:disabled) { + background-color: var(--button-hover) !important; +} + /* Spinner for PDF generation */ .spinner { display: inline-block; @@ -205,7 +206,7 @@ .darkMode .weeklyProjectSummaryCard { background: var(--card-bg); - box-shadow: 0 2px 5px rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 5px rgb(255 255 255 / 10%); } .financialSmall { @@ -222,7 +223,7 @@ padding: 0; background: white; border-radius: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 8px rgb(0 0 0 / 10%); overflow: hidden; margin-bottom: 20px; } @@ -298,6 +299,11 @@ background: var(--section-bg); } +.darkMode .weeklyProjectSummaryDashboardCategoryContent { + background-color: var(--section-bg); + border-top: 1px solid rgb(255 255 255 / 20%); +} + /* Adjust grid columns based on section size */ .weeklyProjectSummaryDashboardSection.half .weeklyProjectSummaryDashboardCategoryContent { grid-template-columns: repeat(2, 1fr); @@ -309,10 +315,10 @@ /* ---------------- DARK MODE VARIABLES ---------------- */ :root { - --bg-color: #ffffff; - --text-color: #000000; + --bg-color: #fff; + --text-color: #000; --card-bg: #fafafa; - --card-shadow: rgba(0, 0, 0, 0.1); + --card-shadow: rgb(0 0 0 / 10%); --section-bg: white; --section-title-bg: white; --section-title-hover: #e0e0e0; @@ -323,9 +329,9 @@ /* ---------------- DARK MODE STYLES ---------------- */ .darkMode { --bg-color: #1b2a41; - --text-color: #ffffff; + --text-color: #fff; --card-bg: #2b3e59; - --card-shadow: rgba(255, 255, 255, 0.1); + --card-shadow: rgb(255 255 255 / 10%); --section-bg: #253342; --section-title-bg: #2d4059; --section-title-hover: #3a506b; @@ -338,7 +344,7 @@ /* MERGED: keep background + FORCE all title text visible in dark mode */ .darkMode .weeklyProjectSummaryDashboardCategoryTitle { background: #2d4059; - color: #ffffff; + color: #fff; } .darkMode .weeklyProjectSummaryDashboardCategoryTitle:hover { @@ -346,16 +352,11 @@ } .darkMode .weeklyProjectSummaryDashboardCategoryTitle span { - color: #ffffff; + color: #fff; } .darkMode .weeklyProjectSummaryDashboardCategoryTitle * { - color: #ffffff; -} - -.darkMode .weeklyProjectSummaryDashboardCategoryContent { - background-color: var(--section-bg); - border-top: 1px solid rgba(255, 255, 255, 0.2); + color: #fff; } .darkMode .weeklySummaryHeaderContainer { @@ -371,7 +372,7 @@ /* Firefox */ scrollbar-width: thin; - scrollbar-color: rgba(0, 0, 0, 0.3) rgba(0, 0, 0, 0.1); + scrollbar-color: rgb(0 0 0 / 30%) rgb(0 0 0 / 10%); } /* Chrome/Safari scrollbar */ @@ -380,30 +381,30 @@ } .quantityOfMaterialsUsedChartTooltip::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.1); + background: rgb(0 0 0 / 10%); border-radius: 4px; } .quantityOfMaterialsUsedChartTooltip::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.3); + background: rgb(0 0 0 / 30%); border-radius: 4px; } /* Additional adjustments for dark mode if needed */ .darkMode .quantityOfMaterialsUsedChartTooltip::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.1); + background: rgb(255 255 255 / 10%); } .darkMode .quantityOfMaterialsUsedChartTooltip::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); + background: rgb(255 255 255 / 30%); } .darkMode .quantityOfMaterialsUsedChartTooltip { - scrollbar-color: rgba(255, 255, 255, 0.3) rgba(255, 255, 255, 0.1); + scrollbar-color: rgb(255 255 255 / 30%) rgb(255 255 255 / 10%); } /* ---------------- RESPONSIVE DESIGN ---------------- */ -@media (max-width: 1024px) { +@media (width <= 1024px) { .weeklySummaryHeaderContainer { flex-direction: column; align-items: stretch; @@ -421,7 +422,7 @@ } } -@media (max-width: 768px) { +@media (width <= 768px) { .weeklySummaryHeaderContainer { flex-direction: column; align-items: stretch; @@ -469,13 +470,14 @@ max-width: 284px; height: 190px; text-align: center; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 8px rgb(0 0 0 / 15%); padding: 20px; - border: 1px solid rgba(0, 0, 0, 0.1); + border: 1px solid rgb(0 0 0 / 10%); position: relative; } /* ---------------- RESPONSIVE GRID LAYOUT ---------------- */ + /* MERGED: keep ONLY one projectStatusGrid */ .projectStatusGrid { display: grid; @@ -487,6 +489,7 @@ max-width: 1600px; margin: auto; } + /* .projectStatusGrid { display: grid; grid-template-columns: repeat(6, 1fr); @@ -502,7 +505,7 @@ display: flex; align-items: center; justify-content: center; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); + box-shadow: 0 2px 5px rgb(0 0 0 / 15%); margin: 12px 0; } @@ -519,31 +522,31 @@ } /* ---------------- RESPONSIVE BREAKPOINTS ---------------- */ -@media (min-width: 1600px) { +@media (width >= 1600px) { .projectStatusGrid { grid-template-columns: repeat(6, 1fr); } } -@media (max-width: 1400px) { +@media (width <= 1400px) { .projectStatusGrid { grid-template-columns: repeat(4, 1fr); } } -@media (max-width: 1024px) { +@media (width <= 1024px) { .projectStatusGrid { grid-template-columns: repeat(3, 1fr); } } -@media (max-width: 768px) { +@media (width <= 768px) { .projectStatusGrid { grid-template-columns: repeat(2, 1fr); } } -@media (max-width: 576px) { +@media (width <= 576px) { .projectStatusGrid { grid-template-columns: repeat(1, 1fr); } diff --git a/src/components/CommunityPortal/Activities/ActivityList.module.css b/src/components/CommunityPortal/Activities/ActivityList.module.css index da9ae4a4eb..c4093629cf 100644 --- a/src/components/CommunityPortal/Activities/ActivityList.module.css +++ b/src/components/CommunityPortal/Activities/ActivityList.module.css @@ -145,11 +145,6 @@ border-radius: 0; } -.activityList.darkActivityList li strong { - color: #00f3ff; - font-size: 1.2rem; -} - .activityList li:hover { transform: translateY(-3px); box-shadow: 0 4px 8px rgb(0 0 0 / 10%); @@ -169,6 +164,11 @@ color: #5c9ce6 !important; } +.activityList.darkActivityList li strong { + color: #00f3ff; + font-size: 1.2rem; +} + .activityItem span { color: #4b5563; } @@ -288,11 +288,6 @@ accent-color: #3b82f6; } -.showPastToggle input[type="checkbox"]:focus-visible { - outline: none; - border-radius: 4px; -} - .darkShowPastToggle { color: #f1f5f9; } @@ -301,6 +296,11 @@ accent-color: #60a5fa; } +.showPastToggle input[type="checkbox"]:focus-visible { + outline: none; + border-radius: 4px; +} + /* Responsive */ @media (width <= 768px) { .filters { diff --git a/src/components/CommunityPortal/Reports/Participation/Participation.module.css b/src/components/CommunityPortal/Reports/Participation/Participation.module.css index 3c5392b1ae..1e88f4da37 100644 --- a/src/components/CommunityPortal/Reports/Participation/Participation.module.css +++ b/src/components/CommunityPortal/Reports/Participation/Participation.module.css @@ -1,6 +1,6 @@ /* ---------- General Styling ---------- */ .body { - font-family: 'Arial', sans-serif; + font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; @@ -15,7 +15,7 @@ max-width: 90%; margin: 0 auto; background-color: #1b2a41; - color: #ffffff; + color: #fff; } /* ---------- Page Header ---------- */ @@ -38,7 +38,7 @@ .landingPageHeaderDark { font-size: 2.2rem; font-weight: 700; - color: #ffffff !important; + color: #fff !important; letter-spacing: 0.5px; } @@ -75,7 +75,7 @@ transform: translateY(-1px); } -@media (max-width: 768px) { +@media (width <= 768px) { .landingPageHeaderContainer { flex-direction: column; align-items: center; @@ -138,7 +138,7 @@ background: white; border: 1px solid #ddd; border-radius: 5px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 4px rgb(0 0 0 / 10%); } /* ---------- Analytics Section ---------- */ @@ -155,9 +155,9 @@ .insights { flex: 1; min-width: 48%; - background-color: #ffffff; + background-color: #fff; border-radius: 10px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 8px rgb(0 0 0 / 10%); padding: 20px; box-sizing: border-box; } @@ -167,15 +167,15 @@ flex: 1; min-width: 48%; background-color: #1c2541; - color: #ffffff; + color: #fff; border-radius: 10px; border: 1px solid #ddd; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 8px rgb(0 0 0 / 10%); padding: 20px; box-sizing: border-box; } -@media (max-width: 768px) { +@media (width <= 768px) { .analyticsSection { flex-wrap: wrap; } @@ -199,7 +199,7 @@ justify-content: space-between; align-items: center; margin-bottom: 20px; - color: #ffffff; + color: #fff; } .trackingHeader h3 { @@ -209,7 +209,7 @@ } .trackingHeaderDark h3 { - color: #ffffff; + color: #fff; font-size: 1.3rem; margin: 0; } @@ -276,13 +276,14 @@ .trackingRateDark .trackingRateSubheading span, .trackingRateDark .trackingRateValue span { - color: #ffffff; + color: #fff; } .trackingRateValue { font-size: 0.9rem; display: inline-block; } + .trackingRate .trackingRateValue span.trackingRateValuePositive, .trackingRateDark .trackingRateValue span.trackingRateValuePositive { color: #f44336 !important; @@ -302,6 +303,7 @@ padding: 10px; background: #fff; } + .trackingListContainerDark { max-height: 600px; overflow-y: auto; @@ -309,14 +311,14 @@ border-radius: 10px; padding: 10px; background: #3a506b; - color: #ffffff; + color: #fff; } .trackingTable { width: 100%; border-collapse: collapse; table-layout: fixed; - word-wrap: break-word; + overflow-wrap: break-word; } .trackingTable thead th { @@ -327,7 +329,7 @@ .trackingTableDark thead th { background: #1c2541; - color: #ffffff; + color: #fff; } .trackingTable tbody td { @@ -352,8 +354,9 @@ display: flex; justify-content: space-between; } + .insightsHeaderDark { - color: #ffffff; + color: #fff; font-weight: bold; margin-bottom: 15px; display: flex; @@ -365,8 +368,9 @@ color: #333; margin: 0; } + .insightsHeaderDark h3 { - color: #ffffff; + color: #fff; font-size: 1.3rem; margin: 0; } @@ -462,7 +466,7 @@ .insightsDark .insightsTab { background-color: #3A506B; - color: #ffffff; + color: #fff; border-right: 1px solid #555; } @@ -494,12 +498,14 @@ color: #555; margin-right: 10px; } + .insightLabelDark { - color: #ffffff; + color: #fff; font-size: 0.9rem; flex: 1; margin-right: 10px; } + .insightBar { flex: 2; height: 8px; @@ -525,7 +531,7 @@ } .insightsPercentageDark { - color: #ffffff; + color: #fff; } .tooltipWrapper { @@ -559,7 +565,7 @@ .modalOverlay { position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.45); + background: rgb(0 0 0 / 45%); display: flex; justify-content: center; align-items: center; @@ -655,9 +661,17 @@ /* ---------- PDF/Print helpers ---------- */ -.pageBreakBefore { break-before: page; page-break-before: always; } -.pageBreakAfter { break-after: page; page-break-after: always; } -.avoidBreak { break-inside: avoid; page-break-inside: avoid; } +.pageBreakBefore { + break-before: page; +} + +.pageBreakAfter { + break-after: page; +} + +.avoidBreak { + break-inside: avoid; +} :global(html[data-exporting="true"]) .caseCards:global(.expanded), :global(html[data-exporting="true"]) .caseList:global(.expanded), @@ -672,6 +686,7 @@ border: 1px solid #555; background-color: #1e293b; /* Dark background */ } + /* Dark mode tab */ .insightsTabDarkMode { color: #ddd; @@ -735,22 +750,22 @@ display: grid !important; grid-template-columns: repeat(2, 1fr) !important; gap: 12px !important; - page-break-inside: auto !important; + break-inside: auto !important; } :global(.case-card-global) { break-inside: avoid-page !important; - page-break-inside: avoid !important; + break-inside: avoid !important; height: auto !important; } :global(.case-list-global) { - page-break-inside: auto !important; + break-inside: auto !important; } :global(.case-list-item-global) { break-inside: avoid-page !important; - page-break-inside: avoid !important; + break-inside: avoid !important; } .trackingListContainer, @@ -762,12 +777,10 @@ :global(.tracking-table-global) tr { break-inside: avoid; - page-break-inside: avoid; } .insightItem { break-inside: avoid; - page-break-inside: avoid; } :global(.view-switcher-global), @@ -789,23 +802,19 @@ :global(.tracking-table-global-dark) thead th { background: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; } .printOnly { display: block !important; } - .participationLandingPage, - .participationLandingPage * { + :global(#root) .participationLandingPage, + :global(#root) .participationLandingPage * { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; } - .participationLandingPage { - background: white !important; - } - :global(#root) .case-card-global { background: white !important; color: #333 !important; @@ -827,40 +836,40 @@ /* Dark mode styles for print */ .participationLandingPageDark { background: #1B2A41 !important; - color: #ffffff !important; + color: #fff !important; } .participationLandingPageDark :global(.case-card-global) { background: #3A506B !important; - color: #ffffff !important; + color: #fff !important; border: 1px solid #555 !important; } .participationLandingPageDark .trackingContainerDark { background: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border: 1px solid #555 !important; } .participationLandingPageDark .insightsDark { background: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; border: 1px solid #555 !important; } .participationLandingPageDark .trackingListContainerDark { background: #3A506B !important; - color: #ffffff !important; + color: #fff !important; } .participationLandingPageDark :global(.tracking-table-global-dark) thead th { background: #1C2541 !important; - color: #ffffff !important; + color: #fff !important; } .participationLandingPageDark .trackingRateDark { background: #3A506B !important; - color: #ffffff !important; + color: #fff !important; } /* Event badge colors for print */ @@ -907,9 +916,10 @@ .participationLandingPageDark .attendeesCountDark, .participationLandingPageDark .insightsLabelDark, .participationLandingPageDark .insightsPercentageDark { - color: #ffffff !important; + color: #fff !important; } } + /* Active tab dark mode */ .activeTabDarkMode { background-color: #3b82f6; @@ -931,18 +941,8 @@ } /* ---------- Modal ---------- */ -.modalOverlay { - position: fixed; - inset: 0; - background: rgba(0, 0, 0, 0.45); - display: flex; - align-items: center; - justify-content: center; - z-index: 1000; -} - .modalContent { - background: #ffffff; + background: #fff; width: 420px; max-height: 80vh; border-radius: 10px; @@ -953,13 +953,7 @@ .modalContentDark { background: #1C2541; - color: #ffffff; -} - -.modalHeader { - display: flex; - justify-content: space-between; - align-items: center; + color: #fff; } .closeBtn { @@ -1030,41 +1024,30 @@ /* ===== Dark mode fixes ===== */ .participationLandingPageDark :global(button) { - color: #ffffff !important; + color: #fff !important; } .participationLandingPageDark :global(.btn), .participationLandingPageDark :global(.btn-light), .participationLandingPageDark :global(.btn-outline-secondary) { - color: #ffffff !important; + color: #fff !important; background-color: #3a506b !important; border-color: #3a506b !important; } .participationLandingPageDark :global(.btn-primary), .participationLandingPageDark :global(.active) { - color: #ffffff !important; + color: #fff !important; } .trackingContainerDark .trackingFilters select { background-color: #3A506B; - color: #ffffff; + color: #fff; border: 1px solid #3A506B; } .insightsDark .insightsFilters select { background-color: #3A506B; - color: #ffffff; + color: #fff; border: 1px solid #3A506B; } - -.insightsDark .insightsTab { - background-color: #3A506B; - color: #ffffff; - border-right: 1px solid #555; -} - -.insightsDark .insightsTab.activeTab { - background-color: #007bff; - color: #ffffff; -} \ No newline at end of file diff --git a/src/components/HGNHelpSkillsDashboard/FeedbackModal.module.css b/src/components/HGNHelpSkillsDashboard/FeedbackModal.module.css index 2283cda43e..cb0351f272 100644 --- a/src/components/HGNHelpSkillsDashboard/FeedbackModal.module.css +++ b/src/components/HGNHelpSkillsDashboard/FeedbackModal.module.css @@ -62,7 +62,7 @@ max-height: 200px; overflow-y: auto; z-index: 1000; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 6px rgb(0 0 0 / 10%); margin-top: 4px; } @@ -141,16 +141,13 @@ body.dark-mode .suggestion-link { color: #ccc; } -body.dark-mode .star { - color: #555; -} - -body.dark-mode .star:hover { - color: #ffc107; +:global(body.dark-mode) .star { + color: #555 !important; } -body.dark-mode .star.selected { - color: #ffc107; +:global(body.dark-mode) .star:hover, +:global(body.dark-mode) .star.selected { + color: #ffc107 !important; } body.dark-mode .autocomplete-dropdown { @@ -158,6 +155,10 @@ body.dark-mode .autocomplete-dropdown { border-color: #444; } +:global(body.dark-mode) .autocomplete-item { + border-bottom-color: #444 !important; +} + body.dark-mode .autocomplete-item { border-bottom-color: #444; } @@ -177,6 +178,7 @@ body.dark-mode .user-details { body.dark-mode .loading { color: #fff; } + /* Dark Mode Support - Issues #6 & #7 */ :global(body.dark-mode) .feedback-modal :global(.modal-content) { background-color: #1e1e1e !important; @@ -222,10 +224,6 @@ body.dark-mode .loading { border-color: #444 !important; } -:global(body.dark-mode) .autocomplete-item { - border-bottom-color: #444 !important; -} - :global(body.dark-mode) .autocomplete-item:hover { background-color: #3d3d3d !important; } @@ -248,13 +246,4 @@ body.dark-mode .loading { :global(body.dark-mode) .loading { color: #fff !important; -} - -:global(body.dark-mode) .star { - color: #555 !important; -} - -:global(body.dark-mode) .star:hover, -:global(body.dark-mode) .star.selected { - color: #ffc107 !important; } \ No newline at end of file diff --git a/src/components/LBDashboard/Components/ImageCarousel.jsx b/src/components/LBDashboard/Components/ImageCarousel.jsx index 2e254ed1e5..ff3337c4e1 100644 --- a/src/components/LBDashboard/Components/ImageCarousel.jsx +++ b/src/components/LBDashboard/Components/ImageCarousel.jsx @@ -1,10 +1,21 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; import styles from './ImageCarousel.module.css'; -export default function ImageCarousel({ images }) { +const getClassNames = (baseClass, darkClass, darkMode) => + `${baseClass} ${darkMode ? darkClass : ''}`; + +export default function ImageCarousel({ images, darkMode = false }) { const [currentIndex, setCurrentIndex] = useState(0); - if (!images || images.length === 0) return

No images available

; + useEffect(() => { + setCurrentIndex(0); + }, [images]); + + if (!images || images.length === 0) + return ( +

No images available

+ ); const handleNext = () => { setCurrentIndex(prev => (prev + 1) % images.length); @@ -19,35 +30,57 @@ export default function ImageCarousel({ images }) { }; return ( -
-
+
+
{images.map((image, index) => ( {`Slide ))}
- - -
+
{images.map((image, index) => ( handleIndicatorClick(index)} onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { @@ -60,3 +93,8 @@ export default function ImageCarousel({ images }) {
); } + +ImageCarousel.propTypes = { + images: PropTypes.arrayOf(PropTypes.string), + darkMode: PropTypes.bool, +}; diff --git a/src/components/LBDashboard/Components/ImageCarousel.module.css b/src/components/LBDashboard/Components/ImageCarousel.module.css index 4917163506..cbe5063c28 100644 --- a/src/components/LBDashboard/Components/ImageCarousel.module.css +++ b/src/components/LBDashboard/Components/ImageCarousel.module.css @@ -9,6 +9,7 @@ .carouselWrapper { width: 100%; + min-height: 200px; overflow: hidden; } @@ -29,7 +30,7 @@ position: absolute; top: 50%; transform: translateY(-50%); - background-color: rgba(0, 0, 0, 0.5); + background-color: rgb(0 0 0 / 50%); color: white; border: none; padding: 10px; @@ -38,11 +39,11 @@ z-index: 10; } -.left { +.imgArrow.left { left: 10px; } -.right { +.imgArrow.right { right: 10px; } @@ -63,6 +64,36 @@ transition: background-color 0.3s ease; } -.active { +.indicator.active { background-color: #333; } + +/* Dark mode styles */ +.carouselContainer--dark { + /* Container dark mode styles if needed */ +} + +.imgArrow--dark { + background-color: rgb(255 255 255 / 70%) !important; + color: #000 !important; +} + +.imgArrow--dark:hover { + background-color: rgb(255 255 255 / 90%) !important; +} + +.indicator--dark { + background-color: #666 !important; +} + +.indicator--dark:hover { + background-color: #888 !important; +} + +.active--dark { + background-color: #fff !important; +} + +.no-images--dark { + color: #e1e1e1 !important; +} \ No newline at end of file diff --git a/src/components/LBDashboard/Header.jsx b/src/components/LBDashboard/Header.jsx index f6cc81473a..d1386fdcd4 100644 --- a/src/components/LBDashboard/Header.jsx +++ b/src/components/LBDashboard/Header.jsx @@ -1,5 +1,6 @@ -import { connect } from 'react-redux'; -import { useState } from 'react'; +import { connect, useSelector } from 'react-redux'; +import { useState, useCallback } from 'react'; +import { useHistory, Link } from 'react-router-dom'; import Navbar from 'react-bootstrap/Navbar'; import Container from 'react-bootstrap/Container'; @@ -7,45 +8,122 @@ import Nav from 'react-bootstrap/Nav'; import { FiUser } from 'react-icons/fi'; import { BsChat } from 'react-icons/bs'; -import { Link } from 'react-router-dom'; import { IoNotificationsOutline } from 'react-icons/io5'; +import { FIXED_VILLAGES } from './Home/data.jsx'; +import itemStyles from './WishList/ItemOverview.module.css'; + +const cx = (base, darkClass, darkMode) => `${base} ${darkMode ? darkClass : ''}`.trim(); + function LBDashboardHeader(props) { const [selectedVillage, setSelectedVillage] = useState(''); const { authUser } = props; + const darkMode = useSelector(state => state.theme.darkMode); + const history = useHistory(); + + const selectorWrapperStyle = darkMode + ? { + backgroundColor: '#1c2541', + border: '1px solid #2f3b59', + } + : undefined; + + const selectorStyle = darkMode + ? { + backgroundColor: 'transparent', + color: '#ffffff', + } + : undefined; + + const goButtonStyle = darkMode + ? { + backgroundColor: '#4f6fdc', + borderColor: '#89a2ff', + } + : undefined; + + const handleGoClick = useCallback(() => { + const qs = selectedVillage ? `?village=${encodeURIComponent(selectedVillage)}` : ''; + history.push(`/lbdashboard/listingshome${qs}`); + }, [history, selectedVillage]); return ( - + - {/* Left Section - Village Selector */} -
-
- setSelectedVillage(e.target.value)} + style={selectorStyle} + aria-label="Filter by village" + > + + {FIXED_VILLAGES.map(v => ( + + ))}
-
+
+
- {/* Right Section - User Info and Icons */} -
+

WELCOME {authUser?.name || 'USER_NAME'}

-
+
diff --git a/src/components/LBDashboard/Home/Home.jsx b/src/components/LBDashboard/Home/Home.jsx index 7505916507..c67db69aec 100644 --- a/src/components/LBDashboard/Home/Home.jsx +++ b/src/components/LBDashboard/Home/Home.jsx @@ -22,7 +22,7 @@ import { import { BsSliders } from 'react-icons/bs'; import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'; import 'leaflet/dist/leaflet.css'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; import L from 'leaflet'; import logo from '../../../assets/images/logo2.png'; import { fetchVillages, fetchListings, fetchBiddings, FIXED_VILLAGES } from './data'; @@ -42,6 +42,51 @@ const unitIcon = new L.Icon({ popupAnchor: [1, -34], }); +const VILLAGE_SUFFIX = 'village'; + +const stripVillageSuffix = value => { + const input = String(value || '').trim(); + if (!input) return ''; + const lower = input.toLowerCase(); + if (!lower.endsWith(VILLAGE_SUFFIX)) return input; + return input.slice(0, -VILLAGE_SUFFIX.length).trim(); +}; + +const buildApiFilters = (selectedVillage, dateRange) => { + const filters = {}; + if (selectedVillage) filters.village = selectedVillage; + if (dateRange.startDate) filters.availableFrom = dateRange.startDate; + if (dateRange.endDate) filters.availableTo = dateRange.endDate; + return filters; +}; + +const withVillageFallback = async ( + fetcher, + currentPage, + pageSize, + filters, + selectedVillage, + villageFilterCandidates, +) => { + let response = await fetcher(currentPage, pageSize, filters); + if (!selectedVillage || (response.items || []).length > 0) return response; + + const fallbackVillages = villageFilterCandidates(selectedVillage).filter( + v => v !== filters.village, + ); + for (const villageCandidate of fallbackVillages) { + const retryResponse = await fetcher(currentPage, pageSize, { + ...filters, + village: villageCandidate, + }); + if ((retryResponse.items || []).length > 0) { + response = retryResponse; + break; + } + } + return response; +}; + function Home() { const [viewMode, setViewMode] = useState('Grid'); const [activeTab, setActiveTab] = useState('listings'); @@ -74,6 +119,28 @@ function Home() { const pageSizeOptions = [12, 24, 36, 48]; const navigate = useHistory(); + const location = useLocation(); + + const villageFilterCandidates = useCallback(village => { + const raw = String(village || '').trim(); + if (!raw) return []; + const noSuffix = stripVillageSuffix(raw); + const withSuffix = `${noSuffix} Village`; + return Array.from(new Set([raw, noSuffix, withSuffix])).filter(Boolean); + }, []); + + const normalizeVillageName = useCallback( + village => stripVillageSuffix(village).toLowerCase(), + [], + ); + + useEffect(() => { + const params = new URLSearchParams(location.search); + const village = params.get('village'); + if (village) { + setSelectedVillage(village); + } + }, [location.search]); useEffect(() => { const fetchVillagesData = async () => { @@ -106,69 +173,77 @@ function Home() { ); useEffect(() => { + const loadListings = async filters => { + const listingsData = await withVillageFallback( + fetchListings, + pagination.currentPage, + pagination.pageSize, + filters, + selectedVillage, + villageFilterCandidates, + ); + setAllListings(listingsData.items || []); + setPagination(prev => ({ + ...prev, + totalPages: listingsData.pagination.totalPages || 1, + })); + }; + + const loadBiddings = async filters => { + const biddingsData = await withVillageFallback( + fetchBiddings, + pagination.currentPage, + pagination.pageSize, + filters, + selectedVillage, + villageFilterCandidates, + ); + setAllBiddings(biddingsData.items || []); + setPagination(prev => ({ + ...prev, + totalPages: biddingsData.pagination.totalPages || 1, + })); + }; + const fetchData = async () => { try { setIsLoading(true); - - const filters = {}; - if (selectedVillage) filters.village = selectedVillage; - if (dateRange.startDate) filters.availableFrom = dateRange.startDate; - if (dateRange.endDate) filters.availableTo = dateRange.endDate; - - if (activeTab === 'listings') { - try { - const listingsData = await fetchListings( - pagination.currentPage, - pagination.pageSize, - filters, - ); - setAllListings(listingsData.items || []); - console.log('fetching listings', listingsData.items); - setPagination(prev => ({ - ...prev, - totalPages: listingsData.pagination.totalPages || 1, - })); - } catch (error) { - console.error('API Error (Listings):', error); - setError('Failed to fetch listings. Please try again later.'); - - setAllListings([]); - } - } else { - console.log('fetching biddings'); - try { - const biddingsData = await fetchBiddings( - pagination.currentPage, - pagination.pageSize, - filters, - ); - - setAllBiddings(biddingsData.items || []); - - setPagination(prev => ({ - ...prev, - totalPages: biddingsData.pagination.totalPages || 1, - })); - } catch (error) { - console.error('API Error (Biddings):', error); - setError('Failed to fetch biddings. Please try again later.'); - - setAllBiddings([]); - } - } + const filters = buildApiFilters(selectedVillage, dateRange); + if (activeTab === 'listings') await loadListings(filters); + else await loadBiddings(filters); setIsLoading(false); - } catch (err) { + } catch (error) { + console.error(`API Error (${activeTab}):`, error); + if (activeTab === 'listings') setAllListings([]); + else setAllBiddings([]); setError('Failed to load data. Please try again later.'); setIsLoading(false); } }; fetchData(); - }, [pagination.currentPage, pagination.pageSize, selectedVillage, dateRange, activeTab]); - - const currentItems = activeTab === 'listings' ? allListings : allBiddings; - const allItems = activeTab === 'listings' ? allListings : allBiddings; + }, [ + pagination.currentPage, + pagination.pageSize, + selectedVillage, + dateRange, + activeTab, + villageFilterCandidates, + ]); + + const baseItems = activeTab === 'listings' ? allListings : allBiddings; + const currentItems = useMemo(() => { + if (!selectedVillage) return baseItems; + const selectedKey = normalizeVillageName(selectedVillage); + const strictMatches = baseItems.filter( + item => normalizeVillageName(item.village) === selectedKey, + ); + // If backend returns unresolved village ids/names that do not match dropdown labels, + // avoid showing an empty page while data actually exists. + return strictMatches.length > 0 ? strictMatches : baseItems; + }, [baseItems, normalizeVillageName, selectedVillage]); + const allItems = currentItems; const handlePageChange = useCallback( newPage => { @@ -397,7 +472,14 @@ function Home() { onClick={() => handlePropertySelect(unit)} >
- {unit.title} + {unit.title} { + e.currentTarget.onerror = null; + e.currentTarget.src = 'https://picsum.photos/seed/lb-fallback/600/400'; + }} + />
diff --git a/src/components/LBDashboard/Home/Home.module.css b/src/components/LBDashboard/Home/Home.module.css index fd2bafa15b..50edf2a859 100644 --- a/src/components/LBDashboard/Home/Home.module.css +++ b/src/components/LBDashboard/Home/Home.module.css @@ -11,9 +11,6 @@ transition: transform 0.3s ease; } -.lbLogo img:hover { - transform: scale(1.05); -} .lbOutsideContainer { width: 100%; @@ -28,8 +25,9 @@ margin: 0 auto; padding: 20px; background-color: #fff; + /* border-radius: 8px; */ - box-shadow: 0 2px 10px rgba(0,0,0,0.05); + box-shadow: 0 2px 10px rgb(0 0 0 / 5%); } /* Navbar Styling */ @@ -41,9 +39,10 @@ align-items: center; padding: 0 20px; background-color: #9dd425; + /* border-radius: 8px; */ height: 60px; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); + box-shadow: 0 2px 10px rgb(0 0 0 / 10%); } .lbNavLeft { @@ -274,13 +273,13 @@ gap: 25px; } -@media (max-width: 1100px) { +@media (width <= 1100px) { .lbPropertiesContainer.lbGridView { grid-template-columns: repeat(2, 1fr); } } -@media (max-width: 768px) { +@media (width <= 768px) { .lbPropertiesContainer.lbGridView { grid-template-columns: 1fr; } @@ -296,15 +295,16 @@ .lbPropertyCard { background: #fff; border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); + box-shadow: 0 2px 8px rgb(0 0 0 / 10%); overflow: hidden; transition: transform 0.3s, box-shadow 0.3s; cursor: pointer; + color: #1f2937; } .lbPropertyCard:hover { transform: translateY(-5px); - box-shadow: 0 5px 15px rgba(0,0,0,0.15); + box-shadow: 0 5px 15px rgb(0 0 0 / 15%); } .lbGridView .lbPropertyCard { @@ -333,10 +333,6 @@ transition: transform 0.5s; } -.lbPropertyCard:hover .lbPropertyImage img { - transform: scale(1.05); -} - .lbListView .lbPropertyImage { width: 250px; height: 180px; @@ -349,6 +345,8 @@ flex-direction: column; justify-content: space-between; flex-grow: 1; + background: #fff; + color: #1f2937; } .lbListView .lbPropertyDetails { @@ -361,17 +359,20 @@ color: #2c4f3c; margin: 0 0 5px; font-size: 1.2rem; + text-shadow: none; } .lbPropertyDetails p { color: #666; margin: 0 0 10px; + text-shadow: none; } .lbPrice { font-weight: bold; color: #2c4f3c; font-size: 1.1rem; + text-shadow: none; } /* Loading and Error States */ @@ -419,8 +420,9 @@ transition: all 0.3s; } -.lbPaginationButton:hover:not(:disabled) { - background: #e0e0e0; +.lbVillagePagination .lbPaginationButton { + padding: 5px 10px; + font-size: 14px; } .lbPaginationButton:disabled { @@ -428,6 +430,10 @@ cursor: not-allowed; } +.lbPaginationButton:hover:not(:disabled) { + background: #e0e0e0; +} + .lbPaginationInfo { margin: 0 10px; color: #666; @@ -450,11 +456,8 @@ /* Modal Styles */ .lbModalOverlay { position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.5); + inset: 0; + background-color: rgb(0 0 0 / 50%); display: flex; justify-content: center; align-items: center; @@ -472,15 +475,16 @@ max-width: 800px; max-height: 90vh; overflow-y: auto; - box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); - animation: modalFadeIn 0.3s ease; + box-shadow: 0 5px 20px rgb(0 0 0 / 15%); + animation: modal-fade-in 0.3s ease; } -@keyframes modalFadeIn { +@keyframes modal-fade-in { from { opacity: 0; transform: translateY(-20px); } + to { opacity: 1; transform: translateY(0); @@ -719,11 +723,6 @@ font-size: 14px; } -.lbVillagePagination .lbPaginationButton { - padding: 5px 10px; - font-size: 14px; -} - .lbVillageChips { max-height: 300px; overflow-y: auto; @@ -818,6 +817,14 @@ object-fit: cover; } +.lbLogo img:hover { + transform: scale(1.05); +} + +.lbPropertyCard:hover .lbPropertyImage img { + transform: scale(1.05); +} + .lbPropertyDetailsInfo { display: grid; grid-template-columns: repeat(2, 1fr); @@ -903,7 +910,7 @@ } /* Responsive Adjustments */ -@media (max-width: 768px) { +@media (width <= 768px) { .lbNavbar { flex-direction: column; height: auto; diff --git a/src/components/LBDashboard/Home/data.jsx b/src/components/LBDashboard/Home/data.jsx index 2d774f1ccf..163a20d595 100644 --- a/src/components/LBDashboard/Home/data.jsx +++ b/src/components/LBDashboard/Home/data.jsx @@ -2,6 +2,8 @@ import { ENDPOINTS } from '~/utils/URL'; import httpService from '../../../services/httpService'; const API_BASE_URL = ENDPOINTS.LB_LISTINGS_BASE; +const VILLAGES_URL = `${ENDPOINTS.APIEndpoint()}/villages`; +const OBJECT_ID_REGEX = /^[a-f\d]{24}$/i; export const FIXED_VILLAGES = [ 'Earthbag', @@ -14,7 +16,7 @@ export const FIXED_VILLAGES = [ 'City Center', ]; -const DEFAULT_IMAGE = 'https://via.placeholder.com/300x200?text=Unit'; +const DEFAULT_IMAGE = 'https://picsum.photos/seed/lb-unit/600/400'; /** * Transform API listing to match the frontend data format @@ -71,9 +73,9 @@ export const fetchVillages = async () => { try { ensureAuthentication(); - const response = await httpService.get(`${API_BASE_URL}/villages`); - - const apiVillages = response.data.data || []; + const response = await httpService.get(VILLAGES_URL); + const rawVillages = Array.isArray(response.data) ? response.data : response.data?.data || []; + const apiVillages = rawVillages.map(v => (typeof v === 'string' ? v : v?.name)).filter(Boolean); const allVillages = [...new Set([...FIXED_VILLAGES, ...apiVillages])]; @@ -99,17 +101,18 @@ export const fetchVillages = async () => { export const fetchListings = async (page = 1, size = 12, filters = {}) => { try { ensureAuthentication(); + const headers = { + page: String(page), + size: String(size), + }; + if (filters.availableFrom) headers.availableFrom = filters.availableFrom; + if (filters.availableTo) headers.availableTo = filters.availableTo; + // Backend expects village ObjectId in header; ignore display names. + if (filters.village && OBJECT_ID_REGEX.test(String(filters.village))) { + headers.village = String(filters.village); + } - const params = new URLSearchParams({ - page, - size, - }); - - if (filters.village) params.append('village', filters.village); - if (filters.availableFrom) params.append('availableFrom', filters.availableFrom); - if (filters.availableTo) params.append('availableTo', filters.availableTo); - - const response = await httpService.get(`${API_BASE_URL}/listing?${params.toString()}`); + const response = await httpService.get(`${API_BASE_URL}/listings`, { headers }); const responseData = response.data; const items = responseData.data?.items || responseData.items || []; @@ -156,17 +159,17 @@ export const fetchListings = async (page = 1, size = 12, filters = {}) => { export const fetchBiddings = async (page = 1, size = 12, filters = {}) => { try { ensureAuthentication(); + const headers = { + page: String(page), + size: String(size), + }; + if (filters.availableFrom) headers.availableFrom = filters.availableFrom; + if (filters.availableTo) headers.availableTo = filters.availableTo; + if (filters.village && OBJECT_ID_REGEX.test(String(filters.village))) { + headers.village = String(filters.village); + } - const params = new URLSearchParams({ - page, - size, - }); - - if (filters.village) params.append('village', filters.village); - if (filters.availableFrom) params.append('availableFrom', filters.availableFrom); - if (filters.availableTo) params.append('availableTo', filters.availableTo); - - const response = await httpService.get(`${API_BASE_URL}/biddings?${params.toString()}`); + const response = await httpService.get(`${API_BASE_URL}/biddings`, { headers }); const responseData = response.data; const items = responseData.data?.items || responseData.items || []; diff --git a/src/components/LBDashboard/Messaging/LBMessaging.jsx b/src/components/LBDashboard/Messaging/LBMessaging.jsx index 46a048d089..3be697ca1b 100644 --- a/src/components/LBDashboard/Messaging/LBMessaging.jsx +++ b/src/components/LBDashboard/Messaging/LBMessaging.jsx @@ -1,17 +1,18 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useMemo, useCallback } from 'react'; +import { useLocation, useHistory } from 'react-router-dom'; import styles from './LBMessaging.module.css'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faBell, faLocationArrow, faSearch } from '@fortawesome/free-solid-svg-icons'; import { useDispatch, useSelector } from 'react-redux'; -import { getUserProfileBasicInfo } from '../../../actions/userManagement'; +import { getUserProfileBasicInfo } from '~/actions/userManagement'; import { fetchUserPreferences, updateUserPreferences, -} from '../../../actions/lbdashboard/userPreferenceActions'; +} from '~/actions/lbdashboard/userPreferenceActions'; import { toast } from 'react-toastify'; -import { fetchExistingChats, fetchMessages } from '../../../actions/lbdashboard/messagingActions'; +import { fetchExistingChats, fetchMessages } from '~/actions/lbdashboard/messagingActions'; import axios from 'axios'; -import { ENDPOINTS } from '../../../utils/URL'; +import { ENDPOINTS } from '~/utils/URL'; import { initMessagingSocket, getMessagingSocket, @@ -19,113 +20,12 @@ import { markMessagesAsReadViaSocket, } from '../../../utils/messagingSocket'; import logo from '../../../assets/images/logo2.png'; -import Header from '../../Header/Header'; - -const DEFAULT_PROFILE_PICTURE = '/pfp-default-header.png'; - -function MessagingContactItem({ user, contactNameClassName, onSelect }) { - return ( -
onSelect(user)} - onKeyDown={event => { - if (event.key === 'Enter' || event.key === ' ') { - onSelect(user); - } - }} - > - User Profile { - event.target.onerror = null; - event.target.src = DEFAULT_PROFILE_PICTURE; - }} - /> -
-
- {user.firstName} {user.lastName} -
-
-
- ); -} - -function MessagingContactsPanel({ - isSearchOpen, - placeholder, - searchQuery, - onSearchChange, - onToggleSearch, - searchResults, - existingChats, - onSelectUser, - headerClassName, - bodyClassName, - contactNameClassName, - searchIconClassName, -}) { - const visibleUsers = isSearchOpen ? searchResults : existingChats; - - return ( - <> - {isSearchOpen ? ( -
- -
{ - if (event.key === 'Enter' || event.key === ' ') { - onToggleSearch(); - } - }} - > - multiply -
-
- ) : ( -
-

Messages

-
- -
-
- )} -
- {visibleUsers.length === 0 ? ( -

No chats available.

- ) : ( - visibleUsers.map(user => ( - - )) - )} -
- - ); -} export default function LBMessaging() { const dispatch = useDispatch(); + const location = useLocation(); + const history = useHistory(); + const darkMode = useSelector(state => state.theme.darkMode); const [selectedUser, updateSelectedUser] = useState({}); const [searchQuery, setSearchQuery] = useState(''); const [messageText, setMessageText] = useState(''); @@ -136,34 +36,38 @@ export default function LBMessaging() { const [bellDropdownActive, setBellDropdownActive] = useState(false); const [showContacts, setShowContacts] = useState(false); - const [notificationSettings, setNotificationSettings] = useState({ - notifyInApp: false, - notifyEmail: false, - notifySms: false, - smsPhone: '', - smsPhoneMasked: '', - }); + const [selectedOption, setSelectedOption] = useState({}); const messageEndRef = useRef(null); const menuRef = useRef(null); - const lastSendAtRef = useRef(0); - const lastMessageRef = useRef(''); - const lastNotificationCountRef = useRef(0); - - const users = useSelector(state => state.allUserProfilesBasicInfo) || { - userProfilesBasicInfo: [], - }; - const userProfilesBasicInfo = users.userProfilesBasicInfo || []; - const auth = useSelector(state => state.auth.user) || {}; - const authUserId = auth?.userid || auth?.userId || auth?._id; - const messagesState = useSelector(state => state.messages) || {}; - const existingChats = messagesState.existingChats || []; - const { - messages = [], - loading: messagesLoading = false, - notifications = [], - error, - } = messagesState; - const unreadNotificationsCount = notifications.length; + const appliedListingSelectionRef = useRef(null); + + const users = useSelector(state => state.allUserProfilesBasicInfo); + const wishlists = useSelector(state => state.wishlistItem?.wishlists); + const auth = useSelector(state => state.auth.user); + const currentUserId = auth?.userid ?? auth?.userId ?? auth?._id; + const messagesState = useSelector(state => state.messages) ?? {}; + const existingChats = Array.isArray(messagesState.existingChats) + ? messagesState.existingChats + : []; + const messages = Array.isArray(messagesState.messages) ? messagesState.messages : []; + const messagesLoading = messagesState.loading ?? false; + const safeSearchResults = Array.isArray(searchResults) ? searchResults : []; + + const sidebarContacts = useMemo(() => { + const chats = [...existingChats]; + const sid = selectedUser?.userId; + if (!sid) return chats; + const exists = chats.some(c => String(c.userId ?? c._id) === String(sid)); + if (!exists) { + chats.unshift({ + userId: sid, + firstName: selectedUser.firstName, + lastName: selectedUser.lastName, + profilePic: selectedUser.profilePic, + }); + } + return chats; + }, [existingChats, selectedUser]); useEffect(() => { if (messageEndRef.current) { @@ -171,26 +75,20 @@ export default function LBMessaging() { } }, [messages]); - useEffect(() => { - if (error) { - toast.error(error); - } - }, [error]); - const searchUserProfiles = async query => { try { const { data } = await axios.get(`${ENDPOINTS.LB_SEARCH_USERS}?query=${query}`); - setSearchResults(data); + setSearchResults(Array.isArray(data) ? data : []); } catch (error) { Error('Error searching user profiles:', error); } }; useEffect(() => { - if (userProfilesBasicInfo.length === 0) { + if ((users?.userProfilesBasicInfo?.length ?? 0) === 0) { dispatch(getUserProfileBasicInfo()); } - }, [dispatch, userProfilesBasicInfo, authUserId]); + }, [dispatch, users?.userProfilesBasicInfo?.length, currentUserId]); useEffect(() => { const token = localStorage.getItem('token'); @@ -202,7 +100,7 @@ export default function LBMessaging() { socket.close(); } }; - }, [authUserId]); + }, [currentUserId]); useEffect(() => { if (selectedUser.userId) { @@ -223,49 +121,33 @@ export default function LBMessaging() { }, [selectedUser]); useEffect(() => { - if (authUserId) { - dispatch(fetchExistingChats(authUserId)); + if (currentUserId) { + dispatch(fetchExistingChats(currentUserId)); } - }, [dispatch, authUserId]); + }, [dispatch, currentUserId]); useEffect(() => { - if (!authUserId) { - setNotificationSettings({ + if (selectedUser.userId) { + dispatch(fetchUserPreferences(currentUserId, selectedUser.userId)).then(response => { + if (response) { + setSelectedOption({ + notifyInApp: response.notifyInApp || false, + notifyEmail: response.notifyEmail || false, + }); + } else { + setSelectedOption({ + notifyInApp: false, + notifyEmail: false, + }); + } + }); + } else { + setSelectedOption({ notifyInApp: false, notifyEmail: false, - notifySms: false, - smsPhone: '', }); - return; } - - dispatch(fetchUserPreferences(authUserId, null)).then(response => { - if (response) { - setNotificationSettings({ - notifyInApp: response.notifyInApp || false, - notifyEmail: response.notifyEmail || false, - notifySms: response.notifySms || false, - smsPhone: '', - smsPhoneMasked: response.smsPhoneMasked || getMaskedPhone(response.smsPhone) || '', - }); - } else { - setNotificationSettings({ - notifyInApp: false, - notifyEmail: false, - notifySms: false, - smsPhone: '', - smsPhoneMasked: '', - }); - } - }); - }, [dispatch, authUserId]); - - useEffect(() => { - if (notifications.length > lastNotificationCountRef.current) { - toast.info('New message received.'); - } - lastNotificationCountRef.current = notifications.length; - }, [notifications]); + }, [dispatch, currentUserId, selectedUser.userId]); useEffect(() => { const handleClickOutside = event => { @@ -280,133 +162,130 @@ export default function LBMessaging() { }; }, []); - const updateSelection = user => { - const newSelectedUser = { - userId: user.userId || user._id, - firstName: user.firstName, - lastName: user.lastName, - profilePic: user.profilePic || '/pfp-default-header.png', - }; - - updateSelectedUser(newSelectedUser); - - if (newSelectedUser.userId) { - if (authUserId) { - dispatch(fetchMessages(authUserId, newSelectedUser.userId)); + const updateSelection = useCallback( + user => { + const newSelectedUser = { + userId: user.userId || user._id, + firstName: user.firstName, + lastName: user.lastName, + profilePic: user.profilePic || '/pfp-default-header.png', + }; + + updateSelectedUser(newSelectedUser); + + if (newSelectedUser.userId && currentUserId) { + dispatch(fetchMessages(currentUserId, newSelectedUser.userId)); + } else if (!newSelectedUser.userId) { + toast.error('Invalid user selected. Please try again.'); } - } else { - Error('Invalid user selected:', user); - toast.error('Invalid user selected. Please try again.'); - } - }; + }, + [currentUserId, dispatch], + ); - const persistNotificationPreferences = () => { - if (!authUserId) { - toast.error('Unable to update preferences without a valid user.'); - return Promise.reject(new Error('Missing user ID')); - } - const cleanedPhone = notificationSettings.smsPhone.replace(/[^\d+]/g, ''); - const digits = cleanedPhone.replace(/\D/g, ''); - if (notificationSettings.notifySms && digits.length > 0 && digits.length !== 10) { - toast.error('Please enter a valid 10-digit phone number for SMS notifications.'); - return Promise.reject(new Error('Invalid phone number')); + useEffect(() => { + const params = new URLSearchParams(location.search); + const listingId = params.get('listingId'); + if (!listingId) { + appliedListingSelectionRef.current = null; + return; } + if (!wishlists?.length || !currentUserId) return; - const payload = { - notifyInApp: notificationSettings.notifyInApp, - notifyEmail: notificationSettings.notifyEmail, - notifySms: notificationSettings.notifySms, - }; - if (digits.length > 0) { - payload.smsPhone = cleanedPhone; - } + const wishItem = wishlists.find(w => String(w.id) === String(listingId)); + const host = wishItem?.host; + if (!host?.userId) return; - return dispatch(updateUserPreferences(authUserId, null, payload)).then(() => { - // Refresh preferences after saving - return dispatch(fetchUserPreferences(authUserId, null)).then(response => { - const refreshed = response?.payload || response; - if (refreshed) { - setNotificationSettings(prev => ({ - ...prev, - notifyInApp: refreshed.notifyInApp || false, - notifyEmail: refreshed.notifyEmail || false, - notifySms: refreshed.notifySms || false, - smsPhoneMasked: refreshed.smsPhoneMasked || getMaskedPhone(refreshed.smsPhone) || '', - })); - } - }); + if (appliedListingSelectionRef.current === listingId) return; + appliedListingSelectionRef.current = listingId; + + const profiles = users?.userProfilesBasicInfo ?? []; + const matched = profiles.find(p => String(p._id) === String(host.userId)); + + updateSelection({ + userId: host.userId, + firstName: matched?.firstName ?? host.firstName, + lastName: matched?.lastName ?? host.lastName, + profilePic: matched?.profilePic || host.profilePic || '/pfp-default-header.png', }); - }; + + params.delete('listingId'); + const nextSearch = params.toString(); + history.replace({ + pathname: location.pathname, + search: nextSearch ? `?${nextSearch}` : '', + hash: location.hash, + }); + }, [ + location.search, + location.pathname, + location.hash, + wishlists, + users?.userProfilesBasicInfo, + currentUserId, + updateSelection, + history, + ]); + + useEffect(() => { + const uid = selectedUser?.userId; + if (!uid) return; + const profiles = users?.userProfilesBasicInfo ?? []; + if (!profiles.length) return; + const matched = profiles.find(p => String(p._id) === String(uid)); + if (!matched) return; + updateSelectedUser(prev => { + const nextPic = matched.profilePic || prev.profilePic; + const nextFirst = matched.firstName ?? prev.firstName; + const nextLast = matched.lastName ?? prev.lastName; + if ( + prev.firstName === nextFirst && + prev.lastName === nextLast && + prev.profilePic === nextPic + ) { + return prev; + } + return { + ...prev, + firstName: nextFirst, + lastName: nextLast, + profilePic: nextPic, + }; + }); + }, [users?.userProfilesBasicInfo, selectedUser?.userId]); const saveUserPreferences = () => { - persistNotificationPreferences() + dispatch(updateUserPreferences(currentUserId, selectedUser.userId, selectedOption)) .then(() => { toast.success('Preferences updated successfully!'); setBellDropdownActive(false); - }) - .catch(error => { - if (error?.message !== 'Missing user ID' && error?.message !== 'Invalid phone number') { - toast.error('Failed to update preferences. Please try again.'); - } - Error('Error updating preferences:', error); - }); - }; - const saveSmsPreferences = () => { - if (!authUserId) { - toast.error('Unable to update SMS settings without a valid user.'); - return; - } - - persistNotificationPreferences() - .then(() => { - toast.success('SMS settings updated successfully!'); + // Refresh preferences after saving + dispatch(fetchUserPreferences(currentUserId, selectedUser.userId)).then(response => { + if (response && response.payload) { + setSelectedOption({ + notifyInApp: response.payload.notifyInApp || false, + notifyEmail: response.payload.notifyEmail || false, + }); + } + }); }) .catch(error => { - if (error?.message !== 'Missing user ID' && error?.message !== 'Invalid phone number') { - toast.error('Failed to update SMS settings. Please try again.'); - } - Error('Error updating SMS settings:', error); + toast.error('Failed to update preferences. Please try again.'); + Error('Error updating preferences:', error); }); }; const handleSendMessage = () => { const socket = getMessagingSocket(); - const trimmedMessage = messageText.trim(); - if (!selectedUser.userId) { - toast.error('Select a user before sending a message.'); - return; - } - if (!trimmedMessage) { - toast.error('Message cannot be empty.'); - return; - } - if (trimmedMessage.length > 1000) { - toast.error('Message is too long. Please keep it under 1000 characters.'); - return; - } - - const now = Date.now(); - if (now - lastSendAtRef.current < 1500) { - toast.error('You are sending messages too quickly. Please wait a moment.'); - return; - } - if (trimmedMessage === lastMessageRef.current && now - lastSendAtRef.current < 10000) { - toast.error('Please avoid sending duplicate messages.'); - return; - } - if (socket && socket.readyState === WebSocket.OPEN) { socket.send( JSON.stringify({ action: 'SEND_MESSAGE', receiver: selectedUser.userId, - content: trimmedMessage, + content: messageText.trim(), }), ); setMessageText(''); - lastSendAtRef.current = now; - lastMessageRef.current = trimmedMessage; } else { toast.error('WebSocket is not connected. Please try again later.'); Error('WebSocket is not connected or is in an invalid state:', socket); @@ -434,72 +313,74 @@ export default function LBMessaging() { return () => window.removeEventListener('resize', handleResize); }, []); - const getMaskedPhone = phone => { - if (!phone) return ''; - const digits = phone.replace(/\D/g, ''); - if (digits.length <= 4) { - return digits; + const renderContacts = () => { + if (sidebarContacts.length === 0) { + return ( +

No contacts yet. Use the search icon to find someone.

+ ); } - const lastFour = digits.slice(-4); - return `••• ••• ${lastFour}`; - }; - const maskSensitiveText = text => { - if (!text) return ''; - return text.replace(/(\+?\d[\d\s().-]{6,}\d)/g, match => { - const digits = match.replace(/\D/g, ''); - if (digits.length < 8) return match; - const lastFour = digits.slice(-4); - return `••• ••• ${lastFour}`; - }); + return sidebarContacts.map(user => ( + + )); }; const renderChatMessages = () => { if (messagesLoading) { - return

Loading messages...

; + return

Loading messages...

; } if (messages.length === 0) { - return

No messages to display.

; - } - - const uniqueMessages = []; - const seenKeys = new Set(); - for (const message of messages) { - const key = - message._id || - `${message.sender}-${message.receiver}-${message.timestamp}-${message.content}`; - if (!seenKeys.has(key)) { - seenKeys.add(key); - uniqueMessages.push(message); - } + return

No messages to display.

; } - const filteredMessages = uniqueMessages.filter( + const filteredMessages = messages.filter( message => - (message.sender === authUserId && message.receiver === selectedUser.userId) || - (message.sender === selectedUser.userId && message.receiver === authUserId), + (message.sender === currentUserId && message.receiver === selectedUser.userId) || + (message.sender === selectedUser.userId && message.receiver === currentUserId), ); if (filteredMessages.length === 0) { - return

No messages to display.

; + return

No messages to display.

; } return ( -
-
+
+
{filteredMessages.map(message => (
-

- {maskSensitiveText(message.content) +

+ {String(message.content ?? '') .split('\n') - .map(line => ( - + .map((line, lineIdx) => ( + {line}
@@ -512,241 +393,289 @@ export default function LBMessaging() { ); }; - const handleSearchChange = event => { - const query = event.target.value; - setSearchQuery(query); - if (query.trim() !== '') { - searchUserProfiles(query); - return; - } - setSearchResults([]); - }; - - const handleToggleContacts = () => { - setShowContacts(prev => !prev); - }; - - const handleSelectUser = user => { - updateSelection(user); - setMobileHamMenu(false); - }; - return ( - userProfilesBasicInfo.length !== 0 && ( -

-
-
- One Community Logo -
-
-
- {mobileView && ( -
-
- - {mobileHamMenu && ( -
+
+
+ One Community Logo +
+
+ {mobileView ? ( +
+
+
+ +
+ ) : ( +
+

Messages

+
+ setShowContacts(prev => !prev)} + /> +
+
+ )} +
+ {showContacts + ? safeSearchResults.map(user => ( + + )) + : renderContacts()} +
+
+
+ )} +
+
+
+ ) : null} +
+ {/* Contacts Section */} + {!mobileView && ( +
+ {showContacts ? ( +
+ { + const query = e.target.value; + setSearchQuery(query); + if (query.trim() !== '') { + searchUserProfiles(query); + } else { + setSearchResults([]); + } + }} + /> + +
+ ) : ( +
+

Messages

+
+ setShowContacts(prev => !prev)} />
)} +
+ {showContacts + ? safeSearchResults.map(user => ( + + )) + : renderContacts()} +
-
- )} -
- - {bellDropdownActive && ( -
-
Notification Settings
- - -
-
SMS Notifications
- -
- SMS Phone - { - const nextValue = e.target.value; - setNotificationSettings(prev => ({ - ...prev, - smsPhone: nextValue, - })); + )} + + {/* Chat Window Section */} +
+
+
+ { + e.target.onerror = null; + e.target.src = '/pfp-default-header.png'; }} + alt="Profile" /> - {notificationSettings.smsPhoneMasked && ( - - Saved as {notificationSettings.smsPhoneMasked} - - )} + + {selectedUser.firstName + ? `${selectedUser.firstName} ${selectedUser.lastName}` + : 'Select a user to chat'} +
- - + {selectedUser.userId && ( +
+ { + setBellDropdownActive(prev => !prev); + }} + className={`${styles.lgMessagingNotificationBell}`} + /> + {bellDropdownActive && ( +
+ + + +
+ )} +
+ )}
- )} -
-
-
- {/* Contacts Section */} - {!mobileView && ( -
- -
- )} - - {/* Chat Window Section */} -
-
-
- { - e.target.onerror = null; - e.target.src = '/pfp-default-header.png'; +
+ {selectedUser.userId ? ( + renderChatMessages() + ) : ( +

Select a user to start chatting

+ )} +
+
+