diff --git a/.babelrc.json b/.babelrc.json new file mode 100644 index 000000000..1edc76172 --- /dev/null +++ b/.babelrc.json @@ -0,0 +1,7 @@ +{ + "sourceType": "unambiguous", + "presets": ["@babel/preset-env", "@babel/preset-typescript", [ + "@babel/preset-react", {"runtime": "automatic"} + ]], + "plugins": ["@babel/plugin-transform-runtime"] +} \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 271484925..2d3e1638d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,6 +54,12 @@ running_yarn_build: &running_yarn_build yarn install yarn build +running_yarn_sb_build: &running_yarn_sb_build + name: Running Yarn Storybook Build + command: | + source buildenvvar + yarn sb:build + running_yarn_test: &running_yarn_test name: Running Yarn Test Build command: | @@ -91,6 +97,7 @@ build_steps: &build_steps - run: *install_deploysuite - run: *build_configuration_fetch - run: *running_yarn_build + - run: *running_yarn_sb_build - persist_to_workspace: *workspace_persist test_steps: &test_steps @@ -129,13 +136,13 @@ deploy_steps: &deploy_steps ./master_deploy.sh -d CFRONT -e $DEPLOY_ENV -c $ENABLE_CACHE jobs: - lint-dev: - <<: *defaults - environment: - DEPLOY_ENV: "DEV" - LOGICAL_ENV: "dev" - APPNAME: "platform-ui-mvp" - steps: *lint_steps + # lint-dev: + # <<: *defaults + # environment: + # DEPLOY_ENV: "DEV" + # LOGICAL_ENV: "dev" + # APPNAME: "platform-ui-mvp" + # steps: *lint_steps # lint-prod: # <<: *defaults @@ -153,6 +160,14 @@ jobs: APPNAME: "platform-ui-mvp" steps: *build_steps + build-qa: + <<: *defaults + environment: + DEPLOY_ENV: "QA" + LOGICAL_ENV: "qa" + APPNAME: "platform-ui-mvp" + steps: *build_steps + build-prod: <<: *defaults environment: @@ -179,6 +194,15 @@ jobs: APPNAME: "platform-ui-mvp" steps: *deploy_steps + deployQa: + <<: *deploy_defaults + environment: + DEPLOY_ENV: "QA" + LOGICAL_ENV: "qa" + ENABLE_CACHE: true + APPNAME: "platform-ui-mvp" + steps: *deploy_steps + deployProd: <<: *deploy_defaults environment: @@ -192,12 +216,12 @@ workflows: version: 2 build: jobs: - - lint-dev: - context : org-global - filters: - branches: - ignore: - - master + # - lint-dev: + # context : org-global + # filters: + # branches: + # ignore: + # - master # - lint-prod: # context : org-global @@ -212,6 +236,14 @@ workflows: branches: ignore: - master + - qa + + - build-qa: + context : org-global + filters: + branches: + only: + - qa - build-prod: context : org-global @@ -229,6 +261,15 @@ workflows: only: - dev + - deployQa: + context : org-global + requires: + - build-qa + filters: + branches: + only: + - qa + - deployProd: context : org-global requires: diff --git a/.environments/.env.dev b/.environments/.env.dev new file mode 100644 index 000000000..1c2c3148b --- /dev/null +++ b/.environments/.env.dev @@ -0,0 +1,25 @@ +REACT_APP_HOST_ENV=dev + +REACT_APP_ENABLE_TCA_CERT_MONETIZATION=false + +# Stripe configs +REACT_APP_STRIPE_API_KEY=pk_test_rfcS49MHRVUKomQ9JgSH7Xqz +REACT_APP_STRIPE_API_VERSION=2020-08-27 +# not really used anywhere +REACT_APP_STRIPE_ADMIN_TOKEN= +REACT_APP_STRIPE_CUSTOMER_TOKEN= + +# Vanilla Forums +REACT_APP_VANILLA_ACCESS_TOKEN=va.JApNvUOx3549h20I6tnl1kOQDc75NDIp.0jG3dA.EE3gZgV + +# DataDogLogging +REACT_APP_DATADOG_PUBLIC_TOKEN=puba0825671e469d16f940c5a30dc738f11 + +REACT_APP_MEMBER_VERIFY_LOOKER=3322 + +REACT_APP_SPRIG_ENV_ID=bUcousVQ0-yF + +# Filestack configuration for uploading Submissions +REACT_APP_FILESTACK_API_KEY='AzFINuQoqTmqw0QEoaw9az' +REACT_APP_FILESTACK_REGION='us-east-1' +REACT_APP_FILESTACK_SUBMISSION_CONTAINER='topcoder-dev-submissions-dmz' diff --git a/.environments/.env.prod b/.environments/.env.prod new file mode 100644 index 000000000..29368ec95 --- /dev/null +++ b/.environments/.env.prod @@ -0,0 +1,25 @@ +REACT_APP_HOST_ENV=prod + +REACT_APP_ENABLE_TCA_CERT_MONETIZATION=false + +# Stripe configs +REACT_APP_STRIPE_API_KEY=pk_live_m3bCBVSfkfMOEp3unZFRsHXi +REACT_APP_STRIPE_API_VERSION=2020-08-27 +# not really used anywhere +REACT_APP_STRIPE_ADMIN_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIiwiYWRtaW5pc3RyYXRvciJdLCJpc3MiOiJodHRwczovL2FwaS50b3Bjb2Rlci1kZXYuY29tIiwiaGFuZGxlIjoidGVzdDEiLCJleHAiOjI1NjMwNzY2ODksInVzZXJJZCI6IjQwMDUxMzMzIiwiaWF0IjoxNDYzMDc2MDg5LCJlbWFpbCI6InRlc3RAdG9wY29kZXIuY29tIiwianRpIjoiYjMzYjc3Y2QtYjUyZS00MGZlLTgzN2UtYmViOGUwYWU2YTRhIn0.wKWUe0-SaiFVN-VR_-GwgFlvWaDkSbc8H55ktb9LAVw +REACT_APP_STRIPE_CUSTOMER_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.jl6Lp_friVNwEP8nfsfmL-vrQFzOFp2IfM_HC7AwGcg + +# Vanilla Forums +REACT_APP_VANILLA_ACCESS_TOKEN=va.JApNvUOx3549h20I6tnl1kOQDc75NDIp.0jG3dA.EE3gZgV + +# DataDogLogging +REACT_APP_DATADOG_PUBLIC_TOKEN=puba0825671e469d16f940c5a30dc738f11 + +REACT_APP_MEMBER_VERIFY_LOOKER=3322 + +REACT_APP_SPRIG_ENV_ID=bUcousVQ0-yF + +# Filestack configuration for uploading Submissions +REACT_APP_FILESTACK_API_KEY= +REACT_APP_FILESTACK_REGION= +REACT_APP_FILESTACK_SUBMISSION_CONTAINER= diff --git a/.environments/.env.qa b/.environments/.env.qa new file mode 100644 index 000000000..24db1f9ac --- /dev/null +++ b/.environments/.env.qa @@ -0,0 +1,25 @@ +REACT_APP_HOST_ENV=qa + +REACT_APP_ENABLE_TCA_CERT_MONETIZATION=false + +# Stripe configs +REACT_APP_STRIPE_API_KEY=pk_test_rfcS49MHRVUKomQ9JgSH7Xqz +REACT_APP_STRIPE_API_VERSION=2020-08-27 +# not really used anywhere +REACT_APP_STRIPE_ADMIN_TOKEN= +REACT_APP_STRIPE_CUSTOMER_TOKEN= + +# Vanilla Forums +REACT_APP_VANILLA_ACCESS_TOKEN=va.JApNvUOx3549h20I6tnl1kOQDc75NDIp.0jG3dA.EE3gZgV + +# DataDogLogging +REACT_APP_DATADOG_PUBLIC_TOKEN=puba0825671e469d16f940c5a30dc738f11 + +REACT_APP_MEMBER_VERIFY_LOOKER=3322 + +REACT_APP_SPRIG_ENV_ID=bUcousVQ0-yF + +# Filestack configuration for uploading Submissions +REACT_APP_FILESTACK_API_KEY= +REACT_APP_FILESTACK_REGION= +REACT_APP_FILESTACK_SUBMISSION_CONTAINER= diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..2b7a30cb0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,7 @@ +Related JIRA Ticket: +https://topcoder.atlassian.net/browse/ + +# What's in this PR? + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6a24ee936..272d0ee0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -/node_modules +node_modules /.pnp .pnp.js @@ -18,11 +18,11 @@ # misc .DS_Store .env.local -.env.development.local -.env.test.local -.env.production.local +.env.*.local .env npm-debug.log* yarn-debug.log* yarn-error.log* + +storybook-static diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 000000000..26a4cb686 --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,38 @@ +import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin"; +import type { StorybookConfig } from "@storybook/react-webpack5"; + +import cracoConfig from '../craco.config'; + +const config: StorybookConfig = { + stories: ["../src/**/*.docs.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/preset-create-react-app", + "@storybook/addon-interactions", + ], + framework: { + name: "@storybook/react-webpack5", + options: {}, + }, + docs: { + autodocs: "tag", + }, + staticDirs: ["../public"], + webpackFinal: async (config, { configType }) => { + + if (config.resolve) { + config.resolve.plugins = [ + ...(config.resolve.plugins ?? []), + new TsconfigPathsPlugin() + ]; + config.resolve.alias = { + ...config.resolve.alias, + ...cracoConfig.webpack.alias, + }; + } + + return config; + } +}; +export default config; diff --git a/.storybook/manager-head.html b/.storybook/manager-head.html new file mode 100644 index 000000000..34389bd54 --- /dev/null +++ b/.storybook/manager-head.html @@ -0,0 +1,3 @@ + diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 000000000..95062d7cf --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,6 @@ + diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 000000000..1c372b694 --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,15 @@ +import type { Preview } from "@storybook/react"; + +const preview: Preview = { + parameters: { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + }, +}; + +export default preview; diff --git a/.vscode/components.code-snippets b/.vscode/components.code-snippets new file mode 100644 index 000000000..da4851e5f --- /dev/null +++ b/.vscode/components.code-snippets @@ -0,0 +1,91 @@ +{ + // Place your mfe-customer-work workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } + "[MFE] React component": { + "scope": "typescript,typescriptreact", + "prefix": "rfc", + "body": [ + "import { FC } from 'react'", + "", + "import styles from './${1:ComponentName}.module.scss'", + "", + "interface ${1:ComponentName}Props {", + "}", + "", + "const ${1:ComponentName}: FC<${1:ComponentName}Props> = (props: ${1:ComponentName}Props) => {", + "", + " return (", + "
+ {header}
+
+ setIsExpanded(!isExpanded)}
+ >
+
+ Welcome to the Beta version of the new Challenge Listings. During + this Beta phase, we will be fine-tuning the platform based on + feedback we receive from you, our community members. +
++ You may encounter occasional broken links or error messages. If so, + please let us know! This is what the Beta phase is intended for, and + your feedback will enable us to greatly improve the new site.{" "} +
+You can click on the Feedback button on page.
+Thank you!
+{children};
+ }
+ const keys = key.split("-");
+ const child = InlineWrapper({
+ children,
+ key: keys.slice(1).join("-"),
+ });
+
+ if (keys[0].startsWith("a:")) {
+ return {children};
+ }
+
+ return React.createElement(keys[0], {}, child);
+}
+
+InlineWrapper.propTypes = {
+ children: PT.node.isRequired,
+ hrefs: PT.shape.isRequired,
+ key: PT.string.isRequired,
+};
+
+export default function inlineWrapperFactory(key, hrefs) {
+ return ({ children }) => InlineWrapper({ children, hrefs, key });
+}
diff --git a/src/apps/earn/src/components/Editor/MarkdownEditor/md-utils.js b/src/apps/earn/src/components/Editor/MarkdownEditor/md-utils.js
new file mode 100644
index 000000000..23689f398
--- /dev/null
+++ b/src/apps/earn/src/components/Editor/MarkdownEditor/md-utils.js
@@ -0,0 +1,456 @@
+/**
+ * Markdown utilities for DraftJS editor.
+ *
+ * There are plenty of Markdown-related plugins for DraftJS in Internet, but it
+ * looks like nobody made it right so far; thus here we go with the correct
+ * solution.
+ */
+
+import _ from "lodash";
+import Markdown from "markdown-it";
+
+import { EditorState, SelectionState, Modifier } from "draft-js";
+
+import { List } from "immutable";
+
+import shortId from "shortid";
+
+import inlineWrapperFactory from "./inlineWrapperFactory";
+
+import "./style.scss";
+
+/**
+ * Counts the specified characters in the given string.
+ * @param {String} string
+ * @param {String} char
+ * @return {Number}
+ */
+function count(string, char) {
+ let pos = -1;
+ let res = 0;
+ for (;;) {
+ const c = string[(pos += 1)];
+ if (c === char) res += 1;
+ else if (!c) return res;
+ }
+}
+
+/**
+ * Finds position of the n-th occurance of the specified character in the given
+ * string, and returns it. Returns -1, if not found.
+ * @param {String} string
+ * @param {String} char
+ * @param {Number} n
+ * @return {Number}
+ */
+function findNth(string, char, n) {
+ let pos = -1;
+ let res = n;
+ for (;;) {
+ const c = string[(pos += 1)];
+ if (c === char) res -= 1;
+ if (!res) return pos;
+ if (!c) return -1;
+ }
+}
+
+/* Internal. */
+function newSelector(key, pos = 0, end, endKey) {
+ return SelectionState.createEmpty(key).merge({
+ anchorOffset: pos,
+ focusKey: endKey === undefined ? key : endKey,
+ focusOffset: end === undefined ? pos : end,
+ });
+}
+
+/* Internal. */
+function insertChar(content, key, pos, char) {
+ return Modifier.insertText(content, newSelector(key, pos), char);
+}
+
+/* Internal. */
+function mergeBlockData(content, key, data) {
+ return Modifier.mergeBlockData(content, newSelector(key), data);
+}
+
+/* Internal. */
+function removeChar(content, key, pos) {
+ return Modifier.removeRange(content, newSelector(key, pos, pos + 1));
+}
+
+/* Internal. */
+function removeLastChar(content, key, pos) {
+ const s = newSelector(key, pos, 0, content.getKeyAfter(key));
+ return Modifier.removeRange(content, s);
+}
+
+/* Internal. */
+function splitBlock(content, key, pos) {
+ return Modifier.splitBlock(content, newSelector(key, pos));
+}
+
+/* Internal. */
+function setBlockType(content, key, type) {
+ const block = content.getBlockForKey(key);
+ if (block.getType() === type) return content;
+ return Modifier.setBlockType(content, newSelector(key, 0), type);
+}
+
+export default class MdUtils {
+ /**
+ * Constructs a new MdUtils instance.
+ * @param {ContentState} contentState Optional. If provided, constructor will
+ * automatically call parse(contentState) in the end of initialization.
+ */
+ constructor(contentState) {
+ this.markdown = new Markdown();
+ this.markdown.disable(["table"]);
+ this.tokens = [];
+ if (contentState) this.parse(contentState);
+ }
+
+ /**
+ * Private.
+ *
+ * Merges and/or splits DraftJS blocks to ensure that the current block
+ * contains exactly the specified number of lines.
+ *
+ * @param {Number} numLines
+ */
+ alignLines(numLines) {
+ for (;;) {
+ const text = this.content.getBlockForKey(this.key).getText();
+ const numLinesInBlock = 1 + count(text, "\n");
+ if (numLinesInBlock < numLines) {
+ const nextKey = this.content.getKeyAfter(this.key);
+ if (!nextKey) return;
+ this.content = removeLastChar(this.content, this.key, text.length);
+ this.content = insertChar(this.content, this.key, text.length, "\n");
+ if (this.selection.getAnchorKey() === nextKey) {
+ this.selection = this.selection.merge({
+ anchorKey: this.key,
+ anchorOffset: this.selection.getAnchorOffset() + text.length + 1,
+ });
+ }
+ if (this.selection.getFocusKey() === nextKey) {
+ this.selection = this.selection.merge({
+ focusKey: this.key,
+ focusOffset: this.selection.getFocusOffset() + text.length + 1,
+ });
+ }
+ } else if (numLinesInBlock > numLines) {
+ const splitPoint = findNth(text, "\n", numLines);
+ this.content = removeChar(this.content, this.key, splitPoint);
+ this.content = splitBlock(this.content, this.key, splitPoint);
+ if (
+ this.selection.getAnchorKey() === this.key &&
+ this.selection.getAnchorOffset() > splitPoint
+ ) {
+ this.selection = this.selection.merge({
+ anchorKey: this.content.getKeyAfter(this.key),
+ anchorOffset: this.selection.getAnchorOffset() - splitPoint - 1,
+ });
+ }
+ if (
+ this.selection.getFocusKey() === this.key &&
+ this.selection.getFocusOffset() > splitPoint
+ ) {
+ this.selection = this.selection.merge({
+ focusKey: this.content.getKeyAfter(this.key),
+ focusOffset: this.selection.getFocusOffset() - splitPoint - 1,
+ });
+ }
+ } else return;
+ }
+ }
+
+ getDecorations(block) {
+ _.noop(this);
+ const res = block.getData().get("decorations") || List();
+ return res.setSize(block.getLength());
+ }
+
+ getComponentForKey(key) {
+ _.noop(this);
+ return inlineWrapperFactory(key, this.hrefs);
+ }
+
+ getPropsForKey() {
+ _.noop(this);
+ return {};
+ }
+
+ /**
+ * Highlights Markdown syntax in the given DraftJS state.
+ * @param {EditorState} state DraftJS EditorState that holds a plain text with
+ * a valid Markdown markup, previously loaded into this MdUtils instance via
+ * parse(..) method.
+ * @return {EditorState} Resulting state with the proper formatting and
+ * styling of the markup.
+ */
+ highlight(state) {
+ this.blockTypes = [];
+ this.content = state.getCurrentContent();
+ this.decorations = {};
+ this.decoreLevel = 0;
+ this.endLines = [];
+ this.hrefs = {};
+ this.key = this.content.getFirstBlock().getKey();
+ this.selection = state.getSelection();
+ this.styleLine = 0;
+ this.tokenId = 0;
+ this.tokens.forEach(() => this.highlightNextToken());
+ while (this.key) {
+ this.content = setBlockType(this.content, this.key, "unstyled");
+ this.highlightInline();
+ this.key = this.content.getKeyAfter(this.key);
+ }
+ const res = EditorState.push(state, this.content, "custom");
+ return EditorState.acceptSelection(res, this.selection);
+ }
+
+ /**
+ * Private.
+ *
+ * Highlights inline Markdown syntax in the current DraftJS block.
+ */
+ highlightInline(subTokens) {
+ let pos = 0;
+ const text = this.content.getBlockForKey(this.key).getText();
+ const res = new Array(text.length);
+ res.fill("mdSyntax");
+
+ if (subTokens) {
+ const styles = [];
+ subTokens.forEach((st) => {
+ switch (st.type) {
+ case "link_open": {
+ const id = shortId().replace(/-/g, ":");
+ [[, this.hrefs[id]]] = st.attrs;
+ styles.push(`a:${id}`);
+ break;
+ }
+
+ case "em_open":
+ case "strong_open":
+ styles.push(st.tag);
+ break;
+
+ case "s_open":
+ styles.push("strike");
+ break;
+
+ case "em_close":
+ case "link_close":
+ case "s_close":
+ case "strong_close":
+ styles.pop();
+ break;
+
+ case "code_inline": {
+ if (!st.content.length) break;
+ let style = styles.join("-");
+ if (!style) style = "inlineCode";
+ else style = `${style}-inlineCode`;
+ pos = text.indexOf(st.content, pos);
+ const end = pos + st.content.length;
+ while (pos < end) {
+ res[pos] = style;
+ pos += 1;
+ }
+ break;
+ }
+
+ case "text": {
+ if (!st.content.length) break;
+ pos = text.indexOf(st.content, pos);
+ const end = pos + st.content.length;
+ const style = styles.join("-") || "text";
+ while (pos < end) {
+ res[pos] = style;
+ pos += 1;
+ }
+ if (styles.length && _.last(styles).startsWith("a:")) {
+ pos += 3 + _.last(styles).slice(2).length;
+ }
+ break;
+ }
+
+ default:
+ }
+ });
+ }
+
+ let i = text.length - 1;
+ while (i >= 0 && text[i] === " ") {
+ res[i] = "text";
+ i -= 1;
+ }
+ while (i >= 0 && res[i] === "mdSyntax") i -= 1;
+ if (i < 0) res.fill("mdSyntax");
+
+ const decorations = List(res);
+ this.content = mergeBlockData(this.content, this.key, { decorations });
+ }
+
+ /**
+ * Private.
+ *
+ * Highlights Markdown syntax in the specified number of lines, starting from
+ * the first non-styled line.
+ *
+ * @param {Number} numLines
+ */
+ highlightLines(numLines) {
+ this.alignLines(numLines);
+ const type = this.blockTypes.join("-") || "unstyled";
+ this.content = setBlockType(this.content, this.key, type);
+ }
+
+ /**
+ * Private.
+ *
+ * Highlights all Markdown syntax between the last highlighted DraftJS block
+ * and the current MarkdownIt token.
+ */
+ highlightNextToken() {
+ const token = this.tokens[this.tokenId];
+
+ /* If token opens a new range, we:
+ * - Style any block before this token line;
+ * - Remember the range of this token, and the line of the first non-styled
+ * block. */
+ if (token.map) {
+ const linesBefore = token.map[0] - this.styleLine;
+ if (linesBefore) {
+ this.highlightLines(linesBefore);
+ this.highlightInline();
+ [this.styleLine] = token.map;
+ this.key = this.content.getKeyAfter(this.key);
+ }
+ }
+
+ switch (token.type) {
+ case "blockquote_open":
+ case "bullet_list_open":
+ this.decoreLevel += 1;
+ this.blockTypes.push(`${token.tag}:${this.decoreLevel}`);
+ this.endLines.push(token.map[1]);
+ break;
+
+ case "ordered_list_open":
+ this.decoreLevel += 2;
+ this.blockTypes.push(`${token.tag}:${this.decoreLevel}`);
+ this.endLines.push(token.map[1]);
+ break;
+
+ case "heading_open":
+ case "list_item_open":
+ case "paragraph_open":
+ this.blockTypes.push(token.tag);
+ this.endLines.push(token.map[1]);
+ break;
+
+ case "blockquote_close":
+ case "bullet_list_close":
+ case "heading_close":
+ case "list_item_close":
+ case "ordered_list_close":
+ case "paragraph_close": {
+ const linesBefore = _.last(this.endLines) - this.styleLine;
+ if (linesBefore) {
+ this.highlightLines(linesBefore);
+ this.highlightInline();
+ this.key = this.content.getKeyAfter(this.key);
+ this.styleLine = _.last(this.endLines);
+ }
+ this.blockTypes.pop();
+ this.endLines.pop();
+
+ switch (token.type) {
+ case "blockquote_close":
+ case "bullet_list_close":
+ this.decoreLevel -= 1;
+ break;
+ case "ordered_list_close":
+ this.decoreLevel -= 2;
+ break;
+ default:
+ }
+
+ break;
+ }
+
+ case "fence": {
+ this.blockTypes.push(token.tag);
+ let numLines = token.map[1] - token.map[0];
+ const subTokens = [{ type: "text", content: token.content }];
+ const isClosed = 1 + count(token.content, "\n") < numLines;
+ if (!isClosed) {
+ let i = 1 + this.tokenId;
+ while (i < this.tokens.length && !this.tokens[i].map) i += 1;
+ if (
+ i === this.tokens.length ||
+ this.tokens[i].map[0] > token.map[1]
+ ) {
+ numLines += 1;
+ }
+ }
+ this.highlightLines(numLines);
+ this.highlightInline(subTokens);
+ this.key = this.content.getKeyAfter(this.key);
+ [, this.styleLine] = token.map;
+ this.blockTypes.pop();
+ break;
+ }
+
+ case "code_block":
+ case "hr": {
+ this.blockTypes.push(token.tag);
+ this.highlightLines(token.map[1] - token.map[0]);
+ this.highlightInline(token.children);
+ this.key = this.content.getKeyAfter(this.key);
+ [, this.styleLine] = token.map;
+ this.blockTypes.pop();
+ break;
+ }
+
+ case "inline":
+ this.highlightLines(token.map[1] - token.map[0]);
+ this.highlightInline(token.children);
+ this.key = this.content.getKeyAfter(this.key);
+ [, this.styleLine] = token.map;
+ break;
+
+ default:
+ }
+
+ this.tokenId += 1;
+ }
+
+ /**
+ * Returns HTML representation of the Markdown markup previously loaded by
+ * parse(..) method of MdUtils.
+ * @return {String}
+ */
+ getHtml() {
+ if (!this.html) {
+ this.html = this.markdown.renderer.render(this.tokens, this.env);
+ }
+ return this.html;
+ }
+
+ /**
+ * Parses the given DraftJS state. The state should contain a plain text with
+ * Markdown markup. After the parse you can call other methods of MdUtils to
+ * generate corresponding HTML markup, or DraftJS state for rendered Markdown
+ * representation, or DraftJS decorator for Markdown syntax highlighting in
+ * the original state.
+ * @param {ContentState} state
+ */
+ parse(contentState) {
+ delete this.html;
+ this.env = {};
+ this.tokens = this.markdown.parse(contentState.getPlainText(), this.env);
+ }
+}
diff --git a/src/apps/earn/src/components/Editor/MarkdownEditor/style.scss b/src/apps/earn/src/components/Editor/MarkdownEditor/style.scss
new file mode 100644
index 000000000..24c5e4fe3
--- /dev/null
+++ b/src/apps/earn/src/components/Editor/MarkdownEditor/style.scss
@@ -0,0 +1,148 @@
+@import "@earn/styles/mixins";
+
+/* Styling of Markdown syntax highlighting. */
+.container {
+ @include tc-body-md;
+
+ overflow: hidden;
+
+ a {
+ color: $tc-dark-blue-110;
+ text-decoration: underline;
+ }
+
+ code {
+ background: $tc-gray-neutral-light;
+ border: 1px solid $tc-gray-20;
+ border-radius: 6px;
+ display: block;
+ font-family: "Roboto Mono", monospace;
+ padding: 15px 20px;
+ white-space: pre-wrap;
+
+ &:global.inline {
+ background: $tc-gray-10;
+ border: none;
+ border-radius: 0;
+ display: inline;
+ padding: 0 5px;
+ }
+ }
+
+ em {
+ font-style: italic;
+ }
+ strong {
+ font-weight: bold;
+ }
+
+ /* This styling leads to some artefacts :( */
+ :global .hr {
+ width: 100%;
+
+ > div > span > span {
+ border-top: 1px solid firebrick;
+ display: inline-block;
+ height: 0;
+ line-height: 0;
+ width: 100%;
+ }
+ }
+
+ :global {
+ li {
+ display: inline-block;
+ position: relative;
+
+ &.leadingLi {
+ margin-top: 10px;
+ }
+
+ &::before {
+ border-left: 3px solid $tc-gray-40;
+ content: "";
+ height: 100%;
+ position: absolute;
+ }
+ }
+
+ $offset: -15;
+
+ @for $level from 0 to 10 {
+ ol.md-syntax-level-#{$level} > li::before {
+ left: #{$offset}px;
+ }
+ $offset: $offset + 18;
+ }
+
+ $offset: -5;
+
+ @for $level from 0 to 10 {
+ ul.md-syntax-level-#{$level} > li::before {
+ left: #{$offset}px;
+ }
+ $offset: $offset + 18;
+ }
+
+ .blockquote {
+ position: relative;
+
+ &::before {
+ border-left: 3px solid $tc-light-blue;
+ content: "";
+ height: 100%;
+ position: absolute;
+ }
+ }
+
+ $offset: -15;
+
+ @for $level from 0 to 10 {
+ .blockquote.md-syntax-level-#{$level}::before {
+ left: #{$offset}px;
+ }
+ $offset: $offset + 18;
+ }
+
+ div,
+ .h1,
+ .h2,
+ .h3,
+ .h4,
+ .h5,
+ .h6,
+ p {
+ @include tc-body-md;
+
+ margin: 0 !important;
+ text-transform: none;
+ }
+
+ .h1 .text {
+ @include tc-heading-xl;
+ }
+ .h2 .text {
+ @include tc-heading-lg;
+ }
+ .h3 .text {
+ @include tc-heading-md;
+ }
+ .h4 .text {
+ @include tc-heading-sm;
+ }
+ .h5 .text {
+ @include tc-heading-xs;
+ }
+ .h6 .text {
+ @include tc-heading-xs;
+ }
+
+ .mdSyntax {
+ @include tc-body-md;
+
+ color: firebrick;
+ font-family: "Roboto Mono", monospace;
+ font-weight: bold;
+ }
+ }
+}
diff --git a/src/apps/earn/src/components/Editor/MultiEditor.jsx b/src/apps/earn/src/components/Editor/MultiEditor.jsx
new file mode 100644
index 000000000..d5b236874
--- /dev/null
+++ b/src/apps/earn/src/components/Editor/MultiEditor.jsx
@@ -0,0 +1,164 @@
+/**
+ * The MultiEditor component combines together WysiwygEditor and Markdown editor
+ * allowing to easily switch between them.
+ */
+
+import PT from "prop-types";
+import React from "react";
+import Turndown from "turndown";
+
+import { OrderedSet } from "immutable";
+
+import Connector from "./Connector";
+import MarkdownEditor from "./MarkdownEditor";
+import WysiwygEditor from ".";
+
+export const MODES = {
+ MARKDOWN: "MARKDOWN",
+ WYSIWYG: "WYSIWYG",
+};
+
+export default class MultiEditor extends React.Component {
+ constructor(props) {
+ super(props);
+ this.fakeConnector = new Connector();
+ this.fakeConnector.setToolbar(this);
+ this.id = props.id;
+ this.state = {
+ mode: props.initialMode,
+ };
+ this.turndown = new Turndown();
+ }
+
+ componentDidMount() {
+ const { connector } = this.props;
+ if (connector) {
+ connector.addEditor(this);
+ this.fakeConnector.setPreviewer(connector.previewer);
+ }
+ }
+
+ componentWillReceiveProps({ connector, id }) {
+ const { connector: prevConnector } = this.props;
+ this.id = id;
+ if (connector !== prevConnector) {
+ if (prevConnector) prevConnector.removeEditor(this);
+ if (connector) {
+ connector.addEditor(this);
+ this.fakeConnector.setPreviewer(connector.previewer);
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ const { connector } = this.props;
+ if (connector) connector.removeEditor(this);
+ }
+
+ onFocusedEditorChanged(state) {
+ const { connector } = this.props;
+ if (connector) connector.setFocusedEditor(this, state);
+ }
+
+ getHtml() {
+ return this.editor.getHtml();
+ }
+
+ setHtml(html) {
+ this.editor.setHtml(html);
+ }
+
+ setMode(value) {
+ const { mode } = this.state;
+ if (value === mode) return;
+ const { connector } = this.props;
+ const state = this.editor.getHtml();
+ this.setState({ mode: value }, () => {
+ this.editor.setHtml(state);
+ if (connector) connector.setFocusedEditor(this, this.editor.state.editor);
+ });
+ }
+
+ applyBlockStyle(type) {
+ const { mode } = this.state;
+ if (mode === MODES.WYSIWYG) this.editor.applyBlockStyle(type);
+ }
+
+ applyColorStyle(type, color) {
+ const { mode } = this.state;
+ if (mode === MODES.WYSIWYG) {
+ this.editor.applyColorStyle(type, color);
+ }
+ }
+
+ focus() {
+ const { mode } = this.state;
+ if (mode === MODES.WYSIWYG) this.editor.focus();
+ }
+
+ insertImage(src, triggerModal) {
+ const { mode } = this.state;
+ switch (mode) {
+ case MODES.WYSIWYG:
+ return this.editor.insertImage(src, triggerModal);
+ case MODES.MARKDOWN:
+ return this.editor.insertImage();
+ default:
+ return undefined;
+ }
+ }
+
+ insertLink(title, href, triggerPopup) {
+ const { mode } = this.state;
+ if (mode === MODES.WYSIWYG) {
+ this.editor.insertLink(title, href, triggerPopup);
+ }
+ }
+
+ toggleInlineStyle(styleName) {
+ const { mode } = this.state;
+ if (mode === MODES.WYSIWYG) {
+ return this.editor.toggleInlineStyle(styleName);
+ }
+ return OrderedSet();
+ }
+
+ render() {
+ const { mode } = this.state;
+ switch (mode) {
+ case MODES.MARKDOWN:
+ return (
+ ,
+ },
+ note: {
+ element: "span",
+ wrapper: ,
+ },
+ }),
+ // Provides custom styling for inline elements (mainly text)
+ customStyleMap: {
+ CODE: {
+ background: "#fafafb",
+ fontFamily: '"Roboto Mono", monospace',
+ },
+ ...inlineStyles,
+ },
+ // Provides custom component rendering for images and links
+ decorators: [
+ {
+ strategy: createStrategy("LINK"),
+ component: Link,
+ props: { updateEntityData },
+ },
+ {
+ strategy: createStrategy("IMG"),
+ component: Image,
+ props: { updateEntityData },
+ },
+ ],
+ };
+};
diff --git a/src/apps/earn/src/components/Editor/style.scss b/src/apps/earn/src/components/Editor/style.scss
new file mode 100644
index 000000000..d3dea4cb3
--- /dev/null
+++ b/src/apps/earn/src/components/Editor/style.scss
@@ -0,0 +1,71 @@
+@import "@earn/styles/mixins";
+
+:global {
+ // Has a default z-index of 0 which interferes with
+ // This will prevent the code/pre styles from being applied twice
+ pre {
+ code {
+ background: none;
+ border: 0;
+ border-radius: none;
+ margin: 0;
+ padding: 0;
+ }
+ }
+}
+
+.focused {
+ border: 1px solid $tc-dark-blue;
+
+ &:hover {
+ border: 1px solid $tc-dark-blue-110;
+ }
+}
+
+.note {
+ @include tc-body-sm;
+
+ background: $tc-yellow-30;
+ border: 1px solid $tc-yellow-70;
+ border-radius: 6px;
+ font-style: italic;
+ color: $tc-black;
+ line-height: 20px;
+ padding: 15px 20px;
+ margin: 25px 0;
+
+ a,
+ p,
+ ul {
+ font-size: 13px;
+ }
+}
diff --git a/src/apps/earn/src/components/ErrorMessage/index.jsx b/src/apps/earn/src/components/ErrorMessage/index.jsx
new file mode 100644
index 000000000..6c1eddaba
--- /dev/null
+++ b/src/apps/earn/src/components/ErrorMessage/index.jsx
@@ -0,0 +1,58 @@
+import { useEffect } from "react";
+import PT from "prop-types";
+import { BaseModal, Button } from "~/libs/ui";
+
+const ErrorMessage = ({ title, details, onOk }) => {
+ useEffect(() => {
+ document.body.classList.add("scrolling-disabled-by-modal");
+
+ return () => {
+ document.body.classList.remove("scrolling-disabled-by-modal");
+ };
+ }, []);
+
+ return (
+ {details}
+ ++ We are sorry that you have encountered this problem. Please, contact our + support + support@topcoder.com + to help us resolve it as soon as possible. +
+
+ Looks like you're not a Topcoder member yet. Or maybe you're
+ not logged in?
+
+
+
+ Already a member? Login here +
++ {warning.details} +
++ {setStatusInfo().title} +
+ {/* + NOTE: TonyJ asked to remove the OR links from the page to keep + users within the new Topcoder site as much as we can. Not wiping + out the code just in case we decide to bring it back later. + + Online Review + + */} ++ {setStatusInfo().message} + + {' '}Need help? + +
++ Need more info on how to pass screening? + Go to help to read Rules & Policies. +
+ )} ++ Manage your submissions +
+ + {/* { + isDesign && currentPhase && ( ++ + {currentPhase.name} + {' '} + Ends: + + {' '} + {end.format('dddd MM/DD/YY hh:mm A')} +
+ ) + } */} ++ Current Deadline:{' '} + {currentPhase.name} +
+ ) + } + + { + challenge.status !== 'Completed' ? ( ++ Current Deadline Ends: {' '} + + {days > 0 && (`${days}D`)} + {' '} + {hours} + H + {' '} + {minutes} + M + +
++ The challenge has ended +
+ ) + } ++ {/* eslint-disable-next-line max-len */} + We always recommend to download your submission to check you uploaded the correct .zip files + {/* eslint-disable-next-line max-len */} + and also to verify your declarations file is accurate. If you don’t want to see a submission + {/* eslint-disable-next-line max-len */} + simply delete it. If you have a new submissions, use the “Add Submission” button to add one + to the top of the list. +
+ ) + } + { + isDevelop && ( ++ {/* eslint-disable-next-line max-len */} + We always recommend to download your submission to check you uploaded the correct .zip files + {/* eslint-disable-next-line max-len */} + and also to verify your declarations file is accurate. If you don’t want to see a submission + {/* eslint-disable-next-line max-len */} + simply delete it. If you have a new submissions, use the “Add Submission” button to add one + to the top of the list. +
+ ) + } + {loadingSubmissions &&| + ID + | ++ Type + | ++ Submission Date + | + {track === COMPETITION_TRACKS.DES && ( ++ Screening Status + | + )} ++ Actions + | +
|---|
Empty Feedback", + }} + /> + ) + } +
{title}
+
+ {showRange ? (
+
+ { past ?
+ { past ?
+ { past ?
+ { past ?
+ Timezone: + {moment.tz.guess()} +
+ { deadlines.map((d, index) => { + const { + name, start, end, showRange, + } = getCardProps(d, index); + return ( +SORT
++ + BONUS: + {' '} + {numberOfCheckpointsPrizes} + + + CHECKPOINTS AWARDED WORTH + + + $ + {topCheckPointPrize} + + + EACH +
+ ) + : ( ++ + RELIABILITY BONUS: $ + {reliabilityBonus.toFixed()} + +
+ ) + } ++ + POINTS: + {drPoints} + +
+${currentPointer.customData.handle}
+${currentPointer.customData.submissionCount} submissions
+Score: ${this.y}
+Submitted: ${moment(currentPointer.customData.created).format('MM/DD/YYYY')}
++ {/* TODO: It is not good to compose the event URL like this, as + * in general there is not guaranteed to be correct. */} + + {content} + +
+{reviewTypeDescription}
+Customer has final opportunity to sign-off on the delivered assets.
++ Make sure you review the scorecard before you start. + This will show you how your submission will be judged and scored. +
+A set of guidelines to help determine if code is acceptable or not.
++ Shortcuts to perform actions related to Topcoder platform without having to open a browser +
++ + Environment + +
+ ) + } + { + isDevelop && codeRepo && codeRepo.length > 0 + && ( + + ) + } + { + screeningScorecardId > 0 + && ( + + ) + } + { + reviewScorecardId > 0 && !isDesign + && ( + + + Review Scorecard + ++ Trouble formatting your submission or want to learn more? + + + Read the FAQ. + +
++ All fonts, stock photos, and icons within your design must be declared + when you submit. DO NOT include any 3rd party files in your + submission or source files. Read about the + {' '} + + policy. + +
++ All submissions are screened for eligibility before the challenge + holder picks winners. Don't let your hard work go to waste. + Learn more about how to + + + pass screening. + +
++ Questions? + + {_.isEmpty(discuss) && ( + + Ask in the Challenge Discussion Forums. + + )} +
+ )} + {!_.isEmpty(discuss) && discuss.map(d => ( + + )) + } ++ You must include all source files with your submission. +
++ { + submissionLimit + ? submissionLimitDisplay : ( + + {submissionLimitDisplay} + + ) + } +
+
+
+ On Demand Challenges are customer-initiated single round design challenges.
+
+
+ Please note the following important information for Topcoder competitors who participate in this challenge:
+
+ Please read the challenge specification carefully and + watch the forums for any questions or feedback + concerning this challenge. It is important that you + monitor any updates provided by the client or Studio + Admins in the forums. Please post any questions you + might have for the client in the forums. +
++ {stockArtText} + + + See this page for more details. + +
++ Submissions are viewable to the client as they are entered + into the challenge. Winners are selected by the client and + are chosen solely at the client's discretion. +
++ For employees of Wipro Technologies, following are the + payment terms. Winner/s would be awarded the prize money on + successful completion and acceptance of the submission by + the stakeholder. Accumulated prize money for the month will + be paid through Wipro payroll as part of subsequent month’s + salary (eg. Aug month challenge winners payment will be + credited as part Sept month salary). For payment of prize + money, respective country currency conversion shall be + considered as per Wipro standard currency conversion + guidelines. Please refer to policy document at + + + https://wipro365.sharepoint.com/sites/wipro-people-policies/wipro%20policies/TopGear-RewardPoints-Policy.pdf + + + for details regarding the policy. +
+
+
+ + {moment(s.created).format('MMM DD, YYYY HH:mm')} +
++ { + (!_.isEmpty(s.review) && !_.isEmpty(s.review[0]) && s.review[0].score) + ? s.review[0].score.toFixed(2) + : 'N/A' + } +
++ { + (s.reviewSummation && s.reviewSummation[0].aggregateScore) + ? s.reviewSummation[0].aggregateScore.toFixed(2) + : 'N/A' + } +
++ Uploading a resume will change your resume for all jobs that you apply + to. +
+Your resume has been successfuly updated.
++ {title} +
++ Drag and drop your{' '} + {fileExtensions.join(' or ')} + {' '} + file here. +
+ ) + } + { + !fileName && !isChallengeBelongToTopgearGroup && ( + + or + + ) + } + { + fileName && ( ++ {fileName} +
+ ) + } + { + _.isNumber(uploadProgress) && uploadProgress < 100 ? ( ++ Uploading: + {uploadProgress} + % +
+ ) : null + } + { + isChallengeBelongToTopgearGroup && ( ++ {title} +
++ Hey, your work is AWESOME! Please don't close this window while uploading, + Your progress will be lost. +
+ ) + } + { + isSubmitting && !submitDone + && ( ++ Uploading: + {(100 * uploadProgress).toFixed()} + % +
+ ) + } + { + error + && ( ++ Oh, that’s embarrassing! The file couldn’t be + uploaded. +
+ ) + } + { + error + && ( ++ Thanks for participating! We’ve received your submission and will + send you an email shortly to confirm and explain what happens next. +
+ ) + } + { + submitDone && !error + && ( ++ This will permanently remove all + files from our servers and can’t be undone. + You’ll have to upload all the files again in order to restore it. + Note that deleting the file may take a few minutes to propagate + through the Topcoder platform. +
+\r\nTo agree to the Appirio NDA, please electronically sign the document by following this link:\r\n\r\nhttps://community.topcoder.com/tc?module=SignDocument&templateId=fake-template-id\r\n
\r\n\r\n\r\nOnce signed, you will be automatically added to the NDA terms of use and notified by email. \r\n
", + "agreed": false, + "docusignTemplateId": "fake-template-id", + "serverInformation": { + "serverName": "TopCoder API", + "apiVersion": "0.0.1", + "requestDuration": 4, + "currentTime": 1504891122158 + }, + "requesterInformation": { + "id": "d9994de712597c11d1caad64996d9fa0d9b4aa2c-w2VCwwGwnN6EeyhK", + "remoteIP": "12.34.56.789", + "receivedParams": { + "apiVersion": "v2", + "termsOfUseId": "21153", + "action": "getTermsOfUse" + } + } +} diff --git a/src/apps/earn/src/services/__mocks__/data/terms-noauth.json b/src/apps/earn/src/services/__mocks__/data/terms-noauth.json new file mode 100644 index 000000000..66dfa2b96 --- /dev/null +++ b/src/apps/earn/src/services/__mocks__/data/terms-noauth.json @@ -0,0 +1,35 @@ +{ + "terms": [ + { + "termsOfUseId": 21193, + "title": "Standard Terms for Topcoder Competitions v2.1", + "url": "", + "agreeabilityType": "Electronically-agreeable", + "templateId": null + }, + { + "termsOfUseId": 21153, + "title": "Appirio NDA v2.0", + "url": "http://community.topcoder.com/tc?module=Terms&tuid=21153", + "agreeabilityType": "DocuSignable", + "templateId": "fake-template-id" + } + ], + "serverInformation": { + "serverName": "Topcoder API", + "apiVersion": "0.0.1", + "requestDuration": 11471, + "currentTime": 1504879510947 + }, + "requesterInformation": { + "id": "456f987dee6e9823179c8184fd3509ffdf9c613a-FyefLdEpb8UHgFQF", + "remoteIP": "12.34.567.890", + "receivedParams": { + "role": "Submitter", + "noauth": "true", + "apiVersion": "v2", + "challengeId": "30059255", + "action": "getChallengeTerms" + } + } +} diff --git a/src/apps/earn/src/services/__mocks__/data/terms-reviewer-details.json b/src/apps/earn/src/services/__mocks__/data/terms-reviewer-details.json new file mode 100644 index 000000000..d2d38ba62 --- /dev/null +++ b/src/apps/earn/src/services/__mocks__/data/terms-reviewer-details.json @@ -0,0 +1,22 @@ +{ + "termsOfUseId": 20704, + "title": "Standard Reviewer Terms v1.0", + "url": "", + "text": "THESE ARE THE TERMS AND CONDITIONS (\"TERMS\") UNDER WHICH YOU AGREE TO WORK UNDER AS A TOPCODER REVIEW BOARD MEMBER. THESE TERMS AND CONDITIONS AFFECT YOUR RIGHTS AND YOU SHOULD READ THEM CAREFULLY BEFORE AGREEING TO THEM. IN THESE TERMS AND CONDITIONS, \"WE,\" \"US,\" \"ITS\" AND \"OUR\" REFER TO TOPCODER, INC. AND \"YOU\" AND \"YOUR\" REFER TO YOU.
\r\n\r\nIt is understood that We need, and You have, expertise in evaluating and critiquing software designs and/or software development solutions. Furthermore, You agree that You are ready, willing, and able to undertake the performance of evaluating and critiquing such software designs and/or software development solutions submitted to Us, and You agree to assign and transfer your rights as a result of performing such services.
\r\n\r\nIn consideration of the premises and the mutual promises and covenants set forth herein, and for other good and valuable consideration, the receipt and sufficiency of which are hereby acknowledged, the parties agree as follows:
\r\n\r\nAs used in these Terms, the following capitalized terms shall have the following meanings unless otherwise indicated:
\r\n\r\n1.1.\t\"Development(s)\" shall mean any idea, design, concept, development, component, algorithm, process, method, formula, code, software, technique, technology, discovery or improvement, whether or not patentable, made, conceived, created, discovered, invented or reduced to practice by You in connection with the performance of services hereunder.
\r\n\r\n1.2.\t\"Intellectual Property Rights\" shall mean all intellectual property rights worldwide arising under statutory or common law or by contract and whether or not perfected, now existing or hereafter filed, issued or acquired, including all patent rights; all rights associated with works of authorship including copyrights and moral rights; rights relating to the protection of trade secrets and confidential information; and any right analogous to those set forth herein and any other proprietary rights relating to intangible property, other than Trademarks.
\r\n\r\n1.3.\t\"TopCoder Information\" shall mean TopCoder's and TopCoder Software's specifications, descriptions, architecture, plans, interfaces, and code for TopCoder's and TopCoder Software's hardware, software, and web site; TopCoder's competitions and competition operation procedures; TopCoder's and TopCoder Software's business and operational plans; and derivatives of the foregoing. The TopCoder Information shall be Confidential Information hereunder.
\r\n\r\n1.4.\t\"Software Component\" shall mean all software and related materials, technology and documentation (including without limitation design documents, source code and object code) to be evaluated and assessed by You for Us hereunder in accordance with our requirements, as set forth herein and in other documents provided by Us. The Software Component shall be Confidential Information hereunder.
\r\n\r\n2.1\tYou hereby agree to provide services relating to the evaluation and assessment of the Software Component. You agree to perform such services according to and in conformity with the following specifications, in addition to any specifications and/or scheduled provided by Us in our sole discretion (the \"Services\"):
\r\n\r\n2.2\tYou agree to commit sufficient time and resources to perform the Services according to the schedule set forth by Us. You shall promptly notify Us of any circumstances, as such circumstances arise, that may reasonably be anticipated to lead to a material deviation from the schedule.
\r\n\r\n2.3\tYou agree to keep Us updated, promptly upon our request, of any progress, problems, and/or developments of which You are aware regarding the Services. We shall have the right to require such updates in writing from You in a format specified by Us or acceptable to Us in our sole discretion. You shall conduct and conclude the Services in a professional manner.
\r\n\r\n3.1\tFee. In consideration for performance of the Services required by You, We shall pay You the fee set forth on TopCoder's website and/or in other correspondence from Us to You (the \"Fee\"). The Fee shall be in U.S. Dollars and may be paid in installments, as set forth on our website or in other correspondence from Us. The Fee shall be paid upon the conclusion of the review period, and once completed scorecards have been received, provided the completed scorecards are submitted to Us by the deadline as set forth on the website and/or in the correspondence from Us.
\r\n\r\n3.2\tRoyalty Payments.
\r\n \r\n(a)\tDefinitions. As used in this Section 3, the following capitalized terms shall have the following meanings unless otherwise indicated:
\r\n(b)\tIn consideration of Your evaluation of the Software Components and performance of Your obligations hereunder, We may pay to You a royalty (the \"Royalty Payment\"). The Royalty Payment to be paid shall be a portion of the Royalty Pool. The Royalty Pool shall be distributed as follows:
\r\n\r\n3.3\tTotal Payment. The sum of the Fee and the Royalty Payments shall be the total payment due to You. Any and all out-of-pocket expenses incurred by You in connection with performing the obligations hereunder are your sole responsibility. We will not reimburse You for any expenses incurred.
\r\n\r\n3.4\tYou shall not be entitled to receive any other compensation or any benefits from Us in connection with the Services. Except as otherwise required by law, We shall not withhold any sums or payments made to You for social security or other federal, state or local tax liabilities or contributions, and all withholdings, liabilities, and contributions shall be solely your responsibility. Further, You understand and agree that the Services are not covered under the unemployment compensation laws and are not intended to be covered by workers' compensation laws.
\r\n\r\n4.1\tYou hereby acknowledge and agree that We own, solely and exclusively, all right, title and interest, including all Intellectual Property Rights, in and to the TopCoder Information. In addition, You hereby irrevocably and unconditionally transfer and assign to Us all right, title and interest You had, have, may have or acquire in or to all Developments and Software Components, and You agree to execute and deliver such documents, certificates, assignments and other writings, and take such other actions as may be necessary or desirable to vest in Us the ownership rights granted to Us hereunder.
\r\n\r\n4.2\tYou further agree that any and all works of authorship created, authored or developed by You hereunder shall be deemed to be \"works made for hire\" within the meaning of the United States Copyright Law and, as such, all rights therein including copyright shall belong solely and exclusively to Us from the time of their creation. To the extent any such work of authorship may not be deemed to be a work made for hire, You agree to, and do hereby, irrevocably and unconditionally transfer and assign to Us all right, title and interest including copyright in and to such work.
\r\n\r\n4.3\tYou agree that if We are unable, because of your unavailability, or for any other reason, to secure your signature to apply for or to pursue any application for any United States or foreign patents, mask work, copyright or trademark registrations covering the assignments to Us above, then You hereby irrevocably designates and appoints Us and your duly authorized officers and agents as your agent and attorney in fact, to act for and in your behalf and stead to execute and file any such applications and to do all other lawfully permitted acts to further the prosecution and issuance of patents, copyright, mask work and trademark registrations thereon with the same legal force and effect as if executed by your authorized agent.
\r\n\r\n4.4\tAll Intellectual Property Rights owned by a party as of the date You agree to these Terms shall remain the property of such party and no licenses or other rights with respect to such intellectual property are granted to the other party except as expressly set forth herein.
\r\n\r\n4.5\tNothing in these terms shall be construed as granting You any right or license under any of our Intellectual Property Right (including any rights We may have in any patents, copyrights, trademarks, service marks or any trade secrets), by implication, estoppel or otherwise, except as expressly set forth herein.
\r\n\r\n5.1\t\"Confidential Information\" shall mean any information, in whatever form, provided by Us to You with obligation of confidentiality, or designated by Us in writing as confidential, proprietary or marked with words of like import when provided to You, and information orally conveyed if We state at the time of oral conveyance or promptly thereafter that such information is confidential. Notwithstanding anything to the contrary contained herein, information about or relating to our software, our system interfaces, our hardware and software architecture, our business, operational and marketing plans, our member lists and database, all information and technology provided by Us to You to enable You to perform your obligations hereunder, TopCoder Information, and any and all Developments shall be considered Confidential Information.
\r\n\r\n5.2\tConfidential Information shall not include information which (a) was in your possession without confidentiality restriction prior to disclosure by Us hereunder; (b) at or after the time of disclosure by Us becomes generally available to the public through no act or omission on our part; (c) is developed by You independently of and without reference to any Confidential Information You receive from Us; or (d) has come into your possession without confidentiality restriction from a third party and such third party is under no obligation to Us to maintain the confidentiality of such information.
\r\n\r\n5.3\tYou acknowledge the confidential and proprietary nature of Confidential Information and agree (i) to hold Confidential Information in confidence and to take all reasonable precautions to protect such Confidential Information (including, without limitation, all precautions You employ with respect to your own confidential materials), (ii) not to divulge any such Confidential Information to any third person; and (iii) not to make any use whatsoever of such Confidential Information except as expressly authorized herein.
\r\n\r\n5.4\tIn the event You are ordered to disclose Confidential Information pursuant to a judicial or government request, requirement or order, You shall promptly notify Us and upon our request, You shall, at our expense, take reasonable steps to assist Us in contesting such request, requirement or order or in otherwise protecting our rights prior to disclosure.
\r\n\r\n5.5\tYou agree not to reproduce or copy by any means Confidential Information, except as reasonably required to perform the Services. Upon termination of your performance of the Services as a review board member, your right to use Confidential Information shall immediately terminate. In addition, upon such termination, or upon demand by Us at any time, You shall return promptly to Us or destroy, at our option, all tangible materials and computer data that disclose or embody Confidential Information.
\r\n\r\n5.6\tYou agree that any breach of these terms by You could cause irreparable damage to Us. In view of the difficulties of placing a monetary value on the Confidential Information, We shall have, in addition to any and all remedies of law, the right to an injunction or other equitable relief, and may be entitled to a preliminary and final injunction without the necessity of posting any bond or undertaking in connection therewith to prevent any further breach or further unauthorized use of Confidential Information. This remedy is separate from any other remedy We may have.
\r\n\r\n6.1\tYou represent and warrant that:\r\n
7.1\tYou shall indemnify, hold harmless and defend Us and our customers from and against any and all suits, actions, damages, costs, losses, expenses (including settlement awards and reasonable attorneys' fees) and other liabilities arising from or in connection with any claim alleging that, to your knowledge, any Development and/or Software Component violates any trade secret right, or infringes any copyright, patent, trademark, or other intellectual property interest, in any country, and shall pay all costs and damages awarded. We shall promptly notify You of any such claim of which We are aware.
\r\n\r\n7.2\tYour obligations shall not extend to any claim for violation or infringement resulting solely from your compliance with any specific or direct written instructions from Us if such infringement would have been avoided but for such compliance.
\r\n\r\n8.1\tBoth parties expressly agree and understand that You are an independent contractor and nothing herein nor the services rendered hereunder is meant, or shall be construed in any way or manner, to create a relationship of employer and employee, principal and agent, partners or any other relationship other than that of independent parties contracting with each other solely for the purpose of carrying out the provisions of these Terms. Accordingly, You acknowledge and agree that You shall not be entitled to any benefits provided by Us to our employees. You shall be responsible for any and all out-of-pocket expenses incurred in connection with performing the Services. In addition, You shall have sole and exclusive responsibility for the payment of all federal, state and local income taxes, for all employment and disability insurance and for Social Security and other similar taxes with respect to any compensation provided by Us hereunder. You further agree that if We pay or become liable for such taxes or related civil penalties or interest as a result of your failure to pay taxes or report same, or due to our failure to withhold taxes, You shall indemnify and hold us harmless for any such liability. You shall assume and accept all responsibilities which are imposed on independent contractors by any statute, regulation, rule of law, or otherwise. You are not our agent and are not authorized and shall not have the power or authority to bind Us or incur any liability or obligation, or act on behalf of Us. At no time shall You represent that You are our agent, or that any of the views, advice, statements and/or information that may be provided while performing the Services are ours.
\r\n\r\n8.2\tWe are entitled to provide You with general guidance to assist You in completing the scope of work to our satisfaction, You are ultimately responsible for directing and controlling the performance of the task and the scope of work, in accordance with these Terms. You shall use your best efforts, energy and skill in your own name and in such manner as You see fit.
", + "agreeabilityType": "Electronically-agreeable", + "serverInformation": { + "serverName": "Topcoder API", + "apiVersion": "0.0.1", + "requestDuration": 52, + "currentTime": 1504892902498 + }, + "requesterInformation": { + "id": "d8c441f8332161f71533f368c09aeead856e4366-K1RdFai7LCAgXVu5", + "remoteIP": "12.34.56.78", + "receivedParams": { + "apiVersion": "v2", + "termsOfUseId": "21193", + "action": "getTermsOfUse" + } + } +} diff --git a/src/apps/earn/src/services/__mocks__/data/terms-reviewer.json b/src/apps/earn/src/services/__mocks__/data/terms-reviewer.json new file mode 100644 index 000000000..744297d8a --- /dev/null +++ b/src/apps/earn/src/services/__mocks__/data/terms-reviewer.json @@ -0,0 +1,36 @@ +{ + "terms": [ + { + "termsOfUseId": 21153, + "title": "Appirio NDA v2.0", + "url": "http://community.topcoder.com/tc?module=Terms&tuid=21153", + "agreeabilityType": "DocuSignable", + "agreed": false, + "templateId": "fake-template-id" + }, + { + "termsOfUseId": 20704, + "title": "Standard Reviewer Terms v1.0", + "url": "", + "agreeabilityType": "Electronically-agreeable", + "agreed": false, + "templateId": null + } + ], + "serverInformation": { + "serverName": "Topcoder API", + "apiVersion": "0.0.1", + "requestDuration": 29, + "currentTime": 1504878884618 + }, + "requesterInformation": { + "id": "1b37607c519c318194ce6da08c519c0a3f7c9855-7FSFCyd6oSX2mV6Z", + "remoteIP": "12.34.567.890", + "receivedParams": { + "role": "Submitter", + "apiVersion": "v2", + "challengeId": "30059255", + "action": "getChallengeTerms" + } + } +} diff --git a/src/apps/earn/src/services/__mocks__/data/terms-topcoder-details.json b/src/apps/earn/src/services/__mocks__/data/terms-topcoder-details.json new file mode 100644 index 000000000..5cc72e1f9 --- /dev/null +++ b/src/apps/earn/src/services/__mocks__/data/terms-topcoder-details.json @@ -0,0 +1,23 @@ +{ + "termsOfUseId": 21193, + "title": "Standard Terms for Topcoder Competitions v2.1", + "url": "", + "agreeabilityType": "Electronically-agreeable", + "text": "\r\n\r\n\r\n\r\n\r\n\r\n| BY E-MAIL: | \r\nGC@appirio.com | \r\n
| BY MAIL: | \r\nDan Lascell\r\n\r\nTopcoder, Inc.\r\n\r\n760 Market Street\r\n\r\nSan Francisco, CA 94102 | \r\n
| BY PHONE: | \r\n(650) 268-9911 | \r\n
@@ -65,11 +64,15 @@ const CourseView: FC
+ Wipro requires employees to enable Multifactor Authentication + with DICE ID in order to take Topcoder Academy courses. +
++ Please go to Account Settings to configure your account. +
+
+
+
+ When you have completed configuring your account, + click below to refresh your settings. +
+
+
+
{description}
+ )} + {open && children &&
+
NEED ANOTHER PAGE?
+{option.title || title}
+{formatOption(option.option)}
++ {key} {i + 1} +
++ {Object.keys(item).map((subKey) => + renderOption(item[subKey], subKey) + )} +
+Page {index + 1} Name
+{page?.pageName}
+Page {index + 1} Requirements
+{page?.pageDetails}
++ {step.label} + {enableEdit && ( + + edit + + )} +
++
+ Topcoder design experts will take all of the information you provide + below, and create visual designs for your website that fit your + industry and match your desired look & feel. We've done this for + hundreds of customers and will work with you to create your ideal + design. +
+ > + ), + breadcrumbs: { + basic: [ + { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, + { url: ROUTES.INTAKE_FORM, name: "Start work" }, + { url: "#", name: "Website Design" }, + ], + review: [ + { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, + { url: ROUTES.INTAKE_FORM, name: "Start work" }, + { url: ROUTES.WEBSITE_DESIGN, name: "Website design" }, + { url: "#", name: "Review & Payment" }, + ], + }, + }, + { + type: WorkType.data, + title: "Data Exploration", + subTitle: "Get insights about your data from Topcoder experts.", + shortDescription: "Uncover What's Interesting About Your Data", + 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.packages?.base?.price, + duration: `${dataExplorationConfigs.DEFAULT_DURATION} Days`, + featured: true, + startRoute: `${selfServiceRootRoute}/new/data-exploration/basic-info`, + basePath: "data-exploration", + bgImage: imgProductDataExploration, + helperBannerTitle: "WHAT WILL I GET?", + helperBannerContent: ( + <> ++ In Data Exploration, multiple data science experts uncover the most + significant patterns and relationships in your data. Unlock the full + potential of your data with expert insights presented in an + easy-to-understand format. +
+ ), + breadcrumbs: { + basic: [ + { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, + { url: ROUTES.INTAKE_FORM, name: "Start work" }, + { url: "#", name: "Data exploration" }, + ], + review: [ + { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, + { url: ROUTES.INTAKE_FORM, name: "Start work" }, + { url: ROUTES.DATA_EXPLORATION, name: "Data exploration" }, + { url: "#", name: "Review & Payment" }, + ], + }, + }, + { + type: WorkType.problem, + title: "Problem Statement & Data Advisory", + subTitle: + "Translate your data science idea into an actionable data science approach.", + shortDescription: "The easiest way to get started in data science", + 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.packages?.base?.price, + duration: `${dataAdvisoryConfigs.DEFAULT_DURATION} Days`, + featured: true, + startRoute: `${selfServiceRootRoute}/new/data-advisory/basic-info`, + basePath: "data-advisory", + helperBannerTitle: "WHAT WILL I RECEIVE?", + bgImage: imgProductProblemStatement, + helperBannerContent: ( + <> ++ 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? +
+ ), + breadcrumbs: { + basic: [ + { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, + { url: ROUTES.INTAKE_FORM, name: "Start work" }, + { url: "#", name: "Problem statement & data advisory" }, + ], + review: [ + { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, + { url: ROUTES.INTAKE_FORM, name: "Start work" }, + { + url: ROUTES.PROBLEM_STATEMENT, + name: "Problem statement & data advisory", + }, + { url: "#", name: "Review & Payment" }, + ], + }, + }, + { + type: WorkType.findData, + title: "Find Me Data", + subTitle: "Get the data you need to meet your analysis goals.", + shortDescription: "Our Experts Source Useful Data Sets For You", + 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.packages?.base?.price, + duration: `${findMeDataConfigs.DEFAULT_DURATION} Days`, + featured: true, + startRoute: `${selfServiceRootRoute}/new/find-me-data/basic-info`, + basePath: "find-me-data", + bgImage: imgProductFindMeData, + helperBannerTitle: "WHAT WILL I RECEIVE?", + helperBannerContent: ( + <> ++ Find Me Data is designed for business leaders, researchers or any + individual who has a data question and is struggling to find the data + to answer it. +
+Use Find Me Data if you:
+
+ I have specific fonts I want to use.
+
+ Share a link to your publicly accessible fonts via drive, dropbox,
+ etc.
+
+ Share a link to your publicly accessible assets via drive, + dropbox, etc. +
+{item.name}
+{option.title || title}
+{formatOption(option.option)}
++ {key} {i + 1} +
++ {Object.keys(item).map((subKey) => + renderOption(item[subKey], subKey) + )} +
+Page {index + 1} Name
+{page?.pageName}
+Page {index + 1} Requirements
+{page?.pageDetails}
++ {step.label} + {enableEdit && ( + + edit + + )} +
++ {subHeading} +
++ {subHeadingMobile || subHeading} +
+ ++ {content} +
++ {content} +
+{content}
+ + {!!ctaText && !!ctaButtonOnClick && ( +
+ Amazing talent. Passionate people.
+
+ Start something great today.
+
+ You are about to share secured information. To ensure your + security, please log in or create an account. +
+ +{description}
- )} - {open && children &&
-
NEED ANOTHER PAGE?
-{serviceType}
} -- Topcoder design experts will take all of the information you provide - below, and create visual designs for your website that fit your - industry and match your desired look & feel. We've done this for - hundreds of customers and will work with you to create your ideal - design. -
- > - ), - breadcrumbs: { - basic: [ - { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, - { url: ROUTES.INTAKE_FORM, name: "Start work" }, - { url: "#", name: "Website Design" }, - ], - review: [ - { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, - { url: ROUTES.INTAKE_FORM, name: "Start work" }, - { url: ROUTES.WEBSITE_DESIGN, name: "Website design" }, - { url: "#", name: "Review & Payment" }, - ], - }, - }, - { - type: WorkType.data, - title: "Data Exploration", - subTitle: "Get insights about your data from Topcoder experts.", - shortDescription: "Uncover What's Interesting About Your Data", - 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.packages?.base?.price, - duration: `${dataExplorationConfigs.DEFAULT_DURATION} Days`, - featured: true, - startRoute: `${selfServiceRootRoute}/new/data-exploration/basic-info`, - basePath: "data-exploration", - bgImage: imgProductDataExploration, - helperBannerTitle: "WHAT WILL I GET?", - helperBannerContent: ( - <> -- In Data Exploration, multiple data science experts uncover the most - significant patterns and relationships in your data. Unlock the full - potential of your data with expert insights presented in an - easy-to-understand format. -
- ), - breadcrumbs: { - basic: [ - { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, - { url: ROUTES.INTAKE_FORM, name: "Start work" }, - { url: "#", name: "Data exploration" }, - ], - review: [ - { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, - { url: ROUTES.INTAKE_FORM, name: "Start work" }, - { url: ROUTES.DATA_EXPLORATION, name: "Data exploration" }, - { url: "#", name: "Review & Payment" }, - ], - }, - }, - { - type: WorkType.problem, - title: "Problem Statement & Data Advisory", - subTitle: - "Translate your data science idea into an actionable data science approach.", - shortDescription: "The easiest way to get started in data science", - 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.packages?.base?.price, - duration: `${dataAdvisoryConfigs.DEFAULT_DURATION} Days`, - featured: true, - startRoute: `${selfServiceRootRoute}/new/data-advisory/basic-info`, - basePath: "data-advisory", - helperBannerTitle: "WHAT WILL I RECEIVE?", - bgImage: imgProductProblemStatement, - helperBannerContent: ( - <> -- 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? -
- ), - breadcrumbs: { - basic: [ - { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, - { url: ROUTES.INTAKE_FORM, name: "Start work" }, - { url: "#", name: "Problem statement & data advisory" }, - ], - review: [ - { url: ROUTES.DASHBOARD_PAGE, name: "My work" }, - { url: ROUTES.INTAKE_FORM, name: "Start work" }, - { - url: ROUTES.PROBLEM_STATEMENT, - name: "Problem statement & data advisory", - }, - { url: "#", name: "Review & Payment" }, - ], - }, - }, - { - type: WorkType.findData, - title: "Find Me Data", - subTitle: "Get the data you need to meet your analysis goals.", - shortDescription: "Our Experts Source Useful Data Sets For You", - 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.packages?.base?.price, - duration: `${findMeDataConfigs.DEFAULT_DURATION} Days`, - featured: true, - startRoute: `${selfServiceRootRoute}/new/find-me-data/basic-info`, - basePath: "find-me-data", - bgImage: imgProductFindMeData, - helperBannerTitle: "WHAT WILL I RECEIVE?", - helperBannerContent: ( - <> -- Find Me Data is designed for business leaders, researchers or any - individual who has a data question and is struggling to find the data - to answer it. -
-Use Find Me Data if you:
-
Hi
diff --git a/src-ts/lib/contact-support-form/contact-support-form.config.ts b/src/libs/shared/lib/components/contact-support-form/contact-support-form.config.ts
similarity index 99%
rename from src-ts/lib/contact-support-form/contact-support-form.config.ts
rename to src/libs/shared/lib/components/contact-support-form/contact-support-form.config.ts
index c41d6dff9..acba4f009 100644
--- a/src-ts/lib/contact-support-form/contact-support-form.config.ts
+++ b/src/libs/shared/lib/components/contact-support-form/contact-support-form.config.ts
@@ -1,4 +1,4 @@
-import { FormDefinition, validatorEmail, validatorRequired } from '../form'
+import { FormDefinition, validatorEmail, validatorRequired } from '~/libs/ui'
export enum ContactSupportFormField {
email = 'email',
diff --git a/src-ts/lib/contact-support-form/contact-support-functions/contact-support-store/contact-support-request.model.ts b/src/libs/shared/lib/components/contact-support-form/contact-support-functions/contact-support-store/contact-support-request.model.ts
similarity index 100%
rename from src-ts/lib/contact-support-form/contact-support-functions/contact-support-store/contact-support-request.model.ts
rename to src/libs/shared/lib/components/contact-support-form/contact-support-functions/contact-support-store/contact-support-request.model.ts
diff --git a/src/libs/shared/lib/components/contact-support-form/contact-support-functions/contact-support-store/contact-support.store.ts b/src/libs/shared/lib/components/contact-support-form/contact-support-functions/contact-support-store/contact-support.store.ts
new file mode 100644
index 000000000..f7354cd40
--- /dev/null
+++ b/src/libs/shared/lib/components/contact-support-form/contact-support-functions/contact-support-store/contact-support.store.ts
@@ -0,0 +1,9 @@
+import { EnvironmentConfig } from '~/config'
+import { xhrPostAsync } from '~/libs/core'
+
+import { ContactSupportRequest } from './contact-support-request.model'
+
+export async function submitRequestAsync(request: ContactSupportRequest): Promise
+ Unfortunately, you are not permitted to access the site. If you feel you should be able to, please
+ {' '}
+
+ contact us
+
+ .
+
+ {errString ?? error.response?.data?.result?.content ?? error.message ?? error}
+ {' '}
+ Please try again later or
+ {' '}
+
+ Contact Support
+
+ .
+ {fileName} Main page content
- I have specific fonts I want to use.
-
- Share a link to your publicly accessible assets via drive,
- dropbox, etc.
- {item.name}
- You are about to share secured information. To ensure your
- security, please log in or create an account.
- {option.title || title} {formatOption(option.option)}
- {key} {i + 1}
-
- {Object.keys(item).map((subKey) =>
- renderOption(item[subKey], subKey)
- )}
- Page {index + 1} Name {page?.pageName} Page {index + 1} Requirements {page?.pageDetails}
- {step.label}
- {enableEdit && (
-
- edit
-
- )}
-
- {subHeading}
-
- {subHeadingMobile || subHeading}
-
- {content}
-
- {content}
- {content}
- The application is under maintenance. Please contact{" "}
- support@topcoder.com{" "}
- if you need help!
- Page title
+
- Share a link to your publicly accessible fonts via drive, dropbox,
- etc.
-
-
-
-
-
-
- The top navigation of the website should allow a user to mouse
- over core navigation sections. Our Services, Our Walkies, Our
- Promise, Locations We Serve, FAQs.
-
-
-
I want to see amazing imagery choice/design here and a
- large tagline that reads, “We Love Your Dog, Too” with a main
- button that says “Find Your Walkie”.
-
-
-
-
-
-
-
-
-
-
-
I would like to see a landing screen to welcome our
- customers and make them feel welcome and warm. We love their
- dog and we want them to feel it! We really want our audience
- to do one core action and that’s to get started finding
- their perfect “Walkie” which is what we call our
- professional dog walkers.
-
- Our customers should be able to reach information about: Our
- Services, Our Walkies, and Locations We Serve. Also, a user
- must be able to Create an Account.
-
- Assets could be:
-
-
- {title}
-
- }
-
{title}
-
-
A dog walking website that allows
- visitors to select dog walkers and schedule dog walking appointments
-
-
- “As a dog owner, I want someone trustworthy to walk my dog, so that
- he feels loved when I'm at work.“
-