Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
534f702
Initial engagements work
jmgasper Jan 9, 2026
a126dd1
Minor UI and API usage fixes
jmgasper Jan 12, 2026
9f08052
Engagements cleanup
jmgasper Jan 12, 2026
2c0f48a
Engagements cleanup
jmgasper Jan 12, 2026
a828dd1
Lint
jmgasper Jan 12, 2026
723bf4a
Updates to support changes to the engagements-api-v6
jmgasper Jan 13, 2026
11fa6da
Navigation and usability tweaks for engagements
jmgasper Jan 14, 2026
97f497f
Add feedback functionality
jmgasper Jan 14, 2026
984db2f
Work in progress updates on the new statuses, multi-member assignment…
jmgasper Jan 15, 2026
c648bc0
Lint
jmgasper Jan 15, 2026
168b3dc
Assignment handling for multi-member engagements
jmgasper Jan 15, 2026
20f0c29
PM-3416 and additional fixes for new assignment / multi-member engag…
jmgasper Jan 15, 2026
3cdb283
More UI fixes
jmgasper Jan 16, 2026
9a42d25
Fix up the engagements list page
jmgasper Jan 16, 2026
a903302
QA fixes for engagements
jmgasper Jan 19, 2026
a192264
Navigation and UI fixes
jmgasper Jan 19, 2026
fb1e064
Fix some API references
jmgasper Jan 19, 2026
29fc7f3
Fix statuses to align with API
jmgasper Jan 19, 2026
a9e5872
Fix spacing for error messages
jmgasper Jan 19, 2026
4a8beb5
Fix engagements payment creation and tweak a UI element
jmgasper Jan 19, 2026
5d66635
Better origin for payments, to match DB
jmgasper Jan 19, 2026
3eaa70b
QA fixes, and add phone number to applications view
jmgasper Jan 20, 2026
5ea1a40
Member experience viewing for PM / TM
jmgasper Jan 20, 2026
9465973
Feedback and experience fixes
jmgasper Jan 20, 2026
84bbe66
UI and QA fixes
jmgasper Jan 21, 2026
73f230c
QA fixes
jmgasper Jan 21, 2026
4333353
Feedback and QA updates https://topcoder.atlassian.net/browse/PM-3559
jmgasper Jan 22, 2026
d1435d1
Add basic tooltip for assignee mousover
jmgasper Jan 22, 2026
6ff326d
Payment weaks for PM-3555
jmgasper Jan 22, 2026
e79b94a
Remove payment status display
jmgasper Jan 22, 2026
d66ada9
Merge branch 'develop' into engagements
jmgasper Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/constants/development.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@ module.exports = {
ACCOUNTS_APP_CONNECTOR_URL: `https://accounts-auth0.${DOMAIN}`,
ACCOUNTS_APP_LOGIN_URL: `https://accounts-auth0.${DOMAIN}`,
COMMUNITY_APP_URL: `https://www.${DOMAIN}`,
ENGAGEMENTS_APP_URL: 'https://engagements.topcoder-dev.com',
MEMBER_API_URL: `${DEV_API_HOSTNAME}/v6/members`,
CHALLENGE_API_URL: `${DEV_API_HOSTNAME}/v6/challenges`,
ENGAGEMENTS_API_URL: `${DEV_API_HOSTNAME}/v6/engagements/engagements`,
ENGAGEMENTS_ROOT_API_URL: `${DEV_API_HOSTNAME}/v6/engagements`,
APPLICATIONS_API_URL: `${DEV_API_HOSTNAME}/v6/engagements/applications`,
TC_FINANCE_API_URL: process.env.TC_FINANCE_API_URL || `${API_V6}/finance`,
CHALLENGE_DEFAULT_REVIEWERS_URL: `${DEV_API_HOSTNAME}/v6/challenge/default-reviewers`,
CHALLENGE_API_VERSION: '1.1.0',
CHALLENGE_TIMELINE_TEMPLATES_URL: `${DEV_API_HOSTNAME}/v6/timeline-templates`,
Expand Down Expand Up @@ -62,6 +67,7 @@ module.exports = {
HEADER_AUTH_URLS_HREF: `https://accounts-auth0.${DOMAIN}?utm_source=community-app-main`,
HEADER_AUTH_URLS_LOCATION: `https://accounts-auth0.${DOMAIN}?retUrl=%S&utm_source=community-app-main`,
SKILLS_V5_API_URL: `${API_V5}/standardized-skills/skills/autocomplete`,
SKILLS_V5_SKILLS_URL: `${API_V5}/standardized-skills/skills`,
UPDATE_SKILLS_V5_API_URL: `${API_V5}/standardized-skills/challenge-skills`,
SALESFORCE_BILLING_ACCOUNT_LINK: 'https://c.cs18.visual.force.com/apex/baredirect?id=',
PROFILE_URL: 'https://profiles.topcoder-dev.com/'
Expand Down
6 changes: 6 additions & 0 deletions config/constants/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ module.exports = {
ACCOUNTS_APP_CONNECTOR_URL: `https://accounts-auth0.${DOMAIN}`,
ACCOUNTS_APP_LOGIN_URL: `https://accounts-auth0.${DOMAIN}`,
COMMUNITY_APP_URL: `https://www.${DOMAIN}`,
ENGAGEMENTS_APP_URL: 'https://engagements.topcoder-dev.com',

// Local service URLs
MEMBER_API_URL: `${LOCAL_MEMBER_API}/members`,
CHALLENGE_API_URL: `${LOCAL_CHALLENGE_API}/challenges`,
ENGAGEMENTS_API_URL: `${LOCAL_CHALLENGE_API}/engagements/engagements`,
ENGAGEMENTS_ROOT_API_URL: `${LOCAL_CHALLENGE_API}/engagements`,
APPLICATIONS_API_URL: `${LOCAL_CHALLENGE_API}/engagements/applications`,
TC_FINANCE_API_URL: process.env.TC_FINANCE_API_URL || 'http://localhost:3009/v6/finance',
CHALLENGE_DEFAULT_REVIEWERS_URL: `${LOCAL_CHALLENGE_API.replace(/\/v6$/, '')}/v6/challenge/default-reviewers`,
CHALLENGE_API_VERSION: '1.1.0',
CHALLENGE_TIMELINE_TEMPLATES_URL: `${LOCAL_CHALLENGE_API}/timeline-templates`,
Expand Down Expand Up @@ -90,6 +95,7 @@ module.exports = {

// Standardized skills API on local
SKILLS_V5_API_URL: `${LOCAL_SKILLS_API_V5}/skills/autocomplete`,
SKILLS_V5_SKILLS_URL: `${LOCAL_SKILLS_API_V5}/skills`,
UPDATE_SKILLS_V5_API_URL: `${LOCAL_SKILLS_API_V5}/challenge-skills`,

SALESFORCE_BILLING_ACCOUNT_LINK: 'https://c.cs18.visual.force.com/apex/baredirect?id=',
Expand Down
6 changes: 6 additions & 0 deletions config/constants/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ module.exports = {
ACCOUNTS_APP_CONNECTOR_URL: process.env.ACCOUNTS_APP_CONNECTOR_URL || `https://accounts-auth0.${DOMAIN}`,
ACCOUNTS_APP_LOGIN_URL: `https://accounts-auth0.${DOMAIN}`,
COMMUNITY_APP_URL: `https://www.${DOMAIN}`,
ENGAGEMENTS_APP_URL: 'https://engagements.topcoder.com',
MEMBER_API_URL: `${PROD_API_HOSTNAME}/v6/members`,
CHALLENGE_API_URL: `${PROD_API_HOSTNAME}/v6/challenges`,
ENGAGEMENTS_API_URL: `${PROD_API_HOSTNAME}/v6/engagements/engagements`,
ENGAGEMENTS_ROOT_API_URL: `${PROD_API_HOSTNAME}/v6/engagements`,
APPLICATIONS_API_URL: `${PROD_API_HOSTNAME}/v6/engagements/applications`,
TC_FINANCE_API_URL: process.env.TC_FINANCE_API_URL || `${API_V6}/finance`,
CHALLENGE_DEFAULT_REVIEWERS_URL: `${PROD_API_HOSTNAME}/v6/challenge/default-reviewers`,
CHALLENGE_API_VERSION: '1.1.0',
CHALLENGE_TIMELINE_TEMPLATES_URL: `${PROD_API_HOSTNAME}/v6/timeline-templates`,
Expand Down Expand Up @@ -62,6 +67,7 @@ module.exports = {
HEADER_AUTH_URLS_HREF: `https://accounts-auth0.${DOMAIN}?utm_source=community-app-main`,
HEADER_AUTH_URLS_LOCATION: `https://accounts-auth0.${DOMAIN}?retUrl=%S&utm_source=community-app-main`,
SKILLS_V5_API_URL: `${API_V5}/standardized-skills/skills/autocomplete`,
SKILLS_V5_SKILLS_URL: `${API_V5}/standardized-skills/skills`,
UPDATE_SKILLS_V5_API_URL: `${API_V5}/standardized-skills/challenge-skills`,
SALESFORCE_BILLING_ACCOUNT_LINK: 'https://topcoder.my.salesforce.com/apex/baredirect?id=',
PROFILE_URL: 'https://profiles.topcoder.com/'
Expand Down
138 changes: 138 additions & 0 deletions config/formatWebpackMessages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

const chalk = require('chalk');
const friendlySyntaxErrorLabel = 'Syntax error:';

function isLikelyASyntaxError(message) {
return message.indexOf(friendlySyntaxErrorLabel) !== -1;
}

// Cleans up webpack error messages.
// eslint-disable-next-line no-unused-vars
function formatMessage(message, isError) {
// Webpack 5 can emit warning/error objects; normalize to strings.
if (typeof message !== 'string') {
if (message && typeof message.message === 'string') {
message = message.message;
} else if (message && typeof message.stack === 'string') {
message = message.stack;
} else {
message = String(message);
}
}

let lines = message.split('\n');

// Strip Webpack-added headers off errors/warnings
// https://github.com/webpack/webpack/blob/master/lib/ModuleError.js
lines = lines.filter(line => !/Module [A-z ]+\(from/.test(line));

// Transform parsing error into syntax error
// TODO: move this to our ESLint formatter?
lines = lines.map(line => {
const parsingError = /Line (\d+):(?:(\d+):)?\s*Parsing error: (.+)$/.exec(
line
);
if (!parsingError) {
return line;
}
const [, errorLine, errorColumn, errorMessage] = parsingError;
return `${friendlySyntaxErrorLabel} ${errorMessage} (${errorLine}:${errorColumn})`;
});

message = lines.join('\n');
// Smoosh syntax errors (commonly found in CSS)
message = message.replace(
/SyntaxError\s+\((\d+):(\d+)\)\s*(.+?)\n/g,
`${friendlySyntaxErrorLabel} $3 ($1:$2)\n`
);
// Remove columns from ESLint formatter output (we added these for more
// accurate syntax errors)
message = message.replace(/Line (\d+):\d+:/g, 'Line $1:');
// Clean up export errors
message = message.replace(
/^.*export '(.+?)' was not found in '(.+?)'.*$/gm,
`Attempted import error: '$1' is not exported from '$2'.`
);
message = message.replace(
/^.*export 'default' \(imported as '(.+?)'\) was not found in '(.+?)'.*$/gm,
`Attempted import error: '$2' does not contain a default export (imported as '$1').`
);
message = message.replace(
/^.*export '(.+?)' \(imported as '(.+?)'\) was not found in '(.+?)'.*$/gm,
`Attempted import error: '$1' is not exported from '$3' (imported as '$2').`
);
lines = message.split('\n');

// Remove leading newline
if (lines.length > 2 && lines[1].trim() === '') {
lines.splice(1, 1);
}
// Clean up file name
lines[0] = lines[0].replace(/^(.*) \d+:\d+-\d+$/, '$1');

// Cleans up verbose "module not found" messages for files and packages.
if (lines[1] && lines[1].indexOf('Module not found: ') === 0) {
lines = [
lines[0],
lines[1]
.replace('Error: ', '')
.replace('Module not found: Cannot find file:', 'Cannot find file:'),
];
}

// Add helpful message for users trying to use Sass for the first time
if (lines[1] && lines[1].match(/Cannot find module.+node-sass/)) {
lines[1] = 'To import Sass files, you first need to install node-sass.\n';
lines[1] +=
'Run `npm install node-sass` or `yarn add node-sass` inside your workspace.';
}

lines[0] = chalk.inverse(lines[0]);

message = lines.join('\n');
// Internal stacks are generally useless so we strip them... with the
// exception of stacks containing `webpack:` because they're normally
// from user code generated by Webpack. For more information see
// https://github.com/facebook/create-react-app/pull/1050
message = message.replace(
/^\s*at\s((?!webpack:).)*:\d+:\d+[\s)]*(\n|$)/gm,
''
); // at ... ...:x:y
message = message.replace(/^\s*at\s<anonymous>(\n|$)/gm, ''); // at <anonymous>
lines = message.split('\n');

// Remove duplicated newlines
lines = lines.filter(
(line, index, arr) =>
index === 0 || line.trim() !== '' || line.trim() !== arr[index - 1].trim()
);

// Reassemble the message
message = lines.join('\n');
return message.trim();
}

function formatWebpackMessages(json) {
const formattedErrors = json.errors.map(function(message) {
return formatMessage(message, true);
});
const formattedWarnings = json.warnings.map(function(message) {
return formatMessage(message, false);
});
const result = { errors: formattedErrors, warnings: formattedWarnings };
if (result.errors.some(isLikelyASyntaxError)) {
// If there are any syntax errors, show just them.
result.errors = result.errors.filter(isLikelyASyntaxError);
}
return result;
}

module.exports = formatWebpackMessages;
18 changes: 17 additions & 1 deletion config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ module.exports = function (webpackEnv) {
const isEnvDevelopment = webpackEnv === 'development'
const isEnvProduction = webpackEnv === 'production'
const WM_DEBUG = /^(1|true|on|yes)$/i.test(String(process.env.WM_DEBUG || ''))
const reactDevUtilsContextRegExp = /[\\/]react-dev-utils[\\/]/

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
Expand All @@ -60,6 +61,14 @@ module.exports = function (webpackEnv) {

// common function to get style loaders
const getStyleLoaders = (cssOptions, preProcessor) => {
const resolvedCssOptions = Object.assign(
{
url: {
filter: url => !url.toLowerCase().startsWith('data:')
}
},
cssOptions
)
const loaders = [
isEnvDevelopment && require.resolve('style-loader'),
isEnvProduction && {
Expand All @@ -71,7 +80,7 @@ module.exports = function (webpackEnv) {
},
{
loader: require.resolve('css-loader'),
options: cssOptions
options: resolvedCssOptions
},
{
// Options for PostCSS as we reference these options twice
Expand Down Expand Up @@ -476,6 +485,13 @@ module.exports = function (webpackEnv) {
// This gives some necessary context to module not found errors, such as
// the requesting resource.
new ModuleNotFoundPlugin(paths.appPath),
// Ensure the dev client tolerates webpack 5 warning/error objects.
isEnvDevelopment &&
new webpack.NormalModuleReplacementPlugin(/\.\/formatWebpackMessages$/, (resource) => {
if (reactDevUtilsContextRegExp.test(resource.context || '')) {
resource.request = path.resolve(__dirname, 'formatWebpackMessages')
}
}),
// (DefinePlugin already added above with merged env)
// This is necessary to emit hot updates (currently CSS only):
isEnvDevelopment && new webpack.HotModuleReplacementPlugin(),
Expand Down
19 changes: 19 additions & 0 deletions scripts/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const clearConsole = require('react-dev-utils/clearConsole')
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles')
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages')
// Webpack 5 returns error objects; normalize to strings for react-dev-utils@7.
const normalizeWebpackMessages = (json = {}) => {
const normalizeMessage = (message) => {
if (typeof message === 'string') return message
if (message && typeof message.message === 'string') return message.message
if (message && typeof message.stack === 'string') return message.stack
return String(message)
}
return {
...json,
errors: Array.isArray(json.errors) ? json.errors.map(normalizeMessage) : [],
warnings: Array.isArray(json.warnings) ? json.warnings.map(normalizeMessage) : []
}
}
const formatWebpackMessagesPatched = (json) =>
formatWebpackMessages(normalizeWebpackMessages(json))
require.cache[require.resolve('react-dev-utils/formatWebpackMessages')].exports =
formatWebpackMessagesPatched
const {
choosePort,
createCompiler,
Expand Down
109 changes: 109 additions & 0 deletions src/actions/applications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import _ from 'lodash'
import {
fetchApplications,
fetchApplication,
updateApplicationStatus as updateApplicationStatusAPI
} from '../services/engagements'
import {
LOAD_APPLICATIONS_PENDING,
LOAD_APPLICATIONS_SUCCESS,
LOAD_APPLICATIONS_FAILURE,
LOAD_APPLICATION_DETAILS_PENDING,
LOAD_APPLICATION_DETAILS_SUCCESS,
LOAD_APPLICATION_DETAILS_FAILURE,
UPDATE_APPLICATION_STATUS_PENDING,
UPDATE_APPLICATION_STATUS_SUCCESS,
UPDATE_APPLICATION_STATUS_FAILURE
} from '../config/constants'

/**
* Loads applications for an engagement
* @param {String|Number} engagementId
* @param {String} statusFilter
*/
export function loadApplications (engagementId, statusFilter = 'all') {
return async (dispatch) => {
dispatch({
type: LOAD_APPLICATIONS_PENDING
})

const filters = {}
if (statusFilter && statusFilter !== 'all') {
filters.status = statusFilter
}

try {
const response = await fetchApplications(engagementId, filters)
return dispatch({
type: LOAD_APPLICATIONS_SUCCESS,
applications: _.get(response, 'data', [])
})
} catch (error) {
dispatch({
type: LOAD_APPLICATIONS_FAILURE,
error
})
return Promise.reject(error)
}
}
}

/**
* Loads application details
* @param {String|Number} applicationId
*/
export function loadApplicationDetails (applicationId) {
return async (dispatch) => {
if (!applicationId) {
return dispatch({
type: LOAD_APPLICATION_DETAILS_SUCCESS,
applicationDetails: {}
})
}

dispatch({
type: LOAD_APPLICATION_DETAILS_PENDING
})

try {
const response = await fetchApplication(applicationId)
return dispatch({
type: LOAD_APPLICATION_DETAILS_SUCCESS,
applicationDetails: _.get(response, 'data', {})
})
} catch (error) {
dispatch({
type: LOAD_APPLICATION_DETAILS_FAILURE,
error
})
return Promise.reject(error)
}
}
}

/**
* Updates application status
* @param {String|Number} applicationId
* @param {String} status
*/
export function updateApplicationStatus (applicationId, status) {
return async (dispatch) => {
dispatch({
type: UPDATE_APPLICATION_STATUS_PENDING
})

try {
const response = await updateApplicationStatusAPI(applicationId, status)
return dispatch({
type: UPDATE_APPLICATION_STATUS_SUCCESS,
application: _.get(response, 'data', {})
})
} catch (error) {
dispatch({
type: UPDATE_APPLICATION_STATUS_FAILURE,
error
})
return Promise.reject(error)
}
}
}
Loading
Loading