├── .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 |
16 |
RDS ADMIN PANEL
17 |
18 |
19 | --Select a option--
20 | Create Skill Tags
21 | Create Level Tags
22 |
23 |
24 |
25 |
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 |
23 | PROFILE DIFFERENCES
24 |
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 | Users:
18 |
25 |
26 |
27 | Fetch Wallets
28 |
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 |
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 |
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 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
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 |
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 |
12 |
13 |
14 |
15 |
16 |
21 |
25 |
30 |
35 |
36 |
37 |
38 |
39 | Pending
40 |
41 |
42 | Approved
43 |
44 |
45 | Rejected
46 |
47 |
48 |
49 |
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 | ? ` `
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 |
30 | Sign In With GitHub
31 |
32 |
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 |
27 |
34 |
35 |
36 |
48 |
49 |
50 |
55 |
56 |
57 |
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 |
25 | Identity Service Logs
26 |
27 |
28 |
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 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
36 |
37 |
38 | Filters
39 |
40 |
41 |
42 |
43 |
44 |
Filters
45 | Clear
46 |
47 |
48 |
Status
49 |
50 |
51 |
52 | Apply Filter
53 |
54 |
55 |
56 |
64 |
69 |
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 |
27 |
28 |
29 |
30 | Filters
31 |
32 |
33 |
34 |
35 |
Filters
36 | Clear
37 |
38 |
39 |
Availability
40 |
41 | Skills
42 |
43 |
44 |
45 | Apply Filter
46 |
47 |
48 |
49 |
50 |
51 |
52 |
59 |
60 |
61 |
68 |
69 |
70 |
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 |
79 |
80 |
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 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Professional Details
30 |
31 |
36 |
37 |
38 |
39 |
40 |
41 |
Skills
42 |
43 |
48 |
49 |
50 |
51 |
52 |
53 |
Availability
54 |
55 |
60 |
61 |
62 |
63 |
64 |
65 |
Tasks
66 |
67 |
72 |
73 |
74 |
75 |
76 |
77 |
PR's
78 |
79 |
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 |
--------------------------------------------------------------------------------