diff --git a/__tests__/photo-verification/photo-verification.test.js b/__tests__/photo-verification/photo-verification.test.js new file mode 100644 index 00000000..507f59db --- /dev/null +++ b/__tests__/photo-verification/photo-verification.test.js @@ -0,0 +1,269 @@ +const puppeteer = require('puppeteer'); +const { + photoVerificationRequestApprovedResponse, + photoVerificationRequestRejectedResponse, + photoVerificationRequestsListPending, + photoVerificationRequestsListUserSearch, + photoVerificationRequestDiscordUpdateResponse, +} = require('../../mock-data/photo-verification'); +// const { +// extensionRequestLogs, +// extensionRequestLogsInSentence, +// } = require('../../mock-data/logs'); +// const { +// userSunny, +// userRandhir, +// allUsersData, +// superUserForAudiLogs, +// searchedUserForAuditLogs, +// } = require('../../mock-data/users'); +// const { usersStatus } = require('../../mock-data/users-status'); +const baseUrl = 'http://localhost:8000/photo-verification-requests'; + +describe('Tests the Photo Verification Screen', () => { + let browser; + let page; + let title; + let searchBar; + let photoVerificationRequestsElement; + jest.setTimeout(60000); + + beforeAll(async () => { + browser = await puppeteer.launch({ + headless: 'new', + ignoreHTTPSErrors: true, + args: ['--incognito', '--disable-web-security'], + devtools: false, + }); + + page = await browser.newPage(); + + await page.setRequestInterception(true); + + page.on('request', (interceptedRequest) => { + const url = interceptedRequest.url(); + if (url === 'https://api.realdevsquad.com/users/picture/all/') { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(photoVerificationRequestsListPending), + }); + } else if ( + url === + 'https://api.realdevsquad.com/users/picture/all/?username=vinayak-g' + ) { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(photoVerificationRequestsListUserSearch), + }); + } else if ( + url === + `https://api.realdevsquad.com/users/picture/verify/${photoVerificationRequestsListPending.data[0].userId}/?status=APPROVED&type=both` + ) { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(photoVerificationRequestApprovedResponse), + }); + } else if ( + url === + `https://api.realdevsquad.com/users/picture/verify/${photoVerificationRequestsListPending.data[0].userId}/?status=REJECTED&type=both` + ) { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(photoVerificationRequestRejectedResponse), + }); + } else if ( + url === + `https://api.realdevsquad.com/discord-actions/avatar/photo-verification-update/${photoVerificationRequestsListPending.data[0].discordId}` + ) { + interceptedRequest.respond({ + status: 200, + contentType: 'application/json', + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + body: JSON.stringify(photoVerificationRequestDiscordUpdateResponse), + }); + } else { + interceptedRequest.continue(); + } + }); + + await page.goto(baseUrl); + + await page.waitForNetworkIdle(); + + title = await page.$('.header'); + searchBar = await page.$('#search'); + photoVerificationRequestsElement = await page.$( + '.photo-verification-requests', + ); + }); + + afterEach(async () => { + await page.goto('http://localhost:8000/photo-verification-requests'); + await page.waitForNetworkIdle(); + }); + afterAll(async () => { + await browser.close(); + }); + it('Checks the UI elements on photo verification requests listing page', async () => { + title = await page.$('.header'); + searchBar = await page.$('#search'); + photoVerificationRequestCardList = await page.$$( + '.photo-verification-card', + ); + photoVerificationRequestsElement = await page.$( + '.photo-verification-requests', + ); + expect(title).toBeTruthy(); + expect(searchBar).toBeTruthy(); + expect(photoVerificationRequestCardList.length).toBe(2); + expect(photoVerificationRequestsElement).toBeTruthy(); + }); + + it('checks the search functionality', async () => { + await page.type('#user-search', 'vinayak-g'); + await page.keyboard.press('Enter'); + await page.waitForNetworkIdle(); + + const cardsList = await page.$$('.photo-verification-card'); + expect(cardsList.length).toBe(1); + const cardTextContent = await page.evaluate( + (element) => element.textContent, + cardsList[0], + ); + expect(cardTextContent).toContain('vinayak-g'); + }); + + it('checks the refresh discord avatar image functionality', async () => { + const photoVerificationRequestId = + photoVerificationRequestsListPending.data[0].id; + + photoVerificationRequestCard = await page.$( + `.photo-verification-card--${photoVerificationRequestId}`, + ); + + const refreshButton = await photoVerificationRequestCard.$( + '.refresh-discord-avatar-button', + ); + + await refreshButton.click(); + // wait for 500ms for the request to complete + await page.waitForTimeout(500); + + photoVerificationRequestCard = await page.$( + `.photo-verification-card--${photoVerificationRequestId}`, + ); + + const discordImage = await photoVerificationRequestCard.$( + '.photo-verification-image-box__block--discord', + ); + + const discordImageSrc = await page.evaluate( + (element) => element.querySelector('img').src, + discordImage, + ); + + expect(discordImageSrc).toBe( + photoVerificationRequestDiscordUpdateResponse.discordAvatarUrl, + ); + }); + it('checks the reject photo verification functionality', async () => { + const photoVerificationRequestId = + photoVerificationRequestsListPending.data[0].id; + + photoVerificationRequestCard = await page.$( + `.photo-verification-card--${photoVerificationRequestId}`, + ); + + const rejectButton = await photoVerificationRequestCard.$('.reject-button'); + + await rejectButton.click(); + // wait for 2500ms for the request to complete + await page.waitForTimeout(2500); + + photoVerificationRequestCard = await page.$( + `.photo-verification-card--${photoVerificationRequestId}`, + ); + + expect(photoVerificationRequestCard).toBe(null); + }); + + it('checks the reject photo verification functionality', async () => { + await page.evaluate(() => { + window.location.reload = () => { + console.log('window.location.reload was called'); + }; + }); + const photoVerificationRequestId = + photoVerificationRequestsListPending.data[0].id; + + photoVerificationRequestCard = await page.$( + `.photo-verification-card--${photoVerificationRequestId}`, + ); + + const approveButton = await photoVerificationRequestCard.$( + '.approve-both-button', + ); + + await approveButton.click(); + // wait for 500ms for the request to complete + await page.waitForTimeout(500); + + photoVerificationRequestCard = await page.$( + `.photo-verification-card--${photoVerificationRequestId}`, + ); + + responseMessage = await photoVerificationRequestCard.$eval( + 'p', + (el) => el.textContent, + ); + + expect(responseMessage).toBe( + photoVerificationRequestApprovedResponse.message, + ); + }); + + it('Checks details of the first photo verification card', async () => { + photoVerificationRequestCardList = await page.$$( + '.photo-verification-card', + ); + + const firstPhotoVerificationRequestCard = + photoVerificationRequestCardList[0]; + + const titleText = await firstPhotoVerificationRequestCard.$eval( + 'h3', + (el) => el.textContent, + ); + expect(titleText).toBe( + `Photo Verifcation for ${photoVerificationRequestsListPending.data[0].user.username}`, + ); + }); +}); diff --git a/constants.js b/constants.js index 2095f752..c9387fcd 100644 --- a/constants.js +++ b/constants.js @@ -27,6 +27,7 @@ const DISABLED = 'disabled'; const STATUS_BASE_URL_PROD = 'https://status.realdevsquad.com'; const STATUS_BASE_URL_STAGING = 'https://staging-status.realdevsquad.com'; const STATUS_BASE_URL = STATUS_BASE_URL_PROD; +const PHOTO_VERIFICATION_REQUESTS_BUTTON = 'photo-verification-requests-button'; const dummyPicture = 'https://dashboard.realdevsquad.com/images/avatar.png'; const USER_MANAGEMENT_URL = diff --git a/index.html b/index.html index 24e4950b..b8e1d64a 100644 --- a/index.html +++ b/index.html @@ -144,6 +144,13 @@ > Extension Requests + + Photo Verification Requests + + + + + + + Photo Verification Requests + + + +
+

Photo Verification Requests

+
+ +
+ +
+ +
+
+
+
+ + + + + + + diff --git a/photo-verification-requests/local-utils.js b/photo-verification-requests/local-utils.js new file mode 100644 index 00000000..fb4e1139 --- /dev/null +++ b/photo-verification-requests/local-utils.js @@ -0,0 +1,256 @@ +async function refreshDiscordImage(discordId, id) { + const res = await refreshDiscordImageRequest(discordId); + const card = document.querySelector(`.photo-verification-card--${id}`); + const discordImage = card.querySelector( + '.photo-verification-image-box__block--discord img', + ); + + if (res.statusCode === 200) { + discordImage.src = res.data.discordAvatarUrl; + } +} + +async function approvePhotoVerificationRequest(id, userId, imageType) { + const res = await modifyPhotoVerificationRequest( + userId, + imageType, + 'APPROVED', + ); + const card = document.querySelector(`.photo-verification-card--${id}`); + const message = document.createElement('p'); + message.innerText = res.data.message; + + card.appendChild(message); + + setTimeout(() => { + window.location.reload(); + }, 2000); +} + +async function rejectPhotoVerificationRequest(id, userId) { + const res = await modifyPhotoVerificationRequest(userId, 'both', 'REJECTED'); + const card = document.querySelector(`.photo-verification-card--${id}`); + const message = document.createElement('p'); + message.innerText = res.data.message; + + card.appendChild(message); + + if (res.statusCode === 200) { + setTimeout(() => { + card.remove(); + }, 2000); + } +} + +function notifyPhotoVerificationRequest(id) { + console.log(id, 'method to be implemented on the backend'); +} + +function createPhotoVerificationRequestImageBox( + prevImage, + newImage, + discordImage, +) { + const imageBox = document.createElement('div'); + imageBox.className = 'photo-verification-image-box'; + + const prevImageBox = document.createElement('div'); + prevImageBox.className = 'photo-verification-image-box__block'; + prevImageBox.innerHTML = `

Previous Image

Previous Image`; + imageBox.appendChild(prevImageBox); + + const newImageBox = document.createElement('div'); + newImageBox.className = 'photo-verification-image-box__block'; + newImageBox.innerHTML = `

New Image

New Image`; + imageBox.appendChild(newImageBox); + + const discordImageBox = document.createElement('div'); + discordImageBox.className = + 'photo-verification-image-box__block photo-verification-image-box__block--discord'; + discordImageBox.innerHTML = `

Discord Image

Discord Image`; + imageBox.appendChild(discordImageBox); + + return imageBox; +} + +function createPhotoVerificationRequestButtonBox( + approvePhoto, + rejectPhoto, + notifyPhoto, + refreshDiscordAvatarPhoto, + discordImageStatus, + profileImageStatus, +) { + const buttonBox = document.createElement('div'); + buttonBox.className = 'photo-verification-button-box'; + + if (!discordImageStatus) { + const approveDiscordButton = document.createElement('button'); + approveDiscordButton.innerText = 'Approve Discord'; + approveDiscordButton.onclick = () => approvePhoto('discord'); + buttonBox.appendChild(approveDiscordButton); + } + + if (!profileImageStatus) { + const approveProfileButton = document.createElement('button'); + approveProfileButton.innerText = 'Approve Profile'; + approveProfileButton.onclick = () => approvePhoto('profile'); + buttonBox.appendChild(approveProfileButton); + } + + const approveBothButton = document.createElement('button'); + approveBothButton.innerText = 'Approve Both'; + approveBothButton.className = 'approve-both-button'; + approveBothButton.onclick = () => approvePhoto('both'); + buttonBox.appendChild(approveBothButton); + + const rejectButton = document.createElement('button'); + rejectButton.innerText = 'Reject'; + rejectButton.className = 'reject-button'; + rejectButton.onclick = rejectPhoto; + buttonBox.appendChild(rejectButton); + + const refreshDiscordAvatarButton = document.createElement('button'); + refreshDiscordAvatarButton.innerText = 'Refresh Discord Image'; + refreshDiscordAvatarButton.onclick = refreshDiscordAvatarPhoto; + refreshDiscordAvatarButton.className = 'refresh-discord-avatar-button'; + buttonBox.appendChild(refreshDiscordAvatarButton); + + const notifyButton = document.createElement('button'); + notifyButton.innerText = 'Notify User'; + notifyButton.onclick = notifyPhoto; + notifyButton.className = 'notify-button'; + notifyButton.disabled = true; + buttonBox.appendChild(notifyButton); + + return buttonBox; +} + +function createPhotoVerificationRequestStatusBox( + discordImageStatus, + profileImageStatus, +) { + const statusBox = document.createElement('div'); + statusBox.className = 'photo-verification-status-box'; + + const statusHeading = document.createElement('h3'); + statusHeading.innerText = 'Status'; + statusBox.appendChild(statusHeading); + + const statusBoxContent = document.createElement('div'); + statusBoxContent.className = `photo-verification-status-box__block`; + statusBoxContent.innerHTML = `

Discord Image - ${ + discordImageStatus ? 'Approved' : 'Pending' + }

Profile Image - ${ + profileImageStatus ? 'Approved' : 'Pending' + }

`; + statusBox.appendChild(statusBoxContent); + + return statusBox; +} + +function createPhotoVerificationRequestCard(photoVerificationRequest) { + const card = document.createElement('div'); + card.className = `photo-verification-card photo-verification-card--${photoVerificationRequest.id}`; + + const heading = document.createElement('h3'); + heading.innerText = `Photo Verifcation for ${photoVerificationRequest.user?.username}`; + card.appendChild(heading); + + card.appendChild( + createPhotoVerificationRequestImageBox( + photoVerificationRequest.user.picture, + photoVerificationRequest.profile.url, + photoVerificationRequest.discord.url, + ), + ); + + card.appendChild( + createPhotoVerificationRequestStatusBox( + photoVerificationRequest.discord.approved, + photoVerificationRequest.profile.approved, + ), + ); + + card.appendChild( + createPhotoVerificationRequestButtonBox( + (imageType) => + approvePhotoVerificationRequest( + photoVerificationRequest.id, + photoVerificationRequest.userId, + imageType, + ), + () => + rejectPhotoVerificationRequest( + photoVerificationRequest.id, + photoVerificationRequest.userId, + ), + () => notifyPhotoVerificationRequest(photoVerificationRequest.id), + () => + refreshDiscordImage( + photoVerificationRequest.discordId, + photoVerificationRequest.id, + ), + photoVerificationRequest.discord.approved, + photoVerificationRequest.profile.approved, + ), + ); + + return card; +} + +async function getPhotoVerificationRequests(username) { + let url = `${API_BASE_URL}/users/picture/all/`; + if (username) { + url += `?username=${username}`; + } + + const response = await fetch(url, { + credentials: 'include', + method: 'GET', + headers: { + 'Content-type': 'application/json', + }, + }); + return await response.json(); +} + +async function modifyPhotoVerificationRequest(userId, imageType, status) { + const response = await fetch( + `${API_BASE_URL}/users/picture/verify/${userId}/?status=${status}&type=${imageType}`, + { + credentials: 'include', + method: 'PATCH', + headers: { + 'Content-type': 'application/json', + }, + }, + ); + return { data: await response.json(), statusCode: response.status }; +} + +async function refreshDiscordImageRequest(discordId) { + const response = await fetch( + `${API_BASE_URL}/discord-actions/avatar/photo-verification-update/${discordId}`, + { + credentials: 'include', + method: 'PATCH', + headers: { + 'Content-type': 'application/json', + }, + }, + ); + return { data: await response.json(), statusCode: response.status }; +} + +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} diff --git a/photo-verification-requests/script.js b/photo-verification-requests/script.js new file mode 100644 index 00000000..09b556bb --- /dev/null +++ b/photo-verification-requests/script.js @@ -0,0 +1,35 @@ +const photoVerificationRequestContainer = document.querySelector( + '.photo-verification-requests', +); +const userSearchInput = document.querySelector('#user-search'); + +async function render() { + const photoVerificationRequests = await getPhotoVerificationRequests(); + const photoVerificationRequestObjects = photoVerificationRequests.data; + photoVerificationRequestObjects.forEach((obj) => { + photoVerificationRequestContainer.append( + createPhotoVerificationRequestCard(obj), + ); + }); +} + +render(); + +async function onUserSearchInput(e) { + photoVerificationRequestContainer.innerHTML = ''; + if (e.target.value === '') { + render(); + } + const photoVerificationRequests = await getPhotoVerificationRequests( + e.target.value, + ); + const photoVerificationRequestObjects = photoVerificationRequests.data; + photoVerificationRequestContainer.innerHTML = ''; + photoVerificationRequestObjects.forEach((obj) => { + photoVerificationRequestContainer.append( + createPhotoVerificationRequestCard(obj), + ); + }); +} + +userSearchInput.addEventListener('input', debounce(onUserSearchInput, 500)); diff --git a/photo-verification-requests/style.css b/photo-verification-requests/style.css new file mode 100644 index 00000000..107c4add --- /dev/null +++ b/photo-verification-requests/style.css @@ -0,0 +1,178 @@ +: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); +} + +*, +::after, +::before { + box-sizing: border-box; + font-family: monospace; + margin: 0; + padding: 0; +} +.header { + background-color: var(--dark-blue); + text-align: center; + color: var(--white); + padding: 2rem; +} + +.search-filter { + display: flex; + justify-content: end; + align-items: center; + width: 100%; + padding: 2.5rem; + gap: 1rem; +} + +#user-search { + width: 90%; + max-width: 15rem; + min-width: 10rem; + padding: 0.7rem 0.7rem; + border-radius: 0.4rem; + border: 2.5px solid var(--black-color); + font-size: medium; + background-color: var(--light-gray-color); + margin: 0 10px; + height: 2.5rem; +} + +.container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 1.5rem; +} + +.photo-verification-requests { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 1.5rem; +} + +.photo-verification-card { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + width: min-content; + padding: 1.5rem; + border-radius: 10px; + text-align: left; + border: 1px solid var(--medium-gray); + position: relative; + max-width: 46rem; + width: 100%; + gap: 1rem; + max-height: 100%; +} + +.photo-verification-card > h3 { + font-size: 1.5rem; + font-weight: 500; +} + +.photo-verification-image-box { + display: flex; + justify-content: center; + align-items: center; + margin-top: 20px; + gap: 1.5rem; + width: 100%; +} + +.photo-verification-image-box__block { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 5px; +} + +.photo-verification-image-box__block > img { + width: 150px; + aspect-ratio: 1/1; + object-fit: cover; +} + +.photo-verification-button-box { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 1.2rem; + margin-top: 20px; + margin-left: auto; + flex-wrap: wrap; +} + +.photo-verification-button-box > button { + padding: 10px 20px; + border: none; + border-radius: 5px; + background-color: var(--green500); + color: var(--white); + cursor: pointer; + transition: all 0.3s; +} + +.photo-verification-button-box > .reject-button { + background-color: var(--color-red); +} +.photo-verification-button-box > .notify-button, +.photo-verification-button-box > .refresh-discord-avatar-button { + background-color: var(--blue-color); +} + +.photo-verification-button-box > button:hover { + opacity: 0.9; +} + +button:disabled, +button:disabled:hover { + opacity: 0.5; + cursor: not-allowed; +} + +.photo-verification-status-box { + display: flex; + justify-content: center; + align-items: flex-start; + gap: 10px; + margin-top: 20px; + flex-direction: column; + width: 100%; +} diff --git a/script.js b/script.js index 8126ee05..665e5a64 100644 --- a/script.js +++ b/script.js @@ -2,6 +2,9 @@ const userManagementLink = document.getElementById(USER_MANAGEMENT_LINK); const discordUserLink = document.getElementById('discord-user-link'); const extensionRequestsLink = document.getElementById(EXTENSION_REQUESTS_LINK); const syncUsersStatusButton = document.getElementById(SYNC_USERS_STATUS); +const photoVerificationRequestsButton = document.getElementById( + PHOTO_VERIFICATION_REQUESTS_BUTTON, +); const UpdatedstatusMessage = 'All repos uptodate'; const syncExternalAccountsButton = document.getElementById( SYNC_EXTERNAL_ACCOUNTS, @@ -95,6 +98,7 @@ showSuperUserOptions( extensionRequestsLink, discordUserLink, taskRequestsLink, + photoVerificationRequestsButton, ); const createGoalButton = document.getElementById('create-goal');