diff --git a/src-ts/tools/work/work-lib/index.ts b/src-ts/tools/work/work-lib/index.ts index a03454165..f0cda9934 100644 --- a/src-ts/tools/work/work-lib/index.ts +++ b/src-ts/tools/work/work-lib/index.ts @@ -1,3 +1,4 @@ +export * from './work-constants' export * from './work-images' export * from './work-pdfs' export * from './work-provider' diff --git a/src-ts/tools/work/work-lib/work-constants/index.ts b/src-ts/tools/work/work-lib/work-constants/index.ts new file mode 100644 index 000000000..05f2a6a0c --- /dev/null +++ b/src-ts/tools/work/work-lib/work-constants/index.ts @@ -0,0 +1 @@ +export { default as WorkStrings } from './strings.json' diff --git a/src-ts/tools/work/work-lib/work-constants/strings.json b/src-ts/tools/work/work-lib/work-constants/strings.json new file mode 100644 index 000000000..40ac3840d --- /dev/null +++ b/src-ts/tools/work/work-lib/work-constants/strings.json @@ -0,0 +1,5 @@ +{ + "INFO_NOT_PROVIDED": "Information not provided", + "MARKDOWN_SUBMISSION_GUIDELINES": "## Final Submission Guidelines \n\n Please submit a zip file containing your analysis/solution.", + "NOT_PROVIDED": "Not provided" +} diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/index.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/index.ts index 75f476892..4d8857a90 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/index.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/index.ts @@ -2,6 +2,7 @@ export * from './work-factory' export { type Challenge, ChallengeMetadataName, + ChallengeMetadataTitle, type Work, workBugHuntConfig, workPriceData, @@ -27,4 +28,5 @@ export { getGroupedByStatus as workGetGroupedByStatus, getPricesConfig as workGetPricesConfig, getStatusFilter as workGetStatusFilter, + updateAsync as workUpdateAsync, } from './work.functions' diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/index.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/index.ts index 035bbfc35..6aa17fe5c 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/index.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/index.ts @@ -2,6 +2,7 @@ export * from './work-solution.model' export { create as workFactoryCreate, buildCreateBody as workFactoryBuildCreateBody, + buildUpdateBody as workFactoryBuildUpdateBody, getStatus as workFactoryGetStatus, mapFormData as workFactoryMapFormData, } from './work.factory' diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/work.factory.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/work.factory.ts index c7e223d4e..f5d9e370a 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/work.factory.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/work.factory.ts @@ -1,15 +1,20 @@ import moment from 'moment' +import { WorkStrings } from '../../../work-constants' import { Challenge, ChallengeCreateBody, ChallengeMetadata, ChallengeMetadataName, + ChallengeMetadataTitle, ChallengePhase, ChallengePhaseName, + ChallengeUpdateBody, Work, WorkPrice, + WorkPriceBreakdown, WorkPricesType, + WorkPrize, WorkProgress, WorkProgressStep, WorkStatus, @@ -74,7 +79,7 @@ export function buildCreateBody(workTypeConfig: WorkTypeConfig): ChallengeCreate } return { - description: 'Information not provided', + description: WorkStrings.INFO_NOT_PROVIDED, discussions: [ { name: 'new-self-service-project', @@ -97,6 +102,69 @@ export function buildCreateBody(workTypeConfig: WorkTypeConfig): ChallengeCreate } } +export function buildUpdateBody(workTypeConfig: WorkTypeConfig, challenge: Challenge, formData: any): ChallengeUpdateBody { + + const type: WorkType = workTypeConfig.type + + const intakeForm: ChallengeMetadata | undefined = findMetadata(challenge, ChallengeMetadataName.intakeForm) || undefined + const form: IntakeForm = !!intakeForm?.value ? JSON.parse(intakeForm.value)?.form : {} + form.basicInfo = formData + + // TODO: Add the progress.currentStep to form to determine if it's in the review phase (review page) + // or not. The legacy intakes use currentStep 7 for review and 5 for taking the user to the login step + // as those numbers map to the routes configured for each work type (see IntakeForm.jsx for an example). + // We can probably clean that up as we don't need that many routes + + // --- Build Metadata --- // + const intakeMetadata: Array = [ + { + name: ChallengeMetadataName.intakeForm, + value: JSON.stringify({ form }), + }, + ] + + Object.keys(formData).forEach((key) => { + intakeMetadata.push({ + name: ChallengeMetadataName[key as keyof typeof ChallengeMetadataName], + value: formData[key] || '', + }) + }) + // ---- End Build Metadata ---- // + + // --- Build the Markdown string that gets displayed in Work Manager app and others --- // + const templateString: Array = [] + + const data: ReadonlyArray = mapFormData( + type, + formData + ) + + data.forEach((formDetail) => { + if (Object.keys(formDetail).length <= 0) { return } + + const formattedValue: string = formatFormDataOption(formDetail.value) + templateString.push(`### ${formDetail.title}\n\n${formattedValue}\n\n`) + }) + + if (getTypeCategory(type) === WorkTypeCategory.data) { + templateString.push( + WorkStrings.MARKDOWN_SUBMISSION_GUIDELINES + ) + } + // ---- End Build Markdown string ---- // + + const body: ChallengeUpdateBody = { + description: templateString.join(''), + id: challenge.id, + metadata: intakeMetadata, + name: formData.projectTitle, + phases: workTypeConfig.timeline, + prizeSets: getPrizes(workTypeConfig), + } + + return body +} + export function getStatus(challenge: Challenge): WorkStatus { switch (challenge.status) { @@ -135,11 +203,50 @@ export function mapFormData(type: string, formData: any): ReadonlyArray { + return [ + { + key: ChallengeMetadataName.projectTitle, + title: ChallengeMetadataTitle.projectTitle, + value: formData.projectTitle, + }, + { + key: ChallengeMetadataName.websiteURL, + title: ChallengeMetadataTitle.websiteURL, + value: formData.websiteURL, + }, + { + key: ChallengeMetadataName.goals, + title: ChallengeMetadataTitle.bugHuntGoals, + value: formData.goals, + }, + { + key: ChallengeMetadataName.featuresToTest, + title: ChallengeMetadataTitle.featuresToTest, + value: formData.featuresToTest, + }, + { + key: ChallengeMetadataName.deliveryType, + title: ChallengeMetadataTitle.bugDeliveryType, + value: `${formData.deliveryType}${formData.repositoryLink ? ': ' + formData.repositoryLink : ''}`, + }, + { + key: ChallengeMetadataName.additionalInformation, + title: ChallengeMetadataTitle.additionalInformation, + value: formData.additionalInformation, + }, + { + key: ChallengeMetadataName.packageType, + title: ChallengeMetadataTitle.bugHuntPackage, + value: formData.packageType, + }, + ] +} function buildFormDataData(formData: any): ReadonlyArray { return [ @@ -207,41 +314,6 @@ function buildFormDataDesign(formData: any): ReadonlyArray { ] } -function buildFormBughunt(formData: any): ReadonlyArray { - - return [ - { - key: 'projectTitle', - ...formData.projectTitle, - }, - { - key: 'websiteURL', - ...formData.websiteURL, - }, - { - key: 'goals', - ...formData.goals, - }, - { - key: 'features', - ...formData.features, - }, - { - key: 'bugDelivery', - title: 'Bug Delivery', - value: `[${formData.deliveryType.value}]: ${formData.deliveryUrl.value}`, - }, - { - key: 'additionalInformation', - ...formData.additionalInformation, - }, - { - key: 'packageType', - ...formData.packageType, - }, - ] -} - function buildFormDataFindData(formData: any): ReadonlyArray { const isPrimaryDataChallengeOther: boolean = formData.primaryDataChallenge?.value === 3 return [ @@ -289,6 +361,22 @@ function buildFormDataProblem(formData: any): ReadonlyArray { ] } +/** + * This function checks if the param provided is empty based on its type + * The param is empty if: is falsey || is an empty string || is an empty array || is an empty object + * This is used for determining if a form field entry is emtpy after being formatted for display + */ +function checkFormDataOptionEmpty(detail: any): boolean { + return ( + !detail || + (typeof detail === 'string' && detail.trim().length === 0) || + (Array.isArray(detail) && detail.length === 0) || + (typeof detail === 'object' && + Object.values(detail).filter((val: any) => val && val.trim().length !== 0) + .length === 0) + ) +} + function findMetadata(challenge: Challenge, metadataName: ChallengeMetadataName): ChallengeMetadata | undefined { return challenge.metadata?.find((item: ChallengeMetadata) => item.name === metadataName) } @@ -326,6 +414,27 @@ function findPhase(challenge: Challenge, phases: Array): ChallengePhase return phase } +function formatFormDataOption(detail: Array | { [key: string]: any }): string { + const noInfoProvidedText: string = WorkStrings.NOT_PROVIDED + const isEmpty: boolean = checkFormDataOptionEmpty(detail) + + if (isEmpty) { + return noInfoProvidedText + } + if (Array.isArray(detail)) { + return detail.join('\n\n') + } + if (typeof detail === 'object') { + return Object.keys(detail) + .map((key: string) => { + const value: string = detail[key] || noInfoProvidedText + return `${key}: ${value}` + }) + .join('\n\n') + } + return detail +} + function getCost(challenge: Challenge, priceConfig: WorkPrice, type: WorkType): number | undefined { switch (type) { @@ -357,6 +466,33 @@ function getDescription(challenge: Challenge, type: WorkType): string | undefine } } +function getPrizes(workTypeConfig: WorkTypeConfig): Array { + const priceConfig: WorkPriceBreakdown = + workTypeConfig.usePromo && workTypeConfig.promo + ? workTypeConfig.promo + : workTypeConfig.base + + return [ + { + description: 'Challenge Prizes', + prizes: priceConfig.placementDistributions.map((percentage) => ({ + type: 'USD', + value: Math.round(percentage * priceConfig.price), + })), + type: 'placement', + }, + { + description: 'Reviewer Payment', + prizes: + priceConfig.reviewerDistributions.map((percentage) => ({ + type: 'USD', + value: Math.round(percentage * priceConfig.price), + })), + type: 'reviewer', + }, + ] +} + function getProgress(challenge: Challenge, workStatus: WorkStatus): WorkProgress { const steps: ReadonlyArray = [ diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge-metadata-name.enum.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge-metadata-name.enum.ts index adf7fbde8..f997cd19b 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge-metadata-name.enum.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge-metadata-name.enum.ts @@ -1,8 +1,15 @@ export enum ChallengeMetadataName { + additionalInformation = 'additionalInformation', + deliveryType = 'deliveryType', deviceCount = 'basicInfo.numberOfDevices', description = 'websitePurpose.description', + featuresToTest = 'featuresToTest', feedback = 'customerFeedback', goals = 'goals', intakeForm = 'intake-form', pageCount = 'basicInfo.numberOfPages', + packageType = 'packageType', + projectTitle = 'projectTitle', + repositoryLink = 'repositoryLink', + websiteURL = 'websiteURL', } diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge-metadata-title.enum.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge-metadata-title.enum.ts new file mode 100644 index 000000000..8f8491ef7 --- /dev/null +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge-metadata-title.enum.ts @@ -0,0 +1,9 @@ +export enum ChallengeMetadataTitle { + additionalInformation = 'Additional Information', + bugDeliveryType = 'Bug Delivery', + bugHuntGoals = 'Bug Hunt Goals', + bugHuntPackage = 'Bug Hunt Package', + featuresToTest = 'Features to Test', + projectTitle = 'Project Title', + websiteURL = 'Website URL', +} diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge.model.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge.model.ts index 6fbc17076..8a8d12ceb 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge.model.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/challenge.model.ts @@ -1,18 +1,21 @@ import { ChallengeMetadata } from './challenge-metadata.model' import { ChallengePhase } from './challenge-phase' import { ChallengeTag } from './challenge-tag.enum' +import { WorkPrize } from './work-prize.model' +import { WorkTimelinePhase } from './work-timeline-phase.model' export interface Challenge { created: string description: string discussions?: Array<{ [key: string]: string }> id: string - legacy?: { [key: string]: any }, + legacy?: { [key: string]: any } metadata: Array name: string numOfRegistrants?: number numOfSubmissions?: number phases: Array + prizeSets?: Array status: string tags: Array timelineTemplateId?: string @@ -33,3 +36,14 @@ export type ChallengeCreateBody = Pick< 'trackId' | 'typeId' > + +export type ChallengeUpdateBody = Pick< + Challenge, + 'description' | + 'id' | + 'metadata' | + 'name' | + 'prizeSets' +> & { + phases: ReadonlyArray +} diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/index.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/index.ts index f87aec9c1..9975ce3bd 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/index.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/index.ts @@ -1,4 +1,5 @@ export * from './challenge-metadata-name.enum' +export * from './challenge-metadata-title.enum' export * from './challenge-metadata.model' export * from './challenge-phase' export * from './challenge-phase-name.enum' @@ -14,6 +15,7 @@ export { problem as workPriceProblem, getPricesConfig as workGetPricesConfig, } from './work-prices.store' +export * from './work-prize.model' export * from './work-progress.model' export * from './work-progress-step.model' export * from './work-status-filter.enum' @@ -31,4 +33,5 @@ export { deleteAsync as workStoreDeleteAsync, getAsync as workStoreGetAsync, getFilteredByStatus as workStoreGetFilteredByStatus, + updateAsync as workStoreUpdateAsync, } from './work.store' diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-price.model.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-price.model.ts index fe280c99f..23513afad 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-price.model.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-price.model.ts @@ -1,17 +1,13 @@ export interface WorkPrice { - base: number, + base: WorkPriceBreakdown, getPrice: (price: WorkPrice, pageCount?: number, deviceCount?: number) => number, - payments?: { - base?: { - prizes: ReadonlyArray, - reviewers: ReadonlyArray, - }, - promo?: { - prizes: ReadonlyArray, - reviewers: ReadonlyArray, - }, - }, perPage?: number, - promo?: number, + promo?: WorkPriceBreakdown, usePromo?: boolean, } + +export interface WorkPriceBreakdown { + placementDistributions: ReadonlyArray, + price: number, + reviewerDistributions: ReadonlyArray, +} diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-prices.config.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-prices.config.ts index 86d244784..76fbc3a6e 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-prices.config.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-prices.config.ts @@ -1,66 +1,109 @@ import { WorkPrice } from './work-price.model' import { WorkPricesType } from './work-prices-type.model' +import { WorkPrize } from './work-prize.model' import { WorkType } from './work-type.enum' export const WorkPricesConfig: WorkPricesType = { // TODO: get real values for bug hunt [WorkType.bugHunt]: { - base: 2, + base: { + placementDistributions: [0.2609, 0.2174, 0.1304], + price: 1599, + reviewerDistributions: [0.0435, 0.0435], + }, getPrice: getPriceDefault, - payments: { - base: { - prizes: [0.2609, 0.2174, 0.1304], - reviewers: [0.0435, 0.0435], - }, - promo: { - prizes: [0.348, 0.29, 0.174], - reviewers: [0.058, 0.058], - }, + promo: { + placementDistributions: [0.348, 0.29, 0.174], + price: 1599, + reviewerDistributions: [0.058, 0.058], }, - promo: 1, usePromo: true, }, [WorkType.data]: { - base: 799, + base: { + placementDistributions: [0.4, 0.3333, 0.1333], + price: 799, + reviewerDistributions: [0.0667, 0.0667], + }, getPrice: getPriceDefault, - promo: 599, + promo: { + placementDistributions: [0.4, 0.3333, 0.1333], + price: 599, + reviewerDistributions: [0.0667, 0.0667], + }, usePromo: true, }, [WorkType.design]: { - base: 499, + base: { + placementDistributions: [0.4, 0.3333, 0.1333], + price: 499, + reviewerDistributions: [0.0667, 0.0667], + }, getPrice: getPriceDefault, perPage: 99, - promo: 299, + promo: { + placementDistributions: [0.4, 0.3333, 0.1333], + price: 299, + reviewerDistributions: [0.0667, 0.0667], + }, usePromo: false, }, [WorkType.designLegacy]: { - base: 398, + base: { + placementDistributions: [0.5, 0.2, 0.1], + price: 398, + reviewerDistributions: [0.1, 0.1], + }, getPrice: getPriceDesignLegacy, perPage: 99, - promo: 100, + promo: { + placementDistributions: [0.5, 0.2, 0.1], + price: 100, + reviewerDistributions: [0.1, 0.1], + }, usePromo: true, }, [WorkType.findData]: { - base: 399, + base: { + placementDistributions: [0.2609, 0.2174, 0.1304], + price: 399, + reviewerDistributions: [0.0435, 0.0435], + }, getPrice: getPriceDefault, - promo: 299, + promo: { + placementDistributions: [0.348, 0.29, 0.174], + price: 299, + reviewerDistributions: [0.058, 0.058], + }, usePromo: true, }, [WorkType.problem]: { - base: 999, + base: { + placementDistributions: [0.375, 0.3125, 0.125], + price: 999, + reviewerDistributions: [0.0625, 0.0625], + }, getPrice: getPriceDefault, - promo: 799, + promo: { + placementDistributions: [0.375, 0.3125, 0.125], + price: 799, + reviewerDistributions: [0.0625, 0.0625], + }, usePromo: true, }, [WorkType.unknown]: { - base: 0, + base: { + placementDistributions: [], + price: 0, + reviewerDistributions: [], + }, getPrice: () => 0, usePromo: false, }, } function getPriceDefault(price: WorkPrice): number { - return price.usePromo && price.promo ? price.promo : price.base + return price.usePromo && price.promo?.price ? price.promo?.price : price.base.price } function getPriceDesignLegacy(price: WorkPrice, pageCount?: number, deviceCount?: number): number { diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-prize.model.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-prize.model.ts new file mode 100644 index 000000000..4a8c966c6 --- /dev/null +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-prize.model.ts @@ -0,0 +1,8 @@ +export interface WorkPrize { + description: string, + prizes: Array<{ + type: string, + value: number, + }>, + type: string, +} diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timeline-phase.model.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timeline-phase.model.ts new file mode 100644 index 000000000..c0c4b5ccf --- /dev/null +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timeline-phase.model.ts @@ -0,0 +1,4 @@ +export interface WorkTimelinePhase { + duration: number, + phaseId: string, +} diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timeline.model.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timeline.model.ts index ec993036f..06552d227 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timeline.model.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timeline.model.ts @@ -1,4 +1,5 @@ +import { WorkTimelinePhase } from './work-timeline-phase.model' + export interface WorkTimeline { - duration: number, - phaseId: string, + [workType: string]: ReadonlyArray } diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timelines.config.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timelines.config.ts index d3e2174ce..f0346047d 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timelines.config.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-timelines.config.ts @@ -1,7 +1,7 @@ import { WorkTimeline } from './work-timeline.model' import { WorkType } from './work-type.enum' -export const WorkTimelines: { [workType: string]: ReadonlyArray } = { +export const WorkTimelines: WorkTimeline = { // TODO: Determine actual timeline for Bug Hunt [WorkType.bugHunt]: [ { diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-type.model.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-type.model.ts index e9ddcf91d..70bb9b435 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-type.model.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-type.model.ts @@ -1,6 +1,6 @@ import { ChallengeTag } from './challenge-tag.enum' import { WorkPrice } from './work-price.model' -import { WorkTimeline } from './work-timeline.model' +import { WorkTimelinePhase } from './work-timeline-phase.model' import { WorkType } from './work-type.enum' export interface WorkTypeConfig extends WorkPrice { @@ -21,7 +21,7 @@ export interface WorkTypeConfig extends WorkPrice { startRoute: string, subtitle: string, tags: Array, - timeline: ReadonlyArray, + timeline: ReadonlyArray timelineTemplateId: string, title: string, trackId: string, diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-url.config.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-url.config.ts index 9c63cae1f..338d3e65e 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-url.config.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work-url.config.ts @@ -13,4 +13,8 @@ export function getUrl(handle: string, page: Page): string { return `${challengesPath}?createdBy=${handle}&perPage=${page.size}&page=${page.number}&selfService=true` } +export function updateUrl(workId: string): string { + return `${challengesPath}/${workId}` +} + const challengesPath: string = `${EnvironmentConfig.API.V5}/challenges` diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work.store.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work.store.ts index fecdcebf5..487d2e9a8 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work.store.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work-store/work.store.ts @@ -1,9 +1,9 @@ -import { Page, xhrDeleteAsync, xhrGetAsync, xhrPostAsync } from '../../../../../../lib' +import { Page, xhrDeleteAsync, xhrGetAsync, xhrPatchAsync, xhrPostAsync } from '../../../../../../lib' -import { Challenge, ChallengeCreateBody } from './challenge.model' +import { Challenge, ChallengeCreateBody, ChallengeUpdateBody } from './challenge.model' import { WorkStatusFilter } from './work-status-filter.enum' import { WorkStatus } from './work-status.enum' -import { createUrl, deleteUrl, getUrl } from './work-url.config' +import { createUrl, deleteUrl, getUrl, updateUrl } from './work-url.config' import { Work } from './work.model' export async function createAsync(body: ChallengeCreateBody): Promise { @@ -28,3 +28,7 @@ export function getFilteredByStatus(work: ReadonlyArray, workStatusFilter? && (workStatusFilter === WorkStatusFilter.all || w.status === WorkStatus[workStatusFilter as keyof typeof WorkStatus])) } + +export async function updateAsync(body: ChallengeUpdateBody): Promise { + return xhrPatchAsync(updateUrl(body.id), JSON.stringify(body)) +} diff --git a/src-ts/tools/work/work-lib/work-provider/work-functions/work.functions.ts b/src-ts/tools/work/work-lib/work-provider/work-functions/work.functions.ts index 61d9aa3f1..501deabcf 100644 --- a/src-ts/tools/work/work-lib/work-provider/work-functions/work.functions.ts +++ b/src-ts/tools/work/work-lib/work-provider/work-functions/work.functions.ts @@ -2,10 +2,11 @@ import { Page, UserProfile } from '../../../../../lib' import { WorkByStatus } from './work-by-status.model' -import { workFactoryBuildCreateBody, workFactoryCreate } from './work-factory' +import { workFactoryBuildCreateBody, workFactoryBuildUpdateBody, workFactoryCreate } from './work-factory' import { Challenge, ChallengeCreateBody, + ChallengeUpdateBody, Work, workGetPricesConfig, WorkPricesType, @@ -15,6 +16,7 @@ import { workStoreDeleteAsync, workStoreGetAsync, workStoreGetFilteredByStatus, + workStoreUpdateAsync, WorkType, WorkTypeConfig, WorkTypeConfigs, @@ -93,6 +95,12 @@ export function getStatusFilter(filterKey?: string): WorkStatusFilter | undefine return !workStatusFilter ? undefined : WorkStatusFilter[workStatusFilter] } +export async function updateAsync(type: WorkType, challenge: Challenge, intakeForm: any): Promise { + const workConfig: WorkTypeConfig = WorkTypeConfigs[type] + const body: ChallengeUpdateBody = workFactoryBuildUpdateBody(workConfig, challenge, intakeForm) + return workStoreUpdateAsync(body) +} + async function getPageAsync(handle: string, page: Page): Promise> { // get the response diff --git a/src-ts/tools/work/work-self-service/intake-forms/bug-hunt/BugHuntIntakeForm.tsx b/src-ts/tools/work/work-self-service/intake-forms/bug-hunt/BugHuntIntakeForm.tsx index 78ab7e1df..05c2f38d3 100644 --- a/src-ts/tools/work/work-self-service/intake-forms/bug-hunt/BugHuntIntakeForm.tsx +++ b/src-ts/tools/work/work-self-service/intake-forms/bug-hunt/BugHuntIntakeForm.tsx @@ -10,11 +10,18 @@ import { PageDivider, useCheckIsMobile } from '../../../../../lib' -import { Challenge, workBugHuntConfig, workCreateAsync, WorkType } from '../../../work-lib' +import { + Challenge, + ChallengeMetadataName, + workBugHuntConfig, + workCreateAsync, + WorkType, + workUpdateAsync +} from '../../../work-lib' import { WorkServicePrice } from '../../../work-service-price' import { WorkTypeBanner } from '../../../work-type-banner' -import { BugHuntFormConfig, FormInputNames } from './bug-hunt.form.config' +import { BugHuntFormConfig } from './bug-hunt.form.config' import styles from './BugHunt.module.scss' import { DeliverablesInfoCard } from './deliverables-info-card' @@ -45,17 +52,17 @@ const BugHuntIntakeForm: React.FC = ({ workId }) => { }, [workId]) const requestGenerator: (inputs: ReadonlyArray) => void = (inputs) => { - const projectTitle: string = formGetInputModel(inputs, FormInputNames.title).value as string - const featuresToTest: string = formGetInputModel(inputs, FormInputNames.features).value as string - const deliveryType: string = formGetInputModel(inputs, FormInputNames.deliveryType).value as string - const repositoryLink: string = formGetInputModel(inputs, FormInputNames.repositoryLink).value as string - const websiteURL: string = formGetInputModel(inputs, FormInputNames.websiteURL).value as string - const bugHuntGoals: string = formGetInputModel(inputs, FormInputNames.goals).value as string - const packageType: string = formGetInputModel(inputs, FormInputNames.packageType).value as string + const projectTitle: string = formGetInputModel(inputs, ChallengeMetadataName.projectTitle).value as string + const featuresToTest: string = formGetInputModel(inputs, ChallengeMetadataName.featuresToTest).value as string + const deliveryType: string = formGetInputModel(inputs, ChallengeMetadataName.deliveryType).value as string + const repositoryLink: string = formGetInputModel(inputs, ChallengeMetadataName.repositoryLink).value as string + const websiteURL: string = formGetInputModel(inputs, ChallengeMetadataName.websiteURL).value as string + const goals: string = formGetInputModel(inputs, ChallengeMetadataName.goals).value as string + const packageType: string = formGetInputModel(inputs, ChallengeMetadataName.packageType).value as string return { - bugHuntGoals, deliveryType, featuresToTest, + goals, packageType, projectTitle, repositoryLink, @@ -64,7 +71,12 @@ const BugHuntIntakeForm: React.FC = ({ workId }) => { } const onSave: (val: any) => Promise = (val: any) => { - return new Promise(() => { }).then(() => { }) + if (!challenge) { return Promise.resolve() } + + return workUpdateAsync(WorkType.bugHunt, challenge, val) + .then(() => { + // TODO: Navigate to a different page (review, back to dashboard, etc) + }) } const defaultValues: object = { diff --git a/src-ts/tools/work/work-self-service/intake-forms/bug-hunt/bug-hunt.form.config.tsx b/src-ts/tools/work/work-self-service/intake-forms/bug-hunt/bug-hunt.form.config.tsx index f9e679519..8da625832 100644 --- a/src-ts/tools/work/work-self-service/intake-forms/bug-hunt/bug-hunt.form.config.tsx +++ b/src-ts/tools/work/work-self-service/intake-forms/bug-hunt/bug-hunt.form.config.tsx @@ -1,19 +1,9 @@ import { ReactComponent as BackIcon } from '../../../../../../src/assets/images/icon-back-arrow.svg' import { FormDefinition, GithubIcon, GitlabIcon, RadioButton, validatorRequired } from '../../../../../lib' +import { ChallengeMetadataName, ChallengeMetadataTitle } from '../../../work-lib' import { SupportInfoCard } from '../support-info-card' -export enum FormInputNames { - additionalInformation = 'additionalInformation', - title = 'projectTitle', - features = 'featuresToTest', - goals = 'bugHuntGoals', - deliveryType = 'deliveryType', - packageType = 'packageType', - repositoryLink = 'repositoryLink', - websiteURL = 'websiteURL', -} - export const BugHuntFormConfig: FormDefinition = { buttons: { primaryGroup: [ @@ -45,7 +35,7 @@ export const BugHuntFormConfig: FormDefinition = { { hideInlineErrors: true, label: 'Project title', - name: FormInputNames.title, + name: ChallengeMetadataName.projectTitle, placeholder: 'Enter a descriptive title', type: 'text', validators: [ @@ -56,14 +46,14 @@ export const BugHuntFormConfig: FormDefinition = { }, ], instructions: 'Enter a title for your website bug hunt project.', - title: 'Project Title', + title: ChallengeMetadataTitle.projectTitle, }, { inputs: [ { hideInlineErrors: true, label: 'Website URL', - name: FormInputNames.websiteURL, + name: ChallengeMetadataName.websiteURL, placeholder: 'Enter a descriptive title', type: 'text', validators: [ @@ -74,14 +64,14 @@ export const BugHuntFormConfig: FormDefinition = { }, ], instructions: 'Enter a title for your website bug hunt project.', - title: 'Website URL', + title: ChallengeMetadataTitle.websiteURL, }, { inputs: [ { hideInlineErrors: true, - label: 'Project title', - name: FormInputNames.goals, + label: 'Bug hunt goals', + name: ChallengeMetadataName.goals, placeholder: 'Describe your goal', type: 'textarea', validators: [ @@ -95,13 +85,13 @@ export const BugHuntFormConfig: FormDefinition = { Do you have any specific goals for your website bug hunt?
For example: find bugs in my online shopping experience `, - title: 'Bug Hunt Goals', + title: ChallengeMetadataTitle.bugHuntGoals, }, { inputs: [ { label: 'Features to test (optional)', - name: FormInputNames.features, + name: ChallengeMetadataName.featuresToTest, placeholder: 'List the sepcific features', type: 'textarea', }, @@ -110,24 +100,24 @@ export const BugHuntFormConfig: FormDefinition = { Are there specific features we should focus on testing?
For example: [An example not used above] `, - title: 'Features to test', + title: ChallengeMetadataTitle.featuresToTest, }, { inputs: [ { hideInlineErrors: true, - name: FormInputNames.deliveryType, + name: ChallengeMetadataName.deliveryType, notTabbable: false, options: [ { checked: false, children: } />, - id: 'github', + id: 'GitHub', }, { checked: false, children: } />, - id: 'gitlab', + id: 'GitLab', }, ], type: 'radio', @@ -139,19 +129,19 @@ export const BugHuntFormConfig: FormDefinition = { }, { label: 'Repository Link (Optional)', - name: FormInputNames.repositoryLink, + name: ChallengeMetadataName.repositoryLink, placeholder: 'www.example-share-link.com', type: 'text', }, ], instructions: 'How do you want your bugs delivered?', - title: 'Bug Delivery', + title: ChallengeMetadataTitle.bugDeliveryType, }, { inputs: [ { label: 'Additional information (optional)', - name: FormInputNames.additionalInformation, + name: ChallengeMetadataName.additionalInformation, placeholder: '[Suggestion text]', type: 'textarea', }, @@ -159,7 +149,7 @@ export const BugHuntFormConfig: FormDefinition = { instructions: ` Is there anything else we should know about testing your website? `, - title: 'Additional Information', + title: ChallengeMetadataTitle.additionalInformation, }, { inputs: [ @@ -358,13 +348,13 @@ export const BugHuntFormConfig: FormDefinition = { title: 'Premium', }, ], - name: FormInputNames.packageType, + name: ChallengeMetadataName.packageType, notTabbable: false, type: 'card-set', }, ], instructions: 'Select your bug hunt package.', - title: 'Bug Hunt Package', + title: ChallengeMetadataTitle.bugHuntPackage, }, { element: , diff --git a/src/constants/index.js b/src/constants/index.js index 97771a646..ace1bbee2 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -276,7 +276,7 @@ export const webWorkTypes = [ subTitle: "​​Create a beautiful custom visual design for your website. Specify the scope and device types, your vision, and receive up to 5 modern designs.", price: workPriceDesignLegacy.getPrice(workPriceDesignLegacy), - stickerPrice: workPriceDesignLegacy.base, + stickerPrice: workPriceDesignLegacy.base.price, featured: false, startRoute: "/self-service/work/new/website-design-legacy/basic-info", basePath: "website-design-legacy", @@ -355,7 +355,7 @@ export const webWorkTypes = [ description: "We accumulate data every day in the course of life and business, yet rarely have the time to give it a closer look. Get multiple fresh, expert perspectives to identify the patterns and relationships in your data. They might just be the key to your next 'Aha' moment.", price: workPriceData.getPrice(workPriceData), - stickerPrice: workPriceData.base, + stickerPrice: workPriceData.base.price, duration: `${dataExplorationConfigs.DEFAULT_DURATION} Days`, featured: true, startRoute: "/self-service/work/new/data-exploration/basic-info", @@ -411,7 +411,7 @@ export const webWorkTypes = [ description: "Problem Statement & Data Advisory is for those asking themselves: How can I apply data science to this idea or goal? How will I interpret solutions, and how will that help me take action? What data do I need?", price: workPriceProblem.getPrice(workPriceProblem), - stickerPrice: workPriceProblem.base, + stickerPrice: workPriceProblem.base.price, duration: `${dataAdvisoryConfigs.DEFAULT_DURATION} Days`, featured: true, startRoute: "/self-service/work/new/data-advisory/basic-info", @@ -473,7 +473,7 @@ export const webWorkTypes = [ description: "Sometimes data is the only thing standing between you and something great. Tell us what you're solving for, and let our experts find the data to get you started now.", price: workPriceFindData.getPrice(workPriceFindData), - stickerPrice: workPriceFindData.base, + stickerPrice: workPriceFindData.base.price, duration: `${findMeDataConfigs.DEFAULT_DURATION} Days`, featured: true, startRoute: "/self-service/work/new/find-me-data/basic-info", diff --git a/src/utils/index.js b/src/utils/index.js index ede6415db..757644a5d 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -57,7 +57,7 @@ export function getDataAdvisoryPriceAndTimelineEstimate() { const total = workPriceProblem.getPrice(workPriceProblem); return { total, - stickerPrice: workPriceProblem.base, + stickerPrice: workPriceProblem.base.price, submissionDuration: 3, totalDuration: dataAdvisoryConfigs.DEFAULT_DURATION, prizeSets: [ @@ -89,7 +89,7 @@ export function getDataExplorationPriceAndTimelineEstimate() { const total = workPriceData.getPrice(workPriceData) return { total, - stickerPrice: workPriceData.base, + stickerPrice: workPriceData.base.price, submissionDuration: 3, totalDuration: dataExplorationConfigs.DEFAULT_DURATION, prizeSets: [ @@ -162,7 +162,7 @@ export function getFindMeDataPriceAndTimelineEstimate() { return { total, - stickerPrice: workPriceFindData.base, + stickerPrice: workPriceFindData.base.price, submissionDuration: 3, totalDuration: findMeDataConfigs.DEFAULT_DURATION, prizeSets: [