diff --git a/index.html b/index.html index aea4c8e4..b473a0bf 100644 --- a/index.html +++ b/index.html @@ -86,7 +86,7 @@ Create Goals Create Tasks - Profile + Profile Diff Identity Service Logs + + Profile Update Requests + + + diff --git a/profile-diff-details/constants.js b/profile-diff-details/constants.js new file mode 100644 index 00000000..767a36b8 --- /dev/null +++ b/profile-diff-details/constants.js @@ -0,0 +1,22 @@ +const YEARS_OF_EXPERIENCE = 'yoe'; + +const fieldDisplayName = Object.freeze({ + first_name: 'First name', + last_name: 'Last name', + designation: 'Role', + company: 'Company', + yoe: 'Years of experience', + email: 'Email', + phone: 'Phone no.', + linkedin_id: 'Linkedin', + github_id: 'Github', + twitter_id: 'Twitter', + instagram_id: 'Instagram', + website: 'Website', +}); + +const Status = Object.freeze({ + APPROVED: 'APPROVED', + PENDING: 'PENDING', + NOT_APPROVED: 'NOT APPROVED', +}); diff --git a/profile-diff-details/index.html b/profile-diff-details/index.html new file mode 100644 index 00000000..9df40dd4 --- /dev/null +++ b/profile-diff-details/index.html @@ -0,0 +1,32 @@ + + + + + + + + Profile Diff + + + +
+ + Back + + Request Details +
+ +
+ + + + + + + + + + diff --git a/profile-diff-details/profileDiffDetails.apiCalls.js b/profile-diff-details/profileDiffDetails.apiCalls.js new file mode 100644 index 00000000..df464055 --- /dev/null +++ b/profile-diff-details/profileDiffDetails.apiCalls.js @@ -0,0 +1,40 @@ +const getProfileDiff = async (id) => { + try { + const finalUrl = `${API_BASE_URL}/profileDiffs/${id}`; + const res = await fetch(finalUrl, { + credentials: 'include', + method: 'GET', + headers: { + 'Content-type': 'application/json', + }, + }); + if (res.status === 401) { + return { notAuthorized: true }; + } + if (res.status === 404) { + return { profileDiffExist: false }; + } + return { profileDiffExist: true, ...(await res.json()).profileDiff }; + } catch (err) { + throw err; + } +}; + +const fetchUser = async (userId) => { + try { + const userResponse = await fetch(`${API_BASE_URL}/users?id=${userId}`, { + method: 'GET', + credentials: 'include', + headers: { + 'Content-type': 'application/json', + }, + }); + if (userResponse.status === 404) { + return { userExist: false }; + } + const { user } = await userResponse.json(); + return { ...user, userExist: true }; + } catch (err) { + throw err; + } +}; diff --git a/profile-diff-details/profileDiffDetails.utils.js b/profile-diff-details/profileDiffDetails.utils.js new file mode 100644 index 00000000..939b8cc4 --- /dev/null +++ b/profile-diff-details/profileDiffDetails.utils.js @@ -0,0 +1,58 @@ +function getUserDataItem(data, itemName) { + const item = data[itemName]; + + if (item || (itemName === YEARS_OF_EXPERIENCE && item === 0)) { + return item; + } + + return ''; +} + +function checkDifferentValues(primaryData, secondaryData) { + const diffValues = new Set(); + + for (const listItem in primaryData) { + const oldValue = getUserDataItem(primaryData, listItem); + const newValue = getUserDataItem(secondaryData, listItem); + const isValueEqual = String(oldValue).trim() === String(newValue).trim(); + + if (!isValueEqual) { + diffValues.add(listItem); + } + } + + return diffValues; +} + +function wantedData(data) { + const { + id, + first_name, + last_name, + email, + phone, + yoe, + company, + designation, + github_id, + linkedin_id, + twitter_id, + instagram_id, + website, + } = data; + return { + id, + first_name, + last_name, + email, + phone, + yoe, + company, + designation, + github_id, + linkedin_id, + twitter_id, + instagram_id, + website, + }; +} diff --git a/profile-diff-details/script.js b/profile-diff-details/script.js new file mode 100644 index 00000000..fb1443d9 --- /dev/null +++ b/profile-diff-details/script.js @@ -0,0 +1,320 @@ +const CONTAINER = document.querySelector('.container'); +let expanded = false; +let profileDetailsContainer; +let reasonHeading; + +const toast = document.getElementById('toast'); + +function showToast({ message, type }) { + toast.innerText = message; + + if (type === 'success') { + toast.classList.add('success'); + toast.classList.remove('failure'); + } else { + toast.classList.add('failure'); + toast.classList.remove('success'); + } + + toast.classList.remove('hidden'); + toast.classList.add('animated_toast'); + + setTimeout(() => { + toast.classList.add('hidden'); + toast.classList.remove('animated_toast'); + }, 3000); +} + +function renderProfileDetails(user, profileDiff) { + if (profileDetailsContainer) profileDetailsContainer.remove(); + profileDetailsContainer = createElement({ + type: 'div', + attributes: { class: 'profile-details-container' }, + }); + + CONTAINER.insertBefore(profileDetailsContainer, reasonHeading); + + const { id, ...oldData } = wantedData(user); + const { id: profileDiffId, ...newData } = wantedData(profileDiff); + + const difference = checkDifferentValues(oldData, newData); + + const fields = [ + 'first_name', + 'last_name', + 'designation', + 'company', + 'yoe', + 'email', + 'phone', + 'linkedin_id', + 'github_id', + 'twitter_id', + 'instagram_id', + 'website', + ]; + + let i = 0; + fields.forEach((field, index) => { + if (difference.has(field) || expanded) { + i++; + const profileDetailsListItem = createElement({ + type: 'div', + attributes: { + class: `profile-details-list-item ${ + expanded + ? fields.length - 1 === index + ? 'profile-details-border-none' + : '' + : i === difference.size + ? 'profile-details-border-none' + : '' + }`, + }, + }); + + const profileDetailsName = createElement({ + type: 'div', + attributes: { class: 'profile-details-name' }, + }); + + profileDetailsName.innerHTML = fieldDisplayName[field]; + profileDetailsListItem.appendChild(profileDetailsName); + if (difference.has(field)) { + const profileDetailsOldData = createElement({ + type: 'div', + attributes: { class: 'profile-details-old-data' }, + }); + profileDetailsOldData.innerHTML = oldData[field] || '—'; + + const profileDetailsNewData = createElement({ + type: 'div', + attributes: { class: 'profile-details-new-data' }, + }); + profileDetailsNewData.innerHTML = newData[field] || ''; + + profileDetailsListItem.appendChild(profileDetailsOldData); + profileDetailsListItem.appendChild(profileDetailsNewData); + } else { + const profileDetailsData = createElement({ + type: 'div', + attributes: { class: 'profile-details-data' }, + }); + profileDetailsData.innerHTML = oldData[field] || ''; + profileDetailsListItem.appendChild(profileDetailsData); + } + profileDetailsContainer.appendChild(profileDetailsListItem); + } + }); +} + +async function setUser(profileDiff) { + let user; + try { + user = await fetchUser(profileDiff.userId); + } catch (err) {} + if (!user.userExist) { + } + + CONTAINER.innerHTML = ''; + + const displayImage = createElement({ + type: 'img', + attributes: { + class: 'user_image', + src: user.picture.url, + height: '71px', + width: '71px', + }, + }); + CONTAINER.appendChild(displayImage); + + const username = createElement({ + type: 'div', + attributes: { + class: 'user_name', + }, + }); + username.innerHTML = `${user.first_name} ${user.last_name}`; + CONTAINER.appendChild(username); + + const expandControlContainer = createElement({ + type: 'div', + attributes: { class: 'expand-control-container' }, + }); + const expandTextDiv = createElement({ + type: 'div', + attributes: { class: 'expand-control-text' }, + }); + expandControlContainer.appendChild(expandTextDiv); + expandTextDiv.innerHTML = `See ${ + expanded ? 'Less' : 'All' + }`; + + expandTextDiv.addEventListener('click', () => { + expanded = !expanded; + expandTextDiv.innerHTML = `See ${ + expanded ? 'Less' : 'All' + }`; + renderProfileDetails(user, profileDiff); + }); + CONTAINER.appendChild(expandControlContainer); + renderProfileDetails(user, profileDiff); + reasonHeading = createElement({ + type: 'p', + attributes: { + class: 'reason_heading', + }, + }); + reasonHeading.innerHTML = 'Reason ( Optional )'; + CONTAINER.appendChild(reasonHeading); + + const reasonTextArea = createElement({ + type: 'textarea', + attributes: { + class: 'reason_textarea', + rows: 4, + }, + }); + CONTAINER.appendChild(reasonTextArea); + + const buttonsDiv = createElement({ + type: 'div', + attributes: { + class: 'button__container', + }, + }); + + const approvalButton = createElement({ + type: 'button', + attributes: { + class: 'button__approve', + }, + }); + approvalButton.innerHTML = 'Approve'; + buttonsDiv.appendChild(approvalButton); + + const rejectButton = createElement({ + type: 'button', + attributes: { + class: 'button__reject', + }, + }); + rejectButton.innerHTML = 'Reject'; + buttonsDiv.appendChild(rejectButton); + + approvalButton.onclick = async () => { + approvalButton.innerHTML = '
'; + approvalButton.disabled = true; + rejectButton.disabled = true; + try { + const response = await fetch(`${API_BASE_URL}/users/${user.id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + id: profileDiff.id, + message: reasonTextArea.value, + }), + }); + + if (response.ok) { + showToast({ + type: 'success', + message: 'The Profile changes are Approved!', + }); + setTimeout(() => { + window.location.href = '/profile-diffs'; + }, 3000); + } else { + showToast({ type: 'error', message: 'Something went wrong!' }); + approvalButton.innerHTML = 'Approve'; + approvalButton.disabled = false; + rejectButton.disabled = false; + } + } catch (error) { + showToast({ type: 'error', message: 'Something went wrong!' }); + approvalButton.innerHTML = 'Approve'; + approvalButton.disabled = false; + rejectButton.disabled = false; + } + }; + + rejectButton.onclick = async () => { + rejectButton.innerHTML = '
'; + approvalButton.disabled = true; + rejectButton.disabled = true; + try { + const response = await fetch(`${API_BASE_URL}/users/rejectDiff`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + profileDiffId: profileDiff.id, + message: reasonTextArea.value, + }), + }); + + if (response.ok) { + showToast({ + type: 'success', + message: 'The Profile changes are Rejected!', + }); + setTimeout(() => { + window.location.href = '/profile-diffs'; + }, 3000); + } else { + showToast({ type: 'error', message: 'Something went wrong!' }); + rejectButton.innerHTML = 'Reject'; + approvalButton.disabled = false; + rejectButton.disabled = false; + } + } catch (error) { + showToast({ type: 'error', message: 'Something went wrong!' }); + rejectButton.innerHTML = 'Reject'; + approvalButton.disabled = false; + rejectButton.disabled = false; + } + }; + + CONTAINER.appendChild(buttonsDiv); +} + +async function render() { + CONTAINER.innerHTML = '
'; + const id = new URLSearchParams(window.location.search).get('id'); + if (id) { + let profileDiff; + try { + profileDiff = await getProfileDiff(id); + } catch { + CONTAINER.innerHTML = 'Something went wrong fetching Profile Diff!'; + return; + } + if (profileDiff.notAuthorized) { + CONTAINER.innerHTML = 'You are not AUTHORIZED to view this page!'; + return; + } + if (!profileDiff.profileDiffExist) { + CONTAINER.innerHTML = 'Profile Diff not found!'; + return; + } + if (profileDiff.approval === Status.PENDING) { + await setUser(profileDiff); + } else { + CONTAINER.innerHTML = `The request status is ${profileDiff.approval}`; + } + } else { + CONTAINER.innerHTML = `No id found!`; + } +} + +render(); diff --git a/profile-diff-details/style.css b/profile-diff-details/style.css new file mode 100644 index 00000000..51b28e30 --- /dev/null +++ b/profile-diff-details/style.css @@ -0,0 +1,341 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); + +:root { + --blue-color: #1d1283; + --blue-hover-color: #11085c; + --dark-blue: #1b1378; + --light-aqua: #d4f9f2; + --scandal: #e5fcf5; + --green-transparent: rgba(0, 255, 0, 0.2); + --green-color: green; + --red-transparent: rgba(255, 0, 0, 0.145); + --white: #ffffff; + --black-transparent: #000000a8; + --black: #181717; + --light-gray: #d9d9d9; + --razzmatazz: #df0057; + --red-color: red; + --gray: #808080; + --button-proceed: #008000; + --modal-color: #00000048; + --black-color: black; + --light-gray-color: lightgray; + --green10: #e1f9f1; + --green500: #19805e; + --secondary10: #fff0f6; + --secondary600: #b6004e; + --medium-gray: #aeaeae; + --dark-gray: #737373; + --blue-color-heading: #041187; + --white-gray: #f2f2f3; + --color-red: #ae1820; + --color-green: rgba(0, 128, 0, 0.8); + --color-warn: rgba(199, 129, 18, 0.8); + --font-family: 'Inter', sans-serif; +} + +*, +::after, +::before { + box-sizing: border-box; +} + +body { + font-family: var(--font-family); + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + min-height: 100vh; + user-select: none; + max-width: 100vw; + background-color: #f8fafd; +} + +.header { + height: 7.25em; + background-color: #233876; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + font-optical-sizing: auto; + font-weight: 500; + font-style: normal; + font-size: 1.25em; + color: var(--white); + position: relative; +} + +.back { + background-color: transparent; + border: none; + padding: 0.625em; + text-decoration: none; + display: flex; + align-items: center; + position: absolute; + left: 10%; +} + +.back__icon { + height: 1.6rem; +} + +.container { + margin: 0 auto; + padding: 2em 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 1em; + font-weight: 700; + font-size: x-large; + text-align: center; + width: 95%; + max-width: 992px; +} + +.user_image { + border-radius: 37px; +} + +.user_name { + font-style: normal; + font-weight: 500; + font-size: 20px; + line-height: 150%; + color: #111928; + margin-top: -1em; + margin-bottom: 1em; +} + +.expand-control-container { + width: 100%; + display: flex; + justify-content: end; + align-items: center; + color: #233876; + font-weight: 400; + font-size: 14px; +} + +.expand-control-text { + cursor: pointer; +} + +.expand-control-icon { + margin-left: 0.5em; + font-size: 10px !important; +} + +.reason_heading { + font-style: normal; + font-weight: 500; + font-size: 17px; + line-height: 150%; + color: #111928; + width: 100%; + text-align: left; + padding-left: 1.25em; + margin-block-end: 0; +} + +.reason_textarea { + font-style: normal; + font-size: 16px; + background: #ffffff; + box-shadow: 0px 8px 3px rgba(0, 0, 0, 0.01), 0px 5px 3px rgba(0, 0, 0, 0.02), + 0px 2px 2px rgba(0, 0, 0, 0.04), 0px 1px 1px rgba(0, 0, 0, 0.04); + border-radius: 12px; + border: none; + padding: 1em; + width: 100%; + resize: none; +} + +.button__container { + width: 100%; + display: flex; + justify-content: space-between; + margin-top: 1em; +} + +.button__approve { + padding: 11px 32px; + background: #046c4e; + border-radius: 10px; + border: none; + font-style: normal; + font-weight: 500; + font-size: 18px; + line-height: 150%; + color: #ffffff; + cursor: pointer; +} + +.button__approve:disabled { + opacity: 0.2; +} + +.button__reject { + padding: 11px 32px; + background: #c81e1e; + border-radius: 10px; + border: none; + font-style: normal; + font-weight: 500; + font-size: 18px; + line-height: 150%; + color: #ffffff; + cursor: pointer; +} + +.button__reject:disabled { + opacity: 0.2; +} + +.loader { + width: 60px; + aspect-ratio: 2; + --_g: no-repeat radial-gradient(circle closest-side, #000 90%, #0000); + background: var(--_g) 0% 50%, var(--_g) 50% 50%, var(--_g) 100% 50%; + background-size: calc(100% / 3) 50%; + animation: loader-animation 1s infinite linear; +} + +.button__loader { + width: 60px; + aspect-ratio: 2; + --_g: no-repeat radial-gradient(circle closest-side, #fff 90%, #0000); + background: var(--_g) 0% 50%, var(--_g) 50% 50%, var(--_g) 100% 50%; + background-size: calc(100% / 3) 50%; + animation: loader-animation 1s infinite linear; +} + +@keyframes loader-animation { + 20% { + background-position: 0% 0%, 50% 50%, 100% 50%; + } + 40% { + background-position: 0% 100%, 50% 0%, 100% 50%; + } + 60% { + background-position: 0% 50%, 50% 100%, 100% 0%; + } + 80% { + background-position: 0% 50%, 50% 50%, 100% 100%; + } +} + +.profile-details-container { + background: #ffffff; + box-shadow: 0px 8px 3px rgba(0, 0, 0, 0.01), 0px 5px 3px rgba(0, 0, 0, 0.02), + 0px 2px 2px rgba(0, 0, 0, 0.04), 0px 1px 1px rgba(0, 0, 0, 0.04); + border-radius: 12px; + width: 100%; + padding: 0 0.5em; +} + +.profile-details-list-item { + padding: 0.5em 0.75em; + border-bottom: 1px solid #cec4c4; + display: flex; + justify-content: space-between; + align-items: center; +} + +.profile-details-border-none { + border: none; +} + +.profile-details-name { + font-style: normal; + font-weight: 500; + font-size: 14px; + line-height: 150%; + color: #585b61; + width: 30%; + text-align: left; + word-wrap: break-word; +} + +.profile-details-old-data { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 150%; + color: #c81e1e; + width: 30%; + text-align: center; + word-wrap: break-word; +} + +.profile-details-new-data { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 150%; + color: #046c4e; + width: 30%; + text-align: right; + word-wrap: break-word; +} + +.profile-details-data { + font-style: normal; + font-weight: 400; + font-size: 14px; + line-height: 150%; + color: #233876; +} + +#toast { + position: fixed; + top: 20px; + right: -300px; + color: #fff; + padding: 15px; + border-radius: 5px; +} + +.animated_toast { + animation: slideIn 0.5s ease-in-out forwards, + slideOut 0.5s ease-in-out 2.5s forwards; +} + +.success { + background: green; +} + +.failure { + background: #f43030; +} + +.hidden { + visibility: collapse; +} + +.disable-button { + opacity: 0.2; +} + +@keyframes slideIn { + from { + right: -300px; + } + + to { + right: 20px; + } +} + +@keyframes slideOut { + from { + right: 20px; + } + + to { + right: -300px; + } +} diff --git a/profile-diffs/assets/search.svg b/profile-diffs/assets/search.svg new file mode 100644 index 00000000..af3d86d4 --- /dev/null +++ b/profile-diffs/assets/search.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/profile-diffs/assets/sort-asc.svg b/profile-diffs/assets/sort-asc.svg new file mode 100644 index 00000000..8c3e2d8e --- /dev/null +++ b/profile-diffs/assets/sort-asc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/profile-diffs/assets/sort-desc.svg b/profile-diffs/assets/sort-desc.svg new file mode 100644 index 00000000..0302f6e0 --- /dev/null +++ b/profile-diffs/assets/sort-desc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/profile-diffs/constants.js b/profile-diffs/constants.js new file mode 100644 index 00000000..19a931c7 --- /dev/null +++ b/profile-diffs/constants.js @@ -0,0 +1,19 @@ +const SORT_BUTTON = '.sort-button'; +const SORT_ASC_ICON = 'asc-sort-icon'; +const SORT_DESC_ICON = 'desc-sort-icon'; +const DEFAULT_PAGE_SIZE = 10; +const OLDEST_FIRST = 'Oldest first'; +const NEWEST_FIRST = 'Newest first'; +const SEARCH_ELEMENT = 'assignee-search'; +const LAST_ELEMENT_CONTAINER = '.virtual'; + +const Status = Object.freeze({ + APPROVED: 'APPROVED', + PENDING: 'PENDING', + NOT_APPROVED: 'NOT APPROVED', +}); + +const Order = Object.freeze({ + DESCENDING: 'desc', + ASCENDING: 'asc', +}); diff --git a/profile-diffs/index.html b/profile-diffs/index.html new file mode 100644 index 00000000..4d4a0536 --- /dev/null +++ b/profile-diffs/index.html @@ -0,0 +1,63 @@ + + + + + + + Profile Diffs + + + +
Profile Difference Requests
+ +
+ +
+ + + +
+
+
+
+
+
+ + + + + + + + + + + diff --git a/profile-diffs/profileDiffs.apiCalls.js b/profile-diffs/profileDiffs.apiCalls.js new file mode 100644 index 00000000..5a4bb0fc --- /dev/null +++ b/profile-diffs/profileDiffs.apiCalls.js @@ -0,0 +1,34 @@ +const getProfileDiffs = async (query = {}, nextLink) => { + const finalUrl = + API_BASE_URL + + (nextLink || generateProfileDiffsParams({ ...query, dev: true })); + const res = await fetch(finalUrl, { + credentials: 'include', + method: 'GET', + headers: { + 'Content-type': 'application/json', + }, + }); + if (res.status === 401) { + return { notAuthorized: true }; + } + return await res.json(); +}; + +const users = {}; + +const getUser = async (userId) => { + if (users[userId]) { + return users[userId]; + } + const userResponse = await fetch(`${API_BASE_URL}/users?id=${userId}`, { + method: 'GET', + credentials: 'include', + headers: { + 'Content-type': 'application/json', + }, + }); + const { user } = await userResponse.json(); + users[userId] = user; + return user; +}; diff --git a/profile-diffs/profileDiffs.utils.js b/profile-diffs/profileDiffs.utils.js new file mode 100644 index 00000000..1155f8a6 --- /dev/null +++ b/profile-diffs/profileDiffs.utils.js @@ -0,0 +1,19 @@ +// Utility functions +const parseProfileDiffParams = (uri, nextPageParamsObject) => { + const urlSearchParams = new URLSearchParams(uri); + for (const [key, value] of urlSearchParams.entries()) { + nextPageParamsObject[key] = value; + } + return nextPageParamsObject; +}; + +const generateProfileDiffsParams = (nextPageParams, forApi = true) => { + const urlSearchParams = new URLSearchParams(); + for (const [key, value] of Object.entries(nextPageParams)) { + if (!value) continue; + urlSearchParams.append(key, value); + } + return `/${ + forApi ? 'profileDiffs' : 'profile-diffs' + }?${urlSearchParams.toString()}`; +}; diff --git a/profile-diffs/script.js b/profile-diffs/script.js new file mode 100644 index 00000000..513c5150 --- /dev/null +++ b/profile-diffs/script.js @@ -0,0 +1,301 @@ +let currentUserDetails; +const filterStates = {}; +const container = document.querySelector('.container'); +const sortButton = document.querySelector(SORT_BUTTON); +const ascIcon = document.getElementById(SORT_ASC_ICON); +const descIcon = document.getElementById(SORT_DESC_ICON); +const filterButtons = document.querySelectorAll('#filter-button'); +const profileDiffsContainer = document.querySelector('.profile-diffs'); +const searchElement = document.getElementById(SEARCH_ELEMENT); +const lastElementContainer = document.querySelector(LAST_ELEMENT_CONTAINER); +let nextLink = ''; +let isDataLoading = false; + +const toast = document.getElementById('toast'); + +function showToast({ message, type }) { + toast.innerText = message; + + if (type === 'success') { + toast.classList.add('success'); + toast.classList.remove('failure'); + } else { + toast.classList.add('failure'); + toast.classList.remove('success'); + } + + toast.classList.remove('hidden'); + toast.classList.add('animated_toast'); + + setTimeout(() => { + toast.classList.add('hidden'); + toast.classList.remove('animated_toast'); + }, 3000); +} + +const addTooltipToSortButton = () => { + const sortToolTip = createElement({ + type: 'span', + attributes: { class: 'tooltip sort-button-tooltip' }, + innerText: + filterStates.order === Order.ASCENDING ? OLDEST_FIRST : NEWEST_FIRST, + }); + sortButton.appendChild(sortToolTip); +}; + +function toggleStatusButton(statusValue) { + if (!statusValue) return; + filterButtons.forEach((filterButton) => { + filterButton.classList.toggle( + 'selected', + filterButton.name === statusValue, + ); + }); +} + +const updateUrl = () => { + if (history.pushState) { + window.history.pushState( + null, + '', + `${window.location.protocol}//${ + window.location.host + }${generateProfileDiffsParams(filterStates, false)}`, + ); + } +}; + +const updateUIBasedOnFilterStates = () => { + if (filterStates.order === 'asc') { + descIcon.style.display = 'none'; + ascIcon.style.display = 'block'; + } else { + ascIcon.style.display = 'none'; + descIcon.style.display = 'block'; + } + toggleStatusButton(filterStates.status); + searchElement.value = filterStates.username; +}; + +const render = async () => { + if (window.location.search) { + parseProfileDiffParams(window.location.search, filterStates); + filterStates.order ||= Order.DESCENDING; + filterStates.username ||= ''; + } else { + Object.assign(filterStates, { + status: Status.PENDING, + order: Order.DESCENDING, + size: DEFAULT_PAGE_SIZE, + username: '', + }); + } + addTooltipToSortButton(); + updateUIBasedOnFilterStates(); + changeFilter(); + updateUrl(); + await populateProfileDiffs(filterStates); + addIntersectionObserver(); +}; + +const changeFilter = () => { + nextLink = ''; + profileDiffsContainer.innerHTML = ''; +}; + +let profileDiffsListElement; + +async function populateProfileDiffs(query = {}, newLink) { + try { + isDataLoading = true; + const profileDiffsElement = document.getElementById('profile-diffs'); + if (!newLink) profileDiffsElement.innerHTML = ''; + const profileDiffs = await getProfileDiffs(query, newLink); + if (profileDiffs?.notAuthorized) { + showToast({ type: 'error', message: 'You are not AUTHORIZED!' }); + setTimeout(() => { + window.location.href = '/index.html'; + }, 3000); + return; + } + nextLink = profileDiffs.next; + const allProfileDiffs = profileDiffs.profileDiffs; + + if (!newLink) { + if (allProfileDiffs.length === 0) { + profileDiffsElement.innerHTML = 'No Data found!'; + return; + } + profileDiffsElement.innerHTML = ''; + profileDiffsListElement = createElement({ + type: 'div', + attributes: { class: 'profileDiffs__list-container' }, + }); + } + profileDiffsElement.appendChild(profileDiffsListElement); + for (let data of allProfileDiffs) { + await createProfileDiffCard(data, profileDiffsListElement); + } + } catch (error) { + showToast({ type: 'error', message: 'Something went wrong!' }); + } finally { + isDataLoading = false; + } +} + +const updateFilterStates = (key, value) => { + filterStates[key] = value; +}; + +const toggleSortIconAndOrder = () => { + const tooltip = sortButton.querySelector('.tooltip'); + const isAscending = filterStates.order === Order.ASCENDING; + + tooltip.textContent = isAscending ? NEWEST_FIRST : OLDEST_FIRST; + ascIcon.style.display = isAscending ? 'block' : 'none'; + descIcon.style.display = isAscending ? 'none' : 'block'; + + updateFilterStates('order', isAscending ? Order.DESCENDING : Order.ASCENDING); + updateUrl(); +}; + +const changeStatus = (status) => { + updateFilterStates('status', status); + updateUrl(); +}; + +const changeUsername = (username) => { + updateFilterStates('username', username); + updateUrl(); +}; + +function debounce(func, delay) { + let timerId; + return (...args) => { + clearTimeout(timerId); + timerId = setTimeout(() => func(...args), delay); + }; +} + +async function createProfileDiffCard(data, profileDiffCardList) { + const time = data.timestamp; + const fireBaseTime = new Date( + time._seconds * 1000 + time._nanoseconds / 1000000, + ); + const date = new Intl.DateTimeFormat('en-GB', { + day: '2-digit', + month: 'short', + year: '2-digit', + }).format(fireBaseTime); + const formattedTime = new Intl.DateTimeFormat('en-US', { + hour: 'numeric', + minute: 'numeric', + hour12: true, + }).format(fireBaseTime); + + const profileCard = createElement({ + type: 'div', + attributes: { class: 'profile-card' }, + }); + if (filterStates.status === Status.PENDING) { + profileCard.style.cursor = 'pointer'; + profileCard.addEventListener('click', () => { + window.location.href = `/profile-diff-details/?id=${data.id}`; + }); + } + profileDiffCardList.appendChild(profileCard); + + const profileCardLeft = createElement({ + type: 'div', + attributes: { class: 'profile shimmer' }, + }); + const profileCardPhoto = createElement({ + type: 'div', + attributes: { class: 'profile-pic' }, + }); + const profileCardInfo = createElement({ + type: 'div', + attributes: { class: 'profile-info' }, + }); + const profileCardName = createElement({ + type: 'div', + attributes: { class: 'profile-name-shimmer' }, + }); + const profileCardUsername = createElement({ + type: 'div', + attributes: { class: 'profile-username-shimmer' }, + }); + + profileCardInfo.appendChild(profileCardName); + profileCardInfo.appendChild(profileCardUsername); + profileCardLeft.appendChild(profileCardPhoto); + profileCardLeft.appendChild(profileCardInfo); + profileCard.appendChild(profileCardLeft); + + const profileCardRight = createElement({ + type: 'div', + attributes: { class: 'profile-card_right' }, + }); + const profileCardRightDate = createElement({ + type: 'span', + attributes: { class: 'profile-card_right-date-time' }, + innerText: `${date}`, + }); + const profileCardRightTime = createElement({ + type: 'span', + attributes: { class: 'profile-card_right-date-time' }, + innerText: `${formattedTime.toLowerCase()}`, + }); + profileCardRight.appendChild(profileCardRightDate); + profileCardRight.appendChild(profileCardRightTime); + profileCard.appendChild(profileCardLeft); + profileCard.appendChild(profileCardRight); + + const user = await getUser(data.userId); + profileCardLeft.classList.remove('shimmer'); + + profileCardPhoto.style.backgroundImage = `url(${user.picture?.url})`; + profileCardPhoto.style.backgroundSize = 'cover'; + + profileCardName.classList.remove('profile-name-shimmer'); + profileCardName.classList.add('profile-name'); + profileCardName.textContent = `${user.first_name} ${user.last_name}`; + + profileCardUsername.classList.remove('profile-username-shimmer'); + profileCardUsername.classList.add('profile-username'); + profileCardUsername.textContent = `${user.username}`; +} + +const addIntersectionObserver = () => { + intersectionObserver.observe(lastElementContainer); +}; + +const intersectionObserver = new IntersectionObserver(async (entries) => { + if (entries[0].isIntersecting && !isDataLoading && nextLink) { + await populateProfileDiffs({}, nextLink); + } +}); + +render(); + +sortButton.addEventListener('click', async () => { + toggleSortIconAndOrder(); + changeFilter(); + await populateProfileDiffs(filterStates); +}); + +searchElement.addEventListener( + 'input', + debounce(async (event) => { + changeUsername(event.target.value); + await populateProfileDiffs(filterStates); + }, 500), +); + +filterButtons.forEach((filterButton) => { + filterButton.addEventListener('click', async (event) => { + toggleStatusButton(filterButton.name); + changeStatus(filterButton.name); + await populateProfileDiffs(filterStates); + }); +}); diff --git a/profile-diffs/style.css b/profile-diffs/style.css new file mode 100644 index 00000000..97120341 --- /dev/null +++ b/profile-diffs/style.css @@ -0,0 +1,354 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap'); + +:root { + --blue-color: #1d1283; + --blue-hover-color: #11085c; + --dark-blue: #1b1378; + --light-aqua: #d4f9f2; + --scandal: #e5fcf5; + --green-transparent: rgba(0, 255, 0, 0.2); + --green-color: green; + --red-transparent: rgba(255, 0, 0, 0.145); + --white: #ffffff; + --black-transparent: #000000a8; + --black: #181717; + --light-gray: #d9d9d9; + --razzmatazz: #df0057; + --red-color: red; + --gray: #808080; + --button-proceed: #008000; + --modal-color: #00000048; + --black-color: black; + --light-gray-color: lightgray; + --green10: #e1f9f1; + --green500: #19805e; + --secondary10: #fff0f6; + --secondary600: #b6004e; + --medium-gray: #aeaeae; + --dark-gray: #737373; + --blue-color-heading: #041187; + --white-gray: #f2f2f3; + --color-red: #ae1820; + --color-green: rgba(0, 128, 0, 0.8); + --color-warn: rgba(199, 129, 18, 0.8); + --font-family: 'Inter', sans-serif; +} + +*, +::after, +::before { + box-sizing: border-box; +} + +body { + font-family: var(--font-family); + font-style: normal; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + min-height: 100vh; + user-select: none; + max-width: 100vw; + background-color: #f8fafd; +} + +.header { + height: 7.25em; + background-color: #233876; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + font-optical-sizing: auto; + font-weight: 500; + font-style: normal; + font-size: 20px; + color: var(--white); +} + +.search-filter { + display: flex; + flex-direction: column; + align-items: center; + flex-wrap: wrap; + width: 100%; + padding: 1rem 0; + gap: 1rem; +} + +#search { + width: 90%; + max-width: 70rem; + display: flex; + justify-content: center; + align-items: center; + position: relative; +} + +#assignee-search { + width: 100%; + padding: 0.7rem 2.7rem 0.7rem 3.243rem; + border: 1px solid #fff; + box-shadow: 2px 1px 5px 0px #d4cccc40; + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; + font-size: 16px; + background-color: #fff; + height: 3.5rem; + color: var(--black-color); +} + +#assignee-search::placeholder { + color: #212429; +} + +#filter-container { + display: flex; + width: 90%; + max-width: 70rem; + justify-content: space-around; +} + +.search-icon { + position: absolute; + left: 0.7rem; +} + +#desc-sort-icon { + display: none; +} + +.sort-button { + padding: 8px; + height: 2.5rem; + cursor: pointer; + background-color: transparent; + border: none; + right: 0.7em; +} + +.tooltip-container { + position: relative; +} + +.sort-button-position { + position: absolute; +} + +.filter-button { + background-color: transparent; + color: #90959f; + font-optical-sizing: auto; + font-weight: 600; + font-style: normal; + font-size: 16px; + border: none; + cursor: pointer; +} + +.filter-button:hover { + color: #233876; + text-decoration: underline; + text-underline-position: under; +} + +.selected { + color: #233876; + text-decoration: underline; + text-underline-position: under; +} + +.profile-diffs { + display: flex; + justify-content: center; +} + +.profileDiffs__list-container { + display: flex; + width: 90%; + max-width: 70rem; + flex-direction: column; + overflow: hidden; + border-radius: 10px; + background-color: white; + margin-bottom: 1em; +} + +.profile-card { + padding: 1em 1em 1em 0.5em; + border-bottom: 1px solid #eae8e8; + display: flex; + justify-content: space-between; +} + +.profile-card_right { + display: flex; + flex-direction: column; + align-items: end; +} + +.profile-card_right-date-time { + font-style: normal; + font-weight: 500; + font-size: 13px; + line-height: 150%; + color: #90959f; +} + +.profile { + display: flex; + align-items: center; +} + +.profile-pic { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: #ccc; + margin-right: 10px; +} + +.profile-info { + display: flex; + flex-direction: column; +} + +.profile-name { + font-style: normal; + font-weight: 500; + font-size: 16px; + line-height: 150%; + color: #111928; +} + +.profile-username { + font-style: normal; + font-weight: 300; + font-size: 13px; + line-height: 150%; + color: #111928; +} + +.profile-name-shimmer, +.profile-username-shimmer { + width: 100px; + height: 10px; + background-color: #ccc; + margin-bottom: 5px; +} + +.profile.shimmer .profile-pic, +.profile.shimmer .profile-name-shimmer, +.profile.shimmer .profile-username-shimmer { + background: linear-gradient(to right, #eeeeee 8%, #dddddd 18%, #eeeeee 33%); + background-size: 1000px 100%; + animation: shimmer 1.5s infinite linear; +} + +@keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } +} + +.tooltip { + background-color: var(--black-color); + color: var(--white); + visibility: hidden; + text-align: center; + border-radius: 4px; + padding: 0.5rem; + position: absolute; + opacity: 0.9; + font-size: 0.7rem; + width: 10rem; + bottom: 100%; + left: 50%; + margin-left: -5rem; +} + +.sort-button-tooltip { + bottom: 120%; + width: 7rem; + margin-left: -5.8rem; +} + +.tooltip-container:hover .tooltip { + visibility: visible; + transition-delay: 400ms; +} + +.tooltip-container:hover .sort-button-tooltip { + visibility: visible; + transition-delay: 200ms; +} + +/* Loader Container */ +.loader-text { + text-align: center; + font-size: 1.5rem; +} + +.loader { + margin: auto auto; +} + +.loader p { + font-weight: 600; + font-size: 2em; +} + +#toast { + position: fixed; + top: 20px; + right: -300px; + color: #fff; + padding: 15px; + border-radius: 5px; +} + +.animated_toast { + animation: slideIn 0.5s ease-in-out forwards, + slideOut 0.5s ease-in-out 2.5s forwards; +} + +.success { + background: green; +} + +.failure { + background: #f43030; +} + +.hidden { + visibility: collapse; +} + +.disable-button { + opacity: 0.2; +} + +@keyframes slideIn { + from { + right: -300px; + } + + to { + right: 20px; + } +} + +@keyframes slideOut { + from { + right: 20px; + } + + to { + right: -300px; + } +} diff --git a/script.js b/script.js index 034a2253..7bfc24cb 100644 --- a/script.js +++ b/script.js @@ -103,6 +103,9 @@ const repoSyncButton = document.getElementById('repo-sync-button'); const createActivityFeedButton = document.getElementById( 'create-activity-feed', ); +const profileUpdateRequestsButton = document.getElementById( + 'profile-update-requests', +); const requestPageButton = document.getElementById('requests-link'); const toast = document.getElementById('toast'); if (params.get('dev') === 'true') { @@ -110,6 +113,7 @@ if (params.get('dev') === 'true') { repoSyncDiv.classList.remove('element-display-remove'); createActivityFeedButton.classList.remove('element-display-remove'); requestPageButton.classList.remove('element-display-remove'); + profileUpdateRequestsButton.classList.remove('element-display-remove'); } function addClickEventListener(