Login.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <template>
  3. <!-- TODO: Only a dummy implementation for the login... -->
  4. <div class="flex h-full min-h-screen flex-col items-center px-7 pt-7 pb-4">
  5. <div class="m-auto w-full max-w-md">
  6. <div class="flex grow flex-col justify-center">
  7. <div class="my-5 grow">
  8. <div class="flex justify-center p-2">
  9. <CommonLogo />
  10. </div>
  11. <div class="mb-6 flex justify-center p-2 text-2xl font-extrabold">
  12. {{ applicationConfig.value.product_name }}
  13. </div>
  14. <template v-if="applicationConfig.value.maintenance_login">
  15. <!-- eslint-disable vue/no-v-html -->
  16. <div
  17. class="my-1 flex items-center rounded bg-green py-2 px-4 text-white"
  18. v-html="applicationConfig.value.maintenance_login_message"
  19. ></div>
  20. </template>
  21. <Form
  22. v-bind:schema="formSchema"
  23. class="text-left"
  24. v-on:submit="login"
  25. >
  26. <template v-slot:after-fields>
  27. <div class="mt-4 flex grow items-center justify-center">
  28. <span class="ltr:mr-1 rtl:ml-1">{{ i18n.t('New user?') }}</span>
  29. <CommonLink
  30. v-bind:link="'TODO'"
  31. class="cursor-pointer select-none !text-yellow underline"
  32. >{{ i18n.t('Register') }}</CommonLink
  33. >
  34. </div>
  35. <FormKit
  36. wrapper-class="mx-8 mt-8 flex grow justify-center items-center"
  37. input-class="py-2 px-4 w-full h-14 text-xl font-semibold text-black bg-yellow rounded select-none"
  38. type="submit"
  39. >
  40. {{ i18n.t('Sign in') }}
  41. </FormKit>
  42. </template>
  43. </Form>
  44. </div>
  45. </div>
  46. </div>
  47. <div class="mb-6 flex items-center justify-center">
  48. <CommonLink link="TODO" class="!text-gray underline">
  49. {{ i18n.t('Continue to desktop app') }}
  50. </CommonLink>
  51. </div>
  52. <div class="flex items-center justify-center align-middle text-gray-200">
  53. <CommonLink
  54. link="https://zammad.org"
  55. is-external
  56. open-in-new-tab
  57. class="ltr:mr-1 rtl:ml-1"
  58. >
  59. <CommonIcon name="logo" v-bind:fixed-size="{ width: 24, height: 24 }" />
  60. </CommonLink>
  61. <span class="ltr:mr-1 rtl:ml-1">{{ i18n.t('Powered by') }}</span>
  62. <CommonLink
  63. link="https://zammad.org"
  64. is-external
  65. open-in-new-tab
  66. class="font-semibold !text-gray-200"
  67. >
  68. Zammad
  69. </CommonLink>
  70. </div>
  71. </div>
  72. </template>
  73. <script setup lang="ts">
  74. import useNotifications from '@common/composables/useNotifications'
  75. import useAuthenticationStore from '@common/stores/authenticated'
  76. import { useRouter } from 'vue-router'
  77. import { NotificationTypes } from '@common/types/notification'
  78. import CommonLogo from '@common/components/common/CommonLogo.vue'
  79. import useApplicationConfigStore from '@common/stores/application/config'
  80. import { i18n } from '@common/utils/i18n'
  81. import { FormKitGroupValue } from '@formkit/core'
  82. import Form from '@common/components/form/Form.vue'
  83. interface Props {
  84. invalidatedSession?: string
  85. }
  86. const props = defineProps<Props>()
  87. // Output a hint when the session is longer valid.
  88. // This could happen because because the session was deleted on the server.
  89. if (props.invalidatedSession === '1') {
  90. const { notify } = useNotifications()
  91. notify({
  92. message: __('The session is no longer valid. Please log in again.'),
  93. type: NotificationTypes.WARN,
  94. })
  95. }
  96. const authentication = useAuthenticationStore()
  97. const router = useRouter()
  98. const forFloatingLabel = {
  99. wrapper: {
  100. attrs: {
  101. 'data-has-value': {
  102. if: '$_value != "" && $fns.string($_value) !== "undefined"',
  103. then: 'true',
  104. else: undefined,
  105. },
  106. },
  107. },
  108. }
  109. const formSchema = [
  110. {
  111. type: 'text',
  112. name: 'login',
  113. label: __('Username / Email'),
  114. placeholder: __('Username / Email'),
  115. wrapperClass: 'relative floating-input',
  116. inputClass:
  117. 'block mt-1 w-full h-14 text-sm bg-gray-500 rounded border-none focus:outline-none placeholder:text-transparent',
  118. labelClass:
  119. 'absolute top-0 left-0 py-5 px-3 h-full text-base transition-all duration-100 ease-in-out origin-left pointer-events-none',
  120. sectionsSchema: forFloatingLabel,
  121. validation: 'required',
  122. },
  123. {
  124. type: 'password',
  125. label: __('Password'),
  126. name: 'password',
  127. placeholder: __('Password'),
  128. wrapperClass: 'relative floating-input',
  129. inputClass:
  130. 'block mt-1 w-full h-14 text-sm bg-gray-500 rounded border-none focus:outline-none placeholder:text-transparent',
  131. labelClass:
  132. 'absolute top-0 left-0 py-5 px-3 h-full text-base transition-all duration-100 ease-in-out origin-left pointer-events-none',
  133. sectionsSchema: forFloatingLabel,
  134. validation: 'required',
  135. },
  136. {
  137. isLayout: true,
  138. element: 'div',
  139. attrs: {
  140. class: 'mt-2 flex grow items-center justify-between text-white',
  141. },
  142. children: [
  143. {
  144. type: 'checkbox',
  145. label: __('Remember me'),
  146. name: 'remember_me',
  147. wrapperClass: 'inline-flex items-center',
  148. inputClass:
  149. 'appearance-none h-4 w-4 border-[1.5px] border-white rounded-sm bg-transparent',
  150. innerClass: 'mr-2',
  151. },
  152. {
  153. isLayout: true,
  154. component: 'CommonLink',
  155. props: {
  156. class: 'text-right !text-white',
  157. link: 'TODO',
  158. },
  159. children: i18n.t('Forgot password?'),
  160. },
  161. ],
  162. },
  163. ]
  164. interface FormData {
  165. login: string
  166. password: string
  167. }
  168. const login = (formData: FormKitGroupValue): void => {
  169. const data = formData as unknown as FormData
  170. authentication
  171. .login(data.login, data.password)
  172. .then(() => {
  173. router.replace('/')
  174. })
  175. .catch((errors) => {
  176. const { notify } = useNotifications()
  177. notify({
  178. message: errors[0],
  179. type: NotificationTypes.ERROR,
  180. })
  181. })
  182. }
  183. const applicationConfig = useApplicationConfigStore()
  184. </script>
  185. <style lang="postcss">
  186. .floating-input > .formkit-inner > input:focus,
  187. .floating-input > .formkit-inner > input:not(:placeholder-shown) {
  188. @apply pt-8;
  189. }
  190. .floating-input:focus-within > label,
  191. .floating-input[data-has-value] > label {
  192. @apply -translate-y-3 translate-x-1 scale-75 opacity-75;
  193. }
  194. </style>