diff --git a/components/Partner/About/Tags/index.jsx b/components/Partner/About/Tags/index.jsx deleted file mode 100644 index 3f21a73f..00000000 --- a/components/Partner/About/Tags/index.jsx +++ /dev/null @@ -1,108 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import styled from '@emotion/styled'; -import { useRouter } from 'next/router'; -import Chip from '@mui/material/Chip'; -import Link from 'next/link'; -import { COLOR_TABLE } from '../../../../constants/notion'; - -const TagsWrapper = styled.ul` - display: flex; - flex-wrap: wrap; -`; - -// const TagItemWrapper = styled.li` -// color: black; -// border-radius: 15px; -// padding: 2px 10px; -// margin: 0 5px; -// white-space: nowrap; -// cursor: pointer; -// ${({ color }) => css` -// background-color: ${COLOR_TABLE[color ?? "default"]}; -// `} -// `; - -const Tags = ({ tags = [], type }) => { - const { query, push } = useRouter(); - const linkTagsHandler = useCallback( - (newQuery) => { - // 複製一份,避免影響到使用體驗 - const clonedQuery = { ...query }; - delete clonedQuery.title; - if (clonedQuery[type]) { - push({ - pathname: '/search', - query: { - ...clonedQuery, - [type]: [clonedQuery[type].split(','), newQuery].join(','), - }, - }); - } else { - push({ - pathname: '/search', - query: { - ...clonedQuery, - [type]: newQuery, - }, - }); - } - }, - [push, query, type], - ); - - const linkList = useMemo(() => { - return tags.map((newQuery) => { - // 複製一份,避免影響到使用體驗 - const clonedQuery = { ...query }; - delete clonedQuery.title; - if (clonedQuery[type]) { - const queryObject = { - ...clonedQuery, - [type]: [clonedQuery[type].split(','), newQuery].join(','), - }; - const queryStirng = Object.keys(queryObject) - .map((key) => queryObject[key]) - .join('&'); - return `/search?${queryStirng}`; - } else { - const queryObject = { - ...clonedQuery, - [type]: newQuery, - }; - const queryStirng = Object.keys(queryObject) - .map((key) => queryObject[key]) - .join('&'); - return `/search?${queryStirng}`; - } - }); - }, [tags, query]); - - return ( - - {tags.map(({ name, color }, index) => ( -
  • - - linkTagsHandler(name)} - sx={{ - backgroundColor: COLOR_TABLE[color ?? 'default'], - cursor: 'pointer', - margin: '5px', - whiteSpace: 'nowrap', - fontWeight: 500, - fontSize: '14px', - '&:hover': { - opacity: '60%', - transition: 'transform 0.4s', - }, - }} - /> - -
  • - ))} -
    - ); -}; - -export default Tags; diff --git a/components/Partner/About/index.jsx b/components/Partner/About/index.jsx deleted file mode 100644 index dd4b6085..00000000 --- a/components/Partner/About/index.jsx +++ /dev/null @@ -1,154 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled'; -import Box from '@mui/material/Box'; -import { Typography, Button } from '@mui/material'; -import { FacebookRounded } from '@mui/icons-material'; -import Chip from '@mui/material/Chip'; -import { useRouter } from 'next/router'; -import { COLOR_TABLE } from '../../../constants/notion'; -import { CATEGORIES } from '../../../constants/category'; -import RelatedResources from '../../../shared/components/RelatedResources'; - -const GuideWrapper = styled.div` - width: 90%; - /* height: calc(var(--section-height) + var(--section-height-offset)); */ - margin: 0 auto; - padding-top: 80px; - padding-bottom: 80px; - .guide-title { - color: #536166; - font-weight: bold; - font-size: 40px; - line-height: 50px; - letter-spacing: 0.08em; - margin-left: '20px'; - } - - @media (max-width: 767px) { - padding-top: 40px; - padding-bottom: 20px; - } -`; - -const About = () => { - const router = useRouter(); - return ( - - - 來點島島阿學的資源吧! - - - coffeeandlearning - - - - 「學習資源爆炸多,卻常常找不到適合自己的?」 - - - - ✅ 由各領域資深學習者分享及彙整 - - - ✅ 免費資源百百種 - - - ✅ 資源跨領域跨年齡跨國 - - - ✅ 三鍵篩選出合適資源 - - - ✅ 人人都可以分享資源 - - - 自主學習的時代,用共好共享成為彼此學習路上的橋樑吧! - - - - 豐富的學習類別 - - - {CATEGORIES.map(({ key, value }) => ( - router.push(`/search?cats=${value}`)} - sx={{ - backgroundColor: COLOR_TABLE.green, - opacity: '60%', - cursor: 'pointer', - margin: '5px', - whiteSpace: 'nowrap', - fontWeight: 500, - fontSize: '16px', - '&:hover': { - opacity: '100%', - backgroundColor: COLOR_TABLE.green, - transition: 'transform 0.4s', - }, - }} - /> - ))} - - - - - - - ); -}; - -export default About; diff --git a/components/Partner/Banner/BannerImage/index.jsx b/components/Partner/Banner/BannerImage/index.jsx deleted file mode 100644 index 1b5cd14e..00000000 --- a/components/Partner/Banner/BannerImage/index.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled'; -import { Box } from '@mui/material'; - -const BannerImageWrapper = styled.div` - position: absolute; - width: 100%; - overflow: hidden; - z-index: -1; - top: 0px; - height: 60vh; - /* @media (max-width: 768px) { - height: 80vh; - } */ -`; - -function BannerImage() { - return ( - - - - ); -} - -export default BannerImage; diff --git a/components/Partner/Banner/index.jsx b/components/Partner/Banner/index.jsx index c4dff161..42cea4a0 100644 --- a/components/Partner/Banner/index.jsx +++ b/components/Partner/Banner/index.jsx @@ -1,71 +1,84 @@ -import React from 'react'; -import { Box, Button, Typography } from '@mui/material'; +import styled from '@emotion/styled'; import { useRouter } from 'next/router'; -import BannerImage from './BannerImage'; +import { Box } from '@mui/material'; +import Button from '@/shared/components/Button'; +import Image from '@/shared/components/Image'; +import partnerImg from '@/public/assets/partner-banner.png'; + +const StyledBanner = styled(Box)(({ theme }) => ({ + height: '398px', + zIndex: 0, + position: 'relative', + [theme.breakpoints.down('md')]: { + height: '256px', + }, + '> picture': { + position: 'absolute', + width: '100%', + top: 0, + height: '398px', + zIndex: -1, + [theme.breakpoints.down('md')]: { + height: '256px', + }, + img: { + height: 'inherit', + }, + }, +})); + +const StyledContent = styled(Box)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + height: '100%', + paddingBottom: '40px', + [theme.breakpoints.down('md')]: { + paddingBottom: '0', + }, + '> h1': { + fontWeight: 700, + fontSize: '36px', + lineHeight: '140%', + color: '#536166', + marginBottom: '8px', + [theme.breakpoints.down('md')]: { + fontSize: '22px', + }, + }, + '> p': { + fontWeight: 400, + fontSize: '14px', + lineHeight: '140%', + color: '#536166', + [theme.breakpoints.down('md')]: { + textAlign: 'center', + }, + }, +})); const Banner = () => { const router = useRouter(); return ( - - - - 尋找夥伴 - - - 想找到一起交流的學習夥伴嗎 - - - 註冊加入會員,並填寫個人資料,你的資訊就會刊登在頁面上囉! - - - - - + + +

    尋找夥伴

    +

    想找到一起交流的學習夥伴嗎

    +

    註冊加入會員,並填寫個人資料,你的資訊就會刊登在頁面上囉!

    + +
    + + 尋找夥伴 + +
    ); }; diff --git a/components/Partner/Group/index.jsx b/components/Partner/Group/index.jsx deleted file mode 100644 index daae7a54..00000000 --- a/components/Partner/Group/index.jsx +++ /dev/null @@ -1,137 +0,0 @@ -import React, { useState } from 'react'; -import styled from '@emotion/styled'; -import Box from '@mui/material/Box'; -import { Button, Typography } from '@mui/material'; -import { FacebookRounded } from '@mui/icons-material'; -import { useRouter } from 'next/router'; -import WramModal from '../../../shared/components/WarmModal'; - -const GroupWrapper = styled.div` - width: 90%; - /* height: calc(var(--section-height) + var(--section-height-offset)); */ - margin: 0 auto; - padding-top: 80px; - padding-bottom: 80px; - - @media (max-width: 767px) { - padding-top: 40px; - padding-bottom: 20px; - } -`; - -const Group = () => { - const router = useRouter(); - const [open, setOpen] = useState(false); - - return ( - - - 加入島島阿學學習社群 - - - - - - 我們是島島阿學學習社群,努力搭起互助學習的橋梁。 - - - - - 期盼以集體智慧,打造沒有天花板的學習環境,一個以自主學習為主的民主社群。 - - - - - 目前提供學習資源網以及社群的服務,包含各領域各種形式的資源、學習活動、學習經驗、教育新聞等等。 - - - - - 我們認為社群即資源、支援,讓學習者在民主教育的社群中,以共好的概念,解決彼此學習的問題,支持彼此成為自己想成為的人。 - - - - - 社群中有許多有愛的島友即時地分享各種學習資源唷!快加入吧! - - - - - - - - - - - group - - - ); -}; - -export default Group; diff --git a/components/Partner/Parnter.styled.jsx b/components/Partner/Parnter.styled.jsx new file mode 100644 index 00000000..7ea799b2 --- /dev/null +++ b/components/Partner/Parnter.styled.jsx @@ -0,0 +1,30 @@ +import styled from '@emotion/styled'; +import { Box } from '@mui/material'; + +export const StyledWrapper = styled.div` + position: relative; + margin: 70px auto 0; + width: 100%; + max-width: 1024px; + min-height: 100vh; + margin-top: -80px; + + @media (max-width: 900px) { + padding: 0 16px; + margin-top: -50px; + } +`; +export const StyledContent = styled(Box)` + margin-top: 24px; + padding: 32px 40px; + background-color: #fff; + border-radius: 20px; + @media (max-width: 900px) { + padding: 0; + background-color: transparent; + } +`; + +export const StyledSearchWrapper = styled(Box)` + margin-top: 24px; +`; diff --git a/components/Partner/PartnerList/PartnerCard/PartnerCard.styled.jsx b/components/Partner/PartnerList/PartnerCard/PartnerCard.styled.jsx new file mode 100644 index 00000000..de5d5a61 --- /dev/null +++ b/components/Partner/PartnerList/PartnerCard/PartnerCard.styled.jsx @@ -0,0 +1,112 @@ +import styled from '@emotion/styled'; +import { LazyLoadImage } from 'react-lazy-load-image-component'; +import { Grid, Box, Typography } from '@mui/material'; + +export const StyledCard = styled(Box)` + display: flex; + padding: 12px; + background-color: #fff; + justify-content: space-between; + align-items: flex-start; + width: 100%; + border-radius: 20px; + cursor: pointer; + &:hover { + box-shadow: 0px 4px 10px 0px rgba(196, 194, 193, 0.4); + h2 { + color: #16b9b3; + } + } +`; + +export const StyledCardContainer = styled(Box)` + width: 100%; +`; + +export const StyledImage = styled(LazyLoadImage)` + display: block; + width: 50px; + height: 50px; + border-radius: 50%; + background: rgba(240, 240, 240, 0.8); + object-fit: cover; + object-position: center; +`; + +export const StyledCardTitle = styled.h2` + color: #293a3d; + font-weight: 500; + font-size: 16px; + margin-right: 5px; +`; + +export const StyledCardLabel = styled(Typography)` + color: var(--black-white-gray-dark, #293a3d); + text-align: center; + font-family: Noto Sans TC; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 140%; + border-radius: 4px; + background: #f3f3f3; + padding: 3px 10px; +`; + +export const StyledCardSubtitle = styled(Typography)` + color: #92989a; + font-weight: 400; + font-size: 14px; +`; + +export const StyledTypoCaption = styled(Typography)` + color: #92989a; + font-family: 'Noto Sans TC'; + font-size: 12px; + font-style: normal; + line-height: 1.4; +`; + +export const StyledTagContainer = styled(Grid)` + display: flex; + align-items: center; +`; + +export const StyledTagText = styled(Grid)` + color: var(--black-white-gray, #536166); + text-align: center; + font-family: 'Noto Sans TC'; + font-size: 12px; + font-style: normal; + line-height: 1.4; + border-radius: 13px; + padding: 3px 10px; + display: flex; + justify-content: center; + background: #def5f5; +`; + +export const StyledTypoEllipsis = styled(Box)` + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +export const FlexSBAlignCenter = styled(Box)` + display: flex; + justify-content: space-between; + align-items: center; +`; + +export const FlexAlignCenter = styled(Box)` + display: flex; + align-items: center; +`; + +export const FlexColCenterSB = styled(Box)` + display: flex; + flex-direction: column; + justify-content: center; + align-items: space-between; +`; diff --git a/components/Partner/PartnerList/PartnerCard/PartnerCardAvator.jsx b/components/Partner/PartnerList/PartnerCard/PartnerCardAvator.jsx new file mode 100644 index 00000000..6b599709 --- /dev/null +++ b/components/Partner/PartnerList/PartnerCard/PartnerCardAvator.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Skeleton } from '@mui/material'; +import { StyledImage } from './PartnerCard.styled'; + +const PartnerCardAvator = ({ image }) => { + return image ? ( + + ) : ( + + ); +}; + +export default PartnerCardAvator; diff --git a/components/Partner/PartnerList/PartnerCard/PartnerCardDescription.jsx b/components/Partner/PartnerList/PartnerCard/PartnerCardDescription.jsx new file mode 100644 index 00000000..513689a2 --- /dev/null +++ b/components/Partner/PartnerList/PartnerCard/PartnerCardDescription.jsx @@ -0,0 +1,20 @@ +import { Typography } from '@mui/material'; +import { StyledTypoEllipsis } from './PartnerCard.styled'; + +const PartnerCardDescription = ({ title, content, ...rest }) => { + return ( + + + {title} + + + | + + + {content || '尚未填寫'} + + + ); +}; + +export default PartnerCardDescription; diff --git a/components/Partner/PartnerList/PartnerCard/PartnerCardTag.jsx b/components/Partner/PartnerList/PartnerCard/PartnerCardTag.jsx new file mode 100644 index 00000000..0724878e --- /dev/null +++ b/components/Partner/PartnerList/PartnerCard/PartnerCardTag.jsx @@ -0,0 +1,20 @@ +import { StyledTagContainer, StyledTagText } from './PartnerCard.styled'; + +const PartnerCardTag = ({ tagList = [] }) => { + const showItems = tagList.slice(0, 5); + const hideItems = tagList.slice(5); + return ( + + {showItems.map((tag) => ( + + {tag} + + ))} + {hideItems.length > 0 && ( + {hideItems.length} + )} + + ); +}; + +export default PartnerCardTag; diff --git a/components/Partner/PartnerList/PartnerCard/index.jsx b/components/Partner/PartnerList/PartnerCard/index.jsx index d94f5b56..e9c1e5ad 100644 --- a/components/Partner/PartnerList/PartnerCard/index.jsx +++ b/components/Partner/PartnerList/PartnerCard/index.jsx @@ -1,140 +1,77 @@ -import React, { useRef } from 'react'; -import styled from '@emotion/styled'; -import { Box, Typography, Divider, Skeleton } from '@mui/material'; -import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; -import { LazyLoadImage } from 'react-lazy-load-image-component'; +import { Box } from '@mui/material'; import { WANT_TO_DO_WITH_PARTNER, - CATEGORIES, -} from '../../../../constants/member'; -import { mapToTable } from '../../../../utils/helper'; + ROLE, + EDUCATION_STEP, +} from '@/constants/member'; +import { mapToTable } from '@/utils/helper'; +import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; +import PartnerCardAvator from './PartnerCardAvator'; +import PartnerCardDescription from './PartnerCardDescription'; +import PartnerCardTag from './PartnerCardTag'; + +import { + StyledCard, + StyledCardContainer, + StyledCardTitle, + StyledCardLabel, + StyledCardSubtitle, + StyledTypoCaption, + FlexSBAlignCenter, + FlexAlignCenter, + FlexColCenterSB, +} from './PartnerCard.styled'; const WANT_TO_DO_WITH_PARTNER_TABLE = mapToTable(WANT_TO_DO_WITH_PARTNER); -const CATEGORIES_TABLE = mapToTable(CATEGORIES); +const ROLELIST = mapToTable(ROLE); +const EDUCATION_STEP_TABLE = mapToTable(EDUCATION_STEP); + function PartnerCard({ - id, image, name, - subTitle, - canShare = [], - canTogether = [], + share, + tagList = [], + wantToDoList = [], + roleList = [], + educationStage, }) { - return ( - - - - - - } - /> + const wantTodo = wantToDoList + .map((item) => WANT_TO_DO_WITH_PARTNER_TABLE[item]) + .join('、'); - - - {name} - - - {subTitle} - - {/* - {' '} - 台北市松山區 - */} - - - {/* - {tagList.map((tag) => ( - - ))} - */} - - - - - 可分享 - - - {canShare - .map((item) => WANT_TO_DO_WITH_PARTNER_TABLE[item] || '') - .join(', ')} - - - - - 想一起 - - - {canTogether - .map((item) => CATEGORIES_TABLE[item] || '') - .join(', ')} - - + const role = roleList.length > 0 && ROLELIST[roleList[0]]; + const edu = educationStage && EDUCATION_STEP_TABLE[educationStage]; + + return ( + + {/* TODO: href redirect */} + + + + + + {name} + {edu && {edu}} + + {role && {role}} + + + + + - - + + + + + + 台北市松山區 + + + 兩天前更新 + + + ); } diff --git a/components/Partner/PartnerList/index.jsx b/components/Partner/PartnerList/index.jsx index 807fcefe..2fc0cf10 100644 --- a/components/Partner/PartnerList/index.jsx +++ b/components/Partner/PartnerList/index.jsx @@ -1,86 +1,60 @@ -import React, { useRef } from 'react'; -import styled from '@emotion/styled'; -import { Box, Typography, Divider, Skeleton } from '@mui/material'; -import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; -import { LazyLoadImage } from 'react-lazy-load-image-component'; +import { useEffect, Fragment } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchAllPartners } from '@/redux/actions/partners'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { Grid, Box } from '@mui/material'; import PartnerCard from './PartnerCard'; -const LIST = [ - { - name: '許浪手', - image: - 'https://images.unsplash.com/photo-1502680390469-be75c86b636f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8c3VyZnxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=800&q=60', - subTitle: '實驗教育老師', - canShare: '心智圖法', - canTogether: '學習交流、教學相長', - tags: ['實驗教育'], - location: '台北市松山區', - }, - { - name: '許浪手2', - image: - 'https://images.unsplash.com/photo-1502680390469-be75c86b636f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8c3VyZnxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=800&q=60', - subTitle: '實驗教育老師', - canShare: '心智圖法', - canTogether: '學習交流、教學相長', - tags: ['實驗教育'], - location: '台北市松山區', - }, - { - name: '許浪手3', - image: - 'https://images.unsplash.com/photo-1502680390469-be75c86b636f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8c3VyZnxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=800&q=60', - subTitle: '實驗教育老師', - canShare: '心智圖法', - canTogether: '學習交流、教學相長', - tags: ['實驗教育'], - location: '台北市松山區', - }, - { - name: '許浪手4', - image: - 'https://images.unsplash.com/photo-1502680390469-be75c86b636f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxzZWFyY2h8Mnx8c3VyZnxlbnwwfHwwfHw%3D&auto=format&fit=crop&w=800&q=60', - subTitle: '實驗教育老師', - canShare: '心智圖法', - canTogether: '學習交流、教學相長', - tags: ['實驗教育'], - location: '台北市松山區', - }, -]; +function PartnerList() { + const partners = useSelector((state) => state.partners); + const dispatch = useDispatch(); + + // TODO: ADD PAGE + const handleFetchData = () => { + dispatch(fetchAllPartners()); + }; + + useEffect(() => { + handleFetchData(); + }, []); + + const lists = partners.items || []; + const mobileScreen = useMediaQuery('(max-width: 900px)'); -function PartnerList({ list = [] }) { return ( - - - {list.map( - ({ - id, - userName, - photoURL, - subTitle, - wantToLearnList, - interestAreaList, - }) => ( + + {lists.map((item, idx) => ( + + - ), - )} - - + + {!mobileScreen && (idx + 1) % 2 === 0 && idx + 1 !== lists.length && ( + + + + )} + + ))} + ); } diff --git a/components/Partner/SearchField/AgeCheckbox/index.jsx b/components/Partner/SearchField/AgeCheckbox/index.jsx deleted file mode 100644 index 20fc06de..00000000 --- a/components/Partner/SearchField/AgeCheckbox/index.jsx +++ /dev/null @@ -1,91 +0,0 @@ -/* eslint-disable react/jsx-wrap-multilines */ -import React, { useState } from 'react'; -import styled from '@emotion/styled'; -import { Box, Select, MenuItem } from '@mui/material'; -import { useRouter } from 'next/router'; -// import { SEARCH_TAGS } from "../../../constants/category"; -import OutlinedInput from '@mui/material/OutlinedInput'; -import InputLabel from '@mui/material/InputLabel'; -import FormControl from '@mui/material/FormControl'; -import Chip from '@mui/material/Chip'; -import FormLabel from '@mui/material/FormLabel'; -import FormGroup from '@mui/material/FormGroup'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import FormHelperText from '@mui/material/FormHelperText'; -import Checkbox from '@mui/material/Checkbox'; - -const ITEM_HEIGHT = 48; -const ITEM_PADDING_TOP = 8; -const MenuProps = { - PaperProps: { - style: { - maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, - width: 250, - }, - }, -}; - -const names = ['學齡前', '國小', '國高中', '大學以上']; - -const AgeDropdown = () => { - const { query, push } = useRouter(); - const ages = query?.ages ? (query?.ages).split(',') : []; - const handleChange = (event) => { - const newAges = query?.ages ? (query?.ages).split(',') : []; - const { name } = event.target; - const { checked } = event.target; - if (checked) { - newAges.push(name); - } else { - const index = newAges.indexOf(name); - newAges.splice(index, 1); - } - if (newAges.length === 0) { - delete query.ages; - push({ - pathname: '/search', - query, - }); - } else { - push({ - pathname: '/search', - query: { - ...query, - ages: newAges.join(','), - }, - }); - } - }; - return ( - - 年齡層 - - {names.map((name) => ( - - } - /> - ))} - - - ); -}; - -export default AgeDropdown; diff --git a/components/Partner/SearchField/AgeDropdown/index.jsx b/components/Partner/SearchField/AgeDropdown/index.jsx deleted file mode 100644 index 93531bbe..00000000 --- a/components/Partner/SearchField/AgeDropdown/index.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { useState } from 'react'; -import styled from '@emotion/styled'; -import { Box, Select, MenuItem } from '@mui/material'; -import { useRouter } from 'next/router'; -// import { SEARCH_TAGS } from "../../../constants/category"; -import OutlinedInput from '@mui/material/OutlinedInput'; -import InputLabel from '@mui/material/InputLabel'; -import FormControl from '@mui/material/FormControl'; -import Chip from '@mui/material/Chip'; - -const ITEM_HEIGHT = 48; -const ITEM_PADDING_TOP = 8; -const MenuProps = { - PaperProps: { - style: { - maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, - width: 250, - }, - }, -}; - -const names = ['學齡前', '國小', '國高中', '大學以上']; - -const AgeDropdown = () => { - const { query, push } = useRouter(); - const ages = query?.ages ? (query?.ages).split(',') : []; - const handleChange = (event) => { - const { - target: { value }, - } = event; - - if (value.length > 0) { - push({ - pathname: '/search', - query: { - ...query, - ages: value.join(','), - }, - }); - } else { - delete query.ages; - push({ - pathname: '/search', - query, - }); - } - }; - return ( - - 年齡層 - - - ); -}; - -export default AgeDropdown; diff --git a/components/Partner/SearchField/FeeDropdown/index.jsx b/components/Partner/SearchField/FeeDropdown/index.jsx deleted file mode 100644 index 19452fae..00000000 --- a/components/Partner/SearchField/FeeDropdown/index.jsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { useState } from 'react'; -import styled from '@emotion/styled'; -import { Box, Select, MenuItem } from '@mui/material'; -import { useRouter } from 'next/router'; -// import { SEARCH_TAGS } from "../../../constants/category"; -import OutlinedInput from '@mui/material/OutlinedInput'; -import InputLabel from '@mui/material/InputLabel'; -import FormControl from '@mui/material/FormControl'; -import Chip from '@mui/material/Chip'; -import Radio from '@mui/material/Radio'; -import RadioGroup from '@mui/material/RadioGroup'; -import FormControlLabel from '@mui/material/FormControlLabel'; -import FormLabel from '@mui/material/FormLabel'; - -const ITEM_HEIGHT = 48; -const ITEM_PADDING_TOP = 8; -const MenuProps = { - PaperProps: { - style: { - maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, - width: 250, - }, - }, -}; - -const names = ['不拘', '免費', '部分免費', '需付費']; - -const FeeDropdown = () => { - const { query, push } = useRouter(); - const fee = query?.fee ? (query?.fee).split(',') : []; - const handleChange = (event) => { - const { - target: { value }, - } = event; - - if (value === names[0]) { - delete query.fee; - push({ - pathname: '/search', - query, - }); - } else { - push({ - pathname: '/search', - query: { - ...query, - fee: value, - }, - }); - } - }; - return ( - - 費用 - - {names.map((name) => ( - } - /> - ))} - - {/* */} - - ); -}; - -export default FeeDropdown; diff --git a/components/Partner/SearchField/HotTags/index.jsx b/components/Partner/SearchField/HotTags/index.jsx deleted file mode 100644 index 6de3607e..00000000 --- a/components/Partner/SearchField/HotTags/index.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled'; -import { Whatshot } from '@mui/icons-material'; -import { Box } from '@mui/material'; -import { SEARCH_TAGS } from '../../../../constants/category'; -import Item from './item'; -// import { TikTokFont } from "../../../../shared/styles/css"; - -const TagsWrapper = styled.ul` - display: flex; - justify-content: flex-start; - align-items: center; - margin: auto 5px; - white-space: nowrap; - max-width: calc(100vw - 49px); - overflow-x: scroll; - -ms-overflow-style: none; /* IE */ - scrollbar-width: none; /* Firefox */ - &::-webkit-scrollbar { - display: none; /* Chrome, Safari, Edge and Opera */ - } -`; - -const Tags = ({ queryList }) => { - const lastSelectedCat = queryList.length > 0 && queryList[0]; - const hotTags = - Array.isArray(queryList) && queryList.length > 0 && lastSelectedCat - ? SEARCH_TAGS[lastSelectedCat] - : SEARCH_TAGS['全部']; - return ( - - - - {hotTags.map((value) => ( - - ))} - - - ); -}; - -export default Tags; diff --git a/components/Partner/SearchField/HotTags/item.jsx b/components/Partner/SearchField/HotTags/item.jsx deleted file mode 100644 index 6804b277..00000000 --- a/components/Partner/SearchField/HotTags/item.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { useRouter } from 'next/router'; -import { Chip } from '@mui/material'; -import { COLOR_TABLE } from '../../../../constants/notion'; -import stringSanitizer from '../../../../utils/sanitizer'; - -// const TagWrapper = styled(Chip)` -// margin: auto 5px; -// font-weight: 700; -// white-space: nowrap; -// a { -// color: #37b9eb; -// font-weight: bold; -// font-size: 16px; -// } - -// a:hover { -// text-decoration: underline; -// } - -// @media (max-width: 767px) { -// left: 70px; -// width: 85vw; -// overflow-x: visible; -// a { -// color: #007bbb; -// font-size: 14px; -// } -// } -// `; -const Tag = ({ title }) => { - const { push, query } = useRouter(); - const queryTags = useMemo( - () => - typeof query.tags === 'string' - ? stringSanitizer(query.tags).split(',') - : [], - [query.tags], - ); - const linkHandler = useCallback( - (targetQuery) => { - push({ - pathname: '/search', - query: { - ...query, - tags: [...new Set([...queryTags, targetQuery])].join(','), - }, - }); - }, - [push, query, queryTags], - ); - return ( - linkHandler(title)} - sx={{ - backgroundColor: COLOR_TABLE.pink, - cursor: 'pointer', - margin: '5px', - whiteSpace: 'nowrap', - fontWeight: 500, - fontSize: '14px', - '&:hover': { - opacity: '60%', - transition: 'transform 0.4s', - }, - }} - /> - ); -}; - -export default Tag; diff --git a/components/Partner/SearchField/SearchInput.jsx b/components/Partner/SearchField/SearchInput.jsx new file mode 100644 index 00000000..ba485a31 --- /dev/null +++ b/components/Partner/SearchField/SearchInput.jsx @@ -0,0 +1,98 @@ +import { useState, useEffect } from 'react'; +import dynamic from 'next/dynamic'; +import styled from '@emotion/styled'; +import InputBase from '@mui/material/InputBase'; +import Paper from '@mui/material/Paper'; +import IconButton from '@mui/material/IconButton'; +import MicIcon from '@mui/icons-material/Mic'; +import SearchIcon from '@mui/icons-material/Search'; +import useSearchParamsManager from '@/hooks/useSearchParamsManager'; + +const Speech = dynamic(import('@/shared/components/Speech'), { + ssr: false, +}); + +const SearchInputWrapper = styled(Paper)` + width: 100%; + position: relative; + display: flex; + align-items: center; + border: 1px solid #dbdbdb; + border-radius: 30px; + padding-right: 4px; + box-shadow: none; + overflow: hidden; + + @media (max-width: 767px) { + border-radius: 20px; + width: 100%; + } +`; + +const IconButtonWrapper = styled(IconButton)` + color: #536166; + border-radius: 40px; + height: 40px; + width: 40px; +`; + +const InputBaseWrapper = styled(InputBase)(() => ({ + flex: 1, + '& .MuiInputBase-input': { + paddingTop: '14px', + paddingLeft: '20px', + paddingBottom: '14px', + background: 'white', + zIndex: 10, + borderRadius: '20px', + width: '100%', + fontSize: 14, + }, +})); + +const SearchInput = () => { + const [getSearchParams, pushState] = useSearchParamsManager(); + const [keyword, setKeyword] = useState(''); + const [isSpeechMode, setIsSpeechMode] = useState(false); + const currentKeyword = getSearchParams('q').toString(); + + useEffect(() => { + setKeyword(currentKeyword); + }, [currentKeyword]); + + const handleChange = ({ target }) => { + setKeyword(target.value); + }; + + const handleSubmit = (event) => { + event.preventDefault(); + pushState('q', keyword); + }; + + return ( + + + {isSpeechMode && ( + + )} + setIsSpeechMode(true)} + > + + + + + + + ); +}; + +export default SearchInput; diff --git a/components/Partner/SearchField/SearchInput/Button/index.jsx b/components/Partner/SearchField/SearchInput/Button/index.jsx deleted file mode 100644 index a9e3cb0b..00000000 --- a/components/Partner/SearchField/SearchInput/Button/index.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; -import { IconButton } from '@mui/material'; -import SearchIcon from '@mui/icons-material/Search'; -import styled from '@emotion/styled'; - -const SearchButtonWrapper = styled(IconButton)` - overflow: hidden; - color: #16b9b3; - width: 40px; - height: 100%; - right: 0; - border-radius: 0; - padding: 10px; - - &:hover { - background-color: white; - /* opacity: 0.8; - transition: opacity 0.5s; */ - } - @media (max-width: 767px) { - width: 40px; - padding: 0px; - /* border-radius: 20px; */ - } -`; - -const SearchButton = ({ routingPush }) => ( - { - routingPush(); - // addSearchHistory(); - }} - aria-label="search" - > - - -); - -export default SearchButton; diff --git a/components/Partner/SearchField/SearchInput/SuggestList/index.jsx b/components/Partner/SearchField/SearchInput/SuggestList/index.jsx deleted file mode 100644 index 738f997a..00000000 --- a/components/Partner/SearchField/SearchInput/SuggestList/index.jsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled'; -import { css } from '@emotion/react'; -import Link from 'next/link'; - -const SuggestWrapper = styled.div` - width: 100%; - top: 20px; - left: 0px; - background-color: white; - position: absolute; - display: flex; - flex-direction: column; - justify-content: flex-start; - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; - border: 2px #37b9eb solid; - overflow: hidden; - border-top: 0; - /* box-shadow: 0 4px 6px rgb(32 33 36 / 28%); */ - ${({ isFocus, isEmpty }) => - isFocus && - !isEmpty && - css` - border: 0px; - `} - - a { - display: block; - padding: 6px 12px; - color: black; - - &:hover { - background-color: #eeeeee; - } - - &:first-of-type { - margin-top: 15px; - } - - &:last-of-type { - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; - } - } -`; - -const SuggestList = ({ - isFocus, - keyword, - suggestKeywords, - addSearchHistory, - referenceSelected, -}) => { - const isServerSide = !process.browser; - if (isServerSide) return <>; - const historyKeywords = - JSON.parse(window?.localStorage.getItem('historyKeywords') || null) || []; - - if (!isFocus) return <>; - - if (keyword.length === 0 && historyKeywords.length > 0) { - return ( - - {historyKeywords.map(({ keyword: suggest, id }, idx) => ( - - {suggest} - - ))} - - ); - } - - return ( - - {keyword.length > 0 && - Array.isArray(suggestKeywords) && - suggestKeywords.map((suggest, idx) => ( - addSearchHistory(suggest)} - style={{ - background: referenceSelected === idx ? '#eee' : null, - wordBreak: 'break-all', - }} - > - {suggest} - - ))} - - ); -}; - -export default SuggestList; diff --git a/components/Partner/SearchField/SearchInput/index.jsx b/components/Partner/SearchField/SearchInput/index.jsx deleted file mode 100644 index d23aaaf4..00000000 --- a/components/Partner/SearchField/SearchInput/index.jsx +++ /dev/null @@ -1,178 +0,0 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; -// import ClickAwayListener from "@mui/base/ClickAwayListener"; -import InputBase from '@mui/material/InputBase'; -import Paper from '@mui/material/Paper'; -import styled from '@emotion/styled'; -// import { Search } from "@mui/icons-material"; -import { useRouter } from 'next/router'; -// import i18n from "../../../../../constants/i18n"; -// import SuggestList from "./SuggestList"; -import { IconButton, Box } from '@mui/material'; -import MicIcon from '@mui/icons-material/Mic'; -import dynamic from 'next/dynamic'; -import SearchButton from './Button'; - -const Speech = dynamic(import('../../../../shared/components/Speech'), { - ssr: false, -}); - -const SearchToolsWrapper = styled(Box)` - position: relative; - height: 40px; - margin-left: auto; - margin-right: 5px; - display: flex; -`; - -const SearchButtonWrapper = styled(IconButton)` - /* position: absolute; */ - overflow: hidden; - color: white; - border-radius: 10px; - float: right; - height: 100%; - width: 40px; - right: 0; - &:hover { - /* background-color: #007bbb; */ - } -`; -const FormWrapper = styled.form` - width: 100%; -`; - -const SearchInputWrapper = styled(Paper)` - height: 40px; - width: 100%; - position: relative; - border-radius: 10px; - // 可以試著淡化border - border: 2px solid #16b9b3; - box-shadow: none; - overflow: hidden; - - @media (max-width: 767px) { - border-radius: 20px; - width: 100%; - } -`; - -const PLACEHOLDER_TEXT = [ - '英語, 心理學, 自主學習 ...', - '好想出國喔~該來學英語了', - '我的腦袋不太好,但是知道邏輯要訓練', - '不會寫程式,也要了解科技趨勢', - '斜槓與文青的時間到了', - '誰說健身不是學習的一種?', - '生活在學習', -]; - -const InputBaseWrapper = styled(InputBase)` - background: white; - z-index: 10; - border-bottom-right-radius: 20px; - border-top-right-radius: 20px; - margin-left: 10px; - width: 100%; - - @media (max-width: 767px) { - border-radius: 20px; - } -`; - -const SearchInput = () => { - const { query, push } = useRouter(); - // const isServerSide = useMemo(() => !process.browser, []); - const [keyword, setKeyword] = useState(query?.q); - const [isSpeechMode, setIsSpeechMode] = useState(false); - // const [referenceSelected, setReferenceSelected] = useState(null); - - useEffect(() => { - setKeyword(query?.q ?? ''); - }, [query?.q]); - - const routingPush = useCallback( - (words) => { - if (words !== '') { - push({ - query: { - ...query, - q: words, - }, - }); - } else { - delete query.q; - push({ - query, - }); - } - }, - [push, query], - ); - - const placeholder = useMemo( - () => PLACEHOLDER_TEXT[Math.floor(Math.random() * 7)], - [], - ); - - return ( - - { - e.preventDefault(); - if (keyword !== '') { - push({ - query: { - ...query, - q: keyword, - }, - }); - } else if (keyword.length === 0) { - delete query.q; - push({ query }); - } - }} - > - { - // setReferenceSelected(null); - setKeyword(event.target.value); - }} - // components={<>} - /> - - {isSpeechMode && ( - - )} - - setIsSpeechMode(true)} - > - - - {}} /> - - - ); -}; - -export default SearchInput; diff --git a/components/Partner/SearchField/SearchTags.jsx b/components/Partner/SearchField/SearchTags.jsx new file mode 100644 index 00000000..976a54da --- /dev/null +++ b/components/Partner/SearchField/SearchTags.jsx @@ -0,0 +1,84 @@ +import React, { useState, useEffect } from 'react'; +import styled from '@emotion/styled'; +import { SEARCH_TAGS } from '@/constants/category'; +import useSearchParamsManager from '@/hooks/useSearchParamsManager'; + +const StyledContainer = styled.div` + margin-top: 12px; + display: flex; + align-items: center; + width: 100%; + @media (max-width: 767px) { + margin-left: 10px 0; + flex-direction: column; + align-items: flex-start; + } + > p { + color: #536166; + font-size: 14px; + font-style: normal; + font-weight: 700; + line-height: 140%; + white-space: nowrap; + @media (max-width: 767px) { + margin-bottom: 8px; + } + } + ul { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + + -ms-overflow-style: none; /* IE */ + scrollbar-width: none; /* Firefox */ + scroll-behavior: smooth; + + margin-left: 24px; + + &::-webkit-scrollbar { + display: none; /* Chrome, Safari, Edge and Opera */ + } + @media (max-width: 767px) { + margin-left: 0; + } + } + ul > li { + color: #16b9b3; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 140%; + margin-right: 16px; + flex: 0 0 auto; + cursor: pointer; + } +`; + +const SearchTags = () => { + const [getSearchParams, pushState] = useSearchParamsManager(); + const [_, setTag] = useState(); + const currentTags = getSearchParams('tag').toString(); + + const handleChange = (val) => { + pushState('tag', val.toString()); + }; + + useEffect(() => { + setTag(currentTags); + }, [currentTags]); + + return ( + +

    熱門標籤

    +
      + {SEARCH_TAGS['全部'].map((t) => ( +
    • handleChange(t)}> + {t} +
    • + ))} +
    +
    + ); +}; + +export default SearchTags; diff --git a/components/Partner/SearchField/SelectedAreas.jsx b/components/Partner/SearchField/SelectedAreas.jsx new file mode 100644 index 00000000..d39a5086 --- /dev/null +++ b/components/Partner/SearchField/SelectedAreas.jsx @@ -0,0 +1,29 @@ +import Select from '@/shared/components/Select'; +import { AREAS } from '@/constants/areas'; +import useSearchParamsManager from '@/hooks/useSearchParamsManager'; + +export default function SelectedAreas() { + const QUERY_KEY = 'area'; + const [getSearchParams, pushState] = useSearchParamsManager(); + + const handleChange = ({ target: { value } }) => { + pushState(QUERY_KEY, value.toString()); + }; + + return ( + + selected.length === 0 ? '教育階段' : selected.join('、') + } + sx={{ + '@media (max-width: 767px)': { + width: '100%', + }, + }} + /> + ); +} diff --git a/components/Partner/SearchField/SelectedFriendType.jsx b/components/Partner/SearchField/SelectedFriendType.jsx new file mode 100644 index 00000000..1d012942 --- /dev/null +++ b/components/Partner/SearchField/SelectedFriendType.jsx @@ -0,0 +1,35 @@ +import Select from '@/shared/components/Select'; +import { ROLE } from '@/constants/member'; +import useSearchParamsManager from '@/hooks/useSearchParamsManager'; + +const ROLE_TYPE = ROLE.map(({ label, key }) => ({ label, key })); + +const SelectedFriendType = () => { + const QUERY_KEY = 'role'; + const [getSearchParams, pushState] = useSearchParamsManager(); + + const handleChange = ({ target: { value } }) => { + pushState(QUERY_KEY, value.toString()); + }; + + return ( +