diff --git a/frontend/packages/api_client/src/models/OpenEdXInstanceConfig.ts b/frontend/packages/api_client/src/models/OpenEdXInstanceConfig.ts index 5b823d59c..8a2f639f6 100644 --- a/frontend/packages/api_client/src/models/OpenEdXInstanceConfig.ts +++ b/frontend/packages/api_client/src/models/OpenEdXInstanceConfig.ts @@ -130,6 +130,12 @@ export interface OpenEdXInstanceConfig { * @memberof OpenEdXInstanceConfig */ readonly isEmailVerified?: boolean; + /** + * State of DNS config verfication for external_domain + * @type {string} + * @memberof OpenEdXInstanceConfig + */ + readonly dnsConfigurationState?: OpenEdXInstanceConfigDnsConfigurationStateEnum; } export function OpenEdXInstanceConfigFromJSON(json: any): OpenEdXInstanceConfig { @@ -158,6 +164,7 @@ export function OpenEdXInstanceConfigFromJSONTyped(json: any, ignoreDiscriminato 'draftStaticContentOverrides': !exists(json, 'draft_static_content_overrides') ? undefined : StaticContentOverridesFromJSON(json['draft_static_content_overrides']), 'staticPagesEnabled': !exists(json, 'static_pages_enabled') ? undefined : json['static_pages_enabled'], 'isEmailVerified': !exists(json, 'is_email_verified') ? undefined : json['is_email_verified'], + 'dnsConfigurationState': !exists(json, 'dns_configuration_state') ? undefined : json['dns_configuration_state'], }; } @@ -181,4 +188,15 @@ export function OpenEdXInstanceConfigToJSON(value?: OpenEdXInstanceConfig | null }; } +/** +* @export +* @enum {string} +*/ +export enum OpenEdXInstanceConfigDnsConfigurationStateEnum { + Verified = 'verified', + Pending = 'pending', + Failed = 'failed', + NotRequired = 'not_required' +} + diff --git a/frontend/packages/api_client/src/models/OpenEdXInstanceConfigUpdate.ts b/frontend/packages/api_client/src/models/OpenEdXInstanceConfigUpdate.ts index 25cdde1be..e23dcdf59 100644 --- a/frontend/packages/api_client/src/models/OpenEdXInstanceConfigUpdate.ts +++ b/frontend/packages/api_client/src/models/OpenEdXInstanceConfigUpdate.ts @@ -118,6 +118,12 @@ export interface OpenEdXInstanceConfigUpdate { * @memberof OpenEdXInstanceConfigUpdate */ readonly staticPagesEnabled?: string; + /** + * State of DNS config verfication for external_domain + * @type {string} + * @memberof OpenEdXInstanceConfigUpdate + */ + readonly dnsConfigurationState?: OpenEdXInstanceConfigUpdateDnsConfigurationStateEnum; } export function OpenEdXInstanceConfigUpdateFromJSON(json: any): OpenEdXInstanceConfigUpdate { @@ -144,6 +150,7 @@ export function OpenEdXInstanceConfigUpdateFromJSONTyped(json: any, ignoreDiscri 'heroCoverImage': !exists(json, 'hero_cover_image') ? undefined : json['hero_cover_image'], 'draftStaticContentOverrides': !exists(json, 'draft_static_content_overrides') ? undefined : StaticContentOverridesFromJSON(json['draft_static_content_overrides']), 'staticPagesEnabled': !exists(json, 'static_pages_enabled') ? undefined : json['static_pages_enabled'], + 'dnsConfigurationState': !exists(json, 'dns_configuration_state') ? undefined : json['dns_configuration_state'], }; } @@ -167,4 +174,15 @@ export function OpenEdXInstanceConfigUpdateToJSON(value?: OpenEdXInstanceConfigU }; } +/** +* @export +* @enum {string} +*/ +export enum OpenEdXInstanceConfigUpdateDnsConfigurationStateEnum { + Verified = 'verified', + Pending = 'pending', + Failed = 'failed', + NotRequired = 'not_required' +} + diff --git a/frontend/src/console/actions.ts b/frontend/src/console/actions.ts index b1cce020e..353cc577b 100644 --- a/frontend/src/console/actions.ts +++ b/frontend/src/console/actions.ts @@ -340,13 +340,16 @@ export const updateFieldValue = ( } }); } - } catch { - dispatch({ - type: Types.UPDATE_INSTANCE_INFO_FAILURE, - data: { - [fieldName]: value - } - }); + } catch (e) { + try { + const error = await e.json(); + dispatch({ + type: Types.UPDATE_INSTANCE_INFO_FAILURE, + data: sanitizeErrorFeedback(error) + }); + } catch { + dispatch(push(ROUTES.Error.UNKNOWN_ERROR)); + } } }; diff --git a/frontend/src/console/components/AddDomainModalButton/AddDomainModalButton.spec.tsx b/frontend/src/console/components/AddDomainModalButton/AddDomainModalButton.spec.tsx new file mode 100644 index 000000000..eb64233be --- /dev/null +++ b/frontend/src/console/components/AddDomainModalButton/AddDomainModalButton.spec.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { setupComponentForTesting } from "utils/testing"; +import { AddDomainButton } from './AddDomainModalButton'; + +it('renders without crashing', () => { + const tree = setupComponentForTesting( + , + { + console: { + loading: false, + activeInstance: { + data: { + id: 1, + instanceName: "test", + subdomain: "test", + draftThemeConfig: { + version: 1, + mainColor: "#444444", + linkColor: "#FFAAFF" + }, + draftStaticContentOverrides: { + homepageOverlayHtml: "Test overlay", + } + }, + feedback: [], + loading: ['draftThemeConfig'], + deployment: null, + }, + } + } + ).toJSON(); + expect(tree).toMatchSnapshot(); +}); + +it('renders null when externalDomain in data', () => { + const tree = setupComponentForTesting( + , + { + console: { + loading: false, + activeInstance: { + data: { + id: 1, + instanceName: "test", + externalDomain: 'example.com', + subdomain: "test", + draftThemeConfig: { + version: 1, + mainColor: "#444444", + linkColor: "#FFAAFF" + }, + draftStaticContentOverrides: { + homepageOverlayHtml: "Test overlay", + } + }, + feedback: [], + loading: ['draftThemeConfig'], + deployment: null, + }, + } + } + ).toJSON(); + expect(tree).toMatchSnapshot(); +}) diff --git a/frontend/src/console/components/AddDomainModalButton/AddDomainModalButton.tsx b/frontend/src/console/components/AddDomainModalButton/AddDomainModalButton.tsx new file mode 100644 index 000000000..2803fb894 --- /dev/null +++ b/frontend/src/console/components/AddDomainModalButton/AddDomainModalButton.tsx @@ -0,0 +1,278 @@ +import { InstancesModel } from 'console/models'; +import * as React from 'react'; +import { Button, Col, Container, Form, Modal, Row } from 'react-bootstrap'; +import { RootState } from 'global/state'; +import { connect } from 'react-redux'; +import { TextInputField } from 'ui/components'; +import { WrappedMessage } from 'utils/intl'; +import { updateFieldValue, clearErrorMessage } from 'console/actions'; +import { SUPPORT_LINK } from 'global/constants'; +import messages from './displayMessages'; +import './styles.scss'; + +interface State { + showModal: boolean; + externalDomain?: string; + domainRightsAggrement: boolean; + additionalFeeAggrement: boolean; +} + +interface ActionProps { + updateFieldValue: Function; + clearErrorMessage: Function; +} + +interface StateProps extends InstancesModel {} + +interface Props extends StateProps, ActionProps {} + +class AddDomainButtonComponent extends React.PureComponent { + constructor(props: Props) { + super(props); + + this.state = { + showModal: false, + externalDomain: '', + domainRightsAggrement: false, + additionalFeeAggrement: false + }; + + if (props.activeInstance.data) { + this.state = { + showModal: false, + externalDomain: props.activeInstance.data.externalDomain, + domainRightsAggrement: false, + additionalFeeAggrement: false + }; + } + } + + public componentDidUpdate(prevProps: Props) { + const instance = prevProps.activeInstance; + if (!instance.data) { + this.handleHide(); + } else if ( + prevProps.activeInstance!.data!.externalDomain !== + this.props.activeInstance!.data!.externalDomain + ) { + this.handleHide(); + } + } + + private handleShow = () => { + this.setState({ + showModal: true + }); + }; + + private handleHide = () => { + this.setState({ + showModal: false + }); + }; + + private handleClose = () => { + this.setState({ + showModal: false + }); + }; + + private toggleDomainRights = () => { + this.setState(prevState => ({ + domainRightsAggrement: !prevState.domainRightsAggrement + })); + }; + + private toggleAdditionalFee = () => { + this.setState(prevState => ({ + additionalFeeAggrement: !prevState.additionalFeeAggrement + })); + }; + + private handleChange = (event: React.ChangeEvent) => { + const { value } = event.target; + this.props.clearErrorMessage('externalDomain'); + this.setState({ + externalDomain: value + }); + }; + + private handleUpdateExternalDomain = () => { + const instance = this.props.activeInstance; + if (instance.data) { + this.props.updateFieldValue( + instance.data.id, + 'externalDomain', + this.state.externalDomain + ); + } + }; + + public render() { + const instance = this.props.activeInstance; + + // Do not render if instance data is not present or if externalDomain is present + // in instance data. + if (!instance.data || (instance.data && instance.data.externalDomain)) { + return null; + } + + return ( +
+ + + + + + +

+ +

+ + +

+ + + + + + + + + +

+ +
+ + + + {instance.feedback.externalDomain ? ( +
+

+ + +

+

+ + + + + +

+
+ ) : null} + +
+ + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+
+
+
+
+ ); + } +} + +export const AddDomainButton = connect< + StateProps, + ActionProps, + {}, + Props, + RootState +>((state: RootState) => state.console, { + updateFieldValue, + clearErrorMessage +})(AddDomainButtonComponent); diff --git a/frontend/src/console/components/AddDomainModalButton/__snapshots__/AddDomainModalButton.spec.tsx.snap b/frontend/src/console/components/AddDomainModalButton/__snapshots__/AddDomainModalButton.spec.tsx.snap new file mode 100644 index 000000000..ba3a59aa1 --- /dev/null +++ b/frontend/src/console/components/AddDomainModalButton/__snapshots__/AddDomainModalButton.spec.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders null when externalDomain in data 1`] = `null`; + +exports[`renders without crashing 1`] = ` +
+ +
+`; diff --git a/frontend/src/console/components/AddDomainModalButton/displayMessages.ts b/frontend/src/console/components/AddDomainModalButton/displayMessages.ts new file mode 100644 index 000000000..d28a625bc --- /dev/null +++ b/frontend/src/console/components/AddDomainModalButton/displayMessages.ts @@ -0,0 +1,59 @@ +const messages = { + buttonText: { + defaultMessage: 'Add custom domain (€25/month)', + description: 'Button for adding custom domain' + }, + modalTitle: { + defaultMessage: 'Add a Custom Domain', + description: 'Title for custom domain modal' + }, + modalDescriptionOne: { + defaultMessage: + "Add your domain name below. If you don't have a domain yet, we recommend you go to ", + description: '' + }, + gandiDomainText: { + defaultMessage: 'Gandi.net', + description: '' + }, + modalDescriptionTwo: { + defaultMessage: + ' to order one. Then complete this step once you have a domain.', + description: '' + }, + externalDomain: { + defaultMessage: 'Domain Name', + description: 'Domain name input field' + }, + addDomainBtn: { + defaultMessage: 'Add Domain', + description: 'Submit button to add domain' + }, + domainRightsAgreement: { + defaultMessage: 'I certify that I have the rights to use this domain', + description: '' + }, + additionalFeeAgreement: { + defaultMessage: 'I agree to the additional monthly fee of €25', + description: '' + }, + domainErrorMessage: { + defaultMessage: 'Invalid domain name', + description: '' + }, + domainErrorDescription1: { + defaultMessage: + "We couldnt recognize you domain name. If you're sure it's valid, please ", + description: '' + }, + contantSupport: { + defaultMessage: 'contant support', + description: '' + }, + domainErrorDescription2: { + defaultMessage: ' for assistance.', + description: '' + } +}; + +export default messages; diff --git a/frontend/src/console/components/AddDomainModalButton/index.ts b/frontend/src/console/components/AddDomainModalButton/index.ts new file mode 100644 index 000000000..351a1dae5 --- /dev/null +++ b/frontend/src/console/components/AddDomainModalButton/index.ts @@ -0,0 +1 @@ +export * from './AddDomainModalButton'; diff --git a/frontend/src/console/components/AddDomainModalButton/styles.scss b/frontend/src/console/components/AddDomainModalButton/styles.scss new file mode 100644 index 000000000..f6d342a6a --- /dev/null +++ b/frontend/src/console/components/AddDomainModalButton/styles.scss @@ -0,0 +1,37 @@ +@import "~styles/theme"; + +.checkbox-label { + font-size: 16px; + color: $dark-2; +} + +.add-domain-modal { + padding: 35px; + + h2 { + color: $dark-1; + } + + .add-domain-modal-description { + color: $dark-2; + + a { + color: $link-blue; + } + } + + .domain-error { + font-size: 16px; + .error-msg { + color: $alert-red-3; + margin-bottom: 0.5rem; + } + a { + color: $link-blue; + } + } + + button { + font-size: 12px; + } +} diff --git a/frontend/src/console/components/ConsolePage/__snapshots__/ConsolePage.spec.tsx.snap b/frontend/src/console/components/ConsolePage/__snapshots__/ConsolePage.spec.tsx.snap index b542b83d4..e8df44b85 100644 --- a/frontend/src/console/components/ConsolePage/__snapshots__/ConsolePage.spec.tsx.snap +++ b/frontend/src/console/components/ConsolePage/__snapshots__/ConsolePage.spec.tsx.snap @@ -241,11 +241,9 @@ exports[`Console Page Correctly renders loading page 1`] = ` General Domain @@ -554,11 +552,9 @@ exports[`Console Page Correctly renders page with data 1`] = ` General Domain @@ -823,11 +819,9 @@ exports[`Console Page Correctly renders page with email not verified alert 1`] = General Domain diff --git a/frontend/src/console/components/CoursesManage/__snapshots__/CoursesManage.spec.tsx.snap b/frontend/src/console/components/CoursesManage/__snapshots__/CoursesManage.spec.tsx.snap index 6d585ca38..53d6e3a73 100644 --- a/frontend/src/console/components/CoursesManage/__snapshots__/CoursesManage.spec.tsx.snap +++ b/frontend/src/console/components/CoursesManage/__snapshots__/CoursesManage.spec.tsx.snap @@ -229,11 +229,9 @@ exports[`renders without crashing 1`] = ` General Domain diff --git a/frontend/src/console/components/CustomizationSideMenu/CustomizationSideMenu.tsx b/frontend/src/console/components/CustomizationSideMenu/CustomizationSideMenu.tsx index 84f0cce66..eab933073 100644 --- a/frontend/src/console/components/CustomizationSideMenu/CustomizationSideMenu.tsx +++ b/frontend/src/console/components/CustomizationSideMenu/CustomizationSideMenu.tsx @@ -99,14 +99,7 @@ export const CustomizationSideMenu: React.FC = () => { - { - e.preventDefault(); - }} - > + diff --git a/frontend/src/console/components/CustomizationSideMenu/__snapshots__/CustomizationSideMenu.spec.tsx.snap b/frontend/src/console/components/CustomizationSideMenu/__snapshots__/CustomizationSideMenu.spec.tsx.snap index 8f7e78d46..2dbc4b64f 100644 --- a/frontend/src/console/components/CustomizationSideMenu/__snapshots__/CustomizationSideMenu.spec.tsx.snap +++ b/frontend/src/console/components/CustomizationSideMenu/__snapshots__/CustomizationSideMenu.spec.tsx.snap @@ -179,11 +179,9 @@ exports[`Custom Side Menu Page Renders with correct accordion expanded, and with General Domain diff --git a/frontend/src/console/components/DomainItem/DomainItem.spec.tsx b/frontend/src/console/components/DomainItem/DomainItem.spec.tsx new file mode 100644 index 000000000..fc3c5308c --- /dev/null +++ b/frontend/src/console/components/DomainItem/DomainItem.spec.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { setupComponentForTesting } from "utils/testing"; +import { OpenEdXInstanceConfigUpdateDnsConfigurationStateEnum as DnsStateEnum } from 'ocim-client'; +import { DomainListItem } from './DomainItem'; + +it('renders correctly for subdomains', () => { + const props = { + domainName: 'test.opencraft.hosting', + isExternal: false + } + const tree = setupComponentForTesting( + , + ).toJSON(); + expect(tree).toMatchSnapshot(); +}); + +it('renders correctly for unverified external domains', () => { + const props = { + domainName: 'example.com', + isExternal: true, + dnsState: DnsStateEnum.Failed, + } + const tree = setupComponentForTesting().toJSON(); + expect(tree).toMatchSnapshot(); +}) + +it('renders correctly for verified external domains', () => { + const props = { + domainName: 'example.com', + isExternal: true, + dnsState: DnsStateEnum.Verified, + } + const tree = setupComponentForTesting().toJSON(); + expect(tree).toMatchSnapshot(); +}) diff --git a/frontend/src/console/components/DomainItem/DomainItem.tsx b/frontend/src/console/components/DomainItem/DomainItem.tsx new file mode 100644 index 000000000..ff67121f3 --- /dev/null +++ b/frontend/src/console/components/DomainItem/DomainItem.tsx @@ -0,0 +1,197 @@ +import { EXTERNAL_DOMAIN_CNAME_VALUE } from 'global/constants'; +import { OpenEdXInstanceConfigUpdateDnsConfigurationStateEnum as DnsStateEnum } from 'ocim-client'; +import * as React from 'react'; +import { + Button, + Col, + Container, + Dropdown, + Modal, + Row, + Table +} from 'react-bootstrap'; +import DropdownMenu from 'react-bootstrap/DropdownMenu'; +import { WrappedMessage } from 'utils/intl'; +import messages from './displayMessages'; +import './styles.scss'; + +interface State { + title: string; + subtitle: string; + // extra state to manage the empty title and subtitle and rendering + renderBool: boolean; +} + +interface Props { + domainName?: string; + isExternal: boolean; + dnsState?: DnsStateEnum; + onDelete?: Function; +} + +interface DropdownButtonPropType { + children?: React.ReactNode; + onClick?: React.MouseEventHandler; +} + +interface DomainConfigHelpProps { + domainName?: string; +} + +const DomainConfigHelp: React.FC = ( + props: DomainConfigHelpProps +) => { + const [show, setShow] = React.useState(false); + + return ( + <> + + setShow(false)} show={show} centered size="lg"> + + + +

+ +

+ + +

+ +

+ +
+ + + + + + + + + + + + + + + + + + + + +
NAMETYPEVALUE
{props.domainName}CNAME{EXTERNAL_DOMAIN_CNAME_VALUE}
+ *. + {props.domainName} + CNAME{EXTERNAL_DOMAIN_CNAME_VALUE}
+
+
+ +
+
+
+
+
+ + ); +}; + +export class DomainListItem extends React.PureComponent { + private getDropdown() { + const customToggle = React.forwardRef( + ({ children, onClick }, ref) => { + return ( + + ); + } + ); + + return ( + + + Options + + + + { + event.preventDefault(); + if (this.props.onDelete) { + this.props.onDelete(); + } + }} + > + Delete Domain + + + + ); + } + + public render() { + // Determine the color of the domain name text based on DNS config status + let domainColor: string = 'color-green'; + if ( + this.props.isExternal && + this.props.dnsState !== DnsStateEnum.Verified + ) { + domainColor = ''; + } + + return ( +
    +
  • +
    +
    +
    {this.props.domainName}
    + {this.props.isExternal ? ( +
    Primary Domain
    + ) : ( +
    Default Subdomain
    + )} +
    + {this.props.isExternal && + this.props.dnsState !== DnsStateEnum.Verified ? ( + + ) : null} + {this.props.isExternal ? this.getDropdown() : null} +
    +
  • +
+ ); + } +} diff --git a/frontend/src/console/components/DomainItem/__snapshots__/DomainItem.spec.tsx.snap b/frontend/src/console/components/DomainItem/__snapshots__/DomainItem.spec.tsx.snap new file mode 100644 index 000000000..fa2d0142d --- /dev/null +++ b/frontend/src/console/components/DomainItem/__snapshots__/DomainItem.spec.tsx.snap @@ -0,0 +1,132 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly for subdomains 1`] = ` +
    +
  • +
    +
    +
    + test.opencraft.hosting +
    +
    + Default Subdomain +
    +
    +
    +
  • +
+`; + +exports[`renders correctly for unverified external domains 1`] = ` +
    +
  • +
    +
    +
    + example.com +
    +
    + Primary Domain +
    +
    + +
    + +
    +
    +
  • +
+`; + +exports[`renders correctly for verified external domains 1`] = ` +
    +
  • +
    +
    +
    + example.com +
    +
    + Primary Domain +
    +
    +
    + +
    +
    +
  • +
+`; diff --git a/frontend/src/console/components/DomainItem/displayMessages.ts b/frontend/src/console/components/DomainItem/displayMessages.ts new file mode 100644 index 000000000..064c9cdc0 --- /dev/null +++ b/frontend/src/console/components/DomainItem/displayMessages.ts @@ -0,0 +1,14 @@ +const messages = { + helpTitle: { + defaultMessage: 'DNS Configuration', + description: 'Title for DNS configuration help modal' + }, + helpDescription: { + defaultMessage: + 'This table shows you the DNS entries needed to connect your domain.' + + ' Log in to your domain provider to manage these settings.', + description: 'Description for DNS configuration help modal' + } +}; + +export default messages; diff --git a/frontend/src/console/components/DomainItem/index.ts b/frontend/src/console/components/DomainItem/index.ts new file mode 100644 index 000000000..1c1c38fde --- /dev/null +++ b/frontend/src/console/components/DomainItem/index.ts @@ -0,0 +1 @@ +export * from './DomainItem'; diff --git a/frontend/src/console/components/DomainItem/styles.scss b/frontend/src/console/components/DomainItem/styles.scss new file mode 100644 index 000000000..19964511d --- /dev/null +++ b/frontend/src/console/components/DomainItem/styles.scss @@ -0,0 +1,99 @@ +@import "~styles/theme"; +@import "~bootstrap/scss/functions"; +@import "~bootstrap/scss/variables"; +@import "~bootstrap/scss/mixins"; +@import "~bootstrap/scss/utilities"; + +.list-item { + margin: 20px 0px; + font-size: 18px; + + &.color-green { + color: $green; + } + + .domain-type { + color: $secondary-2; + font-size: 14px; + } + + .dns-config-icon { + color: $alert-yellow-3; + } + + .dns-config { + color: $secondary-2; + font-size: 16px; + margin-left: 10px; + border-bottom: 1px black solid; + } + + .check-dns-btn { + flex-grow: 1; + text-decoration: none; + appearance: none; + border: 0; + padding: 0; + font-family: inherit; + font-size: inherit; + background-color: inherit; + } +} + +.options-dropdown-toggle { + display: inline-flex; + border-radius: 0.375rem; + justify-content: center; + background-color: white; + color: black; + font-size: 16px; + padding: 0.5rem 1rem; + + .dropdown-toggle-caret { + margin-left: 0.375rem; + } +} + +.options-dropdown-toggle:focus { + border: blue solid 2px; +} + +.options-dropdown-toggle::after { + content: none; +} + +.dns-config-help-modal{ + padding: 35px; + + .dns-config-help-modal-description { + color: $dark-2; + font-size: 16px; + max-width: 716px + } + + h2 { + color: $dark-1; + font-size: 30px; + text-align: left; + font-weight: 600; + font-family: $playfair-font; + } + + table { + font-size: 16px; + } + + button { + font-size: 14px; + } +} + +.dns-config-help-modal-header{ + display: block; + position: absolute; + right: 0px; + border: none; + cursor: pointer; + z-index: 10; + padding-right: 25px; +} diff --git a/frontend/src/console/components/DomainSettings/DomainSettings.spec.tsx b/frontend/src/console/components/DomainSettings/DomainSettings.spec.tsx new file mode 100644 index 000000000..6f0de95d4 --- /dev/null +++ b/frontend/src/console/components/DomainSettings/DomainSettings.spec.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { setupComponentForTesting } from "utils/testing"; +import { DomainSettings } from './DomainSettings'; + +it('renders correctly for no externalDomain', () => { + const tree = setupComponentForTesting(, + { + console: { + loading: false, + activeInstance: { + data: { + id: 1, + instanceName: "test", + subdomain: "test", + draftThemeConfig: { + version: 1, + mainColor: "#444444", + linkColor: "#FFAAFF" + }, + draftStaticContentOverrides: { + homepageOverlayHtml: "Test overlay", + } + }, + feedback: {}, + loading: [], + deployment: null, + }, + instances: [{ + id: 1, + instanceName: "test", + subdomain: "test", + }] + } + }).toJSON(); + expect(tree).toMatchSnapshot(); +}); + +it('renders correctly if external domain is present', () => { + const tree = setupComponentForTesting(, + { + console: { + loading: false, + activeInstance: { + data: { + id: 1, + instanceName: "test", + externalDomain: 'example.com', + subdomain: "test", + dnsConfigurationState: 'verified', + draftThemeConfig: { + version: 1, + mainColor: "#444444", + linkColor: "#FFAAFF" + }, + draftStaticContentOverrides: { + homepageOverlayHtml: "Test overlay", + } + }, + feedback: {}, + loading: [], + deployment: null, + }, + instances: [{ + id: 1, + instanceName: "test", + subdomain: "test", + }] + } + }).toJSON(); + expect(tree).toMatchSnapshot(); +}); \ No newline at end of file diff --git a/frontend/src/console/components/DomainSettings/DomainSettings.tsx b/frontend/src/console/components/DomainSettings/DomainSettings.tsx new file mode 100644 index 000000000..ac4e67967 --- /dev/null +++ b/frontend/src/console/components/DomainSettings/DomainSettings.tsx @@ -0,0 +1,102 @@ +import * as React from 'react'; +import { InstancesModel } from 'console/models'; +import { ConsolePage, PreviewBox } from 'newConsole/components'; +import { WrappedMessage } from 'utils/intl'; +import { Col, Container, Row } from 'react-bootstrap'; +import { RootState } from 'global/state'; +import { connect } from 'react-redux'; +import './styles.scss'; +import { INTERNAL_DOMAIN_NAME } from 'global/constants'; +import { updateFieldValue } from 'console/actions'; +import { DomainListItem } from '../DomainItem'; +import messages from './displayMessages'; +import { AddDomainButton } from '../AddDomainModalButton'; + +interface State { + title: string; + subtitle: string; + // extra state to manage the empty title and subtitle and rendering + renderBool: boolean; +} + +interface ActionProps { + updateFieldValue: Function; +} + +interface StateProps extends InstancesModel {} + +interface Props extends StateProps, ActionProps {} + +export class DomainSettingsComponent extends React.PureComponent { + private deleteExternalDomain = () => { + const instance = this.props.activeInstance; + if (instance.data) { + this.props.updateFieldValue(instance.data.id, 'externalDomain', null); + } + }; + + public render() { + let subdomain; + let externalDomain; + let dnsConfigState; + let externalDomainComponent; + const instance = this.props.activeInstance; + if (instance && instance.data) { + subdomain = instance.data.subdomain; + externalDomain = instance.data.externalDomain; + dnsConfigState = instance.data.dnsConfigurationState; + } + + if (externalDomain) { + externalDomainComponent = ( + <> +
+ + + ); + } else { + externalDomainComponent = ; + } + + return ( + + + +

+ +

+ + +

+ +

+ +
+
+ + {externalDomainComponent} +
+
+
+
+ ); + } +} + +export const DomainSettings = connect< + StateProps, + ActionProps, + {}, + Props, + RootState +>((state: RootState) => state.console, { + updateFieldValue +})(DomainSettingsComponent); diff --git a/frontend/src/console/components/DomainSettings/__snapshots__/DomainSettings.spec.tsx.snap b/frontend/src/console/components/DomainSettings/__snapshots__/DomainSettings.spec.tsx.snap new file mode 100644 index 000000000..cd1690356 --- /dev/null +++ b/frontend/src/console/components/DomainSettings/__snapshots__/DomainSettings.spec.tsx.snap @@ -0,0 +1,639 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly for no externalDomain 1`] = ` +
+
+ + One of your email addresses is pending confirmation. Please confirm it to begin managing your Open edX instance. +
+
+
+
+
+
+
+ + +
+
+ Instance Settings +
+ +
+
+
+ Courses (Studio) +
+
+
+ +
+
+
+
+
+
+
+
+

+ Domain Settings +

+
+
+

+ By default, your site is always accessible via an OpenCraft subdomain. Custom domains allow users to access your site via non-OpenCraft domain names. A custom domain increase your monthly OpenCraft bill by €25. +

+
+
+
+
    +
  • +
    +
    +
    + test.opencraft.hosting +
    +
    + Default Subdomain +
    +
    +
    +
  • +
+
+ +
+
+
+
+
+
+
+
+
+
+`; + +exports[`renders correctly if external domain is present 1`] = ` +
+
+ + One of your email addresses is pending confirmation. Please confirm it to begin managing your Open edX instance. +
+
+
+
+
+
+
+ + +
+
+ Instance Settings +
+ +
+
+
+ Courses (Studio) +
+
+
+ +
+
+
+
+
+
+
+
+

+ Domain Settings +

+
+
+

+ By default, your site is always accessible via an OpenCraft subdomain. Custom domains allow users to access your site via non-OpenCraft domain names. A custom domain increase your monthly OpenCraft bill by €25. +

+
+
+
+
    +
  • +
    +
    +
    + test.opencraft.hosting +
    +
    + Default Subdomain +
    +
    +
    +
  • +
+
+
    +
  • +
    +
    +
    + example.com +
    +
    + Primary Domain +
    +
    +
    + +
    +
    +
  • +
+
+
+
+
+
+
+
+
+
+`; diff --git a/frontend/src/console/components/DomainSettings/displayMessages.ts b/frontend/src/console/components/DomainSettings/displayMessages.ts new file mode 100644 index 000000000..0dd2422d2 --- /dev/null +++ b/frontend/src/console/components/DomainSettings/displayMessages.ts @@ -0,0 +1,15 @@ +const messages = { + title: { + defaultMessage: 'Domain Settings', + description: 'Title of the page' + }, + description: { + defaultMessage: + 'By default, your site is always accessible via an OpenCraft subdomain.' + + ' Custom domains allow users to access your site via non-OpenCraft domain names.' + + ' A custom domain increase your monthly OpenCraft bill by €25.', + description: 'Page Explanation' + } +}; + +export default messages; diff --git a/frontend/src/console/components/DomainSettings/index.ts b/frontend/src/console/components/DomainSettings/index.ts new file mode 100644 index 000000000..85b9ea1f9 --- /dev/null +++ b/frontend/src/console/components/DomainSettings/index.ts @@ -0,0 +1 @@ +export * from './DomainSettings'; diff --git a/frontend/src/console/components/DomainSettings/styles.scss b/frontend/src/console/components/DomainSettings/styles.scss new file mode 100644 index 000000000..202422a62 --- /dev/null +++ b/frontend/src/console/components/DomainSettings/styles.scss @@ -0,0 +1,64 @@ +@import "~styles/theme"; + +.domain-settings-container { + font-family: $chivo-font; + padding: 45px; + + .domain-settings-description { + color: $dark-2; + font-size: 16px; + max-width: 716px; + } + + h2 { + color: $dark-1; + font-size: 30px; + text-align: left; + font-weight: 600; + font-family: $playfair-font; + } + + p { + margin: 0; + } + + .addBtn { + font-size: 14px; + } +} + +.add-domain-modal-header { + display: block; + position: absolute; + right: 0px; + border: none; + cursor: pointer; + z-index: 10; + padding-right: 25px; +} + +.add-domain-modal { + padding: 10px; + + .add-domain-modal-description { + color: #797979; + font-size: 16px; + max-width: 716px + } + + h2 { + color: #000000; + font-size: 30px; + text-align: left; + font-weight: 600; + font-family: $playfair-font; + } + + .verify-btn { + margin-right: 0.375rem; + } +} + +.modal-content { + border-radius:4px; +} \ No newline at end of file diff --git a/frontend/src/console/components/InstanceSettings/__snapshots__/InstanceSettings.spec.tsx.snap b/frontend/src/console/components/InstanceSettings/__snapshots__/InstanceSettings.spec.tsx.snap index 6d585ca38..53d6e3a73 100644 --- a/frontend/src/console/components/InstanceSettings/__snapshots__/InstanceSettings.spec.tsx.snap +++ b/frontend/src/console/components/InstanceSettings/__snapshots__/InstanceSettings.spec.tsx.snap @@ -229,11 +229,9 @@ exports[`renders without crashing 1`] = ` General Domain diff --git a/frontend/src/console/components/Logos/__snapshots__/Logos.spec.tsx.snap b/frontend/src/console/components/Logos/__snapshots__/Logos.spec.tsx.snap index 8d34e3839..513dc6157 100644 --- a/frontend/src/console/components/Logos/__snapshots__/Logos.spec.tsx.snap +++ b/frontend/src/console/components/Logos/__snapshots__/Logos.spec.tsx.snap @@ -241,11 +241,9 @@ exports[`renders without crashing 1`] = ` General Domain diff --git a/frontend/src/console/components/ThemeButtons/__snapshots__/ThemeButtons.spec.tsx.snap b/frontend/src/console/components/ThemeButtons/__snapshots__/ThemeButtons.spec.tsx.snap index 8d34e3839..513dc6157 100644 --- a/frontend/src/console/components/ThemeButtons/__snapshots__/ThemeButtons.spec.tsx.snap +++ b/frontend/src/console/components/ThemeButtons/__snapshots__/ThemeButtons.spec.tsx.snap @@ -241,11 +241,9 @@ exports[`renders without crashing 1`] = ` General Domain diff --git a/frontend/src/console/components/ThemeNavigation/__snapshots__/ThemeNavigation.spec.tsx.snap b/frontend/src/console/components/ThemeNavigation/__snapshots__/ThemeNavigation.spec.tsx.snap index 8d34e3839..513dc6157 100644 --- a/frontend/src/console/components/ThemeNavigation/__snapshots__/ThemeNavigation.spec.tsx.snap +++ b/frontend/src/console/components/ThemeNavigation/__snapshots__/ThemeNavigation.spec.tsx.snap @@ -241,11 +241,9 @@ exports[`renders without crashing 1`] = ` General Domain diff --git a/frontend/src/console/components/ThemePreviewAndColors/__snapshots__/ThemePreviewAndColors.spec.tsx.snap b/frontend/src/console/components/ThemePreviewAndColors/__snapshots__/ThemePreviewAndColors.spec.tsx.snap index 418238297..731cb780f 100644 --- a/frontend/src/console/components/ThemePreviewAndColors/__snapshots__/ThemePreviewAndColors.spec.tsx.snap +++ b/frontend/src/console/components/ThemePreviewAndColors/__snapshots__/ThemePreviewAndColors.spec.tsx.snap @@ -205,11 +205,9 @@ exports[`Theme preview and colors page Render theme settings page when instance General Domain @@ -478,11 +476,9 @@ exports[`Theme preview and colors page Render theme settings page with theme set General Domain @@ -1278,11 +1274,9 @@ exports[`Theme preview and colors page Render theme settings page with theme set General Domain @@ -2114,11 +2108,9 @@ exports[`Theme preview and colors page renders without crashing 1`] = ` General Domain diff --git a/frontend/src/console/components/index.ts b/frontend/src/console/components/index.ts index 53e3c59b1..660657d5b 100644 --- a/frontend/src/console/components/index.ts +++ b/frontend/src/console/components/index.ts @@ -43,3 +43,9 @@ export * from './ButtonStyles'; export * from './CourseOutlinePreview'; export * from './CustomizableCourseTab'; + +export * from './DomainSettings'; + +export * from './DomainItem'; + +export * from './AddDomainModalButton'; diff --git a/frontend/src/console/models.ts b/frontend/src/console/models.ts index 760276c73..96aae37f6 100644 --- a/frontend/src/console/models.ts +++ b/frontend/src/console/models.ts @@ -2,7 +2,8 @@ import { OpenEdXInstanceDeploymentStatusStatusEnum, OpenEdXInstanceDeploymentStatusDeploymentTypeEnum, StaticContentOverrides, - ThemeSchema + ThemeSchema, + OpenEdXInstanceConfigUpdateDnsConfigurationStateEnum } from 'ocim-client'; export interface InstanceSettingsModel { @@ -20,6 +21,8 @@ export interface InstanceSettingsModel { logo?: string; favicon?: string; heroCoverImage: null | string; + externalDomain?: string; + dnsConfigurationState?: OpenEdXInstanceConfigUpdateDnsConfigurationStateEnum; } export interface DeploymentNotificationModel { diff --git a/frontend/src/global/constants.ts b/frontend/src/global/constants.ts index 9ee44c588..36f09386e 100644 --- a/frontend/src/global/constants.ts +++ b/frontend/src/global/constants.ts @@ -33,6 +33,9 @@ export const MATOMO_MY_DOMAIN = process.env.REACT_APP_MATOMO_MY_DOMAIN || ''; export const MATOMO_ALIAS_DOMAIN = process.env.REACT_APP_MATOMO_ALIAS_DOMAIN || ''; +export const EXTERNAL_DOMAIN_CNAME_VALUE = + process.env.EXTERNAL_DOMAIN_CNAME_VALUE || 'haproxy.net.opencraft.hosting'; + export interface StringIndexedArray { [key: string]: any; } @@ -65,6 +68,7 @@ export const ROUTES = { THEME_NAVIGATION: '/console/theming/navigation', THEME_FOOTER: '/console/theming/footer', INSTANCE_SETTINGS_GENERAL: '/console/settings/general', + INSTANCE_SETTINGS_DOMAIN: '/console/settings/domain', HERO: '/console/theming/hero', COURSES: '/console/courses/manage' }, diff --git a/frontend/src/newConsole/components/ConsoleHome/__snapshots__/ConsoleHome.spec.tsx.snap b/frontend/src/newConsole/components/ConsoleHome/__snapshots__/ConsoleHome.spec.tsx.snap index 6d585ca38..53d6e3a73 100644 --- a/frontend/src/newConsole/components/ConsoleHome/__snapshots__/ConsoleHome.spec.tsx.snap +++ b/frontend/src/newConsole/components/ConsoleHome/__snapshots__/ConsoleHome.spec.tsx.snap @@ -229,11 +229,9 @@ exports[`renders without crashing 1`] = ` General Domain diff --git a/frontend/src/newConsole/components/newConsolePage/__snapshots__/ConsolePage.spec.tsx.snap b/frontend/src/newConsole/components/newConsolePage/__snapshots__/ConsolePage.spec.tsx.snap index 12ae5bb06..416d31c17 100644 --- a/frontend/src/newConsole/components/newConsolePage/__snapshots__/ConsolePage.spec.tsx.snap +++ b/frontend/src/newConsole/components/newConsolePage/__snapshots__/ConsolePage.spec.tsx.snap @@ -337,11 +337,9 @@ exports[`Console Page Correctly renders loading page 1`] = ` General Domain @@ -634,11 +632,9 @@ exports[`Console Page Correctly renders page with data 1`] = ` General Domain @@ -887,11 +883,9 @@ exports[`Console Page Correctly renders page with email not verified alert 1`] = General Domain diff --git a/frontend/src/newConsole/components/newCustomizationSideMenu/CustomizationSideMenu.tsx b/frontend/src/newConsole/components/newCustomizationSideMenu/CustomizationSideMenu.tsx index 3fe451110..1cdc2ddc9 100644 --- a/frontend/src/newConsole/components/newCustomizationSideMenu/CustomizationSideMenu.tsx +++ b/frontend/src/newConsole/components/newCustomizationSideMenu/CustomizationSideMenu.tsx @@ -93,14 +93,7 @@ export const CustomizationSideMenu: React.FC = () => { - { - e.preventDefault(); - }} - > + diff --git a/frontend/src/newConsole/components/newCustomizationSideMenu/__snapshots__/CustomizationSideMenu.spec.tsx.snap b/frontend/src/newConsole/components/newCustomizationSideMenu/__snapshots__/CustomizationSideMenu.spec.tsx.snap index 64dc63cad..d8bb26e67 100644 --- a/frontend/src/newConsole/components/newCustomizationSideMenu/__snapshots__/CustomizationSideMenu.spec.tsx.snap +++ b/frontend/src/newConsole/components/newCustomizationSideMenu/__snapshots__/CustomizationSideMenu.spec.tsx.snap @@ -167,11 +167,9 @@ exports[`Custom Side Menu Page Renders with correct accordion expanded, and with General Domain diff --git a/frontend/src/routes/console.tsx b/frontend/src/routes/console.tsx index 12ed214f8..eaa4176dd 100644 --- a/frontend/src/routes/console.tsx +++ b/frontend/src/routes/console.tsx @@ -6,7 +6,8 @@ import { InstanceSettings, NoticeBoard, CustomPages, - CoursesManage + CoursesManage, + DomainSettings } from 'console/components'; import { ButtonsCustomization, @@ -26,6 +27,10 @@ export const ConsoleRoutes = () => { path={ROUTES.Console.INSTANCE_SETTINGS_GENERAL} component={InstanceSettings} /> +