diff --git a/config.yaml b/config.yaml index d83207aa..1099fcbb 100644 --- a/config.yaml +++ b/config.yaml @@ -11,8 +11,10 @@ server: #################################### User/Session ############################## user: - # github username + # username for superadmin, setted only in first run, can't be changed super_admin_username: "sunface" + # email for superadmin + super_admin_email: "cto@188.com" # a session is created when user login to im.dev, this session will be expired after X seconds session_expire: 259200 diff --git a/layouts/nav/nav.tsx b/layouts/nav/nav.tsx index aaa3a83f..5a306a90 100644 --- a/layouts/nav/nav.tsx +++ b/layouts/nav/nav.tsx @@ -93,11 +93,11 @@ function HeaderContent() { - + /> */} diff --git a/layouts/nav/vertical-nav.tsx b/layouts/nav/vertical-nav.tsx new file mode 100644 index 00000000..cf4c33f2 --- /dev/null +++ b/layouts/nav/vertical-nav.tsx @@ -0,0 +1,136 @@ +import { + chakra, + Flex, + HStack, + IconButton, + useColorModeValue, + useDisclosure, + useUpdateEffect, + Box, + VStack, + useMediaQuery + } from "@chakra-ui/react" + import siteConfig from "configs/site-config" + import { useViewportScroll } from "framer-motion" + import NextLink from "next/link" + import React from "react" + import { FaGithub, FaSearch } from "react-icons/fa" + import Logo, { LogoIcon } from "src/components/logo" + import { MobileNavButton, MobileNavContent } from "./mobile-nav" + import AlgoliaSearch from "src/components/search/algolia-search" + import { useRouter } from "next/router" + import { ReserveUrls } from "src/data/reserve-urls" + import Link from "next/link" + import DarkMode from "components/dark-mode" + import AccountMenu from "components/account-menu" + + const navLinks = [{ + title: '主页', + url: '/', + }, + { + title: '标签', + url: ReserveUrls.Tags, + }, + { + title: '学习资料', + url: ReserveUrls.Courses, + }, + ] + + + function HeaderContent() { + const router = useRouter() + const { asPath } = router + const mobileNav = useDisclosure() + + + const mobileNavBtnRef = React.useRef() + const [isLargerThan768] = useMediaQuery("(min-width: 768px)") + useUpdateEffect(() => { + mobileNavBtnRef.current?.focus() + }, [mobileNav.isOpen]) + + + return ( + <> + + + + + {isLargerThan768 ? : } + + + + + {navLinks.map(link => {link.title})} + + + + + } + /> + + + } + /> + + + + {/* */} + + + + + ) + } + + function VerticalNav(props) { + const ref = React.useRef() + + return ( + + + + + + ) + } + + export default VerticalNav + + \ No newline at end of file diff --git a/layouts/sidebar/sidebar.tsx b/layouts/sidebar/sidebar.tsx index 910e00a3..786c4f7e 100644 --- a/layouts/sidebar/sidebar.tsx +++ b/layouts/sidebar/sidebar.tsx @@ -1,4 +1,4 @@ -import { Box, Stack } from "@chakra-ui/react" +import { Box, Heading, Stack, VStack } from "@chakra-ui/react" import Card from "components/card" import { useRouter } from "next/router" import * as React from "react" @@ -22,11 +22,13 @@ export function SidebarContent(props) { ) } -const Sidebar = ({ routes, ...props }) => { +const Sidebar = ({ routes,title, ...props }) => { const { pathname } = useRouter() const ref = React.useRef(null) return ( + + {title} { sx={{ overscrollBehavior: "contain", }} - top="6.5rem" + top="8.5rem" pr="3" pb="6" pl="3" @@ -44,11 +46,12 @@ const Sidebar = ({ routes, ...props }) => { overflowY="auto" className="sidebar-content" flexShrink={0} - display={{ base: "none", md: "block" }} + // display={{ base: "none", md: "block" }} > + ) } diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx index b1451add..fb818169 100644 --- a/pages/[username]/index.tsx +++ b/pages/[username]/index.tsx @@ -1,23 +1,58 @@ -import { chakra } from "@chakra-ui/react" +import { Box, chakra, Flex, HStack, VStack ,Image, Heading, Text, Button, useColorModeValue} from "@chakra-ui/react" +import Card from "components/card" import Container from "components/container" import SEO from "components/seo" import siteConfig from "configs/site-config" +import useSession from "hooks/use-session" import Nav from "layouts/nav/nav" +import VerticalNav from "layouts/nav/vertical-nav" import PageContainer from "layouts/page-container" import { useRouter } from "next/router" -import React from "react" +import React, { useEffect, useState } from "react" +import { FaEdit, FaPlus } from "react-icons/fa" +import { User } from "src/types/session" +import { requestApi } from "utils/axios/request" const UserPage = () => { const router = useRouter() + const username = router.query.username + const session = useSession() + const [user,setUser]:[User,any] = useState(null) + const borderColor = useColorModeValue('white','transparent') + useEffect(() => { + if (username) { + requestApi.get(`/user/info/${username}`).then(res => setUser(res.data)) + } + },[username]) return ( <> - - {router.query.username}'s home - + + + + { + user && + + + + + + + {user.nickname} + {user.tagline} + {session?.user.id === user.id ? + + :} + + + + + } + + )} diff --git a/pages/admin/tags.tsx b/pages/admin/tags.tsx index 7f68f2da..161cd1ac 100644 --- a/pages/admin/tags.tsx +++ b/pages/admin/tags.tsx @@ -46,7 +46,7 @@ const PostsPage = () => { <> - + 标签列表({tags.length}) diff --git a/pages/editor/posts.tsx b/pages/editor/posts.tsx index 63c2211a..41803599 100644 --- a/pages/editor/posts.tsx +++ b/pages/editor/posts.tsx @@ -99,7 +99,7 @@ const PostsPage = () => { <> - + 文章列表({posts.length}) diff --git a/pages/settings/profile.tsx b/pages/settings/profile.tsx new file mode 100644 index 00000000..f006238f --- /dev/null +++ b/pages/settings/profile.tsx @@ -0,0 +1,298 @@ +import { Text, Box, Heading, Image, Center, Button, Flex, VStack, Divider, useToast, FormControl, FormLabel, FormHelperText, Input, FormErrorMessage, HStack, Wrap, useMediaQuery, Avatar, Textarea, } from "@chakra-ui/react" +import Card from "components/card" +import Nav from "layouts/nav/nav" +import PageContainer from "layouts/page-container" +import Sidebar from "layouts/sidebar/sidebar" +import React, { useEffect, useState } from "react" +import { adminLinks, settingLinks } from "src/data/links" +import { requestApi } from "utils/axios/request" +import TagCard from "components/posts/tag-edit-card" +import { Post } from "src/types/posts" +import { useRouter } from "next/router" +import Link from "next/link" +import { ReserveUrls } from "src/data/reserve-urls" +import { Tag } from "src/types/tag" +import { route } from "next/dist/next-server/server/router" +import { Field, Form, Formik } from "formik" +import useSession from "hooks/use-session" +import { config } from "utils/config" +var validator = require('validator'); + +const UserProfilePage = () => { + const [user, setUser] = useState(null) + const [isLargerThan1280] = useMediaQuery("(min-width: 768px)") + useEffect(() => { + requestApi.get("/user/self").then(res => setUser(res.data)) + }, []) + const router = useRouter() + const toast = useToast() + + const submitUser = async (values, _) => { + await requestApi.post(`/user/update`,values) + setUser(values) + toast({ + description: "更新成功", + status: "success", + duration: 2000, + isClosable: true, + }) + } + + function validateNickname(value) { + let error + if (!value?.trim()) { + error = "昵称不能为空" + } + + if (value?.length > config.user.nicknameMaxLen) { + error = `长度不能超过${config.user.nicknameMaxLen}` + } + + return error + } + + function validateEmail(value) { + let email = value?.trim() + let error + + if (email?.length > config.user.usernameMaxLen) { + error = `长度不能超过${config.user.usernameMaxLen}` + return error + } + + if (email) { + if (!validator.isEmail(email)) { + error = "Email格式不合法" + return error + } + } + return error + } + + + function validateUrl(value, canBeEmpty=true) { + let url = value?.trim() + let error + if (!canBeEmpty) { + if (!url) { + error = "url不能为空" + return error + } + } + + if (url) { + if (!validator.isURL(value)) { + error = "URL格式不合法" + return error + } + } + + return error + } + + function validateLen(value) { + let error + if (value?.length > config.commonMaxlen) { + error = `长度不能超过${config.commonMaxlen}` + } + + return error + } + + const Layout = isLargerThan1280 ? HStack : VStack + return ( + <> + + + + {user && + + {(props) => ( +
+ + + + + 基本信息 + + {({ field, form }) => ( + + 用户昵称 + + {form.errors.nickname} + + + )} + + + {({ field, form }) => ( + + 邮箱地址 + + {form.errors.email} + + + )} + + validateUrl(v, false)}> + {({ field, form }) => ( + + 头像设置 + + {form.errors.avatar} + {user.avatar && } + + + )} + + validateUrl(v, true)}> + {({ field, form }) => ( + + 封面图片 + + {form.errors.cover} + {user.cover && } + + + )} + + + + + About You + + {({ field, form }) => ( + + 一句话描述自己 + + {form.errors.tagline} + + )} + + + {({ field, form }) => ( + + Location + + {form.errors.location} + + )} + + + {({ field, form }) => ( + + Available for +