├── .gitignore ├── .prettierignore ├── images ├── avatar.png ├── denied.png ├── github.png ├── index.ico ├── approved.png ├── edit-icon.png ├── No-profile-pic.jpg ├── calendar-plus.png ├── page-not-found.png ├── Real-Dev-Squad@1x.png ├── funnel.svg ├── chevron-down-black.svg ├── check-icon.svg ├── check-icon-white.svg ├── chevron-down.svg ├── x-icon-white.svg ├── x-icon.svg ├── external-link.svg ├── time.svg ├── edit-icon.svg ├── sort-desc.svg ├── sort-asc.svg ├── filter-icon.svg ├── x-icon-green.svg ├── x-icon-purple.svg ├── x-icon-red.svg └── x-icon-black.svg ├── babel.config.js ├── task └── constants.js ├── feed ├── assets │ ├── leave.webp │ ├── task.webp │ ├── extensionReq.webp │ ├── taskRequests.webp │ └── user.svg ├── constants.js └── index.html ├── groups ├── assets │ ├── github.png │ ├── avatar.svg │ ├── left-arrow.svg │ ├── plus.svg │ ├── search.svg │ ├── info.svg │ ├── delete.svg │ ├── close.svg │ └── person.svg └── index.html ├── users ├── images │ ├── favicon.ico │ ├── hamburger.png │ ├── four-squares-button.png │ ├── arrow-icon.svg │ ├── twitter.svg │ ├── github.svg │ ├── discord.svg │ ├── linkedin.svg │ ├── lock-icon.svg │ └── info.svg ├── discord │ ├── components │ │ ├── NoUserFound.js │ │ ├── TabsSection.js │ │ ├── UsersSection.js │ │ └── UserDetailsSection.js │ ├── index.js │ ├── utils │ │ ├── util.js │ │ └── react.js │ ├── index.html │ ├── style.css │ └── App.js ├── utils.js ├── constants.js ├── details │ ├── constants.js │ └── index.html └── index.html ├── taskEvents ├── assets │ ├── down.png │ ├── Avatar.png │ ├── cancel.png │ └── cancel-black.png ├── index.html └── skillElement.js ├── .prettierrc.json ├── task-requests ├── assets │ ├── RDSLogo.png │ ├── funnel.svg │ ├── sort-menu.svg │ ├── sort-up.svg │ └── sort-down.svg ├── constants.js └── util.js ├── standup ├── constants.js ├── index.html └── utils.js ├── mock-data ├── skills │ └── index.js ├── constants.js ├── levels │ └── index.js ├── profile-diff-details │ └── index.js ├── tags │ └── index.js ├── user-details │ └── index.js ├── members │ └── index.js ├── tasks-card-date-time-end-date-self │ └── index.js ├── users-status │ └── index.js ├── groups │ └── index.js ├── tasks │ └── index.js ├── users │ └── mockdata.js ├── logs │ └── index.js └── standup │ └── index.js ├── helpers └── loadENV.js ├── jest.config.js ├── admin-panel ├── utils.js ├── index.html └── index.css ├── profile-diffs ├── constants.js ├── profileDiffs.utils.js ├── profileDiffs.apiCalls.js ├── assets │ ├── sort-asc.svg │ ├── sort-desc.svg │ └── search.svg └── index.html ├── utils └── time │ └── index.js ├── jest-puppeteer.config.js ├── profile-diff-details ├── constants.js ├── assets │ ├── default-profile.svg │ └── left-arrow.svg ├── profileDiffDetails.apiCalls.js ├── profileDiffDetails.utils.js └── index.html ├── footer └── footerComponent.js ├── profile ├── constants.js ├── script.js └── index.html ├── applications ├── assets │ └── closeButton.svg └── utils.js ├── goal ├── script.js └── style.css ├── LICENSE ├── requests └── constants.js ├── package.json ├── featureFlag ├── script.js └── style.css ├── components ├── request-card │ └── constant.js ├── toast │ ├── style.css │ └── script.js └── filter │ └── style.css ├── identity-service-logs ├── script.js ├── constants.js └── index.html ├── createGoalTest.js ├── wallet ├── index.html ├── style.css └── script.js ├── online-members ├── online-members.html ├── constants.js ├── sse-events.js ├── utils.js └── online-members.css ├── extension-requests ├── constants.js └── index.html ├── constants.js ├── server.js ├── footerTest.js ├── __tests__ ├── identity-service-logs │ └── identity-service-logs.test.js ├── user-details │ └── Intro-button.test.js ├── users │ ├── onboarding31days.test.js │ └── applyFilterPagination.test.js └── tasks │ └── profile-picture.test.js ├── navbar.global.js ├── .github └── workflows │ └── workflow.yml └── userLogin.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage 2 | .github -------------------------------------------------------------------------------- /images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/avatar.png -------------------------------------------------------------------------------- /images/denied.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/denied.png -------------------------------------------------------------------------------- /images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/github.png -------------------------------------------------------------------------------- /images/index.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/index.ico -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const plugins = ['istanbul']; 2 | 3 | module.exports = { 4 | plugins: plugins, 5 | }; 6 | -------------------------------------------------------------------------------- /images/approved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/approved.png -------------------------------------------------------------------------------- /images/edit-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/edit-icon.png -------------------------------------------------------------------------------- /task/constants.js: -------------------------------------------------------------------------------- 1 | const StatusType = { 2 | AVAILABLE: 'AVAILABLE', 3 | ASSIGNED: 'ASSIGNED', 4 | }; 5 | -------------------------------------------------------------------------------- /feed/assets/leave.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/feed/assets/leave.webp -------------------------------------------------------------------------------- /feed/assets/task.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/feed/assets/task.webp -------------------------------------------------------------------------------- /groups/assets/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/groups/assets/github.png -------------------------------------------------------------------------------- /images/No-profile-pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/No-profile-pic.jpg -------------------------------------------------------------------------------- /images/calendar-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/calendar-plus.png -------------------------------------------------------------------------------- /images/page-not-found.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/page-not-found.png -------------------------------------------------------------------------------- /users/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/users/images/favicon.ico -------------------------------------------------------------------------------- /taskEvents/assets/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/taskEvents/assets/down.png -------------------------------------------------------------------------------- /users/images/hamburger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/users/images/hamburger.png -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /feed/assets/extensionReq.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/feed/assets/extensionReq.webp -------------------------------------------------------------------------------- /feed/assets/taskRequests.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/feed/assets/taskRequests.webp -------------------------------------------------------------------------------- /images/Real-Dev-Squad@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/images/Real-Dev-Squad@1x.png -------------------------------------------------------------------------------- /taskEvents/assets/Avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/taskEvents/assets/Avatar.png -------------------------------------------------------------------------------- /taskEvents/assets/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/taskEvents/assets/cancel.png -------------------------------------------------------------------------------- /task-requests/assets/RDSLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/task-requests/assets/RDSLogo.png -------------------------------------------------------------------------------- /standup/constants.js: -------------------------------------------------------------------------------- 1 | const RDS_API_USERS = `${API_BASE_URL}/users`; 2 | const RDS_API_STANDUP = `${API_BASE_URL}/progresses?userId=`; 3 | -------------------------------------------------------------------------------- /taskEvents/assets/cancel-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/taskEvents/assets/cancel-black.png -------------------------------------------------------------------------------- /users/images/four-squares-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Real-Dev-Squad/website-dashboard/HEAD/users/images/four-squares-button.png -------------------------------------------------------------------------------- /mock-data/skills/index.js: -------------------------------------------------------------------------------- 1 | const skills = [ 2 | { id: 1, name: 'JavaScript' }, 3 | { id: 2, name: 'React' }, 4 | { id: 3, name: 'Node.js' }, 5 | ]; 6 | 7 | module.exports = { skills }; 8 | -------------------------------------------------------------------------------- /helpers/loadENV.js: -------------------------------------------------------------------------------- 1 | window.API_BASE_URL = 'https://api.realdevsquad.com'; 2 | 3 | if (window.location.hostname !== 'dashboard.realdevsquad.com') { 4 | window.API_BASE_URL = 'https://staging-api.realdevsquad.com'; 5 | } 6 | -------------------------------------------------------------------------------- /users/discord/components/NoUserFound.js: -------------------------------------------------------------------------------- 1 | const { createElement } = react; 2 | export const NoUserFound = () => { 3 | return createElement('section', { class: 'no_user_found' }, [ 4 | 'No User Found', 5 | ]); 6 | }; 7 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | preset: 'jest-puppeteer', 3 | collectCoverage: true, 4 | collectCoverageFrom: ['src/**/*'], 5 | reporters: ['default'], 6 | coverageDirectory: 'coverage', 7 | }; 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /users/discord/index.js: -------------------------------------------------------------------------------- 1 | import { App } from './App.js'; 2 | // const urlParams = new URLSearchParams(window.location.search); 3 | 4 | // if (!urlParams.get('dev')) history.back(); 5 | 6 | window['root'].lastElementChild?.remove(); 7 | react.render(App(), window['root']); 8 | -------------------------------------------------------------------------------- /groups/assets/avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /groups/assets/left-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /users/images/arrow-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /admin-panel/utils.js: -------------------------------------------------------------------------------- 1 | function createElement({ type, attributes = {}, innerText }) { 2 | const element = document.createElement(type); 3 | Object.keys(attributes).forEach((item) => { 4 | element.setAttribute(item, attributes[item]); 5 | }); 6 | element.textContent = innerText; 7 | return element; 8 | } 9 | 10 | export { createElement }; 11 | -------------------------------------------------------------------------------- /mock-data/constants.js: -------------------------------------------------------------------------------- 1 | const STAGING_API_URL = 'https://staging-api.realdevsquad.com'; 2 | const LOCAL_TEST_PAGE_URL = 'http://localhost:8000'; 3 | const SKILL_TREE_BACKEND_BASE_URL = 4 | 'https://services.realdevsquad.com/skilltree/v1'; 5 | 6 | module.exports = { 7 | STAGING_API_URL, 8 | LOCAL_TEST_PAGE_URL, 9 | SKILL_TREE_BACKEND_BASE_URL, 10 | }; 11 | -------------------------------------------------------------------------------- /groups/assets/plus.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /groups/assets/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/funnel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /images/chevron-down-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /task-requests/assets/funnel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /images/check-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /images/check-icon-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /images/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /task-requests/assets/sort-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /task-requests/assets/sort-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /profile-diffs/constants.js: -------------------------------------------------------------------------------- 1 | const SORT_BUTTON = '.sort-button'; 2 | const SORT_ASC_ICON = 'asc-sort-icon'; 3 | const SORT_DESC_ICON = 'desc-sort-icon'; 4 | const DEFAULT_PAGE_SIZE = 10; 5 | const OLDEST_FIRST = 'Oldest first'; 6 | const NEWEST_FIRST = 'Newest first'; 7 | const SEARCH_ELEMENT = 'assignee-search'; 8 | const LAST_ELEMENT_CONTAINER = '.virtual'; 9 | 10 | const Status = Object.freeze({ 11 | APPROVED: 'APPROVED', 12 | PENDING: 'PENDING', 13 | NOT_APPROVED: 'NOT APPROVED', 14 | }); 15 | 16 | const Order = Object.freeze({ 17 | DESCENDING: 'desc', 18 | ASCENDING: 'asc', 19 | }); 20 | -------------------------------------------------------------------------------- /task-requests/assets/sort-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /utils/time/index.js: -------------------------------------------------------------------------------- 1 | function getHumanReadableDate(timeStamp) { 2 | if (typeof timeStamp !== 'number') { 3 | return 'N/A'; 4 | } 5 | const date = new Date(timeStamp); 6 | 7 | const options = { 8 | weekday: 'long', 9 | month: 'short', 10 | day: 'numeric', 11 | year: 'numeric', 12 | }; 13 | const parts = date.toLocaleDateString('en-US', options).split(', '); 14 | 15 | const [weekday, monthDay, year] = parts; 16 | const [month, day] = monthDay.split(' '); 17 | 18 | const formattedDate = `${weekday}, ${day} ${month} ${year}`; 19 | 20 | return formattedDate; 21 | } 22 | -------------------------------------------------------------------------------- /jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | const ci = Boolean(process.env.CI || false); 2 | const baseOptions = { 3 | server: { 4 | command: 'npm start', 5 | port: 8000, 6 | launchTimeout: 30000, 7 | }, 8 | }; 9 | const ciPipelineOptions = { 10 | launch: { 11 | executablePath: '/usr/bin/google-chrome-stable', 12 | headless: true, 13 | args: [ 14 | '--ignore-certificate-errors', 15 | '--no-sandbox', 16 | '--disable-setuid-sandbox', 17 | '--disable-gpu', 18 | ], 19 | }, 20 | server: baseOptions.server, 21 | }; 22 | module.exports = ci ? ciPipelineOptions : baseOptions; 23 | -------------------------------------------------------------------------------- /profile-diff-details/constants.js: -------------------------------------------------------------------------------- 1 | const YEARS_OF_EXPERIENCE = 'yoe'; 2 | 3 | const fieldDisplayName = Object.freeze({ 4 | first_name: 'First name', 5 | last_name: 'Last name', 6 | designation: 'Role', 7 | company: 'Company', 8 | yoe: 'Years of experience', 9 | email: 'Email', 10 | phone: 'Phone no.', 11 | linkedin_id: 'Linkedin', 12 | github_id: 'Github', 13 | twitter_id: 'Twitter', 14 | instagram_id: 'Instagram', 15 | website: 'Website', 16 | }); 17 | 18 | const Status = Object.freeze({ 19 | APPROVED: 'APPROVED', 20 | PENDING: 'PENDING', 21 | NOT_APPROVED: 'NOT APPROVED', 22 | }); 23 | -------------------------------------------------------------------------------- /users/discord/utils/util.js: -------------------------------------------------------------------------------- 1 | const API_BASE_URL = window.API_BASE_URL; 2 | 3 | export const getUsers = async (tab) => { 4 | let URL = { 5 | in_discord: `${API_BASE_URL}/users/search/?role=in_discord`, 6 | verified: `${API_BASE_URL}/users/search/?verified=true`, 7 | }; 8 | 9 | try { 10 | const response = await fetch(URL[tab], { 11 | method: 'GET', 12 | credentials: 'include', 13 | headers: { 14 | 'Content-type': 'application/json', 15 | }, 16 | }); 17 | 18 | const data = await response.json(); 19 | return data.users ?? []; 20 | } catch (err) { 21 | console.error(err); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /footer/footerComponent.js: -------------------------------------------------------------------------------- 1 | function loadFooter() { 2 | const footerHTML = ` 3 | 16 | `; 17 | 18 | document.body.insertAdjacentHTML('beforeend', footerHTML); 19 | } 20 | -------------------------------------------------------------------------------- /profile/constants.js: -------------------------------------------------------------------------------- 1 | export const YEARS_OF_EXPERIENCE = 'yoe'; 2 | export const SUPER_USER = 'super_user'; 3 | export const APPROVE_BUTTON_TEXT = 'Approve'; 4 | export const REJECT_BUTTON_TEXT = 'Reject'; 5 | export const APPROVAL_PROMPT_TEXT = 'Reason for Approval'; 6 | export const ALERT_APPROVED = 'User Data Approved !!!'; 7 | export const ALERT_ERROR = 'Something went wrong. Please check console errors.'; 8 | export const ALERT_REJECTED = 'User Data Rejected!!!'; 9 | export const OLD_DATA = 'old-data'; 10 | export const NEW_DATA = 'new-data'; 11 | export const DIFF_CLASS = 'diff'; 12 | export const OLD_DIFF_CLASS = 'oldDiff'; 13 | export const NEW_DIFF_CLASS = 'newDiff'; 14 | -------------------------------------------------------------------------------- /mock-data/levels/index.js: -------------------------------------------------------------------------------- 1 | const levels = { 2 | message: 'Levels returned Successfully', 3 | tags: [ 4 | { 5 | id: 'TBMH3cKsgZSckJRdvvjZ', 6 | date: { 7 | _seconds: 1679415677, 8 | _nanoseconds: 311000000, 9 | }, 10 | createdBy: 'rXmhqm0Bi5xvSlgE6z0v', 11 | name: 'demo', 12 | value: 3, 13 | }, 14 | { 15 | id: 'TI95MLOiWt6TYfw3jqHR', 16 | date: { 17 | _seconds: 1680010158, 18 | _nanoseconds: 168000000, 19 | }, 20 | createdBy: 'rXmhqm0Bi5xvSlgE6z0v', 21 | name: 'test-demo-level', 22 | value: 2, 23 | }, 24 | ], 25 | }; 26 | 27 | module.exports = { levels }; 28 | -------------------------------------------------------------------------------- /images/x-icon-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /images/x-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /profile-diffs/profileDiffs.utils.js: -------------------------------------------------------------------------------- 1 | // Utility functions 2 | const parseProfileDiffParams = (uri, nextPageParamsObject) => { 3 | const urlSearchParams = new URLSearchParams(uri); 4 | for (const [key, value] of urlSearchParams.entries()) { 5 | nextPageParamsObject[key] = value; 6 | } 7 | return nextPageParamsObject; 8 | }; 9 | 10 | const generateProfileDiffsParams = (nextPageParams, forApi = true) => { 11 | const urlSearchParams = new URLSearchParams(); 12 | for (const [key, value] of Object.entries(nextPageParams)) { 13 | if (!value) continue; 14 | urlSearchParams.append(key, value); 15 | } 16 | return `/${ 17 | forApi ? 'profileDiffs' : 'profile-diffs' 18 | }?${urlSearchParams.toString()}`; 19 | }; 20 | -------------------------------------------------------------------------------- /images/external-link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /images/time.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 9 | 11 | -------------------------------------------------------------------------------- /users/utils.js: -------------------------------------------------------------------------------- 1 | async function makeApiCall( 2 | url, 3 | method = 'get', 4 | body = null, 5 | credentials = 'include', 6 | headers = { 'content-type': 'application/json' }, 7 | options = null, 8 | ) { 9 | try { 10 | const response = await fetch(url, { 11 | method, 12 | body, 13 | headers, 14 | credentials, 15 | ...options, 16 | }); 17 | return response; 18 | } catch (err) { 19 | console.error(err); 20 | throw err; 21 | } 22 | } 23 | 24 | function debounce(func, delay) { 25 | let timerId; 26 | return (...args) => { 27 | if (timerId) { 28 | clearTimeout(timerId); 29 | } 30 | 31 | timerId = setTimeout(() => { 32 | func(...args); 33 | }, delay); 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /users/discord/components/TabsSection.js: -------------------------------------------------------------------------------- 1 | const { createElement } = react; 2 | 3 | export const TabsSection = ({ tabs, activeTab, handleTabNavigation }) => { 4 | return createElement( 5 | 'select', 6 | { 7 | class: 'tabs_section', 8 | onchange: handleTabNavigation, 9 | 'data-testid': 'tabs-section-select', 10 | }, 11 | tabs.map((tabItem) => { 12 | return createElement( 13 | 'option', 14 | { 15 | data_key: `${tabItem.id}`, 16 | class: `tab ${activeTab === tabItem.id ? 'active_tab' : ''}`, 17 | value: `${tabItem.value}`, 18 | ...(activeTab === tabItem.id && { selected: 'true' }), 19 | }, 20 | [tabItem.display_name], 21 | ); 22 | }), 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /users/images/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /applications/assets/closeButton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /mock-data/profile-diff-details/index.js: -------------------------------------------------------------------------------- 1 | const pendingProfileDiff = { 2 | message: 'Profile Diff returned successfully!', 3 | profileDiff: { 4 | id: 'n1AzYaVnVBshyIbkhsVG', 5 | designation: 'Engineer', 6 | linkedin_id: 'randhir-kumar-a4a2981b0', 7 | github_id: 'heyrandhir', 8 | company: 'Autoliv', 9 | approval: 'PENDING', 10 | instagram_id: '', 11 | timestamp: { 12 | _seconds: 1736964038, 13 | _nanoseconds: 973233000, 14 | }, 15 | yoe: 3, 16 | website: 'https://portfolio-ankur.com', 17 | userId: '7yzVDl8s1ORNCtH9Ps7K', 18 | 19 | email: 'an**************om', 20 | phone: '9********4', 21 | twitter_id: 'randhirxp', 22 | first_name: 'Randhir', 23 | last_name: 'Kumar', 24 | }, 25 | }; 26 | 27 | module.exports = { pendingProfileDiff }; 28 | -------------------------------------------------------------------------------- /users/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /groups/assets/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /images/edit-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /profile-diff-details/assets/default-profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /users/images/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /images/sort-desc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/sort-asc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /users/constants.js: -------------------------------------------------------------------------------- 1 | const RDS_API_USERS = `${API_BASE_URL}/users`; 2 | const RDS_API_SKILLS = `${API_BASE_URL}/tags`; 3 | const USER_LIST_ELEMENT = 'user-list'; 4 | const HEAD_LIST_ELEMENT = 'head_list'; 5 | const LOADER_ELEMENT = 'loader'; 6 | const USER_LOADER_ELEMENT = 'loader_tag'; 7 | const TILE_VIEW_BTN = 'tile-view-btn'; 8 | const TABLE_VIEW_BTN = 'table-view-btn'; 9 | const USER_SEARCH_ELEMENT = 'user-search'; 10 | const DEFAULT_AVATAR = '/images/avatar.png'; 11 | const USER_FETCH_COUNT = 100; 12 | const NONE = 'NONE'; 13 | const OOO = 'OOO'; 14 | const ACTIVE = 'ACTIVE'; 15 | const IDLE = 'IDLE'; 16 | const FILTER_MODAL = 'filter-modal'; 17 | const FILTER_BUTTON = 'filter-button'; 18 | const AVAILABILITY_FILTER = 'filter-by-avaviability'; 19 | const APPLY_FILTER_BUTTON = 'apply-filter-button'; 20 | const CLEAR_BUTTON = 'clear-button'; 21 | const STATUS_LIST = ['ooo', 'active', 'idle']; 22 | -------------------------------------------------------------------------------- /images/filter-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /goal/script.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | const edits = document.querySelectorAll('.inputBox label.editable'); 3 | edits.forEach((edit, index) => { 4 | const element = document.createElement('span'); 5 | element.innerHTML = 'Edit'; 6 | element.classList.add('edit-button'); 7 | element.addEventListener('click', (event) => { 8 | event.target.classList.toggle('edit-button__active'); 9 | const input = event.target.parentElement.nextElementSibling; 10 | input.classList.toggle('notEditing'); 11 | }); 12 | edit.append(element); 13 | }); 14 | })(); 15 | 16 | const createGoalPage = document.getElementById('goal-page-container'); 17 | const errorContainer = document.getElementById('error-container'); 18 | const params = new URLSearchParams(window.location.search); 19 | if (params.get('dev') === 'true') { 20 | createGoalPage.classList.remove('hidden'); 21 | errorContainer.classList.add('hidden'); 22 | } 23 | -------------------------------------------------------------------------------- /users/images/linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /feed/assets/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /profile-diffs/profileDiffs.apiCalls.js: -------------------------------------------------------------------------------- 1 | const getProfileDiffs = async (query = {}, nextLink) => { 2 | const finalUrl = 3 | API_BASE_URL + 4 | (nextLink || generateProfileDiffsParams({ ...query, dev: true })); 5 | const res = await fetch(finalUrl, { 6 | credentials: 'include', 7 | method: 'GET', 8 | headers: { 9 | 'Content-type': 'application/json', 10 | }, 11 | }); 12 | if (res.status === 401) { 13 | return { notAuthorized: true }; 14 | } 15 | return await res.json(); 16 | }; 17 | 18 | const users = {}; 19 | 20 | const getUser = async (userId) => { 21 | if (users[userId]) { 22 | return users[userId]; 23 | } 24 | const userResponse = await fetch(`${API_BASE_URL}/users?id=${userId}`, { 25 | method: 'GET', 26 | credentials: 'include', 27 | headers: { 28 | 'Content-type': 'application/json', 29 | }, 30 | }); 31 | const { user } = await userResponse.json(); 32 | users[userId] = user; 33 | return user; 34 | }; 35 | -------------------------------------------------------------------------------- /users/discord/components/UsersSection.js: -------------------------------------------------------------------------------- 1 | const { createElement } = react; 2 | 3 | export const UsersSection = ({ users, showUser, handleUserSelected }) => { 4 | return createElement( 5 | 'aside', 6 | { 7 | class: 'users_section', 8 | 9 | 'data-testid': 'users-section', 10 | }, 11 | users?.map((user) => { 12 | return createElement( 13 | 'div', 14 | { 15 | class: `user_card ${ 16 | users[showUser].id === user.id ? 'active_tab' : '' 17 | }`, 18 | 'data-testid': `user-card-${user.id}`, 19 | 'data-key': user.id, 20 | onclick: () => handleUserSelected(user.id), 21 | }, 22 | [ 23 | createElement('img', { 24 | src: user?.picture?.url ?? dummyPicture, 25 | class: 'user_image', 26 | }), 27 | createElement('span', {}, [user.first_name + ' ' + user.last_name]), 28 | ], 29 | ); 30 | }), 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /images/x-icon-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /images/x-icon-purple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /images/x-icon-red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /profile/script.js: -------------------------------------------------------------------------------- 1 | import { SUPER_USER } from './constants.js'; 2 | 3 | import { 4 | getProfileDiffs, 5 | getSelfUser, 6 | getUser, 7 | wantedData, 8 | createCard, 9 | } from './utils.js'; 10 | 11 | const self_user = await getSelfUser(); 12 | 13 | if (self_user?.roles[SUPER_USER]) { 14 | const { profileDiffs } = await getProfileDiffs(); 15 | if (profileDiffs === undefined || profileDiffs.length === 0) { 16 | document.getElementById('loader').innerHTML = 'No Profile Diffs !!!'; 17 | } else { 18 | profileDiffs.forEach(async (profileDiff) => { 19 | const { userId } = profileDiff; 20 | 21 | const user = await getUser(userId); 22 | const { username } = user; 23 | 24 | const { id, ...oldData } = wantedData(user); 25 | const { id: profileDiffId, ...newData } = wantedData(profileDiff); 26 | 27 | createCard({ oldData, newData, userId, username, profileDiffId }); 28 | }); 29 | } 30 | } else { 31 | document.getElementById('loader').innerHTML = 'You are not authorized !'; 32 | } 33 | -------------------------------------------------------------------------------- /feed/constants.js: -------------------------------------------------------------------------------- 1 | const ACITIVITY_FEED_CONTAINER = 'activity_feed_container'; 2 | 3 | const logType = { 4 | PROFILE_DIFF_APPROVED: 'PROFILE_DIFF_APPROVED', 5 | PROFILE_DIFF_REJECTED: 'PROFILE_DIFF_REJECTED', 6 | REQUEST_CREATED: 'REQUEST_CREATED', 7 | REQUEST_APPROVED: 'REQUEST_APPROVED', 8 | REQUEST_REJECTED: 'REQUEST_REJECTED', 9 | REQUEST_BLOCKED: 'REQUEST_BLOCKED', 10 | REQUEST_CANCELLED: 'REQUEST_CANCELLED', 11 | TASK: 'task', 12 | TASK_REQUESTS: 'taskRequests', 13 | EXTENSION_REQUESTS: 'extensionRequests', 14 | }; 15 | 16 | const CATEGORY = { 17 | ALL: 'all', 18 | OOO: 'ooo', 19 | TASK: 'task', 20 | TASK_REQUESTS: 'taskRequests', 21 | EXTENSION_REQUESTS: 'extensionRequests', 22 | }; 23 | 24 | const LAST_ELEMENT_CONTAINER = '.virtual'; 25 | 26 | const ERROR_MESSAGE = { 27 | UNAUTHENTICATED: 28 | 'You are unauthenticated to view this section, please login!', 29 | UNAUTHORIZED: 'You are unauthorized to view this section', 30 | LOGS_NOT_FOUND: 'No logs found', 31 | SERVER_ERROR: 'Unexpected error occurred', 32 | }; 33 | -------------------------------------------------------------------------------- /users/images/lock-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 10 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /mock-data/tags/index.js: -------------------------------------------------------------------------------- 1 | const tags = { 2 | message: 'Tags returned successfully', 3 | tags: [ 4 | { 5 | id: 'j0u4iNvSsP2coNBMDHfO', 6 | date: { 7 | _seconds: 1670515455, 8 | _nanoseconds: 617000000, 9 | }, 10 | reason: 'adding skills to users', 11 | createdBy: 'XAF7rSUvk4p0d098qWYS', 12 | type: 'SKILL', 13 | name: 'TEST-SKILL-2', 14 | }, 15 | { 16 | id: 'uxOsKU4riUzwDC3NG1RG', 17 | date: { 18 | _seconds: 1670105813, 19 | _nanoseconds: 422000000, 20 | }, 21 | reason: 'adding skills to users', 22 | createdBy: 'XAF7rSUvk4p0d098qWYS', 23 | type: 'SKILL', 24 | name: 'TEST-SKILL-1', 25 | }, 26 | { 27 | id: '1VCgWpTU90QG1nfTIO7b', 28 | date: { 29 | _seconds: 1670518002, 30 | _nanoseconds: 287000000, 31 | }, 32 | reason: 'adding skills to users', 33 | createdBy: 'XAF7rSUvk4p0d098qWYS', 34 | type: 'SKILL', 35 | name: 'TEST-SKILL-3', 36 | }, 37 | ], 38 | }; 39 | 40 | module.exports = { tags }; 41 | -------------------------------------------------------------------------------- /admin-panel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Admin Panel | Real Dev Squad 9 | 10 | 11 | 12 | 13 | 14 |

Loading...

15 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Real Dev Squad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /users/discord/utils/react.js: -------------------------------------------------------------------------------- 1 | const render = function (element, container) { 2 | if (!element) return container; 3 | if (typeof element == 'string' || typeof element == 'number') { 4 | container.appendChild(document.createTextNode(String(element))); 5 | return; 6 | } 7 | 8 | const component = document.createElement(element.tag); 9 | element.props && 10 | Object.keys(element.props).forEach((prop) => 11 | // based on requirements, any other type of event listner can be added here as 'prop' 12 | prop == 'onclick' || prop == 'onsubmit' || prop == 'onchange' 13 | ? (component[prop] = element.props[prop]) 14 | : component.setAttribute(prop, element.props[prop]), 15 | ); 16 | 17 | element.children?.forEach?.((child) => { 18 | render(child, component); 19 | }); 20 | 21 | container.appendChild(component); 22 | }; 23 | 24 | const react = { 25 | createElement: function (tag, props, children) { 26 | const el = { tag, props, children }; 27 | return el; 28 | }, 29 | render, 30 | rerender: function (element, container) { 31 | container.firstChild.remove(); 32 | render(element, container); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /profile-diff-details/assets/left-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /users/discord/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Discord Users | Real Dev Squad 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Discord Users

18 |
19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /images/x-icon-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /profile-diff-details/profileDiffDetails.apiCalls.js: -------------------------------------------------------------------------------- 1 | const getProfileDiff = async (id) => { 2 | try { 3 | const finalUrl = `${API_BASE_URL}/profileDiffs/${id}`; 4 | const res = await fetch(finalUrl, { 5 | credentials: 'include', 6 | method: 'GET', 7 | headers: { 8 | 'Content-type': 'application/json', 9 | }, 10 | }); 11 | if (res.status === 401) { 12 | return { notAuthorized: true }; 13 | } 14 | if (res.status === 404) { 15 | return { profileDiffExist: false }; 16 | } 17 | return { profileDiffExist: true, ...(await res.json()).profileDiff }; 18 | } catch (err) { 19 | throw err; 20 | } 21 | }; 22 | 23 | const fetchUser = async (userId) => { 24 | try { 25 | const userResponse = await fetch(`${API_BASE_URL}/users?id=${userId}`, { 26 | method: 'GET', 27 | credentials: 'include', 28 | headers: { 29 | 'Content-type': 'application/json', 30 | }, 31 | }); 32 | if (userResponse.status === 404) { 33 | return { userExist: false }; 34 | } 35 | const { user } = await userResponse.json(); 36 | return { ...user, userExist: true }; 37 | } catch (err) { 38 | throw err; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /requests/constants.js: -------------------------------------------------------------------------------- 1 | const DEV_FEATURE_FLAG = true; 2 | const Status = { 3 | APPROVED: 'APPROVED', 4 | PENDING: 'PENDING', 5 | REJECTED: 'REJECTED', 6 | }; 7 | 8 | const OOO_REQUEST_TYPE = 'OOO'; 9 | const EXTENSION_REQUEST_TYPE = 'EXTENSION'; 10 | const ONBOARDING_EXTENSION_REQUEST_TYPE = 'ONBOARDING'; 11 | const REQUEST_CONTAINER_ID = 'request_container'; 12 | const OOO_TAB_ID = 'ooo_tab_link'; 13 | const EXTENSION_TAB_ID = 'extension_tab_link'; 14 | const ONBOARDING_EXTENSION_TAB_ID = 'onboarding_extension_tab_link'; 15 | 16 | const DEFAULT_DATE_FORMAT = 'DD MMM YYYY'; 17 | 18 | const MessageStatus = { 19 | SUCCESS: 'SUCCESS', 20 | ERROR: 'ERROR', 21 | }; 22 | 23 | const ErrorMessages = { 24 | UNAUTHENTICATED: 25 | 'You are unauthenticated to view this section, please login!', 26 | UNAUTHORIZED: 'You are unauthorized to view this section', 27 | OOO_NOT_FOUND: 'OOO Requests not found', 28 | EXTENSION_NOT_FOUND: 'Extension Requests not found', 29 | ONBOARDING_EXTENSION_NOT_FOUND: 'Onboarding extension Requests not found', 30 | UNAUTHORIZED_ACTION: 'You are unauthorized to perform this action', 31 | SERVER_ERROR: 'Unexpected error occurred', 32 | }; 33 | 34 | const LAST_ELEMENT_CONTAINER = '.virtual'; 35 | -------------------------------------------------------------------------------- /mock-data/user-details/index.js: -------------------------------------------------------------------------------- 1 | const userDetails = { 2 | message: 'User returned successfully!', 3 | user: { 4 | id: 'DtR9sK7CysOVHP17zl8N', 5 | profileURL: 'https://profile-service-rds-randhir.herokuapp.com/', 6 | discordJoinedAt: '2022-01-02T02:56:16.935000+00:00', 7 | roles: { 8 | archived: false, 9 | in_discord: true, 10 | member: true, 11 | }, 12 | profileStatus: 'BLOCKED', 13 | yoe: 4, 14 | updated_at: 1691884285042, 15 | company: 'Autoliv', 16 | twitter_id: 'randhirxp', 17 | first_name: 'Randhir', 18 | website: '', 19 | incompleteUserDetails: false, 20 | discordId: '806833869038944276', 21 | last_name: 'Kumar', 22 | linkedin_id: 'randhir-kumar-a4a2981b0', 23 | picture: { 24 | url: 'https://res.cloudinary.com/realdevsquad/image/upload/v1673312957/profile/DtR9sK7CysOVHP17zl8N/bbtkpea622crqotnhsa3.jpg', 25 | publicId: 'profile/DtR9sK7CysOVHP17zl8N/bbtkpea622crqotnhsa3', 26 | }, 27 | instagram_id: '', 28 | github_display_name: 'Randhir Kumar Singh', 29 | github_id: 'heyrandhir', 30 | designation: 'Engineer', 31 | status: 'active', 32 | username: 'randhir', 33 | }, 34 | }; 35 | 36 | module.exports = { userDetails }; 37 | -------------------------------------------------------------------------------- /users/details/constants.js: -------------------------------------------------------------------------------- 1 | const defaultAvatar = '../images/profile.svg'; 2 | const socialMedia = ['twitter_id', 'github_id', 'linkedin_id']; 3 | const iconMapper = { 4 | twitter_id: { 5 | alt: 'twitter', 6 | src: '../images/twitter.svg', 7 | href: 'https://twitter.com', 8 | }, 9 | github_id: { 10 | alt: 'github', 11 | src: '../images/github.svg', 12 | href: 'https://github.com', 13 | }, 14 | linkedin_id: { 15 | alt: 'linkedIn', 16 | src: '../images/linkedin.svg', 17 | href: 'https://linkedin.com/in', 18 | }, 19 | }; 20 | 21 | const MESSAGE_NOT_FOUND = 'Not Found'; 22 | const MESSAGE_YEARS_OF_EXPERIENCE = 'Years of Experience'; 23 | const noProgressbarStatuses = ['COMPLETED', 'DONE', 'VERIFIED']; 24 | const READABLE_STATUS = { 25 | UN_ASSIGNED: 'Unassigned', 26 | ASSIGNED: 'Assigned', 27 | IN_PROGRESS: 'In Progress', 28 | BLOCKED: 'Blocked', 29 | COMPLETED: 'Completed', 30 | NEEDS_REVIEW: 'Needs Review', 31 | IN_REVIEW: 'In Review', 32 | APPROVED: 'Approved', 33 | SMOKE_TESTING: 'Smoke Testing', 34 | SANITY_CHECK: 'Sanity Check', 35 | REGRESSION_CHECK: 'Regression Check', 36 | MERGED: 'Merged', 37 | RELEASED: 'Released', 38 | VERIFIED: 'Verifed', 39 | DONE: 'Done', 40 | }; 41 | -------------------------------------------------------------------------------- /mock-data/members/index.js: -------------------------------------------------------------------------------- 1 | const members = { 2 | members: [ 3 | { 4 | id: 'tu4zVmwFX91e9oEuSWLg', 5 | incompleteUserDetails: false, 6 | discordJoinedAt: '2023-05-13T19:15:33.704000+00:00', 7 | discordId: '481782009321226241', 8 | github_display_name: 'Satyam Bajpai', 9 | roles: { 10 | archived: false, 11 | in_discord: true, 12 | member: false, 13 | }, 14 | github_id: 'satyam73', 15 | picture: { 16 | publicId: 'profile/tu4zVmwFX91e9oEuSWLg/woqilcqdbp0cudcexkem', 17 | url: 'https://res.cloudinary.com/imgprc/image/upload/v1688145505/profile/tu4zVmwFX91e9oEuSWLg/woqilcqdbp0cudcexkem.jpg', 18 | }, 19 | username: 'satyam', 20 | isMember: false, 21 | }, 22 | { 23 | id: 'txZFJLV40DcjsUtE0iGT', 24 | incompleteUserDetails: true, 25 | discordJoinedAt: '2023-04-14T04:06:56.117000+00:00', 26 | discordId: '464296397626146816', 27 | github_display_name: 'Sumit Dhanania', 28 | roles: { 29 | archived: false, 30 | in_discord: true, 31 | }, 32 | github_id: 'sumitd94', 33 | username: 'sumit', 34 | isMember: false, 35 | }, 36 | ], 37 | }; 38 | module.exports = { members }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website-dashboard", 3 | "version": "1.0.0", 4 | "description": "Dashboard that performs authorized actions acrosss the website", 5 | "main": "script.js", 6 | "scripts": { 7 | "check": "prettier --check .", 8 | "fix": "prettier --write .", 9 | "test": "jest", 10 | "start": "node server.js", 11 | "dev": "local-ssl-proxy --source 443 --target 5500 -n dev.realdevsquad.com" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Real-Dev-Squad/website-dashboard" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/Real-Dev-Squad/website-dashboard" 22 | }, 23 | "pre-commit": "check", 24 | "homepage": "https://github.com/Real-Dev-Squad/website-dashboard#readme", 25 | "devDependencies": { 26 | "jest": "^29.5.0", 27 | "jest-puppeteer": "^8.0.6", 28 | "jest-puppeteer-istanbul": "^0.5.3", 29 | "jsdom": "^21.1.1", 30 | "local-ssl-proxy": "^2.0.5", 31 | "pre-commit": "^1.2.2", 32 | "prettier": "2.3.1", 33 | "puppeteer": "^20.2.1", 34 | "puppeteer-core": "^19.9.0" 35 | }, 36 | "volta": { 37 | "node": "18.16.0", 38 | "yarn": "1.22.19" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /featureFlag/script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let flags; 4 | 5 | // initialize 6 | async function initialize() { 7 | flags = await fetchFlags(); 8 | renderFlagDropdown(flags); 9 | handleOnSubmit(); 10 | } 11 | initialize(); 12 | 13 | // On selecting/changing flag 14 | async function handleFlagChange(e) { 15 | const flagId = e.value; 16 | const flag = flags.find((flag) => flag.flagId === flagId); 17 | 18 | setFlag(flag); 19 | document.getElementById('create-flag').style.display = 'block'; 20 | } 21 | 22 | // On selecting Add button 23 | async function handleAddFlag() { 24 | document.getElementById('flag-dropdown-default').selected = 'selected'; 25 | setFlag(newFlagConfig); 26 | 27 | document.getElementById('create-flag').style.display = 'none'; 28 | console.log(document.getElementById('create-flag')); 29 | } 30 | 31 | // Submitting form 32 | async function handleOnSubmit() { 33 | const flagForm = document.querySelector('#flag-form'); 34 | flagForm.addEventListener('submit', (e) => { 35 | e.preventDefault(); 36 | 37 | toggleSubmitButton({ enable: false }); 38 | // Using flagId to determine weather we want to add or update flag 39 | const { flagId, ...data } = getFlagFormData(flagForm); 40 | flagId ? updateFlagCall(flagId, data) : addFlagCall(data); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /profile-diff-details/profileDiffDetails.utils.js: -------------------------------------------------------------------------------- 1 | function getUserDataItem(data, itemName) { 2 | const item = data[itemName]; 3 | 4 | if (item || (itemName === YEARS_OF_EXPERIENCE && item === 0)) { 5 | return item; 6 | } 7 | 8 | return ''; 9 | } 10 | 11 | function checkDifferentValues(primaryData, secondaryData) { 12 | const diffValues = new Set(); 13 | 14 | for (const listItem in primaryData) { 15 | const oldValue = getUserDataItem(primaryData, listItem); 16 | const newValue = getUserDataItem(secondaryData, listItem); 17 | const isValueEqual = String(oldValue).trim() === String(newValue).trim(); 18 | 19 | if (!isValueEqual) { 20 | diffValues.add(listItem); 21 | } 22 | } 23 | 24 | return diffValues; 25 | } 26 | 27 | function wantedData(data) { 28 | const { 29 | id, 30 | first_name, 31 | last_name, 32 | email, 33 | phone, 34 | yoe, 35 | company, 36 | designation, 37 | github_id, 38 | linkedin_id, 39 | twitter_id, 40 | instagram_id, 41 | website, 42 | } = data; 43 | return { 44 | id, 45 | first_name, 46 | last_name, 47 | email, 48 | phone, 49 | yoe, 50 | company, 51 | designation, 52 | github_id, 53 | linkedin_id, 54 | twitter_id, 55 | instagram_id, 56 | website, 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /components/request-card/constant.js: -------------------------------------------------------------------------------- 1 | const CARD_REMOVAL_INITIAL_DELAY_MS = 800; 2 | const CARD_REMOVAL_ANIMATION_DURATION_MS = 800; 3 | const CARD_REMOVAL_ANIMATION_EASING = 'ease-out'; 4 | const DEADLINE_WARNING_THRESHOLD_DAYS = 3; 5 | const HOVER_CARD_HIDE_DELAY = 300; 6 | const ICONS = Object.freeze({ 7 | DEFAULT_USER_AVATAR: '/images/avatar.png', 8 | EDIT: '/images/edit-icon.svg', 9 | CANCEL: '/images/x-icon.svg', 10 | CANCEL_WHITE: '/images/x-icon-white.svg', 11 | CHECK: '/images/check-icon.svg', 12 | CHECK_WHITE: '/images/check-icon-white.svg', 13 | ARROW_DOWN: '/images/chevron-down-black.svg', 14 | }); 15 | 16 | const REQUEST_STATUS = Object.freeze({ 17 | APPROVED: 'APPROVED', 18 | PENDING: 'PENDING', 19 | DENIED: 'DENIED', 20 | REJECTED: 'REJECTED', 21 | }); 22 | 23 | const REQUEST_TYPE = Object.freeze({ 24 | EXTENSION: 'EXTENSION', 25 | OOO: 'OOO', 26 | ONBOARDING: 'ONBOARDING', 27 | }); 28 | 29 | const ERROR_MESSAGE = Object.freeze({ 30 | UPDATE: 'Error updating request', 31 | DATE_INPUT_ERROR: 'Invalid date format. Please provide a valid date.', 32 | DEADLINE_PASSED: "Past date can't be the new deadline.", 33 | }); 34 | 35 | const SUCCESS_MESSAGE = Object.freeze({ 36 | APPROVED: 'Request approved successfully', 37 | REJECTED: 'Request rejected successfully', 38 | UPDATED: 'Request updated successfully', 39 | }); 40 | -------------------------------------------------------------------------------- /groups/assets/delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /mock-data/tasks-card-date-time-end-date-self/index.js: -------------------------------------------------------------------------------- 1 | const superUserDetails = { 2 | message: 'User returned successfully!', 3 | user: { 4 | id: 'XAF7rSUvk4p0d098qWYS', 5 | profileURL: 'https://my.realdevsquad.com/identity', 6 | discordJoinedAt: '2020-02-01T08:33:38.278000+00:00', 7 | roles: { 8 | archived: false, 9 | in_discord: true, 10 | member: true, 11 | super_user: true, 12 | admin: true, 13 | }, 14 | created_at: 1693166951852, 15 | yoe: '8', 16 | github_created_at: 1341655281000, 17 | updated_at: 1693224375990, 18 | company: 'Amazon', 19 | twitter_id: 'ankushdharkar', 20 | first_name: 'Ankush', 21 | ' instagram_id': 'ankushdharkar', 22 | website: 'NA', 23 | incompleteUserDetails: false, 24 | discordId: '154585730465660929', 25 | linkedin_id: 'ankushdharkar', 26 | last_name: 'Dharkar', 27 | picture: { 28 | publicId: 'profile/XAF7rSUvk4p0d098qWYS/me40uk7taytbjaa67mhe', 29 | url: 'https://res.cloudinary.com/realdevsquad/image/upload/v1692058952/profile/XAF7rSUvk4p0d098qWYS/me40uk7taytbjaa67mhe.jpg', 30 | }, 31 | github_display_name: 'Ankush Dharkar', 32 | company_name: 'Amazon', 33 | github_id: 'ankushdharkar', 34 | designation: 'SDE', 35 | status: 'idle', 36 | username: 'ankush', 37 | }, 38 | }; 39 | 40 | module.exports = { superUserDetails }; 41 | -------------------------------------------------------------------------------- /groups/assets/close.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /profile/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Profile Diffs | Real Dev Squad 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 25 | 26 |

Loading...

27 | 28 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /mock-data/users-status/index.js: -------------------------------------------------------------------------------- 1 | const usersStatus = { 2 | message: 'All User Status found successfully.', 3 | totalUserStatus: 2, 4 | allUserStatus: [ 5 | { 6 | id: 'QISvF7kAmnD9vXHwwIs8', 7 | userId: 'QISvF7kAmnD9vXHwwIs8', 8 | currentStatus: { 9 | state: 'ACTIVE', 10 | updatedAt: 1691993520.03, 11 | from: 1691993520.03, 12 | until: 1691993520.03, 13 | message: 'Message', 14 | }, 15 | futureStatus: { 16 | state: 'ACTIVE', 17 | updatedAt: 1691993520.03, 18 | from: 1691993520.03, 19 | until: 1691993520.03, 20 | message: 'Message', 21 | }, 22 | monthlyHours: { 23 | committed: 40, 24 | updatedAt: 1691993520.03, 25 | }, 26 | }, 27 | { 28 | id: 'lGQ3AjUlgNB6Jd8jXaEC', 29 | userId: 'lGQ3AjUlgNB6Jd8jXaEC', 30 | currentStatus: { 31 | state: 'ACTIVE', 32 | updatedAt: 1691993520.03, 33 | from: 1691993520.03, 34 | until: 1691993520.03, 35 | message: 'Message', 36 | }, 37 | futureStatus: { 38 | state: 'ACTIVE', 39 | updatedAt: 1691993520.03, 40 | from: 1691993520.03, 41 | until: 1691993520.03, 42 | message: 'Message', 43 | }, 44 | monthlyHours: { 45 | committed: 50, 46 | updatedAt: 1691993520.03, 47 | }, 48 | }, 49 | ], 50 | }; 51 | 52 | module.exports = { 53 | usersStatus, 54 | }; 55 | -------------------------------------------------------------------------------- /users/images/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | about 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /admin-panel/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | box-sizing: border-box; 4 | } 5 | #container { 6 | display: flex; 7 | flex-direction: column; 8 | margin: 0 auto; 9 | width: 100%; 10 | max-width: 600px; 11 | height: 100%; 12 | } 13 | 14 | #select-tags { 15 | padding: 10px 15px; 16 | margin-left: 10px; 17 | font-size: 1.2rem; 18 | width: 100%; 19 | } 20 | 21 | #main { 22 | width: 100%; 23 | margin-top: 20px; 24 | } 25 | 26 | .tag-create__container, 27 | .level-create__container { 28 | display: flex; 29 | flex-direction: column; 30 | width: 100%; 31 | padding: 10px; 32 | align-items: center; 33 | border: 1px solid; 34 | justify-content: center; 35 | } 36 | 37 | .input { 38 | font-weight: 550; 39 | width: 80%; 40 | margin-top: 20px; 41 | padding: 1rem; 42 | border: 1px solid gray; 43 | } 44 | 45 | input::placeholder { 46 | color: black; 47 | font-weight: 550; 48 | } 49 | 50 | .level-selector { 51 | font-weight: 550; 52 | width: 85%; 53 | margin-top: 20px; 54 | padding: 1rem; 55 | } 56 | 57 | .submit-button { 58 | color: white; 59 | background-color: royalblue; 60 | font-weight: 550; 61 | width: 85%; 62 | margin-top: 20px; 63 | margin-bottom: 20px; 64 | padding: 1rem; 65 | border: 1px solid gray; 66 | } 67 | 68 | .title { 69 | margin-top: 0; 70 | margin-top: 0; 71 | } 72 | 73 | .hidden { 74 | display: none; 75 | } 76 | 77 | button:disabled { 78 | background-color: gray; 79 | cursor: not-allowed; 80 | } 81 | -------------------------------------------------------------------------------- /identity-service-logs/script.js: -------------------------------------------------------------------------------- 1 | import { 2 | getIdentityLogs, 3 | getIsSuperUser, 4 | fillData, 5 | getUserCount, 6 | addIntersectionObserver, 7 | } from './utils.js'; 8 | const params = new URLSearchParams(window.location.search); 9 | const isDev = params.get('dev') === 'true'; 10 | const { isSuperUser } = await getIsSuperUser(isDev); 11 | 12 | if (isSuperUser && params.has('dev') && params.get('dev') === 'true') { 13 | const { 14 | verifiedUsersCount, 15 | blockedUsersCount, 16 | verifiedDeveloperCount, 17 | blockedDeveloperCount, 18 | developersLeftToVerifyCount, 19 | developersCount, 20 | } = await getUserCount(); 21 | document.getElementById('verified').innerText = verifiedUsersCount; 22 | document.getElementById('blocked').innerText = blockedUsersCount; 23 | document.getElementById('developers').innerText = developersCount; 24 | document.getElementById('verifiedDevelopers').innerText = 25 | verifiedDeveloperCount; 26 | document.getElementById('blockedDevelopers').innerText = 27 | blockedDeveloperCount; 28 | document.getElementById('developersLeft').innerText = 29 | developersLeftToVerifyCount; 30 | const { identityLogs, next } = await getIdentityLogs( 31 | '/logs?dev=true&type=PROFILE_BLOCKED,PROFILE_VERIFIED,PROFILE_DIFF_REJECTED,PROFILE_DIFF_APPROVED,PROFILE_DIFF_STORED&size=10', 32 | ); 33 | fillData(identityLogs, next); 34 | addIntersectionObserver(); 35 | } else { 36 | document.getElementById('loader').innerHTML = 'You are not authorized !'; 37 | } 38 | -------------------------------------------------------------------------------- /createGoalTest.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | let config = { 3 | launchOptions: { 4 | headless: false, 5 | }, 6 | }; 7 | puppeteer.launch(config.launchOptions).then(async (browser) => { 8 | const page = await browser.newPage(); 9 | await page.goto('https://dashboard.realdevsquad.com/goal/index.html'); 10 | 11 | const editButtons = await page.$$('.edit-button'); 12 | 13 | await page.type('#title', 'Complete the task', { delay: 200 }); 14 | 15 | await editButtons[0].click({ delay: 1000 }); 16 | await page.type('#task-name', 'Fix the bug', { delay: 200 }); 17 | 18 | await editButtons[1].click({ delay: 1000 }); 19 | await page.type( 20 | '#links', 21 | 'https://github.com/realdevsquad/website-backend/issues/12', 22 | { delay: 200 }, 23 | ); 24 | await editButtons[2].click({ delay: 1000 }); 25 | await page.select('#user-role', 'DEVELOPER'); 26 | 27 | await editButtons[3].click({ delay: 1000 }); 28 | await page.type('#username', 'ritik', { delay: 200 }); 29 | 30 | await editButtons[4].click({ delay: 1000 }); 31 | await page.select('#priority', 'HIGH'); 32 | 33 | await editButtons[5].click({ delay: 1000 }); 34 | await page.type('#type', 'BUG', { delay: 200 }); 35 | 36 | await editButtons[6].click({ delay: 1000 }); 37 | await page.type('#due-date', '02/20/2023', { delay: 200 }); 38 | 39 | await editButtons[7].click({ delay: 1000 }); 40 | await page.type('#assignee', 'ankush', { delay: 200 }); 41 | 42 | await page.click('#submit', { delay: 1000 }); 43 | 44 | await browser.close(); 45 | }); 46 | -------------------------------------------------------------------------------- /wallet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Wallets | Real Dev Squad 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 25 |
26 | 29 |
30 |
31 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /profile-diff-details/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | Profile Diff 13 | 14 | 15 | 16 |
17 | 18 | Back 19 | 20 | Request Details 21 |
22 | 23 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /users/discord/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-gray: #d4d4d478; 3 | --border-color-gray: #808080; 4 | } 5 | 6 | .header { 7 | background-color: var(--blue-color); 8 | text-align: center; 9 | color: var(--white-color); 10 | } 11 | 12 | main { 13 | display: grid; 14 | grid-template-columns: minmax(10rem, 20rem) 1fr; 15 | grid-template-rows: auto 1fr; 16 | grid-template-areas: 17 | 'tab tab ' 18 | 'aside details'; 19 | } 20 | 21 | .users_section { 22 | grid-area: aside; 23 | overflow: scroll; 24 | padding-inline: 1rem; 25 | } 26 | 27 | .tabs_section { 28 | padding: 1rem; 29 | margin: 1rem; 30 | grid-area: tab; 31 | width: 18rem; 32 | border: 2px solid var(--border-color-gray); 33 | font-size: unset; 34 | } 35 | 36 | .tab { 37 | padding: 1rem; 38 | margin-inline-end: 0.5rem; 39 | cursor: pointer; 40 | } 41 | 42 | .active_tab { 43 | border: 2px solid gray; 44 | } 45 | 46 | .user_card { 47 | padding: 1rem; 48 | display: flex; 49 | align-items: center; 50 | gap: 1rem; 51 | margin-block: 0.5rem; 52 | width: 100%; 53 | background-color: var(--light-gray); 54 | cursor: pointer; 55 | } 56 | 57 | .user_image { 58 | border-radius: 50%; 59 | aspect-ratio: 1; 60 | object-fit: cover; 61 | width: 3rem; 62 | } 63 | 64 | .user_details_section { 65 | grid-area: details; 66 | padding: 1rem; 67 | margin: 0 1rem; 68 | background-color: var(--light-gray); 69 | } 70 | 71 | .user_details_field { 72 | padding: 0.5rem; 73 | } 74 | .no_user_found { 75 | width: 100vw; 76 | text-align: center; 77 | font-size: larger; 78 | padding: 2rem; 79 | } 80 | -------------------------------------------------------------------------------- /users/discord/components/UserDetailsSection.js: -------------------------------------------------------------------------------- 1 | // 2 | const { createElement } = react; 3 | 4 | export const UserDetailsSection = ({ 5 | user: { first_name, username, discordId, discordJoinedAt }, 6 | }) => { 7 | return createElement( 8 | 'section', 9 | { 10 | class: 'user_details_section', 11 | 'data-testid': 'user-details-section', 12 | }, 13 | [ 14 | createElement('div', { class: 'user_details_field' }, [ 15 | createElement('span', {}, ['Name: ']), 16 | createElement('span', {}, [first_name]), 17 | ]), 18 | createElement('div', { class: 'user_details_field' }, [ 19 | createElement('span', {}, ['Username: ']), 20 | createElement('span', {}, [username]), 21 | ]), 22 | createElement('div', { class: 'user_details_field' }, [ 23 | createElement('span', {}, ['Discord ID: ']), 24 | createElement('span', {}, [discordId]), 25 | ]), 26 | createElement('div', { class: 'user_details_field' }, [ 27 | createElement('span', {}, ['Joined RDS server on: ']), 28 | createElement('span', {}, [new Date(discordJoinedAt).toUTCString()]), 29 | ]), 30 | createElement('div', { class: 'user_details_field' }, [ 31 | createElement('span', {}, ['User Management: ']), 32 | createElement( 33 | 'a', 34 | { 35 | target: '_bllank', 36 | href: `${USER_MANAGEMENT_URL}${username}`, 37 | }, 38 | [`${USER_MANAGEMENT_URL}${username}`], 39 | ), 40 | ]), 41 | ], 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /groups/assets/person.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /online-members/online-members.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Online Members | Real Dev Squad 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 |
30 |
31 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /extension-requests/constants.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_AVATAR = '/images/avatar.png'; 2 | const EXTERNAL_LINK_ICON = '/images/external-link.svg'; 3 | const DOWN_ARROW_ICON = '/images/chevron-down.svg'; 4 | const CHECK_ICON = '/images/check-icon.svg'; 5 | const CHECK_ICON_WHITE = '/images/check-icon-white.svg'; 6 | const CANCEL_ICON = '/images/x-icon.svg'; 7 | const CANCEL_ICON_WHITE = '/images/x-icon-white.svg'; 8 | const EDIT_ICON = '/images/edit-icon.svg'; 9 | const ERROR_MESSAGE_RELOAD = 'Something went wrong, Please reload'; 10 | 11 | const DEFAULT_PAGE_SIZE = 5; 12 | 13 | const taskInfoModelHeadings = [ 14 | { title: 'Title' }, 15 | { title: 'Ends On', key: 'endsOn', time: true }, 16 | { title: 'Purpose' }, 17 | { title: 'Assignee' }, 18 | { title: 'Created By', key: 'createdBy' }, 19 | { title: 'Is Noteworthy', key: 'isNoteworthy' }, 20 | ]; 21 | 22 | const extensionRequestCardHeadings = [ 23 | { title: 'Title' }, 24 | { title: 'Reason' }, 25 | { title: 'Old Ends On', key: 'oldEndsOn', time: true }, 26 | { title: 'New Ends On', key: 'newEndsOn', time: true }, 27 | { title: 'Status', bold: true }, 28 | { title: 'Assignee' }, 29 | { title: 'Created At', key: 'timestamp', time: true }, 30 | { title: 'Task', key: 'taskId' }, 31 | ]; 32 | 33 | const FILTER_MODAL = 'filter-modal'; 34 | const FILTER_BUTTON = 'filter-button'; 35 | const APPLY_FILTER_BUTTON = 'apply-filter-button'; 36 | const CLEAR_BUTTON = 'clear-button'; 37 | const SEARCH_ELEMENT = 'assignee-search'; 38 | const LAST_ELEMENT_CONTAINER = '.virtual'; 39 | const SORT_BUTTON = '.sort-button'; 40 | const SORT_ASC_ICON = 'asc-sort-icon'; 41 | const SORT_DESC_ICON = 'desc-sort-icon'; 42 | const OLDEST_FIRST = 'Oldest first'; 43 | const NEWEST_FIRST = 'Newest first'; 44 | 45 | const UPDATE_TOAST_TIMING = 3000; 46 | -------------------------------------------------------------------------------- /profile-diffs/assets/sort-asc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /profile-diffs/assets/sort-desc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /wallet/style.css: -------------------------------------------------------------------------------- 1 | .wallets { 2 | width: 80%; 3 | height: 90vh; 4 | margin: 100px auto; 5 | border-radius: 5px; 6 | background-color: whitesmoke; 7 | padding: 50px; 8 | overflow-y: auto; 9 | } 10 | 11 | .wallets__fetcher { 12 | text-align: center; 13 | } 14 | 15 | .wallets__input input { 16 | margin-top: 10px; 17 | width: 80%; 18 | } 19 | 20 | .wallets__submit { 21 | cursor: pointer; 22 | margin-top: 15px; 23 | margin-left: 20px; 24 | height: 40px; 25 | width: 105px; 26 | background-color: beige; 27 | border-radius: 5px; 28 | } 29 | 30 | .user-wallet { 31 | border-radius: 7px; 32 | border: 1px solid #9e9e9e; 33 | width: 350px; 34 | height: 200px; 35 | margin: 50px auto; 36 | position: relative; 37 | } 38 | 39 | .user-wallet__currencies { 40 | margin-top: 50px; 41 | height: 100px; 42 | width: 100%; 43 | display: flex; 44 | justify-content: space-around; 45 | } 46 | 47 | .user-wallet__username { 48 | font-size: 24px; 49 | margin-left: 20px; 50 | text-transform: capitalize; 51 | color: #757575; 52 | } 53 | 54 | .user-wallet__refresh-btn { 55 | border: 2px solid green; 56 | border-radius: 3px; 57 | position: absolute; 58 | right: 10px; 59 | bottom: 10px; 60 | height: 40px; 61 | width: 70px; 62 | } 63 | 64 | .currency { 65 | display: flex; 66 | padding-top: 5px; 67 | } 68 | 69 | .currencyLabel { 70 | padding-left: 10px; 71 | } 72 | 73 | .currency__label { 74 | margin: 5px 0; 75 | } 76 | 77 | .currency__icon { 78 | height: 35px; 79 | width: 35px; 80 | margin-top: 7px; 81 | } 82 | 83 | .currency-type--neelam { 84 | background-color: blue; 85 | } 86 | 87 | .currency-type--dinero { 88 | border-radius: 50%; 89 | background-color: green; 90 | } 91 | 92 | .info-repo { 93 | font-weight: 100; 94 | margin: 5 auto; 95 | text-align: center; 96 | } 97 | -------------------------------------------------------------------------------- /taskEvents/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Task Logs | Real Dev Squad 9 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 38 |
39 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | const API_BASE_URL = window.API_BASE_URL || 'https://api.realdevsquad.com'; 2 | const REPO_SYNC_API_URL = 3 | 'https://staging-sync.staging-realdevsquad-com.workers.dev'; 4 | const USER_MANAGEMENT_LINK = 'user-management-link'; 5 | const REQUESTS_LINK = 'requests-link'; 6 | const EXTENSION_REQUESTS_LINK = 'extension-requests-link'; 7 | const TASK_REQUESTS_LINK = 'task-requests-link'; 8 | const SYNC_USERS_STATUS = 'sync-users-status'; 9 | const SYNC_IDLE_USERS = 'sync-idle-users'; 10 | const SYNC_EXTERNAL_ACCOUNTS = 'sync-external-accounts'; 11 | const SYNC_UNVERIFIED_USERS = 'sync-unverified-users'; 12 | const SYNC_USERS_STATUS_UPDATE = 'sync-users-status-update'; 13 | const SYNC_IDLE_USERS_UPDATE = 'sync-idle-users-update'; 14 | const SYNC_REPO_STATUS_UPDATE = 'sync-repo-status-update'; 15 | const SYNC_EXTERNAL_ACCOUNTS_UPDATE = 'sync-external-accounts-update'; 16 | const SYNC_UNVERIFIED_USERS_UPDATE = 'sync-unverified-users-update'; 17 | const SYNC_NICKNAMES = 'sync-nicknames'; 18 | const SYNC_NICKNAMES_STATUS_UPDATE = 'sync-nicknames-status-update'; 19 | const SYNC_IDLE_7D_Plus_USERS = 'sync-idle-7d-Plus-users'; 20 | const SYNC_IDLE_7D_Plus_USERS_UPDATE = 'sync-idle-7d-Plus-users-update'; 21 | const SYNC_ONBOARDING_31D_PLUS_USERS = 'sync-onboarding-31d-plus-users'; 22 | const SYNC_ONBOARDING_31D_PLUS_USERS_UPDATE = 23 | 'sync-onboarding-31d-plus-users-update'; 24 | const SYNC_IN_PROGRESS = 'Last Sync: In progress'; 25 | const SYNC_SUCCESSFUL = 'Last Sync: Successful'; 26 | const SYNC_FAILED = 'Last Sync: Failed'; 27 | const DISABLED = 'disabled'; 28 | const STATUS_BASE_URL_PROD = 'https://status.realdevsquad.com'; 29 | const STATUS_BASE_URL_STAGING = 'https://staging-status.realdevsquad.com'; 30 | const STATUS_BASE_URL = STATUS_BASE_URL_PROD; 31 | 32 | const dummyPicture = 'https://dashboard.realdevsquad.com/images/avatar.png'; 33 | const USER_MANAGEMENT_URL = 34 | 'https://dashboard.realdevsquad.com/users/details/?username='; 35 | -------------------------------------------------------------------------------- /identity-service-logs/constants.js: -------------------------------------------------------------------------------- 1 | export const SUPER_USER = 'super_user'; 2 | export const BORDER_COLOR = { 3 | PROFILE_VERIFIED: 'blue', 4 | PROFILE_BLOCKED: 'orangered', 5 | PROFILE_DIFF_REJECTED: 'red', 6 | PROFILE_DIFF_APPROVED: 'green', 7 | PROFILE_DIFF_STORED: '#ADD8E6', 8 | }; 9 | 10 | export const TYPE_NAME = { 11 | PROFILE_VERIFIED: 'PROFILE SERVICE VERIFIED', 12 | PROFILE_BLOCKED: 'PROFILE SERVICE BLOCKED', 13 | PROFILE_DIFF_REJECTED: 'PROFILE DIFFERENCE REJECTED', 14 | PROFILE_DIFF_APPROVED: 'PROFILE DIFFERENCE APPROVED', 15 | PROFILE_DIFF_STORED: 'PROFILE DIFFERENCE STORED', 16 | }; 17 | 18 | export const TYPE_DESCRIPTION = { 19 | PROFILE_VERIFIED: ({ username }) => 20 | `${username}'s profile has been verified and is now officially authenticated.`, 21 | PROFILE_BLOCKED: ({ username, reason }) => 22 | `${username}'s profile has been blocked. ${ 23 | reason ? `Reason provided: "${reason}."` : 'No reason provided!' 24 | }`, 25 | PROFILE_DIFF_REJECTED: ({ username, adminUserName, profileDiffId, reason }) => 26 | `${adminUserName} rejected a profile update request for ${username}. The request with Profile Diff ID ${profileDiffId} was declined due to: "${ 27 | reason || '' 28 | }"`, 29 | PROFILE_DIFF_APPROVED: ({ username, adminUserName, profileDiffId, reason }) => 30 | `${adminUserName} approved a profile update request for ${username}. The changes are now live. The request has a Profile Diff ID ${profileDiffId}. Message: "${ 31 | reason || '' 32 | }"`, 33 | PROFILE_DIFF_STORED: ({ username }) => 34 | `${username}'s profile changes have been saved for further review.`, 35 | }; 36 | 37 | export const BACKGROUND_COLOR = { 38 | PROFILE_VERIFIED: 'rgba(0,0,255,0.2)', 39 | PROFILE_BLOCKED: 'rgba(255,69,0,0.2)', 40 | PROFILE_DIFF_REJECTED: 'rgba(255,0,0,0.2)', 41 | PROFILE_DIFF_APPROVED: 'rgba(0,128,0,0.2)', 42 | PROFILE_DIFF_STORED: 'rgb(173,216,230,0.2)', 43 | }; 44 | -------------------------------------------------------------------------------- /taskEvents/skillElement.js: -------------------------------------------------------------------------------- 1 | import { createElement, removeSkillFromUser } from './utils.js'; 2 | import { modalOverlay } from './script.js'; 3 | 4 | const modal = document.getElementById('modal'); 5 | /** 6 | @params skillTagName: tagname of skill to be added to skill element 7 | @params skillLevel: level of skill to be added to skill element 8 | @params skillTagId: tagId of skill 9 | @params userId: id of the user that skills are being modified 10 | @params skills: all skills of the user 11 | @returns skillItemElement: HTML element of skill consisting of tagname and level with remove button 12 | */ 13 | function skillElement(skillTagName, skillLevel, skillTagId, userId, skills) { 14 | const skillItemElement = createElement({ 15 | type: 'div', 16 | attributes: { class: 'roles-div-item' }, 17 | innerText: `${skillTagName} `, 18 | }); 19 | const skillLevelElement = createElement({ 20 | type: 'small', 21 | innerText: `LVL: ${skillLevel}`, 22 | }); 23 | skillItemElement.append(skillLevelElement); 24 | const removeBtn = createElement({ 25 | type: 'button', 26 | attributes: { class: 'remove-btn' }, 27 | innerText: 'x', 28 | }); 29 | removeBtn.addEventListener('click', () => 30 | handleRemoveSkill(skillItemElement), 31 | ); 32 | skillItemElement.appendChild(removeBtn); 33 | 34 | async function handleRemoveSkill(skillItemElement) { 35 | modal.style.pointerEvents = 'none'; 36 | modalOverlay.classList.add('active'); 37 | const response = await removeSkillFromUser(skillTagId, userId); 38 | if (response.ok) { 39 | const skillToBeRemoved = (skill) => skill.tagId === skillTagId; 40 | const index = skills.findIndex(skillToBeRemoved); 41 | skills.splice(index, 1); 42 | skillItemElement.remove(); 43 | } 44 | modal.style.pointerEvents = 'auto'; 45 | modalOverlay.classList.remove('active'); 46 | } 47 | 48 | return skillItemElement; 49 | } 50 | 51 | export default skillElement; 52 | -------------------------------------------------------------------------------- /components/toast/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: 'Inter', sans-serif; 3 | --red-color: #f05252; 4 | --red-color-variant1: #f8b4b4; 5 | --green-color: #0e9f6e; 6 | --green-color-variant1: #84e1bc; 7 | --white: #ffffff; 8 | } 9 | 10 | .toast__component { 11 | display: flex; 12 | align-items: flex-start; 13 | gap: 0.5rem; 14 | background: var(--white); 15 | padding: 1rem; 16 | width: min(20rem, 90%); 17 | position: fixed; 18 | right: 2%; 19 | z-index: 1; 20 | bottom: 2rem; 21 | border-radius: 0.5rem; 22 | border: solid 2px; 23 | } 24 | 25 | .toast__component p { 26 | flex: 0 0 80%; 27 | word-break: break-word; 28 | margin: 0; 29 | font-size: 0.9rem; 30 | } 31 | 32 | .toast__close__button { 33 | flex: 0 0 20%; 34 | display: flex; 35 | justify-content: flex-end; 36 | } 37 | 38 | .toast__component img { 39 | width: 1.5rem; 40 | height: 1.5rem; 41 | } 42 | .success__toast { 43 | border-color: var(--green-color-variant1); 44 | color: var(--green-color); 45 | } 46 | 47 | .error__toast { 48 | border-color: var(--red-color-variant1); 49 | color: var(--red-color); 50 | } 51 | 52 | .toast__component button { 53 | border: none; 54 | background: transparent; 55 | cursor: pointer; 56 | color: inherit; 57 | } 58 | 59 | .toast__hidden { 60 | display: none; 61 | } 62 | 63 | @keyframes toastIn { 64 | from { 65 | opacity: 0; 66 | transform: translateY(20px) scale(0.95); 67 | } 68 | to { 69 | opacity: 1; 70 | transform: translateY(0) scale(1); 71 | } 72 | } 73 | 74 | @keyframes toastOut { 75 | from { 76 | opacity: 1; 77 | transform: translateY(0) scale(1); 78 | } 79 | to { 80 | opacity: 0; 81 | transform: translateY(20px) scale(0.95); 82 | } 83 | } 84 | 85 | .toast__component.show { 86 | animation: toastIn 300ms ease-out forwards; 87 | pointer-events: auto; 88 | } 89 | 90 | .toast__component.hide { 91 | animation: toastOut 300ms ease-in forwards; 92 | pointer-events: none; 93 | } 94 | -------------------------------------------------------------------------------- /online-members/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Users Constants 4 | const USERS_CLASS = 'users'; 5 | const USERS_CLASS_LIST = [USERS_CLASS]; 6 | const USERS_UL_CLASS_LIST = ['users-list']; 7 | const USERS_LIST_ID = 'users-list'; 8 | const USERS_CONTAINER_CLASS_LIST = ['users-container']; 9 | const USERS_CONTAINER_ID = 'users-container'; 10 | const USERS_ONLINE_CLASS = 'users-online'; 11 | const USERS_ONLINE_HIDDEN_CLASS = 'users-online-hidden'; 12 | const USERS_ONLINE_CLASS_LIST = [USERS_ONLINE_CLASS, USERS_ONLINE_HIDDEN_CLASS]; 13 | 14 | const PROFILE_NAME_CLASS = 'users-profile-and-name'; 15 | const PROFILE_NAME_CLASS_LIST = [PROFILE_NAME_CLASS]; 16 | 17 | // Users Search Constants 18 | const USERS_SEARCH_CLASS_LIST = ['users-search']; 19 | const USERS_SEARCH_INPUT_CLASS_LIST = ['users-search-input']; 20 | const USERS_SEARCH_ID = 'search-users'; 21 | const USERS_SEARCH_PLACEHOLDER = 'Search for users'; 22 | 23 | // Task Constants 24 | const TASKS_CONTAINER_ID = 'task-container'; 25 | const TASKS_SUBCONTAINER_CLASS_LIST = ['task-subcontainer']; 26 | const TASKS_CONTAINER_TITLE_CLASS_LIST = ['task-container-title']; 27 | const TASKS_CLASS_LIST = ['task']; 28 | const TASKS_CONTAINER_CLASS_LIST = ['tasks-container']; 29 | 30 | // RDS Api Constants 31 | const RDS_API_USERS = API_BASE_URL + '/users'; 32 | const RDS_API_TASKS_USERS = API_BASE_URL + '/tasks'; 33 | const RDS_CLOUDINARY_CLOUD_URL = `https://res.cloudinary.com/realdevsquad/image/upload`; 34 | 35 | const RDS_SSE_ONLINE_URL = 'https://online.realdevsquad.com/online-members'; 36 | 37 | // Image Size constants 38 | const RDS_PROFILE_IMAGE_SIZE = 'w_40,h_40'; 39 | const RDS_PROFILE_DEFAULT_IMAGE = ''; 40 | 41 | const PROFILE_IMAGE_CLASS = 'users-profile'; 42 | const PROFILE_IMAGE_CLASS_LIST = [PROFILE_IMAGE_CLASS]; 43 | 44 | /* MESSAGES CONSTANTS */ 45 | const MESSAGE_SOMETHING_WENT_WRONG = 46 | 'Something went wrong. Please contact admin'; 47 | const MESSAGE_NO_TASK = 'No tasks found, create some for them please :P'; 48 | const MESSAGE_LOADING = 'LOADING...'; 49 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const port = process.env.PORT || 8000; 5 | const isAbsolutePath = require('path-is-absolute'); 6 | 7 | http 8 | .createServer(function (req, res) { 9 | /** 10 | * @param {string} urlPath - the path to the requested file 11 | * @sanitizer path.normalize 12 | */ 13 | const [url, params] = path.normalize(req.url).split('?'); 14 | /** 15 | * @param {string} filePath - the absolute path to the requested file 16 | * @flowsource urlPath 17 | * @sanitizer path.join 18 | * @sanitizer isAbsolutePath 19 | */ 20 | let filePath = path.join(__dirname, url); 21 | 22 | if (!isAbsolutePath(filePath)) { 23 | res.statusCode = 400; 24 | res.end('Invalid file path'); 25 | return; 26 | } 27 | 28 | if (!path.extname(filePath)) { 29 | filePath = path.join(filePath, 'index.html'); 30 | } 31 | 32 | // Check that the file exists before reading it 33 | if (!fs.existsSync(filePath)) { 34 | res.statusCode = 404; 35 | res.end('File not found'); 36 | return; 37 | } 38 | 39 | fs.readFile(filePath, function (err, data) { 40 | let contentType = 'text/html'; 41 | switch (path.extname(filePath)) { 42 | case '.css': 43 | contentType = 'text/css'; 44 | break; 45 | case '.js': 46 | contentType = 'text/javascript'; 47 | break; 48 | case '.json': 49 | contentType = 'application/json'; 50 | break; 51 | case '.png': 52 | contentType = 'image/png'; 53 | break; 54 | case '.jpg': 55 | contentType = 'image/jpg'; 56 | break; 57 | case '.svg': 58 | contentType = 'image/svg+xml'; 59 | break; 60 | } 61 | res.writeHead(200, { 'Content-Type': contentType }); 62 | res.end(data); 63 | }); 64 | }) 65 | .listen(port, () => console.log(`Listening on port ${port}`)); 66 | -------------------------------------------------------------------------------- /standup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | User Standup | Real Dev Squad 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

User Standup

19 | 20 |
21 |
22 |
23 | 29 | 30 | 33 |
34 |
35 | 36 |
37 |
38 | 39 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /components/toast/script.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_TIMEOUT = 3000; 2 | const SUCCESS_ICON_PATH = '/images/x-icon-green.svg'; 3 | const ERROR_ICON_PATH = '/images/x-icon-red.svg'; 4 | 5 | function createToast(type, message, timeout = DEFAULT_TIMEOUT) { 6 | const toastContainer = document.getElementById('toastComponent'); 7 | if (!toastContainer) { 8 | console.error('Toast component container not found in the DOM'); 9 | return; 10 | } 11 | 12 | toastContainer.replaceChildren(); 13 | toastContainer.classList.remove( 14 | 'toast__hidden', 15 | 'success__toast', 16 | 'error__toast', 17 | 'hide', 18 | ); 19 | toastContainer.classList.add( 20 | type === 'success' ? 'success__toast' : 'error__toast', 21 | 'show', 22 | ); 23 | 24 | const toastContent = document.createElement('p'); 25 | toastContent.textContent = message; 26 | toastContent.setAttribute('data-testid', 'toast-message'); 27 | 28 | const toastCloseButton = document.createElement('button'); 29 | toastCloseButton.setAttribute('data-testid', 'toast-close-button'); 30 | toastCloseButton.setAttribute('aria-label', 'Close notification'); 31 | toastCloseButton.classList.add('toast__close__button'); 32 | const img = document.createElement('img'); 33 | img.src = type === 'success' ? SUCCESS_ICON_PATH : ERROR_ICON_PATH; 34 | img.alt = 'Close toast'; 35 | toastCloseButton.appendChild(img); 36 | 37 | toastContainer.append(toastContent, toastCloseButton); 38 | 39 | toastCloseButton.addEventListener('click', () => { 40 | toastContainer.classList.remove('show'); 41 | toastContainer.classList.add('hide'); 42 | }); 43 | 44 | setTimeout(() => { 45 | toastContainer.classList.remove('show'); 46 | toastContainer.classList.add('hide'); 47 | }, timeout); 48 | } 49 | 50 | function showToastMessage({ isDev, oldToastFunction, type, message }) { 51 | if (isDev) { 52 | createToast(type, message); 53 | } else if (oldToastFunction.length === 1) { 54 | oldToastFunction(message); 55 | } else { 56 | oldToastFunction(type, message); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /task-requests/constants.js: -------------------------------------------------------------------------------- 1 | const taskRequestStatus = { 2 | WAITING: 'WAITING', 3 | APPROVED: 'APPROVED', 4 | }; 5 | const DEV_FEATURE_FLAG = 'dev'; 6 | const DEFAULT_PAGE_SIZE = 20; 7 | const Status = { 8 | APPROVED: 'approved', 9 | PENDING: 'pending', 10 | DENIED: 'denied', 11 | }; 12 | 13 | const Order = { 14 | REQUESTORS_COUNT_ASC: { requestors: 'asc' }, 15 | REQUESTORS_COUNT_DESC: { requestors: 'desc' }, 16 | CREATED_TIME_DESC: { created: 'desc' }, 17 | CREATED_TIME_ASC: { created: 'asc' }, 18 | }; 19 | 20 | const FILTER_MODAL = 'filter-modal'; 21 | const FILTER_BUTTON = 'filter-button'; 22 | const APPLY_FILTER_BUTTON = 'apply-filter-button'; 23 | const SEARCH_ELEMENT = 'assignee-search'; 24 | const SORT_BUTTON = '.sort-button'; 25 | const SORT_ICON = 'sort-icon'; 26 | const SORT_ASC_ICON = 'asc-sort-icon'; 27 | const SORT_DESC_ICON = 'desc-sort-icon'; 28 | const SORT_TEXT = '.sort-button__text'; 29 | const BADGES = '.sort-filters__badges'; 30 | const CLEAR_BUTTON = 'clear-button'; 31 | const CLEAR_ALL_BUTTON = 'clear-all-button'; 32 | const FILTERS_HEADER = '.filters__header'; 33 | const FILTER_CONTAINER = '.sort-filters'; 34 | const SORT_MODAL = 'sort-modal'; 35 | const ASSIGNEE_COUNT = 'REQUESTORS_COUNT_ASC'; 36 | const ASSIGNEE_DESC = 'REQUESTORS_COUNT_DESC'; 37 | const CREATED_TIME = 'CREATED_TIME_ASC'; 38 | const CREATED_TIME_DESC = 'CREATED_TIME_DESC'; 39 | const BACKDROP = '.backdrop'; 40 | const LAST_ELEMENT_CONTAINER = '.virtual'; 41 | 42 | const MessageStatus = { 43 | SUCCESS: 'SUCCESS', 44 | ERROR: 'ERROR', 45 | }; 46 | 47 | const TaskRequestAction = { 48 | APPROVE: 'approve', 49 | REJECT: 'reject', 50 | }; 51 | const ErrorMessages = { 52 | UNAUTHENTICATED: 53 | 'You are unauthenticated to view this section, please login!', 54 | UNAUTHORIZED: 'You are unauthrozed to view this section', 55 | NOT_FOUND: 'Task Requests not found', 56 | SERVER_ERROR: 'Unexpected error occurred', 57 | }; 58 | 59 | const Sort = { 60 | REQUESTORS_COUNT_ASC: 'requestors-asc', 61 | REQUESTORS_COUNT_DESC: 'requestors-desc', 62 | CREATED_TIME_DESC: 'created-desc', 63 | CREATED_TIME_ASC: 'created-asc', 64 | }; 65 | -------------------------------------------------------------------------------- /profile-diffs/assets/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /users/discord/App.js: -------------------------------------------------------------------------------- 1 | import { TabsSection } from './components/TabsSection.js'; 2 | import { UsersSection } from './components/UsersSection.js'; 3 | import { UserDetailsSection } from './components/UserDetailsSection.js'; 4 | import { getUsers } from './utils/util.js'; 5 | import { NoUserFound } from './components/NoUserFound.js'; 6 | 7 | const { createElement, rerender } = react; 8 | 9 | const tabs = [ 10 | { display_name: 'In Discord', id: 'in_discord', value: 'in_discord' }, 11 | { display_name: 'Linked Accounts', id: 'verified', value: 'verified' }, 12 | ]; 13 | export const usersData = { 14 | in_discord: null, 15 | verified: null, 16 | }; 17 | const urlParams = new URLSearchParams(window.location.search); 18 | 19 | let activeTab = urlParams.get('tab') ?? 'in_discord'; 20 | 21 | let showUser = 0; 22 | usersData[activeTab] = await getUsers(activeTab); 23 | 24 | const handleTabNavigation = async (e) => { 25 | const selectedTabId = e.target.value; 26 | if (selectedTabId) { 27 | document.location.search = `tab=${selectedTabId}`; 28 | 29 | activeTab = selectedTabId; 30 | 31 | if (!usersData[activeTab]) { 32 | const data = await getUsers(activeTab); 33 | 34 | usersData[activeTab] = data; 35 | } 36 | rerender(App(), window['root']); 37 | } 38 | }; 39 | 40 | const handleUserSelected = (userId) => { 41 | if (userId) { 42 | const selectedUserIndex = usersData[activeTab]?.findIndex( 43 | (user) => user.id === userId, 44 | ); 45 | 46 | if (selectedUserIndex !== -1) { 47 | showUser = selectedUserIndex; 48 | rerender(App(), window['root']); 49 | } 50 | } 51 | }; 52 | 53 | export const App = () => { 54 | const users = usersData[activeTab] ?? []; 55 | 56 | if (users.length) 57 | return createElement('main', {}, [ 58 | TabsSection({ tabs, activeTab, handleTabNavigation }), 59 | UsersSection({ 60 | users, 61 | showUser, 62 | handleUserSelected, 63 | }), 64 | UserDetailsSection({ user: users[showUser] ?? {} }), 65 | ]); 66 | 67 | return createElement('main', {}, [ 68 | TabsSection({ tabs, activeTab, handleTabNavigation }), 69 | NoUserFound(), 70 | ]); 71 | }; 72 | -------------------------------------------------------------------------------- /mock-data/groups/index.js: -------------------------------------------------------------------------------- 1 | const discordGroups = { 2 | message: 'Roles fetched successfully!', 3 | groups: [ 4 | { 5 | id: 'CqnEhbwtCqdcZdlrixLn', 6 | date: { 7 | _seconds: 1683238758, 8 | _nanoseconds: 183000000, 9 | }, 10 | createdBy: 'V4rqL1aDecNGoa1IxiCu', 11 | rolename: 'group-first-daaa', 12 | roleid: '1103808103641780225', 13 | firstName: 'Test', 14 | lastName: 'User1', 15 | image: 'https://image.cdn.com/123dfg', 16 | memberCount: 2, 17 | }, 18 | { 19 | id: 'Mky71E6f6QWCY5MOBJFy', 20 | date: { 21 | _seconds: 1687619454, 22 | _nanoseconds: 560000000, 23 | }, 24 | createdBy: 'jbGcfZLGYjHwxQ1Zh8ZJ', 25 | rolename: 'group-DSA', 26 | roleid: '1122182070509244416', 27 | firstName: 'Test', 28 | lastName: 'User2', 29 | image: 'https://image.cdn.com/12dfg', 30 | memberCount: 200, 31 | lastUsedOn: { 32 | _nanoseconds: 61000000, 33 | _seconds: 1703615100, 34 | }, 35 | }, 36 | { 37 | id: '"mvWVuAxtSuhQtunjcywv"', 38 | date: { 39 | _seconds: 1684078062, 40 | _nanoseconds: 434000000, 41 | }, 42 | createdBy: 'k15z2SLFe1U2J3gshXUG', 43 | rolename: 'group-DSA-Coding-Group', 44 | roleid: '1107328395722899496', 45 | firstName: 'Test', 46 | lastName: 'User1', 47 | image: 'https://image.cdn.com/123dfgh', 48 | memberCount: 0, 49 | lastUsedOn: { 50 | _nanoseconds: 61070000, 51 | _seconds: 1703615154, 52 | }, 53 | }, 54 | ], 55 | }; 56 | 57 | const GroupRoleData = { 58 | message: 'User group roles Id fetched successfully!', 59 | userId: '1234398439439989', 60 | groups: [ 61 | { 62 | roleId: '1103808103641780925', 63 | }, 64 | { 65 | roleId: '1151598744278667264', 66 | }, 67 | { 68 | roleId: '1151808031613521951', 69 | }, 70 | { 71 | roleId: '1122182070509244416', 72 | }, 73 | { 74 | roleId: '1151807937149419531', 75 | }, 76 | { 77 | roleId: '1151598712573939792', 78 | }, 79 | ], 80 | }; 81 | 82 | module.exports = { discordGroups, GroupRoleData }; 83 | -------------------------------------------------------------------------------- /online-members/sse-events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // setupDragDropEvent('.members-list'); 4 | 5 | let currentOnlineList = []; 6 | let previousOnlineList = []; 7 | 8 | function setUpOnlineUsersEventSource(url) { 9 | const eventSource = new EventSource(url, { withCredentials: true }); 10 | 11 | eventSource.onopen = (e) => { 12 | console.info('The connection has been established.'); 13 | }; 14 | 15 | eventSource.onmessage = function (event) { 16 | try { 17 | const objectData = JSON.parse(event.data); 18 | currentOnlineList = objectData.users; 19 | 20 | previousOnlineList.forEach((user) => { 21 | users[user].isOnline = false; 22 | }); 23 | 24 | currentOnlineList.forEach((user) => { 25 | users[user].isOnline = true; 26 | }); 27 | 28 | previousOnlineList = currentOnlineList; 29 | updateUsersOnlineStatus(currentOnlineList, users); 30 | } catch (error) { 31 | console.error( 32 | 'Error occurred while processing data, please contact admin', 33 | error, 34 | ); 35 | } 36 | }; 37 | 38 | eventSource.onerror = (e) => { 39 | console.error('An error occurred while attempting to connect.', e); 40 | eventSource.close(); 41 | }; 42 | 43 | return eventSource; 44 | } 45 | 46 | const onlineUsersEventSource = setUpOnlineUsersEventSource(RDS_SSE_ONLINE_URL); 47 | 48 | function updateUsersOnlineStatus(onlineUsersList, users) { 49 | let targetPositionOfOnlineMember = 0; 50 | const usersUl = document.getElementById(USERS_LIST_ID); 51 | const userLi = usersUl.getElementsByTagName('li'); 52 | // Hiding online status for all users 53 | for (const user of userLi) { 54 | const userDiv = user.getElementsByClassName(USERS_CLASS)[0]; 55 | const username = userDiv.dataset.username; 56 | 57 | const userOnlineStatusDiv = 58 | user.getElementsByClassName(USERS_ONLINE_CLASS)[0]; 59 | userOnlineStatusDiv.classList.add(USERS_ONLINE_HIDDEN_CLASS); 60 | 61 | // Showing online status for online users 62 | if (users[username].isOnline) { 63 | userOnlineStatusDiv.classList.remove(USERS_ONLINE_HIDDEN_CLASS); 64 | 65 | // Moving online users to the top of list 66 | usersUl.insertBefore(user, userLi[targetPositionOfOnlineMember++]); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /footerTest.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | 3 | let config = { 4 | launchOptions: { 5 | headless: 'new', 6 | ignoreHTTPSErrors: true, 7 | }, 8 | }; 9 | 10 | let urls = [ 11 | 'https://dashboard.realdevsquad.com/index.html', 12 | 'https://dashboard.realdevsquad.com/profile/index.html', 13 | 'https://dashboard.realdevsquad.com/goal/index.html', 14 | 'https://dashboard.realdevsquad.com/task/index.html', 15 | 'https://dashboard.realdevsquad.com/users/index.html', 16 | 'https://dashboard.realdevsquad.com/users/details/index.html', 17 | 'https://dashboard.realdevsquad.com/taskevents/index.html', 18 | 'https://dashboard.realdevsquad.com/featureflag/index.html', 19 | 'https://dashboard.realdevsquad.com/wallet/index.html', 20 | 'https://dashboard.realdevsquad.com/online-members/online-members.html', 21 | ]; 22 | 23 | const test_footer = async (url, index) => { 24 | await puppeteer.launch(config.launchOptions).then(async (browser) => { 25 | const context = await browser.createIncognitoBrowserContext(); 26 | const page = await context.newPage(); 27 | await page.goto(url, { waitUntil: ['load', 'networkidle2'] }); 28 | await page.content(); 29 | page.setDefaultNavigationTimeout(0); 30 | const footer = await page.$('footer'); 31 | if (footer) { 32 | // is the text same in footer? 33 | const footer_text = await page.evaluate(() => { 34 | const element = document.querySelector('.info-repo'); 35 | return ( 36 | element && 37 | element.innerText === 38 | 'The contents of this website are deployed from this open sourced repo' 39 | ); 40 | }); 41 | console.log(`✅ footer text:${footer_text} index:${index}:${url}`); 42 | 43 | if (footer_text) { 44 | // links inside footer are working or not 45 | await page.click('.info-repo a', { delay: 2000 }); 46 | console.log(`✅ link works for ${url}`); 47 | } 48 | } else { 49 | console.log(`❌no footer at ${url}`); 50 | } 51 | 52 | context.close(); 53 | }); 54 | }; 55 | 56 | async function runTest() { 57 | for (let url of urls) { 58 | await test_footer(url, urls.indexOf(url)); 59 | 60 | if (urls.indexOf(url) === urls.length - 1) { 61 | process.exit(0); 62 | } 63 | } 64 | } 65 | 66 | runTest(); 67 | -------------------------------------------------------------------------------- /profile-diffs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Profile Diffs 8 | 9 | 10 | 11 |
Profile Difference Requests
12 | 13 |
14 | 37 |
38 | 41 | 44 | 47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /online-members/utils.js: -------------------------------------------------------------------------------- 1 | async function makeApiCall( 2 | url, 3 | method = 'get', 4 | body = null, 5 | headers = [], 6 | options = null, 7 | ) { 8 | try { 9 | const response = await fetch(url, { 10 | method, 11 | body, 12 | headers, 13 | ...options, 14 | }); 15 | return response; 16 | } catch (err) { 17 | console.error(MESSAGE_SOMETHING_WENT_WRONG, err); 18 | throw err; 19 | } 20 | } 21 | 22 | async function getUsersData() { 23 | let usersList = []; 24 | const userObject = {}; 25 | const usersRequest = await makeApiCall(RDS_API_USERS); 26 | if (usersRequest.status === 200) { 27 | usersList = await usersRequest.json(); 28 | usersList = usersList.users; 29 | usersList = usersList.filter((user) => !user.incompleteUserDetails); 30 | usersList.forEach((user) => { 31 | userObject[`${user.username}`] = { 32 | isOnline: false, 33 | ...user, 34 | }; 35 | }); 36 | } 37 | return userObject; 38 | } 39 | 40 | async function getUserTaskData(username) { 41 | let taskData = null; 42 | const usersRequest = await makeApiCall(rdsApiTaskDetails(username)); 43 | if (usersRequest.status === 200) { 44 | taskData = await usersRequest.json(); 45 | taskData = taskData.tasks; 46 | } 47 | return taskData; 48 | } 49 | 50 | const rdsApiTaskDetails = (username = null) => 51 | `${RDS_API_TASKS_USERS}/${username}`; 52 | 53 | const getCloudinaryImgURL = (publicId, configs) => { 54 | const imageSizeOptions = configs ? `/${configs}` : ''; 55 | return `${RDS_CLOUDINARY_CLOUD_URL}${imageSizeOptions}/${publicId}`; 56 | }; 57 | 58 | function getUserProfileImageLink(publicId) { 59 | return publicId 60 | ? getCloudinaryImgURL(publicId, RDS_PROFILE_IMAGE_SIZE) 61 | : RDS_PROFILE_DEFAULT_IMAGE; 62 | } 63 | 64 | function searchFunction() { 65 | let divText, txtValue; 66 | const input = document.getElementById('search-users'); 67 | const filter = input.value.toUpperCase(); 68 | const ul = document.getElementById(USERS_LIST_ID); 69 | const li = ul.getElementsByTagName('li'); 70 | const liArray = Array.from(li); 71 | liArray.forEach((liItem) => { 72 | divText = liItem.getElementsByTagName('div')[0]; 73 | txtValue = divText.textContent || divText.innerText; 74 | const displayStyle = 75 | txtValue.toUpperCase().indexOf(filter) > -1 ? '' : 'none'; 76 | liItem.style.display = displayStyle; 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /__tests__/identity-service-logs/identity-service-logs.test.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const { 3 | STAGING_API_URL, 4 | LOCAL_TEST_PAGE_URL, 5 | } = require('../../mock-data/constants'); 6 | describe('Toast Functionality (Dev Mode Enabled)', () => { 7 | let browser; 8 | let page; 9 | jest.setTimeout(60000); 10 | 11 | beforeAll(async () => { 12 | browser = await puppeteer.launch({ 13 | headless: 'new', 14 | ignoreHTTPSErrors: true, 15 | args: ['--incognito', '--disable-web-security'], 16 | devtools: false, 17 | }); 18 | page = await browser.newPage(); 19 | 20 | await page.setRequestInterception(true); 21 | 22 | page.on('request', (interceptedRequest) => { 23 | const url = interceptedRequest.url(); 24 | 25 | if (url === `${STAGING_API_URL}/users?profile=true`) { 26 | interceptedRequest.respond({ 27 | status: 401, 28 | contentType: 'application/json', 29 | 30 | headers: { 31 | 'Access-Control-Allow-Origin': '*', 32 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 33 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 34 | }, 35 | }); 36 | } else { 37 | interceptedRequest.continue(); 38 | } 39 | }); 40 | await page.goto(`${LOCAL_TEST_PAGE_URL}/identity-service-logs?dev=true`); 41 | await page.waitForNetworkIdle(); 42 | }); 43 | 44 | afterAll(async () => { 45 | await browser.close(); 46 | }); 47 | 48 | it('should show error toast when user is not logged in ', async function () { 49 | const toastComponent = await page.$('[data-testid="toast-component"]'); 50 | expect( 51 | await toastComponent.evaluate((el) => el.classList.contains('show')), 52 | ).toBe(true); 53 | expect( 54 | await toastComponent.evaluate((el) => el.classList.contains('hide')), 55 | ).toBe(false); 56 | expect( 57 | await toastComponent.evaluate((el) => 58 | el.classList.contains('success__toast'), 59 | ), 60 | ).toBe(false); 61 | expect( 62 | await toastComponent.evaluate((el) => 63 | el.classList.contains('error__toast'), 64 | ), 65 | ).toBe(true); 66 | const toastMessage = await page.$('[data-testid="toast-message"]'); 67 | expect(await toastMessage.evaluate((el) => el.textContent)).toBe( 68 | 'You are not logged-in. Please login!', 69 | ); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /__tests__/user-details/Intro-button.test.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const { userDetails } = require('../../mock-data/user-details/index'); 3 | const { STAGING_API_URL } = require('../../mock-data/constants'); 4 | 5 | describe('Intro User Button Users Detail Page', () => { 6 | let browser; 7 | let page; 8 | 9 | jest.setTimeout(60000); 10 | 11 | beforeAll(async () => { 12 | browser = await puppeteer.launch({ 13 | headless: 'new', 14 | ignoreHTTPSErrors: true, 15 | args: ['--incognito', '--disable-web-security'], 16 | devtools: false, 17 | }); 18 | page = await browser.newPage(); 19 | 20 | await page.setRequestInterception(true); 21 | 22 | page.on('request', (interceptedRequest) => { 23 | const url = interceptedRequest.url(); 24 | if (url === `${STAGING_API_URL}/users/randhir`) { 25 | interceptedRequest.respond({ 26 | status: 200, 27 | contentType: 'application/json', 28 | headers: { 29 | 'Access-Control-Allow-Origin': '*', 30 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 31 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 32 | }, 33 | body: JSON.stringify(userDetails), 34 | }); 35 | } else { 36 | interceptedRequest.continue(); 37 | } 38 | }); 39 | await page.goto( 40 | 'http://localhost:8000/users/details/index.html?username=randhir', 41 | ); 42 | await page.waitForNetworkIdle(); 43 | }); 44 | 45 | afterAll(async () => { 46 | await browser.close(); 47 | }); 48 | 49 | it('should render the button', async () => { 50 | const pivotButton = await page.$('.Intro'); 51 | expect(pivotButton).toBeTruthy(); 52 | }); 53 | 54 | it('should show tooltip on button hover for non-superusers', async () => { 55 | const button = await page.$('.disabled'); 56 | expect(button).toBeTruthy(); 57 | 58 | await button.hover(); 59 | await page.waitForTimeout(500); 60 | 61 | const tooltipElement = await page.$('.tooltip'); 62 | expect(tooltipElement).toBeTruthy(); 63 | 64 | const tooltipText = await tooltipElement.evaluate( 65 | (tooltip) => tooltip.textContent, 66 | ); 67 | expect(tooltipText).toContain( 68 | 'You do not have required permissions to view this.', 69 | ); 70 | 71 | await page.mouse.move(0, 0); 72 | await page.waitForTimeout(500); 73 | 74 | const updatedTooltipElement = await page.$('.tooltip'); 75 | expect(updatedTooltipElement).toBeFalsy(); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /navbar.global.js: -------------------------------------------------------------------------------- 1 | const urlParams = new URLSearchParams(window.location.search); 2 | const devFlag = urlParams.get('dev') === 'true'; 3 | const chevronIcon = devFlag 4 | ? `chevron-down icon` 5 | : ''; 6 | const addNavbartoPage = async () => { 7 | const navbar = document?.getElementById('tasksNav'); 8 | const navbarParams = new URLSearchParams(window?.location?.search); 9 | navbar.innerHTML = ` 10 | 15 | 28 |
29 | 33 |
34 |
35 | 36 | 37 | 38 | 39 | ${chevronIcon} 40 |
41 | 42 | `; 43 | 44 | const hamburgerDiv = document.querySelector('.hamburger'); 45 | const navLinks = document.querySelector('.links'); 46 | let toggle = true; 47 | 48 | hamburgerDiv?.addEventListener('click', function () { 49 | if (toggle) { 50 | navLinks.classList.add('active'); 51 | toggle = false; 52 | } else { 53 | navLinks.classList.remove('active'); 54 | toggle = true; 55 | } 56 | }); 57 | 58 | if (navbarParams?.get('dev') === 'true') { 59 | let navActive = document?.querySelector('.nav-links'); 60 | const navLinks = document?.querySelector('.links'); 61 | document?.addEventListener('click', function (event) { 62 | if (!navActive?.contains(event.target)) { 63 | navLinks?.classList?.remove('active'); 64 | toggle = true; 65 | } 66 | }); 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /mock-data/tasks/index.js: -------------------------------------------------------------------------------- 1 | const taskDone = { 2 | message: 'task returned successfully', 3 | taskData: { 4 | percentCompleted: 80, 5 | endsOn: 1692149100, 6 | isNoteworthy: false, 7 | createdBy: 'ajeyak', 8 | lossRate: { 9 | dinero: 100, 10 | neelam: 0, 11 | }, 12 | assignee: 'ajeyak', 13 | title: 'A Task', 14 | type: 'feature', 15 | priority: 'HIGH', 16 | completionAward: { 17 | dinero: 1000, 18 | neelam: 0, 19 | }, 20 | startedOn: 1690429785.762, 21 | status: 'DONE', 22 | assigneeId: 'eChYAP0kUwLo4wQ1gqMV', 23 | dependsOn: [], 24 | }, 25 | }; 26 | 27 | const taskVerified = { 28 | message: 'task returned successfully', 29 | taskData: { 30 | percentCompleted: 70, 31 | endsOn: 1690528980, 32 | isNoteworthy: false, 33 | createdBy: 'ajeyak', 34 | lossRate: { 35 | dinero: 100, 36 | neelam: 0, 37 | }, 38 | assignee: 'ajeyak', 39 | title: 'Testing task - 2', 40 | type: 'feature', 41 | priority: 'MEDIUM', 42 | completionAward: { 43 | dinero: 1000, 44 | neelam: 0, 45 | }, 46 | startedOn: 1688745009.949, 47 | status: 'VERIFIED', 48 | assigneeId: 'eChYAP0kUwLo4wQ1gqMV', 49 | dependsOn: [], 50 | }, 51 | }; 52 | 53 | const auditLogTasks = { 54 | '7gZ9E0XTQCEFvUynVqAw': { 55 | message: 'task returned successfully', 56 | taskData: { 57 | percentCompleted: 20, 58 | endsOn: 1697800440, 59 | isNoteworthy: false, 60 | createdBy: 'joygupta', 61 | lossRate: { dinero: 100, neelam: 0 }, 62 | assignee: 'joygupta', 63 | title: 'First Task', 64 | type: 'feature', 65 | priority: 'HIGH', 66 | completionAward: { dinero: 1000, neelam: 0 }, 67 | startedOn: 1695452396.039, 68 | status: 'IN_PROGRESS', 69 | assigneeId: 'XBucw7nHW1wOxdWrmLVa', 70 | dependsOn: [], 71 | }, 72 | }, 73 | mZB0akqPUa1GQQdrgsx7: { 74 | message: 'task returned successfully', 75 | taskData: { 76 | percentCompleted: 0, 77 | endsOn: 1697480760, 78 | isNoteworthy: false, 79 | createdBy: 'joygupta', 80 | lossRate: { dinero: 100, neelam: 0 }, 81 | assignee: 'testunity', 82 | title: 'Task for new user', 83 | type: 'feature', 84 | priority: 'HIGH', 85 | completionAward: { dinero: 1000, neelam: 0 }, 86 | startedOn: 1695831976.165, 87 | status: 'ASSIGNED', 88 | assigneeId: 'Hgbb5mFvy0nHaKCTPVcP', 89 | dependsOn: [], 90 | }, 91 | }, 92 | }; 93 | 94 | module.exports = { 95 | taskDone, 96 | taskVerified, 97 | auditLogTasks, 98 | }; 99 | -------------------------------------------------------------------------------- /groups/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Discord Groups | Real Dev Squad 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 35 |
36 |
37 | 41 |
42 | 46 |
47 |
48 |
49 |
50 | 55 | 56 | 57 |
58 |
59 |
60 | 68 | 69 | -------------------------------------------------------------------------------- /mock-data/users/mockdata.js: -------------------------------------------------------------------------------- 1 | const mockUserData = { 2 | message: 'Users found successfully!', 3 | users: [ 4 | { 5 | id: 'aaL1MXrpmnUNfLkhgXRj', 6 | github_created_at: 1595870020000, 7 | github_display_name: 'Shubham Sharma', 8 | github_id: 'skv93-coder', 9 | incompleteUserDetails: false, 10 | discordId: '1005550883062415400', 11 | last_name: 'Sharma', 12 | linkedin_id: 13 | 'https://www.linkedin.com/authwall?trk=bf&trkInfo=AQHYMsRP3tc0OAAAAYoNZX6wuATNqBsHaNmcvvyvI7xW6_p1BWwaPmUuzm_BCNN9-yOKsgGnYm0D8lgJIw3wn_5LghX6G6_oytuczTfM5P6SsJRZy7LFYiEoIs8YPP8Bx5IkPls=&original_referer=&sessionRedirect=https%3A%2F%2Fwww.linkedin.com%2Fin%2Fshubham-sharma-165600206', 14 | company: 'Igzy', 15 | designation: 'Junior engineer', 16 | twitter_id: 'ShubhamSha11638', 17 | first_name: 'Shubham', 18 | username: 'shubham-sharma', 19 | created_at: 1705233567138, 20 | github_user_id: '68867418', 21 | updated_at: 1707409606780, 22 | roles: { 23 | member: false, 24 | in_discord: true, 25 | archived: false, 26 | super_user: true, 27 | }, 28 | }, 29 | ], 30 | links: { 31 | next: '/search?1=10&state=ACTIVE,OOO,IDLE,ONBOARDING,ONBOARDING&time=31d&size=10&dev=true', 32 | prev: null, 33 | }, 34 | count: 39, 35 | }; 36 | 37 | const superUserDetails = { 38 | message: 'User returned successfully!', 39 | user: { 40 | id: 'XAF7rSUvk4p0d098qWYS', 41 | profileURL: 'https://my.realdevsquad.com/identity', 42 | discordJoinedAt: '2020-02-01T08:33:38.278000+00:00', 43 | roles: { 44 | archived: false, 45 | in_discord: true, 46 | member: true, 47 | super_user: true, 48 | admin: true, 49 | }, 50 | created_at: 1693166951852, 51 | yoe: '8', 52 | github_created_at: 1341655281000, 53 | updated_at: 1693224375990, 54 | company: 'Amazon', 55 | twitter_id: 'ankushdharkar', 56 | first_name: 'Ankush', 57 | ' instagram_id': 'ankushdharkar', 58 | website: 'NA', 59 | incompleteUserDetails: false, 60 | discordId: '154585730465660929', 61 | linkedin_id: 'ankushdharkar', 62 | last_name: 'Dharkar', 63 | picture: { 64 | publicId: 'profile/XAF7rSUvk4p0d098qWYS/me40uk7taytbjaa67mhe', 65 | url: 'https://res.cloudinary.com/realdevsquad/image/upload/v1692058952/profile/XAF7rSUvk4p0d098qWYS/me40uk7taytbjaa67mhe.jpg', 66 | }, 67 | github_display_name: 'Ankush Dharkar', 68 | company_name: 'Amazon', 69 | github_id: 'ankushdharkar', 70 | designation: 'SDE', 71 | status: 'idle', 72 | username: 'ankush', 73 | }, 74 | }; 75 | 76 | module.exports = { mockUserData, superUserDetails }; 77 | -------------------------------------------------------------------------------- /identity-service-logs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Identity Service Logs | Real Dev Squad 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 27 | 28 |
29 |
30 |
31 | 32 | 40 | 41 |
42 |
Verified Users:
43 |
Blocked Users:
44 |
45 | Total Developers: 46 |
47 |
48 | Verified Developers: 49 | 50 |
51 |
52 | Blocked Developers: 53 | 54 |
55 |
56 | Developers Left: 57 |
58 |
59 |
60 |

Loading...

61 |
62 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /featureFlag/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | list-style: none; 3 | text-decoration: none; 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | font-family: 'Open Sans', sans-serif; 8 | } 9 | 10 | :root { 11 | --primary-color: #1d1283; 12 | --bg-color: rgb(243 242 255); 13 | --text-grey-color: rgb(95, 95, 95); 14 | --add-btn-color: #94a0af; 15 | } 16 | 17 | body { 18 | display: flex; 19 | align-items: center; 20 | } 21 | 22 | .wrapper { 23 | width: clamp(300px, 90%, 800px); 24 | } 25 | 26 | .header { 27 | display: flex; 28 | justify-content: space-between; 29 | font: 700 14px Roboto, sans-serif; 30 | padding: 20px 10px; 31 | box-shadow: 0 8px 6px -6px rgb(0 0 0 / 65%); 32 | background-color: var(--bg-color); 33 | } 34 | 35 | .dropdown { 36 | background-color: rgb(255, 255, 255); 37 | color: var(--text-grey-color); 38 | font: 700 14px Roboto, sans-serif; 39 | width: clamp(150px, 30%, 200px); 40 | border-radius: 4px; 41 | outline: none; 42 | cursor: pointer; 43 | padding: 8px; 44 | } 45 | 46 | .add-button { 47 | background-color: var(--primary-color); 48 | border: 2px solid black; 49 | border-radius: 4px; 50 | user-select: none; 51 | color: white; 52 | padding: 8px; 53 | } 54 | 55 | .add-button:hover { 56 | color: #49a82e; 57 | } 58 | 59 | .add-button--active { 60 | color: #49a82e; 61 | } 62 | 63 | /* Form */ 64 | 65 | .form { 66 | margin: 20px 0; 67 | user-select: none; 68 | padding: 0 10px; 69 | } 70 | 71 | .field { 72 | padding: 30px 0; 73 | } 74 | 75 | .field--row { 76 | display: flex; 77 | justify-content: space-between; 78 | align-items: flex-start; 79 | } 80 | 81 | .field--end { 82 | display: flex; 83 | justify-content: flex-end; 84 | } 85 | 86 | .field__heading { 87 | margin-bottom: 10px; 88 | font-weight: 700; 89 | } 90 | 91 | .text-input__field { 92 | padding: 4px; 93 | } 94 | 95 | .checkbox { 96 | padding: 4px 0; 97 | display: flex; 98 | align-items: center; 99 | } 100 | 101 | .checkbox__tick-box { 102 | margin-right: 4px; 103 | height: 20px; 104 | width: 20px; 105 | } 106 | 107 | .checkbox__tick-box--reversed { 108 | margin-left: 20px; 109 | margin-right: 0; 110 | } 111 | 112 | .submit-button { 113 | color: var(--add-btn-color); 114 | border: 2px solid var(--add-btn-color); 115 | background-color: white; 116 | border-radius: 6px; 117 | font-weight: 900; 118 | padding: 8px; 119 | } 120 | 121 | .submit-button:hover { 122 | color: white; 123 | background-color: var(--add-btn-color); 124 | } 125 | 126 | .info-repo { 127 | font-weight: 100; 128 | padding: 8px; 129 | text-align: center; 130 | } 131 | 132 | footer { 133 | margin-top: auto; 134 | } 135 | 136 | .submit-button__disabled { 137 | color: white; 138 | background-color: var(--add-btn-color); 139 | } 140 | 141 | @media screen and (max-width: 480px) { 142 | .field--row { 143 | flex-direction: column; 144 | justify-content: space-between; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /online-members/online-members.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | list-style: none; 6 | } 7 | 8 | .body { 9 | display: flex; 10 | align-content: flex-start; 11 | justify-content: space-evenly; 12 | padding-top: 4rem; 13 | } 14 | 15 | .task-container { 16 | overflow: auto; 17 | display: none; 18 | padding-top: 10px; 19 | max-height: 85vh; 20 | padding: 0 1rem; 21 | width: 50%; 22 | } 23 | 24 | .task-subcontainer { 25 | display: flex; 26 | flex-direction: column; 27 | gap: 1.5rem; 28 | } 29 | 30 | .task { 31 | padding: 10px; 32 | border: 2px solid black; 33 | border-radius: 5px; 34 | height: max-content; 35 | width: 100%; 36 | } 37 | 38 | .users-container { 39 | margin: 10px; 40 | border: 2px solid black; 41 | border-radius: 5px; 42 | height: 80vh; 43 | overflow-y: scroll; 44 | min-width: 260px; 45 | } 46 | 47 | .users-list { 48 | overflow: hidden; 49 | } 50 | 51 | .users { 52 | margin: 10px; 53 | padding: 0 5px; 54 | border: 2px solid black; 55 | border-radius: 5px; 56 | height: 2rem; 57 | width: 15rem; 58 | align-items: center; 59 | display: flex; 60 | justify-content: space-between; 61 | cursor: pointer; 62 | background: #8abbcd; 63 | } 64 | 65 | .users:hover { 66 | background: #5992a7; 67 | } 68 | 69 | .users.dragging { 70 | background: transparent; 71 | color: transparent; 72 | border: transparent; 73 | } 74 | 75 | .users-online { 76 | width: 10px; 77 | height: 10px; 78 | background-color: rgb(58, 247, 58); 79 | border-radius: 50%; 80 | } 81 | 82 | .users-online-hidden { 83 | display: none; 84 | } 85 | 86 | .users.scale { 87 | transform: scale(1.1); 88 | } 89 | 90 | .users-search { 91 | margin: 10px; 92 | height: 4rem; 93 | width: 15rem; 94 | position: sticky; 95 | top: 0; 96 | background: #fff; 97 | display: flex; 98 | align-items: center; 99 | } 100 | 101 | .users-search-input { 102 | height: 2rem; 103 | width: 15rem; 104 | } 105 | 106 | .users-profile { 107 | border-radius: 50%; 108 | width: 25px; 109 | margin-right: 5px; 110 | } 111 | 112 | .users-profile-and-name { 113 | display: flex; 114 | align-items: center; 115 | } 116 | 117 | footer { 118 | display: block; 119 | width: 100%; 120 | padding: 8px; 121 | } 122 | 123 | footer .info-repo { 124 | font-weight: 100; 125 | text-align: center; 126 | } 127 | 128 | /* MEDIA QUERY */ 129 | 130 | @media only screen and (max-width: 900px) { 131 | .body { 132 | flex-direction: column-reverse; 133 | align-items: center; 134 | padding-top: 2rem; 135 | gap: 3rem; 136 | } 137 | .task-container { 138 | width: 70%; 139 | margin-bottom: 3rem; 140 | } 141 | .task-container-title { 142 | text-align: center; 143 | } 144 | } 145 | @media only screen and (max-width: 600px) { 146 | .task-container { 147 | width: 85%; 148 | padding: 0; 149 | } 150 | } 151 | @media only screen and (max-width: 400px) { 152 | .task-container { 153 | width: 95%; 154 | padding: 0; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Workflow 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - '**' 7 | schedule: 8 | - cron: '35 9 * * 6' 9 | 10 | jobs: 11 | build-test: 12 | runs-on: ubuntu-latest 13 | if: github.event_name != 'schedule' 14 | strategy: 15 | matrix: 16 | node-version: [22.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Install system dependencies 26 | run: | 27 | echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns 28 | 29 | - run: yarn install 30 | - run: yarn check 31 | 32 | - name: Run tests 33 | run: yarn test 34 | 35 | analyze: 36 | name: Analyze 37 | runs-on: ubuntu-latest 38 | needs: build-test 39 | permissions: 40 | actions: read 41 | contents: read 42 | security-events: write 43 | 44 | strategy: 45 | matrix: 46 | language: ['javascript'] 47 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 48 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 49 | 50 | steps: 51 | - name: Checkout repository 52 | uses: actions/checkout@v4 53 | 54 | # Initializes the CodeQL tools for scanning. 55 | - name: Initialize CodeQL 56 | uses: github/codeql-action/init@v2 57 | with: 58 | languages: ${{ matrix.language }} 59 | # If you wish to specify custom queries, you can do so here or in a config file. 60 | # By default, queries listed here will override any specified in a config file. 61 | # Prefix the list here with "+" to use these queries and those in the config file. 62 | 63 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 64 | # queries: security-extended,security-and-quality 65 | 66 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 67 | # If this step fails, then you should remove it and run the build manually (see below) 68 | - name: Autobuild 69 | uses: github/codeql-action/autobuild@v2 70 | 71 | # ℹ️ Command-line programs to run using the OS shell. 72 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 73 | 74 | # If the Autobuild fails above, remove it and uncomment the following three lines. 75 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 76 | 77 | # - run: | 78 | # echo "Run, Build Application using script" 79 | # ./location_of_script_within_repo/buildscript.sh 80 | 81 | - name: Perform CodeQL Analysis 82 | uses: github/codeql-action/analyze@v2 83 | -------------------------------------------------------------------------------- /__tests__/users/onboarding31days.test.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const { superUserDetails } = require('../../mock-data/users/mockdata'); 3 | const { STAGING_API_URL } = require('../../mock-data/constants'); 4 | 5 | describe('Tests the "Onboarding > 31 Days" Filter', () => { 6 | let browser; 7 | let page; 8 | 9 | jest.setTimeout(60000); 10 | 11 | beforeAll(async () => { 12 | browser = await puppeteer.launch({ 13 | headless: 'new', //change headless to 'new' to check the tests in browser 14 | ignoreHTTPSErrors: true, 15 | args: ['--incognito', '--disable-web-security'], 16 | devtools: false, 17 | }); 18 | page = await browser.newPage(); 19 | 20 | await page.setRequestInterception(true); 21 | 22 | page.on('request', (interceptedRequest) => { 23 | const url = interceptedRequest.url(); 24 | 25 | if (url === `${STAGING_API_URL}/tasks/sunny-s`) { 26 | // When we encounter the respective api call we respond with the below response 27 | interceptedRequest.respond({ 28 | status: 200, 29 | contentType: 'application/json', 30 | headers: { 31 | 'Access-Control-Allow-Origin': '*', 32 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 33 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 34 | }, 35 | body: JSON.stringify(userDetailsApi), 36 | }); 37 | } else if (url === `${STAGING_API_URL}/users?profile=true`) { 38 | // When we encounter the respective api call we respond with the below response 39 | interceptedRequest.respond({ 40 | status: 200, 41 | contentType: 'application/json', 42 | headers: { 43 | 'Access-Control-Allow-Origin': '*', 44 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 45 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 46 | }, 47 | body: JSON.stringify(superUserDetails), // Y contains the json of a superuser in the server which will grant us the access to view the page without locks 48 | }); 49 | } else { 50 | interceptedRequest.continue(); 51 | } 52 | }); 53 | await page.goto('http://localhost:8000/users/'); 54 | 55 | await page.waitForNetworkIdle(); 56 | }); 57 | 58 | afterAll(async () => { 59 | await browser.close(); 60 | }); 61 | 62 | it('should go to filter section', async () => { 63 | const taskDiv = await page.$('.filter-button'); 64 | expect(taskDiv).toBeTruthy(); 65 | 66 | await taskDiv.click(); 67 | 68 | await page.waitForTimeout(2000); 69 | const elements = await page.$$('.checkbox-label'); 70 | 71 | // Checking if elements are found 72 | expect(elements).toBeTruthy(); 73 | 74 | const checkbox = await page.$('#ONBOARDING31DAYS'); 75 | await checkbox.click(); 76 | 77 | const applyfilterbutton = await page.$('.apply-filter-button'); 78 | expect(applyfilterbutton).toBeTruthy(); 79 | await applyfilterbutton.click(); 80 | 81 | await page.waitForTimeout(5000); 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /__tests__/users/applyFilterPagination.test.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const { filteredUsersData } = require('../../mock-data/users'); 3 | const { mockUserData } = require('../../mock-data/users/mockdata'); 4 | const { 5 | STAGING_API_URL, 6 | LOCAL_TEST_PAGE_URL, 7 | } = require('../../mock-data/constants'); 8 | 9 | describe('Apply Filter and Pagination Functionality', () => { 10 | let browser; 11 | let page; 12 | 13 | jest.setTimeout(60000); 14 | 15 | const headers = { 16 | 'Access-Control-Allow-Origin': '*', 17 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 18 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 19 | }; 20 | 21 | beforeAll(async () => { 22 | browser = await puppeteer.launch({ 23 | headless: true, 24 | ignoreHTTPSErrors: true, 25 | args: ['--incognito', '--disable-web-security'], 26 | }); 27 | page = await browser.newPage(); 28 | 29 | await page.setRequestInterception(true); 30 | 31 | page.on('request', (interceptedRequest) => { 32 | const url = interceptedRequest.url(); 33 | if (url === `${STAGING_API_URL}/users/search/?role=in_discord`) { 34 | interceptedRequest.respond({ 35 | status: 200, 36 | contentType: 'application/json', 37 | headers, 38 | body: JSON.stringify({ 39 | ...filteredUsersData, 40 | users: filteredUsersData.users.filter( 41 | (user) => user.roles.in_discord, 42 | ), 43 | }), 44 | }); 45 | } else if (url === `${STAGING_API_URL}/users/search/?verified=true`) { 46 | interceptedRequest.respond({ 47 | status: 200, 48 | contentType: 'application/json', 49 | headers, 50 | body: JSON.stringify({ 51 | ...filteredUsersData, 52 | users: [...filteredUsersData.users, ...mockUserData.users], 53 | }), 54 | }); 55 | } else { 56 | interceptedRequest.continue(); 57 | } 58 | }); 59 | 60 | await page.goto(`${LOCAL_TEST_PAGE_URL}/users/discord/`); 61 | await page.waitForNetworkIdle(); 62 | }); 63 | 64 | afterAll(async () => { 65 | await browser.close(); 66 | }); 67 | 68 | it('should render all sections', async () => { 69 | let tabsSection = await page.$('.tabs_section'); 70 | let usersSection = await page.$('.users_section'); 71 | let firstUser = await page.$('.user_card'); 72 | let userDetailsSection = await page.$('.user_details_section'); 73 | 74 | expect(tabsSection).toBeDefined(); 75 | const tabs = await tabsSection.$$('.tab'); 76 | expect(tabs.length).toEqual(2); 77 | expect(usersSection).toBeDefined(); 78 | expect(userDetailsSection).toBeDefined(); 79 | }); 80 | 81 | it('should update the URL query string when applying filters', async () => { 82 | await page.waitForSelector('[data-testid="tabs-section-select"]'); 83 | await page.select('[data-testid="tabs-section-select"]', 'verified'); 84 | 85 | // get the current URL 86 | const url = await page.url(); 87 | expect(url).toContain('?tab=verified'); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /extension-requests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Extension Requests 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |

Extension Requests

23 |
24 | 27 | 31 | 36 | 37 | 42 | 55 |
56 | 64 | 69 |
70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /users/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | User Management | Real Dev Squad 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

User Management

24 |
25 |
26 | 29 | 33 | 48 |
49 | 50 |
51 | 60 | 69 |
70 |
71 |
72 | 73 |
74 | 75 |
76 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /mock-data/logs/index.js: -------------------------------------------------------------------------------- 1 | const extensionRequestLogs = { 2 | fuQs71a0Y7BX3n4rc5Ii: { 3 | message: 'Logs returned successfully!', 4 | logs: [ 5 | { 6 | meta: { 7 | extensionRequestId: 'fuQs71a0Y7BX3n4rc5Ii', 8 | userId: 'XBucw7nHW1wOxdWrmLVa', 9 | taskId: '7gZ9E0XTQCEFvUynVqAw', 10 | username: 'joygupta', 11 | name: 'Joy Gupta', 12 | }, 13 | type: 'extensionRequests', 14 | body: { status: 'APPROVED' }, 15 | timestamp: { _seconds: 1697042773, _nanoseconds: 461000000 }, 16 | }, 17 | { 18 | meta: { 19 | extensionRequestId: 'fuQs71a0Y7BX3n4rc5Ii', 20 | userId: 'XBucw7nHW1wOxdWrmLVa', 21 | taskId: '7gZ9E0XTQCEFvUynVqAw', 22 | name: 'Joy Gupta', 23 | }, 24 | type: 'extensionRequests', 25 | body: { newEndsOn: 1697800440, oldEndsOn: 1697714040 }, 26 | timestamp: { _seconds: 1697042756, _nanoseconds: 261000000 }, 27 | }, 28 | { 29 | meta: { 30 | extensionRequestId: 'fuQs71a0Y7BX3n4rc5Ii', 31 | userId: 'XBucw7nHW1wOxdWrmLVa', 32 | taskId: '7gZ9E0XTQCEFvUynVqAw', 33 | name: 'Joy Gupta', 34 | }, 35 | type: 'extensionRequests', 36 | body: { newReason: 'ER 1 SU', oldReason: 'ER 1' }, 37 | timestamp: { _seconds: 1697042745, _nanoseconds: 921000000 }, 38 | }, 39 | { 40 | meta: { 41 | extensionRequestId: 'fuQs71a0Y7BX3n4rc5Ii', 42 | userId: 'XBucw7nHW1wOxdWrmLVa', 43 | taskId: '7gZ9E0XTQCEFvUynVqAw', 44 | name: 'Joy Gupta', 45 | }, 46 | type: 'extensionRequests', 47 | body: { 48 | newTitle: 'ER 1 Title SU', 49 | newEndsOn: 1697714040, 50 | oldTitle: 'ER 1 Title', 51 | oldEndsOn: 1697733840, 52 | }, 53 | timestamp: { _seconds: 1697042732, _nanoseconds: 911000000 }, 54 | }, 55 | ], 56 | }, 57 | lw7dRB0I3a6ivsFR5Izs: { message: 'Logs returned successfully!', logs: [] }, 58 | }; 59 | const extensionRequestLogsInSentence = { 60 | 'log-container-fuQs71a0Y7BX3n4rc5Ii': [ 61 | 'Joy has created this extension request on Wed, 11/10/2023, 22:14:45.', 62 | 'You changed the title from ER 1 Title to ER 1 Title SU.', 63 | 'You changed the ETA from Thu, 19/10/2023, 22:14:00 to Thu, 19/10/2023, 16:44:00.', 64 | 'You changed the reason from ER 1 to ER 1 SU.', 65 | 'You changed the ETA from Thu, 19/10/2023, 16:44:00 to Fri, 20/10/2023, 16:44:00.', 66 | 'You APPROVED this request 3 days ago.', 67 | ], 68 | 'log-container-lw7dRB0I3a6ivsFR5Izs': [ 69 | 'JoyTest has created this extension request on Sun, 15/10/2023, 10:05:31.', 70 | ], 71 | }; 72 | 73 | const mockFeedLogs = { 74 | message: 'All Logs fetched successfully', 75 | data: [ 76 | { 77 | user: 'test-1', 78 | taskId: 'MxMSgBgaU3fZqZpx18Z2', 79 | taskTitle: 'test title', 80 | type: 'task', 81 | userId: '4Ij9wAlEZzEjvFX67OrN', 82 | username: 'test', 83 | subType: 'update', 84 | status: 'IN_PROGRESS', 85 | timestamp: 1743149176, 86 | }, 87 | ], 88 | next: null, 89 | prev: null, 90 | }; 91 | module.exports = { 92 | extensionRequestLogs, 93 | extensionRequestLogsInSentence, 94 | mockFeedLogs, 95 | }; 96 | -------------------------------------------------------------------------------- /feed/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Activity Feed | Real Dev Squad 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |

Activity Feed

23 |
24 |
25 |
26 | 27 | 34 | × 41 |
46 |
47 | 48 |
49 |
50 | 51 | 59 | 75 |
76 |
77 |
78 |
79 | 80 |
81 | 82 |
83 |
84 |
    85 |
    86 |
    87 |
    88 |
    89 | 90 | 91 | -------------------------------------------------------------------------------- /goal/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-white: #fff; 3 | --color-black: #000; 4 | --color-gray: #666; 5 | --color-gray-light: #f4f4f4; 6 | --color-rgba-black-065: rgba(0, 0, 0, 0.65); 7 | --color-rgba-black-007: rgba(0, 0, 0, 0.07); 8 | --color-blue: #7171fd; 9 | --color-red: red; 10 | } 11 | 12 | *, 13 | ::after, 14 | ::before { 15 | box-sizing: border-box; 16 | } 17 | 18 | body { 19 | font-family: 'Roboto', sans-serif; 20 | margin: 0; 21 | padding: 0; 22 | display: flex; 23 | flex-direction: column; 24 | height: 100vh; 25 | user-select: none; 26 | } 27 | 28 | .container { 29 | width: 100%; 30 | color: var(--color-gray); 31 | text-align: center; 32 | padding: 1em 0; 33 | } 34 | 35 | .goal-form { 36 | width: 60vw; 37 | max-width: 40rem; 38 | min-width: 18.75rem; 39 | margin: 0 auto; 40 | padding: 1em 1.5em; 41 | border: 1px solid var(--color-gray-light); 42 | border-radius: 20px; 43 | box-shadow: 0 0 15px -7px var(--color-rgba-black-065); 44 | } 45 | 46 | .inputBox { 47 | width: 100%; 48 | margin-bottom: 1em; 49 | display: flex; 50 | flex-direction: column; 51 | justify-content: center; 52 | align-items: flex-start; 53 | } 54 | 55 | .inputBox label { 56 | font-size: 1rem; 57 | font-weight: 700; 58 | width: 100%; 59 | text-align: left; 60 | position: relative; 61 | } 62 | 63 | .inputBox input { 64 | padding: 1em; 65 | border: 1px solid var(--color-gray); 66 | border-radius: 5px; 67 | width: 100%; 68 | } 69 | 70 | ::placeholder { 71 | font-family: sans-serif; 72 | } 73 | 74 | .submitBtn { 75 | font-size: 1rem; 76 | padding: 1em 0.5em; 77 | outline: 0; 78 | box-shadow: var(--color-rgba-black-007) 0 1px 1px, 79 | var(--color-rgba-black-007) 0 2px 2px, var(--color-rgba-black-007) 0 4px 4px, 80 | var(--color-rgba-black-007) 0 8px 8px, 81 | var(--color-rgba-black-007) 0 16px 16px; 82 | } 83 | 84 | .submit { 85 | background-color: var(--color-blue); 86 | color: var(--color-white); 87 | cursor: pointer; 88 | } 89 | 90 | .required { 91 | color: var(--color-red); 92 | font-size: medium; 93 | } 94 | 95 | .type, 96 | .user-role, 97 | .priority { 98 | width: 100%; 99 | padding: 1em; 100 | border-radius: 5px; 101 | } 102 | 103 | #isNoteworthy { 104 | width: auto; 105 | } 106 | 107 | .info-repo { 108 | font-weight: 100; 109 | padding: auto; 110 | text-align: center; 111 | } 112 | 113 | footer { 114 | margin-top: auto; 115 | } 116 | 117 | .notEditing { 118 | display: none; 119 | } 120 | 121 | .inputBox label .edit-button { 122 | width: fit-content; 123 | height: fit-content; 124 | font-size: 0.625em; 125 | opacity: 60%; 126 | right: 5px; 127 | cursor: pointer; 128 | display: block; 129 | transition: opacity ease 0.25s; 130 | padding: 0.3125rem 0.625rem 0.3125rem 0rem; 131 | } 132 | 133 | .inputBox .preview { 134 | font-size: 0.875em; 135 | font-weight: 400; 136 | white-space: pre-wrap; 137 | word-wrap: break-word; 138 | width: 80%; 139 | position: relative; 140 | overflow: hidden; 141 | } 142 | 143 | .inputBox label .edit-button:hover { 144 | opacity: 100%; 145 | } 146 | 147 | .inputBox label .edit-button.edit-button__active { 148 | opacity: 100%; 149 | } 150 | 151 | .error-container { 152 | display: flex; 153 | justify-content: center; 154 | margin: auto; 155 | } 156 | 157 | .hidden { 158 | display: none; 159 | } 160 | -------------------------------------------------------------------------------- /__tests__/tasks/profile-picture.test.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const { allUsersData } = require('../../mock-data/users'); 3 | const { STAGING_API_URL } = require('../../mock-data/constants'); 4 | 5 | describe('Task Page - Assignee Profile Pic', () => { 6 | let browser; 7 | let page; 8 | jest.setTimeout(60000); 9 | 10 | beforeAll(async () => { 11 | browser = await puppeteer.launch({ 12 | headless: 'new', 13 | ignoreHTTPSErrors: true, 14 | args: ['--incognito', '--disable-web-security'], 15 | devtools: false, 16 | }); 17 | page = await browser.newPage(); 18 | await page.setRequestInterception(true); 19 | 20 | page.on('request', (interceptedRequest) => { 21 | const url = interceptedRequest.url(); 22 | if (url === `${STAGING_API_URL}/users`) { 23 | interceptedRequest.respond({ 24 | status: 200, 25 | contentType: 'application/json', 26 | headers: { 27 | 'Access-Control-Allow-Origin': '*', 28 | 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 29 | 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 30 | }, 31 | body: JSON.stringify(allUsersData), 32 | }); 33 | } else { 34 | interceptedRequest.continue(); 35 | } 36 | }); 37 | await page.goto('http://localhost:8000/task/'); 38 | await page.waitForNetworkIdle(); 39 | 40 | // Click the button and select the status as Assigned 41 | const buttonId = 'statusId'; 42 | const button = await page.$(`#${buttonId}`); 43 | await button.click(); 44 | await page.waitForSelector('select.input:not(.notEditing)'); 45 | await page.select('select#status', 'ASSIGNED'); 46 | }); 47 | 48 | afterAll(async () => { 49 | await browser.close(); 50 | }); 51 | 52 | it('Profile picture with url', async () => { 53 | const inputElement = await page.$('input#assignee'); 54 | const name = 'Arpit_02'; 55 | 56 | //Trigger the input event manually 57 | for (let i = 0; i < name.length; i++) { 58 | await inputElement.type(name[i], { delay: 100 }); 59 | } 60 | 61 | await page.waitForSelector('#suggested-users-container'); 62 | 63 | await page.waitForTimeout(2000); 64 | const imgSrc = await page.$eval('#list-items img', (img) => img.src); 65 | const expectedImageFilename = 66 | 'https://res.cloudinary.com/realdevsquad/image/upload/v1679878917/profile/54vObOfoscwiIVNMSqnN/askdcanhcehukqrdugge.jpg'; 67 | expect(imgSrc.endsWith(expectedImageFilename)).toBe(true); 68 | }); 69 | 70 | it('Profile picture without url - default No-profile-pic.jpg to be loaded', async () => { 71 | //Remove the value entered in previous test case 72 | const inputElement = await page.$('input#assignee'); 73 | await inputElement.click({ clickCount: 3 }); // Select all text 74 | await page.keyboard.press('Backspace'); 75 | await new Promise((resolve) => setTimeout(resolve, 500)); 76 | 77 | const name = '19sriram'; 78 | for (let i = 0; i < name.length; i++) { 79 | await inputElement.type(name[i], { delay: 100 }); 80 | } 81 | 82 | await page.waitForSelector('#suggested-users-container'); 83 | await page.waitForTimeout(2000); 84 | 85 | const imgSrc = await page.$eval('#list-items img', (img) => img.src); 86 | const expectedImageFilename = 'No-profile-pic.jpg'; 87 | expect(imgSrc.endsWith(expectedImageFilename)).toBe(true); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /mock-data/standup/index.js: -------------------------------------------------------------------------------- 1 | const standupOne = { 2 | message: 'Progress document retrieved successfully.', 3 | count: 9, 4 | data: [ 5 | { 6 | id: '8hhlqHacZ9iWo3rbWHku', 7 | date: 1685145600000, 8 | createdAt: 1685163538297, 9 | blockers: 'Implement error handling for API endpoints', 10 | completed: 'Implement error handling for API endpoints', 11 | planned: 'Implement error handling for API endpoints', 12 | type: 'user', 13 | userId: 'YleviOe1SsOML8eitV9W', 14 | }, 15 | { 16 | id: 'ANaIkUInoUeAagwMsxHI', 17 | date: 1685836800000, 18 | createdAt: 1685892283432, 19 | blockers: 'Working on a backend Go project', 20 | completed: 'Working on a backend Go project', 21 | planned: 'Working on a backend Go project', 22 | type: 'user', 23 | userId: 'YleviOe1SsOML8eitV9W', 24 | }, 25 | { 26 | id: 'KYvOM36StltlBNxtW9Mm', 27 | date: 1685577600000, 28 | createdAt: 1685616571070, 29 | blockers: 'Working on a backend Go project', 30 | completed: 'Working on a backend Go project', 31 | planned: 'Working on a backend Go project', 32 | type: 'user', 33 | userId: 'YleviOe1SsOML8eitV9W', 34 | }, 35 | { 36 | id: 'T8GZ191H8lVTnGBB3tAQ', 37 | date: 1685318400000, 38 | createdAt: 1685374251104, 39 | blockers: 'Implement error handling for API endpoints', 40 | completed: 'Implement error handling for API endpoints', 41 | planned: 'Implement error handling for API endpoints', 42 | type: 'user', 43 | userId: 'YleviOe1SsOML8eitV9W', 44 | }, 45 | { 46 | id: 'h3eK4lVB5PlOZXX9RFFP', 47 | date: 1685750400000, 48 | createdAt: 1685783692663, 49 | blockers: 'Working on a backend Go project', 50 | completed: 'Working on a backend Go project', 51 | planned: 'Working on a backend Go project', 52 | type: 'user', 53 | userId: 'YleviOe1SsOML8eitV9W', 54 | }, 55 | { 56 | id: 'jng8Zlsjr90EuGbBQisG', 57 | date: 1685059200000, 58 | createdAt: 1685115243500, 59 | blockers: 'Working on a backend Go project', 60 | completed: 'Working on a backend Go project', 61 | planned: 'Working on a backend Go project', 62 | type: 'user', 63 | userId: 'YleviOe1SsOML8eitV9W', 64 | }, 65 | { 66 | id: 'nFTXyHfbufj7XY2mguVX', 67 | date: 1685491200000, 68 | createdAt: 1685506497607, 69 | blockers: 'Implement error handling for API endpoints', 70 | completed: 'Implement error handling for API endpoints', 71 | planned: 'Implement error handling for API endpoints', 72 | type: 'user', 73 | userId: 'YleviOe1SsOML8eitV9W', 74 | }, 75 | { 76 | id: 'sG4sUGTILmxaby9xmU3I', 77 | date: 1685404800000, 78 | createdAt: 1685453697666, 79 | blockers: 'Working on a backend Go project', 80 | completed: 'Working on a backend Go project', 81 | planned: 'Working on a backend Go project', 82 | type: 'user', 83 | userId: 'YleviOe1SsOML8eitV9W', 84 | }, 85 | { 86 | id: 'xxvvSUglhJQNnHWVyEZ9', 87 | date: 1685232000000, 88 | createdAt: 1685257952297, 89 | blockers: 'Waiting for database access credentials', 90 | completed: 'Waiting for database access credentials', 91 | planned: 'Waiting for database access credentials', 92 | type: 'user', 93 | userId: 'YleviOe1SsOML8eitV9W', 94 | }, 95 | ], 96 | }; 97 | 98 | module.exports = { standupOne }; 99 | -------------------------------------------------------------------------------- /standup/utils.js: -------------------------------------------------------------------------------- 1 | async function makeApiCall(url, method, body, credentials, headers, options) { 2 | try { 3 | const response = await fetch(url, { 4 | method, 5 | body, 6 | headers, 7 | credentials, 8 | ...options, 9 | }); 10 | return response; 11 | } catch (err) { 12 | console.error(err); 13 | throw err; 14 | } 15 | } 16 | 17 | function createStandupElement({ type, classList }) { 18 | const element = document.createElement(type); 19 | element.classList.add(...classList); 20 | return element; 21 | } 22 | 23 | function createLoaderElement() { 24 | const loaderElement = document.createElement('div'); 25 | loaderElement.classList.add('loader'); 26 | const wrapperElement = document.createElement('div'); 27 | wrapperElement.classList.add('wrapper'); 28 | const circleElement = document.createElement('div'); 29 | circleElement.classList.add('circle'); 30 | const line1Element = document.createElement('div'); 31 | line1Element.classList.add('line-1'); 32 | const line2Element = document.createElement('div'); 33 | line2Element.classList.add('line-2'); 34 | wrapperElement.appendChild(circleElement); 35 | wrapperElement.appendChild(line1Element); 36 | wrapperElement.appendChild(line2Element); 37 | loaderElement.appendChild(wrapperElement); 38 | return loaderElement; 39 | } 40 | 41 | function createSidebarPanelElement( 42 | completed, 43 | planned, 44 | blockers, 45 | day, 46 | currentMonthName, 47 | currentYear, 48 | ) { 49 | const standupHeadElement = createStandupElement({ 50 | type: 'h4', 51 | classList: ['standup-head'], 52 | }); 53 | standupHeadElement.innerHTML = `Standup for ${day} ${currentMonthName} ${currentYear}`; 54 | const sidebarPanelElement = createStandupElement({ 55 | type: 'div', 56 | classList: ['sidebar-panel', 'sidebar'], 57 | }); 58 | sidebarPanelElement.id = 'standupSidebar'; 59 | const completedElement = createStandupElement({ 60 | type: 'div', 61 | classList: ['completed'], 62 | }); 63 | const plannedElement = createStandupElement({ 64 | type: 'div', 65 | classList: ['planned'], 66 | }); 67 | const blockersElement = createStandupElement({ 68 | type: 'div', 69 | classList: ['blockers'], 70 | }); 71 | 72 | completedElement.innerHTML = `Yesterday
    ${completed}`; 73 | plannedElement.innerHTML = `Today
    ${planned}`; 74 | blockersElement.innerHTML = `Blockers
    ${blockers}`; 75 | sidebarPanelElement.appendChild(standupHeadElement); 76 | sidebarPanelElement.appendChild(completedElement); 77 | sidebarPanelElement.appendChild(plannedElement); 78 | sidebarPanelElement.appendChild(blockersElement); 79 | return sidebarPanelElement; 80 | } 81 | 82 | function formatDateFromTimestamp(timestamp) { 83 | const dateObject = new Date(timestamp); 84 | const year = dateObject.getFullYear(); 85 | const month = dateObject.getMonth() + 1; 86 | const day = dateObject.getDate(); 87 | return `${day}-${month}-${year}`; 88 | } 89 | 90 | function formatDate(dateObject) { 91 | const year = dateObject.getFullYear(); 92 | const month = dateObject.getMonth() + 1; 93 | const day = dateObject.getDate(); 94 | return `${day}-${month}-${year}`; 95 | } 96 | 97 | function getDayOfWeek(date) { 98 | const daysOfWeek = [ 99 | 'Sunday', 100 | 'Monday', 101 | 'Tuesday', 102 | 'Wednesday', 103 | 'Thursday', 104 | 'Friday', 105 | 'Saturday', 106 | ]; 107 | const dayIndex = date.getDay(); 108 | 109 | return daysOfWeek[dayIndex]; 110 | } 111 | -------------------------------------------------------------------------------- /task-requests/util.js: -------------------------------------------------------------------------------- 1 | function createCustomElement(domObjectMap) { 2 | const el = document.createElement(domObjectMap.tagName); 3 | for (const [key, value] of Object.entries(domObjectMap)) { 4 | if (key === 'tagName') { 5 | continue; 6 | } 7 | if (key === 'eventListeners') { 8 | value.forEach((obj) => { 9 | el.addEventListener(obj.event, obj.func); 10 | }); 11 | } 12 | if (key === 'class') { 13 | if (Array.isArray(value)) { 14 | el.classList.add(...value); 15 | } else { 16 | el.classList.add(value); 17 | } 18 | } else if (key === 'child') { 19 | el.append(...value); 20 | } else if (key.startsWith('data-')) { 21 | el.setAttribute(key, value); 22 | } else { 23 | el[key] = value; 24 | } 25 | } 26 | return el; 27 | } 28 | 29 | function getQueryParamsString(taskRequestStates) { 30 | let filterQueries = {}; 31 | let sortQueries = {}; 32 | 33 | if (taskRequestStates.status) { 34 | filterQueries.status = taskRequestStates.status; 35 | } 36 | if (taskRequestStates.requestType) { 37 | filterQueries['request-type'] = taskRequestStates.requestType; 38 | } 39 | if (taskRequestStates.order) { 40 | sortQueries = Order[taskRequestStates.order]; 41 | } 42 | 43 | const queryString = generateRqlQuery(filterQueries, sortQueries); 44 | 45 | const urlParams = new URLSearchParams(); 46 | if (taskRequestStates.size) { 47 | urlParams.append('size', taskRequestStates.size); 48 | } 49 | if (queryString) { 50 | urlParams.append('q', queryString); 51 | } 52 | if (taskRequestStates.dev) { 53 | urlParams.append('dev', true); 54 | } 55 | return '?' + urlParams.toString(); 56 | } 57 | 58 | const addSpinner = (container) => { 59 | const spinner = createCustomElement({ 60 | tagName: 'div', 61 | className: 'spinner', 62 | }); 63 | 64 | container.append(spinner); 65 | 66 | function removeSpinner() { 67 | spinner.remove(); 68 | } 69 | 70 | return removeSpinner; 71 | }; 72 | 73 | /** 74 | * Parses the query parameters from the URLSearchParams object and organizes them into an object. 75 | * 76 | * @param {URLSearchParams} searchParams - The URLSearchParams object that needs to be parsed. 77 | * @returns {Object.} An object containing query parameter keys as properties 78 | * and arrays of corresponding values. 79 | * */ 80 | function parseQueryParams(searchParams) { 81 | const queryObject = {}; 82 | 83 | searchParams.forEach((value, key) => { 84 | if (!queryObject[key]) { 85 | queryObject[key] = []; 86 | } 87 | queryObject[key].push(value); 88 | }); 89 | return queryObject; 90 | } 91 | 92 | function formURLQueryString(queryStates, isDev) { 93 | const urlParams = new URLSearchParams(); 94 | 95 | if (queryStates.order) { 96 | let sortQueryString = Order[queryStates.order]; 97 | const key = Object.keys(sortQueryString)[0]; 98 | const value = sortQueryString[key]; 99 | sortQueryString = key + '-' + value; 100 | urlParams.append('sort', sortQueryString); 101 | } 102 | if (queryStates.status) { 103 | if (Array.isArray(queryStates.status)) { 104 | queryStates.status.forEach((_, index) => { 105 | urlParams.append('status', queryStates.status[index]); 106 | }); 107 | } else { 108 | urlParams.append('status', queryStates.status); 109 | } 110 | } 111 | if (queryStates.requestType) { 112 | queryStates.requestType.forEach((_, index) => 113 | urlParams.append('request-type', queryStates.requestType[index]), 114 | ); 115 | } 116 | 117 | if (isDev) { 118 | urlParams.append('dev', 'true'); 119 | } 120 | 121 | return '?' + urlParams.toString().trim(); 122 | } 123 | -------------------------------------------------------------------------------- /wallet/script.js: -------------------------------------------------------------------------------- 1 | const walletsRef = document.querySelector('.wallets'); 2 | 3 | const ipUsernames = document.querySelector('#all-users'); 4 | const usernames = ipUsernames.value; 5 | 6 | const nodeMapping = {}; 7 | 8 | function createUserWallet(username) { 9 | const userWallet = document.createElement('div'); 10 | userWallet.classList.add('user-wallet'); 11 | nodeMapping[username] = userWallet; 12 | 13 | const currenciesHolder = document.createElement('div'); 14 | currenciesHolder.classList.add('user-wallet__currencies'); 15 | userWallet.append(currenciesHolder); 16 | 17 | const usernameHolder = document.createElement('div'); 18 | usernameHolder.classList.add('user-wallet__username'); 19 | usernameHolder.textContent = username; 20 | userWallet.append(usernameHolder); 21 | 22 | const refreshButton = document.createElement('button'); 23 | refreshButton.classList.add('user-wallet__refresh-btn'); 24 | refreshButton.textContent = 'Refresh'; 25 | refreshButton.onclick = function () { 26 | updateWalletForUser(username); 27 | }; 28 | userWallet.append(refreshButton); 29 | 30 | walletsRef.append(userWallet); 31 | return userWallet; 32 | } 33 | 34 | function getUserWallet(username) { 35 | const userWallet = nodeMapping[username] || createUserWallet(username); 36 | return userWallet; 37 | } 38 | 39 | function updateWalletForUser(username) { 40 | const userWallet = getUserWallet(username); 41 | const currenciesHolder = userWallet.querySelector('.user-wallet__currencies'); 42 | 43 | // Remove previous 44 | const previousCurrencies = currenciesHolder.querySelectorAll('.currency'); 45 | previousCurrencies.forEach((node) => node.remove()); 46 | 47 | // Add fresh 48 | const userDataPromise = async () => { 49 | const response = await fetch(`${API_BASE_URL}/wallet/${username}`, { 50 | credentials: 'include', 51 | }); 52 | return await response.json(); 53 | }; 54 | 55 | userDataPromise().then((data) => { 56 | const currencies = data.wallet.currencies; 57 | for (const [currency, value] of Object.entries(currencies)) { 58 | if (value > 0) { 59 | const newCurrency = createCurrencyNode(currency, value); 60 | currenciesHolder.append(newCurrency); 61 | } 62 | } 63 | }); 64 | } 65 | 66 | function createCurrencyNode(currencyType, currencyVal) { 67 | const currencyRef = document.createElement('div'); 68 | const currencyLabelRef = document.createElement('div'); 69 | currencyRef.classList.add('currency'); 70 | currencyLabelRef.classList.add('currencyLabel'); 71 | 72 | // Create icon 73 | const icon = document.createElement('div'); 74 | icon.classList.add('currency__icon'); 75 | icon.classList.add(`currency-type--${currencyType}`); 76 | currencyRef.append(icon); 77 | 78 | // Create currency label 79 | const currencyLabel = document.createElement('p'); 80 | currencyLabel.classList.add('currency__label'); 81 | const currency = document.createTextNode(currencyType); 82 | currencyLabel.append(currency); 83 | currencyLabelRef.append(currencyLabel); 84 | 85 | // Create balance 86 | const balanceHolder = document.createElement('div'); 87 | balanceHolder.classList.add('currency__balance'); 88 | const balance = document.createTextNode(currencyVal); 89 | balanceHolder.append(balance); 90 | currencyLabelRef.append(balanceHolder); 91 | currencyRef.append(currencyLabelRef); 92 | 93 | return currencyRef; 94 | } 95 | 96 | function getWallets() { 97 | const inputString = document.getElementById('all-users').value; 98 | const usernamesProvided = inputString 99 | .split(',') 100 | .map((usrname) => usrname.trim()); 101 | usernamesProvided.forEach((username) => updateWalletForUser(username)); 102 | } 103 | 104 | getWallets(); 105 | -------------------------------------------------------------------------------- /components/filter/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: 'Inter', sans-serif; 3 | --color-primary: #1d1283; 4 | --color-secondary: #5145cd; 5 | --color-error: #da1e28; 6 | --color-gray-light: #eee; 7 | --color-gray: #666; 8 | --light-gray-color: lightgray; 9 | --white: #ffffff; 10 | --color-text-light: rgba(0, 0, 0, 0.6); 11 | --elevation-1: 0 1px 3px 1px rgba(0, 0, 0, 0.1), 12 | 0 1px 2px 0 rgba(0, 0, 0, 0.1); 13 | --elevation-3: 0px 1px 3px 0px rgba(0, 0, 0, 0.3), 14 | 0px 4px 8px 3px rgba(0, 0, 0, 0.15); 15 | --light-gray-color: lightgray; 16 | --light-secondary-color: #ddd4fa; 17 | --base-light-grey-border: 1px solid var(--light-gray-color); 18 | } 19 | 20 | .filter__component__options__container { 21 | display: flex; 22 | flex-direction: column; 23 | gap: 0.4rem; 24 | } 25 | .filter__component__clear__button { 26 | padding: 0.25rem 0.5rem; 27 | border: solid 1px var(--color-gray-light); 28 | border-radius: 0.25rem; 29 | background-color: var(--light-gray-color); 30 | cursor: pointer; 31 | &:disabled { 32 | background-color: transparent; 33 | cursor: not-allowed; 34 | } 35 | } 36 | 37 | .filter__component__container { 38 | display: flex; 39 | justify-content: end; 40 | gap: 1rem; 41 | } 42 | 43 | .filter__component__toggle__button { 44 | display: flex; 45 | justify-content: space-around; 46 | align-items: center; 47 | font-size: 1rem; 48 | width: 7rem; 49 | background-color: var(--color-secondary); 50 | border-radius: 0.5rem; 51 | border: none; 52 | cursor: pointer; 53 | padding: 0.4rem 1.5rem; 54 | } 55 | 56 | .filter__component__modal { 57 | border: var(--base-light-grey-border); 58 | padding: 1rem; 59 | margin-top: 3rem; 60 | position: absolute; 61 | z-index: 1; 62 | background-color: var(--white); 63 | box-shadow: var(--elevation-3); 64 | border-radius: 0.5rem; 65 | display: flex; 66 | flex-direction: column; 67 | gap: 1rem; 68 | font-size: 0.9rem; 69 | } 70 | 71 | .filter__component__header { 72 | display: flex; 73 | align-items: center; 74 | gap: 1.5rem; 75 | } 76 | 77 | .filter__component__title, 78 | .filter__component__request-type-label { 79 | font-weight: 600; 80 | margin-left: 0.3rem; 81 | } 82 | 83 | .filter__component__label { 84 | font-weight: 500; 85 | color: var(--white); 86 | } 87 | 88 | .filter__component__status__filter input[type='checkbox'] { 89 | accent-color: var(--color-secondary); 90 | } 91 | 92 | .filter__component__active__tags { 93 | display: flex; 94 | flex-wrap: wrap; 95 | gap: 0.5rem; 96 | margin-top: 1.5rem; 97 | justify-content: end; 98 | width: fit-content; 99 | margin-left: auto; 100 | } 101 | 102 | .extension-page-padding { 103 | padding-right: 2.5rem; 104 | bottom: 2.5rem; 105 | position: relative; 106 | } 107 | 108 | @media (max-width: 600px) { 109 | .extension-page-padding { 110 | padding-right: 1rem; 111 | bottom: 2.5rem; 112 | position: relative; 113 | } 114 | } 115 | 116 | .filter__component__tag { 117 | background-color: var(--light-secondary-color); 118 | color: var(--color-secondary); 119 | min-width: 7rem; 120 | padding: 0.5rem 0.75rem; 121 | border-radius: 0.75rem; 122 | font-size: 0.9rem; 123 | display: flex; 124 | justify-content: space-between; 125 | gap: 0.375rem; 126 | box-sizing: border-box; 127 | } 128 | 129 | .filter__component__tag__close { 130 | width: 1.2rem; 131 | } 132 | 133 | .apply__filter__component__button { 134 | border: none; 135 | background-color: var(--color-secondary); 136 | color: var(--white); 137 | padding: 0.5rem 0; 138 | border-radius: 0.5rem; 139 | } 140 | 141 | .filter__component__icon { 142 | width: 1.8rem; 143 | height: 1.5rem; 144 | } 145 | 146 | .filter__component__status__filter { 147 | display: flex; 148 | gap: 0.5rem; 149 | } 150 | -------------------------------------------------------------------------------- /users/details/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | User Management | Real Dev Squad 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
    21 |

    User Management

    22 |
    23 |
    24 |
    25 |
    26 |
      27 |
    • 28 |
      29 |

      Professional Details

      30 |
      31 | expand 36 |
      37 |
      38 |
    • 39 |
    • 40 |
      41 |

      Skills

      42 |
      43 | expand 48 |
      49 |
      50 |
    • 51 |
    • 52 |
      53 |

      Availability

      54 |
      55 | expand 60 |
      61 |
      62 |
    • 63 |
    • 64 |
      65 |

      Tasks

      66 |
      67 | expand 72 |
      73 |
      74 |
    • 75 |
    • 76 |
      77 |

      PR's

      78 |
      79 | expand 84 |
      85 |
      86 |
    • 87 |
    88 |
    89 |
    90 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /userLogin.js: -------------------------------------------------------------------------------- 1 | const urlParam = new URLSearchParams(window.location.search); 2 | const isDevFlag = urlParam.get('dev') === 'true'; 3 | const DROPDOWN_OPTIONS = [ 4 | { 5 | name: 'Home', 6 | link: 'https://dashboard.realdevsquad.com/', 7 | }, 8 | { 9 | name: 'Status', 10 | link: 'https://my.realdevsquad.com/', 11 | }, 12 | { 13 | name: 'Profile', 14 | link: 'https://my.realdevsquad.com/profile', 15 | }, 16 | { 17 | name: 'Tasks', 18 | link: 'https://my.realdevsquad.com/tasks', 19 | }, 20 | { 21 | name: 'Identity', 22 | link: 'https://my.realdevsquad.com/identity', 23 | }, 24 | ]; 25 | 26 | async function handleUserSignin() { 27 | try { 28 | const self_user = await getSelfUser(); 29 | if (self_user) { 30 | const signInButton = document.querySelector('.sign-in-btn'); 31 | signInButton.style.display = 'none'; 32 | const dropdown = document.getElementById('dropdown'); 33 | const userInfo = document.querySelector('.user-info'); 34 | const username = document.getElementById('user-name'); 35 | const userImage = document.getElementById('user-img'); 36 | const tasksNav = document.getElementById('tasksNav'); 37 | username.innerText = `Hello, ${self_user.first_name}!`; 38 | userImage.setAttribute('src', self_user?.picture?.url); 39 | userInfo.classList.add('active'); 40 | tasksNav.style.alignItems = 'center'; 41 | const dropdownList = createElement({ 42 | type: 'ul', 43 | attributes: { 44 | class: 'dropdown-list', 45 | }, 46 | }); 47 | 48 | DROPDOWN_OPTIONS.forEach((option) => { 49 | const listElement = createElement({ 50 | type: 'li', 51 | attributes: { 52 | class: 'dropdown-item', 53 | }, 54 | }); 55 | const anchorElement = createElement({ 56 | type: 'a', 57 | attributes: { 58 | class: 'dropdown-link', 59 | }, 60 | }); 61 | anchorElement.href = `${option.link}`; 62 | anchorElement.innerText = `${option.name}`; 63 | listElement.append(anchorElement); 64 | dropdownList.append(listElement); 65 | }); 66 | const horizontalLine = createElement({ 67 | type: 'hr', 68 | attributes: { 69 | class: 'line', 70 | }, 71 | }); 72 | 73 | dropdownList.append(horizontalLine); 74 | const signOutElement = createElement({ 75 | type: 'li', 76 | attributes: { 77 | class: 'dropdown-item', 78 | id: 'signout-option', 79 | }, 80 | }); 81 | signOutElement.classList.add('dropdown-link'); 82 | 83 | dropdownList.append(signOutElement); 84 | signOutElement.innerText = 'Sign Out'; 85 | dropdown.append(dropdownList); 86 | 87 | userInfo.addEventListener('click', () => { 88 | if (dropdown.classList.contains('active')) { 89 | dropdown.classList.remove('active'); 90 | } else { 91 | dropdown.classList.add('active'); 92 | } 93 | }); 94 | document.addEventListener('click', (event) => { 95 | if ( 96 | isDevFlag && 97 | dropdown.classList.contains('active') && 98 | !dropdown.contains(event.target) && 99 | !userInfo.contains(event.target) 100 | ) { 101 | dropdown.classList.remove('active'); 102 | } 103 | }); 104 | signOutElement.addEventListener('click', () => { 105 | getSelfUser('/auth/signout'); 106 | }); 107 | } 108 | } catch (error) {} 109 | } 110 | 111 | const initializePageListener = () => { 112 | addNavbartoPage().then(handleUserSignin); 113 | }; 114 | 115 | window.addEventListener('load', initializePageListener); 116 | 117 | window.addEventListener('beforeunload', () => { 118 | window.removeEventListener('load', initializePageListener); 119 | }); 120 | -------------------------------------------------------------------------------- /applications/utils.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = window.API_BASE_URL; 2 | 3 | function createElement({ type, attributes = {}, innerText }) { 4 | const element = document.createElement(type); 5 | Object.keys(attributes).forEach((item) => { 6 | element.setAttribute(item, attributes[item]); 7 | }); 8 | element.textContent = innerText; 9 | return element; 10 | } 11 | 12 | async function getApplications({ 13 | applicationStatus, 14 | size = 6, 15 | next = '', 16 | dev = false, 17 | }) { 18 | let url; 19 | 20 | if (next) url = `${BASE_URL}${next}`; 21 | else if (applicationStatus === 'all') { 22 | url = `${BASE_URL}/applications?size=${size}`; 23 | } else { 24 | url = `${BASE_URL}/applications?size=${size}&status=${applicationStatus}`; 25 | if (dev) { 26 | url += '&dev=true'; 27 | } 28 | } 29 | 30 | try { 31 | const res = await fetch(url, { 32 | method: 'GET', 33 | credentials: 'include', 34 | headers: { 35 | 'Content-type': 'application/json', 36 | }, 37 | }); 38 | const data = res.json(); 39 | return data; 40 | } catch (error) { 41 | console.error(error); 42 | const errorMessage = error?.message || 'Something went wrong!'; 43 | showToastMessage({ 44 | isDev: dev, 45 | oldToastFunction: showToast, 46 | type: 'error', 47 | message: errorMessage, 48 | }); 49 | } 50 | } 51 | 52 | async function getApplicationById(applicationId) { 53 | try { 54 | const res = await fetch(`${BASE_URL}/applications/${applicationId}`, { 55 | method: 'GET', 56 | credentials: 'include', 57 | headers: { 58 | 'Content-type': 'application/json', 59 | }, 60 | }); 61 | 62 | if (!res.ok) { 63 | const error = await res.json(); 64 | throw error; 65 | } 66 | 67 | const data = await res.json(); 68 | return data.application; 69 | } catch (error) { 70 | throw error; 71 | } 72 | } 73 | 74 | async function getIsSuperUser(isDev) { 75 | try { 76 | const res = await fetch(`${BASE_URL}/users?profile=true`, { 77 | method: 'GET', 78 | credentials: 'include', 79 | headers: { 80 | 'Content-type': 'application/json', 81 | }, 82 | }); 83 | const self_user = await res.json(); 84 | return self_user?.roles['super_user']; 85 | } catch (error) { 86 | console.error(error); 87 | const errorMessage = error?.message || 'Something went wrong!'; 88 | showToastMessage({ 89 | isDev, 90 | oldToastFunction: showToast, 91 | type: 'error', 92 | message: errorMessage, 93 | }); 94 | } 95 | } 96 | 97 | async function updateApplication({ applicationPayload, applicationId }) { 98 | try { 99 | const res = await fetch(`${BASE_URL}/applications/${applicationId}`, { 100 | method: 'PATCH', 101 | credentials: 'include', 102 | body: JSON.stringify(applicationPayload), 103 | headers: { 104 | 'Content-type': 'application/json', 105 | }, 106 | }); 107 | 108 | if (!res.ok) { 109 | const error = await res.json(); 110 | throw error; 111 | } 112 | 113 | const data = await res.json(); 114 | return data; 115 | } catch (error) { 116 | throw error; 117 | } 118 | } 119 | 120 | function showToast({ message, type }) { 121 | toast.innerText = message; 122 | 123 | if (type === 'success') { 124 | toast.classList.add('success'); 125 | toast.classList.remove('failure'); 126 | } else { 127 | toast.classList.add('failure'); 128 | toast.classList.remove('success'); 129 | } 130 | 131 | toast.classList.remove('hidden'); 132 | toast.classList.add('animated_toast'); 133 | 134 | setTimeout(() => { 135 | toast.classList.add('hidden'); 136 | toast.classList.remove('animated_toast'); 137 | }, 3000); 138 | } 139 | 140 | export { 141 | createElement, 142 | getApplications, 143 | getApplicationById, 144 | updateApplication, 145 | getIsSuperUser, 146 | showToast, 147 | }; 148 | --------------------------------------------------------------------------------