refactor: improved callbackfeedback and modal usage

This commit is contained in:
Zack Spear
2023-06-30 16:14:39 -07:00
parent 6b2d75dd9e
commit dc4a05916e
13 changed files with 239 additions and 207 deletions

View File

@@ -51,7 +51,7 @@ const serverState = {
"locale": "en_US",
"name": "fuji",
// "pluginInstalled": "dynamix.unraid.net.staging.plg",
"pluginInstalled": true,
"pluginInstalled": false,
"registered": true,
"regGen": 0,
"regGuid": "0781-5583-8355-81071A2B0211",

View File

@@ -1,21 +1,21 @@
<script setup lang="ts">
import { XCircleIcon } from '@heroicons/vue/24/solid';
export interface Props {
btnStyle?: 'fill' | 'outline';
download?: boolean;
external?: boolean;
href?: string;
icon?: typeof XCircleIcon;
style?: 'fill' | 'outline';
text?: string;
}
const props = withDefaults(defineProps<Props>(), {
style: 'fill',
btnStyle: 'fill',
});
defineEmits(['click']);
const classes = computed(() => {
switch (props.style) {
switch (props.btnStyle) {
case 'fill':
return 'text-white bg-gradient-to-r from-unraid-red to-orange hover:from-unraid-red/60 hover:to-orange/60 focus:from-unraid-red/60 focus:to-orange/60';
case 'outline':
@@ -31,14 +31,10 @@ const classes = computed(() => {
:href="href"
:rel="external ? 'noopener noreferrer' : ''"
:target="external ? '_blank' : ''"
class="text-14px text-center flex-none flex flex-row items-center justify-center gap-x-8px px-8px py-8px cursor-pointer rounded-md"
class="text-14px text-center font-semibold flex-none flex flex-row items-center justify-center gap-x-8px px-8px py-8px cursor-pointer rounded-md"
:class="classes"
>
<component v-if="icon" :is="icon" class="flex-shrink-0 w-14px" />
{{ text }}
</component>
</template>
<style scoped>
</style>

View File

@@ -51,7 +51,7 @@ const ariaLablledById = computed((): string|undefined => props.title ? `ModalTit
>
<div
@click="closeModal"
class="fixed inset-0 z-0 bg-black bg-opacity-50 transition-opacity"
class="fixed inset-0 z-0 bg-black bg-opacity-80 transition-opacity"
title="Click to close modal"
/>
</TransitionChild>
@@ -73,18 +73,33 @@ const ariaLablledById = computed((): string|undefined => props.title ? `ModalTit
success ? 'shadow-green-600/30 border-green-600/10' : '',
!error && !success ? 'shadow-orange/10 border-white/10' : '',
]"
class="text-alpha bg-beta border-2 border-solid relative transform overflow-hidden rounded-lg px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:p-6"
class="text-beta bg-alpha border-2 border-solid relative transform overflow-hidden rounded-lg px-4 pb-4 pt-5 sm:my-8 sm:p-6 text-left shadow-xl transition-all sm:w-full"
>
<div v-if="showCloseX" class="absolute z-20 right-0 top-0 hidden pt-2 pr-2 sm:block">
<button @click="closeModal" type="button" class="rounded-md text-alpha bg-beta p-2 hover:text-white focus:text-white hover:bg-unraid-red focus:bg-unraid-red focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<button @click="closeModal" type="button" class="rounded-md text-beta bg-alpha p-2 hover:text-white focus:text-white hover:bg-unraid-red focus:bg-unraid-red focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<span class="sr-only">Close</span>
<XMarkIcon class="h-6 w-6" aria-hidden="true" />
</button>
</div>
<h1 v-if="title" :id="ariaLablledById">{{ title }}</h1>
<h2 v-if="description">{{ description }}</h2>
<slot />
<header class="text-center">
<template v-if="!$slots['header']">
<h1 v-if="title" :id="ariaLablledById" class="text-24px font-semibold flex flex-wrap justify-center gap-x-1">
{{ title }}
<slot name="headerTitle"></slot>
</h1>
<h2 v-if="description" class="text-16px opacity-75">{{ description }}</h2>
</template>
<slot name="header"></slot>
</header>
<slot name="main"></slot>
<footer class="text-14px relative -mx-4px -mb-4px sm:-mx-6 sm:-mb-6 p-4 sm:p-6">
<div class="absolute z-0 inset-0 opacity-10 bg-beta"></div>
<div class="relative z-10">
<slot name="footer"></slot>
</div>
</footer>
</div>
</TransitionChild>
</div>

View File

@@ -1,12 +1,14 @@
<script lang="ts" setup>
import { useClipboard } from '@vueuse/core'
import { ClipboardIcon } from '@heroicons/vue/24/solid';
import { ClipboardIcon, InformationCircleIcon } from '@heroicons/vue/24/solid';
import { storeToRefs } from 'pinia';
import 'tailwindcss/tailwind.css';
import '~/assets/main.css';
import { useAccountStore } from '~/store/account';
import { useCallbackActionsStore } from '~/store/callbackActions';
import { useInstallKeyStore } from '~/store/installKey';
import { usePromoStore } from '~/store/promo';
import { useServerStore } from '~/store/server';
export interface Props {
open?: boolean;
@@ -19,98 +21,131 @@ withDefaults(defineProps<Props>(), {
const accountStore = useAccountStore();
const callbackActionsStore = useCallbackActionsStore();
const installKeyStore = useInstallKeyStore();
const promoStore = usePromoStore();
const serverStore = useServerStore();
const { accountActionStatus, accountActionStatusCopy } = storeToRefs(accountStore);
const { callbackData, callbackStatus } = storeToRefs(callbackActionsStore);
const { keyUrl, keyInstallStatus, keyInstallStatusCopy, keyType } = storeToRefs(installKeyStore);
const {
accountActionStatus,
accountActionStatusCopy,
} = storeToRefs(accountStore);
const {
callbackStatus,
} = storeToRefs(callbackActionsStore);
const {
keyActionType,
keyUrl,
keyInstallStatus,
keyInstallStatusCopy,
keyType,
} = storeToRefs(installKeyStore);
const {
pluginInstalled,
} = storeToRefs(serverStore);
/** @todo if post purchase/upgrade thank user for their purchase and support */
/** @todo if post purchase/upgrade and no Connect, show CTA to Connect promo */
/** @todo if signing in show CTA to head to Connect settings to enable features */
const heading = computed(() => callbackStatus.value === 'loading' ? 'Performing actions' : 'Finished performing actions');
const subheading = computed(() => callbackStatus.value === 'loading' ? 'Please keep this window open' : '');
const modalError = computed(() => callbackStatus.value === 'done' && (keyInstallStatus.value === 'failed' || accountActionStatus.value === 'failed'));
const modalSuccess = computed(() => {
if (!callbackData.value) return false;
// if we have multiple actions, we need both to be successful
return callbackData.value.actions.length > 1
? callbackStatus.value === 'done' && (keyInstallStatus.value === 'success' && accountActionStatus.value === 'success')
: callbackStatus.value === 'done' && (keyInstallStatus.value === 'success' || accountActionStatus.value === 'success');
const heading = computed(() => {
switch (callbackStatus.value) {
case 'error':
return 'Error';
case 'loading':
return 'Performing actions';
case 'success':
return 'Success!';
}
});
const subheading = computed(() => {
switch (callbackStatus.value) {
case 'loading':
return 'Please keep this window open while we perform some actions';
default:
return '';
}
});
// @todo keep for now as we may us`e this rather than refreshing once GQL is hooked up
// const close = () => {
// if (callbackStatus.value === 'loading') return console.debug('[close] not allowed');
// callbackActionsStore.setCallbackStatus('ready');
// };
// @close="close"
// :show-close-x="!callbackStatus === 'loading'"
const close = () => {
if (callbackStatus.value === 'loading') return console.debug('[close] not allowed');
window.location.reload();
// callbackActionsStore.setCallbackStatus('ready');
};
const reload = () => window.location.reload();
const promoClick = () => {
promoStore.openOnNextLoad();
close();
};
const { text, copy, copied, isSupported } = useClipboard({ source: keyUrl.value });
watch(callbackStatus, (n, o) => console.debug('[callbackStatus]', n, o));
watch(accountActionStatus, (n, o) => console.debug('[accountActionStatus]', n, o));
watch(keyInstallStatus, (n, o) => console.debug('[keyInstallStatus]', n, o));
watch(modalError, (n, o) => console.debug('[modalError]', n, o));
watch(modalSuccess, (n, o) => console.debug('[modalSuccess]', n, o));
</script>
<template>
<Modal
:title="heading"
:description="subheading"
:open="open"
max-width="max-w-640px"
:error="modalError"
:success="modalSuccess"
:error="callbackStatus === 'error'"
:success="callbackStatus === 'success'"
@close="close"
:show-close-x="callbackStatus !== 'loading'"
>
<div class="text-16px text-center relative w-full min-h-[20vh] flex flex-col justify-between gap-y-16px">
<header>
<h1 class="text-24px font-semibold">{{ heading }}</h1>
<p v-if="subheading" class="text-16px opacity-75">{{ subheading }}</p>
</header>
<!-- <BrandLoading v-if="callbackStatus === 'loading'" class="w-90px mx-auto" /> -->
<UpcCallbackFeedbackStatus
v-if="keyInstallStatus !== 'ready'"
:success="keyInstallStatus === 'success'"
:error="keyInstallStatus === 'failed'"
:text="keyInstallStatusCopy.text"
>
<UpcUptimeExpire v-if="keyType === 'Trial'" :for-expire="true" class="opacity-75 italic mt-4px" />
<template v-if="keyInstallStatus === 'failed'">
<div v-if="isSupported" class="flex justify-center">
<BrandButton
@click="copy(keyUrl)"
:icon="ClipboardIcon"
:text="copied ? 'Copied' : 'Copy Key URL'" />
</div>
<p v-else>Copy your Key URL: {{ keyUrl }}</p>
<p><a href="/Tools/Registration" class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition">Then go to Tools > Registration to manually install it</a></p>
</template>
</UpcCallbackFeedbackStatus>
<UpcCallbackFeedbackStatus
v-if="accountActionStatus !== 'ready'"
:success="accountActionStatus === 'success'"
:error="accountActionStatus === 'failed'"
:text="accountActionStatusCopy.text" />
<footer>
<div v-if="modalSuccess" class="w-full max-w-xs flex flex-col gap-y-16px mx-auto">
<button
@click="reload"
class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition"
<template #main>
<div class="text-16px text-center relative w-full min-h-[15vh] flex flex-col justify-center gap-y-16px">
<div
v-if="keyInstallStatus !== 'ready' || accountActionStatus !== 'ready'"
class="flex flex-col gap-y-16px"
>
<UpcCallbackFeedbackStatus
v-if="keyInstallStatus !== 'ready'"
:success="keyInstallStatus === 'success'"
:error="keyInstallStatus === 'failed'"
:text="keyInstallStatusCopy.text"
>
{{ 'Reload Page to Finalize' }}
</button>
<UpcUptimeExpire v-if="keyType === 'Trial'" :for-expire="true" class="opacity-75 italic mt-4px" />
<template v-if="keyInstallStatus === 'failed'">
<div v-if="isSupported" class="flex justify-center">
<BrandButton
@click="copy(keyUrl)"
:icon="ClipboardIcon"
:text="copied ? 'Copied' : 'Copy Key URL'" />
</div>
<p v-else>{{ 'Copy your Key URL' }}: {{ keyUrl }}</p>
<p><a href="/Tools/Registration" class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition">{{ 'Then go to Tools > Registration to manually install it' }}</a></p>
</template>
</UpcCallbackFeedbackStatus>
<UpcCallbackFeedbackStatus
v-if="accountActionStatus !== 'ready'"
:success="accountActionStatus === 'success'"
:error="accountActionStatus === 'failed'"
:text="accountActionStatusCopy.text" />
</div>
</footer>
</div>
</div>
</template>
<template #footer>
<div v-if="callbackStatus === 'success'" class="flex flex-col gap-y-16px">
<div v-if="!pluginInstalled" class="text-center flex flex-col justify-center gap-y-8px">
<p>{{ 'Enhance your Unraid experience with Unraid Connect' }}</p>
<span class="inline-flex justify-center">
<BrandButton
@click="promoClick"
:icon="InformationCircleIcon"
:text="'Learn More'"
/>
</span>
</div>
<button
@click="close"
class="opacity-75 hover:opacity-100 focus:opacity-100 underline transition"
>
{{ !pluginInstalled ? 'No Thanks' : 'Close' }}
</button>
</div>
</template>
</Modal>
</template>

View File

@@ -3,7 +3,6 @@ import { storeToRefs } from 'pinia';
import { ArrowRightOnRectangleIcon, ArrowTopRightOnSquareIcon, BarsArrowDownIcon, CogIcon, InformationCircleIcon, UserIcon } from '@heroicons/vue/24/solid';
import { ACCOUNT, CONNECT_DASHBOARD, PLUGIN_SETTINGS } from '~/helpers/urls';
import { useDropdownStore } from '~/store/dropdown';
import { usePromoStore } from '~/store/promo';
import { useServerStore } from '~/store/server';
import type { UserProfileLink } from '~/types/userProfile';
@@ -12,7 +11,6 @@ import type { ServerStateDataAction } from '~/types/server';
const myServersEnv = ref<string>('Staging');
const devEnv = ref<string>('development');
const dropdownStore = useDropdownStore();
const promoStore = usePromoStore();
const { keyActions, pluginInstalled, registered, stateData } = storeToRefs(useServerStore());
@@ -59,7 +57,6 @@ const links = computed(():UserProfileLink[] => {
{
click: () => {
promoStore.promoShow();
dropdownStore.dropdownHide();
},
icon: InformationCircleIcon,
text: 'Enhance your Unraid experience with Connect',

View File

@@ -62,32 +62,32 @@ const installButtonClasses = 'text-white text-14px text-center w-full flex flex-
<template>
<Modal
title="Introducing Unraid Connect"
description="Enhance your Unraid experience"
:open="open"
@close="promoStore.promoHide()"
:show-close-x="true"
max-width="max-w-800px"
>
<div class="text-center relative w-full md:p-24px">
<header>
<h1 class="text-24px font-semibold flex flex-wrap justify-center gap-x-1">
Introducing Unraid Connect
<span><UpcBeta class="relative -top-1" /></span>
</h1>
<h2 class="text-20px">
Enhance your Unraid experience
</h2>
</header>
<template #headerTitle>
<span><UpcBeta class="relative -top-1" /></span>
</template>
<div class="flex flex-wrap justify-center my-16px md:my-24px">
<UpcPromoFeature
v-for="(feature, index) in features"
:key="index"
:title="feature.title"
:copy="feature.copy"
/>
<template #main>
<div class="text-center relative w-full">
<div class="flex flex-wrap justify-center my-16px md:my-24px">
<UpcPromoFeature
v-for="(feature, index) in features"
:key="index"
:title="feature.title"
:copy="feature.copy"
/>
</div>
</div>
</template>
<div class="w-full max-w-xs flex flex-col gap-y-16px mx-auto">
<template #footer>
<div class="w-full max-w-xs flex flex-col items-center gap-y-16px mx-auto">
<!-- v-if="devEnv" -->
<SwitchGroup as="div" class="flex items-center justify-center">
<Switch v-model="staging" :class="[staging ? 'bg-indigo-600' : 'bg-gray-200', 'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2']">
@@ -115,7 +115,7 @@ const installButtonClasses = 'text-white text-14px text-center w-full flex flex-
</button>
</div>
</div>
</div>
</template>
</Modal>
</template>

View File

@@ -20,14 +20,14 @@ defineProps<Props>();
<span v-if="!center" class="flex-shrink-0">
<slot></slot>
</span>
<div class="inline-flex flex-col pl-4 mr-4" :class="{ 'text-center': center }">
<h3 class="text-14px font-semibold" :class="{ 'mt-0 mb-4px': copy, 'my-0': !copy, 'flex flex-row justify-center items-center': center }">
<div class="inline-flex flex-col" :class="{ 'text-center': center }">
<h3 class="text-16px font-semibold" :class="{ 'mt-0 mb-4px': copy, 'my-0': !copy, 'flex flex-row justify-center items-center': center }">
<span v-if="center" class="flex-shrink-0 mr-8px">
<slot></slot>
</span>
{{ title }}
</h3>
<p v-if="copy" v-html="copy" class="text-12px opacity-90 py-0" :class="{'px-8px': center}"></p>
<p v-if="copy" v-html="copy" class="text-14px opacity-75 py-0" :class="{'px-8px': center}"></p>
</div>
</div>
</template>

View File

@@ -18,6 +18,8 @@ export const useAccountStore = defineStore('account', () => {
const accountAction = ref<ExternalSignIn|ExternalSignOut>();
const accountActionStatus = ref<'failed' | 'ready' | 'success' | 'updating'>('ready');
const username = ref<string>('');
// Actions
const recover = () => {
console.debug('[accountStore.recover]');
@@ -70,17 +72,20 @@ export const useAccountStore = defineStore('account', () => {
*/
const updatePluginConfig = async (action: ExternalSignIn | ExternalSignOut) => {
console.debug('[accountStore.updatePluginConfig]', action);
// save any existing username before updating
if (serverStore.username) username.value = serverStore.username;
accountAction.value = action;
accountActionStatus.value = 'updating';
const userPayload = {
...(action.user
...(accountAction.value.user
? {
apikey: action.apiKey,
apikey: accountAction.value.apiKey,
// avatar: '',
email: action.user?.email,
email: accountAction.value.user?.email,
regWizTime: `${Date.now()}_${serverStore.guid}`, // set when signing in the first time and never unset for the sake of displaying Sign In/Up in the UPC without needing to validate guid every time
username: action.user?.preferred_username,
username: accountAction.value.user?.preferred_username,
}
: {
accesstoken: '',
@@ -126,20 +131,20 @@ export const useAccountStore = defineStore('account', () => {
case 'updating':
return {
text: accountAction.value?.type === 'signIn'
? 'Signing in...'
: 'Signing out...',
? `Signing in ${accountAction.value.user?.preferred_username}...`
: `Signing out ${username.value}...`,
};
case 'success':
return {
text: accountAction.value?.type === 'signIn'
? 'Signed in successfully'
: 'Signed out successfully',
? `Signed ${accountAction.value.user?.preferred_username} In Successfully`
: `Signed Out ${username.value} Successfully`,
};
case 'failed':
return {
text: accountAction.value?.type === 'signIn'
? 'Sign in failed'
: 'Sign out failed',
? 'Sign In Failed'
: 'Sign Out Failed',
};
}
});

View File

@@ -1,9 +1,32 @@
import AES from 'crypto-js/aes';
import Utf8 from 'crypto-js/enc-utf8';
import { ref } from 'vue';
import { defineStore, createPinia, setActivePinia } from 'pinia';
export interface ServerAccountCallbackServerData {
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
*/
setActivePinia(createPinia());
export type SignIn = 'signIn';
export type SignOut = 'signOut';
export type OemSignOut = 'oemSignOut';
export type Troubleshoot = 'troubleshoot';
export type Recover = 'recover';
export type Replace = 'replace';
export type TrialExtend = 'trialExtend';
export type TrialStart = 'trialStart';
export type Purchase = 'purchase';
export type Redeem = 'redeem';
export type Upgrade = 'upgrade';
export type AccountAction = SignIn | SignOut | OemSignOut | Troubleshoot;
export type AccountKeyAction = Recover | Replace | TrialExtend | TrialStart;
export type PurchaseAction = Purchase | Redeem | Upgrade;
export type ServerAction = AccountAction | AccountKeyAction | PurchaseAction;
export interface ServerData {
description?: string;
deviceCount?: number;
expireTime?: number;
@@ -20,26 +43,9 @@ export interface ServerAccountCallbackServerData {
wanFQDN?: string;
}
export type SignIn = 'signIn';
export type SignOut = 'signOut';
export type Troubleshoot = 'troubleshoot';
export type Recover = 'recover';
export type Replace = 'replace';
export type TrialExtend = 'trialExtend';
export type TrialStart = 'trialStart';
export type Purchase = 'purchase';
export type Redeem = 'redeem';
export type Upgrade = 'upgrade';
export type AccountAction = SignIn | SignOut | Troubleshoot;
export type AccountKeyAction = Recover | Replace | TrialExtend | TrialStart;
export type PurchaseAction = Purchase | Redeem | Upgrade;
export type ServerStateDataActionType = AccountAction | AccountKeyAction | PurchaseAction;
export interface ServerPayload {
server: ServerAccountCallbackServerData;
type: ServerStateDataActionType;
server: ServerData;
type: ServerAction;
}
export interface ExternalSignIn {
@@ -48,7 +54,7 @@ export interface ExternalSignIn {
user: UserInfo;
}
export interface ExternalSignOut {
type: SignOut;
type: SignOut | OemSignOut;
}
export interface ExternalKeyActions {
type: PurchaseAction | AccountKeyAction;
@@ -92,12 +98,6 @@ interface CallbackActionsStore {
sendType: 'fromUpc' | 'forUpc';
}
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
*/
setActivePinia(createPinia());
export const useCallbackStoreGeneric = (
useCallbackActions: () => CallbackActionsStore,
) =>
@@ -105,10 +105,7 @@ export const useCallbackStoreGeneric = (
const callbackActions = useCallbackActions();
const encryptionKey = 'Uyv2o8e*FiQe8VeLekTqyX6Z*8XonB';
const sendType = 'fromUpc';
// state
const encryptedMessage = ref<string | null>(null);
// actions
const send = (url: string, payload: SendPayloads) => {
console.debug('[callback.send]');
const stringifiedData = JSON.stringify({
@@ -118,11 +115,11 @@ export const useCallbackStoreGeneric = (
sender: window.location.href,
type: sendType,
});
encryptedMessage.value = AES.encrypt(stringifiedData, encryptionKey).toString();
const encryptedMessage = AES.encrypt(stringifiedData, encryptionKey).toString();
// build and go to url
const destinationUrl = new URL(url);
destinationUrl.searchParams.set('data', encodeURI(encryptedMessage.value));
console.debug('[callback.send]', encryptedMessage.value, destinationUrl);
destinationUrl.searchParams.set('data', encodeURI(encryptedMessage));
console.debug('[callback.send]', encryptedMessage, destinationUrl);
window.location.href = destinationUrl.toString();
return;
};
@@ -144,7 +141,6 @@ export const useCallbackStoreGeneric = (
};
return {
// actions
send,
watcher,
};

View File

@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
import { useAccountStore } from './account';
import { useInstallKeyStore } from './installKey';
import { useCallbackStoreGeneric, type ExternalPayload, type ExternalActions, type ExternalKeyActions, type QueryPayloads } from './callback';
import { useCallbackStoreGeneric, type ExternalPayload, type ExternalKeyActions, type QueryPayloads } from './callback';
// import { useServerStore } from './server';
export const useCallbackActionsStore = defineStore(
@@ -11,7 +11,7 @@ export const useCallbackActionsStore = defineStore(
const accountStore = useAccountStore();
const installKeyStore = useInstallKeyStore();
// const serverStore = useServerStore();
type CallbackStatus = 'error' | 'loading' | 'ready' | 'done';
type CallbackStatus = 'error' | 'loading' | 'ready' | 'success';
const callbackStatus = ref<CallbackStatus>('ready');
const callbackData = ref<ExternalPayload>();
@@ -38,22 +38,21 @@ export const useCallbackActionsStore = defineStore(
if (action?.keyUrl) {
await installKeyStore.install(action as ExternalKeyActions);
}
/** @todo add oemSignOut */
if (action?.user || action.type === 'signOut') {
if (action?.user || action.type === 'signOut' || action.type === 'oemSignOut') {
await accountStore.updatePluginConfig(action);
}
// all actions have run
if (array.length === (index + 1)) {
callbackStatus.value = 'done';
// if (array.length > 1) {
// // if we have more than 1 action it means there was a key install and an account action so both need to be successful
// const allSuccess = accountStore.accountActionStatus === 'success' && installKeyStore.keyInstallStatus === 'success';
// callbackStatus.value = allSuccess ? 'success' : 'error';
// } else {
// // only 1 action needs to be successful
// const oneSuccess = accountStore.accountActionStatus === 'success' || installKeyStore.keyInstallStatus === 'success';
// callbackStatus.value = oneSuccess ? 'success' : 'error';
// }
// callbackStatus.value = 'done';
if (array.length > 1) {
// if we have more than 1 action it means there was a key install and an account action so both need to be successful
const allSuccess = accountStore.accountActionStatus === 'success' && installKeyStore.keyInstallStatus === 'success';
callbackStatus.value = allSuccess ? 'success' : 'error';
} else {
// only 1 action needs to be successful
const oneSuccess = accountStore.accountActionStatus === 'success' || installKeyStore.keyInstallStatus === 'success';
callbackStatus.value = oneSuccess ? 'success' : 'error';
}
}
});
};
@@ -83,6 +82,10 @@ export const useCallbackActionsStore = defineStore(
}
});
watch(callbackData, () => {
console.debug('[callbackData] watch', callbackData.value);
});
return {
redirectToCallbackType,
callbackData,

View File

@@ -97,6 +97,7 @@ export const useInstallKeyStore = defineStore('installKey', () => {
// State
keyInstallStatus,
// getters
keyActionType,
keyInstallStatusCopy,
keyType,
keyUrl,

View File

@@ -1,6 +1,8 @@
import { useToggle } from '@vueuse/core';
import { defineStore, createPinia, setActivePinia } from 'pinia';
import { useDropdownStore } from '~/store/dropdown';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
@@ -8,17 +10,33 @@ import { defineStore, createPinia, setActivePinia } from 'pinia';
setActivePinia(createPinia());
export const usePromoStore = defineStore('promo', () => {
const promoVisible = ref<boolean>(false);
const dropdownStore = useDropdownStore();
const promoVisible = ref<boolean>(false);
const openOnNextLoad = () => sessionStorage.setItem('unraidConnectPromo', 'show');
const promoHide = () => promoVisible.value = false;
const promoShow = () => promoVisible.value = true;
const promoToggle = useToggle(promoVisible);
watch(promoVisible, (newVal, _oldVal) => {
console.debug('[promoVisible]', newVal, _oldVal);
if (newVal) { // close the dropdown when the promo is opened
dropdownStore.dropdownHide();
}
});
onBeforeMount(() => {
if (sessionStorage.getItem('unraidConnectPromo') === 'show') {
sessionStorage.removeItem('unraidConnectPromo');
promoShow();
}
});
return {
promoVisible,
openOnNextLoad,
promoHide,
promoShow,
promoToggle,

View File

@@ -1,34 +0,0 @@
import type { CognitoUser, ChallengeName } from 'amazon-cognito-identity-js';
import type {
ServerAccountCallbackSendPayload,
ServerPurchaseCallbackSendPayload,
ServerStateDataActionType,
} from '~/types/server';
/**
* These user interfaces are mimiced from the Auth repo
*/
export interface UserInfo {
'custom:ips_id'?: string;
email?: string;
email_verifed?: 'true' | 'false';
preferred_username?: string;
sub?: string;
username?: string;
}
export interface CallbackSendPayload {
server: ServerAccountCallbackSendPayload|ServerPurchaseCallbackSendPayload;
type: ServerStateDataActionType;
}
export interface CallbackAction {
apiKey?: string;
keyUrl?: string;
type: ServerStateDataActionType;
user?: UserInfo;
}
export interface CallbackReceivePayload {
actions: CallbackAction[];
sender: string;
}