You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

622 lines
21 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. <template>
  2. <div class="editPersonalInfo xl:tw-max-w-[1246px] xl:tw-mx-auto">
  3. <div class="xl:tw-flex xl:tw-justify-between xl:tw-items-start">
  4. <userSidebar :userData="userData" :firstName="firstName" :lastName="lastName" class="tw-hidden xl:tw-block">
  5. </userSidebar>
  6. <div class="tw-bg-white xl:tw-p-[30px] xl:tw-rounded-[20px] xl:tw-min-w-[900px] xl:tw-max-w-[900px]">
  7. <div class="tw-text-[20px] tw-font-bold tw-text-base-primary tw-mb-[20px] md:t24 md:tw-mb-[30px]">
  8. <two-dots class="tw-mr-[30px]"></two-dots>{{ $t("userProfile.editPersonalInfo") }}
  9. </div>
  10. <!-- alert -->
  11. <div v-if="successUpdate" class="tw-flex tw-w-full tw-right-auto tw-left-auto">
  12. <div type="success"
  13. class="tw-absolute tw-top-0 tw-right-0 tw-left-0 tw-mx-auto tw-flex tw-flex-row tw-w-fit tw-items-center tw-bg-success-background tw-rounded-[12px] tw-px-[12px] tw-py-[10px]">
  14. <img src="~/assets/svg/alert_success.svg" class="tw-w-[16px] tw-mr-[10px] md:tw-w-[20px]" alt="" />
  15. <div class="tw-hidden md:tw-block tw-text-success-default tw-font-bold tw-text-[16px] md:tw-mr-[16px]">
  16. Awesome!
  17. </div>
  18. <div class="tw-text-[12px] tw-text-success-default tw-mr-[9px] md:tw-text-[16px] md:tw-mr-[16px]">
  19. Successfully updated.
  20. </div>
  21. <img src="~/assets/svg/alert_close.svg" @click="successUpdate = !successUpdate" />
  22. </div>
  23. </div>
  24. <div v-if="fileTooBig" class="tw-flex tw-w-full tw-right-auto tw-left-auto">
  25. <div type="error"
  26. class="tw-absolute tw-top-0 tw-right-0 tw-left-0 tw-mx-auto tw-flex tw-flex-row tw-w-fit tw-items-center tw-bg-error-background tw-rounded-[16px] tw-px-[12px] tw-py-[10px] tw-text-[12px]">
  27. <img src="~/assets/svg/alert_notice.svg" class="tw-w-[16px] tw-mr-[10px] md:tw-w-[20px]" alt="" />
  28. <div class="tw-hidden md:tw-block tw-text-error-default tw-mr-[25px] tw-text-[16px]">
  29. Oops!
  30. </div>
  31. <div class="tw-text-error-default tw-mr-[8px] md:tw-text-[16px] md:tw-mr-[16px]">
  32. Please upload an image under 2MB.
  33. </div>
  34. <img src="~/assets/svg/alert_close_red.svg" @click="fileTooBig = !fileTooBig" alt="" />
  35. </div>
  36. </div>
  37. <!-- user picture -->
  38. <div class="tw-flex tw-items-center tw-mb-[30px]">
  39. <div>
  40. <img v-if="userData.MemberPicture" :src="userData.MemberPicture"
  41. class="tw-border tw-border-solid tw-border-neutral-300 tw-h-[64px] tw-w-[64px] tw-rounded-[50px] tw-mr-[15px] md:tw-h-[90px] md:tw-w-[90px] md:tw-mr-[30px]"
  42. alt="" />
  43. <div v-else
  44. class="tw-flex tw-justify-center tw-items-center tw-bg-primary-1 tw-text-white tw-text-[32px] tw-h-[64px] tw-w-[64px] tw-rounded-[50px] tw-mr-[15px] md:tw-h-[90px] md:tw-w-[90px] md:tw-mr-[30px]">
  45. {{ lastName.split("")[0] || "" }}
  46. </div>
  47. </div>
  48. <div>
  49. <button
  50. class="tw-border tw-border-solid tw-border-primary-1 tw-text-[14px] tw-text-primary-1 tw-px-[16px] tw-py-[9px] tw-rounded-[12px] md:tw-text-[16px]"
  51. @click="handleUploadEvent">
  52. {{ $t("userProfile.uploadPicture") }}
  53. </button>
  54. <input type="file" class="d-none" ref="uploader" @change="getNewPicture($event)" />
  55. <!-- <input type="file" class="d-none" ref="uploader" @change="userPictureUpload" /> -->
  56. </div>
  57. </div>
  58. <!-- form -->
  59. <form
  60. class="tw-grid tw-grid-cols-1 tw-gap-y-[20px] tw-mb-[58px] md:tw-grid-cols-2 md:tw-gap-x-[60px] md:tw-mb-[50px]">
  61. <!-- tilte -->
  62. <div class="element md:tw-col-span-2 md:tw-max-w-[394px]">
  63. <div class="tw-text-[14px] tw-text-base-primary tw-mb-[10px]">
  64. {{ $t("userProfile.Title") }}
  65. </div>
  66. <elementSelectWithIndex :select="{
  67. id: 'Title',
  68. label: 'Title',
  69. required: false,
  70. }" :yearList="genderOptions" :default="userData.Title" :value="genderOptions[0]" :validation="validation.Title"
  71. @change="userData.Title = $event"></elementSelectWithIndex>
  72. </div>
  73. <!-- firstName -->
  74. <div class="element">
  75. <elementInput :input="{
  76. id: 'FirstName',
  77. label: 'First Name',
  78. required: true,
  79. type: 'text',
  80. }" :default="userData.FirstName" :validation="validation.FirstName"
  81. @change="userData.FirstName = $event"></elementInput>
  82. </div>
  83. <!-- lastName -->
  84. <div class="element">
  85. <elementInput :input="{
  86. id: 'LastName',
  87. label: 'Last Name',
  88. required: true,
  89. type: 'text',
  90. }" :default="userData.LastName" :validation="validation.LastName" @change="userData.LastName = $event">
  91. </elementInput>
  92. </div>
  93. <!-- email -->
  94. <div class="element">
  95. <elementInput :input="{
  96. id: 'Email',
  97. label: 'userProfile.personalMail',
  98. required: true,
  99. type: 'email',
  100. }" :default="userData.Email" :validation="validation.Email" @change="userData.Email = $event">
  101. </elementInput>
  102. </div>
  103. <!-- Phone -->
  104. <div>
  105. <div class="tw-text-[14px] tw-text-base-primary tw-mb-[10px]">
  106. {{ $t("userProfile.phone") }}
  107. </div>
  108. <div class="tw-grid tw-grid-cols-[120px_auto] tw-gap-[5px]">
  109. <elementCountryCodeSelect :select="{
  110. required: true,
  111. }" :userCodeSelect="codeSelect" :validation="validation.codeSelect" @returnCode = "getReturnCode"></elementCountryCodeSelect>
  112. <vue-phone-number-input v-model="userData.Phone" :validation="validation.PhoneNo" color="#E5e5e5" error-color="#ef5a5a"
  113. valid-color="#e5e5e5"
  114. :error="error" :border-radius="5"
  115. no-flags :no-country-selector="true" no-example @update="getPhoneData" :translations="translateOption">
  116. </vue-phone-number-input>
  117. </div>
  118. </div>
  119. <!-- Birthday -->
  120. <div class="md:tw-col-span-2 xl:tw-col-span-1">
  121. <div class="tw-text-[14px] tw-text-base-primary tw-mb-[10px]">
  122. {{ $t("userProfile.birthday")
  123. }}<span class="tw-text-error-default">*</span>
  124. </div>
  125. <div class="tw-grid tw-grid-cols-3 tw-gap-[20px]">
  126. <div class="element">
  127. <elementSelectWithIndex :select="{
  128. id: 'yearSelect',
  129. required: true,
  130. }" :yearList="yearOptions" :default="yearSelect" :validation="validation.yearSelect"
  131. @change="yearSelect = $event"></elementSelectWithIndex>
  132. </div>
  133. <div class="element">
  134. <elementSelectWithIndex :select="{
  135. id: 'monthSelect',
  136. required: false,
  137. }" :yearList="monthOptions" :default="monthSelect" :validation="validation.monthSelect"
  138. @change="monthSelect = $event"></elementSelectWithIndex>
  139. </div>
  140. <div class="element">
  141. <elementSelectWithIndex :select="{
  142. id: 'daySelect',
  143. required: false,
  144. }" :yearList="dayOptions" :default="daySelect" :validation="validation.daySelect"
  145. @change="daySelect = $event"></elementSelectWithIndex>
  146. </div>
  147. </div>
  148. </div>
  149. <!-- Country -->
  150. <div class="element md:tw-col-span-2 xl:tw-col-span-1">
  151. <elementSelect :select="{
  152. id: 'Country',
  153. label: 'userProfile.countryAndRegion',
  154. required: true,
  155. }" :selectList="countryOptions" :default="userData.CountryID" :validation="validation.CountryID"
  156. @change="userData.CountryID = $event"></elementSelect>
  157. </div>
  158. <!-- language -->
  159. <v-spacer class="tw-items-center md:tw-flex md:tw-justify-start md:tw-mt-[20px] md:tw-flex-nowrap">
  160. <div class="tw-inline-block tw-mb-[20px] md:tw-whitespace-nowrap md:tw-mb-0">
  161. {{ $t("userProfile.preferLanguage") }}
  162. </div>
  163. <v-spacer class="tw-flex md:tw-w-fit md:tw-ml-[30px]">
  164. <v-checkbox v-model="languageSelect.en" class="mt-0" hide-details :label="$t('English')"></v-checkbox>
  165. <v-checkbox v-model="languageSelect.zhtw" class="ms-10 mt-0" hide-details :label="$t('Chinese')">
  166. </v-checkbox>
  167. </v-spacer>
  168. </v-spacer>
  169. </form>
  170. <!-- button -->
  171. <div>
  172. <button
  173. class="tw-text-[18px] tw-bg-primary-1 tw-text-white tw-py-[12px] tw-w-full tw-rounded-[16px] tw-mb-[10px] md:tw-w-fit md:tw-px-[16px] md:tw-mr-[20px]"
  174. @click="patchUserData()">
  175. {{ $t("userProfile.saveButton") }}
  176. </button>
  177. <button
  178. class="tw-text-[18px] tw-bg-white tw-text-primary-1 tw-py-[12px] tw-w-full tw-rounded-[16px] md:tw-w-fit md:tw-px-[16px] xl:tw-hidden">
  179. {{ $t("Cancel") }}
  180. </button>
  181. </div>
  182. </div>
  183. </div>
  184. <CropImageDialog :isCropImageDialogActive="isCropImageDialogActive" :cropImagePreview="cropImagePreview"
  185. @close-crop-dialog="closeCropDialog" @upload-image-success="userPictureUpload"></CropImageDialog>
  186. <loading :isLoading="isPageLoading"></loading>
  187. </div>
  188. </template>
  189. <script>
  190. import elementSelect from "@/components/newComponent/form/ElementSelect.vue";
  191. import elementInput from "@/components/newComponent/form/ElementInput.vue";
  192. import SavedExhibitions from "../../components/user/savedExhibitions.vue";
  193. import SavedExhibitionsDialog from "../../components/user/savedExhibitionsDialog.vue";
  194. import CropImageDialog from "../../components/user/cropImageDialog.vue";
  195. import TwoDots from "@/components/TwoDots";
  196. import userSidebar from "@/components/user/userSidebar.vue";
  197. import elementSelectWithIndex from "@/components/newComponent/form/ElementSelectWithIndex.vue";
  198. import elementCountryCodeSelect from "@/components/newComponent/form/ElementCountryCodeSelect.vue";
  199. import loading from "@/components/newComponent/loading/loading.vue";
  200. import is from "is_js";
  201. export default {
  202. name: "editPersonalInfo",
  203. layout: "profile",
  204. components: {
  205. elementSelect,
  206. elementInput,
  207. SavedExhibitions,
  208. SavedExhibitionsDialog,
  209. CropImageDialog,
  210. TwoDots,
  211. userSidebar,
  212. elementSelectWithIndex,
  213. is,
  214. elementCountryCodeSelect,
  215. loading,
  216. },
  217. data() {
  218. return {
  219. countryList: [],
  220. genderOptions: ["Mr.", "Ms."],
  221. successUpdate: false,
  222. fileTooBig: false,
  223. firstName: "",
  224. lastName: "",
  225. countryCode: "",
  226. userData: {},
  227. yearOptions: [],
  228. monthOptions: [],
  229. dayOptions: [],
  230. countryOptions: [],
  231. yearSelect: "",
  232. monthSelect: "",
  233. daySelect: "",
  234. languageSelect: {
  235. en: "",
  236. zhtw: "",
  237. },
  238. isCropImageDialogActive: false,
  239. cropImagePreview: "",
  240. translateOption: {
  241. countrySelectorLabel: this.$t("country code"),
  242. phoneNumberLabel: this.$t("phone number"),
  243. },
  244. rules: {
  245. require: (value) => !!value || this.$t("Required."),
  246. email: (v) =>
  247. /^([a-zA-Z0-9_\-\.]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$/.test(
  248. v
  249. ) || this.$t("Invalid email"),
  250. checkPassword: (v) =>
  251. (/(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])/.test(v) &&
  252. v.length >= 8 &&
  253. v.length <= 20) ||
  254. this.$t(
  255. "Passwords must be 8-20 characters with at least 1 number, 1 lower case letter and 1 upper case letter"
  256. ),
  257. },
  258. phoneValid: false,
  259. validation: {
  260. Title: true,
  261. FirstName: true,
  262. LastName: true,
  263. Email: true,
  264. yearSelect: true,
  265. monthSelect: true,
  266. daySelect: true,
  267. CountryID: true,
  268. codeSelect: true,
  269. PhoneNo: true,
  270. },
  271. errors: null,
  272. userPic: {},
  273. payload:[],
  274. dialCode:"",
  275. test:[],
  276. codeSelect: "",
  277. error: false,
  278. isPageLoading: false,
  279. value: "0",
  280. };
  281. },
  282. created() {
  283. this.isPageLoading = true;
  284. this.fetchCountry();
  285. this.$store.dispatch("updatePicture");
  286. this.fetchUserData();
  287. this.$nextTick(()=>{
  288. this.isPageLoading = false;
  289. });
  290. },
  291. mounted() {
  292. this.yearOptions = Array.from(new Array(103), (val, index) =>
  293. (index + 1920).toString()
  294. );
  295. this.monthOptions = Array.from(new Array(13), (val, index) => {
  296. if (index < 10 && index > 0) {
  297. return "0" + index.toString();
  298. }
  299. if (index >= 10) {
  300. return index.toString();
  301. }
  302. });
  303. this.dayOptions = Array.from(new Array(32), (val, index) => {
  304. if (index < 10 && index > 0) {
  305. return "0" + index.toString();
  306. }
  307. if (index >= 10) {
  308. return index.toString();
  309. }
  310. });
  311. this.$nextTick(() => {
  312. window.addEventListener("resize", this.onResize);
  313. });
  314. },
  315. methods: {
  316. getReturnCode(code){
  317. this.userData.PhoneCode = code;
  318. },
  319. getPhoneData(phoneData) {
  320. this.countryCode = phoneData.countryCode;
  321. //this.userData.PhoneCode = phoneData.countryCallingCode;
  322. this.phoneValid = phoneData.isValid;
  323. },
  324. //Save Member Info(待優化)
  325. async patchUserData() {
  326. this.validators();
  327. if(this.validators()){
  328. if(this.$vuetify.breakpoint.name !== "xs"){
  329. this.userData.BirthDate = this.yearSelect + "-" + this.monthSelect + "-" + this.daySelect;
  330. if(this.userData.BirthDate.length < 10){
  331. this.userData.BirthDate = null;
  332. }
  333. }
  334. if(this.languageSelect.en == true){
  335. this.userData.LanguageID = "en-US";
  336. }else if(this.languageSelect.zhtw == true){
  337. this.userData.LanguageID = "zh-TW";
  338. }else{
  339. this.userData.LanguageID = "null";
  340. }
  341. const patchData = JSON.parse(JSON.stringify(this.userData));
  342. console.log(patchData)
  343. await this.$axios
  344. .post(
  345. `/trending/api/Members/Member`, patchData
  346. )
  347. .then((response) => {
  348. //console.log(JSON.stringify(response));
  349. console.log('testresponse: ', response)
  350. if (response.status == 200) {
  351. this.$notify({
  352. type: "success",
  353. text: "更新成功",
  354. });
  355. }
  356. // this.fetchUserData();
  357. this.$auth.$storage.setUniversal("userPicture", patchData.MemberPicture);
  358. this.$store.dispatch("updatePicture");
  359. })
  360. .catch((error) => {
  361. console.log(error);
  362. });
  363. }
  364. },
  365. //Get Member Info
  366. async fetchUserData() {
  367. await this.$axios
  368. .get(
  369. `/trending/api/Members/Info`
  370. )
  371. .then((response) => {
  372. //console.log(response);
  373. if(response && response.data && response.data.DATA && response.data.DATA.rel){
  374. let data = response.data.DATA.rel
  375. if(data){
  376. this.userData = data;
  377. if(this.userData.CountryID == null){
  378. this.userData.CountryID = "0";
  379. }
  380. if(this.userData.Title == null){
  381. this.userData.Title = "0";
  382. }
  383. if(this.userData.PhoneCode == null){
  384. this.userData.PhoneCode = "0";
  385. }
  386. if(this.userData.BirthDate == null){
  387. this.yearSelect = "0";
  388. this.monthSelect = "0";
  389. this.daySelect = "0";
  390. }
  391. this.firstName = this.userData.FirstName;
  392. this.lastName = this.userData.LastName;
  393. //this.userData.Phone ? (this.phoneValid = true): (this.phoneValid = false);
  394. this.codeSelect = this.userData.PhoneCode;
  395. if(this.userData.LanguageID == "en-US"){
  396. this.languageSelect.en = true;
  397. }else if(this.userData.LanguageID == "zh-TW"){
  398. this.languageSelect.zhtw = true;
  399. }else{
  400. this.languageSelect.en = "";
  401. this.languageSelect.zhtw = "";
  402. }
  403. if (
  404. this.userData.BirthDate && typeof this.userData.BirthDate === "object"
  405. ){
  406. this.yearSelect = "";
  407. this.monthSelect = "";
  408. this.daySelect = "";
  409. }else{
  410. const space = this.userData.BirthDate.split("T");
  411. console.log('space: ', space)
  412. const date = space[0].split("-");
  413. console.log('date: ', date)
  414. this.yearSelect = date[0];
  415. this.monthSelect = date[1];
  416. this.daySelect = date[2];
  417. }
  418. }
  419. }
  420. })
  421. .catch((error) => {
  422. console.log(error);
  423. });
  424. },
  425. handleUploadEvent() {
  426. window.addEventListener("focus", () => { }, { once: true });
  427. this.$refs.uploader.click();
  428. },
  429. //
  430. getNewPicture(e) {
  431. this.payload = new FormData();
  432. this.payload.append("file", e.target.files[0]);
  433. const path = URL.createObjectURL(e.target.files[0]);
  434. this.cropImagePreview = path ;
  435. this.isCropImageDialogActive = !this.isCropImageDialogActive;
  436. },
  437. userPictureUpload(e) {
  438. //const fileSize = (e.target.files[0].size / 1024 / 1024).toFixed(1);
  439. this.$axios
  440. .post(
  441. `/trending/api/Members/UploadAvatar`, this.payload
  442. )
  443. .then((response) => {
  444. //console.log(JSON.stringify(response))
  445. if(response && response.data){
  446. this.isCropImageDialogActive = !this.isCropImageDialogActive;
  447. this.fetchUserData();
  448. this.$notify({
  449. type: "success",
  450. text: "更新成功",
  451. });
  452. }
  453. })
  454. .catch((error) => {
  455. console.log(error);
  456. });
  457. },
  458. async fetchCountry() {
  459. await this.$axios
  460. .get(`/trending/api/location/countries?RegionID&Lang=${this.$i18n.localeProperties["langQuery"]}`)
  461. .then((response) => {
  462. //console.log(JSON.stringify(response));
  463. if(response && response.data && response.data.DATA && response.data.DATA.rel){
  464. let data = response.data.DATA.rel
  465. if(data){
  466. this.countryList = data;
  467. this.countryOptions = this.countryList.map((item) => {
  468. return {
  469. id: item.CountryID,
  470. name: item.CountryENName + " " + item.CountryName,
  471. };
  472. });
  473. }
  474. }
  475. })
  476. .catch((error) => {
  477. console.log(error);
  478. });
  479. },
  480. closeCropDialog() {
  481. this.isCropImageDialogActive = !this.isCropImageDialogActive;
  482. },
  483. handleImageUpdate() {
  484. this.fetchUserData();
  485. //this.patchUserData();
  486. this.closeCropDialog();
  487. },
  488. validators() {
  489. if (is.empty(this.userData.FirstName)) {
  490. this.validation.FirstName = false;
  491. } else {
  492. this.validation.FirstName = true;
  493. }
  494. if (is.empty(this.userData.LastName)) {
  495. this.validation.LastName = false;
  496. } else {
  497. this.validation.LastName = true;
  498. }
  499. if (is.empty(this.userData.Email) || is.not.email(this.userData.Email)) {
  500. this.validation.Email = false;
  501. } else {
  502. this.validation.Email = true;
  503. }
  504. if (is.empty(this.yearSelect)) {
  505. this.validation.yearSelect = false;
  506. } else {
  507. this.validation.yearSelect = true;
  508. }
  509. if (is.empty(this.monthSelect)) {
  510. this.validation.monthSelect = false;
  511. } else {
  512. this.validation.monthSelect = true;
  513. }
  514. if (is.empty(this.daySelect)) {
  515. this.validation.daySelect = false;
  516. } else {
  517. this.validation.daySelect = true;
  518. }
  519. if (is.null(this.userData.CountryID) || this.userData.CountryID == 0) {
  520. this.validation.CountryID = false;
  521. } else {
  522. this.validation.CountryID = true;
  523. }
  524. if(this.userData.PhoneCode == "999"){
  525. this.validation.codeSelect = false;
  526. }else{
  527. this.validation.codeSelect = true;
  528. }
  529. if (this.userData.Phone == null
  530. ) {
  531. this.validation.PhoneNo = false;
  532. this.error = true;
  533. } else {
  534. this.validation.PhoneNo = true;
  535. this.error = false;
  536. }
  537. this.errors = Object.entries(this.validation).filter(
  538. (e) => e[1] == false
  539. );
  540. if (this.errors.length > 0) {
  541. return false;
  542. } else {
  543. return true;
  544. }
  545. },
  546. },
  547. };
  548. </script>
  549. <style scoped lang="scss" scoped>
  550. select {
  551. background-image: url("~/assets/svg/dropdownarrow.svg");
  552. width: auto;
  553. height: auto;
  554. background-position: right 12px center;
  555. background-repeat: no-repeat;
  556. }
  557. .success-alert {
  558. position: absolute;
  559. left: 13%;
  560. z-index: 10;
  561. top: -3%;
  562. }
  563. .success-alert-dialog {
  564. position: absolute;
  565. left: 13%;
  566. z-index: 10;
  567. top: 3%;
  568. }
  569. :deep() {
  570. .input-tel__input {
  571. height: 44px;
  572. border: 1px solid #e5e5e5;
  573. }
  574. }
  575. </style>