From 58076ed31079d81a68ce4a43b454c5b3a1850c58 Mon Sep 17 00:00:00 2001 From: Yash Maheshwari Date: Wed, 23 Oct 2024 11:49:11 +0530 Subject: [PATCH] Improved: UI of the app and added support to configure instance from the same page(#33) --- src/components/OmsModal.vue | 60 ++++++++++++ src/views/ShopifyInstall.vue | 173 ++++++++++++++++++++++++++++------- 2 files changed, 200 insertions(+), 33 deletions(-) create mode 100644 src/components/OmsModal.vue diff --git a/src/components/OmsModal.vue b/src/components/OmsModal.vue new file mode 100644 index 0000000..6ee801d --- /dev/null +++ b/src/components/OmsModal.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/views/ShopifyInstall.vue b/src/views/ShopifyInstall.vue index 86837c5..40c0485 100644 --- a/src/views/ShopifyInstall.vue +++ b/src/views/ShopifyInstall.vue @@ -4,13 +4,52 @@
- - {{ $t('Checkout our app on the app store!') }} - - {{ $t('View app') }} - - + + + + {{ $t('Installing HotWax Commerce onto your Shopify store...') }} +

{{ $t("You'll be auto redirected to your Shopify store to complete the installation process") }}

+
+
+ + + + {{ $t('HotWax Commerce is installed on your Shopify store') }} +

{{ shop }}

+
+
+ + + + {{ $t('Syncing Shopify store to HotWax Commerce') }} + {{ $t('Shopify store synced with HotWax Commerce') }} + + + + + {{ $t('Learn more about connecting HotWax Commerce with Shopify') }} + +
@@ -22,20 +61,24 @@ import { IonButton, IonContent, IonIcon, + IonInput, IonItem, IonLabel, IonList, IonPage, + IonProgressBar, + modalController } from "@ionic/vue"; import { defineComponent } from "vue"; import { hasError, showToast } from "@/utils"; import { useRouter } from "vue-router"; -import { generateAccessToken, getApiKey, verifyRequest } from "@/services" -import { useStore } from "vuex"; +import { generateAccessToken, getApiKey, setConfiguration, verifyRequest } from "@/services" +import { mapGetters, useStore } from "vuex"; import Logo from '@/components/Logo.vue'; import { loadingController } from '@ionic/vue'; -import { openOutline } from 'ionicons/icons' +import { arrowForwardOutline, bookOutline, checkmarkCircleOutline, closeCircleOutline, cloudDownloadOutline, cloudUploadOutline } from 'ionicons/icons' import { translate } from "@/i18n"; +import OmsModal from "@/components/OmsModal.vue"; export default defineComponent({ name: "ShopifyInstall", @@ -43,16 +86,18 @@ export default defineComponent({ IonButton, IonContent, IonIcon, + IonInput, IonItem, IonLabel, IonList, IonPage, + IonProgressBar, Logo }, data() { return { loader: null as any, - apiKey: 'ec8cec8c4299d0ea17269da567eebc28', + apiKey: '61a49df39017a4481e2aaf633900cea9', session: this.$route.query['session'], hmac: this.$route.query['hmac'], shop: this.$route.query['shop'], @@ -62,14 +107,26 @@ export default defineComponent({ code: this.$route.query['code'], state: this.$route.query['state'], embedded: this.$route.query['embedded'], - scopes: '' + scopes: '', + authenticationInProgress: false, + isAppInstalled: false, + syncDetailsToShopify: false, + instanceAddress: '', + instanceToken: '', + isConfigUpdated: false, + payload: {} }; }, + computed: { + ...mapGetters({ + connectConfig: 'shop/getConfig' + }) + }, async mounted() { - await this.presentLoader(); const shop: string = this.shop as string if (this.session) { + this.syncDetailsToShopify = true; const apiKey = await this.getApiKey(shop); if (apiKey) { try { @@ -82,7 +139,6 @@ export default defineComponent({ if(this.scopes) { await this.authorise(shop, undefined, false); } else { - this.dismissLoader(); showToast(translate("Failed to get the access scopes")) console.error('Failed to get the access scopes') return; @@ -92,35 +148,50 @@ export default defineComponent({ window.location.replace(resp.instanceAddress) } else { console.error('Failed to fetch the instance details') + this.isAppInstalled = true; + this.syncDetailsToShopify = false; let query = this.$route.fullPath.split("?")[1] if(!query.includes('clientId')) { query += `&clientId=${apiKey}` } - this.$router.push(`/configure?${query}`); + + this.payload = query ? query.split('&').reduce((params: any, param) => { + const [key, value] = param.split('=') + params[key] = value + return params; + }, {}) : {} + + if(!Object.keys(this.payload).length) { + this.router.push('/') + } } } catch(err: any) { - this.dismissLoader(); + this.isAppInstalled = false; + this.syncDetailsToShopify = false; showToast(translate("Failed to verify the request, please try again")) console.error('error', err) return; } } else { console.error('Api key not found') + this.isAppInstalled = false; + this.syncDetailsToShopify = false; + showToast(translate("Failed to verify the request, please try again")) this.router.push('/') } } else if (this.code) { - this.loader.message = "Verifying request..." + this.syncDetailsToShopify = true; const nonce = localStorage.getItem('nonce') if(nonce !== this.state) { - console.error('Failed to authenticate the redirect request') + this.syncDetailsToShopify = false; this.router.push('/') return; } const apiKey = await this.getApiKey(shop); if (apiKey) { - this.loader.message = "Fetching token..." + // this.loader.message = "Fetching token..." const payload = this.getQueryParams() try { @@ -137,6 +208,7 @@ export default defineComponent({ throw resp.data } } catch(err) { + this.syncDetailsToShopify = false; showToast(translate('Failed to fetch the token')) console.error('err', err) } @@ -144,8 +216,8 @@ export default defineComponent({ showToast(translate('Failed to find the api key')) console.error('Api key not found') this.router.push('/') + this.syncDetailsToShopify = false; } - this.dismissLoader() } else if (this.shop || this.host) { const query = JSON.parse(JSON.stringify(this.$route.query)) if (this.embedded === "1") { @@ -156,26 +228,25 @@ export default defineComponent({ const redirectUri = process.env.VUE_APP_SHOPIFY_REDIRECT_URI; const url = new URL(decodeURIComponent(shop.startsWith("https") ? shop : `https://${shop}/`)); if (url.hostname === location.hostname) { - const apiKey = await this.getApiKey(shop); + await this.getApiKey(shop); // Check why this condition is required } else { // TODO Remove this. Fallback window.location.assign(redirectUri + "?" + updatedQuery); } } else { + this.authenticationInProgress = true // Using await as if not used then the loader gets dismissed await this.authorise(shop, this.host); } - this.dismissLoader() } - this.dismissLoader() }, methods: { async verifyRequest() { try { const payload = this.getQueryParams() - this.loader.message = "Verifying request..." + // this.loader.message = "Verifying request..." const resp = await verifyRequest({ ...payload, clientId: this.apiKey @@ -191,23 +262,19 @@ export default defineComponent({ } }, async authorise(shop: any, host: any, verify = true) { - await this.presentLoader(); - if(verify) { try { // Only checking whether the request is valid or not, not checking for the requestAuthorizationCode - const resp = await this.verifyRequest(); + const resp = await this.verifyRequest() as any; this.scopes = resp.scopes; } catch(err: any) { - this.dismissLoader(); showToast(translate("Failed to verify the request, please try again")) console.error('error', err) + this.authenticationInProgress = false return; } } - this.loader.message = "Redirecting..." - const redirectUri = process.env.VUE_APP_SHOPIFY_REDIRECT_URI; const apiKey = await this.getApiKey(shop); if (apiKey && this.scopes) { @@ -221,11 +288,11 @@ export default defineComponent({ if(!this.scopes) { message = 'Access scopes not found' } + this.authenticationInProgress = false console.error(message) showToast(translate(message)) this.router.push('/') } - this.dismissLoader(); }, async getApiKey(shop: string) { let apiKey = this.apiKey; @@ -278,8 +345,38 @@ export default defineComponent({ return params; }, {}) : {} }, - goToShopifyAppStore() { - window.location.assign('https://apps.shopify.com/hotwax-order-management') + async goToLearnMoreDocument() { + await this.presentLoader(); + this.loader.message = "Redirecting..." + window.location.assign('https://docs.hotwax.co/documents/v/learn-shopify/setup-shopify/shopifyintegration') + this.dismissLoader(); + }, + async updateConnectConfig() { + try { + const resp = await setConfiguration(this.payload, { + 'instanceAddress': this.instanceAddress, + 'instanceToken': this.instanceToken + }) + // TODO Update specific payload + if (resp.status === 200 && !hasError(resp)) { + showToast(translate('HotWax Commerce connection settings updated')) + this.isConfigUpdated = true + window.location.replace(this.instanceAddress) + } else { + throw resp.data + } + } catch(error){ + console.error(error) + showToast(translate("Something went wrong")); + this.isConfigUpdated = false + } + }, + async openOmsModal() { + const omsModal = await modalController.create({ + component: OmsModal + }); + + omsModal.present(); } }, ionViewWillLeave() { @@ -289,7 +386,12 @@ export default defineComponent({ const store = useStore(); const router = useRouter(); return { - openOutline, + arrowForwardOutline, + bookOutline, + checkmarkCircleOutline, + closeCircleOutline, + cloudDownloadOutline, + cloudUploadOutline, router, store, showToast, @@ -309,4 +411,9 @@ export default defineComponent({ ion-list { max-width: 375px; } + +.ion-item-button::part(native) { + background-color: #F5F6F9; + border-radius: 20px; +} \ No newline at end of file