diff --git a/components/Partner/PartnerList/PartnerCard/index.jsx b/components/Partner/PartnerList/PartnerCard/index.jsx index 1f1ee828..24ea1ebe 100644 --- a/components/Partner/PartnerList/PartnerCard/index.jsx +++ b/components/Partner/PartnerList/PartnerCard/index.jsx @@ -4,6 +4,7 @@ import { ROLE, EDUCATION_STEP, } from '@/constants/member'; +import moment from 'moment'; import { mapToTable } from '@/utils/helper'; import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; import PartnerCardAvator from './PartnerCardAvator'; @@ -33,7 +34,9 @@ function PartnerCard({ tagList = [], wantToDoList = [], roleList = [], + location, educationStage, + updatedDate, }) { const wantTodo = wantToDoList .map((item) => WANT_TO_DO_WITH_PARTNER_TABLE[item]) @@ -63,11 +66,19 @@ function PartnerCard({ - - 台北市松山區 + {location && ( + <> + + {location} + + )} - 兩天前更新 + + {updatedDate + ? moment(updatedDate).fromNow() + : moment(new Date() - 500 * 60 * 60).fromNow()} + diff --git a/components/Partner/PartnerList/index.jsx b/components/Partner/PartnerList/index.jsx index 549b3d0a..bf11de8f 100644 --- a/components/Partner/PartnerList/index.jsx +++ b/components/Partner/PartnerList/index.jsx @@ -43,6 +43,7 @@ function PartnerList() { tagList={item.tagList} wantToDoList={item.wantToDoList} location={item.location} + updatedDate={item.updatedDate} /> {!mobileScreen && (idx + 1) % 2 === 0 && idx + 1 !== lists.length && ( diff --git a/components/Profile/Accountsetting/index.jsx b/components/Profile/Accountsetting/index.jsx index cdddf5f1..103d9c56 100644 --- a/components/Profile/Accountsetting/index.jsx +++ b/components/Profile/Accountsetting/index.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { useState, useEffect } from 'react'; import { Box, Typography, @@ -8,7 +8,8 @@ import { FormControlLabel, } from '@mui/material'; import { useRouter } from 'next/router'; -import useFirebase from '../../../hooks/useFirebase'; +import { useDispatch, useSelector } from 'react-redux'; +import { updateUser, userLogout } from '@/redux/actions/user'; const TypographyStyle = { fontFamily: 'Noto Sans TC', @@ -20,15 +21,35 @@ const TypographyStyle = { }; const AccountSetting = () => { - const { push } = useRouter(); - const { auth, user, signInWithFacebook, signOutWithGoogle } = useFirebase(); + const dispatch = useDispatch(); + const router = useRouter(); + + const [isSubscribeEmail, setIsSubscribeEmail] = useState(false); + const user = useSelector((state) => state.user); + + const onUpdateUser = () => { + const payload = { + email: user.email, + isSubscribeEmail, + }; + dispatch(updateUser(payload)); + }; + + const logout = () => { + dispatch(userLogout()); + router.push('/'); + }; + + useEffect(() => { + setIsSubscribeEmail(user?.isSubscribeEmail || false); + }, [user]); + return ( { disabled sx={{ width: '100%', margin: '8px 0 30px 0' }} > - daodao@gmail.com + {user.email} - + {/* 電話驗證 - + */} 電子報 } + control={ + // eslint-disable-next-line react/jsx-wrap-multilines + { + setIsSubscribeEmail(event.target.checked); + // onUpdateUser();//待處理取消訂閱 + }} + /> + } label="訂閱電子報與島島阿學的新資訊" /> @@ -92,10 +121,7 @@ const AccountSetting = () => { margin: '24px 0 30px 0', backgroundColor: 'white', }} - onClick={() => { - signOutWithGoogle(); - push('/'); - }} + onClick={logout} > 登出 diff --git a/components/Profile/Contact/index.jsx b/components/Profile/Contact/index.jsx index a8c38f5c..c24a026c 100644 --- a/components/Profile/Contact/index.jsx +++ b/components/Profile/Contact/index.jsx @@ -7,8 +7,49 @@ import { TextareaAutosize, Avatar, } from '@mui/material'; +import styled from '@emotion/styled'; + +const StyledGroup = styled(Box)` + margin-bottom: 16px; +`; + +const StyledTitle = styled(Typography)` + color: var(--black-white-gray-dark, #293a3d); + /* desktop/body-M-Medium */ + font-family: Noto Sans TC; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: 140%; /* 22.4px */ + margin-bottom: 11px; +`; +const StyledTextArea = styled(TextareaAutosize)` + border-radius: 8px; + border: 1px solid var(--black-white-gray-very-light, #dbdbdb); + background: var(--black-white-white, #fff); + padding: 12px 16px; + width: 100%; + min-height: 128px; +`; + +function ContactModal({ + title, + descipt, + avatar, + onClose, + onOk, + isLoadingSubmit, + open, +}) { + const [message, setMessage] = useState(''); + const [contact, setContact] = useState(''); + + const handleSubmit = () => { + onOk({ message, contact }); + setMessage(''); + setContact(''); + }; -function ContactModal({ onClose, onOk, isLoadingSubmit, open }) { return ( - + - 黃芊宇 + {title} - 實驗教育學生 + {descipt} - - 邀請訊息 - - - - 聯繫資訊 - - + + 邀請訊息 + setMessage(e.target.value)} + placeholder="想要和新夥伴交流什麼呢?可以簡單的自我介紹,寫下想認識夥伴的原因。" + /> + + + + 聯絡資訊 + + setContact(e.target.value)} + placeholder="寫下您的聯繫資訊,如 e-mail、line、Facebook、Instagram 等等。" + /> + handleSubmit({ message, contact })} > 送出 diff --git a/components/Profile/Edit/Edit.styled.jsx b/components/Profile/Edit/Edit.styled.jsx new file mode 100644 index 00000000..4e125605 --- /dev/null +++ b/components/Profile/Edit/Edit.styled.jsx @@ -0,0 +1,142 @@ +import styled from '@emotion/styled'; +import { Box, Typography, Button } from '@mui/material'; + +export const HomePageWrapper = styled.div` + --section-height: calc(100vh - 80px); + --section-height-offset: 80px; +`; + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border-radius: 16px; + margin: 0 auto; + width: 672px; + @media (max-width: 767px) { + width: 80%; + .title { + text-overflow: ellipsis; + width: 100%; + } + } +`; + +export const StyledTitleWrap = styled(Box)` + background-color: #ffffff; + padding: 5%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + border-radius: 16px; + h2 { + font-weight: 700; + font-size: 22px; + line-height: 140%; + text-align: center; + color: #536166; + } + p { + font-weight: 700; + font-size: 14px; + line-height: 140%; + text-align: center; + color: #536166; + margin-top: 8px; + } +`; + +export const StyledSection = styled(Box)` + background-color: #ffffff; + padding: 40px; + margin-top: 16px; + width: 100%; + border-radius: 16px; +`; + +export const StyledGroup = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + margin-top: ${({ mt = '20' }) => `${mt}px`}; + input { + padding: 17px 16px 12px; + } +`; + +export const StyledSelectWrapper = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + width: 100%; + margin-top: 10px; + @media (maxwidth: 767px) { + flex-direction: column; + } +`; + +export const StyledSelectText = styled(Typography)` + margin: auto; + font-weight: ${({ isselected }) => + isselected === 'true' ? '700' : 'normal'}; +`; + +export const StyledSelectBox = styled(Box)` + border: 1px solid #dbdbdb; + border-radius: 8px; + padding: 10px; + width: ${({ col = '3' }) => `calc(calc(100% - 16px) / ${col})`}; + display: flex; + justify-items: center; + align-items: center; + cursor: pointer; + background-color: ${({ isselected }) => + isselected === 'true' ? '#DEF5F5' : 'initial'}; + border: ${({ isselected }) => + isselected === 'true' ? '1px solid #16B9B3' : '1px solid #DBDBDB'}; + margin-bottom: 12px; + + @media (maxwidth: 767px) { + width: 100%; + margin: 10px; + } +`; + +export const StyledToggleWrapper = styled(Box)` + border: 1px solid #dbdbdb; + border-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 16px; + padding: 13px 16px; +`; + +export const StyledToggleText = styled(Typography)` + font-weight: 500; + font-size: 16px; + line-height: 140%; + color: #293a3d; +`; + +export const StyledButtonGroup = styled(Box)` + margin-top: 24px; + width: 100%; + display: flex; +`; + +export const StyledButton = styled(Button)(({ variant = 'contained' }) => ({ + ...(variant === 'contained' && { + color: '#ffffff', + backgroundColor: '#16b9b3', + }), + width: '100%', + height: '40px', + borderRadius: '20px', + marginRight: '8px', +})); diff --git a/components/Profile/Edit/index.jsx b/components/Profile/Edit/index.jsx index 5934cda4..fbe9ea3a 100644 --- a/components/Profile/Edit/index.jsx +++ b/components/Profile/Edit/index.jsx @@ -1,177 +1,76 @@ -import React, { useMemo, useState, useEffect } from 'react'; -import styled from '@emotion/styled'; +import React, { useMemo, useEffect } from 'react'; +import toast from 'react-hot-toast'; import { useRouter } from 'next/router'; -import Script from 'next/script'; +import { useSelector } from 'react-redux'; +import COUNTIES from '@/constants/countries.json'; +import { + GENDER, + ROLE, + EDUCATION_STAGE, + WANT_TO_DO_WITH_PARTNER, +} from '@/constants/member'; + import { Box, Typography, - Button, Skeleton, TextField, - Divider, Switch, TextareaAutosize, MenuItem, Select, + Grid, } from '@mui/material'; import { LazyLoadImage } from 'react-lazy-load-image-component'; -import toast from 'react-hot-toast'; -import { useAuthState } from 'react-firebase-hooks/auth'; -import { getAuth, updateProfile } from 'firebase/auth'; import { MobileDatePicker } from '@mui/x-date-pickers/MobileDatePicker'; -import { - getFirestore, - collection, - getDocs, - doc, - getDoc, - setDoc, - addDoc, - updateDoc, -} from 'firebase/firestore'; -import dayjs from 'dayjs'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import SEOConfig from '../../../shared/components/SEO'; -import Navigation from '../../../shared/components/Navigation_v2'; -import Footer from '../../../shared/components/Footer_v2'; -import { - GENDER, - ROLE, - EDUCATION_STEP, - WANT_TO_DO_WITH_PARTNER, - CATEGORIES, -} from '../../../constants/member'; -import COUNTIES from '../../../constants/countries.json'; - -const HomePageWrapper = styled.div` - --section-height: calc(100vh - 80px); - --section-height-offset: 80px; -`; +import SEOConfig from '@/shared/components/SEO'; -const ContentWrapper = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background: white; - border-radius: 16px; - margin: 0 auto; - padding: 40px 10px; - width: 672px; - @media (max-width: 767px) { - width: 80%; - .title { - text-overflow: ellipsis; - width: 100%; - } - } -`; +import useEditProfile from './useEditProfile'; +import { + HomePageWrapper, + ContentWrapper, + StyledGroup, + StyledSelectWrapper, + StyledSelectBox, + StyledSelectText, + StyledToggleWrapper, + StyledToggleText, + StyledTitleWrap, + StyledSection, + StyledButtonGroup, + StyledButton, +} from './Edit.styled'; function EditPage() { const router = useRouter(); - const auth = getAuth(); - const [user, isLoading] = useAuthState(auth); - const [userName, setUserName] = useState(''); - const [photoURL, setPhotoURL] = useState(''); - const [birthDay, setBirthDay] = useState(dayjs()); - const [gender, setGender] = useState(''); - const [roleList, setRoleList] = useState([]); - const [wantToLearnList, setWantToLearnList] = useState([]); - const [interestAreaList, setInterestAreaList] = useState([]); - const [educationStep, setEducationStep] = useState('-1'); - const [location, setLocation] = useState('tw'); - const [url, setUrl] = useState(''); - const [description, setDescription] = useState(''); - const [isOpenLocation, setIsOpenLocation] = useState(false); - const [isOpenProfile, setIsOpenProfile] = useState(false); - const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); - console.log('user', user); - useEffect(() => { - if (!isLoading) { - const db = getFirestore(); - if (user?.uid) { - const docRef = doc(db, 'partnerlist', user?.uid); - getDoc(docRef).then((docSnap) => { - const data = docSnap.data(); - console.log('data', data); - setUserName(data?.userName || user?.displayName || ''); - setPhotoURL(data?.photoURL || user?.photoURL || ''); - setBirthDay(dayjs(data?.birthDay) || dayjs()); - setGender(data?.gender || ''); - setRoleList(data?.roleList || []); - setWantToLearnList(data?.wantToLearnList || []); - setInterestAreaList(data?.interestAreaList || []); - setEducationStep(data?.educationStep); - setLocation(data?.location || ''); - setUrl(data?.url || ''); - setDescription(data?.description || ''); - setIsOpenLocation(data?.isOpenLocation || false); - setIsOpenProfile(data?.isOpenProfile || false); - }); - } - } - }, [user, isLoading]); - const onUpdateUser = (successCallback) => { - const payload = { - userName, - photoURL, - birthDay: birthDay.toISOString(), - gender, - roleList, - wantToLearnList, - interestAreaList, - educationStep, - location, - url, - description, - isOpenLocation, - isOpenProfile, - lastUpdateDate: dayjs().toISOString(), - }; + const { userState, onChangeHandler, onSubmit } = useEditProfile(); + const user = useSelector((state) => state.user); - const db = getFirestore(); + useEffect(() => { + if (user._id) { + Object.entries(user).forEach(([key, value]) => { + if (key !== 'contactList') { + onChangeHandler({ key, value }); + } else { + const { instagram, facebook, discord, line } = value; + onChangeHandler({ key: 'instagram', value: instagram || '' }); + onChangeHandler({ key: 'facebook', value: facebook || '' }); + onChangeHandler({ key: 'discord', value: discord || '' }); + onChangeHandler({ key: 'line', value: line || '' }); + } + }); + } else { + router.push('/'); + } + }, [user]); - const docRef = doc(db, 'partnerlist', user?.uid); - const partnerlistDocRef = doc(db, 'partnerlist', user?.uid); - getDoc(docRef).then((docSnap) => { - setIsLoadingSubmit(true); - if (isOpenProfile) { - toast - .promise( - Promise.allSettled([ - updateDoc(docRef, payload), - setDoc(partnerlistDocRef, payload), - ]).then(() => { - setIsLoadingSubmit(false); - }), - { - success: '更新成功!', - error: '更新失敗', - loading: '更新中...', - }, - ) - .then(() => { - successCallback(); - }); - } else { - toast - .promise( - updateDoc(docRef, payload).then(() => { - setIsLoadingSubmit(false); - }), - { - success: '更新成功!', - error: '更新失敗', - loading: '更新中...', - }, - ) - .then(() => { - successCallback(); - }); - } - }); + const onUpdateUser = async (successCallback) => { + await onSubmit({ id: user._id, email: user.email }); + toast.success('更新成功'); + successCallback(); }; const SEOData = useMemo( @@ -197,657 +96,373 @@ function EditPage() { background: 'linear-gradient(0deg, #f3fcfc, #f3fcfc), #f7f8fa', }} > - - - + +

編輯個人頁面

+

填寫完整資訊可以幫助其他夥伴更了解你哦!

+ - + } + /> + + + + 名稱 * + + onChangeHandler({ key: 'name', value: event.target.value }) + } + /> + + + 生日 * + + onChangeHandler({ key: 'birthDay', value: date }) + } + renderInput={(params) => ( + + )} + /> + + + 性別 * + + {GENDER.map(({ label, value }) => ( + { + onChangeHandler({ key: 'gender', value }); + }} + > + + {label} + + + ))} + + + + 身份 * + + {ROLE.map(({ label, value }) => ( + + onChangeHandler({ + key: 'roleList', + value, + isMultiple: true, + }) + } + > + + {label} + + + ))} + + + +
+ + + + 教育階段 + + + + 居住地 + + + + + {/* 聯絡方式 */} + + + 聯絡方式 + - 填寫完整資訊可以幫助其他夥伴更了解你哦! + 聯絡資訊會呈現在你的公開頁面上,讓夥伴能聯繫你 - + + + + Instagram + { + onChangeHandler({ + key: 'instagram', + value: event.target.value, + }); }} - variant="circular" - animation="wave" + placeholder="請填寫ID" + sx={{ width: '100%' }} /> - } - /> - - - - 您的名稱 * + + + + + Discord { + onChangeHandler({ + key: 'discord', + value: event.target.value, + }); + }} + placeholder="請填寫ID" sx={{ width: '100%' }} - value={userName} - onChange={(event) => setUserName(event.target.value)} /> -
- - 生日 * - setBirthDay(date)} - renderInput={(params) => ( - - )} - /> - - - 性別 * - + + + + Line + { + onChangeHandler({ + key: 'line', + value: event.target.value, + }); }} - > - {GENDER.map(({ label, value }) => ( - { - setGender(value); - }} - sx={{ - border: '1px solid #DBDBDB', - borderRadius: '8px', - padding: '10px', - width: 'calc(calc(100% - 16px) / 3)', - display: 'flex', - justifyItems: 'center', - alignItems: 'center', - cursor: 'pointer', - ...(gender === value - ? { - backgroundColor: '#DEF5F5', - border: '1px solid #16B9B3', - } - : {}), - }} - > - {label} - - ))} - - - - 身份 * - + + + + + Facebook + { + onChangeHandler({ + key: 'facebook', + value: event.target.value, + }); }} - > - {ROLE.slice(0, 3).map(({ label, value }) => ( - { - if (roleList.includes(value)) { - setRoleList((state) => - state.filter((data) => data !== value), - ); - } else { - setRoleList((state) => [...state, value]); - } - }} - sx={{ - border: '1px solid #DBDBDB', - borderRadius: '8px', - padding: '10px', - width: 'calc(calc(100% - 16px) / 3)', - display: 'flex', - justifyItems: 'center', - alignItems: 'center', - cursor: 'pointer', - ...(roleList.includes(value) - ? { - backgroundColor: '#DEF5F5', - border: '1px solid #16B9B3', - } - : {}), - '@media (maxWidth: 767px)': { - width: '100%', - margin: '10px', - }, - }} - > - - {label} - - - ))} - - + + + + + + + + 想和夥伴一起 + + {WANT_TO_DO_WITH_PARTNER.map(({ label, value }) => ( + { + onChangeHandler({ + key: 'wantToDoList', + value, + isMultiple: true, + }); }} > - {ROLE.slice(3).map(({ label, value }) => ( - { - if (roleList.includes(value)) { - setRoleList((state) => - state.filter((data) => data !== value), - ); - } else { - setRoleList((state) => [...state, value]); - } - }} - sx={{ - border: '1px solid #DBDBDB', - borderRadius: '8px', - padding: '10px', - width: 'calc(calc(100% - 16px) / 3)', - display: 'flex', - justifyItems: 'center', - alignItems: 'center', - cursor: 'pointer', - ...(roleList.includes(value) - ? { - backgroundColor: '#DEF5F5', - border: '1px solid #16B9B3', - } - : {}), - '@media (maxWidth: 767px)': { - width: '100%', - margin: '10px', - }, - }} - > - - {label} - - - ))} - - -
-
- - - 教育階段 - - - + + ))} + + + + 可以和夥伴分享的事物 + { + onChangeHandler({ key: 'share', value: e.target.value }); }} - > - 居住地 - - {/* { - setLocation(event.target.value); - }} - /> */} - - + + + {/* TODO: NEED TO FIXED */} + 標籤 + { + onChangeHandler({ + key: 'tagList', + value: event.target.value, + }); }} + /> + - 想和夥伴一起 - - - {WANT_TO_DO_WITH_PARTNER.slice(0, 3).map( - ({ label, value }) => ( - { - if (wantToLearnList.includes(value)) { - setWantToLearnList((state) => - state.filter((data) => data !== value), - ); - } else { - setWantToLearnList((state) => [...state, value]); - } - }} - sx={{ - border: '1px solid #DBDBDB', - borderRadius: '8px', - padding: '10px', - width: 'calc(calc(100% - 16px) / 3)', - display: 'flex', - justifyItems: 'center', - alignItems: 'center', - cursor: 'pointer', - ...(wantToLearnList.includes(value) - ? { - backgroundColor: '#DEF5F5', - border: '1px solid #16B9B3', - } - : {}), - }} - > - - {label} - - - ), - )} - - - {WANT_TO_DO_WITH_PARTNER.slice(3).map( - ({ label, value }) => ( - { - if (wantToLearnList.includes(value)) { - setWantToLearnList((state) => - state.filter((data) => data !== value), - ); - } else { - setWantToLearnList((state) => [...state, value]); - } - }} - sx={{ - border: '1px solid #DBDBDB', - borderRadius: '8px', - padding: '10px', - width: 'calc(calc(100% - 16px) / 3)', - display: 'flex', - justifyItems: 'center', - alignItems: 'center', - cursor: 'pointer', - ...(wantToLearnList.includes(value) - ? { - backgroundColor: '#DEF5F5', - border: '1px solid #16B9B3', - } - : {}), - }} - > - - {label} - - - ), - )} - - - - + + + + 個人簡介 + - 可以和夥伴分享的事物 - - - {/* { + onChangeHandler({ + key: 'selfIntroduction', + value: event.target.value, + }); }} - > - 標籤 - - - 可以是學習領域、興趣等等的標籤,例如:音樂創作、程式語言、電繪、社會議題。 - - */} - + + + + + + 公開顯示居住地 + { + onChangeHandler({ + key: 'isOpenLocation', + value, + }); }} - > - 個人網站或社群 - { - setUrl(event.target.value); - }} - /> - - + + + 公開個人頁面尋找夥伴 + { + onChangeHandler({ + key: 'isOpenProfile', + value, + }); }} - > - 個人簡介 - { - setDescription(event.target.value); - }} - /> - - - + + + + + { + router.push('/profile/myprofile'); }} > - - - 公開顯示居住地 - - { - setIsOpenLocation(value); - }} - /> - - - - 公開個人頁面尋找夥伴 - - { - setIsOpenProfile(value); - }} - /> - - - + { + onUpdateUser(() => router.push('/profile')); }} > - - - - - + 儲存資料 + + + ); diff --git a/components/Profile/Edit/useEditProfile.jsx b/components/Profile/Edit/useEditProfile.jsx new file mode 100644 index 00000000..e682236e --- /dev/null +++ b/components/Profile/Edit/useEditProfile.jsx @@ -0,0 +1,110 @@ +import dayjs from 'dayjs'; +import { useReducer } from 'react'; +import { useDispatch } from 'react-redux'; +import { updateUser } from '@/redux/actions/user'; + +const initialState = { + name: '', + photoURL: '', + birthDay: dayjs(), + gender: '', + roleList: [], + wantToDoList: [], + instagram: '', + facebook: '', + discord: '', + line: '', + educationStage: '-1', + location: 'tw', + tagList: [], + selfIntroduction: '', + share: '', + isOpenLocation: false, + isOpenProfile: false, + isLoadingSubmit: false, +}; + +const userReducer = (state, payload) => { + const { key, value, isMultiple = false } = payload; + if (isMultiple) { + return { + ...state, + [key]: state[key].includes(value) + ? state[key].filter((role) => role !== value) + : [...state[key], value], + }; + } else if (state && state[key] !== undefined) { + return { + ...state, + [key]: value, + }; + } + return state; +}; + +const useEditProfile = () => { + const reduxDispatch = useDispatch(); + + const [userState, stateDispatch] = useReducer(userReducer, initialState); + + // TODO ErrorMap + + const onChangeHandler = ({ key, value, isMultiple }) => { + stateDispatch({ key, value, isMultiple }); + }; + + const onSubmit = async ({ id, email }) => { + if (!id || !email) return; + const { + name, + birthDay, + gender, + roleList, + educationStage, + location, + wantToDoList, + share, + isOpenLocation, + isOpenProfile, + tagList, + selfIntroduction, + instagram, + facebook, + discord, + line, + } = userState; + + const payload = { + id, + email, + name, + birthDay, + gender, + roleList, + contactList: { + instagram, + facebook, + discord, + line, + }, + wantToDoList, + educationStage, + location, + tagList: [tagList], // TODO: 要修改 + selfIntroduction, + share, + isOpenLocation, + isOpenProfile, + }; + + reduxDispatch(updateUser(payload)); + }; + + return { + userState, + onChangeHandler, + onSubmit, + }; +}; + +export default useEditProfile; diff --git a/components/Profile/UserCard/index.jsx b/components/Profile/UserCard/index.jsx index 290d8e56..9c4cfacb 100644 --- a/components/Profile/UserCard/index.jsx +++ b/components/Profile/UserCard/index.jsx @@ -190,6 +190,8 @@ function UserCard({ photoURL, userName, location, + contactList = {}, + updatedDate, }) { const router = useRouter(); @@ -230,30 +232,41 @@ function UserCard({ {!!tagList.length && + !!tagList[0] && tagList[0].split('、').map((tag) => )} -
  • - -

    Chien22

    -
  • -
  • - -

    ene_soew

    -
  • -
  • - -

    Chien12348888

    -
  • -
  • - -

    #Chien22

    -
  • + {!!contactList.instagram && ( +
  • + +

    {contactList.instagram}

    +
  • + )} + {!!contactList.facebook && ( +
  • + +

    {contactList.facebook}

    +
  • + )} + {!!contactList.line && ( +
  • + +

    {contactList.line}

    +
  • + )} + {!!contactList.discord && ( +
  • + +

    {contactList.discord}

    +
  • + )}
    - {moment(new Date() - 500 * 60 * 60).fromNow()} + {updatedDate + ? moment(updatedDate).fromNow() + : moment(new Date() - 500 * 60 * 60).fromNow()}
    diff --git a/components/Profile/UserTabs/index.jsx b/components/Profile/UserTabs/index.jsx index 2bbd7d68..2be4d1bb 100644 --- a/components/Profile/UserTabs/index.jsx +++ b/components/Profile/UserTabs/index.jsx @@ -41,17 +41,17 @@ const UserTabs = ({ description = '', wantToDoList = [], share = '' }) => { sx={{ borderBottom: '1px solid #F3F3F3', paddingBottom: '6px' }} >

    可分享

    - {share || '無'} + {share || '尚未填寫'}

    想一起

    - {wantToDoList || '無'} + {wantToDoList || '尚未填寫'}

    簡介

    - {description || '無'} + {description || '尚未填寫'}
    diff --git a/components/Profile/index.jsx b/components/Profile/index.jsx index 1ccb9a3e..ede32ae5 100644 --- a/components/Profile/index.jsx +++ b/components/Profile/index.jsx @@ -11,7 +11,6 @@ import { mapToTable } from '@/utils/helper'; import SEOConfig from '@/shared/components/SEO'; import UserCard from './UserCard'; import UserTabs from './UserTabs'; -import ContactModal from './Contact'; const BottonBack = { color: '#536166', @@ -33,6 +32,7 @@ const EDUCATION_STEP_TABLE = mapToTable(EDUCATION_STEP); const Profile = ({ name, + email, photoURL, tagList = [], roleList = [], @@ -41,11 +41,14 @@ const Profile = ({ wantToDoList = [], location, share, + enableContactBtn = false, + sendEmail, + handleContactPartner, + contactList = {}, + updatedDate, }) => { const router = useRouter(); const [isLoading] = useState(false); - const [open, setOpen] = useState(false); - const role = roleList.length > 0 && ROLELIST[roleList[0]]; const edu = educationStage && EDUCATION_STEP_TABLE[educationStage]; const wantTodo = wantToDoList @@ -81,20 +84,6 @@ const Profile = ({ }, }} > - { - setOpen(false); - // router.push('/'); - // router.push('/partner'); - }} - onOk={() => { - setOpen(false); - // router.push('/profile'); - // router.push('/profile/edit'); - }} - /> @@ -130,21 +122,23 @@ const Profile = ({ wantToDoList={wantTodo} share={share} /> - - + {email !== sendEmail && ( + + )} ); }; diff --git a/constants/member.js b/constants/member.js index 97938e23..f585b8e8 100644 --- a/constants/member.js +++ b/constants/member.js @@ -106,6 +106,41 @@ export const EDUCATION_STEP = [ }, ]; +export const EDUCATION_STAGE = [ + { + label: '學齡前', + value: 'preschool', + }, + { + label: '國小低年級', + value: 'elementary-junior', + }, + { + label: '國小中年級', + value: 'elementary-middle', + }, + { + label: '國小高年級', + value: 'elementary-senior', + }, + { + label: '國中', + value: 'junior-high', + }, + { + label: '高中', + value: 'high', + }, + { + label: '大學', + value: 'university', + }, + { + label: '其他', + value: 'other', + }, +]; + export const WANT_TO_DO_WITH_PARTNER = [ { label: '交朋友', @@ -128,7 +163,7 @@ export const WANT_TO_DO_WITH_PARTNER = [ value: 'make-group-class', }, { - label: '做專案', + label: '做專案/競賽', key: 'do-project', value: 'do-project', }, diff --git a/package.json b/package.json index 12734547..51031fbb 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "react-typed": "^1.2.0", "redux": "^4.1.0", "redux-logger": "^3.0.6", + "redux-persist": "^6.0.0", "redux-saga": "^1.1.3", "regenerator-runtime": "^0.13.9", "use-image": "^1.0.10" diff --git a/pages/_app.jsx b/pages/_app.jsx index 4c00e61b..896410a4 100644 --- a/pages/_app.jsx +++ b/pages/_app.jsx @@ -7,14 +7,18 @@ import { useRouter } from 'next/router'; import Script from 'next/script'; import Head from 'next/head'; import { initializeApp } from 'firebase/app'; -import GlobalStyle from '../shared/styles/Global'; -import themeFactory from '../shared/styles/themeFactory'; -import storeFactory from '../redux/store'; +import { persistStore } from 'redux-persist'; +import { PersistGate } from 'redux-persist/integration/react'; +import GlobalStyle from '@/shared/styles/Global'; +import themeFactory from '@/shared/styles/themeFactory'; +import storeFactory from '@/redux/store'; import { initGA, logPageView } from '../utils/analytics'; import Mode from '../shared/components/Mode'; import 'regenerator-runtime/runtime'; // Speech.js const store = storeFactory(); +const persistor = persistStore(store); + const firebaseConfig = { apiKey: 'AIzaSyBJK-FKcGHwDy1TMcoJcBdEqbTYpEquUi4', authDomain: 'daodaoedu-4ae8f.firebaseapp.com', @@ -93,7 +97,9 @@ const App = ({ Component, pageProps }) => { /> - + + + ); diff --git a/pages/login/index.jsx b/pages/login/index.jsx index d0a4f949..7a1cba70 100644 --- a/pages/login/index.jsx +++ b/pages/login/index.jsx @@ -1,4 +1,5 @@ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; import styled from '@emotion/styled'; import Router, { useRouter } from 'next/router'; import Script from 'next/script'; @@ -39,8 +40,8 @@ const ContentWrapper = styled.div` `; const LoginPage = () => { - const provider = new GoogleAuthProvider(); const router = useRouter(); + const SEOData = useMemo( () => ({ title: '登入島島|島島阿學', @@ -56,42 +57,8 @@ const LoginPage = () => { ); const onLogin = () => { - const auth = getAuth(); - - signInWithPopup(auth, provider) - .then((result) => { - // This gives you a Google Access Token. You can use it to access the Google API. - // const credential = GoogleAuthProvider.credentialFromResult(result); - // const token = credential.accessToken; - // The signed-in user info. - // console.log('result', result); - const { displayName } = result.user; - // sendDataToChromeExtension( - // 'locidnghejlnnlnbglelhaflehebblei', - // result.user, - // ); - const db = getFirestore(); - const docRef = doc(db, 'partnerlist', result?.user?.uid); - getDoc(docRef).then((docSnap) => { - // const isNewUser = Object.keys(docSnap.data() || {}).length === 0; - // if (isNewUser) { - toast.success(`歡迎登入! ${displayName}`); - router.push('/signin'); - // } else { - // toast.success(`歡迎回來! ${displayName}`); - // router.push('/'); - // } - }); - console.log(result); - }) - .catch((error) => { - console.log('error', error); - toast.error('登入失敗', { - style: { - marginTop: '70px', - }, - }); - }); + // toast.success(`歡迎登入! ${displayName}`); + window.open('https://daodao-server.onrender.com/auth/google', '_target'); }; return ( diff --git a/pages/partner/detail/index.jsx b/pages/partner/detail/index.jsx index 8f192ea2..5e99f3df 100644 --- a/pages/partner/detail/index.jsx +++ b/pages/partner/detail/index.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/router'; import { useSelector, useDispatch } from 'react-redux'; @@ -6,21 +6,55 @@ import styled from '@emotion/styled'; import Navigation from '@/shared/components/Navigation_v2'; import Footer from '@/shared/components/Footer_v2'; import Profile from '@/components/Profile'; - -import { fetchPartnerById } from '@/redux/actions/partners'; +import ContactModal from '@/components/Profile/Contact'; +import { sendEmailToPartner, fetchPartnerById } from '@/redux/actions/partners'; +import toast from 'react-hot-toast'; +import { ROLE } from '@/constants/member'; +import { mapToTable } from '@/utils/helper'; const HomePageWrapper = styled.div` --section-height: calc(100vh - 80px); --section-height-offset: 80px; `; +const ROLELIST = mapToTable(ROLE); + const Detail = () => { const dispatch = useDispatch(); + const [open, setOpen] = useState(false); + const { partner } = useSelector((state) => state?.partners); + const partnerRole = useMemo(() => { + return partner?.roleList && partner.roleList.length > 0 + ? ROLELIST[partner.roleList[0]] + : ''; + }, [partner]); + + const { + name, + roleList, + photoURL, + email: loginUserEmail, + } = useSelector((state) => state?.user); const router = useRouter(); const { id } = router.query; + const handleOnOk = ({ message, contact }) => { + dispatch( + sendEmailToPartner({ + to: partner.email, + name, + roleList: roleList.length > 0 ? roleList : [''], + photoURL, + text: message, + information: [loginUserEmail, contact], + }), + ); + setOpen(false); + toast.success('寄送成功'); + }; + const fetchUser = async () => { dispatch(fetchPartnerById({ id })); }; @@ -33,7 +67,24 @@ const Detail = () => { return ( - + {!!partner && ( + { + setOpen(false); + }} + onOk={handleOnOk} + /> + )} + setOpen(true)} + />