Browse Source

Maintenance: Desktop - 2FA Login implementation.

Mantas Masalskis 1 year ago
parent
commit
ac79fcbfdd

+ 8 - 9
app/frontend/apps/desktop/AppDesktop.vue

@@ -2,6 +2,7 @@
 
 <script setup lang="ts">
 import useFormKitConfig from '#shared/composables/form/useFormKitConfig.ts'
+import CommonButton from '#desktop/components/CommonButton/CommonButton.vue'
 import CommonNotifications from '#shared/components/CommonNotifications/CommonNotifications.vue'
 import useAppMaintenanceCheck from '#shared/composables/useAppMaintenanceCheck.ts'
 import { useAppTheme } from '#shared/stores/theme.ts'
@@ -83,6 +84,13 @@ const appTheme = useAppTheme()
 <template>
   <template v-if="application.loaded">
     <CommonNotifications />
+    <CommonButton
+      class="fixed top-2 ltr:right-2 rtl:left-2"
+      size="medium"
+      aria-label="Change theme"
+      :icon="appTheme.theme === 'light' ? 'sun' : 'moon'"
+      @click="appTheme.toggleTheme(false)"
+    />
   </template>
   <!-- TODO: styles are placeholders -->
   <div v-if="application.loaded" class="flex h-full">
@@ -98,15 +106,6 @@ const appTheme = useAppTheme()
       class="w-full h-full antialiased bg-white dark:bg-gray-500 text-gray-100 dark:text-neutral-400"
     >
       <RouterView />
-
-      <div class="flex">
-        Change Theme:
-        <CommonIcon
-          name="info-circle"
-          size="small"
-          @click="appTheme.toggleTheme(false)"
-        />
-      </div>
     </article>
   </div>
 </template>

+ 1 - 1
app/frontend/apps/desktop/components/CommonButton/CommonButton.vue

@@ -129,7 +129,7 @@ const iconSizeClass = computed(() => {
 
 <template>
   <button
-    class="btn h-min min-h-min border-0 shadow-none font-normal flex-nowrap gap-x-2.5 hover:outline hover:outline-2 hover:outline-offset-2 hover:outline-blue-600 dark:hover:outline-blue-900 focus-visible:outline-blue-800"
+    class="btn h-min min-h-min border-0 shadow-none font-normal flex-nowrap gap-x-2.5 hover:outline hover:outline-1 hover:outline-offset-1 hover:outline-blue-600 dark:hover:outline-blue-900 focus-visible:outline-1 focus-visible:outline-offset-1 focus-visible:outline-blue-800"
     :class="[
       ...variantClasses,
       ...sizeClasses,

+ 41 - 0
app/frontend/apps/desktop/components/CommonLoader/CommonLoader.vue

@@ -0,0 +1,41 @@
+<!-- Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/ -->
+
+<script setup lang="ts">
+/* eslint-disable vue/no-v-html */
+
+import { markup } from '#shared/utils/markup.ts'
+import CommonAlert from '#shared/components/CommonAlert/CommonAlert.vue'
+
+interface Props {
+  loading?: boolean
+  error?: string | null
+}
+
+defineProps<Props>()
+</script>
+
+<script lang="ts">
+export default {
+  inheritAttrs: false,
+}
+</script>
+
+<template>
+  <div
+    v-if="loading"
+    v-bind="$attrs"
+    class="flex justify-center items-center"
+    role="status"
+  >
+    <CommonIcon
+      class="fill-yellow-300"
+      name="spinner"
+      animation="spin"
+      :label="__('Loading…')"
+    />
+  </div>
+  <CommonAlert v-else-if="error" v-bind="$attrs" variant="danger">
+    <span v-html="markup($t(error))" />
+  </CommonAlert>
+  <slot v-else />
+</template>

+ 68 - 0
app/frontend/apps/desktop/components/CommonLoader/__tests__/CommonLoader.spec.ts

@@ -0,0 +1,68 @@
+// Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
+
+import { getByIconName } from '#tests/support/components/iconQueries.ts'
+import { renderComponent } from '#tests/support/components/index.ts'
+import CommonLoader from '../CommonLoader.vue'
+
+describe('CommonLoader.vue', () => {
+  it('does not render with default prop values', async () => {
+    const view = renderComponent(CommonLoader)
+
+    expect(view.queryByRole('status')).not.toBeInTheDocument()
+  })
+
+  it('renders loading animation with loading prop set', async () => {
+    const view = renderComponent(CommonLoader, {
+      props: {
+        loading: true,
+      },
+    })
+
+    const loader = view.getByRole('status')
+
+    expect(getByIconName(loader, 'spinner')).toBeInTheDocument()
+  })
+
+  it('hides loading animation when loading prop is unset', async () => {
+    const view = renderComponent(CommonLoader, {
+      props: {
+        loading: true,
+      },
+    })
+
+    const loader = view.getByRole('status')
+
+    expect(loader).toBeInTheDocument()
+
+    await view.rerender({
+      loading: false,
+    })
+
+    expect(loader).not.toBeInTheDocument()
+  })
+
+  it('renders alert if error prop is supplied', async () => {
+    const view = renderComponent(CommonLoader, {
+      props: {
+        error: 'foobar',
+      },
+    })
+
+    const alert = view.getByRole('alert')
+
+    expect(alert).toHaveTextContent('foobar')
+    expect(getByIconName(alert, 'close-small')).toBeInTheDocument()
+  })
+
+  it('provides default slot', async () => {
+    const view = renderComponent(CommonLoader, {
+      slots: {
+        default: 'foobar',
+      },
+    })
+
+    expect(view.baseElement).toHaveTextContent('foobar')
+    expect(view.queryByRole('status')).not.toBeInTheDocument()
+    expect(view.queryByRole('alert')).not.toBeInTheDocument()
+  })
+})

+ 26 - 7
app/frontend/apps/desktop/components/layout/LayoutPublicPage.vue

@@ -6,8 +6,8 @@ import CommonLogo from '#shared/components/CommonLogo/CommonLogo.vue'
 import type { BoxSizes } from './types'
 
 export interface Props {
-  title: string
-  showLogo: boolean
+  title?: string
+  showLogo?: boolean
   boxSize?: BoxSizes
 }
 
@@ -38,7 +38,7 @@ const hoverPoweredByLogo = ref(false)
         <div v-if="showLogo" class="flex justify-center">
           <CommonLogo />
         </div>
-        <h1 class="mb-5 flex justify-center text-xl">
+        <h1 v-if="title" class="mb-5 text-xl text-center">
           {{ $t(title) }}
         </h1>
         <slot />
@@ -62,13 +62,32 @@ const hoverPoweredByLogo = ref(false)
           @mouseover="hoverPoweredByLogo = true"
           @mouseleave="hoverPoweredByLogo = false"
         >
-          <CommonIcon
-            :name="hoverPoweredByLogo ? 'logo' : 'logo-flat'"
-            size="base"
-          />
+          <div class="relative">
+            <CommonIcon name="logo-flat" size="base" />
+            <Transition name="fade">
+              <CommonIcon
+                v-if="hoverPoweredByLogo"
+                class="absolute top-0"
+                name="logo"
+                size="base"
+              />
+            </Transition>
+          </div>
           {{ $t('Zammad') }}
         </CommonLink>
       </footer>
     </div>
   </div>
 </template>
+
+<style>
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.3s ease;
+}
+
+.fade-enter-from,
+.fade-leave-to {
+  opacity: 0;
+}
+</style>

+ 3 - 2
app/frontend/apps/desktop/form/theme/global/getCoreDesktopClasses.ts

@@ -12,7 +12,8 @@ const textInputClasses = (classes: Classes = {}) => {
     wrapper: 'flex flex-col items-start justify-start mb-1.5 last:mb-0',
     input: 'grow bg-transparent',
     label: 'block mb-1 text-sm text-gray-100 dark:text-neutral-400',
-    inner: `flex items-center w-full h-10 py-2 px-2.5 bg-blue-200 dark:bg-gray-700 text-black dark:text-white hover:outline hover:outline-2 hover:outline-offset-2 hover:outline-blue-600 dark:hover:outline-blue-900 focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-blue-800 hover:focus-within:outline-blue-800 dark:hover:focus-within:outline-blue-800 formkit-invalid:outline formkit-invalid:outline-2 formkit-invalid:outline-offset-2 formkit-invalid:outline-red-500`,
+    inner:
+      'flex items-center w-full h-10 py-2 px-2.5 bg-blue-200 dark:bg-gray-700 text-black dark:text-white hover:outline hover:outline-1 hover:outline-offset-1 hover:outline-blue-600 dark:hover:outline-blue-900 focus-within:outline focus-within:outline-1 focus-within:outline-offset-1 focus-within:outline-blue-800 hover:focus-within:outline-blue-800 dark:hover:focus-within:outline-blue-800 formkit-invalid:outline formkit-invalid:outline-1 formkit-invalid:outline-offset-1 formkit-invalid:outline-red-500 dark:hover:formkit-invalid:outline-red-500',
   })
 }
 
@@ -48,7 +49,7 @@ export const getCoreDesktopClasses: FormThemeExtension = (
       input:
         'peer appearance-none focus:outline-none focus:ring-0 focus:ring-offset-0',
       decorator:
-        'w-3 h-3 relative border border-stone-200 dark:border-neutral-500 peer-hover:border-blue-600 dark:peer-hover:border-blue-900 peer-focus:border-blue-800 rounded-sm bg-transparent text-stone-200 dark:text-neutral-500 peer-hover:text-blue-600 dark:peer-hover:text-blue-900 peer-focus:text-blue-800',
+        'w-3 h-3 relative border border-stone-200 dark:border-neutral-500 peer-hover:border-blue-600 dark:peer-hover:border-blue-900 peer-focus:border-blue-800 peer-focus:outline peer-focus:outline-1 peer-focus:outline-offset-1 peer-focus:outline-blue-800 rounded-sm bg-transparent text-stone-200 dark:text-neutral-500 peer-hover:text-blue-600 dark:peer-hover:text-blue-900 peer-focus:text-blue-800',
       decoratorIcon:
         'absolute invisible formkit-is-checked:visible -top-px ltr:-left-px rtl:-right-px',
     },

+ 41 - 10
app/frontend/apps/desktop/initializer/3RD-PARTY-ICONS.md

@@ -1,24 +1,55 @@
 # Third Party Icons (`desktop`)
 
+- `assets/box-arrow-up-right.svg`
 - `assets/check-circle-outline.svg`
 - `assets/exclamation-triangle.svg`
-- `assets/eye.svg`
 - `assets/eye-slash.svg`
+- `assets/eye.svg`
 - `assets/info-circle.svg`
 - `assets/key.svg`
+- `assets/moon.svg`
 - `assets/phone.svg`
-- `assets/box-arrow-up-right.svg`
-- `assets/microsoft.svg`
-- `assets/linkedin.svg`
-- `assets/google.svg`
-- `assets/facebook.svg`
-- `assets/github.svg`
-- `assets/twitter.svg`
-- `assets/sina-weibo.svg`
-- `assets/spinner.svg`
 - `assets/spinner.svg`
+- `assets/sun.svg`
 - `assets/x-circle.svg`
 - `assets/x-lg.svg`
   - Author: The Bootstrap Authors
   - License: MIT
   - URL: https://github.com/twbs/icons
+
+- `assets/github.svg`
+  - Author: GitHub
+  - URL: https://github.com/logos
+
+- `assets/gitlab.svg`
+  - Author: GitLab
+  - URL: https://about.gitlab.com/press/press-kit/
+  - URL: https://github.com/logos
+
+- `assets/google.svg`
+  - Author: Google
+  - URL: https://about.google/brand-resource-center/
+
+- `assets/facebook.svg`
+  - Author: Facebook
+  - URL: https://www.facebook.com/brand/resources/facebookapp/logo
+
+- `assets/linkedin.svg`
+  - Author: LinkedIn
+  - URL: https://brand.linkedin.com/downloads
+
+- `assets/microsoft.svg`
+  - Author: Microsoft
+  - URL: https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks
+
+- `assets/saml.svg`
+  - Author: OASIS
+  - URL: https://saml.xml.org/wiki/saml-logos
+
+- `assets/sina-weibo.svg`
+  - Author: Sina Weibo
+  - URL: https://weibo.com
+
+- `assets/twitter.svg`
+  - Author: Twitter
+  - URL: https://about.twitter.com/en/who-we-are/brand-toolkit

+ 3 - 0
app/frontend/apps/desktop/initializer/assets/moon.svg

@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.0003 0.278304C6.1825 0.500435 6.2447 0.83521 6.0807 1.13613C5.5201 2.16474 5.20151 3.34302 5.20151 4.59686C5.20151 8.6173 8.4803 11.8727 12.5202 11.8727C13.0466 11.8727 13.5594 11.8175 14.0534 11.7129C14.3912 11.6413 14.6984 11.794 14.8631 12.0286C15.033 12.2707 15.0686 12.6318 14.8318 12.9224C13.3034 14.7985 10.9648 16 8.34357 16C3.73342 16 0 12.2863 0 7.71002C0 4.2658 2.11415 1.31197 5.12354 0.0600878C5.47124 -0.0845512 5.81229 0.0490932 6.0003 0.278304ZM4.85797 1.31064C2.575 2.54229 1.02491 4.94609 1.02491 7.71002C1.02491 11.7305 4.30371 14.9859 8.34357 14.9859C10.3796 14.9859 12.2216 14.1598 13.5491 12.8244C13.2118 12.8656 12.8684 12.8868 12.5202 12.8868C7.91002 12.8868 4.17659 9.17319 4.17659 4.59686C4.17659 3.43014 4.41946 2.31873 4.85797 1.31064Z" />
+</svg>

+ 11 - 0
app/frontend/apps/desktop/initializer/assets/sun.svg

@@ -0,0 +1,11 @@
+<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+<path d="M8 11C6.34315 11 5 9.65685 5 8C5 6.34315 6.34315 5 8 5C9.65685 5 11 6.34315 11 8C11 9.65685 9.65685 11 8 11ZM8 12C10.2091 12 12 10.2091 12 8C12 5.79086 10.2091 4 8 4C5.79086 4 4 5.79086 4 8C4 10.2091 5.79086 12 8 12Z" />
+<path d="M8 0C8.27614 0 8.5 0.223858 8.5 0.5V2.5C8.5 2.77614 8.27614 3 8 3C7.72386 3 7.5 2.77614 7.5 2.5V0.5C7.5 0.223858 7.72386 0 8 0Z" />
+<path d="M8 13C8.27614 13 8.5 13.2239 8.5 13.5V15.5C8.5 15.7761 8.27614 16 8 16C7.72386 16 7.5 15.7761 7.5 15.5V13.5C7.5 13.2239 7.72386 13 8 13Z" />
+<path d="M16 8C16 8.27614 15.7761 8.5 15.5 8.5H13.5C13.2239 8.5 13 8.27614 13 8C13 7.72386 13.2239 7.5 13.5 7.5H15.5C15.7761 7.5 16 7.72386 16 8Z" />
+<path d="M3 8C3 8.27614 2.77614 8.5 2.5 8.5H0.5C0.223858 8.5 -1.20706e-08 8.27614 0 8C1.20706e-08 7.72386 0.223858 7.5 0.5 7.5H2.5C2.77614 7.5 3 7.72386 3 8Z" />
+<path d="M13.6569 2.34318C13.8521 2.53844 13.8521 2.85502 13.6569 3.05028L12.2426 4.4645C12.0474 4.65976 11.7308 4.65976 11.5355 4.4645C11.3403 4.26924 11.3403 3.95265 11.5355 3.75739L12.9497 2.34318C13.145 2.14792 13.4616 2.14792 13.6569 2.34318Z" />
+<path d="M4.46446 11.5356C4.65973 11.7308 4.65973 12.0474 4.46446 12.2427L3.05025 13.6569C2.85499 13.8521 2.53841 13.8521 2.34314 13.6569C2.14788 13.4616 2.14788 13.145 2.34314 12.9498L3.75736 11.5356C3.95262 11.3403 4.2692 11.3403 4.46446 11.5356Z" />
+<path d="M13.6569 13.6569C13.4616 13.8522 13.145 13.8522 12.9497 13.6569L11.5355 12.2427C11.3403 12.0474 11.3403 11.7308 11.5355 11.5356C11.7308 11.3403 12.0474 11.3403 12.2426 11.5356L13.6569 12.9498C13.8521 13.1451 13.8521 13.4616 13.6569 13.6569Z" />
+<path d="M4.46447 4.46451C4.2692 4.65977 3.95262 4.65977 3.75736 4.46451L2.34315 3.0503C2.14788 2.85503 2.14788 2.53845 2.34315 2.34319C2.53841 2.14793 2.85499 2.14793 3.05025 2.34319L4.46447 3.7574C4.65973 3.95267 4.65973 4.26925 4.46447 4.46451Z" />
+</svg>

+ 2 - 2
app/frontend/apps/desktop/initializer/desktopIconsAliasesMap.ts

@@ -52,8 +52,8 @@ export default {
   'editor-mention-knowledge-base': 'mention-kb',
   'editor-mention-text-module': 'snippet',
 
-  '2fa-security-keys': 'security-key',
-  '2fa-authenticator-app': 'mobile-code',
+  '2fa-security-keys': 'key',
+  '2fa-authenticator-app': 'phone',
 
   'form-field-link': 'box-arrow-up-right',
 

Some files were not shown because too many files changed in this diff