diff --git a/components/Group/Form/useGroupForm.jsx b/components/Group/Form/useGroupForm.jsx index 502ae9f1..cf5134fc 100644 --- a/components/Group/Form/useGroupForm.jsx +++ b/components/Group/Form/useGroupForm.jsx @@ -143,7 +143,13 @@ export default function useGroupForm() { }; useEffect(() => { - if (notLogin) openLoginWindow('/login'); + let timer; + if (notLogin) { + timer = setTimeout(() => { + openLoginWindow(); + }, 100); + } + return () => clearTimeout(timer); }, [notLogin]); return { diff --git a/components/Group/GroupList/GroupCard.jsx b/components/Group/GroupList/GroupCard.jsx index 4c0c6876..57501760 100644 --- a/components/Group/GroupList/GroupCard.jsx +++ b/components/Group/GroupList/GroupCard.jsx @@ -1,7 +1,7 @@ import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; import Image from '@/shared/components/Image'; -import emptyCoverImg from '@/public/assets/empty-cover.png'; import { timeDuration } from '@/utils/date'; +import emptyCoverWithBackgroundImg from '@/public/assets/empty-cover-with-background.png'; import { StyledAreas, StyledContainer, @@ -31,7 +31,10 @@ function GroupCard({ return ( - {photoAlt + {photoAlt {title} diff --git a/components/Group/GroupList/index.jsx b/components/Group/GroupList/index.jsx index 3aa27d00..59352d3b 100644 --- a/components/Group/GroupList/index.jsx +++ b/components/Group/GroupList/index.jsx @@ -1,10 +1,13 @@ import { useEffect, Fragment } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import styled from '@emotion/styled'; +import Button from '@mui/material/Button'; import useMediaQuery from '@mui/material/useMediaQuery'; -import { Box } from '@mui/material'; import useSearchParamsManager from '@/hooks/useSearchParamsManager'; import { setQuery } from '@/redux/actions/group'; +import Image from '@/shared/components/Image'; +import emptyCoverImg from '@/public/assets/empty-cover.png'; +import errorCoverImg from '@/public/assets/contacterror.png'; import GroupCard from './GroupCard'; import SkeletonGroupCard from './SkeletonGroupCard'; @@ -41,10 +44,93 @@ const StyledGroupList = styled.ul` flex-wrap: wrap; `; +const StyledFullItem = styled.li` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 412px; + + .color-gray { + color: #536166; + } + + .color-gray-light { + color: #92989a; + } + + p { + margin-top: 20px; + } + + span { + margin-top: 6px; + font-size: 14px; + } +`; + +function shouldRenderDivider( + index, + isLast, + isMobileScreen, + isPadScreen, + isDeskTopScreen, +) { + return ( + (isMobileScreen && !isLast) || + (isPadScreen && !isLast && index % 2 === 1) || + (isDeskTopScreen && !isLast && index % 3 === 2) + ); +} + +function GroupItems({ items, isMobileScreen, isPadScreen, isDeskTopScreen }) { + return ( + Array.isArray(items) && + items.map((data, index) => { + const isLast = index === items.length - 1; + return ( + + + + + {shouldRenderDivider( + index, + isLast, + isMobileScreen, + isPadScreen, + isDeskTopScreen, + ) && } + + ); + }) + ); +} + +function SkeletonItems({ isMobileScreen, isPadScreen, isDeskTopScreen }) { + return Array.from({ length: isMobileScreen ? 3 : 6 }, (_, index) => { + const isLast = index === (isMobileScreen ? 2 : 5); + return ( + + + + + {shouldRenderDivider( + index, + isLast, + isMobileScreen, + isPadScreen, + isDeskTopScreen, + ) && } + + ); + }); +} + function GroupList() { const dispatch = useDispatch(); const [getSearchParams] = useSearchParamsManager(); - const { items, isLoading } = useSelector((state) => state.group); + const { items, isLoading, isError } = useSelector((state) => state.group); const isMobileScreen = useMediaQuery('(max-width: 560px)'); const isPadScreen = useMediaQuery('(max-width: 767px)') && !isMobileScreen; @@ -55,54 +141,62 @@ function GroupList() { }, [getSearchParams]); return ( - <> - - {isLoading ? ( - // always show 3 || 6 skeleton cards in mobile || desktop screen - Array.from({ length: isMobileScreen ? 3 : 6 }, (_, index) => { - const isLast = index === (isMobileScreen ? 2 : 5); - const shouldRenderDivider = - (isMobileScreen && !isLast) || - (isPadScreen && !isLast && index % 2 === 1) || - (isDeskTopScreen && !isLast && index % 3 === 2); - - return ( - - - - - {shouldRenderDivider && } - - ); - }) - ) : items?.length ? ( - items.map((data, index) => { - const isLast = index === items.length - 1; - const shouldRenderDivider = - (isMobileScreen && !isLast) || - (isPadScreen && !isLast && index % 2 === 1) || - (isDeskTopScreen && !isLast && index % 3 === 2); - - return ( - - - - - {shouldRenderDivider && } - - ); - }) - ) : ( -
  • - 哎呀!這裡好像沒有符合你條件的揪團,別失望!讓我們試試其他選項。 -
  • - )} -
    - + + {isLoading && ( - 搜尋揪團中~ + + )} + {isError ? ( + + 異常錯誤 +

    哎呀!有不明錯誤

    + +
    + ) : ( + !isLoading && + items.length === 0 && ( + + 查無資料 +

    哎呀!這裡沒有符合條件的揪團

    + + 試著更改條件或搜尋其他關鍵字吧 + +
    + ) )} - +
    ); } diff --git a/components/Group/More.jsx b/components/Group/More.jsx index 342f68e9..63362427 100644 --- a/components/Group/More.jsx +++ b/components/Group/More.jsx @@ -11,7 +11,7 @@ export default function More() { - {isMore ? ( + {isMore && ( - ) : ( - '已經到底囉~' )} ); diff --git a/components/Group/SearchField/SearchInput.jsx b/components/Group/SearchField/SearchInput.jsx index 1a70d086..ff374b58 100644 --- a/components/Group/SearchField/SearchInput.jsx +++ b/components/Group/SearchField/SearchInput.jsx @@ -54,7 +54,7 @@ const SearchInput = () => { const [getSearchParams, pushState] = useSearchParamsManager(); const [keyword, setKeyword] = useState(''); const [isSpeechMode, setIsSpeechMode] = useState(false); - const currentKeyword = getSearchParams('q').toString(); + const currentKeyword = getSearchParams('search').toString(); useEffect(() => { setKeyword(currentKeyword); @@ -67,7 +67,7 @@ const SearchInput = () => { /** @type {(event: SubmitEvent) => void} */ const handleSubmit = (event) => { event.preventDefault(); - pushState('q', keyword); + pushState('search', keyword); }; return ( @@ -75,7 +75,7 @@ const SearchInput = () => { user?.roleList?.includes(key))?.label || '暫無資料'; + + const handleClose = () => { + setMessage(''); + setContact(''); + onClose(); + }; + + const handleSubmit = () => { + onSubmit({ message, contact }); + }; + + return ( + + + {label} + + + + + + + +
    + + {user?.name || '名稱'} + + + {role} + +
    +
    + +
    + + {description} + + setMessage(e.target.value)} + placeholder={descriptionPlaceholder} + /> +
    + +
    + 聯絡資訊 + setContact(e.target.value)} + placeholder="寫下您的聯絡資訊,初次聯繫建議提供「想公開的社群媒體帳號、email」即可。" + /> +
    + + + + + +
    +
    + ); +} + +export default ContactPopup; diff --git a/components/Group/detail/Contact/Feedback.jsx b/components/Group/detail/Contact/FeedbackPopup.jsx similarity index 90% rename from components/Group/detail/Contact/Feedback.jsx rename to components/Group/detail/Contact/FeedbackPopup.jsx index b4f8da99..c8282c33 100644 --- a/components/Group/detail/Contact/Feedback.jsx +++ b/components/Group/detail/Contact/FeedbackPopup.jsx @@ -1,21 +1,17 @@ -import { useId, forwardRef } from 'react'; +import { useId } from 'react'; import { Box, Button, Dialog, DialogTitle, - Slide, Typography, useMediaQuery, } from '@mui/material'; import contractDoneImg from '@/public/assets/contactdone.png'; import contractErrorImg from '@/public/assets/contacterror.png'; +import TransitionSlide from './TransitionSlide'; -const Transition = forwardRef((props, ref) => { - return ; -}); - -function Feedback({ type, onClose }) { +function FeedbackPopup({ type, onClose }) { const id = useId(); const isMobileScreen = useMediaQuery('(max-width: 560px)'); const titleId = `modal-title-${id}`; @@ -46,7 +42,7 @@ function Feedback({ type, onClose }) { onClose={onClose} aria-labelledby={titleId} aria-describedby={descriptionId} - TransitionComponent={Transition} + TransitionComponent={TransitionSlide} sx={{ '.MuiPaper-root': { marginTop: isMobileScreen ? '84px' : undefined, @@ -78,6 +74,7 @@ function Feedback({ type, onClose }) { {content.title} + + 請先登入或註冊 + + + 登入後才可以使用聯繫夥伴功能唷! + + + 登入 + + + + + + + ); +} + +export default LoginPopup; diff --git a/components/Group/detail/Contact/TransitionSlide.jsx b/components/Group/detail/Contact/TransitionSlide.jsx new file mode 100644 index 00000000..c596c872 --- /dev/null +++ b/components/Group/detail/Contact/TransitionSlide.jsx @@ -0,0 +1,8 @@ +import { forwardRef } from 'react'; +import { Slide } from '@mui/material'; + +const TransitionSlide = forwardRef((props, ref) => { + return ; +}); + +export default TransitionSlide; diff --git a/components/Group/detail/Contact/index.jsx b/components/Group/detail/Contact/index.jsx index 5d8934ab..cdd778b5 100644 --- a/components/Group/detail/Contact/index.jsx +++ b/components/Group/detail/Contact/index.jsx @@ -1,28 +1,16 @@ -import { useId, useState, forwardRef, useEffect } from 'react'; -import { useRouter } from 'next/router'; +import { useState } from 'react'; import { useSelector } from 'react-redux'; import styled from '@emotion/styled'; -import { - Avatar, - Box, - Button, - Dialog, - DialogTitle, - IconButton, - Slide, - Typography, - TextareaAutosize, - useMediaQuery, -} from '@mui/material'; -import CloseIcon from '@mui/icons-material/Close'; +import { Button } from '@mui/material'; import { ROLE } from '@/constants/member'; import chatSvg from '@/public/assets/icons/chat.svg'; import useMutation from '@/hooks/useMutation'; import { mapToTable } from '@/utils/helper'; -import openLoginWindow from '@/utils/openLoginWindow'; -import Feedback from './Feedback'; +import ContactPopup from './ContactPopup'; +import FeedbackPopup from './FeedbackPopup'; +import LoginPopup from './LoginPopup'; -const ROLELIST = mapToTable(ROLE); +const ROLE_LIST = mapToTable(ROLE); const StyledButton = styled(Button)` padding: 8px 36px; @@ -37,41 +25,6 @@ const StyledButton = styled(Button)` opacity: 0.3; } `; -const StyledTitle = styled.label` - display: block; - color: var(--black-white-gray-dark, #293a3d); - font-size: 16px; - font-style: normal; - font-weight: 500; - line-height: 140%; /* 22.4px */ - margin-bottom: 11px; -`; -const StyledTextArea = styled(TextareaAutosize)` - display: block; - padding: 12px 16px; - background: var(--black-white-white, #fff); - border-radius: 8px; - border: 1px solid var(--black-white-gray-very-light, #dbdbdb); - width: 100%; - min-height: 128px; -`; -const StyledText = styled.div` - margin-top: 6px; - margin-left: 6px; - display: block; - color: black; - font-size: 12px; -`; -const StyledLink = styled.span` - padding: 0 2px; - color: #16b9b3; - text-decoration: underline; - cursor: pointer; -`; - -const Transition = forwardRef((props, ref) => { - return ; -}); function ContactButton({ user, @@ -81,26 +34,13 @@ function ContactButton({ descriptionPlaceholder, isLoading, }) { - const id = useId(); - const router = useRouter(); const me = useSelector((state) => state.user); const [open, setOpen] = useState(false); - const [message, setMessage] = useState(''); - const [contact, setContact] = useState(''); const [feedback, setFeedback] = useState(''); - const isMobileScreen = useMediaQuery('(max-width: 560px)'); - const titleId = `modal-title-${id}`; - const descriptionId = `modal-description-${id}`; - const messageId = `message-${id}`; - const contactId = `contact-${id}`; const isLogin = !!me?._id; - const role = - ROLE.find(({ key }) => user?.roleList?.includes(key))?.label || '暫無資料'; const handleClose = () => { setOpen(false); - setMessage(''); - setContact(''); }; const { mutate } = useMutation(`/email`, { method: 'POST', @@ -114,14 +54,14 @@ function ContactButton({ }, }); - const handleSubmit = () => { + const handleSubmit = ({ message, contact }) => { mutate({ userId: me._id, url: window.location.origin, name: me.name, roleList: me.roleList.length > 0 - ? me.roleList.map((roleKey) => ROLELIST[roleKey]) + ? me.roleList.map((roleKey) => ROLE_LIST[roleKey]) : [''], photoUrl: me.photoURL, from: me.email, @@ -134,17 +74,9 @@ function ContactButton({ }); }; - useEffect(() => { - if (!me?._id && open) router.push('/login'); - }, [me?._id, open, router]); - return (
    - setOpen(true)} - > + setOpen(true)}> contact icon {label} - {!isLogin && ( - - router.push('/login')}>註冊或 - openLoginWindow()}>登入 - 即可聯繫主揪! - + {isLogin ? ( + + ) : ( + setOpen(false)} /> )} - - - {label} - - - - - - - -
    - - {user?.name || '名稱'} - - - {role} - -
    -
    - -
    - - {description} - - setMessage(e.target.value)} - placeholder={descriptionPlaceholder} - /> -
    - -
    - 聯絡資訊 - setContact(e.target.value)} - placeholder="寫下您的聯絡資訊,初次聯繫建議提供「想公開的社群媒體帳號、email」即可。" - /> -
    - - - - - -
    -
    - setFeedback('')} /> + setFeedback('')} />
    ); } diff --git a/components/Group/detail/index.jsx b/components/Group/detail/index.jsx index 8f68da74..d333317e 100644 --- a/components/Group/detail/index.jsx +++ b/components/Group/detail/index.jsx @@ -22,7 +22,7 @@ import ShareButtonGroup from './ShareButtonGroup'; function GroupDetail({ id, source, isLoading }) { const router = useRouter(); const me = useSelector((state) => state.user); - const isMyGroup = source?.userId === me?._id; + const isMyGroup = source?.userId === me?._id && !!me?._id; return ( diff --git a/components/Privacypolicy/AboutUs/index.jsx b/components/Privacypolicy/AboutUs/index.jsx deleted file mode 100644 index f3234916..00000000 --- a/components/Privacypolicy/AboutUs/index.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled'; -import { Box, Typography, Stack } from '@mui/material'; - -const LineWrapper = styled(Typography)` - margin: 5px 0; -`; - -const AboutUs = () => { - return ( - - - 關於我們 - - - daodao - - - - 在島島阿學裡,每個人都是一座獨一無二的「島」,對於學習/生命擁有不同的渴望與資源,因為互相、互助學習,成為一片獨立又連結的群島。 - - - 而島島阿學也希望能有台語「沓沓仔學Ta̍uh-ta̍uh-á - o̍h」,「慢慢學不用急」之意涵,道出組織的教育價值觀是以人為本,尊重每人學習步調與方向。 - - - |島島阿學|學習資源平台由一群學生、老師、家長共創。我們期盼以集體智慧,打造沒有天花板的學習環境,一個以自主學習為主的民主社群。邀請所有學習者一同解決彼此在學習時遇到的困境,例如找不到學習目標、合適資源、學習夥伴等問題。因此平台提供資源分享與整合,以及社群的服務,包含各領域各種形式的資源、教育活動、學習場域、學習經驗等等。我們認為社群即資源、支援,讓學習者在民主教育的社群中,以共好的概念,解決彼此學習的問題,支持彼此成為自己想成為的人。 - - - - ); -}; - -export default AboutUs; diff --git a/components/Privacypolicy/index.jsx b/components/Privacypolicy/index.jsx deleted file mode 100644 index 53210ad5..00000000 --- a/components/Privacypolicy/index.jsx +++ /dev/null @@ -1,171 +0,0 @@ -import React from 'react'; -import styled from '@emotion/styled'; -import { Box, Paper, Typography, Stack, Avatar } from '@mui/material'; - -const PrivacypolicyWrapper = styled.section` - padding-top: 40px; - padding-bottom: 40px; - .title { - font-size: 24px; - font-weight: 500; - margin: 0 10px 0 0; - color: black; - &:hover { - cursor: pointer; - color: #37b9eb; - transition: 0.5s; - } - } - @media (max-width: 767px) { - .title { - text-overflow: ellipsis; - width: 100%; - } - } -`; -const LinkWrapper = styled.a` - color: black; - &:hover { - opacity: 100%; - transition: color 0.5s ease; - color: #16b9b3; - } -`; - -const LineWrapper = styled(Typography)` - margin: 5px 0; -`; - -const PaperWrapper = styled(Paper)` - width: 90%; - margin: 0 auto; - padding: 20px; - @media (max-width: 767px) { - padding: 10px; - } -`; - -const CONTENT = [ - { - id: 0, - title: '使用者條款', - content: - '感謝您有意願貢獻資料及相關內容(以下統稱「內容」)至島島阿學學習社群(https://www.daoedu.tw,以下簡稱「本網站」)。此使用者條款存在於您及本網站管理機關島島阿學學習社群(「管理者」)間,目的在釐清雙方相關智慧財產權利狀態及其他權利義務關係。請閱讀以下條款及條件並確認,當您上傳內容至本網站時,即表示您接受本協議內容。', - }, - { - id: 1, - title: '貢獻內容之合法及適當狀態', - content: - '您的資料貢獻不應侵害他人之智慧財產權利。如您貢獻內容,代表就您所知您表示,您有權授權管理者及本網站的使用者,依管理者指定、揀選的授權條款,來使用並散布這些內容。請注意管理者並不必然需要將您貢獻的內容包括於本網站上,並且得以在任何時候,將您的貢獻於本網站移除。對您貢獻的內容,管理者並無負擔保管責任。', - }, - { - id: 2, - title: '授與權利', - content: - '您在此授與管理者全球性、免授權金、非專屬、永久、不可撤回之著作權及其他司法管轄區域可能定義之鄰接權及資料庫權之權利,讓其能夠不受前述權利限制使用內容之任何內含物,無論是就原始載體,或採其他型態進行利用。所授與的權利明示包括商業性使用,並且不排除任何領域內的利用。此一授權包括並不限於,將作品進行後續再授權利用,並允許多階的被授權人皆得採再授權方式再作利用。就現行法及著作權契約可容許的最大範圍內,您對管理者或經其授權利用內容之人,亦拋棄或不主張任何著作人格權。除本處訂明之範圍,您所貢獻內容之所有權利、地位,及利益,仍保留在您。', - }, - { - id: 3, - title: '免責聲明', - content: - '在現行法容許的範圍內,您是以現狀及現有之基礎提供本內容,但應保證該內容無侵權之虞。除依法不得事先排除或限縮之責任外,您或管理者皆不應依此協議,為其他歸責理論導致任何損害賠償。', - }, - { - id: 4, - title: '其他事項', - content: - '您所提送之表單及因之所需之註冊、登入程序裡,產生或由您填註的個人資訊,您在此同意管理者後續蒐集、處理及利用該個人資料。本「條款」依據中華民國法律。當本協議任一條款被視為無效時不影響其他部份之有效性。', - }, - { - id: 5, - title: '輔助條款', - content: - '除採前述貢獻提供內容外,您亦可透過表格、附錄,補充「描述」欄位說明的方式,表達您指定特一款或多款公眾授權條款,或宣告模式為輔助授權條款之意向。此時該等貢獻內容,除依本條款第2條規定之方式授權本網站外,同時亦依指定條款雙重/多重授權予本網站。您所指定的輔助條款,並不拘束管理者,但在處理方式容許的前提下,管理者得尊重您的指定,除依管理者指定、揀選的授權條款外,同時一併採用輔助條款來散布該等貢獻內容。倘您於輔助條款裡列入「CC0 公眾領域貢獻宣告」,該宣告模式為著作權及相關權利的拋棄聲明,解釋上本網站得不受任何著作權利與相關權利之限制,自由使用該等貢獻內容,並亦得再採 CC0 宣告模式散布該等貢獻內容。', - }, - { - id: 6, - title: '聯絡方式', - content: - '如果您對於本使用者條款、著作權、資料庫處理爭議、服務內容或隱私等有任何疑慮,請寄信至島島阿學學習社群之聯絡信箱 contact@daoedu.tw,由島島阿學學習社群核心團隊在固定會議中或透過其他管道為您處理。紛爭解決和管轄法院。依本使用者條款所產生之爭議,雙方合意以臺灣臺北地方法院為第一審管轄法院。', - }, - { - id: 7, - title: '隱私權政策', - content: - '我們是誰,我們是島島阿學學習社群,網站網址為 https://www.daoedu.tw。這個網站收集了哪些個人資訊,以及為什麼要收集這些資訊。包含留言,使用者在這個網站發佈留言後,我們會收集顯示於留言表單中的資料、使用者的來源 IP 位址及瀏覽器的使用者代理程式字串,以協助網站偵測垃圾留言。這個網站會根據使用者的電子郵件地址建立匿名化字串 (亦稱為雜湊值),並提供給 Gravatar 個人頭像服務以查詢這個使用者是否為這項服務的使用者。如需瞭解 Gravatar 個人頭像服務的隱私權政策,請造訪 https://automattic.com/privacy/。網站管理員核准使用者發佈的留言後,使用者的個人資料圖片便會在留言內容中公開顯示。以及媒體,如需將圖片上傳至這個網站,請避免上傳內嵌 EXIF GPS 位置資料的圖片,因為其他使用者可以從網站上下載圖片並擷取當中的位置資料。', - }, - { - id: 8, - title: '聯絡表單', - content: - '使用者在這個網站發佈留言時,可以選擇是否在 Cookie 中儲存使用者姓名、電子郵件地址及網站網址;儲存這些資料是為了使用者更加方便,以便讓使用者發佈其他留言時,無需再次填寫個人資料。在使用者不自行清除裝置 Cookie 的狀況下,這些 Cookie 在個人裝置上保留一年。如果使用者造訪這個網站的登入頁面,系統會設定一個臨時 Cookie 以確定使用者的瀏覽器是否接受 Cookie;這個 Cookie 不包含任何個人資料,並會在使用者關閉瀏覽器時捨棄。使用者登入網站後,系統會設定幾個 Cookie 以儲存使用者的登入資訊及顯示項目設定;登入資訊 Cookie 會保留兩天,顯示項目設定 Cookie 則會保留一年。如果登入時核取了 [保持登入] 這項設定,使用者的登入狀態會維持兩週;帳號登出後,便會移除使用者裝置上的登入資訊 Cookie。使用者編輯或發佈文章時,會在瀏覽器中儲存其他 Cookie。這個 Cookie 不包含任何個人資料,僅記錄表示使用者撰寫的文章的文章 ID,並會在一天後過期。', - }, - { - id: 9, - title: '來自第三方網站的嵌入內容', - content: - '這個網站上的文章可能會嵌入視訊、圖片、文章等內容,而來自第三方網站的嵌入內容,其隱私權處理方式與使用者造訪這些網站時的規定完全相同。無論使用者是否有這些第三方網站的帳號或是否登入網站,他們都會以各種方式收集與使用者相關的資料,如 Cookie、嵌入第三方追蹤程式碼、監視使用者與嵌入內容的互動等。', - }, - { - id: 10, - title: '使用者資料分析', - content: - '這個網站的個人資料分享對象,如果你提出密碼重設要求,你目前進行連線的 IP 位址會顯示於密碼重設電子郵件中。', - }, - { - id: 11, - title: '這個網站的個人資料保留期限', - content: - '當使用者在這個網站發佈留言後,該則留言及其中繼資料將會無限期保留。這樣系統便可以自動辨識及核准任何後續留言,而不須將其保留在待審核的佇列中。針對在這個網站上註冊的使用者,這個網站還會儲存他們在使用者 [個人資料] 頁面中提供的個人資訊。全部使用者都可以隨時查看、編輯或刪除自己的個人資訊 (無法變更的使用者名稱除外)。請注意,網站管理員也可以查看及編輯這些個人資訊。', - }, - { - id: 12, - title: '使用者對個人資料擁有哪些權利', - content: - '如果使用者在這個網站擁有帳戶或曾發佈留言,便可以要求下載使用者在這個網站上的個人資料的資料匯出檔,這個檔案包含使用者提供給這個網站的全部個人資料。使用者也可以要求清除曾提供給這個網站的全部個人資料,但這項要求不包含站方為了管理、法律或安全目的而必須保留的相關資料。', - }, -]; - -const Privacypolicy = () => { - return ( - - - - 使用者條款與隱私權政策 - - - {CONTENT.map(({ id, title, content }) => ( - - - {title} - - - {content} - - - ))} - - - - ); -}; - -export default Privacypolicy; diff --git a/components/Profile/Edit/index.jsx b/components/Profile/Edit/index.jsx index 324e5d7d..9d4dd038 100644 --- a/components/Profile/Edit/index.jsx +++ b/components/Profile/Edit/index.jsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react'; +import dayjs from 'dayjs'; import toast from 'react-hot-toast'; import useMediaQuery from '@mui/material/useMediaQuery'; import { useRouter } from 'next/router'; @@ -69,6 +70,9 @@ function EditPage() { onChangeHandler({ key: 'facebook', value: facebook || '' }); onChangeHandler({ key: 'discord', value: discord || '' }); onChangeHandler({ key: 'line', value: line || '' }); + } else if (key === 'birthDay') { + const parsedDate = dayjs(value); + onChangeHandler({ key: 'birthDay', value: parsedDate }); } else if (key === 'location') { onChangeHandler({ key, value }); const [country, city, district] = value.split('@'); @@ -132,8 +136,16 @@ function EditPage() { onChangeHandler({ key: 'birthDay', value: date }) } renderInput={(params) => ( - + )} + maxDate={dayjs().subtract(16, 'year')} + defaultCalendarMonth={dayjs().subtract(18, 'year')} /> @@ -202,7 +214,7 @@ function EditPage() { sx={{ width: '100%' }} > - 請選擇您或孩子目前的教育階段 + 請選擇您目前的教育階段 {EDUCATION_STAGE.map(({ label, value }) => ( diff --git a/components/Profile/Edit/useEditProfile.jsx b/components/Profile/Edit/useEditProfile.jsx index 897c8945..70f88872 100644 --- a/components/Profile/Edit/useEditProfile.jsx +++ b/components/Profile/Edit/useEditProfile.jsx @@ -47,6 +47,15 @@ const schema = z.object({ .min(1, { message: '請輸入名字' }) .max(50, { message: '名字過長' }) .optional(), + birthDay: z + .any() + .refine((date) => dayjs(date).isValid(), { + message: '請選擇您的出生日期', + }) + .refine((date) => dayjs().diff(date, 'year') >= 16, { + message: '您的年齡未滿16歲,目前無法於平台註冊,請詳閱島島社群條款', + }) + .optional(), isOpenLocation: z.boolean().optional(), isOpenProfile: z.boolean().optional(), instagram: buildValidator( @@ -73,6 +82,10 @@ const schema = z.object({ '長度最多20個字元', '長度最少6個字元,支援英文、數字、底線、句號', ), + isLoadingSubmit: z.boolean().optional(), + tagList: z.array(z.string()).optional(), + wantToDoList: z.array(z.string()).optional(), + roleList: z.array(z.string()).optional(), }); const userReducer = (state, payload) => { @@ -99,26 +112,26 @@ const useEditProfile = () => { const [errors, setErrors] = useState({}); const validate = (state = {}, isPartial = false) => { - const [key, value] = Object.entries(state)[0]; - if (key !== 'birthDay') { - const result = isPartial - ? schema.partial({ [key]: true }).safeParse({ [key]: value }) - : schema.safeParse({ [key]: value }); - - if (!result.success) { - result.error.errors.forEach((err) => { - setErrors({ [err.path[0]]: err.message }); + const [key, val] = Object.entries(state)[0]; + const result = isPartial + ? schema + .partial({ [key]: true }) + .safeParse({ [key]: key === 'birthDay' ? val?.$d : val }) + : schema.safeParse({ + ...state, + birthDay: state.birthDay.$d, }); - } - if (isPartial && result.success) { - const obj = { ...errors }; - delete obj[key]; - setErrors(obj); - } - - return result.success; + if (!result.success) { + result.error.errors.forEach((err) => { + setErrors({ [err.path[0]]: err.message }); + }); + } + if (isPartial && result.success) { + const obj = { ...errors }; + delete obj[key]; + setErrors(obj); } - return true; + return result.success; }; const onChangeHandler = ({ key, value, isMultiple }) => { @@ -153,7 +166,7 @@ const useEditProfile = () => { id, email, name, - birthDay, + birthDay: dayjs(birthDay).format('YYYY/MM/DD'), gender, roleList, contactList: { diff --git a/components/Terms/Ipr.jsx b/components/Terms/Ipr.jsx new file mode 100644 index 00000000..0421a68f --- /dev/null +++ b/components/Terms/Ipr.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { TermsWrapper, PaperWrapper } from './Terms.styled'; + +const Terms = () => { + return ( + + +

    島島阿學資源共享自主學習網站智慧財產權

    +

    + 島島阿學資源共享自主學習網站( + https://www.daoedu.tw + ,以下簡稱「本網站」)尊重他人的智慧財產權,希望提供一個不包含侵犯這些權利的內容平台。我們的使用者協議要求會員發布的資訊準確、合法且不侵犯第三方的權利,這個要求包含本網站所舉辦的任何型態活動。 +

    +

    + 請注意,無論我們是否禁止存取或刪除內容,本網站都可能會善意地嘗試將書面通知(包括投訴人的聯絡資訊)轉發給發布該內容的會員和/或採取其他合理措施來通知該會員。表示本網站 + 已收到涉嫌侵犯智慧財產權或其他內容違規的通知。我們的政策也是,在適當的情況下,我們可以自行決定停用和/或終止侵犯或反覆侵犯他人權利或以其他方式發布非法內容的會員或團體(視情況而定)的帳戶。 +

    +

    + 請注意,您提交的任何通知或反通知必須真實,且必須遵守偽證罪的處罰規定。虛假通知或反通知可能會導致個人責任。因此,您可能需要在提交通知或反通知之前尋求法律顧問的建議。 +

    +
      +
    1. +

      1. 著作權

      +
        +
      1. +

        + 網站協作者查核後所撰寫的回應、補充或評價等資訊,若有產生著作權利保護之可能,撰寫之編輯同意採公眾領域宣告(CC0-1.0)無償提交至本網站所維運之電腦或相關設備進行存放。 +

        +
      2. +
      3. +

        + 網站協作者若使用第三方之資料來源進行佐證,其引用之資料來源之著作仍屬於原創作之第三方所有,此時協作者同意為查證目的,在合理範圍內引用第三方資料,並於提交時提供原創作出處。 +

        +
      4. +
      5. +

        + 網站協作者及聊天機器人使用者採 CC0-1.0 + 提交資訊後,本網站將存放於電腦或其相關設備的該等資訊進行編輯性整理後,得採 + + {' '} + CC BY-SA 4.0{' '} + + 或其他授權模式,將具編輯性保護之資料集發布於島島阿學學習社群網站、島島阿學學習社群聊天機器人或島島阿學學習社群所提供資料存檔等處。 +

        +
      6. +
      7. +

        + 若資料利用者有違反前項授權模式的情事,將由 本網站主張權利。 +

        +
      8. +
      9. +

        + 本網站將就社會公益及開放性進行綜合評估,來選擇發布資料時使用之授權模式,亦歡迎您透過 + 本網站 + 的聯繫方式,隨時將授權釋出政策有關的寶貴意見提供給我們。 +

        +
      10. +
      +
    2. +
    3. +

      2. 貢獻內容之合法及適當狀態

      +
        +
      1. +

        + 您的資料貢獻不應侵害他人之智慧財產權利。如您貢獻內容,代表就您所知您表示,您有權授權管理者及本網站的使用者,依管理者指定、揀選的授權條款,來使用並散布這些內容。請注意管理者並不必然需要將您貢獻的內容包括於本網站上,並且得以在任何時候,將您的貢獻於本網站移除。對您貢獻的內容,管理者並無負擔保管責任。 +

        +
      2. +
      +
    4. +
    5. +

      3. 授與權利

      +
        +
      1. +

        + 您在此授與管理者全球性、免授權金、非專屬、永久、不可撤回之著作權及其他司法管轄區域可能定義之鄰接權及資料庫權之權利,讓其能夠不受前述權利限制使用內容之任何內含物,無論是就原始載體,或採其他型態進行利用。所授與的權利明示包括商業性使用,並且不排除任何領域內的利用。此一授權包括並不限於,將作品進行後續再授權利用,並允許多階的被授權人皆得採再授權方式再作利用。就現行法及著作權契約可容許的最大範圍內,您對管理者或經其授權利用內容之人,亦拋棄或不主張任何著作人格權。除本處訂明之範圍,您所貢獻內容之所有權利、地位,及利益,仍保留在您。 +

        +
      2. +
      +
    6. +
    7. +

      4. 免責聲明

      +
        +
      1. +

        + 在現行法容許的範圍內,您是以現狀及現有之基礎提供本內容,但應保證該內容無侵權之虞。除依法不得事先排除或限縮之責任外,您或管理者皆不應依此協議,為其他歸責理論導致任何損害賠償。 +

        +
      2. +
      +
    8. +
    9. +

      5. 其他事項

      +
        +
      1. +

        + 您所提送之表單及因之所需之註冊、登入程序裡,產生或由您填註的個人資訊,您在此同意管理者後續蒐集、處理及利用該個人資料。本「條款」依據中華民國法律。當本協議任一條款被視為無效時不影響其他部份之有效性。 +

        +
      2. +
      +
    10. +
    11. +

      6. 輔助條款

      +
        +
      1. +

        + 除採前述貢獻提供內容外,您亦可透過表格、附錄,補充「描述」欄位說明的方式,表達您指定特一款或多款公眾授權條款,或宣告模式為輔助授權條款之意向。此時該等貢獻內容,除依本條款第2條規定之方式授權本網站外,同時亦依指定條款雙重/多重授權予本網站。您所指定的輔助條款,並不拘束管理者,但在處理方式容許的前提下,管理者得尊重您的指定,除依管理者指定、揀選的授權條款外,同時一併採用輔助條款來散布該等貢獻內容。倘您於輔助條款裡列入「 + CC0 + 公眾領域貢獻宣告」,該宣告模式為著作權及相關權利的拋棄聲明,解釋上本網站得不受任何著作權利與相關權利之限制,自由使用該等貢獻內容,並亦得再採 + CC0 宣告模式散布該等貢獻內容。 +

        +
      2. +
      +
    12. +
    13. +

      7. 聯絡方式

      +
        +
      1. +

        + 如果您對於本使用者條款、著作權、資料庫處理爭議、服務內容或隱私等有任何疑慮,請寄信至本網站之聯絡信箱 + contact@daoedu.tw + ,由本網站在固定會議中或透過其他管道為您處理。紛爭解決和管轄法院。依本使用者條款所產生之爭議,雙方合意以臺灣臺北地方法院為第一審管轄法院。 +

        +
      2. +
      +
    14. +
    +
    +
    + ); +}; + +export default Terms; diff --git a/components/Terms/Privacypolicy.jsx b/components/Terms/Privacypolicy.jsx new file mode 100644 index 00000000..ab8df481 --- /dev/null +++ b/components/Terms/Privacypolicy.jsx @@ -0,0 +1,119 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { Paper } from '@mui/material'; +import { TermsWrapper, PaperWrapper } from './Terms.styled'; + +const Terms = () => { + return ( + + +

    島島阿學資源共享自主學習網站隱私權政策

    +

    + 島島阿學的使命是透過促進自主學習來實現終身學習的能力,讓學習者可以交流真實的學習經驗,發掘和分享有價值的學習資源,並與志趣相投的人們建立聯繫。我們的隱私權政策適用於我們服務的任何註冊使用者或訪客。 + 我們的註冊使用者(「會員」)分享他們的學習經驗、學習資源並與其他會員進行學習交流活動,展現個人技能、經歷與成長,發佈和查看相關內容,並尋找可能的共同成長及合作機會。非會員(「訪客」)可以查看我們某些服務的內容和資料。 + 此隱私權政策存在於您及本網站管理機關島島阿學資源共享自主學習網站( + https://www.daoedu.tw + ,以下簡稱「本網站」)(「管理者」)間。請閱讀以下條款及條件並確認,當您上傳內容至本網站時,即表示您接受本協議內容。 +

    +
      +
    1. +

      1. 個人資料

      +
        +
      1. +

        + 網站會員登入後,島島阿學學習社群網站會取得會員在 Google + 上的:電子郵件地址與應用程式使用者 + ID,用以分辨不同帳號以及聯繫會員。顯示名稱與頭像等公開顯示資訊,作為島島阿學學習社群網站的預設頭像與顯示名稱。 +

        +
      2. +
      3. +

        + 網站會員登入後的新增之資源、找夥伴與找揪團公開資料、回應內容、使用本平台之頻率等之公開訊息,將可能為島島阿學團隊用於數據分析,當進行量化或去識別化等過程與原身分識別勾脫後,會留存於開放資料隱去名稱之研究或統計。 +

        +
      4. +
      5. +

        + 本網站會嚴格保護會員之隱私及個人資料,除充分量化或去識別化後得不再視為個人資料者外,將不基於任何目的使其外流至與本服務無關之第三方。 +

        +
      6. +
      7. +

        + 網站會員可在個人頁面逕行變更自己在島島阿學網站的預設頭像與顯示名稱,使其不再與 + Facebook、Twitter、Github 或 Google + 上的公開資訊相同。另外,會員亦可寄信至本網站連絡信箱( + contact@daoedu.tw + ),請求刪除或更換取自 Google、Facebook、Github 或 Google + 等平台的公開顯示資訊或電子郵件地址。 +

        +
      8. +
      9. +

        + 本網站會在確認收到請求後的最遲三十天內,比對驗證寄件者電子郵件地址無誤,足證申請者為原個人資料當事人之後進行刪除或更換處理。 +

        +
      10. +
      11. +

        + 當您註冊帳戶時,我們可能會要求您提供相關資訊,例如您的教育背景、工作經驗、技能、照片、城市或地區。您無需在個人資料中提供其他資訊;但是,個人資料資訊可幫助我們提供給您更多服務。您可以選擇是否在您的個人資料中包含敏感資訊並公開該敏感資訊。請勿在您的個人資料中發布或新增您不希望公開的個人資料。 +

        +
      12. +
      +
    2. +
    3. +

      2. 來自第三方網站的嵌入內容

      +
        +
      1. +

        + 這個網站上的文章可能會嵌入視訊、圖片、文章等內容,而來自第三方網站的嵌入內容,其隱私權處理方式與使用者造訪這些網站時的規定完全相同。無論使用者是否有這些第三方網站的帳號或是否登入網站,他們都會以各種方式收集與使用者相關的資料,如 + Cookie、嵌入第三方追蹤程式碼、監視使用者與嵌入內容的互動等。 +

        +
      2. +
      +
    4. +
    5. +

      3. 使用者資料分析

      +
        +
      1. +

        + 這個網站的個人資料分享對象,如果你提出密碼重設要求,你目前進行連線的 + IP 位址會顯示於密碼重設電子郵件中。 +

        +
      2. +
      +
    6. +
    7. +

      4. 這個網站的個人資料保留期限

      +
        +
      1. +

        + 當使用者在這個網站發佈留言後,該則留言及其中繼資料將會無限期保留。這樣系統便可以自動辨識及核准任何後續留言,而不須將其保留在待審核的佇列中。 +

        +
      2. +
      3. +

        + 針對在這個網站上註冊的使用者,這個網站還會儲存他們在使用者「個人資料」頁面中提供的個人資訊。全部使用者都可以隨時查看、編輯或刪除自己的個人資訊(無法變更的使用者名稱除外)。請注意,網站管理員也可以查看及編輯這些個人資訊。 +

        +
      4. +
      +
    8. +
    9. +

      5. 使用者對個人資料擁有哪些權利

      +
        +
      1. +

        + 如果使用者在這個網站擁有帳戶或曾發佈留言,便可以要求下載使用者在這個網站上的個人資料的資料匯出檔,這個檔案包含使用者提供給這個網站的全部個人資料。 +

        +
      2. +
      3. +

        + 使用者也可以要求清除曾提供給這個網站的全部個人資料,但這項要求不包含站方為了管理、法律或安全目的而必須保留的相關資料。 +

        +
      4. +
      +
    10. +
    +
    +
    + ); +}; + +export default Terms; diff --git a/components/Terms/Service.jsx b/components/Terms/Service.jsx new file mode 100644 index 00000000..303f9084 --- /dev/null +++ b/components/Terms/Service.jsx @@ -0,0 +1,213 @@ +import React from 'react'; +import { TermsWrapper, PaperWrapper } from './Terms.styled'; + +const Terms = () => { + return ( + + +

    島島阿學資源共享自主學習網站使用者條款

    +

    + 島島阿學的使命是透過促進自主學習來實現終身學習的能力。我們的服務致力於創造一個值得信賴的學習生態系統,讓學習者可以交流真實的學習經驗,發掘和分享有價值的學習資源,並與志趣相投的人們建立聯繫。透過自我探索、合作及成長,學習者可以充分發揮自己的潛力,並在瞬息萬變的世界中持續發展。 +

    +

    + 此使用者條款為島島阿學資源共享自主學習網站( + https://www.daoedu.tw + ,以下簡稱「本網站」)使用者於本網站上查找與新增資源、使用找夥伴、揪團和部落格等所有網站功能之規範。我們的使用者條款適用於我們服務的任何註冊使用者或訪客。 +

    +

    本服務僅限於自然人使用,亦僅對自然人生效。

    +
      +
    1. +

      1. 會員及訪客

      +
        +
      1. +

        + 當您註冊並加入本網站時,您就成為會員。如果您選擇不註冊我們的服務,您可以作為「訪客」存取某些功能。本網站對所有人開放,對撰寫查證回應之資格沒有設限。 +

        +
      2. +
      +
    2. +
    3. +

      2. 註冊和帳號使用

      +

      就島島阿學會員帳戶之註冊及使用事宜,您聲明及保證以下事項:

      +
        +
      1. +

        + 本網站使用者需年滿16歲,當您完成註冊或開始使用服務時,即視為您已經充分審閱、了解及同意本條款,並同意您註冊成為會員及使用服務。 +

        +
      2. +
      3. +

        + 您同意使用服務之所有內容包括意思表示等,皆以電子文件做為表示方式,且本網站對您的所有通知皆以電子郵件或其他電子方式為之。 +

        +
      4. +
      5. +

        + 就註冊程序所要求的真實姓名及其他資料,您提供的資訊皆為最新且真實的資料,且如有任何變動的話會即時更新。 +

        +
      6. +
      7. +

        您並非為其他人註冊帳號。

        +
      8. +
      9. +

        您並未也不會註冊兩個以上的個人帳號。

        +
      10. +
      11. +

        如果您的帳號遭停權,您不會註冊另一個帳號。

        +
      12. +
      13. +

        + 您理解並同意,任何以您的帳號於島島阿學中所為行為,均視為您本人或經您授權所為,您可能因此必須負擔相應的法律責任,因此您不會讓其他人透過您的帳號使用島島阿學服務,亦不會將您的帳號密碼透露與他人知悉。 +

        +
      14. +
      15. +

        + 您理解並同意,島島阿學為一開放平台,一般使用者均可自由閱覽所有公開頁面,因此您於發表文章、回應或與其他使用者對話時,均應注意您所揭露的資訊可能導致陌生人或您的親友足以辨識您的真實身份,而可能對您的現實生活產生影響。 +

        +
      16. +
      17. +

        + 您理解並同意,在本網站發起揪團活動內文中充份揭露活動之必要真實資訊,包括但不限於活動目的、內容、要求和所可能需要的費用。 +

        +
      18. +
      19. +

        + 您理解並同意,在註冊為會員後收到本網站定期電子報,您亦可於收到電子報後隨時取消。 +

        +
      20. +
      21. +

        + 網站服務僅開放自然人使用,禁止使用自動化方式操作網站。除已知非直接營利的第三方公眾服務網站爬蟲,採低頻率部份內容捉取模式外(如 + Internet + Archive、Google、Facebook),禁止使用自動化方式爬取網站之內容。 +

        +
      22. +
      +
    4. +
    5. +

      3. 權利和限制

      +

      + 在您和本網站之間,您擁有您提交或發佈到服務的內容和訊息,並且您僅授予我們以下非獨佔許可: +

      +

      + 全球範圍內的、可轉讓和可再許可的權利,可使用、複製、修改、分發、發布和處理您透過我們的服務和其他人的服務提供的資訊和內容,而無需向您或其他人提供任何進一步的同意、通知和 + / 或補償。這些權利受到以下方面的限制: +

      +
        +
      1. +

        + 您可以透過從服務中刪除特定內容的方式終止該內容的許可,或者通常透過關閉您的帳戶來終止該內容的許可,除非 + (a) + 您作為服務的一部分與其他人共享該內容,並且他們複製、重新分享或儲存了該內容(b) + 從備份和其他系統中刪除所需的合理時間。 +

        +
      2. +
      3. +

        + 如果我們想授予其他人在服務之外發布您的內容的權利,我們將徵得您的同意。 +

        +
      4. +
      5. +

        + 雖然我們可能會對您的內容進行編輯和格式變更(例如翻譯或轉錄、修改大小、佈局或檔案類型或刪除元資料),但我們不會修改您表達的含義。 +

        +
      6. +
      7. +

        + 本網站會員登入網站之後,得透過撰寫回應或是評價現有回應、補充其他資訊的方式提供資料貢獻。網站會員撰寫回應時,應採用事實查核手法,回應訊息中可驗證之客觀事實,亦可針對訊息的主觀意見之處,提供他人之不同意見。 +

        +
      8. +
      9. +

        + 由於您擁有您的內容和訊息,而我們對其僅擁有非專有權利,因此您可以選擇將其提供給其他人,包括根據知識共享授權的條款。 +

        +
      10. +
      +
    6. +
    7. +

      4. 資料爭議申訴

      +
        +
      1. +

        + 本網站並不會主動介入任何回應之爭議處理,任何人皆可以直接使用網站撰寫訊息之回應,並歡迎對單一則訊息留下多則回應。 +

        +
      2. +
      3. +

        + 如果對於既有回應內容有其他爭議處理的狀況,請透過聯絡方式寄發申訴信,與本網站聯繫。 +

        +
      4. +
      5. +

        + 申訴信內容需要明確包含申訴人之真實姓名與身分、申訴之回應或訊息、申訴之時間地點與申訴之原因。本網站會試圖聯繫該名會員協助處理,或是以提供新回應的方式新增於該訊息內。 +

        +
      6. +
      7. +

        + 如果會員之回應內容涉及侮辱、毀謗、散布不實資訊、侵害著作權或其他有違反相關法令或侵害他人權利之情事者,本網站得將其回應刪除,並將相關處理資訊紀錄於 + + 資料申訴爭議處理紀錄 + + 。 +

        +
      8. +
      +
    8. +
    9. +

      5. 免責聲明

      +
        +
      1. +

        + 本網站不會侵害訊息、回應、佐證資料之各式顯名及人格權利。採 CC0 + - 1.0 + 收納資訊,主要規劃是擁有後續使用相關資訊的充足地位,並保有是否使用該訊息、回應、佐證資料之最終決定權。 +

        +
      2. +
      3. +

        + 本網站不為任何第三方取用資料的聊天機器人、網站、內容農場做資料背書,也不承認單方取用本網站所提供資料的合作關係。若其他機器人因為其程式設計的限制,無法正確或有效回應使用者的提問,本網站以及網站協作者不負擔資料庫的修正作業。 +

        +
      4. +
      5. +

        + 本網站承諾力求網站內容之準確性及完整性,但內容如有錯誤或遺漏,本網站不會承擔任何賠償責任,所有本網站內容,將會隨時更改,而不作另行通知。 +

        +
      6. +
      7. +

        本網站可隨時停止或變更網頁資料而毋須事前通知用戶。

        +
      8. +
      9. +

        + 本網站不會對使用或連結本網頁而引致任何損害(包括但不限於電腦病毒、系統固障、資料損失)、誹謗、侵犯版權或知識產權所造成的損失,包括但不限於利潤、商譽、使用、資料損失或其他無形損失,本網站不承擔任何直接、間接、附帶、特別、衍生性或懲罰性賠償。 +

        +
      10. +
      11. +

        + 本網站可能會連接至其他機構所提供的網頁,本公司不會對這些網頁內容作出任何保證或承擔任何責任。使用者如瀏覽這些網頁,將要自己承擔後果。是否使用本網站之服務下載或取得任何資料應由用戶自行考慮且自負風險,因前開任何資料之下載而導致用戶電腦系統之任何損壞或資料流失,本網站不承擔任何責任。 +

        +
      12. +
      +
    10. +
    11. +

      6. 聯絡方式

      +

      + 如果您對於本使用者條款、著作權、資料庫處理爭議、服務內容或隱私等有任何疑慮,請寄信至 + contact@daoedu.tw 。 +

      +
    12. +
    13. +

      7. 紛爭解決和管轄法院

      +

      + 依本使用者條款所產生之爭議,雙方合意以臺灣臺北地方法院為第一審管轄法院。 +

      +
    14. +
    +
    +
    + ); +}; + +export default Terms; diff --git a/components/Terms/Terms.styled.jsx b/components/Terms/Terms.styled.jsx new file mode 100644 index 00000000..62238ea2 --- /dev/null +++ b/components/Terms/Terms.styled.jsx @@ -0,0 +1,96 @@ +import styled from '@emotion/styled'; +import { Paper } from '@mui/material'; + +export const TermsWrapper = styled.section` + padding-top: 40px; + padding-bottom: 40px; +`; + +export const PaperWrapper = styled(Paper)` + width: min(90%, 800px); + margin: 0 auto; + padding: 40px 20px; + + @media (max-width: 767px) { + padding: 20px; + } + + h2 { + font-size: 24px; + font-size: min(max(24px, 5vw), 24px); + text-wrap: balance; + margin: 0 auto 1em; + color: #293a3d; + text-align: center; + font-weight: 500; + } + + @media (max-width: 767px) { + h2 { + text-overflow: ellipsis; + width: 100%; + } + } + + h3 { + font-size: 18px; + font-weight: 500; + margin: 1.5em 0 1em 0; + color: #293a3d; + } + + p { + font-size: 16px; + margin: 0 0 1em 0; + color: #536166; + line-height: 150%; + } + + a { + color: #536166; + color: #16b9b3; + @media (hover: hover) { + &:hover { + text-decoration: underline; + } + } + } + ol { + counter-reset: section; + + li { + counter-increment: section; + margin-bottom: 0.5em; + } + } + + .sublist { + counter-reset: item; + list-style-type: none; + margin-left: 20px; + + li { + counter-increment: item; + list-style-type: none; + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: flex-start; + line-height: 150%; + + &:before { + content: counter(section) '.' counter(item) '.'; + font-weight: 400; + display: inline-block; + width: 2em; + flex-shrink: 0; + } + + p { + display: inline-block; + padding-left: 0.5em; + margin-bottom: 0; + } + } + } +`; diff --git a/constants/category.js b/constants/category.js index 8c7f2c6a..cd57b94c 100644 --- a/constants/category.js +++ b/constants/category.js @@ -335,11 +335,11 @@ export const FOOTER_LINK = [ link: '/join', target: '_self', }, - { - name: '隱私權政策', - link: '/privacypolicy', - target: '_self', - }, + // { + // name: '隱私權政策', + // link: '/terms/privacypolicy', + // target: '_self', + // }, // { // name: "體驗問卷", // link: "https://docs.google.com/forms/d/e/1FAIpQLSeyU9-Q-kIWp5uutcik3h-RO4o5VuG6oG0m-4u1Ua18EOu3aw/viewform", diff --git a/functions/hello.js b/functions/hello.js deleted file mode 100644 index e6d068ed..00000000 --- a/functions/hello.js +++ /dev/null @@ -1,16 +0,0 @@ -// GET requests to /filename would return "Hello, world!" -export const onRequestGet = () => { - return new Response("Hello, world!"); -}; - -// POST requests to /filename with a JSON-encoded body would return "Hello, !" -export const onRequestPost = async ({ request }) => { - const { name } = await request.json(); - return new Response(`Hello, ${name}!`); -}; - -// export default async function handler(req = {}, res) { -// const result = await getVector(req); -// res.setHeader("Cache-Control", "private, max-age=0"); -// res.status(200).json(result?.getVector || result); -// } diff --git a/next-sitemap.js b/next-sitemap.js index e7d15179..e18154a1 100644 --- a/next-sitemap.js +++ b/next-sitemap.js @@ -163,7 +163,7 @@ module.exports = { lastmod: new Date().toISOString(), }, { - loc: `/privacypolicy`, + loc: `/terms/privacypolicy`, changefreq: 'daily', priority: 0.5, lastmod: new Date().toISOString(), diff --git a/pages/login/index.jsx b/pages/login/index.jsx index 45f8e3cd..c17c4398 100644 --- a/pages/login/index.jsx +++ b/pages/login/index.jsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import styled from '@emotion/styled'; import { useRouter } from 'next/router'; import Script from 'next/script'; @@ -9,7 +9,7 @@ import Navigation from '@/shared/components/Navigation_v2'; import Footer from '@/shared/components/Footer_v2'; import { BASE_URL } from '@/constants/common'; import openLoginWindow from '@/utils/openLoginWindow'; -// import sendDataToChromeExtension from '../../utils/sendDataToChromeExtension'; +import { useSelector } from 'react-redux'; const HomePageWrapper = styled.div` --section-height: calc(100vh - 80px); @@ -40,6 +40,7 @@ const ContentWrapper = styled.div` const LoginPage = () => { const LOGIN_PATH = `${BASE_URL}/auth/google`; const router = useRouter(); + const me = useSelector((state) => state.user); const SEOData = useMemo( () => ({ @@ -55,6 +56,12 @@ const LoginPage = () => { [router?.asPath], ); + useEffect(() => { + if (me?._id) { + router.push('/'); + } + }, []); + return (