Skip to content

Commit a08b4da

Browse files
authored
Merge pull request #1117 from topcoder-platform/dev
[PROD RELEASE] - Copilot Portal
2 parents a39a297 + b3cc77f commit a08b4da

File tree

9 files changed

+163
-176
lines changed

9 files changed

+163
-176
lines changed

src/apps/copilots/src/models/CopilotOpportunity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ export interface CopilotOpportunity {
2222
startDate: Date,
2323
tzRestrictions: 'yes' | 'no',
2424
createdAt: Date,
25-
members: Array<number>,
25+
canApplyAsCopilot: boolean,
2626
}

src/apps/copilots/src/pages/copilot-opportunity-details/apply-opportunity-modal/ApplyOpportunityModal.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ const ApplyOpportunityModal: FC<ApplyOpportunityModalProps> = props => {
4444
size='lg'
4545
title={
4646
success
47-
? `Your Application for ${props.projectName} Has Been Received!`
48-
: `Confirm Your Copilot Application for ${props.projectName}`
47+
? 'Your Application Has Been Received!'
48+
: 'Confirm Your Copilot Application'
4949
}
5050
buttons={
5151
!success ? (
@@ -62,21 +62,21 @@ const ApplyOpportunityModal: FC<ApplyOpportunityModalProps> = props => {
6262
<div className={styles.info}>
6363
{
6464
success
65-
? `We appreciate the time and effort you've taken to apply
66-
for this exciting opportunity. Our team is committed
67-
to providing a seamless and efficient process to ensure a
68-
great experience for all copilots. We will review your application
69-
within short time.`
65+
? `Thank you for taking the time to apply for this exciting opportunity.
66+
We truly value your interest and effort.
67+
Your application will be reviewed promptly.`
7068
: `We're excited to see your interest in joining our team as a copilot
71-
for the ${props.projectName} project! Before we proceed, we want to
69+
for the "${props.projectName}" project! Before we proceed, we want to
7270
ensure that you have carefully reviewed the project requirements and
73-
are committed to meeting them.`
71+
are committed to meeting them. Please write below the reason(s)
72+
why you believe you're a good fit for this project
73+
(e.g., previous experience, availability, etc.).`
7474
}
7575
</div>
7676
{
7777
!success && (
7878
<InputTextarea
79-
name='Notes'
79+
name='Reason'
8080
onChange={onChange}
8181
value={notes}
8282
error={error}

src/apps/copilots/src/pages/copilot-opportunity-details/index.tsx

Lines changed: 125 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from 'react'
1313
import { useLocation, useNavigate, useParams } from 'react-router-dom'
1414
import { mutate } from 'swr'
15+
import { toast } from 'react-toastify'
1516
import moment from 'moment'
1617

1718
import {
@@ -28,13 +29,15 @@ import { textFormatDateLocaleShortString } from '~/libs/shared'
2829

2930
import { CopilotApplication } from '../../models/CopilotApplication'
3031
import {
32+
cancelCopilotOpportunity,
3133
copilotBaseUrl,
3234
CopilotOpportunityResponse,
3335
useCopilotApplications,
3436
useCopilotOpportunity,
3537
} from '../../services/copilot-opportunities'
3638
import { FormattedMembers, useMembers } from '../../services/members'
3739
import { copilotRoutesMap } from '../../copilots.routes'
40+
import { ProjectType } from '../../constants'
3841

3942
import { ApplyOpportunityModal } from './apply-opportunity-modal'
4043
import {
@@ -103,7 +106,7 @@ const CopilotOpportunityDetails: FC<{}> = () => {
103106

104107
const onApplied: () => void = useCallback(() => {
105108
mutate(`${copilotBaseUrl}/copilots/opportunity/${opportunityId}/applications`)
106-
mutate(`${copilotBaseUrl}/copilots/opportunity/${opportunityId}`)
109+
mutate(`${copilotBaseUrl}/copilot/opportunity/${opportunityId}`)
107110
}, [])
108111

109112
const onCloseApplyModal: () => void = useCallback(() => {
@@ -119,13 +122,42 @@ const CopilotOpportunityDetails: FC<{}> = () => {
119122
)
120123
}
121124

125+
async function cancelCopilotOpportunityHandler(): Promise<void> {
126+
if (opportunityId) {
127+
await cancelCopilotOpportunity(opportunityId)
128+
mutate(`${copilotBaseUrl}/copilots/opportunity/${opportunityId}/applications`)
129+
mutate(`${copilotBaseUrl}/copilot/opportunity/${opportunityId}`)
130+
toast.success('Canceled copilot opportunity successfully')
131+
}
132+
133+
}
134+
122135
const applyCopilotOpportunityButton: ButtonProps = {
123136
label: 'Apply as Copilot',
124137
onClick: () => setShowApplyOpportunityModal(true),
125138
}
126139

140+
const cancelCopilotOpportunityButton: ButtonProps = {
141+
label: 'Cancel opportunity',
142+
onClick: cancelCopilotOpportunityHandler,
143+
}
144+
127145
const application = copilotApplications && copilotApplications[0]
128-
const isAlreadyMemberOfTheProject = profile && opportunity?.members?.includes(profile.userId)
146+
147+
const getOpportunityType = (type: string): ProjectType => {
148+
switch (type) {
149+
case 'ai':
150+
return ProjectType.ai
151+
case 'datascience':
152+
return ProjectType.datascience
153+
case 'dev':
154+
return ProjectType.developement
155+
case 'design':
156+
return ProjectType.design
157+
default:
158+
return ProjectType.qa
159+
}
160+
}
129161

130162
return (
131163
<ContentLayout
@@ -135,7 +167,11 @@ const CopilotOpportunityDetails: FC<{}> = () => {
135167
&& copilotApplications
136168
&& copilotApplications.length === 0
137169
&& opportunity?.status === 'active'
138-
&& !isAlreadyMemberOfTheProject ? applyCopilotOpportunityButton : undefined
170+
&& opportunity?.canApplyAsCopilot ? applyCopilotOpportunityButton : undefined
171+
}
172+
secondaryButtonConfig={
173+
opportunity?.status === 'active'
174+
&& isAdminOrPM ? cancelCopilotOpportunityButton : undefined
139175
}
140176
infoComponent={(isCopilot && !(copilotApplications
141177
&& copilotApplications.length === 0
@@ -156,94 +192,101 @@ const CopilotOpportunityDetails: FC<{}> = () => {
156192
{isValidating && !showNotFound && (
157193
<LoadingSpinner />
158194
) }
159-
<h1 className={styles.header}>
160-
{opportunity?.projectName}
161-
</h1>
162-
<div className={styles.infoRow}>
163-
<div className={styles.infoColumn}>
164-
<IconOutline.ClipboardCheckIcon className={styles.icon} />
165-
<div className={styles.infoText}>
166-
<span className={styles.infoHeading}>Status</span>
167-
<span className={styles.infoValue}>{opportunity?.status}</span>
195+
<div className={styles.wrapper}>
196+
<h1 className={styles.header}>
197+
{opportunity?.projectName}
198+
</h1>
199+
<div className={styles.infoRow}>
200+
<div className={styles.infoColumn}>
201+
<IconOutline.ClipboardCheckIcon className={styles.icon} />
202+
<div className={styles.infoText}>
203+
<span className={styles.infoHeading}>Status</span>
204+
<span className={styles.infoValue}>{opportunity?.status}</span>
205+
</div>
168206
</div>
169-
</div>
170-
<div className={styles.infoColumn}>
171-
<IconOutline.PlayIcon className={styles.icon} />
172-
<div className={styles.infoText}>
173-
<span className={styles.infoHeading}>Start Date</span>
174-
<span className={styles.infoValue}>
175-
{moment(opportunity?.startDate)
176-
.format('MMM D, YYYY')}
177-
178-
</span>
207+
<div className={styles.infoColumn}>
208+
<IconOutline.PlayIcon className={styles.icon} />
209+
<div className={styles.infoText}>
210+
<span className={styles.infoHeading}>Start Date</span>
211+
<span className={styles.infoValue}>
212+
{moment(opportunity?.startDate)
213+
.format('MMM D, YYYY')}
214+
215+
</span>
216+
</div>
179217
</div>
180-
</div>
181-
<div className={styles.infoColumn}>
182-
<IconOutline.CalendarIcon className={styles.icon} />
183-
<div className={styles.infoText}>
184-
<span className={styles.infoHeading}>Duration</span>
185-
<span className={styles.infoValue}>
186-
{opportunity?.numWeeks}
187-
{' '}
188-
weeks
189-
</span>
218+
<div className={styles.infoColumn}>
219+
<IconOutline.CalendarIcon className={styles.icon} />
220+
<div className={styles.infoText}>
221+
<span className={styles.infoHeading}>Duration</span>
222+
<span className={styles.infoValue}>
223+
{opportunity?.numWeeks}
224+
{' '}
225+
weeks
226+
</span>
227+
</div>
190228
</div>
191-
</div>
192-
<div className={styles.infoColumn}>
193-
<IconOutline.ClockIcon className={styles.icon} />
194-
<div className={styles.infoText}>
195-
<span className={styles.infoHeading}>Hours</span>
196-
<span className={styles.infoValue}>
197-
{opportunity?.numHoursPerWeek}
198-
{' '}
199-
hours/week
200-
</span>
229+
<div className={styles.infoColumn}>
230+
<IconOutline.ClockIcon className={styles.icon} />
231+
<div className={styles.infoText}>
232+
<span className={styles.infoHeading}>Hours</span>
233+
<span className={styles.infoValue}>
234+
{opportunity?.numHoursPerWeek}
235+
{' '}
236+
hours/week
237+
</span>
238+
</div>
201239
</div>
202-
</div>
203-
<div className={styles.infoColumn}>
204-
<IconOutline.CogIcon className={styles.icon} />
205-
<div className={styles.infoText}>
206-
<span className={styles.infoHeading}>Type</span>
207-
<span className={styles.infoValue}>{opportunity?.type}</span>
240+
<div className={styles.infoColumn}>
241+
<IconOutline.CogIcon className={styles.icon} />
242+
<div className={styles.infoText}>
243+
<span className={styles.infoHeading}>Type</span>
244+
<span
245+
className={styles.infoValue}
246+
>
247+
{opportunity?.type && getOpportunityType(opportunity?.type)}
248+
</span>
249+
</div>
208250
</div>
209-
</div>
210-
<div className={styles.infoColumn}>
211-
<IconOutline.GlobeAltIcon className={styles.icon} />
212-
<div className={styles.infoText}>
213-
<span className={styles.infoHeading}>Working Hours</span>
214-
<span className={styles.infoValue}>{opportunity?.tzRestrictions}</span>
251+
<div className={styles.infoColumn}>
252+
<IconOutline.GlobeAltIcon className={styles.icon} />
253+
<div className={styles.infoText}>
254+
<span className={styles.infoHeading}>Working Hours</span>
255+
<span className={styles.infoValue}>{opportunity?.tzRestrictions}</span>
256+
</div>
215257
</div>
216258
</div>
217-
</div>
218-
{
219-
initialized && (
220-
<TabsNavbar
221-
defaultActive={activeTab}
222-
onChange={handleTabChange}
223-
tabs={getCopilotDetailsTabsConfig(isAdminOrPM)}
259+
{
260+
initialized && (
261+
<TabsNavbar
262+
defaultActive={activeTab}
263+
onChange={handleTabChange}
264+
tabs={getCopilotDetailsTabsConfig(isAdminOrPM)}
265+
/>
266+
)
267+
}
268+
{activeTab === CopilotDetailsTabViews.details && <OpportunityDetails opportunity={opportunity} />}
269+
{activeTab === CopilotDetailsTabViews.applications && isAdminOrPM && opportunity && (
270+
<CopilotApplications
271+
copilotApplications={copilotApplications}
272+
opportunity={opportunity}
273+
members={members}
224274
/>
225-
)
226-
}
227-
{activeTab === CopilotDetailsTabViews.details && <OpportunityDetails opportunity={opportunity} />}
228-
{activeTab === CopilotDetailsTabViews.applications && isAdminOrPM && opportunity && (
229-
<CopilotApplications
230-
copilotApplications={copilotApplications}
231-
opportunity={opportunity}
232-
members={members}
233-
/>
234-
)}
275+
)}
276+
277+
{
278+
showApplyOpportunityModal
279+
&& opportunity && (
280+
<ApplyOpportunityModal
281+
copilotOpportunityId={opportunity?.id}
282+
onClose={onCloseApplyModal}
283+
projectName={opportunity?.projectName}
284+
onApplied={onApplied}
285+
/>
286+
)
287+
}
288+
</div>
235289

236-
{
237-
showApplyOpportunityModal
238-
&& opportunity && (
239-
<ApplyOpportunityModal
240-
copilotOpportunityId={opportunity?.id}
241-
onClose={onCloseApplyModal}
242-
projectName={opportunity?.projectName}
243-
onApplied={onApplied}
244-
/>
245-
)
246-
}
247290
</ContentLayout>
248291
)
249292
}

src/apps/copilots/src/pages/copilot-opportunity-details/styles.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
@import '@libs/ui/styles/includes';
22

3+
.wrapper {
4+
min-height: 800px;
5+
}
6+
37
.header {
48
display: flex;
59
align-items: center;

src/apps/copilots/src/pages/copilot-opportunity-details/tabs/copilot-applications/CopilotApplicationAction.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const CopilotApplicationAction = (
1515
): JSX.Element => {
1616
const { opportunityId }: {opportunityId?: string} = useParams<{ opportunityId?: string }>()
1717
const isInvited = useMemo(
18-
() => allCopilotApplications.findIndex(item => item.status === CopilotApplicationStatus.INVITED) > -1,
18+
() => allCopilotApplications
19+
&& allCopilotApplications.findIndex(item => item.status === CopilotApplicationStatus.INVITED) > -1,
1920
[allCopilotApplications],
2021
)
2122
const onClick = useCallback(async () => {

src/apps/copilots/src/pages/copilot-opportunity-details/tabs/opportunity-details/OpportunityDetails.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@ const OpportunityDetails: FC<{
1111
<div>
1212
<h2 className={styles.subHeading}> Required skills </h2>
1313
<div className={styles.skillsContainer}>
14-
{props.opportunity?.skills.map((skill: any) => (
15-
<div key={skill.id} className={styles.skillPill}>
16-
{skill.name}
17-
</div>
18-
))}
14+
{props.opportunity?.skills.map(item => item.name)
15+
.join(',')}
1916
</div>
2017
<h2 className={styles.subHeading}> Description </h2>
2118
<p>

src/apps/copilots/src/pages/copilot-opportunity-list/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ const tableColumns: TableColumn<CopilotOpportunity>[] = [
8484
propertyName: 'startDate',
8585
type: 'date',
8686
},
87+
{
88+
label: 'Duration(Weeks)',
89+
propertyName: 'numWeeks',
90+
type: 'number',
91+
},
8792
{
8893
label: 'Complexity',
8994
propertyName: 'complexity',

0 commit comments

Comments
 (0)