diff --git a/app/qml/CMakeLists.txt b/app/qml/CMakeLists.txt index 53c1dc493..b795a448e 100644 --- a/app/qml/CMakeLists.txt +++ b/app/qml/CMakeLists.txt @@ -78,6 +78,8 @@ set(MM_QML onboarding/MMSignUp.qml onboarding/MMWhichIndustry.qml onboarding/MMOnboardingController.qml + onboarding/MMCreateWorkspaceController.qml + onboarding/MMAcceptInvitationController.qml dialogs/MigrateToMerginDialog.qml dialogs/MissingAuthDialog.qml dialogs/NoPermissionsDialog.qml diff --git a/app/qml/ProjectPanel.qml b/app/qml/ProjectPanel.qml index ce0b67dc7..374fc43eb 100644 --- a/app/qml/ProjectPanel.qml +++ b/app/qml/ProjectPanel.qml @@ -64,9 +64,9 @@ Item { } } - function openLoginPage() + function showLogin() { - onboardingController.openLoginPage() + onboardingController.start() } function openChangesPanel() @@ -99,7 +99,7 @@ Item { return false; } // do not show the banner in case of accepting invitation or creating a workspace - if (stackView.currentItem && (stackView.currentItem.objectName === "registrationFinishPanel" || stackView.currentItem.objectName === "createWorkspacePanel")) { + if (onboardingController.inProgress) { return false; } return !__merginApi.userInfo.hasWorkspaces @@ -112,7 +112,7 @@ Item { } onCreateWorkspaceRequested: { - stackView.push(createWorkspaceComponent) + createWorkspaceController.createNewWorkspace() } } @@ -253,7 +253,7 @@ Item { } } else { - root.openLoginPage() + root.showLogin() } } } @@ -496,6 +496,20 @@ Item { stackView: stackView } + MMCreateWorkspaceController { + // TODO move to main.qml + id: createWorkspaceController + enabled: root.visible + stackView: stackView + } + + MMAcceptInvitationController { + // TODO move to main.qml + id: acceptInvitationController + // TODO enabled add controller.showInvitationsList + enabled: root.visible && __merginApi.apiSupportsWorkspaces + stackView: stackView + } Component { id: registrationFinishComponent @@ -605,7 +619,7 @@ Item { function onAuthRequested() { stackView.pending = false - root.openLoginPage() + root.showLogin() } function onAuthChanged() { @@ -622,7 +636,7 @@ Item { } else { // log out - reenable openInvitationsListener - onboardingController.showInvitationList() + acceptInvitationController.showInvitationList = true } } diff --git a/app/qml/main.qml b/app/qml/main.qml index 591ada646..62b768f19 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -200,7 +200,7 @@ ApplicationWindow { onSignInRequested: { stateManager.state = "projects" - projectPanel.openLoginPage() + projectPanel.showLogin() } onLocalChangesPanelRequested: { diff --git a/app/qml/onboarding/MMAcceptInvitation.qml b/app/qml/onboarding/MMAcceptInvitation.qml index deabc63b5..f7f42c04b 100644 --- a/app/qml/onboarding/MMAcceptInvitation.qml +++ b/app/qml/onboarding/MMAcceptInvitation.qml @@ -18,10 +18,14 @@ Page { required property string user required property string workspace + required property string workspaceUuid - signal backClicked - signal continueClicked + property bool haveBack: true + property bool showCreate: true + + signal joinWorkspaceClicked(string workspaceUuid) signal createWorkspaceClicked + signal backClicked readonly property real hPadding: width < __style.maxPageWidth ? 20 * __dp @@ -31,6 +35,17 @@ Page { color: __style.lightGreenColor } + MMHeader { + id: header + + x: mainColumn.leftPadding + y: mainColumn.topPadding + width: parent.width - 2 * root.hPadding + visible: haveBack + + onBackClicked: root.backClicked() + } + ScrollView { width: parent.width height: parent.height @@ -105,17 +120,19 @@ Page { width: parent.width - 2 * root.hPadding text: qsTr("Join workspace") - onClicked: root.continueClicked() + onClicked: root.joinWorkspaceClicked(root.workspaceUuid) } MMHlineText { width: parent.width - 2 * root.hPadding title: qsTr("or") + visible: root.showCreate } MMLinkButton { width: parent.width - 2 * root.hPadding text: qsTr("Create new workspace") + visible: root.showCreate onClicked: root.createWorkspaceClicked() } diff --git a/app/qml/onboarding/MMAcceptInvitationController.qml b/app/qml/onboarding/MMAcceptInvitationController.qml new file mode 100644 index 000000000..f53f4252c --- /dev/null +++ b/app/qml/onboarding/MMAcceptInvitationController.qml @@ -0,0 +1,81 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +import QtQuick + +import "../" +import QtQuick.Controls +import lc 1.0 + +/** + * Show accept invitation page directly without onboarding + * e.g. on app start + */ +Item { + id: controller + + required property bool enabled + required property var stackView + + property bool showInvitationList: false // TODO merge with enabled ? + + Connections { + id: openInvitationsListener + + target: __merginApi + enabled: root.enabled && root.showInvitationList + + function onUserInfoReplyFinished() { + // controller.showInvitationsList = false; + + /* + it should be enabled = false in this case from parent! + + // let's not show invitations when registration finish page is opened + if ( stackView.containsPage("registrationFinishPanel") ) { + return; + } + */ + + if ( !__merginApi.userAuth.hasAuthData() ) { + return; + } + + if ( __merginApi.userInfo.hasInvitations ) { + stackView.push( acceptInvitationsPanelComponent ) + } + } + } + + Connections { + target: __merginApi + enabled: root.enabled && root.showInvitationList + + function onProcessInvitationFinished( accepted ) { + stackView.pop(null) + } + } + + Component { + id: acceptInvitationsPanelComponent + + MMAcceptInvitation { + objectName: "acceptInvitationsPanelDirect" + haveBack: true + showCreate: false + + onBackClicked: { + stackView.popOnePageOrClose() + } + + onJoinWorkspaceClicked: function (workspaceUuid) { + __merginApi.processInvitation( workspaceUuid, true ) + } + } + } +} diff --git a/app/qml/onboarding/MMCreateWorkspace.qml b/app/qml/onboarding/MMCreateWorkspace.qml index ed9fe15bd..a7521c625 100644 --- a/app/qml/onboarding/MMCreateWorkspace.qml +++ b/app/qml/onboarding/MMCreateWorkspace.qml @@ -16,12 +16,17 @@ import "../inputs" Page { id: root - signal continueClicked + signal createWorkspaceClicked(string name) readonly property real hPadding: width < __style.maxPageWidth ? 20 * __dp : (20 + (width - __style.maxPageWidth) / 2) * __dp + // show error message under the respective field + function showErrorMessage( msg ) { + workspaceName.errorMsg = msg + } + Rectangle { anchors.fill: parent color: __style.lightGreenColor @@ -81,6 +86,7 @@ Page { Item { width: 1; height: 1 } MMInputEditor { + id: workspaceName width: parent.width - 2 * root.hPadding title: qsTr("Workspace name") placeholderText: qsTr("Your Workspace") @@ -109,7 +115,7 @@ Page { width: parent.width - 2 * root.hPadding text: qsTr("Create workspace") - onClicked: root.continueClicked() + onClicked: root.createWorkspaceClicked(workspaceName.text) } } } diff --git a/app/qml/onboarding/MMCreateWorkspaceController.qml b/app/qml/onboarding/MMCreateWorkspaceController.qml new file mode 100644 index 000000000..29922967e --- /dev/null +++ b/app/qml/onboarding/MMCreateWorkspaceController.qml @@ -0,0 +1,49 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +import QtQuick + +import "../" +import QtQuick.Controls +import lc 1.0 + +/** + * Directly create workspace but without onboarding questions + * e.g. from account page or projects panel + */ +Item { + id: controller + + required property var stackView + required property bool enabled + + Connections { + target: __merginApi + enabled: controller.enabled + + function onWorkspaceCreated(workspace, result) { + if (result) { + stackView.pop("createWorkspaceDirectPanel") + } + } + } + + Component { + id: createWorkspaceComponent + + MMCreateWorkspace { + id: createWorkspacePanel + + objectName: "createWorkspaceDirectPanel" + + onCreateWorkspaceClicked: function (workspaceName) { + __merginApi.createWorkspace(workspaceName) + } + } + } +} diff --git a/app/qml/onboarding/MMHowYouFoundUs.qml b/app/qml/onboarding/MMHowYouFoundUs.qml index d2681eace..6fa1e821f 100644 --- a/app/qml/onboarding/MMHowYouFoundUs.qml +++ b/app/qml/onboarding/MMHowYouFoundUs.qml @@ -21,7 +21,7 @@ Page { property string selectedText: "" signal backClicked - signal continueClicked(var selectedText) + signal howYouFoundUsSelected(var selectedText) readonly property string headerTitle: qsTr("How did you learn about us?") readonly property real hPadding: width < __style.maxPageWidth @@ -136,6 +136,9 @@ Page { anchors.bottomMargin: 20 * __dp text: qsTr("Continue") - onClicked: root.continueClicked(root.selectedText) + onClicked: { + // TODO not allow in case there is missing selected text! + root.howYouFoundUsSelected(root.selectedText) + } } } diff --git a/app/qml/onboarding/MMOnboardingController.qml b/app/qml/onboarding/MMOnboardingController.qml index 412ecfa59..753d3f839 100644 --- a/app/qml/onboarding/MMOnboardingController.qml +++ b/app/qml/onboarding/MMOnboardingController.qml @@ -9,54 +9,41 @@ import QtQuick import "../" +import QtQuick.Controls +import lc 1.0 -QtObject { +Item { id: controller required property bool enabled required property var stackView + property bool inProgrees: false + QtObject { - id: privateProps + //! Data to send to postRegister endpoint + id: postRegisterData - property bool useOnboarding: true - property bool showInvitationsList: false + property bool wantNewsletter: false + property string howYouFoundUs: "" // one of the category of "other" + property string whichIndustry: "" // one of the category of "other" } - function showInvitationList() + // Start onboarding + function start() { - privateProps.showInvitationsList = true - } + if (controller.inProgress) + return; - // Create workspace but without onboarding questions - function createNewWorkspace() - { - privateProps.useOnboarding = false - stackView.push( createWorkspaceComponent ) - } - - // Create workspace but without onboarding questions - function createNewWorkspace() - { - privateProps.useOnboarding = false - stackView.push( createWorkspaceComponent ) + controller.inProgress = true; + stackView.push( loginPageComp, {}, StackView.PushTransition ) } - // Show login page - function showLogin() + // Finish onboarding + function end() { - privateProps.useOnboarding = true - - for ( let i = 0; i < stackView.depth; i++ ) { - let item = stackView.get( i ) - - if ( item && item.objectName && item.objectName === "loginPage" ) { - // sorry, it is already opened, let's not open it again - return; - } - } - - stackView.push( loginPageComp, {}, StackView.PushTransition ) + controller.inProgress = false; + stackView.pop(null); } Connections { @@ -65,45 +52,19 @@ QtObject { function onRegistrationFailed( msg, field ) { stackView.pending = false - if ( stackView.currentItem.objectName === "registrationPanel" ) { + if ( stackView.currentItem.objectName === "signUpPanel" ) { stackView.currentItem.showErrorMessage(msg, field) } } function onRegistrationSucceeded() { stackView.pending = false - stackView.push( registrationFinishComponent ) + stackView.pushPage(createWorkspaceComponent) } function onWorkspaceCreated(workspace, result) { if (result) { - stackView.popPage("createWorkspacePanel") - } - } - } - - Connections { - id: openInvitationsListener - - property bool showInvitationsList: true - - target: __merginApi - enabled: __merginApi.apiSupportsWorkspaces && openInvitationsListener.showInvitationsList - - function onUserInfoReplyFinished() { - openInvitationsListener.showInvitationsList = false; - - // let's not show invitations when registration finish page is opened - if ( stackView.containsPage("registrationFinishPanel") ) { - return; - } - - if ( !__merginApi.userAuth.hasAuthData() ) { - return; - } - - if ( __merginApi.userInfo.hasInvitations ) { - stackView.push( invitationsPanelComponent ) + stackView.pushPage(howYouFoundUsComponent) } } } @@ -156,7 +117,7 @@ QtObject { Qt.openUrlExternally( __merginApi.apiRoot ) } else { - stackView.push( registrationPanel ) + stackView.push( signUpPanel ) } } @@ -172,11 +133,11 @@ QtObject { } Component { - id: registrationPanel + id: signUpPanel MMSignUp { - objectName: "registrationPanel" + objectName: "signUpPanel" tocString: qsTr("I accept the Mergin %1Terms and Conditions%3 and %2Privacy Policy%3") .arg("") .arg("") @@ -193,7 +154,7 @@ QtObject { stackView.popOnePageOrClose() } - onSignUpClicked: function ( username, email, password, passwordConfirm, tocAccept ) { + onSignUpClicked: function ( username, email, password, passwordConfirm, tocAccept, newsletterSubscribe ) { if ( __merginApi.serverType !== MerginServerType.SAAS ) { return; //should not happen } @@ -204,6 +165,8 @@ QtObject { password, passwordConfirm, tocAccept ) + + postRegisterData.wantNewsletter = newsletterSubscribe } } } @@ -212,35 +175,79 @@ QtObject { Component { id: createWorkspaceComponent - CreateWorkspacePage { + MMCreateWorkspace { id: createWorkspacePanel objectName: "createWorkspacePanel" - onBack: { + onCreateWorkspaceClicked: function (workspaceName) { + __merginApi.createWorkspace(workspaceName) + } + } + } + + Component { + id: howYouFoundUsComponent + + MMHowYouFoundUs { + id: howYouFoundUsPanel + + objectName: "howYouFoundUsPanel" + onBackClicked: { + stackView.popOnePageOrClose() + } + + onHowYouFoundUsSelected: function (selectedText) { + postRegisterData.howYouFoundUs = selectedText + stackView.push(whichIndustryComponent) + } + } + } + + Component { + id: whichIndustryComponent + + MMWhichIndustry { + id: whichIndustryPanel + + objectName: "whichIndustryPanel" + + onBackClicked: { stackView.popOnePageOrClose() } + + onIndustrySelected: function (selectedText) { + postRegisterData.whichIndustry = selectedText + console.log("TODO: finished onboarding?? - CALL postRegister/") + } } } Component { - id: invitationsPanelComponent + id: acceptInvitationsPanelComponent + + MMAcceptInvitation { + objectName: "acceptInvitationsPanel" + haveBack: false + showCreate: true - ManageInvitationsPage { - objectName: "invitationsPanel" - haveBack: true - showCreate: false - onBack: { - stackView.pop( null ) + onJoinWorkspaceClicked: function (workspaceUuid) { + __merginApi.processInvitation( workspaceUuid, true ) + } + + onCreateWorkspaceClicked: { + if (stackView.containsPage("createWorkspacePanel")) + { + stackView.popOnePageOrClose() + } else { + stackView.push(createWorkspaceComponent) + } } Connections { target: __merginApi function onProcessInvitationFinished( accepted ) { - stackView.pop(null) - if ( __merginApi.userInfo.hasWorkspaces && accepted ) { - stackView.push(workspaceListComponent) - } + controller.end() } } } diff --git a/app/qml/onboarding/MMSignUp.qml b/app/qml/onboarding/MMSignUp.qml index 03f59f0b1..9c1e4bd29 100644 --- a/app/qml/onboarding/MMSignUp.qml +++ b/app/qml/onboarding/MMSignUp.qml @@ -29,7 +29,7 @@ Page { signal backClicked signal signInClicked - signal signUpClicked ( string username, string email, string password, string passwordConfirm, bool tocAccept ) + signal signUpClicked ( string username, string email, string password, string passwordConfirm, bool tocAccept, bool newsletterSubscribe ) required property string tocString readonly property real hPadding: width < __style.maxPageWidth @@ -44,7 +44,7 @@ Page { email.errorMsg = "" password.errorMsg = "" passwordConfirm.errorMsg = "" - tocAccept.errorMsg = "" + // TODO tocAccept.errorMsg = "" // TODO errorText.text = "" if( field === RegistrationError.USERNAME ) { @@ -64,8 +64,10 @@ Page { passwordConfirm.focus = true } else if( field === RegistrationError.TOC ) { - tocAccept.errorMsg = msg - tocAccept.focus = true + // TODO where to show MMCheckBox missing errorMsg + // tocAccept.errorMsg = msg + // tocAccept.focus = true + console.log("error " + msg) } else if( field === RegistrationError.OTHER ) { // TODO where to show @@ -181,6 +183,30 @@ Page { } } + Row { + width: parent.width + spacing: 10 * __dp + + MMCheckBox { + id: newsletterSubscribe + + width: 24 * __dp + anchors.verticalCenter: parent.verticalCenter + } + + Text { + width: parent.width - newsletterSubscribe.width - parent.spacing - 2 * root.hPadding + anchors.verticalCenter: parent.verticalCenter + + text: qsTr("I want to subscribe to the newsletter") + font: __style.p5 + color: __style.nightColor + linkColor: __style.forestColor + wrapMode: Text.WordWrap + lineHeight: 1.5 + } + } + Item { width: 1; height: 1 } MMButton { @@ -193,7 +219,8 @@ Page { email.text, password.text, passwordConfirm.text, - tocAccept.checked + tocAccept.checked, + newsletterSubscribe.checked ) } } diff --git a/app/qml/onboarding/MMWhichIndustry.qml b/app/qml/onboarding/MMWhichIndustry.qml index 628a4ba5e..e8a0bbd9c 100644 --- a/app/qml/onboarding/MMWhichIndustry.qml +++ b/app/qml/onboarding/MMWhichIndustry.qml @@ -21,7 +21,7 @@ Page { property string selectedText: "" signal backClicked - signal continueClicked(var selectedText) + signal industrySelected(var selectedText) readonly property string headerTitle: qsTr("In which industry do you work?") readonly property real hPadding: width < __style.maxPageWidth @@ -142,6 +142,9 @@ Page { anchors.bottomMargin: 20 * __dp text: qsTr("Continue") - onClicked: root.continueClicked(root.selectedText) + onClicked: { + // TODO not allow in case there is missing selected text! + root.industrySelected(root.selectedText) + } } }