From 93ef87d414d0b4387663c96081a3f10c8ddf9745 Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:47:45 +0900 Subject: [PATCH 01/12] =?UTF-8?q?chore:=20=E3=82=A2=E3=82=A4=E3=82=B3?= =?UTF-8?q?=E3=83=B3=E3=82=84masonry=E3=83=AC=E3=82=A4=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E9=96=A2=E9=80=A3=E3=81=AE=E3=83=91=E3=83=83=E3=82=B1?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=81=AE=E3=82=A4=E3=83=B3=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=83=BC=E3=83=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 103 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 ++ 2 files changed, 105 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 45f9811..d58e2b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,13 +12,17 @@ "@chakra-ui/react": "^2.1.1", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", + "@heroicons/react": "^2.1.5", "firebase": "^11.0.1", "framer-motion": "^11.11.11", "next-themes": "^0.4.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.3.0", - "react-router-dom": "^6.27.0" + "react-masonry-css": "^1.0.16", + "react-responsive-carousel": "^3.2.23", + "react-router-dom": "^6.27.0", + "react-tsparticles": "^2.12.2" }, "devDependencies": { "@eslint/js": "^9.13.0", @@ -1983,6 +1987,15 @@ "node": ">=6" } }, + "node_modules/@heroicons/react": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.1.5.tgz", + "integrity": "sha512-FuzFN+BsHa+7OxbvAERtgBTNeZpUjgM/MIizfVkSCL2/edriN0Hx/DWRCR//aPYwO5QX/YlgLGXk+E3PcfZwjA==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -4073,6 +4086,12 @@ "dev": true, "license": "MIT" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -8116,6 +8135,18 @@ "react": "^18.3.1" } }, + "node_modules/react-easy-swipe": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/react-easy-swipe/-/react-easy-swipe-0.0.21.tgz", + "integrity": "sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.8" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", @@ -8160,6 +8191,15 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-masonry-css": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/react-masonry-css/-/react-masonry-css-1.0.16.tgz", + "integrity": "sha512-KSW0hR2VQmltt/qAa3eXOctQDyOu7+ZBevtKgpNDSzT7k5LA/0XntNa9z9HKCdz3QlxmJHglTZ18e4sX4V8zZQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/react-remove-scroll": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", @@ -8207,6 +8247,17 @@ } } }, + "node_modules/react-responsive-carousel": { + "version": "3.2.23", + "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz", + "integrity": "sha512-pqJLsBaKHWJhw/ItODgbVoziR2z4lpcJg+YwmRlSk4rKH32VE633mAtZZ9kDXjy4wFO+pgUZmDKPsPe1fPmHCg==", + "license": "MIT", + "dependencies": { + "classnames": "^2.2.5", + "prop-types": "^15.5.8", + "react-easy-swipe": "^0.0.21" + } + }, "node_modules/react-router": { "version": "6.27.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", @@ -8262,6 +8313,34 @@ } } }, + "node_modules/react-tsparticles": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/react-tsparticles/-/react-tsparticles-2.12.2.tgz", + "integrity": "sha512-/nrEbyL8UROXKIMXe+f+LZN2ckvkwV2Qa+GGe/H26oEIc+wq/ybSG9REDwQiSt2OaDQGu0MwmA4BKmkL6wAWcA==", + "deprecated": "@tsparticles/react is the new version, please use that", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "tsparticles-engine": "^2.12.0" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -8954,6 +9033,28 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsparticles-engine": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/tsparticles-engine/-/tsparticles-engine-2.12.0.tgz", + "integrity": "sha512-ZjDIYex6jBJ4iMc9+z0uPe7SgBnmb6l+EJm83MPIsOny9lPpetMsnw/8YJ3xdxn8hV+S3myTpTN1CkOVmFv0QQ==", + "deprecated": "starting from tsparticles v3 the packages are now moved to @tsparticles/package-name instead of tsparticles-package-name", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/matteobruni" + }, + { + "type": "github", + "url": "https://github.com/sponsors/tsparticles" + }, + { + "type": "buymeacoffee", + "url": "https://www.buymeacoffee.com/matteobruni" + } + ], + "hasInstallScript": true, + "license": "MIT" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 6f405a1..826023b 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,15 @@ "@chakra-ui/react": "^2.1.1", "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", + "@heroicons/react": "^2.1.5", "firebase": "^11.0.1", "framer-motion": "^11.11.11", "next-themes": "^0.4.3", "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.3.0", + "react-masonry-css": "^1.0.16", + "react-responsive-carousel": "^3.2.23", "react-router-dom": "^6.27.0" }, "devDependencies": { From f01a833e53752718b105d72bad1763bbe0f9ac68 Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:48:49 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=E3=83=97=E3=83=AD=E3=83=95?= =?UTF-8?q?=E3=82=A3=E3=83=BC=E3=83=AB=E7=94=BB=E9=9D=A2=E3=80=81=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB=E7=B7=A8=E9=9B=86?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=AE=E3=83=AB=E3=83=BC=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.jsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/App.jsx b/src/App.jsx index b6392d1..aa3811b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,6 +8,8 @@ import Thread from './components/Thread'; import ThreadDetail from './components/ThreadDetail'; import CreateThread from './components/CreateThread'; import AdminPage from './components/AdminPage'; +import ProfileMasonry from './components/Profile/ProfileMasonry'; +import ProfileEdit from './components/ProfileEdit'; function App() { const location = useLocation(); @@ -25,6 +27,8 @@ function App() { } /> } /> } /> + } /> + } /> ); From d765cd50be2951a4e38e34bea7b840aafcf6bb34 Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:49:30 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E6=83=85=E5=A0=B1=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=95?= =?UTF-8?q?=E3=81=9B=E3=82=8BHero=E3=82=BB=E3=82=AF=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=8D?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=AE=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/HeroCard.jsx | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/components/HeroCard.jsx diff --git a/src/components/HeroCard.jsx b/src/components/HeroCard.jsx new file mode 100644 index 0000000..a3e97d3 --- /dev/null +++ b/src/components/HeroCard.jsx @@ -0,0 +1,35 @@ +import { motion } from 'framer-motion'; +import { PropTypes } from 'prop-types'; +import { Avatar, Text } from '@chakra-ui/react'; + +const HeroCard = ({ user }) => { + const cardVariants = { + hover: { + scale: 1.05, + transition: { duration: 0.2 }, + }, + }; + + HeroCard.propTypes = { + user: PropTypes.object.isRequired, + }; + + return ( + + + + {user.displayName || 'Anonymous'} + + {user.email} + + ); +}; + +export default HeroCard; From 7811e09687153b4a8f349f9a51df65293fbce80b Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:49:52 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=E3=83=97=E3=83=AD=E3=83=95?= =?UTF-8?q?=E3=82=A3=E3=83=BC=E3=83=AB=E7=B7=A8=E9=9B=86=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AE=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProfileEdit.jsx | 201 +++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 src/components/ProfileEdit.jsx diff --git a/src/components/ProfileEdit.jsx b/src/components/ProfileEdit.jsx new file mode 100644 index 0000000..30a9270 --- /dev/null +++ b/src/components/ProfileEdit.jsx @@ -0,0 +1,201 @@ +import { useState, useEffect } from 'react'; +import { auth, db } from '../config/firebase'; +import { updateProfile, updateEmail, updatePassword } from 'firebase/auth'; +import { doc, updateDoc } from 'firebase/firestore'; +import { + Box, + Button, + Input, + Flex, + Avatar, + Select, + FormControl, + FormLabel, + FormErrorMessage, + Text, +} from '@chakra-ui/react'; +import { motion } from 'framer-motion'; + +const MotionBox = motion(Box); +const MotionButton = motion(Button); +const MotionInput = motion(Input); +const MotionSelect = motion(Select); + +const ProfileEdit = () => { + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [icon, setIcon] = useState(''); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + + const iconOptions = ['icon1.png', 'icon2.png', 'icon3.png']; // アイコンの選択肢 + + useEffect(() => { + const user = auth.currentUser; + if (user) { + setUsername(user.displayName || ''); + setEmail(user.email || ''); + setIcon(user.photoURL || ''); + } + }, []); + + const handleUpdateProfile = async () => { + try { + const user = auth.currentUser; + if (user) { + await updateProfile(user, { + displayName: username, + photoURL: icon, + }); + await updateEmail(user, email); + if (password) { + await updatePassword(user, password); + } + const userDoc = doc(db, 'users', user.uid); + await updateDoc(userDoc, { + username, + email, + photoURL: icon, + }); + setSuccess('プロフィールが更新されました'); + setError(''); // 成功時はエラーメッセージをクリア + } + } catch (err) { + console.error(err); + setError('プロフィールの更新に失敗しました'); + setSuccess(''); // 失敗時は成功メッセージをクリア + } + }; + + return ( + + + + + setIcon(e.target.value)} + mb={4} + bg="rgba(255, 255, 255, 0.06)" + color="white" + _hover={{ bg: 'rgba(255, 255, 255, 0.08)' }} // commonStyles.inputHoverBg を直接指定 + _focus={{ + bg: 'rgba(255, 255, 255, 0.08)', // commonStyles.inputFocusBg を直接指定 + boxShadow: '0 0 0 2px pink.400', // commonStyles.inputFocusBoxShadow を直接指定 + borderColor: 'transparent', + }} + borderRadius="2xl" // commonStyles.borderRadius を直接指定 + fontSize="lg" // commonStyles.fontSize を直接指定 + fontWeight="bold" // commonStyles.fontWeight を直接指定 + transition="all 0.3s" + border="1px solid rgba(255, 255, 255, 0.1)" // commonStyles.border を直接指定 + boxShadow="0 0 30px rgba(236, 72, 153, 0.3)" + whileHover={{ + scale: 1.05, + boxShadow: '0 0 25px rgba(236, 72, 153, 0.5)', + }} + whileTap={{ scale: 0.95 }} + > + {iconOptions.map((option) => ( + + ))} + + + + {' '} + {/* errorがnullでない場合にisInvalidをtrueにする */} + + パスワード + + setPassword(e.target.value)} + placeholder="新しいパスワードを入力してください" + bg="rgba(255, 255, 255, 0.06)" + color="white" + _placeholder={{ color: 'gray.400' }} + _hover={{ bg: 'rgba(255, 255, 255, 0.08)' }} // commonStyles.inputHoverBg を直接指定 + _focus={{ + bg: 'rgba(255, 255, 255, 0.08)', // commonStyles.inputFocusBg を直接指定 + boxShadow: '0 0 0 2px pink.400', // commonStyles.inputFocusBoxShadow を直接指定 + borderColor: 'transparent', + }} + borderRadius="2xl" // commonStyles.borderRadius を直接指定 + fontSize="lg" // commonStyles.fontSize を直接指定 + fontWeight="bold" // commonStyles.fontWeight を直接指定 + transition="all 0.3s" + border="1px solid rgba(255, 255, 255, 0.1)" // commonStyles.border を直接指定 + boxShadow="0 0 30px rgba(236, 72, 153, 0.3)" + whileHover={{ + scale: 1.05, + boxShadow: '0 0 25px rgba(236, 72, 153, 0.5)', + }} + whileTap={{ scale: 0.95 }} + /> + {error}{' '} + {/* エラーメッセージを表示 */} + + {/* ... existing code for email and password ... */} + + 更新 + + {error && ( + + {error} + + )}{' '} + {/* エラーメッセージ */} + {success && ( + + {success} + + )}{' '} + {/* 成功メッセージ */} + + + ); +}; + +export default ProfileEdit; From 5e30dc607892e849f5ac2f8afe644cecf6acaac1 Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:50:16 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20masonry=E3=83=AC=E3=82=A4?= =?UTF-8?q?=E3=82=A2=E3=82=A6=E3=83=88=E3=81=AB=E4=BD=BF=E3=81=86=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=81=AEcss=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ProfileStyles.css | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/components/ProfileStyles.css diff --git a/src/components/ProfileStyles.css b/src/components/ProfileStyles.css new file mode 100644 index 0000000..f31cd9e --- /dev/null +++ b/src/components/ProfileStyles.css @@ -0,0 +1,24 @@ +.masonry-grid { + display: flex; + width: auto; + margin-left: -16px; +} + +.masonry-grid_column { + padding-left: 16px; + background-clip: padding-box; +} + +.masonry-grid_column>div { + margin-bottom: 16px; +} + +@media (max-width: 700px) { + .masonry-grid { + margin-left: -8px; + } + + .masonry-grid_column { + padding-left: 8px; + } +} \ No newline at end of file From 745a18f1b0793a9c42bf71265c86796e4ca3b2ed Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:50:37 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20=E3=82=A2=E3=83=81=E3=83=BC?= =?UTF-8?q?=E3=83=96=E3=83=A1=E3=83=B3=E3=83=88=E3=83=90=E3=83=83=E3=82=B8?= =?UTF-8?q?=E3=82=BB=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AB=E4=BD=BF?= =?UTF-8?q?=E3=81=86=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=AE=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Profile/AchievementCard.jsx | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/components/Profile/AchievementCard.jsx diff --git a/src/components/Profile/AchievementCard.jsx b/src/components/Profile/AchievementCard.jsx new file mode 100644 index 0000000..22641ed --- /dev/null +++ b/src/components/Profile/AchievementCard.jsx @@ -0,0 +1,66 @@ +import { Box, Grid, HStack, Text, Icon, VStack } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { TrophyIcon } from '@heroicons/react/24/outline'; + +import { PropTypes } from 'prop-types'; + +const MotionBox = motion(Box); + +const AchievementBadge = ({ icon: IconComponent, name }) => ( + + + + {name} + + +); + +AchievementBadge.propTypes = { + icon: PropTypes.elementType.isRequired, + name: PropTypes.string.isRequired, +}; + +const AchievementCard = ({ achievements }) => ( + + + + + アチーブメント + + + + {achievements.map((achievement) => ( + + ))} + + +); + +AchievementCard.propTypes = { + achievements: PropTypes.array.isRequired, +}; + +export default AchievementCard; From a1f7306b40ee0bcf530c27251dbd62f427a3f64e Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:51:42 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20=E9=81=8E=E5=8E=BB=E3=81=AB?= =?UTF-8?q?=E6=AE=BF=E5=A0=82=E5=85=A5=E3=82=8A=E3=81=97=E3=81=9F=E6=8A=95?= =?UTF-8?q?=E7=A8=BF=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=95=E3=81=9B=E3=82=8B?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=81=AE=E5=AE=9F=E8=A3=85(=E5=8B=95=E4=BD=9C=E7=A2=BA?= =?UTF-8?q?=E8=AA=8D=E4=B8=8D=E5=8F=AF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Profile/HallOfFameCard.jsx | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/components/Profile/HallOfFameCard.jsx diff --git a/src/components/Profile/HallOfFameCard.jsx b/src/components/Profile/HallOfFameCard.jsx new file mode 100644 index 0000000..ca58c0d --- /dev/null +++ b/src/components/Profile/HallOfFameCard.jsx @@ -0,0 +1,49 @@ +import { Box, HStack, Text, Icon } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { HiOutlineStar } from 'react-icons/hi'; +import { PropTypes } from 'prop-types'; + +const MotionBox = motion(Box); + +const HallOfFameCard = ({ post }) => ( + + + + + 殿堂入り投稿 + + + + {post.content} + + + + {new Date(post.createdAt?.toDate()).toLocaleDateString()} + + + + {post.likes} + + + +); + +HallOfFameCard.propTypes = { + post: PropTypes.object.isRequired, +}; + +export default HallOfFameCard; From 61a0efdb0b3b1252020e37e12e896c2ad3fe266c Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:52:04 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=E3=83=97=E3=83=AD=E3=83=95?= =?UTF-8?q?=E3=82=A3=E3=83=BC=E3=83=AB=E7=94=BB=E9=9D=A2=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=AE=E5=AE=9F?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Profile/ProfileMasonry.jsx | 160 ++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 src/components/Profile/ProfileMasonry.jsx diff --git a/src/components/Profile/ProfileMasonry.jsx b/src/components/Profile/ProfileMasonry.jsx new file mode 100644 index 0000000..dff986e --- /dev/null +++ b/src/components/Profile/ProfileMasonry.jsx @@ -0,0 +1,160 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import Masonry from 'react-masonry-css'; +import { Box } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { auth, db } from '../../config/firebase'; +import { collection, query, where, getDocs, orderBy } from 'firebase/firestore'; +import { + ChatBubbleBottomCenterTextIcon, + StarIcon, + FireIcon, + HeartIcon, + ChartBarIcon, + CheckBadgeIcon, +} from '@heroicons/react/24/outline'; +import UserInfoCard from './UserInfoCard'; +import StatCard from './StatCard'; +import AchievementCard from './AchievementCard'; +import HallOfFameCard from './HallOfFameCard'; +import '../ProfileStyles.css'; + +const MotionBox = motion(Box); + +const ProfileMasonry = () => { + const navigate = useNavigate(); + const [loading, setLoading] = useState(true); + const [user, setUser] = useState(null); + const [userStats, setUserStats] = useState({ + totalPosts: 0, + totalHallOfFame: 0, + streak: 0, + }); + const [achievements, setAchievements] = useState([]); + const [hallOfFamePosts, setHallOfFamePosts] = useState([]); + + const breakpointColumns = { + default: 3, + 1100: 2, + 700: 1, + }; + + const mockStats = { + totalPosts: 42, + totalHallOfFame: 10, + streak: 5, + }; + + const mockAchievements = [ + { id: 1, name: '初投稿', icon: ChatBubbleBottomCenterTextIcon }, + { id: 2, name: '殿堂入り', icon: StarIcon }, + { id: 3, name: '3日連続投稿', icon: FireIcon }, + { id: 4, name: '人気者', icon: HeartIcon }, + { id: 5, name: 'トレンド入り', icon: ChartBarIcon }, + { id: 6, name: '完璧な回答', icon: CheckBadgeIcon }, + ]; + + useEffect(() => { + const unsubscribe = auth.onAuthStateChanged((currentUser) => { + setUser(currentUser); + if (currentUser) { + fetchUserData(currentUser.uid); + } else { + setLoading(false); + navigate('/login'); + } + }); + + return () => unsubscribe(); + }, [navigate]); + + const fetchUserData = async (uid) => { + try { + // Stats取得 + const postsQuery = query( + collection(db, 'posts'), + where('userId', '==', uid), + orderBy('createdAt', 'desc') + ); + const hallOfFameQuery = query( + collection(db, 'posts'), + where('userId', '==', uid), + where('isHallOfFame', '==', true) + ); + const streakQuery = query( + collection(db, 'userStats'), + where('userId', '==', uid) + ); + + // Achievement取得 + const achievementsQuery = query( + collection(db, 'achievements'), + where('userId', '==', uid) + ); + + const [ + postsSnapshot, + hallOfFameSnapshot, + streakSnapshot, + achievementsSnapshot, + ] = await Promise.all([ + getDocs(postsQuery), + getDocs(hallOfFameQuery), + getDocs(streakQuery), + getDocs(achievementsQuery), + ]); + + setUserStats({ + totalPosts: postsSnapshot.size, + totalHallOfFame: hallOfFameSnapshot.size, + streak: streakSnapshot.docs[0]?.data()?.currentStreak || 0, + }); + + setAchievements( + achievementsSnapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })) + ); + + setHallOfFamePosts( + hallOfFameSnapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })) + ); + + setLoading(false); + } catch (error) { + console.error('Error fetching user data:', error); + setLoading(false); + } + }; + + if (loading) { + return ( + + Loading... + + ); + } + + return ( + + + + + + {hallOfFamePosts.map((post) => ( + + ))} + + + ); +}; + +export default ProfileMasonry; From 0b79d6753b7a45bd46876e566c5e984771a030b9 Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:52:38 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E7=B5=B1=E8=A8=88=E6=83=85=E5=A0=B1=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=95=E3=81=9B=E3=82=8B=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E3=81=AE=E5=AE=9F?= =?UTF-8?q?=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Profile/StatCard.jsx | 77 +++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/components/Profile/StatCard.jsx diff --git a/src/components/Profile/StatCard.jsx b/src/components/Profile/StatCard.jsx new file mode 100644 index 0000000..afffa63 --- /dev/null +++ b/src/components/Profile/StatCard.jsx @@ -0,0 +1,77 @@ +import { Box, Grid, VStack, Text, Icon } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { + ChatBubbleBottomCenterTextIcon, + StarIcon, + FireIcon, +} from '@heroicons/react/24/outline'; +import { PropTypes } from 'prop-types'; + +const MotionBox = motion(Box); + +const StatItem = ({ icon, value, label }) => ( + + + + {value} + + + {label} + + +); + +StatItem.propTypes = { + icon: PropTypes.elementType.isRequired, + value: PropTypes.number.isRequired, + label: PropTypes.string.isRequired, +}; + +const StatCard = ({ stats }) => ( + + + + + + + +); + +StatCard.propTypes = { + stats: PropTypes.object.isRequired, +}; +export default StatCard; From 77cc62f0174ba7ad5ed6baf6c8017b713fb7d1ad Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:55:01 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat:=20=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E6=83=85=E5=A0=B1=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=95?= =?UTF-8?q?=E3=81=9B=E3=82=8BCard=E3=82=B3=E3=83=B3=E3=83=9D=E3=83=BC?= =?UTF-8?q?=E3=83=8D=E3=83=B3=E3=83=88=E3=81=AE=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Profile/UserInfoCard.jsx | 61 +++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/components/Profile/UserInfoCard.jsx diff --git a/src/components/Profile/UserInfoCard.jsx b/src/components/Profile/UserInfoCard.jsx new file mode 100644 index 0000000..31d0eaa --- /dev/null +++ b/src/components/Profile/UserInfoCard.jsx @@ -0,0 +1,61 @@ +import { Link } from 'react-router-dom'; +import { Box, VStack, Text, IconButton, Avatar } from '@chakra-ui/react'; +import { motion } from 'framer-motion'; +import { HiPencil } from 'react-icons/hi'; +import { PropTypes } from 'prop-types'; + +const MotionBox = motion(Box); + +const UserInfoCard = ({ user }) => ( + + } + position="absolute" + top={4} + right={4} + colorScheme="purple" + variant="ghost" + borderRadius="full" + _hover={{ + bg: 'rgba(138, 43, 226, 0.2)', + }} + /> + + + + {user?.displayName} + + @{user?.uid?.slice(0, 8)} + + +); + +UserInfoCard.propTypes = { + user: PropTypes.object.isRequired, +}; + +export default UserInfoCard; From 2e7a89d4b874d4fe9314f1b534820414f7e74309 Mon Sep 17 00:00:00 2001 From: Jou Okuyama Date: Fri, 15 Nov 2024 16:56:29 +0900 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20photoURL=E3=81=8C=E7=A9=BA?= =?UTF-8?q?=E3=81=AE=E6=99=82=E3=81=ABdefaultPhotoURL=E3=82=92=E6=A0=BC?= =?UTF-8?q?=E7=B4=8D=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Profile/UserInfoCard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Profile/UserInfoCard.jsx b/src/components/Profile/UserInfoCard.jsx index 31d0eaa..c9aa664 100644 --- a/src/components/Profile/UserInfoCard.jsx +++ b/src/components/Profile/UserInfoCard.jsx @@ -36,7 +36,7 @@ const UserInfoCard = ({ user }) => ( Date: Fri, 15 Nov 2024 17:08:43 +0900 Subject: [PATCH 12/12] =?UTF-8?q?chore:=20lint=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8B=E4=BD=BF=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=AA=E3=81=84=E5=A4=89=E6=95=B0=E3=82=92=E3=82=B9=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Profile/ProfileMasonry.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/Profile/ProfileMasonry.jsx b/src/components/Profile/ProfileMasonry.jsx index dff986e..1229bce 100644 --- a/src/components/Profile/ProfileMasonry.jsx +++ b/src/components/Profile/ProfileMasonry.jsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import Masonry from 'react-masonry-css'; import { Box } from '@chakra-ui/react'; +// eslint-disable-next-line no-unused-vars import { motion } from 'framer-motion'; import { auth, db } from '../../config/firebase'; import { collection, query, where, getDocs, orderBy } from 'firebase/firestore'; @@ -19,17 +20,19 @@ import AchievementCard from './AchievementCard'; import HallOfFameCard from './HallOfFameCard'; import '../ProfileStyles.css'; -const MotionBox = motion(Box); +//const MotionBox = motion(Box); const ProfileMasonry = () => { const navigate = useNavigate(); const [loading, setLoading] = useState(true); const [user, setUser] = useState(null); + // eslint-disable-next-line no-unused-vars const [userStats, setUserStats] = useState({ totalPosts: 0, totalHallOfFame: 0, streak: 0, }); + // eslint-disable-next-line no-unused-vars const [achievements, setAchievements] = useState([]); const [hallOfFamePosts, setHallOfFamePosts] = useState([]);