瀏覽代碼

enhance(auth): add multilingual support for signup and password reset flows

charlie 4 天之前
父節點
當前提交
9b5916c563

+ 15 - 0
packages/ui/src/amplify/lang.ts

@@ -1,8 +1,15 @@
 export default {
   'en': {
+    'signup': 'Sign Up',
+    'reset-password': 'Reset Password',
+    'confirm-code': 'Confirm Code',
     'CODE_ON_THE_WAY_TIP': 'Your code is on the way. To log in, enter the code we sent you. It may take a minute to arrive.',
   },
   'zh-cn': {
+    'login': '登录',
+    'signup': '注册',
+    'reset-password': '重置密码',
+    'confirm-code': '确认验证码',
     'CODE_ON_THE_WAY_TIP': '验证码已发送。请输入我们发送给您的验证码以登录。可能需要一分钟才能收到。',
     'Sign in to your account': '登录到您的账户',
     'Email': '电子邮箱',
@@ -29,6 +36,10 @@ export default {
     'Enter your email': '请输入您的电子邮箱'
   },
   'zh-hant': {
+    'login': '登入',
+    'signup': '註冊',
+    'reset-password': '重置密碼',
+    'confirm-code': '確認驗證碼',
     'CODE_ON_THE_WAY_TIP': '驗證碼已發送。請輸入我們發送給您的驗證碼以登入。可能需要一分鐘才能收到。',
     'Sign in to your account': '登入到您的帳戶',
     'Email': '電子郵箱',
@@ -55,6 +66,10 @@ export default {
     'Enter your email': '請輸入您的電子郵箱'
   },
   'ja': {
+    'login': 'ログイン',
+    'signup': 'サインアップ',
+    'reset-password': 'パスワードをリセットする',
+    'confirm-code': 'コードを確認する',
     'CODE_ON_THE_WAY_TIP': 'コードが送信されました。ログインするには、送信したコードを入力してください。届くまでに1分ほどかかる場合があります。',
     'Sign in to your account': 'アカウントにサインイン',
     'Email': 'メール',

+ 16 - 16
packages/ui/src/amplify/ui.tsx

@@ -59,7 +59,7 @@ function InputRow(
       <Input type={localType} {...rest as any} />
 
       {isPassword && (
-        <a className={'absolute px-2 right-1 top-6 py-3  flex items-center opacity-50 hover:opacity-80 select-none'}
+        <a className={'absolute px-2 right-1 top-7 py-3  flex items-center opacity-50 hover:opacity-80 select-none'}
            onClick={() => {
              setShowPassword(!showPassword)
              setLocalType(showPassword ? 'password' : 'text')
@@ -197,7 +197,7 @@ export function LoginForm() {
             setCurrentTab({ type: 'confirm-code', props: { user: { ...ret, username }, nextStep } })
             return
           case 'RESET_PASSWORD':
-            setCurrentTab({ type: 'reset', props: { user: { ...ret, username }, nextStep } })
+            setCurrentTab({ type: 'reset-password', props: { user: { ...ret, username }, nextStep } })
             return
           case 'DONE':
             // signed in
@@ -227,15 +227,15 @@ export function LoginForm() {
             <span className={'opacity-50'}>{t('Don\'t have an account?')} </span>
             <a
               onClick={() => setCurrentTab('signup')}
-              className={'underline opacity-50 hover:opacity-80'}
+              className={'underline opacity-60 hover:opacity-80'}
             >{t('Sign up')}</a>
             <br/>
             <span className={'opacity-50'}>{t('or')} &nbsp;</span>
           </span>
 
           <a onClick={() => {
-            setCurrentTab('reset')
-          }} className={'text-sm opacity-50 hover:opacity-80 underline'}>
+            setCurrentTab('reset-password')
+          }} className={'text-sm opacity-60 hover:opacity-80 underline'}>
             {t('Forgot your password?')}
           </a>
         </p>
@@ -357,7 +357,7 @@ export function SignupForm() {
 
         <p className={'pt-1 text-center'}>
           <a onClick={() => setCurrentTab('login')}
-             className={'text-sm opacity-50 hover:opacity-80 underline'}>
+             className={'text-sm opacity-60 hover:opacity-80 underline'}>
             {t('Back to login')}
           </a>
         </p>
@@ -487,7 +487,7 @@ export function ResetPasswordForm() {
 
             <p className={'pt-4 text-center'}>
               <a onClick={() => setCurrentTab('login')}
-                 className={'text-sm opacity-50 hover:opacity-80 underline'}>
+                 className={'text-sm opacity-60 hover:opacity-80 underline'}>
                 {t('Back to login')}
               </a>
             </p>
@@ -510,7 +510,7 @@ export function ResetPasswordForm() {
 
             <p className={'pt-3 text-center'}>
               <a onClick={() => setCurrentTab('login')}
-                 className={'text-sm opacity-50 hover:opacity-80 underline'}>
+                 className={'text-sm opacity-60 hover:opacity-80 underline'}>
                 {t('Back to login')}
               </a>
             </p>
@@ -564,7 +564,7 @@ export function ConfirmWithCodeForm(
               challengeResponse: data.code as string,
             })
 
-            console.log('===>> confirmSignIn: ', ret)
+            console.debug('confirmSignIn: ', ret)
           }
         } catch (e) {
           setErrors({ code: { message: (e as Error).message, title: t('Bad Response.') } })
@@ -605,7 +605,7 @@ export function ConfirmWithCodeForm(
                   username: props.user?.username
                 })
 
-                console.log('===>>', ret)
+                console.debug('resendSignUpCode: ', ret)
               } else {
                 // await Auth.resendSignInCode(props.user)
               }
@@ -634,7 +634,7 @@ export function ConfirmWithCodeForm(
 
         <p className={'pt-4 text-center'}>
           <a onClick={() => setCurrentTab('login')}
-             className={'text-sm opacity-50 hover:opacity-80 underline'}>
+             className={'text-sm opacity-60 hover:opacity-80 underline'}>
             {t('Back to login')}
           </a>
         </p>
@@ -645,7 +645,7 @@ export function ConfirmWithCodeForm(
 
 export function LSAuthenticator(props: any) {
   const [errors, setErrors] = React.useState<string | null>(null)
-  const [currentTab, setCurrentTab] = React.useState<'login' | 'reset' | 'signup' | 'confirm-code' | any>('login')
+  const [currentTab, setCurrentTab] = React.useState<'login' | 'signup' | 'reset-password' | 'confirm-code' | any>('login')
   const onSessionCallback = React.useCallback((session: any) => {
     props.onSessionCallback?.(session)
   }, [props.onSessionCallback])
@@ -663,12 +663,12 @@ export function LSAuthenticator(props: any) {
     case 'login':
       content = <LoginForm/>
       break
-    case 'reset':
-      content = <ResetPasswordForm/>
-      break
     case 'signup':
       content = <SignupForm/>
       break
+    case 'reset-password':
+      content = <ResetPasswordForm/>
+      break
     case 'confirm-code':
       content = <ConfirmWithCodeForm {..._currentTabProps}/>
       break
@@ -679,7 +679,7 @@ export function LSAuthenticator(props: any) {
       errors, setErrors, setCurrentTab,
       onSessionCallback, userSessionRender: props.children
     }}>
-      {props.titleRender?.(_currentTab)}
+      {props.titleRender?.(_currentTab, t(_currentTab))}
       <div className={'ls-authenticator-content'}>
         {content}
       </div>

+ 14 - 10
src/main/frontend/components/user/login.cljs

@@ -1,7 +1,7 @@
 (ns frontend.components.user.login
   (:require [cljs-bean.core :as bean]
             [clojure.string :as string]
-            [dommy.core :refer-macros [sel]]
+            [dommy.core :refer-macros [sel by-id]]
             [frontend.config :as config]
             [frontend.handler.notification :as notification]
             [frontend.handler.route :as route-handler]
@@ -55,17 +55,19 @@
 
 (rum/defc page-impl
   []
-  (let [*ref-el (rum/use-ref nil)]
-
+  (let [*ref-el (rum/use-ref nil)
+        [tab set-tab!] (rum/use-state nil)]
     [:div.cp__user-login
-     {:ref *ref-el}
+     {:ref *ref-el
+      :id (str "user-auth-" tab)}
      (LSAuthenticator
-      {:titleRender (fn [tab]
+      {:titleRender (fn [key title]
+                      (set-tab! key)
                       (shui/card-header
                        {:class "px-0"}
                        (shui/card-title
                         {:class "capitalize"}
-                        (string/replace tab "-" " "))))
+                        (string/replace title "-" " "))))
        :onSessionCallback #()}
       (fn [^js op]
         (let [sign-out!' (.-signOut op)
@@ -86,7 +88,9 @@
   (shui/dialog-open!
    (fn [_close] (modal-inner))
    {:label "user-login"
-    :content-props {:onPointerDownOutside #(let [inputs (sel ".ls-authenticator-content form input:not([type=checkbox])")
-                                                 inputs (some->> inputs (map (fn [^js e] (.-value e))) (remove string/blank?))]
-                                             (when (seq inputs)
-                                               (.preventDefault %)))}}))
+    :content-props {:onPointerDownOutside #(if (by-id "#user-auth-login")
+                                             (let [inputs (sel ".ls-authenticator-content form input:not([type=checkbox])")
+                                                   inputs (some->> inputs (map (fn [^js e] (.-value e))) (remove string/blank?))]
+                                               (when (seq inputs)
+                                                 (.preventDefault %)))
+                                             (.preventDefault %))}}))

+ 6 - 2
src/main/frontend/components/user/login.css

@@ -1,10 +1,14 @@
-.cp__user-login {}
+.cp__user-login {
+  span.opacity-50, a.opacity-60 {
+    @apply opacity-80;
+  }
+}
 
 .ui__dialog-content[label=user-login] {
   @apply flex items-center top-0 px-6 pt-0 w-auto;
 
   .ui__dialog-main-content {
-    @apply p-0 w-[70vw] relative max-w-[600px] sm:max-w-[80vw] sm:w-[450px];
+    @apply p-0 w-[70vw] relative max-w-[500px] sm:w-[440px];
   }
 
   .ui__modal-close-wrap {

+ 2 - 1
tailwind.config.js

@@ -129,7 +129,8 @@ module.exports = {
     './resources/**/*.html',
     './deps/shui/src/**/*.cljs',
     './deps/shui/src/**/*.cljc',
-    './packages/ui/@/components/**/*.{ts,tsx}'
+    './packages/ui/@/components/**/*.{ts,tsx}',
+    './packages/ui/src/amplify/**/*.{ts,tsx}'
   ],
   safelist: [
     'bg-black', 'bg-white', 'capitalize-first',