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)}
+ />
);
diff --git a/pages/profile/index.jsx b/pages/profile/index.jsx
index 629c35bf..fad4f742 100644
--- a/pages/profile/index.jsx
+++ b/pages/profile/index.jsx
@@ -74,6 +74,7 @@ const ProfilePage = () => {
backgroundColor: 'white',
borderRadius: '8px',
margin: '26px 40px 0 0',
+ padding: '8px',
}}
>
{
value={value}
onChange={handleChange}
aria-label="Vertical tabs example"
+ indicatorColor="transparent"
>
-
-
+
+
diff --git a/pages/profile/myprofile/index.jsx b/pages/profile/myprofile/index.jsx
index 1873317d..8d54854c 100644
--- a/pages/profile/myprofile/index.jsx
+++ b/pages/profile/myprofile/index.jsx
@@ -1,8 +1,9 @@
import React from 'react';
+import { useSelector } from 'react-redux';
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 Navigation from '@/shared/components/Navigation_v2';
+import Footer from '@/shared/components/Footer_v2';
+import Profile from '@/components/Profile';
const HomePageWrapper = styled.div`
--section-height: calc(100vh - 80px);
@@ -10,10 +11,12 @@ const HomePageWrapper = styled.div`
`;
const ProfilePage = () => {
+ const user = useSelector((state) => state.user);
+
return (
-
+
);
diff --git a/pages/signin/index.jsx b/pages/signin/index.jsx
index 3a87431c..4166e2a8 100644
--- a/pages/signin/index.jsx
+++ b/pages/signin/index.jsx
@@ -1,57 +1,29 @@
-import React, { useMemo, useState, useEffect } from 'react';
-import styled from '@emotion/styled';
+import { useMemo, useState, useEffect } from 'react';
import { useRouter } from 'next/router';
-import Script from 'next/script';
-import {
- Box,
- Typography,
- Button,
- Skeleton,
- TextField,
- Divider,
- Switch,
- TextareaAutosize,
- MenuItem,
- Select,
-} from '@mui/material';
+import { useSelector, useDispatch } from 'react-redux';
+import { fetchUserById, updateUser } from '@/redux/actions/user';
+import { GENDER, ROLE } from '@/constants/member';
+import dayjs from 'dayjs';
+
+import { Box, Typography, Button, Skeleton, TextField } 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,
-} from 'firebase/firestore';
-import dayjs from 'dayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
-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';
+import SEOConfig from '@/shared/components/SEO';
+import Navigation from '@/shared/components/Navigation_v2';
+import Footer from '@/shared/components/Footer_v2';
+import styled from '@emotion/styled';
-const HomePageWrapper = styled.div`
+export const HomePageWrapper = styled.div`
--section-height: calc(100vh - 80px);
--section-height-offset: 80px;
background: linear-gradient(0deg, #f3fcfc, #f3fcfc), #f7f8fa;
`;
-const ContentWrapper = styled.div`
+export const StyledContentWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
@@ -68,63 +40,79 @@ const ContentWrapper = styled.div`
width: 100%;
}
}
+
+ h2 {
+ font-weight: 700;
+ font-size: 22px;
+ line-height: 140%;
+ text-align: center;
+ color: #536166;
+ margin-top: 40px;
+ }
+`;
+
+export const StyledQuestionInput = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-start;
+ margin-top: 20px;
`;
function EditPage() {
const router = useRouter();
- const auth = getAuth();
- const [user, isLoading] = useAuthState(auth);
+ const dispatch = useDispatch();
+
+ const {
+ birthDay: userBirthDay,
+ gender: userGender,
+ roleList: userRoleList,
+ isSubscribeEmail: userIsSubscribeEmail,
+ email: userEmail,
+ createdDate,
+ updatedDate,
+ } = useSelector((state) => state?.user);
+ const { id } = router.query;
+
const [isSubscribeEmail, setIsSubscribeEmail] = useState(false);
- const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
const [birthDay, setBirthDay] = useState(dayjs());
const [gender, setGender] = useState('');
const [roleList, setRoleList] = useState([]);
+
+ const fetchUser = async () => {
+ dispatch(fetchUserById(id));
+ };
+
useEffect(() => {
- if (!isLoading) {
- const db = getFirestore();
- if (user?.uid) {
- // console.log('auth.currentUser', auth.currentUser);
- const docRef = doc(db, 'partnerlist', user?.uid);
- getDoc(docRef).then((docSnap) => {
- const data = docSnap.data();
- setBirthDay(dayjs(data?.birthDay) || dayjs());
- setGender(data?.gender || '');
- setRoleList(data?.roleList || []);
- });
- }
+ if (id) {
+ fetchUser();
}
- }, [user, isLoading]);
+ }, [id]);
+
+ useEffect(() => {
+ setBirthDay(userBirthDay ? dayjs(userBirthDay) : dayjs());
+ setGender(userGender || '');
+ setRoleList(userRoleList || []);
+ setIsSubscribeEmail(userIsSubscribeEmail || false);
- const onUpdateUser = (successCallback) => {
+ if (createdDate !== updatedDate) {
+ router.push('/profile');
+ }
+ }, [userEmail]);
+
+ const onUpdateUser = () => {
const payload = {
+ id,
+ email: userEmail,
birthDay: birthDay.toISOString(),
gender,
roleList,
- lastUpdateDate: dayjs().toISOString(),
isSubscribeEmail,
};
-
- const db = getFirestore();
-
- const docRef = doc(db, 'partnerlist', user?.uid);
- getDoc(docRef).then(() => {
- setIsLoadingSubmit(true);
- toast
- .promise(
- setDoc(docRef, payload).then(() => {
- setIsLoadingSubmit(false);
- }),
- {
- success: '更新成功!',
- error: '更新失敗',
- loading: '更新中...',
- },
- )
- .then(() => {
- successCallback();
- });
- });
+ dispatch(updateUser(payload));
+ router.push(`/signin/interest?id=${id}`);
};
+
const SEOData = useMemo(
() => ({
title: '編輯我的島島資料|島島阿學',
@@ -146,29 +134,10 @@ function EditPage() {
-
-
- 基本資料
-
+
+ 基本資料
-
+
生日 *
)}
/>
-
-
+
+
性別 *
))}
-
-
+
+
身份 *
))}
-
+
{
- onUpdateUser(() => router.push('/signin/interest'));
- }}
+ onClick={onUpdateUser}
>
下一步
-
+
diff --git a/pages/signin/interest/index.jsx b/pages/signin/interest/index.jsx
index 0d341cf1..3e2b1da0 100644
--- a/pages/signin/interest/index.jsx
+++ b/pages/signin/interest/index.jsx
@@ -1,41 +1,19 @@
import React, { useMemo, useState, useEffect } from 'react';
import styled from '@emotion/styled';
import { useRouter } from 'next/router';
-import Script from 'next/script';
-import {
- Box,
- Typography,
- Button,
- Skeleton,
- Modal,
- TextField,
- Divider,
- Switch,
- TextareaAutosize,
- MenuItem,
- Select,
-} from '@mui/material';
+import { useSelector, useDispatch } from 'react-redux';
+import { fetchUserById, updateUser } from '@/redux/actions/user';
+
+import { Box, Typography, Button, Skeleton } 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,
- updateDoc,
- setDoc,
- addDoc,
-} 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 SEOConfig from '@/shared/components/SEO';
+import Navigation from '@/shared/components/Navigation_v2';
+import Footer from '@/shared/components/Footer_v2';
import {
GENDER,
ROLE,
@@ -73,52 +51,35 @@ const ContentWrapper = styled.div`
function EditPage() {
const router = useRouter();
- const auth = getAuth();
- const [user, isLoading] = useAuthState(auth);
- const [interestAreaList, setInterestAreaList] = useState([]);
- const [isLoadingSubmit, setIsLoadingSubmit] = useState(false);
+ const { id } = router.query;
+ const dispatch = useDispatch();
+
+ const {
+ _id: userId,
+ interestList: userInterestList,
+ email: userEmail,
+ } = useSelector((state) => state?.user);
+
+ const [interestList, setInterestList] = useState([]);
const [open, setOpen] = useState(false);
useEffect(() => {
- if (!isLoading) {
- const db = getFirestore();
- if (user?.uid) {
- // console.log('auth.currentUser', auth.currentUser);
- const docRef = doc(db, 'partnerlist', user?.uid);
- getDoc(docRef).then((docSnap) => {
- const data = docSnap.data();
- setInterestAreaList(data?.interestAreaList || []);
- });
- }
+ if (userId) {
+ setInterestList(userInterestList);
+ }
+ if (id) {
+ dispatch(fetchUserById(id));
}
- }, [user, isLoading]);
+ }, [userId, id]);
const onUpdateUser = (successCallback) => {
const payload = {
- interestAreaList,
- lastUpdateDate: dayjs().toISOString(),
+ id: userId,
+ interestList,
+ email: userEmail,
};
-
- const db = getFirestore();
-
- const docRef = doc(db, 'partnerlist', user?.uid);
- getDoc(docRef).then(() => {
- setIsLoadingSubmit(true);
- toast
- .promise(
- updateDoc(docRef, payload).then(() => {
- setIsLoadingSubmit(false);
- }),
- {
- success: '更新成功!',
- error: '更新失敗',
- loading: '更新中...',
- },
- )
- .then(() => {
- successCallback();
- });
- });
+ dispatch(updateUser(payload));
+ successCallback();
};
const SEOData = useMemo(
@@ -139,7 +100,6 @@ function EditPage() {
{
setOpen(false);
router.push('/');
@@ -202,12 +162,12 @@ function EditPage() {
{
- if (interestAreaList.includes(value)) {
- setInterestAreaList((state) =>
+ if (interestList.includes(value)) {
+ setInterestList((state) =>
state.filter((data) => data !== value),
);
} else {
- setInterestAreaList((state) => [...state, value]);
+ setInterestList((state) => [...state, value]);
}
}}
sx={{
@@ -221,7 +181,7 @@ function EditPage() {
justifyItems: 'center',
alignItems: 'center',
cursor: 'pointer',
- ...(interestAreaList.includes(value)
+ ...(interestList.includes(value)
? {
backgroundColor: '#DEF5F5',
border: '1px solid #16B9B3',
@@ -267,7 +227,7 @@ function EditPage() {
{
router.back();
}}
diff --git a/redux/actions/partners.js b/redux/actions/partners.js
index 9e1704f1..cf8a855c 100644
--- a/redux/actions/partners.js
+++ b/redux/actions/partners.js
@@ -17,3 +17,20 @@ export function fetchPartnerById({ id } = {}) {
},
};
}
+
+export function sendEmailToPartner(payload) {
+ const { to, name, roleList, photoURL, text, information } = payload;
+ return {
+ type: 'SEND_EMAIL_TO_PARTNER',
+ payload: {
+ to, // 收件者信箱
+ subject: '【島島阿學】點開 Email,認識新夥伴',
+ title: '有新夥伴想認識你!',
+ name, // 寄件者
+ roleList: roleList.length ? roleList : [''], // 寄件者教育階段
+ photoUrl: photoURL,
+ text,
+ information, //寄件者聯絡資訊
+ },
+ };
+}
diff --git a/redux/actions/user.js b/redux/actions/user.js
index 12b05ec5..9dd45fac 100644
--- a/redux/actions/user.js
+++ b/redux/actions/user.js
@@ -4,6 +4,12 @@ export function userLogin() {
};
}
+export function userLogout() {
+ return {
+ type: 'USER_LOGOUT',
+ };
+}
+
export function checkUserAccount() {
return {
type: 'CHECK_USER_ACCOUNT',
@@ -16,6 +22,15 @@ export function fetchAllUsers() {
};
}
+export function fetchUserById(id) {
+ return {
+ type: 'FETCH_USER_BY_ID',
+ payload: {
+ id,
+ },
+ };
+}
+
export function addResourceToCollection(resourceId) {
return {
type: 'ADD_RESOURCE_TO_COLLECTION',
@@ -33,3 +48,12 @@ export function removeResourceFromCollection(resourceId) {
},
};
}
+
+export function updateUser(user) {
+ return {
+ type: 'UPDATE_USER_PROFILE',
+ payload: {
+ user,
+ },
+ };
+}
diff --git a/redux/reducers/partners.js b/redux/reducers/partners.js
index 93fe2f21..392c9ed3 100644
--- a/redux/reducers/partners.js
+++ b/redux/reducers/partners.js
@@ -41,6 +41,12 @@ const reducer = (state = initialState, action) => {
partner: null,
};
}
+ case 'SEND_EMAIL_TO_PARTNER_SUCCESS': {
+ return { ...state };
+ }
+ case 'SEND_EMAIL_TO_PARTNER_FAILURE': {
+ return { ...state };
+ }
default: {
return state;
}
diff --git a/redux/reducers/user.js b/redux/reducers/user.js
index 5539ab2a..126e9f46 100644
--- a/redux/reducers/user.js
+++ b/redux/reducers/user.js
@@ -1,10 +1,6 @@
// import toast from 'react-hot-toast';
-const initialState = {
- name: '',
- email: '',
- photoURL: '',
-};
+const initialState = {};
const reducer = (state = initialState, action) => {
switch (action.type) {
@@ -20,6 +16,11 @@ const reducer = (state = initialState, action) => {
...action.payload,
};
}
+ case 'USER_LOGOUT': {
+ return {
+ ...initialState,
+ };
+ }
case 'ADD_RESOURCE_TO_COLLECTION_SUCCESS': {
return {
...state,
@@ -32,6 +33,23 @@ const reducer = (state = initialState, action) => {
...action.payload,
};
}
+ case 'FETCH_USER_BY_ID_SUCCESS': {
+ return {
+ ...action.payload,
+ };
+ }
+
+ case 'FETCH_USER_BY_ID_FAILURE': {
+ return {
+ ...state,
+ };
+ }
+ case 'UPDATE_USER_PROFILE_SUCCESS': {
+ return {
+ ...state,
+ ...action.payload,
+ };
+ }
default: {
return state;
}
diff --git a/redux/sagas/partnersSaga.js b/redux/sagas/partnersSaga.js
index 1fb2382f..619c0368 100644
--- a/redux/sagas/partnersSaga.js
+++ b/redux/sagas/partnersSaga.js
@@ -49,9 +49,31 @@ function* fetchPartnerById(action) {
}
}
+function* sendEmailToPartner(action) {
+ try {
+ const baseUrl = process.env.NEXT_PUBLIC_API_URL || BASEURL;
+ const URL = `${baseUrl}/email`;
+ const result = yield fetch(URL, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ ...action.payload,
+ }),
+ }).then((res) => res.json());
+ yield put({
+ type: 'SEND_EMAIL_TO_PARTNER_SUCCESS',
+ });
+ } catch (error) {
+ yield put({ type: 'SEND_EMAIL_TO_PARTNER_FAILURE' });
+ }
+}
+
function* partnerSaga() {
yield takeEvery('FETCH_PARTNERS', fetchPartnersResource);
yield takeEvery('FETCH_PARTNER_BY_ID', fetchPartnerById);
+ yield takeEvery('SEND_EMAIL_TO_PARTNER', sendEmailToPartner);
}
export default partnerSaga;
diff --git a/redux/sagas/user/index.js b/redux/sagas/user/index.js
index dd5995c2..f4e8cc5f 100644
--- a/redux/sagas/user/index.js
+++ b/redux/sagas/user/index.js
@@ -47,10 +47,52 @@ function* fetchAllUsers() {
}
}
+function* updateUserProfile(action) {
+ const { user } = action.payload;
+ try {
+ const baseUri =
+ process.env.NEXT_PUBLIC_API_URL || 'https://daodao-server.onrender.com';
+ const URL = `${baseUri}/user/${user.id}`;
+
+ const result = yield fetch(URL, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ ...user,
+ }),
+ }).then((res) => res.json());
+
+ yield put({ type: 'UPDATE_USER_PROFILE_SUCCESS', payload: result.data });
+ } catch (error) {
+ yield put({ type: 'UPDATE_USER_PROFILE_FAILURE' });
+ }
+}
+
+function* fetchUserById(action) {
+ const { id } = action.payload;
+ try {
+ const baseUrl =
+ process.env.NEXT_PUBLIC_API_URL || 'https://daodao-server.onrender.com';
+ const URL = `${baseUrl}/user/${id}`;
+ const result = yield fetch(URL).then((res) => res.json());
+ yield put({
+ type: 'FETCH_USER_BY_ID_SUCCESS',
+ payload: result.data && result.data[0],
+ });
+ } catch (error) {
+ console.log(error);
+ yield put({ type: 'FETCH_USER_BY_ID_FAILURE' });
+ }
+}
+
function* userSaga() {
yield takeEvery('CHECK_USER_ACCOUNT', checkUserStatus);
yield takeEvery('USER_LOGIN', userLogin);
yield takeEvery('FETCH_ALL_USERS', fetchAllUsers);
+ yield takeEvery('UPDATE_USER_PROFILE', updateUserProfile);
+ yield takeEvery('FETCH_USER_BY_ID', fetchUserById);
}
export default userSaga;
diff --git a/redux/store/index.js b/redux/store/index.js
index 2ebd9c17..889e4926 100644
--- a/redux/store/index.js
+++ b/redux/store/index.js
@@ -1,11 +1,29 @@
import { createStore, applyMiddleware } from 'redux';
+import storage from 'redux-persist/lib/storage';
import logger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import { configureStore } from '@reduxjs/toolkit';
+import {
+ persistReducer,
+ FLUSH,
+ REHYDRATE,
+ PAUSE,
+ PERSIST,
+ PURGE,
+ REGISTER,
+} from 'redux-persist';
+
+const persistConfig = {
+ key: 'root',
+ storage,
+ whitelist: ['user'],
+};
import rootReducer from '../reducers';
import rootSaga from '../sagas';
+const persistedReducer = persistReducer(persistConfig, rootReducer);
+
// create a makeStore function
const storeFactory = (preloadedState) => {
const enableLog =
@@ -13,7 +31,7 @@ const storeFactory = (preloadedState) => {
const sagaMiddleware = createSagaMiddleware();
const middlewares = enableLog ? [logger, sagaMiddleware] : [sagaMiddleware];
const store = configureStore({
- reducer: rootReducer,
+ reducer: persistedReducer,
preloadedState,
middleware: [...middlewares],
});
diff --git a/shared/components/Navigation_v2/MainNav/SubList/UserAvatar/index.jsx b/shared/components/Navigation_v2/MainNav/SubList/UserAvatar/index.jsx
index 767b7975..d23a0106 100644
--- a/shared/components/Navigation_v2/MainNav/SubList/UserAvatar/index.jsx
+++ b/shared/components/Navigation_v2/MainNav/SubList/UserAvatar/index.jsx
@@ -1,16 +1,20 @@
import React, { useState } from 'react';
-import styled from '@emotion/styled';
-import Link from 'next/link';
+import { useSelector } from 'react-redux';
import { Avatar, Box, IconButton, Menu, MenuItem } from '@mui/material';
import { Group } from '@mui/icons-material';
import { useRouter } from 'next/router';
-import useFirebase from '../../../../../../hooks/useFirebase';
const UserAvatar = () => {
const { push } = useRouter();
- const { auth, user, signInWithFacebook, signOutWithGoogle } = useFirebase();
+ const user = useSelector((state) => state.user);
+
const [isOpenMenu, setIsOpenMenu] = useState(null);
- if (!user) {
+
+ const handleSignOut = () => {
+ console.log('handleSignOut');
+ };
+
+ if (!user._id) {
return (
{