pull/52/head
codemystery 4 years ago
parent aeaf7aacd2
commit fe2090db57

@ -188,6 +188,11 @@ const UserPage = () => {
<chakra.a fontWeight="500" ml="2" href={user.website} target="_blank">{user.website}</chakra.a>
</HStack>}
{user.email && <HStack mt="1">
<chakra.span layerStyle="textSecondary" width="90px">: </chakra.span>
<chakra.a fontWeight="500" ml="2" href={user.email} target="_blank">{user.email}</chakra.a>
</HStack>}
<HStack mt="1">
<chakra.span layerStyle="textSecondary" width="90px">Joined: </chakra.span>
<chakra.span fontWeight="500" ml="2">{moment(user.created).fromNow()}</chakra.span>
@ -209,7 +214,7 @@ const UserPage = () => {
</Box>}
</Card>
{user.rawSkills?.length > 0 && <Card>
<Heading size="sm" layerStyle="textSecondary" fontWeight="500"></Heading>
<Heading size="sm" layerStyle="textSecondary" fontWeight="500">{user.type === IDType.User ? "擅长技术" : "组织技术栈"}</Heading>
<Wrap mt="4" p="1">
{
user.rawSkills.map(skill =>

@ -0,0 +1,301 @@
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, orgSettingLinks, settingLinks } from "src/data/links"
import { requestApi } from "utils/axios/request"
import { useRouter } from "next/router"
import { Field, Form, Formik } from "formik"
import { config } from "configs/config"
import Tags from "components/tags/tags"
var validator = require('validator');
const UserProfilePage = () => {
const [user, setUser] = useState(null)
const [skills, setSkills] = useState([])
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 (
<>
<PageContainer>
<Box display="flex">
<Sidebar routes={orgSettingLinks} width={["120px", "120px", "250px", "250px"]} height="fit-content" title="组织管理" />
{user && <VStack alignItems="left" ml="4" width="100%">
<Formik
initialValues={user}
onSubmit={submitUser}
>
{(props) => (
<Form>
<Card p={[2, 2, 6, 6]}>
<Layout spacing={isLargerThan1280 ? "8" : "6"} alignItems={isLargerThan1280 ? 'top' : 'left'}>
<Box width="100%">
<VStack alignItems="left" spacing="6">
<Heading size="sm"></Heading>
<Field name="nickname" validate={validateNickname}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.nickname && form.touched.nickname} >
<FormLabel></FormLabel>
<Input {...field} placeholder="enter your nick name" size="lg" />
<FormErrorMessage>{form.errors.nickname}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="email" validate={validateEmail}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.email && form.touched.email} >
<FormLabel></FormLabel>
<Input {...field} placeholder="" size="lg" />
<FormErrorMessage>{form.errors.email}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="avatar" validate={(v) => validateUrl(v, false)}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.avatar && form.touched.avatar} >
<FormLabel></FormLabel>
<Input {...field} placeholder="输入图片链接可以用github或postimg.cc当图片存储服务" size="lg" />
<FormErrorMessage>{form.errors.avatar}</FormErrorMessage>
{user.avatar && <Image width="120px" mt="4" src={user.avatar} />}
</FormControl>
)}
</Field>
<Field name="cover" validate={(v) => validateUrl(v, true)}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.cover && form.touched.cover} >
<FormLabel></FormLabel>
<Input {...field} placeholder="输入图片链接" size="lg" />
<FormErrorMessage>{form.errors.cover}</FormErrorMessage>
{user.cover && <Image width="100%" mt="4" src={user.cover} />}
</FormControl>
)}
</Field>
</VStack>
<VStack alignItems="left" spacing="6" mt="6">
<Heading size="sm">About You</Heading>
<Field name="tagline" validate={validateLen}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.tagline && form.touched.tagline} >
<FormLabel></FormLabel>
<Input {...field} placeholder="I'm dev, working for google now" size="lg" />
<FormErrorMessage>{form.errors.tagline}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="location" validate={validateLen}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.location && form.touched.location} >
<FormLabel>Location</FormLabel>
<Input {...field} placeholder="Califonia, US" size="lg" />
<FormErrorMessage>{form.errors.location}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="availFor" validate={validateLen}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.availFor && form.touched.availFor} >
<FormLabel>Available for</FormLabel>
<Textarea {...field} placeholder="I'm available for ..." size="lg" />
<FormErrorMessage>{form.errors.availFor}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="about" validate={validateLen}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.about && form.touched.about} >
<FormLabel></FormLabel>
<Textarea {...field} placeholder="give us more info about you" size="lg" />
<FormErrorMessage>{form.errors.about}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="skills" validate={validateLen}>
{({ field, form }) => (
<FormControl >
<FormLabel></FormLabel>
<Tags tags={user.skills} onChange={(v) => form.values.skills = v} size="lg"/>
</FormControl>
)}
</Field>
</VStack>
</Box>
<Box width="100%" >
<VStack alignItems="left" spacing="6">
<Heading size="sm"></Heading>
<Field name="website" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.website && form.touched.website} >
<FormLabel></FormLabel>
<Input {...field} placeholder="https://sunface.dev" size="lg" />
<FormErrorMessage>{form.errors.website}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="github" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.github && form.touched.github} >
<FormLabel>Github</FormLabel>
<Input {...field} placeholder="https://github.com/sunface" size="lg" />
<FormErrorMessage>{form.errors.github}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="twitter" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.twitter && form.touched.twitter} >
<FormLabel>Twitter</FormLabel>
<Input {...field} placeholder="https://twitter.com/imdotdev" size="lg" />
<FormErrorMessage>{form.errors.twitter}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="zhihu" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.zhihu && form.touched.zhihu} >
<FormLabel></FormLabel>
<Input {...field} placeholder="https://www.zhihu.com/people/iSunface" size="lg" />
<FormErrorMessage>{form.errors.zhihu}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="weibo" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.weibo && form.touched.weibo} >
<FormLabel></FormLabel>
<Input {...field} placeholder="https://weibo.com/2734382464" size="lg" />
<FormErrorMessage>{form.errors.weibo}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="facebook" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.facebook && form.touched.facebook} >
<FormLabel>Facebook</FormLabel>
<Input {...field} placeholder="" size="lg" />
<FormErrorMessage>{form.errors.facebook}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="stackoverflow" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.stackoverflow && form.touched.stackoverflow} >
<FormLabel>StackOverflow</FormLabel>
<Input {...field} placeholder="" size="lg" />
<FormErrorMessage>{form.errors.stackoverflow}</FormErrorMessage>
</FormControl>
)}
</Field>
</VStack>
</Box>
</Layout>
</Card>
<Box mt={6}>
<Button
colorScheme="cyan"
variant="outline"
type="submit"
_focus={null}
>
</Button>
</Box>
</Form>
)}
</Formik>
</VStack>}
</Box>
</PageContainer>
</>
)
}
export default UserProfilePage

@ -0,0 +1,296 @@
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, orgSettingLinks, settingLinks } from "src/data/links"
import { requestApi } from "utils/axios/request"
import { useRouter } from "next/router"
import { Field, Form, Formik } from "formik"
import { config } from "configs/config"
import Tags from "components/tags/tags"
var validator = require('validator');
const UserProfilePage = () => {
const [user, setUser] = useState(null)
const [skills, setSkills] = useState([])
const [isLargerThan1280] = useMediaQuery("(min-width: 768px)")
const router = useRouter()
useEffect(() => {
if (router.query.id) {
requestApi.get(`/user/info/${router.query.id}`).then(res => setUser(res.data))
}
}, [router.query.id])
const toast = useToast()
const submitUser = async (values, _) => {
await requestApi.post(`/org/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 (
<>
<PageContainer>
<Box display="flex">
<Sidebar routes={orgSettingLinks} width={["120px", "120px", "250px", "250px"]} height="fit-content" title="组织管理" />
{user && <VStack alignItems="left" ml="4" width="100%">
<Formik
initialValues={user}
onSubmit={submitUser}
>
{(props) => (
<Form>
<Card p={[2, 2, 6, 6]}>
<Layout spacing={isLargerThan1280 ? "8" : "6"} alignItems={isLargerThan1280 ? 'top' : 'left'}>
<Box width="100%">
<VStack alignItems="left" spacing="6">
<Heading size="sm"></Heading>
<Field name="nickname" validate={validateNickname}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.nickname && form.touched.nickname} >
<FormLabel></FormLabel>
<Input {...field} placeholder="enter your nick name" size="lg" />
<FormErrorMessage>{form.errors.nickname}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="email" validate={validateEmail}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.email && form.touched.email} >
<FormLabel></FormLabel>
<Input {...field} placeholder="" size="lg" />
<FormErrorMessage>{form.errors.email}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="avatar" validate={(v) => validateUrl(v, false)}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.avatar && form.touched.avatar} >
<FormLabel></FormLabel>
<Input {...field} placeholder="输入图片链接可以用github或postimg.cc当图片存储服务" size="lg" />
<FormErrorMessage>{form.errors.avatar}</FormErrorMessage>
{user.avatar && <Image width="120px" mt="4" src={user.avatar} />}
</FormControl>
)}
</Field>
<Field name="cover" validate={(v) => validateUrl(v, true)}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.cover && form.touched.cover} >
<FormLabel></FormLabel>
<Input {...field} placeholder="输入图片链接" size="lg" />
<FormErrorMessage>{form.errors.cover}</FormErrorMessage>
{user.cover && <Image width="100%" mt="4" src={user.cover} />}
</FormControl>
)}
</Field>
</VStack>
<VStack alignItems="left" spacing="6" mt="6">
<Heading size="sm">About You</Heading>
<Field name="tagline" validate={validateLen}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.tagline && form.touched.tagline} >
<FormLabel></FormLabel>
<Input {...field} placeholder="I'm dev, working for google now" size="lg" />
<FormErrorMessage>{form.errors.tagline}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="location" validate={validateLen}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.location && form.touched.location} >
<FormLabel>Location</FormLabel>
<Input {...field} placeholder="Califonia, US" size="lg" />
<FormErrorMessage>{form.errors.location}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="about" validate={validateLen}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.about && form.touched.about} >
<FormLabel></FormLabel>
<Textarea {...field} placeholder="give us more info about you" size="lg" />
<FormErrorMessage>{form.errors.about}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="skills" validate={validateLen}>
{({ field, form }) => (
<FormControl >
<FormLabel></FormLabel>
<Tags tags={user.skills} onChange={(v) => form.values.skills = v} size="lg"/>
</FormControl>
)}
</Field>
</VStack>
</Box>
<Box width="100%" >
<VStack alignItems="left" spacing="6">
<Heading size="sm"></Heading>
<Field name="website" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.website && form.touched.website} >
<FormLabel></FormLabel>
<Input {...field} placeholder="https://sunface.dev" size="lg" />
<FormErrorMessage>{form.errors.website}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="github" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.github && form.touched.github} >
<FormLabel>Github</FormLabel>
<Input {...field} placeholder="https://github.com/sunface" size="lg" />
<FormErrorMessage>{form.errors.github}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="twitter" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.twitter && form.touched.twitter} >
<FormLabel>Twitter</FormLabel>
<Input {...field} placeholder="https://twitter.com/imdotdev" size="lg" />
<FormErrorMessage>{form.errors.twitter}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="zhihu" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.zhihu && form.touched.zhihu} >
<FormLabel></FormLabel>
<Input {...field} placeholder="https://www.zhihu.com/people/iSunface" size="lg" />
<FormErrorMessage>{form.errors.zhihu}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="weibo" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.weibo && form.touched.weibo} >
<FormLabel></FormLabel>
<Input {...field} placeholder="https://weibo.com/2734382464" size="lg" />
<FormErrorMessage>{form.errors.weibo}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="facebook" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.facebook && form.touched.facebook} >
<FormLabel>Facebook</FormLabel>
<Input {...field} placeholder="" size="lg" />
<FormErrorMessage>{form.errors.facebook}</FormErrorMessage>
</FormControl>
)}
</Field>
<Field name="stackoverflow" validate={validateUrl}>
{({ field, form }) => (
<FormControl isInvalid={form.errors.stackoverflow && form.touched.stackoverflow} >
<FormLabel>StackOverflow</FormLabel>
<Input {...field} placeholder="" size="lg" />
<FormErrorMessage>{form.errors.stackoverflow}</FormErrorMessage>
</FormControl>
)}
</Field>
</VStack>
</Box>
</Layout>
</Card>
<Box mt={6}>
<Button
colorScheme="cyan"
variant="outline"
type="submit"
_focus={null}
>
</Button>
</Box>
</Form>
)}
</Formik>
</VStack>}
</Box>
</PageContainer>
</>
)
}
export default UserProfilePage

@ -13,6 +13,7 @@ import { isAdmin } from "utils/role"
import userCustomTheme from "theme/user-custom"
import { useRouter } from "next/router"
import Link from "next/link"
import { ReserveUrls } from "src/data/reserve-urls"
const UserOrgsPage = () => {
const [orgs, setOrgs]:[Org[],any] = useState([])
@ -99,7 +100,7 @@ const UserOrgsPage = () => {
</HStack>
</Link>
<Button variant="ghost">View</Button>
<Button variant="outline" size="md" onClick={() => router.push(`${ReserveUrls.Settings}/org/profile?id=${o.id}`)}>Manage</Button>
</Flex>)
}
</VStack>

@ -37,7 +37,22 @@ func CreateOrg(c *gin.Context) {
}
func UpdateOrg(c *gin.Context) {
u := &models.User{}
c.Bind(&u)
currentUser := user.CurrentUser(c)
if !org.IsOrgAdmin(currentUser.ID, u.ID) {
c.JSON(http.StatusForbidden, common.RespError(e.NoPermission))
return
}
err := user.UpdateUser(u)
if err != nil {
c.JSON(err.Status, common.RespError(err.Message))
return
}
c.JSON(http.StatusOK, common.RespSuccess(nil))
}
func GetOrgByUserID(c *gin.Context) {

@ -100,3 +100,14 @@ func GetMemberCount(orgID string) int {
return count
}
func IsOrgAdmin(userID string, orgID string) bool {
var role models.RoleType
err := db.Conn.QueryRow("SELECT role FROM org_member WHERE org_id=? and user_id=?", orgID, userID).Scan(&role)
if err != nil {
logger.Warn("check is org admin error", "error", err)
return false
}
return role.IsAdmin()
}

@ -101,6 +101,7 @@ func (s *Server) Start() error {
// org apis
r.POST("/org/create", IsLogin(), api.CreateOrg)
r.POST("/org/update", IsLogin(), api.UpdateOrg)
r.GET("/org/byUserID/:userID", api.GetOrgByUserID)
r.GET("/org/members/:id", api.GetOrgMembers)
// other apis

@ -93,6 +93,21 @@ export const settingLinks: Route[] = [{
},
]
export const orgSettingLinks: Route[] = [{
title: '组织信息',
path: `${ReserveUrls.Settings}/org/profile`,
icon: <FaUserCircle />,
disabled: false
},
{
title: '成员管理',
path: `${ReserveUrls.Settings}/org/members`,
icon: <FaUserFriends />,
disabled: false
},
]
export const navLinks = [{
title: '主页',

Loading…
Cancel
Save