Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
0be46e1
Update text on system admin UI
himaniraghav3 Sep 11, 2026
b2cf657
fix(PM-6323): apply component navigation and dropdown colors
jmgasper Sep 15, 2026
e0bab7e
fix(PM-6324): scroll submission actions to the upload form
jmgasper Sep 15, 2026
22c8550
fix(PM-6231): persist pending final deliverable file types
jmgasper Sep 15, 2026
08ccf5e
fix(PM-6311): combine Marathon test progress and status
jmgasper Sep 15, 2026
d16dc85
Merge pull request #2298 from topcoder-platform/fix/PM-6323-navigatio…
jmgasper Sep 15, 2026
df10f10
Merge pull request #2299 from topcoder-platform/fix/PM-6324-scroll-to…
jmgasper Sep 15, 2026
129358d
Merge pull request #2300 from topcoder-platform/fix/PM-6231-final-del…
jmgasper Sep 15, 2026
667c802
Merge pull request #2301 from topcoder-platform/fix/PM-6311-combined-…
jmgasper Sep 15, 2026
5f4b881
Merge pull request #2276 from topcoder-platform/PM-5327
himaniraghav3 Sep 15, 2026
bf7752c
PM-6346 - fix type for challenge
vas3a Sep 15, 2026
d34b61e
PM-6329 Expand Work showcase forms and add WIN sharing
jmgasper Sep 15, 2026
525db0e
Merge pull request #2302 from topcoder-platform/PM-6329
jmgasper Sep 15, 2026
64b2a92
PM-6343 Add Sales report app and Work integration
jmgasper Sep 16, 2026
ba9ef84
Merge pull request #2303 from topcoder-platform/PM-6343
jmgasper Sep 16, 2026
31067c6
PS-616 Display full installment totals in Wallet Admin
jmgasper Sep 16, 2026
12db9eb
PM-6343 Align Sales search and filter controls
jmgasper Sep 16, 2026
00d3eb4
Merge pull request #2305 from topcoder-platform/PM-6343
jmgasper Sep 16, 2026
4d65f64
Merge pull request #2304 from topcoder-platform/PS-616
jmgasper Sep 16, 2026
e1e27ea
Fix: Platform UI bugs
himaniraghav3 Sep 16, 2026
3b2232c
PM-6289 Restore Topgear URL submission flow in Opportunities
jmgasper Sep 16, 2026
af2ba39
Merge pull request #2308 from topcoder-platform/PM-6346_ingest-type-fix
vas3a Sep 16, 2026
6aa1b57
Merge pull request #2306 from topcoder-platform/PM-6326
himaniraghav3 Sep 16, 2026
1fe56bf
Merge pull request #2307 from topcoder-platform/PM-6289
jmgasper Sep 16, 2026
6561907
chat recovery on timeouts
kkartunov Sep 16, 2026
0dab1d6
Merge pull request #2309 from topcoder-platform/chat-recovery
kkartunov Sep 16, 2026
6d15208
Merge pull request #2311 from topcoder-platform/HOTFIX_add-updatedDat…
vas3a Sep 16, 2026
1d7f90d
PM-6087 Display review config to submitters
himaniraghav3 Sep 16, 2026
fa4f316
Merge pull request #2313 from topcoder-platform/PM-6087
himaniraghav3 Sep 16, 2026
03041ac
PM-6347 Search sales dynamically and remove Apply button
jmgasper Sep 17, 2026
642c4c7
PM-6329 Show new showcase fields in preview and customer showcase
jmgasper Sep 17, 2026
265791d
Merge pull request #2314 from topcoder-platform/PM-6347
jmgasper Sep 17, 2026
8e77824
Merge pull request #2315 from topcoder-platform/PM-6329-1
jmgasper Sep 17, 2026
a8c2736
PM-6347 Serve Sales dev host from the shared root build
jmgasper Sep 17, 2026
1dd6eaf
PM-6087 Fix the review config for anonymous user
himaniraghav3 Sep 17, 2026
a8731c3
Merge pull request #2317 from topcoder-platform/PM-6087
himaniraghav3 Sep 17, 2026
7b76f96
PM-6364 Add Created/Close date range filters and totals to Sales
jmgasper Sep 17, 2026
1097352
fix(opportunities): render Marathon Match dashboards above the Highch…
jmgasper Sep 17, 2026
6a25f44
PM-6363: Import project details from a Salesforce opportunity
jmgasper Sep 17, 2026
011c198
Merge pull request #2320 from topcoder-platform/dashboard-fix
jmgasper Sep 17, 2026
6aea17e
Merge pull request #2321 from topcoder-platform/PM-6363
jmgasper Sep 17, 2026
a056ea5
Merge branch 'dev' into PM-6364
jmgasper Sep 17, 2026
34806a9
Merge pull request #2319 from topcoder-platform/PM-6364
jmgasper Sep 17, 2026
2d8cff9
Merge pull request #2316 from topcoder-platform/PM-6347-1
jmgasper Sep 17, 2026
82d25a8
PM-6329 Render uploaded images in showcase rich text sections
jmgasper Sep 17, 2026
46b51da
Merge pull request #2322 from topcoder-platform/PM-6329-file-upload
jmgasper Sep 17, 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
29 changes: 29 additions & 0 deletions docs/wallet-payment-installments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Wallet Admin installment amounts

Finance winnings can contain historical split payments. For PS-616, the two
installments are $2,760 and $920, while each installment repeats the full $3,680
in `totalAmount`.

Wallet Admin uses the winning-level `grossAmount` supplied by Finance for the
list, payment details summary and edit form. If that field is unavailable, it
sums the returned installment `grossAmount` values, rounded to cents. It never
adds the repeated `totalAmount` fields or billing markup. Finance returns all
current installment rows, ordered with installment 1 first. Separate payment
rows sharing an installment number are preserved because versions protect
in-place updates rather than identifying historical copies.

The General Info tab displays each installment's amount and status when there
is more than one installment. The amount is the historical gross face value,
including any cancelled installments; individual statuses remain visible in
the breakdown. Primary-installment status/date behavior and payment mutations
are unchanged. Member Wallet already displays the full `totalAmount`; its
withdrawal flow is unchanged by this display correction.

Deploy the companion Finance PS-616 change before the platform-ui change to
provide the complete installment data. An older API that only returns the first
installment cannot supply the omitted amount to the UI fallback. Finance CSV
exports use the same winning-level gross summary.

Regression tests cover the $2,760 + $920 split with and without the summary field,
the amount passed to the details modal, single-installment amounts with billing
markup, and paid/cancelled installment breakdowns.
57 changes: 57 additions & 0 deletions docs/work-showcase-win.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Work showcase and WIN (PM-6329)

The project editor and showcase form share `project.details.customer`, `smu`,
`smuOther`, and `dealCloseDate`. New showcases start with the current project
values. Existing showcases load those values from the post API response. Saving
a showcase updates its content and these project fields in one projects API
transaction, then refreshes the cached project.

Project metadata is optional in the project editor and required when saving the
showcase form. SMU accepts APMEA, Europe, Americas1, Americas2, or Others. Others
requires a custom value; selecting a standard SMU clears the stored custom value.
Deal Close Date is a `YYYY-MM-DD` calendar date and is not converted between timezones.

The showcase form follows this order:

1. Title
2. Type (required: Open Innovation, Private POD Delivery, Flexi-Talent Supply, AI Data Licensing)
3. Customer / SMU / Deal Close Date
4. Industry/Sector
5. Category/Technology
6. The Challenge
7. The Solution (the existing `content` API field)
8. Business Impact Realised
9. Key Win
10. Post Media
11. Topcoder Challenge Launched
12. Current Status (Delivered, In Delivery, On-Hold, Planned)
13. Owner (a name or handle)

The Challenge, The Solution and Business Impact Realised use the same Markdown
editor and sanitized rich-text preview. Key Win and Owner are single-line strings
of up to 255 characters. Existing title, taxonomy and solution requirements remain.

The Work preview modal and the customer portal showcase (`/showcase` cards and
post pages) share `ShowcasePostDetails`. Type, Customer, SMU (the custom value
for Others), Deal Close Date, Key Win, Current Status and Owner appear in a
labelled summary, followed by The Challenge, The Solution and Business Impact
Realised. Anything left blank is hidden, including empty media, challenge,
stats and skills sections. Showcase cards show Type, Customer and Current Status
when they are set.

**Send to WIN** is unchecked for new posts and retains the saved choice on edits.
Saving with the checkbox selected makes the record available to authorized callers
of `GET /v6/reports/WIN`. This is a pull integration: the success message confirms
availability to WIN. Validation and API failures keep the form open with an error.
Unchecking removes the post from the WIN report after saving. Archived posts and
posts belonging to deleted projects are excluded from the report.

## Deployment and verification

Deploy the PM-6329 projects API migration and application before enabling this UI;
deploy the reports API change for WIN callers. No new UI environment variables are needed.

Use `.nvmrc`, then `yarn lint`, `yarn run build`, and the tests for
`ProjectShowcasePage`, `ProjectEditorForm`, `project-editor.schema`, and
`showcase-post.schema` and `ShowcasePostDetails`. Manually check that editing metadata in either form is
reflected when reopening the other form, and that opting in/out changes the WIN report.
39 changes: 39 additions & 0 deletions infrastructure/sales/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Sales dev route

`sales.topcoder-dev.com` is served by the shared Platform UI root build that
CircleCI deploys on every `dev` merge, like Work, Customer and Reports. The build
detects the `sales` subdomain at runtime (`EnvironmentConfig.SUBDOMAIN`) and
renders the Sales page at `/`, so no Sales-specific build or upload is needed.

| Resource | Dev value |
| --- | --- |
| AWS account / region | `811668436784` / `us-east-1` |
| CloudFront distribution | `EFA7R1KH3UX5M` (`d10303ypawq4ub.cloudfront.net`) |
| S3 bucket | `platform-mvp.topcoder-dev.com` (root build, shared with other hosts) |
| Route 53 public hosted zone | `Z2CIRG3R0ZSGFQ` |
| Host | `sales.topcoder-dev.com` |
| Viewer request function | `platform-sales-dev-viewer-request` |

`dev-viewer-request.js` mirrors the live viewer-request function. It has no Sales
branch: Sales requests pass through unchanged and deep links fall back to the root
`/index.html` through the distribution's 403/404 custom error responses. The
function still routes the Contact and Accounts hosts to their isolated
`contact-app/` and `accounts-preferences-app/` shells. Re-read the live function
before publishing later changes to avoid overwriting newer routing, test with
`aws cloudfront test-function` against the DEVELOPMENT stage, then publish.

The distribution alias and Route 53 A/AAAA records for the Sales host point at the
same distribution; its ACM certificate covers `*.topcoder-dev.com`. Authentication
continues through the standard Platform UI login.

The former isolated `sales-app/` S3 prefix (PM-6343) is no longer referenced and
can be deleted once the shared root route is confirmed.

Deploy the reports-api-v6 PM-6343 branch to dev with its normal `dev-PM-6343` tag
workflow. Server-only Salesforce configuration is documented in that repo's
`SALES.md`. The Sales page uses `/v6/reports/sales`; WIN uses the separate
`/v6/reports/win/sales` endpoint and requires an M2M grant for `reports:sales`.

Rollback: remove the Sales alias/DNS entries, leaving shared settings intact.
Restore the prior Reports API ECS task revision if rolling back the backend.
No database migration is involved.
26 changes: 26 additions & 0 deletions infrastructure/sales/dev-viewer-request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Routes the Contact and Accounts dev hosts to their isolated S3 shells.
* Sales and every other Platform UI host use the shared root build deployed by CircleCI.
* @param {Object} event CloudFront viewer-request event containing the URI and Host header.
* @returns {Object} Request with an app-specific SPA fallback; assets and unrelated hosts are preserved.
* @throws No exceptions for valid CloudFront request events; performs no network I/O.
*/
function handler(event) {
var request = event.request;
var host = request.headers.host ? request.headers.host.value.toLowerCase() : '';
var prefix = '';
if (host === 'contact.topcoder-dev.com' || host === 'contact.topcoder-dev.com:443') {
prefix = '/contact-app/';
} else if (host === 'account-settings.topcoder-dev.com' || host === 'account-settings.topcoder-dev.com:443') {
// Keep shared assets available to Accounts pages opened before its isolated deployment.
if (request.uri.indexOf('/static/') === 0
|| ['/global.css', '/favicon.png', '/manifest.json', '/logo_512x512.png', '/robots.txt'].indexOf(request.uri) !== -1) {
return request;
}
prefix = '/accounts-preferences-app/';
}
if (prefix && request.uri.indexOf(prefix) !== 0) {
request.uri = prefix + 'index.html';
}
return request;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@
color: $black-100;
padding: $sp-2 $sp-4;

a {
color: inherit;
}

&:hover:not(.disabled) {
background: $turq-160;
background: var(--tc-2026-teal, #{$turq-160});
color: $tc-white;
cursor: pointer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@ const ManualAwardTab: FC<ManualAwardTabProps> = (props: ManualAwardTabProps) =>
<div className={styles.tabWrap}>
<h3>Manual Award</h3>
<div className={styles.manualFormWrap}>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Neque ullamcorper neque sed orci, enim amet, sed.
</p>
<div className={styles.manualForm}>
<InputHandleAutocomplete
label='Select Member'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,17 +181,17 @@
display: none;
left: 0;
min-width: 220px;
padding: 8px 0;
padding: 4px 0;
position: absolute;
top: calc(100% - 4px);
top: calc(100% - 1px);
z-index: 120;

li {
display: block;
font-weight: 400;
line-height: 22px;
margin-left: 0;
padding: 10px 16px;
padding: 2px 16px;
white-space: nowrap;

&:hover,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
@import '@libs/ui/styles/includes';

.page {
box-sizing: border-box;
color: #0a0a0a;
display: flex;
flex-direction: column;
font-family: 'Nunito Sans', sans-serif;
padding: 8px 0 0;
min-height: calc(#{$content-height} - 180px);
padding: 8px 0 $sp-10;

h1 {
color: #151515;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
@import url('https://fonts.googleapis.com/css2?family=Figtree:wght@400;500;600;700;800&family=Nunito+Sans:wght@400;500;600;700&display=swap');

.page {
box-sizing: border-box;
color: $tc-2026-body;
display: flex;
flex-direction: column;
font-family: $tc-2026-font-body;
padding: 8px 0 0;
min-height: calc(#{$content-height} - 180px);
padding: 8px 0 $sp-8;

h1 {
color: $tc-2026-ink;
Expand All @@ -34,34 +36,42 @@
}

.layout {
align-items: stretch;
display: flex;
flex: 1 1 auto;
gap: 16px;
height: 70vh;
height: calc(#{$content-height} - 320px);
margin-top: 20px;
margin-bottom: 28px;
min-height: 480px;
width: 100%;

@include ltesm {
flex-direction: column;
height: auto;
min-height: 560px;
}
}

.sidebar {
display: flex;
flex: 0 0 133px;
flex-direction: column;
max-width: 133px;
min-height: 0;

@include ltesm {
flex: 0 0 auto;
max-height: 160px;
max-width: 100%;
min-height: 160px;
}
}

.chatPanel {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-height: 0;
min-width: 0;
position: relative;
width: 100%;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,28 @@
white-space: pre;
}

.recoveryBanner {
align-items: center;
background-color: $tc-2026-canvas;
border-top: 1px solid $tc-2026-border;
color: $tc-2026-muted;
display: flex;
flex: 0 0 auto;
font-size: 12px;
gap: 8px;
padding: 8px $sp-4;
}

.recoveryBannerError {
color: $tc-2026-danger;
}

.recoveryDots {
.thinkingDot {
background-color: currentcolor;
}
}

.composer {
align-items: flex-end;
border-top: 1px solid $tc-2026-border;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DefaultChatTransport, UIMessage } from 'ai'
import { FC, useCallback, useMemo } from 'react'
import { FC, useCallback, useEffect, useMemo, useRef } from 'react'
import classNames from 'classnames'

import { useChat } from '@ai-sdk/react'
Expand All @@ -13,7 +13,13 @@ import {
import { useAISDKRuntime } from '@assistant-ui/react-ai-sdk'
import { IconOutline } from '~/libs/ui'

import { authFetch, CHAT_ENDPOINT_URL } from '../../lib'
import {
authFetch,
CHAT_ENDPOINT_URL,
useStreamRecovery,
UseStreamRecoveryResult,
withStallWatchdog,
} from '../../lib'

import MarkdownText from './MarkdownText'
import styles from './ChatThread.module.scss'
Expand Down Expand Up @@ -151,7 +157,7 @@ const ChatThread: FC<ChatThreadProps> = props => {
thread: props.threadId,
},
},
fetch: authFetch,
fetch: withStallWatchdog(authFetch),
}),
[props.resourceId, props.threadId],
)
Expand All @@ -167,6 +173,32 @@ const ChatThread: FC<ChatThreadProps> = props => {
transport,
})

const handleRecovered = useCallback((messages: UIMessage[]) => {
chat.setMessages(messages)
props.onThreadActivity?.()
}, [chat, props.onThreadActivity])

const { startRecovery, status: recoveryStatus }: UseStreamRecoveryResult
= useStreamRecovery(props.threadId, handleRecovered)

// `useChat`'s `onError` fires once per request, so re-wiring it here as
// a plain option risks missing an error that lands before this render
// commits. Watching `chat.error` instead catches it regardless of when
// it's set, and `handledErrorRef` stops a still-set error from
// re-triggering recovery on unrelated re-renders.
const handledErrorRef = useRef<Error | undefined>(undefined)

useEffect(() => {
if (chat.error && chat.error !== handledErrorRef.current) {
handledErrorRef.current = chat.error
// Mirrors `toChatUIMessages`' empty-parts filter, so this lines
// up with what the recovered thread history will look like.
const baselineMessageCount = chat.messages.filter(message => message.parts.length > 0).length

startRecovery(baselineMessageCount)
}
}, [chat.error, chat.messages, startRecovery])

const runtime = useAISDKRuntime(chat)

return (
Expand Down Expand Up @@ -204,6 +236,19 @@ const ChatThread: FC<ChatThreadProps> = props => {
/>
</ThreadPrimitive.Viewport>

{recoveryStatus === 'recovering' && (
<div className={styles.recoveryBanner}>
<ThinkingDots className={styles.recoveryDots} />
Still working on that — reconnecting to fetch the response…
</div>
)}

{recoveryStatus === 'timed-out' && (
<div className={classNames(styles.recoveryBanner, styles.recoveryBannerError)}>
This is taking longer than expected. Refresh the page to check whether a response arrived.
</div>
)}

<ComposerPrimitive.Root className={styles.composer}>
<ComposerPrimitive.Input
className={styles.input}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ export * from './chat.service'
export * from './constants'
export * from './convert-messages'
export * from './models'
export * from './stream-watchdog'
export * from './use-stream-recovery'
Loading
Loading