From d8a5b1711a8f826f602a504f125c157afb27af90 Mon Sep 17 00:00:00 2001 From: Zack Spear Date: Wed, 29 Jan 2025 11:08:23 -0800 Subject: [PATCH] feat(web): activation modal steps, updated copy (#1079) * feat(stepper): add shadcn stepper components * chore(serverState): remove partnerLogo property from server state configuration * refactor(web): modal add subfooter slot - adds ability to display content below the modal's content box * feat(modal): add ActivationSteps component to subFooter slot in WelcomeModal and ActivationModal * refactor: improve activation modal buttons responsiveness * refactor: update activation flow messaging and UI * feat: web/deploy-dev.sh add dynamic web component JS file whitelisting in auth-request.php * fix: remove test UTM parameters from Unraid docs links in activation modal * refactor: improve konami code handling and add type safety to activation steps * chore: remove extra semicolon in serverState.ts * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- plugin/scripts/deploy-dev.sh | 2 +- .../include/translations.php | 9 +- web/_data/serverState.ts | 2 - web/components/Activation/Modal.vue | 78 ++++++------ web/components/Activation/Steps.vue | 119 ++++++++++++++++++ web/components/Modal.vue | 23 +++- web/components/WelcomeModal.ce.vue | 7 +- web/components/shadcn/stepper/Stepper.vue | 28 +++++ .../shadcn/stepper/StepperDescription.vue | 27 ++++ .../shadcn/stepper/StepperIndicator.vue | 37 ++++++ web/components/shadcn/stepper/StepperItem.vue | 29 +++++ .../shadcn/stepper/StepperSeparator.vue | 33 +++++ .../shadcn/stepper/StepperTitle.vue | 26 ++++ .../shadcn/stepper/StepperTrigger.vue | 28 +++++ web/components/shadcn/stepper/index.ts | 7 ++ web/locales/en_US.json | 14 ++- web/scripts/deploy-dev.sh | 51 ++++++++ 17 files changed, 470 insertions(+), 50 deletions(-) create mode 100644 web/components/Activation/Steps.vue create mode 100644 web/components/shadcn/stepper/Stepper.vue create mode 100644 web/components/shadcn/stepper/StepperDescription.vue create mode 100644 web/components/shadcn/stepper/StepperIndicator.vue create mode 100644 web/components/shadcn/stepper/StepperItem.vue create mode 100644 web/components/shadcn/stepper/StepperSeparator.vue create mode 100644 web/components/shadcn/stepper/StepperTitle.vue create mode 100644 web/components/shadcn/stepper/StepperTrigger.vue create mode 100644 web/components/shadcn/stepper/index.ts diff --git a/plugin/scripts/deploy-dev.sh b/plugin/scripts/deploy-dev.sh index 672ee0d62..42717c9f2 100755 --- a/plugin/scripts/deploy-dev.sh +++ b/plugin/scripts/deploy-dev.sh @@ -60,7 +60,7 @@ fi if [ "$deploy" = "yes" ]; then cd web || exit - npm run deploy-wc:dev + npm run deploy-to-unraid:dev elif [ "$deploy" = "build" ]; then cd web || exit npm run build:dev diff --git a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php index d780e2c3c..c771872eb 100644 --- a/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php +++ b/plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php @@ -80,6 +80,7 @@ class WebComponentTranslations '

Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.

' => '

' . _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.') . '

', 'A Trial key provides all the functionality of an Unleashed Registration key' => _('A Trial key provides all the functionality of an Unleashed Registration key'), 'Acklowledge that you have made a Flash Backup to enable this action' => _('Acklowledge that you have made a Flash Backup to enable this action'), + 'Activate License' => _('Activate License'), 'Activate Now' => _('Activate Now'), 'ago' => _('ago'), 'All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.' => _('All you need is an active internet connection, an Unraid.net account, and the Connect plugin.') . ' ' . _('Get started by installing the plugin.'), @@ -119,11 +120,14 @@ class WebComponentTranslations 'Copy your Key URL: {0}' => sprintf(_('Copy your Key URL: %s'), '{0}'), 'Create Flash Backup' => _('Create Flash Backup'), 'Create a password' => _('Create a password'), + 'Create an Unraid.net account and activate your key' => _('Create an Unraid.net account and activate your key'), + 'Create Device Password' => _('Create Device Password'), 'Current Version {0}' => sprintf(_('Current Version %s'), '{0}'), 'Current Version: Unraid {0}' => sprintf(_('Current Version: Unraid %s'), '{0}'), 'Customizable Dashboard Tiles' => _('Customizable Dashboard Tiles'), 'day' => sprintf(_('%s day'), '{n}') . ' | ' . sprintf(_('%s days'), '{n}'), 'Deep Linking' => _('Deep Linking'), + 'Device is ready to configure' => _('Device is ready to configure'), 'DNS issue, unable to resolve wanip4.unraid.net' => _('DNS issue, unable to resolve wanip4.unraid.net'), 'Downgrade Unraid OS to {0}' => sprintf(_('Downgrade Unraid OS to %s'), '{0}'), 'Downgrade Unraid OS' => _('Downgrade Unraid OS'), @@ -206,7 +210,7 @@ class WebComponentTranslations 'Learn more and link your key to your account' => _('Learn more and link your key to your account'), 'Learn More' => _('Learn More'), 'Learn more' => _('Learn more'), - 'Let\'s activate your Unraid license!' => _('Let\'s activate your Unraid license!'), + 'Let\'s activate your Unraid OS License' => _('Let\'s activate your Unraid OS License'), 'Let\'s Unleash your Hardware!' => _('Let\'s Unleash your Hardware!'), 'License key actions' => _('License key actions'), 'License key type' => _('License key type'), @@ -286,6 +290,7 @@ class WebComponentTranslations 'Requires the local unraid-api to be running successfully' => _('Requires the local unraid-api to be running successfully'), 'Restarting unraid-api…' => _('Restarting unraid-api…'), 'second' => sprintf(_('%s second'), '{n}') . ' | ' . sprintf(_('%s seconds'), '{n}'), + 'Secure your device' => _('Secure your device'), 'Server Up Since {0}' => sprintf(_('Server Up Since %s'), '{0}'), 'Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI. Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.' => _('Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI.') . ' ' . _('Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.'), 'Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.' => _('Set custom server tiles how you like and automatically display your server\'s banner image on your Connect Dashboard.'), @@ -307,7 +312,7 @@ class WebComponentTranslations 'SSL certificates for unraid.net deprecated' => _('SSL certificates for unraid.net deprecated'), 'Stale Server' => _('Stale Server'), 'Stale' => _('Stale'), - 'Start by creating an Unraid.net account — this will let you manage your license and access support. Once that\'s done, we\'ll guide you through a quick checkout process to register your license and install your key.' => _('Start by creating an Unraid.net account — this will let you manage your license and access support.') . ' ' . _('Once that\'s done, we\'ll guide you through a quick checkout process to register your license and install your key.'), + 'On the following screen, your license will be activated. You\'ll then create an Unraid.net Account to manage your license going forward.' => _('On the following screen, your license will be activated.') . ' ' . _('You\'ll then create an Unraid.net Account to manage your license going forward.'), 'Start Free 30 Day Trial' => _('Start Free 30 Day Trial'), 'Starting your free 30 day trial' => _('Starting your free 30 day trial'), 'Success!' => _('Success!'), diff --git a/web/_data/serverState.ts b/web/_data/serverState.ts index cd983b4a2..d508812f6 100644 --- a/web/_data/serverState.ts +++ b/web/_data/serverState.ts @@ -1,4 +1,3 @@ -; // import dayjs, { extend } from 'dayjs'; // import customParseFormat from 'dayjs/plugin/customParseFormat'; // import relativeTime from 'dayjs/plugin/relativeTime'; @@ -11,7 +10,6 @@ import type { Server, ServerState // ServerUpdateOsResponse, } from '~/types/server'; - // dayjs plugins // extend(customParseFormat); // extend(relativeTime); diff --git a/web/components/Activation/Modal.vue b/web/components/Activation/Modal.vue index 28a728814..04c980e86 100644 --- a/web/components/Activation/Modal.vue +++ b/web/components/Activation/Modal.vue @@ -19,10 +19,10 @@ const activationCodeStore = useActivationCodeStore(); const { partnerLogo, showActivationModal } = storeToRefs(activationCodeStore); const purchaseStore = usePurchaseStore(); -const title = computed(() => props.t("Let's activate your Unraid license!")); +const title = computed(() => props.t("Let's activate your Unraid OS License")); const description = computed(() => props.t( - `Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.` + `On the following screen, your license will be activated. You’ll then create an Unraid.net Account to manage your license going forward.` ) ); const docsButtons = computed(() => { @@ -47,40 +47,41 @@ const docsButtons = computed(() => { }); /** - * Listen for a key sequence to close the modal - * @todo - temporary solution until we have a better way to handle this + * Listen for konami code sequence to close the modal */ + const keySequence = [ + "ArrowUp", + "ArrowUp", + "ArrowDown", + "ArrowDown", + "ArrowLeft", + "ArrowRight", + "ArrowLeft", + "ArrowRight", + "b", + "a", +]; +let sequenceIndex = 0; + +const handleKeydown = (event: KeyboardEvent) => { + if (event.key === keySequence[sequenceIndex]) { + sequenceIndex++; + } else { + sequenceIndex = 0; + } + + if (sequenceIndex === keySequence.length) { + activationCodeStore.setActivationModalHidden(true); + window.location.href = "/Tools/Registration"; + } +}; + onMounted(() => { - const keySequence = [ - "ArrowUp", - "ArrowUp", - "ArrowDown", - "ArrowDown", - "ArrowLeft", - "ArrowRight", - "ArrowLeft", - "ArrowRight", - "b", - "a", - ]; - let sequenceIndex = 0; - - window.addEventListener("keydown", (event) => { - if (event.key === keySequence[sequenceIndex]) { - sequenceIndex++; - } else { - sequenceIndex = 0; - } - - if (sequenceIndex === keySequence.length) { - activationCodeStore.setActivationModalHidden(true); - window.location.href = "/Tools/Registration"; - } - }); + window.addEventListener("keydown", handleKeydown); }); onUnmounted(() => { - window.removeEventListener("keydown", () => {}); + window.removeEventListener("keydown", handleKeydown); }); @@ -103,11 +104,6 @@ onUnmounted(() => { - + + diff --git a/web/components/Activation/Steps.vue b/web/components/Activation/Steps.vue new file mode 100644 index 000000000..404ffbebe --- /dev/null +++ b/web/components/Activation/Steps.vue @@ -0,0 +1,119 @@ + + + diff --git a/web/components/Modal.vue b/web/components/Modal.vue index 9851f8dba..257b6ad54 100644 --- a/web/components/Modal.vue +++ b/web/components/Modal.vue @@ -58,12 +58,12 @@ const closeModal = () => { const ariaLablledById = computed(() => props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined); const computedVerticalCenter = computed(() => { if (props.tallContent) { - return 'items-start sm:items-center'; + return 'justify-start sm:justify-center'; } if (typeof props.modalVerticalCenter === 'string') { return props.modalVerticalCenter; } - return props.modalVerticalCenter ? 'items-center' : 'items-start'; + return props.modalVerticalCenter ? 'justify-center' : 'justify-start'; }); @@ -78,7 +78,7 @@ const computedVerticalCenter = computed(() => { @keyup.esc="closeModal" >
(() => { success ? 'shadow-green-600/30 border-green-600/10' : '', !error && !success && !disableShadow ? 'shadow-orange/10 border-white/10' : '', ]" - class="text-16px text-foreground bg-background text-left relative z-10 flex flex-col justify-around border-2 border-solid transform overflow-hidden rounded-lg transition-all sm:w-full" + class="text-16px text-foreground bg-background text-left relative z-10 mx-auto flex flex-col justify-around border-2 border-solid transform overflow-hidden rounded-lg transition-all sm:w-full" > + + +
+ +
+
diff --git a/web/components/WelcomeModal.ce.vue b/web/components/WelcomeModal.ce.vue index b823ed224..c811f5958 100644 --- a/web/components/WelcomeModal.ce.vue +++ b/web/components/WelcomeModal.ce.vue @@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'; import 'tailwindcss/tailwind.css'; import '~/assets/main.css'; +import ActivationSteps from '~/components/Activation/Steps.vue'; import { useActivationCodeStore } from '~/store/activationCode'; import { useServerStore } from '~/store/server'; import type { Server } from '~/types/server'; @@ -28,7 +29,7 @@ const title = computed(() => ); const description = computed(() => - t(`You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.`) + t(`First, you’ll create your device’s login credentials, then you’ll activate your Unraid license—your device’s operating system (OS).`) ); const showModal = ref(true); @@ -98,6 +99,10 @@ onBeforeMount(() => { /> + + diff --git a/web/components/shadcn/stepper/Stepper.vue b/web/components/shadcn/stepper/Stepper.vue new file mode 100644 index 000000000..175875738 --- /dev/null +++ b/web/components/shadcn/stepper/Stepper.vue @@ -0,0 +1,28 @@ + + + diff --git a/web/components/shadcn/stepper/StepperDescription.vue b/web/components/shadcn/stepper/StepperDescription.vue new file mode 100644 index 000000000..87f3fc6ba --- /dev/null +++ b/web/components/shadcn/stepper/StepperDescription.vue @@ -0,0 +1,27 @@ + + + diff --git a/web/components/shadcn/stepper/StepperIndicator.vue b/web/components/shadcn/stepper/StepperIndicator.vue new file mode 100644 index 000000000..05a44fb01 --- /dev/null +++ b/web/components/shadcn/stepper/StepperIndicator.vue @@ -0,0 +1,37 @@ + + + diff --git a/web/components/shadcn/stepper/StepperItem.vue b/web/components/shadcn/stepper/StepperItem.vue new file mode 100644 index 000000000..c152709e5 --- /dev/null +++ b/web/components/shadcn/stepper/StepperItem.vue @@ -0,0 +1,29 @@ + + + diff --git a/web/components/shadcn/stepper/StepperSeparator.vue b/web/components/shadcn/stepper/StepperSeparator.vue new file mode 100644 index 000000000..73c986224 --- /dev/null +++ b/web/components/shadcn/stepper/StepperSeparator.vue @@ -0,0 +1,33 @@ + + + diff --git a/web/components/shadcn/stepper/StepperTitle.vue b/web/components/shadcn/stepper/StepperTitle.vue new file mode 100644 index 000000000..213391e73 --- /dev/null +++ b/web/components/shadcn/stepper/StepperTitle.vue @@ -0,0 +1,26 @@ + + + diff --git a/web/components/shadcn/stepper/StepperTrigger.vue b/web/components/shadcn/stepper/StepperTrigger.vue new file mode 100644 index 000000000..8c50d032a --- /dev/null +++ b/web/components/shadcn/stepper/StepperTrigger.vue @@ -0,0 +1,28 @@ + + + diff --git a/web/components/shadcn/stepper/index.ts b/web/components/shadcn/stepper/index.ts new file mode 100644 index 000000000..a4065a57a --- /dev/null +++ b/web/components/shadcn/stepper/index.ts @@ -0,0 +1,7 @@ +export { default as Stepper } from './Stepper.vue' +export { default as StepperDescription } from './StepperDescription.vue' +export { default as StepperIndicator } from './StepperIndicator.vue' +export { default as StepperItem } from './StepperItem.vue' +export { default as StepperSeparator } from './StepperSeparator.vue' +export { default as StepperTitle } from './StepperTitle.vue' +export { default as StepperTrigger } from './StepperTrigger.vue' diff --git a/web/locales/en_US.json b/web/locales/en_US.json index f63a92d03..0a576b123 100644 --- a/web/locales/en_US.json +++ b/web/locales/en_US.json @@ -35,6 +35,7 @@ "A valid OS version is required to check for OS updates.": "A valid OS version is required to check for OS updates.", "Acklowledge that you have made a Flash Backup to enable this action": "Acklowledge that you have made a Flash Backup to enable this action", "Activate Now": "Activate Now", + "Activate License": "Activate License", "ago": "ago", "All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.": "All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.", "Attached Storage Devices": "Attached Storage Devices", @@ -75,6 +76,7 @@ "Copy your Key URL: {0}": "Copy your Key URL: {0}", "Create Flash Backup": "Create Flash Backup", "Create a password": "Create a password", + "Create Device Password": "Create Device Password", "Current Version {0}": "Current Version {0}", "Current Version: Unraid {0}": "Current Version: Unraid {0}", "Customizable Dashboard Tiles": "Customizable Dashboard Tiles", @@ -175,7 +177,7 @@ "Learn more and link your key to your account": "Learn more and link your key to your account", "Learn more": "Learn more", "Learn More": "Learn More", - "Let's activate your Unraid license!": "Let's activate your Unraid license!", + "Let's activate your Unraid OS License": "Let's activate your Unraid OS License", "Let's Unleash your Hardware!": "Let's Unleash your Hardware!", "License key actions": "License key actions", "License key type": "License key type", @@ -284,7 +286,7 @@ "SSL certificates for unraid.net deprecated": "SSL certificates for unraid.net deprecated", "Stale Server": "Stale Server", "Stale": "Stale", - "Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.": "Start by creating an Unraid.net account — this will let you manage your license and access support. Once that's done, we'll guide you through a quick checkout process to register your license and install your key.", + "On the following screen, your license will be activated. You'll then create an Unraid.net Account to manage your license going forward.": "On the following screen, your license will be activated. You'll then create an Unraid.net Account to manage your license going forward.", "Start Free 30 Day Trial": "Start Free 30 Day Trial", "Starter": "Starter", "Starting your free 30 day trial": "Starting your free 30 day trial", @@ -317,6 +319,7 @@ "Unable to open release notes": "Unable to open release notes", "Unknown error": "Unknown error", "Unknown": "Unknown", + "Unleash Your Hardware": "Unleash Your Hardware", "Unleashed": "Unleashed", "unlimited": "unlimited", "Unraid {0} Available": "Unraid {0} Available", @@ -368,7 +371,7 @@ "You have exceeded the number of devices allowed for your license. Please remove a device before adding another.": "You have exceeded the number of devices allowed for your license. Please remove a device before adding another.", "You have not activated the Flash Backup feature via the Unraid Connect plugin.": "You have not activated the Flash Backup feature via the Unraid Connect plugin.", "You may still update to releases dated prior to your update expiration date.": "You may still update to releases dated prior to your update expiration date.", - "You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.": "You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You’ll use this password every time you access the Unraid web interface.", + "You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You'll use this password every time you access the Unraid web interface.": "You're about to create a password to secure access to your system. This password is essential for managing and configuring your server. You'll use this password every time you access the Unraid web interface.", "You're one step closer to enhancing your Unraid experience": "You're one step closer to enhancing your Unraid experience", "Your {0} Key has been replaced!": "Your {0} Key has been replaced!", "Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.": "Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.", @@ -377,5 +380,8 @@ "Your license key is not eligible for Unraid OS {0}": "Your license key is not eligible for Unraid OS {0}", "Your license key's OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.": "Your license key's OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.", "Your Trial has expired": "Your Trial has expired", - "Your Trial key has been extended!": "Your Trial key has been extended!" + "Your Trial key has been extended!": "Your Trial key has been extended!", + "Create an Unraid.net account and activate your key": "Create an Unraid.net account and activate your key", + "Device is ready to configure": "Device is ready to configure", + "Secure your device": "Secure your device" } diff --git a/web/scripts/deploy-dev.sh b/web/scripts/deploy-dev.sh index d8f863b1f..430bbfcdb 100755 --- a/web/scripts/deploy-dev.sh +++ b/web/scripts/deploy-dev.sh @@ -40,6 +40,57 @@ echo "$rsync_command" eval "$rsync_command" exit_code=$? +# Update the auth-request.php file to include the new web component JS +update_auth_request() { + local server_name="$1" + # SSH into server and update auth-request.php + ssh "root@${server_name}" " + AUTH_REQUEST_FILE='/usr/local/emhttp/auth-request.php' + WEB_COMPS_DIR='/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/_nuxt/' + + # Find JS files and modify paths + mapfile -t JS_FILES < <(find \"\$WEB_COMPS_DIR\" -type f -name \"*.js\" | sed 's|/usr/local/emhttp||' | sort -u) + + FILES_TO_ADD+=(\"\${JS_FILES[@]}\") + + if grep -q '\$arrWhitelist' \"\$AUTH_REQUEST_FILE\"; then + awk ' + BEGIN { in_array = 0 } + /\\\$arrWhitelist\s*=\s*\[/ { + in_array = 1 + print \$0 + next + } + in_array && /^\s*\]/ { + in_array = 0 + print \$0 + next + } + !in_array || !/\/plugins\/dynamix\.my\.servers\/unraid-components\/_nuxt\/unraid-components\.client-/ { + print \$0 + } + ' \"\$AUTH_REQUEST_FILE\" > \"\${AUTH_REQUEST_FILE}.tmp\" + + # Now add new entries right after the opening bracket + awk -v files_to_add=\"\$(printf '%s\n' \"\${FILES_TO_ADD[@]}\" | sort -u | awk '{printf \" \\\x27%s\\\x27,\n\", \$0}')\" ' + /\\\$arrWhitelist\s*=\s*\[/ { + print \$0 + print files_to_add + next + } + { print } + ' \"\${AUTH_REQUEST_FILE}.tmp\" > \"\${AUTH_REQUEST_FILE}\" + + rm \"\${AUTH_REQUEST_FILE}.tmp\" + echo \"Updated \$AUTH_REQUEST_FILE with new web component JS files\" + else + echo \"\\\$arrWhitelist array not found in \$AUTH_REQUEST_FILE\" + fi + " +} + +update_auth_request "$server_name" + # Play built-in sound based on the operating system if [[ "$OSTYPE" == "darwin"* ]]; then # macOS