From 13ae6947a48f4c061b1a6479df889fa2aaf07099 Mon Sep 17 00:00:00 2001
From: Jack <jack.su@pollex.com.tw>
Date: 星期一, 29 十一月 2021 16:01:58 +0800
Subject: [PATCH] Merge branch 'master' of ssh://192.168.0.10:29418/pcalife/PAM

---
 PAMapp/pages/login/index.vue                                           |  500 +++++++++++++++++++++++++++--------------
 pamapi/src/main/java/com/pollex/pam/service/mapper/CustomerMapper.java |   19 +
 pamapi/src/main/java/com/pollex/pam/domain/Customer.java               |   30 +-
 pamapi/src/doc/客戶API/更新個人帳號資訊.txt                                      |   10 
 pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java        |    5 
 pamapi/src/main/java/com/pollex/pam/service/dto/CustomerDTO.java       |   31 ++
 pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java    |    3 
 pamapi/src/main/java/com/pollex/pam/service/CustomerService.java       |   43 ++
 pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java     |    6 
 PAMapp/plugins/filters/date.filter.ts                                  |    2 
 pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java      |    6 
 pamapi/src/main/java/com/pollex/pam/web/rest/CustomerInfoResource.java |   33 ++
 pamapi/src/main/java/com/pollex/pam/service/CustomerAuthService.java   |   21 
 pamapi/src/doc/客戶API/取得個人帳號資訊.txt                                      |    8 
 14 files changed, 495 insertions(+), 222 deletions(-)

diff --git a/PAMapp/pages/login/index.vue b/PAMapp/pages/login/index.vue
index 865dc7f..42d1190 100644
--- a/PAMapp/pages/login/index.vue
+++ b/PAMapp/pages/login/index.vue
@@ -24,7 +24,9 @@
                     'is-invalid': !phoneValid
                   }"
                   v-model="phoneNumber"
-                  placeholder="隢撓�����Ⅳ">
+                  placeholder="隢撓�����Ⅳ"
+                  :disabled="showPhoneOtpCodeField"
+                >
               <div class="error mt-5 mb-5">
                   <span v-show="!phoneValid">����Ⅳ�撘�炊</span>
               </div>
@@ -38,6 +40,7 @@
                 }"
                 v-model="email"
                 placeholder="隢撓� Email ���"
+                :disabled="showEmailVerifyField"
               >
               <div class="error mt-5 mb-5">
                   <span v-show="!emailValid">Email�撘�炊</span>
@@ -51,11 +54,11 @@
               <el-row type="flex" justify="space-between">
                   <div class="mdTxt">頛詨撽�Ⅳ</div>
                   <div class="otp-count-timer">
-                    {{otpCounter}}
+                    {{phoneCounter}}
                   </div>
               </el-row>
 
-              <el-row class="mb-30">
+              <el-row>
                 <input
                   class="pam-input mt-10"
                   :class="{
@@ -65,6 +68,9 @@
                   placeholder="隢撓�撽�Ⅳ"
                   >
               </el-row>
+              <div class="error mt-5 mb-10">
+                  <span v-show="otpCounterSec === 0">撽�Ⅳ撌脤�����撽�Ⅳ</span>
+              </div>
 
               <el-row>
                 <el-button
@@ -72,7 +78,10 @@
                   @click="resentOtp('MOBILE')"
                   icon="icon-arrow"
                 >
-                  ��撽�Ⅳ<span class="pam-field-title__hint pl-5">({{ otpResendCounter }})</span>
+                  ��撽�Ⅳ<span
+                    class="pam-field-title__hint pl-5"
+                    v-if="otpResendCounter !== 0"
+                  >({{ otpResendCounter }})</span>
                 </el-button>
               </el-row>
             </div>
@@ -93,14 +102,37 @@
           <!-- email 撽�Ⅳ -->
           <template v-if="connectDevice === 'EMAIL'">
             <el-row v-show="showEmailVerifyField">
+              <el-row type="flex" justify="space-between">
+                  <div class="mdTxt">頛詨撽�Ⅳ</div>
+                  <div class="otp-count-timer">
+                    {{emailOtpCounter}}
+                  </div>
+              </el-row>
+
+              <el-row>
+                <input
+                  class="pam-input mt-10"
+                  :class="{
+                    'is-invalid': !emailOtpCode
+                  }"
+                  v-model="emailOtpCode"
+                  placeholder="隢撓�撽�Ⅳ"
+                  >
+              </el-row>
+              <div class="error mt-5 mb-10">
+                  <span v-show="emailCounterSec === 0">撽�Ⅳ撌脤�����撽�Ⅳ</span>
+              </div>
+
               <el-button
                 :disabled="!email || emailResendCounter !== 0 || !emailValid"
                 icon="icon-arrow"
                 @click="resentOtp('EMAIL')"
               >
-                ��撽�Ⅳ<span class="pam-field-title__hint pl-5">({{ emailResendCounter }})</span>
+                ��撽�Ⅳ<span
+                    v-if="emailResendCounter !== 0"
+                    class="pam-field-title__hint pl-5"
+                  >({{ emailResendCounter }})</span>
               </el-button>
-              <div class="mt-10 smTxt_bold text--primary">! 隢�����mail敺�</div>
             </el-row>
 
             <el-row v-show="!showEmailVerifyField">
@@ -116,12 +148,12 @@
       </div>
 
 
-      <el-row type="flex" justify="center" class="pam-login-page__action-bar mt-30">
+      <el-row type="flex" justify="center" class="pam-login-page__action-bar mt-30 mb-30">
         <el-button
           type="primary"
-          v-if="connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP'"
-          :disabled="!otpCode || !phoneNumber || !phoneValid"
-          @click="phoneLogin">
+          v-if="(connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP') || (connectDevice === 'EMAIL' && onEmailVerifyResendStatus === 'CAN_RESEND')"
+          :disabled="isSubmitBtnDisabled"
+          @click="login">
           �
         </el-button>
       </el-row>
@@ -129,7 +161,7 @@
       <el-dialog
         title="甇∟�雿輻��"
         :custom-class="'pam-register-dialog'"
-        :visible.sync="registerDialogVisable"
+        :visible.sync="registerDialogVisible"
         :fullscreen="true"
         :close-on-click-modal="false"
         :show-close="false"
@@ -153,7 +185,8 @@
           <el-row class="pt-10">
             <div
               class="mdTxt pam-register-dialog__contract"
-              @scroll="detectContructReadStatus">
+              ref="contract"
+              @scroll="detectContractReadStatus">
               <h3>����犖鞈��鈭��</h3>
               <p class="mt-10">
               �摰�犖鞈��風瘜��������犖鞈����������
@@ -216,15 +249,15 @@
           </el-row>
           <el-row class="pt-30">
             <div class="pam-agree-radio">
-              <label for="agreeControct" class="pam-radio"
+              <label for="agreeContract" class="pam-radio"
                 :class="{disabled: !isReadContract}">
                 <input
                   type="radio"
-                  id="agreeControct"
-                  @click="agreeControct = !agreeControct"
+                  id="agreeContract"
+                  @click="agreeContract = !agreeContract"
                   :disabled="!isReadContract"
-                  value="agreeControct">
-                  <i :class="agreeControct ?'icon-checkbox-1': 'icon-checkbox'"></i>����蒂蝜潛��
+                  value="agreeContract">
+                  <i :class="agreeContract ?'icon-checkbox-1': 'icon-checkbox'"></i>����蒂蝜潛��
               </label>
             </div>
           </el-row>
@@ -232,7 +265,7 @@
         <span slot="footer" class="dialog-footer">
           <el-button
             type="primary"
-            :disabled="!name || !agreeControct || !isReadContract"
+            :disabled="!name || !agreeContract || !isReadContract"
             @click="applyAccount"
             >撱箇�撣唾��
           </el-button>
@@ -288,8 +321,8 @@
 
 <script lang="ts">
 import { namespace } from 'nuxt-property-decorator';
-import { Vue, Component } from 'vue-property-decorator';
-import { LoginRequest, loginVerify, OtpInfo, register, RegisterInfo, sendOtp } from '~/assets/ts/api/consultant';
+import { Vue, Component, Ref } from 'vue-property-decorator';
+import { LoginRequest, LoginVerify, loginVerify, OtpInfo, register, RegisterInfo, sendOtp } from '~/assets/ts/api/consultant';
 import { Role } from '~/assets/ts/models/enum/Role';
 
 const roleStorage = namespace('localStorage');
@@ -298,36 +331,52 @@
 export default class Login extends Vue {
   @roleStorage.Mutation storageIdToken!: (token:string) => void;
   @roleStorage.Mutation storageRole!: (role:string) => void;
+  @Ref('contract') readonly contract!: any;
 
   connectDevice: 'MOBILE' | 'EMAIL' = 'MOBILE';
 
   phoneNumber = '';
   otpCode = '';
   onPhoneVerifyStep: 'APPLY_OTP' | 'INPUT_OTP' | 'SUBMIT_OTP' = 'APPLY_OTP';
-  otpCounter = '15:00';
+  otpCounterSec = 900;
   otpResendCounter = 30;
   otpInterval: any;
   phoneOtpInfo!: OtpInfo;
 
   email = '';
   onEmailVerifyResendStatus: 'APPLY_OTP' | 'CAN_RESEND' = 'APPLY_OTP';
+  emailCounterSec = 900;
   emailResendCounter = 30;
+  emailOtpCode = '';
   emailResendInterval: any;
   emailOtpInfo!: OtpInfo;
 
   name = '';
-  agreeControct = false;
+  agreeContract = false;
   isReadContract = false;
 
   phoneSuccessConfirmVisable = false;
   emailOtpConfirmVisable = false;
 
-  registerDialogVisable = false;
+  registerDialogVisible = false;
   registerSuccessConfirmVisable = false;
 
   applyAccount_onAction = false;
 
-  detectContructReadStatus(event: any): void {
+  mounted() {
+    const phoneOtpTime = localStorage.getItem('phoneOtpTime');
+    const emailOtpTime = localStorage.getItem('emailOtpTime');
+    const parsePhoneOtpTime = phoneOtpTime ? JSON.parse(phoneOtpTime) : '';
+    const parseEmailOtpTime = emailOtpTime ? JSON.parse(emailOtpTime) : '';
+    if (parsePhoneOtpTime && parsePhoneOtpTime.contactType === 'SMS') {
+      this.phoneDiffTime(parsePhoneOtpTime);
+    }
+    if (parseEmailOtpTime && parseEmailOtpTime.contactType === 'EMAIL') {
+      this.emailDiffTime(parseEmailOtpTime);
+    }
+  }
+
+  detectContractReadStatus(event: any): void {
     const scrollTop = Math.round(event.target.scrollTop);
     const height = event.target.scrollHeight - event.target.clientHeight;
     if (Math.floor(scrollTop/10) === (Math.floor(height/10))) {
@@ -335,12 +384,30 @@
     }
   };
 
+  get isSubmitBtnDisabled(): boolean {
+    return this.connectDevice === 'MOBILE'
+      ? (!this.otpCode || !this.phoneNumber || !this.phoneValid || !this.otpCounterSec)
+      : (!this.emailOtpCode || !this.email || !this.emailValid || !this.emailCounterSec)
+  }
+
+  get phoneCounter() {
+    let min = Math.floor(this.otpCounterSec / 60);
+    let sec = Math.floor(this.otpCounterSec % 60);
+    return `${min < 10 ? '0' + min : min} : ${sec < 10 ? '0' + sec : sec}`;
+  }
+
+  get emailOtpCounter() {
+    let min = Math.floor(this.emailCounterSec / 60);
+    let sec = Math.floor(this.emailCounterSec % 60);
+    return `${min < 10 ? '0' + min : min} : ${sec < 10 ? '0' + sec : sec}`;
+  }
+
   get showPhoneOtpCodeField(): boolean {
     return this.connectDevice === 'MOBILE' && this.onPhoneVerifyStep === 'INPUT_OTP';
   };
 
   get showEmailVerifyField(): boolean {
-    return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus !== 'APPLY_OTP';
+    return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus === 'CAN_RESEND';
   };
 
   get phoneValid() {
@@ -353,6 +420,12 @@
     return this.email ? rule.test(this.email) : true;
   }
 
+  get isLoginBtnDisabled() {
+    return this.connectDevice === 'MOBILE'
+      ? (!this.otpCode || !this.phoneNumber || !this.phoneValid || !this.otpCounterSec)
+      : (!this.emailOtpCode || !this.email || !this.emailValid || !this.emailCounterSec)
+  }
+
   applyOtpVerification(type: string): void {
     const isMobile = this.connectDevice === 'MOBILE';
     const loginInfo: LoginRequest = {
@@ -361,7 +434,9 @@
     }
     sendOtp(loginInfo).then(otpInfo => {
       if (otpInfo.success) {
-        this.startOtpCount(type, otpInfo);
+        this.storageOtpTime(type, otpInfo);
+        this.startOtpSetting(type);
+        this.startOtpCount(type);
       }
     });
   };
@@ -370,52 +445,150 @@
     if (type === 'MOBILE') {
       clearInterval(this.otpInterval);
       this.otpResendCounter = 30;
-      this.otpCounter = '15:00';
-      this.startPhoneCounter();
+      this.otpCounterSec = 900;
     } else {
+      clearInterval(this.emailResendInterval);
       this.emailResendCounter = 30;
-      this.startEmailCounter();
+      this.emailCounterSec = 900;
+    }
+    this.applyOtpVerification(type);
+  }
+
+  applyAccount(): void {
+    if (this.applyAccount_onAction) {
+      return ;
+    }
+
+    this.applyAccount_onAction = true;
+    const registerInfo = this.setRegisterInfo();
+
+    register(registerInfo).then(res => {
+      this.storageIdToken(res.data.id_token);
+      this.storageRole(Role.USER);
+      this.storagePhoneOrEmail(registerInfo);
+      this.registerSuccessConfirmVisable = true;
+    }).catch(() => {
+      this.applyAccount_onAction = false;
+    });
+  };
+
+  confirmApplySuccess(): void {
+    this.phoneSuccessConfirmVisable = false;
+    this.registerSuccessConfirmVisable = false;
+    this.$router.go(-1);
+  }
+
+  login() {
+    const login: LoginVerify = this.setLoginInfo();
+    this.removeOtpTime();
+    loginVerify(login).then(res => {
+      this.storageIdToken(res.data.id_token);
+      this.storageRole(Role.USER);
+      this.phoneSuccessConfirmVisable = true;
+      this.storagePhoneOrEmail(this.setRegisterInfo());
+    }).catch(error => {
+      if (error.response.status === 401) {
+        this.registerDialogVisible = true;
+        setTimeout(() => {
+          const isScrollBarNeedless = this.contract.scrollHeight <= this.contract.clientHeight;
+          if (isScrollBarNeedless) {
+            this.isReadContract = true;
+          }
+        }, 1000);
+      }
+    })
+  }
+
+  destroyed() {
+    this.removeOtpTime();
+    clearInterval(this.otpInterval);
+    clearInterval(this.emailResendInterval);
+  }
+
+  private phoneDiffTime(parseOtpTime: any) {
+    const diffSecs = this.calcDiffSecs(parseOtpTime.time);
+
+    if (diffSecs < this.otpCounterSec) {
+      this.otpResendCounter = diffSecs < 30 ? 30 - diffSecs : 0;
+        this.otpCounterSec -= diffSecs;
+        this.phoneNumber = parseOtpTime.phone;
+        this.onPhoneVerifyStep = 'INPUT_OTP';
+        this.phoneOtpInfo = this.setOtpInfo(parseOtpTime);
+        this.startOtpCount('MOBILE');
+    } else {
+      localStorage.removeItem('phoneOtpTime');
+    }
+  }
+
+  private emailDiffTime(parseOtpTime: any) {
+    const diffSecs = this.calcDiffSecs(parseOtpTime.time);
+
+    if (diffSecs < this.emailCounterSec) {
+      this.emailResendCounter =  diffSecs < 30 ? 30 - diffSecs : 0;
+      this.emailCounterSec -= diffSecs;
+      this.email = parseOtpTime.email;
+      this.onEmailVerifyResendStatus = 'CAN_RESEND';
+      this.emailOtpInfo = this.setOtpInfo(parseOtpTime);
+      this.startOtpCount('EMAIL');
+    } else {
+      localStorage.removeItem('emailOtpTime');
+    }
+  }
+
+  private calcDiffSecs(parseOtpTime) {
+    const currentTime = new Date().getTime();
+    const storageTime = new Date(parseOtpTime).getTime();
+    return Math.floor((currentTime - storageTime) / 1000);
+  }
+
+  private setOtpInfo(parseOtpTime) {
+    return {
+      indexKey: parseOtpTime.indexKey,
+      success: true,
+      failCode: '',
+      failReason: '',
+    }
+  }
+
+  private storageOtpTime(type: string, otpInfo: OtpInfo) {
+    type === 'MOBILE' ? this.phoneOtpInfo = otpInfo : this.emailOtpInfo = otpInfo;
+    const info = {...this.setRegisterInfo(), time: new Date()}
+    type === 'MOBILE' ? localStorage.setItem('phoneOtpTime',JSON.stringify(info))
+                      : localStorage.setItem('emailOtpTime',JSON.stringify(info));
+  }
+
+  private startOtpSetting(type: string) {
+    if (type === 'MOBILE') {
+      this.onPhoneVerifyStep = 'INPUT_OTP';
+    } else {
+      this.onEmailVerifyResendStatus = 'CAN_RESEND';
       this.emailOtpConfirmVisable = true;
     }
   }
 
-  private startOtpCount(type: string, otpInfo) {
-    type === 'MOBILE' ? this.phoneOtpInfo = otpInfo : this.emailOtpInfo = otpInfo;
-    if (type === 'MOBILE') {
-        this.onPhoneVerifyStep = 'INPUT_OTP';
-        this.startPhoneCounter();
-      } else {
-        this.onEmailVerifyResendStatus = 'CAN_RESEND';
-        this.startEmailCounter();
-        this.emailOtpConfirmVisable = true;
-      }
+  private startOtpCount(type: string) {
+    type === 'MOBILE' ? this.startPhoneCounter() : this.startEmailCounter();;
   }
 
   private startEmailCounter() {
     this.emailResendInterval = setInterval(() => {
-      this.emailResendCounter -= 1;
-      if (this.emailResendCounter === 0) {
-        clearInterval(this.emailResendInterval)
+      this.emailCounterSec -= 1;
+      if (this.emailResendCounter !== 0) {
+        this.emailResendCounter -= 1;
+      }
+      if (this.emailCounterSec === 0) {
+        clearInterval(this.emailResendInterval);
       }
     }, 1000)
   }
 
   private startPhoneCounter() {
-    const minCount = this.otpCounter.split(':');
-    let secCount = (+minCount[0] * 60) + (+minCount[1]);
-    let min = 0;
-    let sec = 0;
     this.otpInterval = setInterval(() => {
-      secCount -= 1;
-      min = Math.floor(secCount/60);
-      sec = Math.floor(secCount%60);
-      this.otpCounter = `${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}`;
-
+      this.otpCounterSec -= 1;
       if (this.otpResendCounter !== 0) {
         this.otpResendCounter -= 1;
       }
-
-      if (secCount === 0) {
+      if (this.otpCounterSec === 0) {
         clearInterval(this.otpInterval)
       }
     }, 1000)
@@ -439,86 +612,55 @@
         }
   }
 
-  applyAccount(): void {
-    if (this.applyAccount_onAction) {
-      return ;
+  private storagePhoneOrEmail(registerInfo:RegisterInfo):void{
+    const info = {...registerInfo, time: new Date()}
+    localStorage.setItem('userInfo',JSON.stringify(info));
+  }
+
+  private removeOtpTime() {
+    localStorage.removeItem('emailOtpTime');
+    localStorage.removeItem('phoneOtpTime');
+  }
+
+  private setLoginInfo() {
+    const isMobile = this.connectDevice === 'MOBILE'
+    return {
+      account: isMobile ? this.phoneNumber : this.email,
+      indexKey: isMobile ? this.phoneOtpInfo.indexKey : this.emailOtpInfo.indexKey,
+      otpCode: isMobile ? this.otpCode : this.emailOtpCode
     }
-
-    this.applyAccount_onAction = true;
-    const registerInfo = this.setRegisterInfo();
-    this.storagePhoneOrEmail(registerInfo);
-    register(registerInfo).then(res => {
-      this.storageIdToken(res.data.id_token);
-      this.storageRole(Role.USER);
-      this.registerSuccessConfirmVisable = true;
-    }).catch(() => {
-      this.applyAccount_onAction = false;
-    });
-  };
-
-  storagePhoneOrEmail(registerInfo:RegisterInfo):void{
-    localStorage.setItem('userInfo',JSON.stringify(registerInfo));
   }
-
-  confirmApplySuccess(): void {
-    this.phoneSuccessConfirmVisable = false;
-    this.registerSuccessConfirmVisable = false;
-    this.$router.go(-1);
-  }
-
-  phoneLogin() {
-    const login = {
-      account: this.phoneNumber,
-      indexKey: this.phoneOtpInfo.indexKey,
-      otpCode: this.otpCode
-    }
-    loginVerify(login).then(res => {
-      this.storageIdToken(res.data.id_token);
-      this.storageRole(Role.USER);
-      this.phoneSuccessConfirmVisable = true;
-    }).catch(error => {
-      if (error.response.status === 401) {
-        this.registerDialogVisable = true;
-      }
-    })
-  }
-
-  destroyed() {
-    clearInterval(this.otpInterval);
-    clearInterval(this.emailResendInterval);
-  }
-
 }
 </script>
 
 <style lang="scss">
 .pam-login-page {
-  font-size: 20px !important;
-  display: flex;
-  flex-direction: column;
-  .pam-login-page__action-bar {
+    font-size: 20px !important;
     display: flex;
-    flex: 1;
-    align-items: flex-end;
+    flex-direction: column;
+    .pam-login-page__action-bar {
+      display: flex;
+      flex: 1;
+      align-items: flex-end;
+    }
   }
-}
 
-.pam-input {
-  height: 26px;
-  width: calc(100% - 36px);
-  border-radius: 10px !important;
-  padding: 12px 18px !important;
-  border:1px solid #CCCCCC;
-  outline: 0;
-  @extend .text--middle;
-  &::placeholder {
-    color: $PRUDENTIAL_GREY;
+  .pam-input {
+    height: 26px;
+    width: calc(100% - 36px);
+    border-radius: 10px !important;
+    padding: 12px 18px !important;
+    border:1px solid #CCCCCC;
+    outline: 0;
+    @extend .text--middle;
+    &::placeholder {
+      color: $PRUDENTIAL_GREY;
+    }
+    &.is-invalid {
+      border: 1px solid $PRIMARY_RED !important;
+      border-radius: 20px;
+    }
   }
-  &.is-invalid {
-    border: 1px solid $PRIMARY_RED !important;
-    border-radius: 20px;
-  }
-}
 
 .pam-register-dialog__contract {
   $DEVICE_EXTRA_HEIGHT: 42px;
@@ -530,63 +672,79 @@
   border-radius: 6px;
   border: 1px solid #707070;
   padding: 20px;
-}
-
-.pam-radio {
-  color: $PRIMARY_RED;
-  align-items: center;
-  display: flex;
-  font-size: 20px;
-  font-weight: bold;
-  input {
-    display: none;
-  }
-  i {
-    font-size: 27px;
-    padding-right: 5px;
+  @include desktop {
+    height: 335px;
   }
 }
 
-.pam-register-dialog {
-  padding: 30px 20px;
-  display: flex;
-  flex-direction: column;
-  border-radius: 0;
-  &.el-dialog {
-    border-radius: 0;
+  .pam-register-dialog__contract {
+    $DEVICE_EXTRA_HEIGHT: 42px;
+    $ALIGN_PADDING: 60px;
+    $TOP_CONTENT_HEIGHT: 186px;
+    $BOTTOM_CONTENT_HEIGHT: 131px;
+    max-height: calc(100vh - $DEVICE_EXTRA_HEIGHT - $ALIGN_PADDING - $TOP_CONTENT_HEIGHT - $BOTTOM_CONTENT_HEIGHT);
+    overflow-y: scroll;
+    border-radius: 6px;
+    border: 1px solid #707070;
+    padding: 20px;
   }
-  .el-dialog__header {
-    padding: 0;
-    margin-bottom: 30px;
-    .el-dialog__title {
-      @extend .subTitle;
+
+  .pam-radio {
+    color: $PRIMARY_RED;
+    align-items: center;
+    display: flex;
+    font-size: 20px;
+    font-weight: bold;
+    input {
+      display: none;
+    }
+    i {
+      font-size: 27px;
+      padding-right: 5px;
     }
   }
-  .el-dialog__body {
-    flex: 1;
-    padding: 0;
-    margin-bottom: 30px;
+
+  .pam-register-dialog {
+    padding: 30px 20px;
+    display: flex;
+    flex-direction: column;
+    border-radius: 0;
+    &.el-dialog {
+      border-radius: 0;
+    }
+    .el-dialog__header {
+      padding: 0;
+      margin-bottom: 30px;
+      .el-dialog__title {
+        @extend .subTitle;
+      }
+    }
+    .el-dialog__body {
+      flex: 1;
+      padding: 0;
+      margin-bottom: 30px;
+    }
+    .el-dialog__footer {
+      padding: 0 !important;
+    }
   }
-  .el-dialog__footer {
-    padding: 0 !important;
+
+  .pam-field-title__hint {
+    @extend .smTxt_bold;
+    color: #68737A;
   }
-}
 
-.pam-field-title__hint {
-  @extend .smTxt_bold;
-  color: #68737A;
-}
+  .error {
+    @extend .smTxt_bold;
+    @extend .text--primary;
+    height: 16px;
+  }
 
-.error {
-  @extend .smTxt_bold;
-  @extend .text--primary;
-  height: 16px;
-}
-
-.pam-popUp-title {
-  line-height: 24px;
-}
-.disabled {
-  color: #A7A8AA;
-}
+  .pam-popUp-title {
+      font-size: 20px;
+      line-height: 27px;
+  }
+  .disabled {
+    color: #A7A8AA;
+  }
 </style>
diff --git a/PAMapp/plugins/filters/date.filter.ts b/PAMapp/plugins/filters/date.filter.ts
index 9a15a4e..069a7af 100644
--- a/PAMapp/plugins/filters/date.filter.ts
+++ b/PAMapp/plugins/filters/date.filter.ts
@@ -24,7 +24,7 @@
     return compareDate.getFullYear() === today.getFullYear();
   };
 
-  const minutes = date.getMinutes() > 10 ?  date.getMinutes() : `0${date.getMinutes()}`;
+  const minutes = date.getMinutes() > 9 ?  date.getMinutes() : `0${date.getMinutes()}`;
 
   if (isThisYear(date)) {
     return isToday(date)
diff --git "a/pamapi/src/doc/\345\256\242\346\210\266API/\345\217\226\345\276\227\345\200\213\344\272\272\345\270\263\350\231\237\350\263\207\350\250\212.txt" "b/pamapi/src/doc/\345\256\242\346\210\266API/\345\217\226\345\276\227\345\200\213\344\272\272\345\270\263\350\231\237\350\263\207\350\250\212.txt"
new file mode 100644
index 0000000..c95a24a
--- /dev/null
+++ "b/pamapi/src/doc/\345\256\242\346\210\266API/\345\217\226\345\276\227\345\200\213\344\272\272\345\270\263\350\231\237\350\263\207\350\250\212.txt"
@@ -0,0 +1,8 @@
+http get: http://localhost:8080/api/customer/info
+
+response body:
+{
+    "email": null,
+    "phone": "0955555555",
+    "name": "test999"
+}
diff --git "a/pamapi/src/doc/\345\256\242\346\210\266API/\346\233\264\346\226\260\345\200\213\344\272\272\345\270\263\350\231\237\350\263\207\350\250\212.txt" "b/pamapi/src/doc/\345\256\242\346\210\266API/\346\233\264\346\226\260\345\200\213\344\272\272\345\270\263\350\231\237\350\263\207\350\250\212.txt"
new file mode 100644
index 0000000..5884854
--- /dev/null
+++ "b/pamapi/src/doc/\345\256\242\346\210\266API/\346\233\264\346\226\260\345\200\213\344\272\272\345\270\263\350\231\237\350\263\207\350\250\212.txt"
@@ -0,0 +1,10 @@
+http put: http://localhost:8080/api/customer/info
+
+request body:
+{
+    "email": null,
+    "phone": "0955555555",
+    "name": "test998"
+}
+
+response http code: 204 (NO_CONTENT)
diff --git a/pamapi/src/main/java/com/pollex/pam/domain/Customer.java b/pamapi/src/main/java/com/pollex/pam/domain/Customer.java
index 359d0a3..9a94a8c 100644
--- a/pamapi/src/main/java/com/pollex/pam/domain/Customer.java
+++ b/pamapi/src/main/java/com/pollex/pam/domain/Customer.java
@@ -4,49 +4,43 @@
 import java.time.Instant;
 import java.util.Optional;
 
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.EnumType;
-import javax.persistence.Enumerated;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.Table;
+import javax.persistence.*;
 
 import org.springframework.data.annotation.CreatedDate;
 import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
 import org.springframework.util.StringUtils;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.pollex.pam.enums.OtpLoginTypeEnum;
 
+@EntityListeners(AuditingEntityListener.class)
 @Entity
 @Table(name = "customer")
 public class Customer implements Serializable {
-	
-	
+
 	/**
-	 * 
+	 *
 	 */
 	private static final long serialVersionUID = 1L;
-	
+
 	@Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
-	
+
 	@Column(name = "name")
     private String name;
-	
+
 	@Column(name = "phone")
     private String phone;
-	
+
 	@Column(name = "email")
     private String email;
-	
+
 	@Enumerated(value = EnumType.STRING)
 	@Column(name = "contact_type")
     private OtpLoginTypeEnum contactType;
-	
+
 	@CreatedDate
     @Column(name = "created_date", updatable = false)
     @JsonIgnore
@@ -112,7 +106,7 @@
 	public void setContactType(OtpLoginTypeEnum contactType) {
 		this.contactType = contactType;
 	}
-    
+
     public String toAccountString() {
     	return Optional.ofNullable(getPhone())
 				.filter(StringUtils::hasText)
diff --git a/pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java b/pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java
index f1ccc21..e9209f3 100644
--- a/pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java
+++ b/pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java
@@ -1,10 +1,8 @@
 package com.pollex.pam.enums;
 
 public enum CustomerDetailEnum {
-    ID("CustomerId"),
-    NAME("CustomerName"),
-    ACCOUNT("CustomerAccount"),
-    CONTACT_TYPE("ContactType");
+    DB_ID("CustomerDBId"),
+    NAME("CustomerName");
 
     private final String value;
 
diff --git a/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java b/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java
index 5e6c77b..787c1f3 100644
--- a/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java
+++ b/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java
@@ -107,10 +107,9 @@
         return userDetails.get(ConsultantDetailEnum.AGENT_NO.getValue());
     }
 
-    // todo , should get id from user details
-    public static Long getCustomerId() {
+    public static Long getCustomerDBId() {
     	Map<String, String> userDetails = getCurrentUserDetails();
-    	return Long.parseLong(userDetails.get(CustomerDetailEnum.ID.getValue()));
+    	return Long.parseLong(userDetails.get(CustomerDetailEnum.DB_ID.getValue()));
     }
 
     public static Map<String, String> getCurrentUserDetails() {
diff --git a/pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java b/pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java
index 7b0ebfa..b23f88e 100644
--- a/pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java
+++ b/pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java
@@ -16,7 +16,6 @@
 import com.pollex.pam.service.dto.AppointmentCustomerViewDTO;
 import com.pollex.pam.service.mapper.AppointmentCustomerViewMapper;
 import com.pollex.pam.service.mapper.AppointmentDTOMapper;
-import com.pollex.pam.service.mapper.AppointmentMapper;
 import com.pollex.pam.web.rest.errors.AppointmentNotFoundException;
 
 @Service
@@ -40,7 +39,7 @@
 
 	public void customerCreateAppointment(AppointmentCreateDTO appointmentCreateDTO) {
 		Appointment appointment = appointmentDTOMapper.toAppointment(appointmentCreateDTO);
-		appointment.setCustomerId(SecurityUtils.getCustomerId());
+		appointment.setCustomerId(SecurityUtils.getCustomerDBId());
 		appointment.setCommunicateStatus(ContactStatusEnum.RESERVED);
 		appointmentRepository.save(appointment);
 	}
diff --git a/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java b/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
index af9c247..f59f84b 100644
--- a/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
+++ b/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
@@ -44,7 +44,7 @@
     AppointmentCustomerViewMapper appointmentCustomerViewMapper;
 
     public List<ConsultantDTO> getMyConsultantList() {
-        Long userId = SecurityUtils.getCustomerId();
+        Long userId = SecurityUtils.getCustomerDBId();
         return customerFavoriteConsultantRepository.findAllByCustomerId(userId)
             .stream()
             .map(consultantMapper::toDto)
@@ -81,7 +81,7 @@
     public void addConsultantToCustomList(AddConsultantParam param) {
         List<String> agentNoList = param.getAgentNoList();
         List<Consultant> consultants = consultantRepository.findAllByAgentNoIn(agentNoList);
-        Long userId = SecurityUtils.getCustomerId();
+        Long userId = SecurityUtils.getCustomerDBId();
 
         consultants.forEach(consultant -> {
             boolean isConsultantInList = customerFavoriteConsultantRepository.findOneByCustomerIdAndConsultant(userId, consultant).isPresent();
@@ -107,7 +107,7 @@
 	}
 
     public void removeConsultantFromCustomList(String agentNo) {
-        Long customId = SecurityUtils.getCustomerId();
+        Long customId = SecurityUtils.getCustomerDBId();
         Consultant consultant = consultantRepository.findOneByAgentNo(agentNo).orElseThrow(ConsultantNotFoundException::new);
         CustomerFavoriteConsultant target = customerFavoriteConsultantRepository.findOneByCustomerIdAndConsultant(customId, consultant).orElse(null);
 
diff --git a/pamapi/src/main/java/com/pollex/pam/service/CustomerAuthService.java b/pamapi/src/main/java/com/pollex/pam/service/CustomerAuthService.java
index aa9b5b4..8929adf 100644
--- a/pamapi/src/main/java/com/pollex/pam/service/CustomerAuthService.java
+++ b/pamapi/src/main/java/com/pollex/pam/service/CustomerAuthService.java
@@ -25,16 +25,16 @@
 @Service
 @Transactional
 public class CustomerAuthService {
-	
+
 	@Autowired
     AuthenticationManagerBuilder authenticationManagerBuilder;
-	
+
 	@Autowired
     CustomerRepository customerRepository;
 
     @Autowired
     TokenProvider tokenProvider;
-    
+
 	public String authorize(Customer account, String indexKey, String otpCode) {
 		OtpAccount otpAccount = OtpAccount.createOtpAccount(account, indexKey);
         OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(
@@ -43,26 +43,25 @@
         );
 
         SecurityContextHolder.getContext().setAuthentication(authenticationToken);
-        
+
         Authentication authentication = buildCustomerAuthToken(account, otpCode, indexKey);
         String jwt = tokenProvider.createToken(authentication, false);
         return jwt;
 	}
-	
+
 	public UsernamePasswordAuthenticationToken buildCustomerAuthToken(Customer customer
     		, String otpCode, String indexKey) {
-    	
+
         List<GrantedAuthority> grantedAuths = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
-        
+
         final String account = customer.toAccountString();
-        UsernamePasswordAuthenticationToken authenticationToken = 
+        UsernamePasswordAuthenticationToken authenticationToken =
         		new UsernamePasswordAuthenticationToken(account, otpCode, grantedAuths);
 
         Map<String, String> details = new HashMap<>();
-        details.put(CustomerDetailEnum.ID.getValue(), customer.getId().toString());
+        details.put(CustomerDetailEnum.DB_ID.getValue(), customer.getId().toString());
         details.put(CustomerDetailEnum.NAME.getValue(), customer.getName());
-        details.put(CustomerDetailEnum.ACCOUNT.getValue(), account);
-//        details.put(CustomerDetailEnum.CONTACT_TYPE.getValue(), customer.getContactType());
+
         authenticationToken.setDetails(details);
 
         return authenticationToken;
diff --git a/pamapi/src/main/java/com/pollex/pam/service/CustomerService.java b/pamapi/src/main/java/com/pollex/pam/service/CustomerService.java
index fe9a396..79a086d 100644
--- a/pamapi/src/main/java/com/pollex/pam/service/CustomerService.java
+++ b/pamapi/src/main/java/com/pollex/pam/service/CustomerService.java
@@ -2,6 +2,9 @@
 
 import java.util.Optional;
 
+import com.pollex.pam.security.SecurityUtils;
+import com.pollex.pam.service.dto.CustomerDTO;
+import com.pollex.pam.service.mapper.CustomerMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
 import org.springframework.stereotype.Service;
@@ -18,31 +21,34 @@
 @Service
 @Transactional
 public class CustomerService {
-	
+
 	@Autowired
 	CustomerRepository customerRepository;
-	
+
 	@Autowired
     CustomerDTOMapper customerDTOMapper;
-	
+
 	@Autowired
 	CustomerAuthService customerAuthService;
-	
+
 	@Autowired
 	OtpTmpService otpTmpService;
-	
+
+    @Autowired
+    CustomerMapper customerMapper;
+
 	public Customer save(Customer customer) {
 		return customerRepository.save(customer);
 	}
-	
+
 	public Customer registerCustomer(CustomerRegisterDTO registDTO) {
 		boolean isCustomerExist = checkCustomerExist(registDTO);
 		if(isCustomerExist) {
 			throw new UsernameAlreadyUsedException();
-			
+
 		}else {
 			String account = getCustomerAccount(registDTO);
-			
+
 			OtpTmp otpTmp = otpTmpService.findByAccountAndIndexKey(account, registDTO.getIndexKey());
 	    	if(otpTmp.getStatus() == OtpTmpStatusEnum.VERRIFIED) {
 	    		Customer customer = customerDTOMapper.toCustomer(registDTO);
@@ -54,9 +60,28 @@
 	    				+ " => status: " + otpTmp.getStatus());
 	    	}
 		}
-		
+
 	}
 
+    public void updateLoggedCustomer(CustomerDTO customerDTO) {
+        Long customerId = SecurityUtils.getCustomerDBId();
+        Customer customer = customerRepository.findById(customerId)
+            .orElseThrow(() -> new UsernameNotFoundException("customerId which is from token is not found in customer db table, customer id = " + customerId));
+
+        customer.setEmail(customerDTO.getEmail());
+        customer.setPhone(customerDTO.getPhone());
+        customer.setName(customerDTO.getName());
+        customerRepository.save(customer);
+    }
+
+    public CustomerDTO getLoggedCustomerDTO() {
+        Long customerId = SecurityUtils.getCustomerDBId();
+        Customer customer = customerRepository.findById(customerId)
+            .orElseThrow(() -> new UsernameNotFoundException("customerId which is from token is not found in customer db table, customer id = " + customerId));
+
+        return customerMapper.toDto(customer);
+    }
+
 	private String getCustomerAccount(CustomerRegisterDTO registDTO) {
 		return registDTO.getContactType() == OtpLoginTypeEnum.EMAIL?registDTO.getEmail():registDTO.getPhone();
 	}
diff --git a/pamapi/src/main/java/com/pollex/pam/service/dto/CustomerDTO.java b/pamapi/src/main/java/com/pollex/pam/service/dto/CustomerDTO.java
new file mode 100644
index 0000000..176dc0b
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/service/dto/CustomerDTO.java
@@ -0,0 +1,31 @@
+package com.pollex.pam.service.dto;
+
+public class CustomerDTO {
+    private String email;
+    private String phone;
+    private String name;
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/service/mapper/CustomerMapper.java b/pamapi/src/main/java/com/pollex/pam/service/mapper/CustomerMapper.java
new file mode 100644
index 0000000..757d8c9
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/service/mapper/CustomerMapper.java
@@ -0,0 +1,19 @@
+package com.pollex.pam.service.mapper;
+
+import com.pollex.pam.domain.Customer;
+import com.pollex.pam.service.dto.CustomerDTO;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CustomerMapper {
+
+    public CustomerDTO toDto(Customer customer) {
+        CustomerDTO dto = new CustomerDTO();
+        dto.setEmail(customer.getEmail());
+        dto.setPhone(customer.getPhone());
+        dto.setName(customer.getName());
+
+        return dto;
+    }
+
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/CustomerInfoResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/CustomerInfoResource.java
new file mode 100644
index 0000000..df6d70b
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/web/rest/CustomerInfoResource.java
@@ -0,0 +1,33 @@
+package com.pollex.pam.web.rest;
+
+import com.pollex.pam.service.CustomerService;
+import com.pollex.pam.service.dto.CustomerDTO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/customer/info")
+public class CustomerInfoResource {
+
+    @Autowired
+    CustomerService customerService;
+
+    @GetMapping("")
+    public ResponseEntity<CustomerDTO> getLoggedCustomerInfo() {
+        return new ResponseEntity<>(customerService.getLoggedCustomerDTO(), HttpStatus.OK);
+    }
+
+    @PutMapping("")
+    public ResponseEntity<Void> updateLoggedCustomerInfo(@RequestBody CustomerDTO customerDTO) {
+        boolean hasEmail = StringUtils.hasText(customerDTO.getEmail());
+        boolean hasPhone = StringUtils.hasText(customerDTO.getPhone());
+        Assert.isTrue(hasEmail || hasPhone, "the email and the phone both are empty!");
+
+        customerService.updateLoggedCustomer(customerDTO);
+        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+    }
+}

--
Gitblit v1.8.0