diff --git a/pages/[username]/index.tsx b/pages/[username]/index.tsx
index e49997b0..1f65dcb0 100644
--- a/pages/[username]/index.tsx
+++ b/pages/[username]/index.tsx
@@ -188,6 +188,11 @@ const UserPage = () => {
{user.website}
}
+ {user.email &&
+ 联系邮箱:
+ {user.email}
+ }
+
Joined:
{moment(user.created).fromNow()}
@@ -209,7 +214,7 @@ const UserPage = () => {
}
{user.rawSkills?.length > 0 &&
- 擅长技能
+ {user.type === IDType.User ? "擅长技术" : "组织技术栈"}
{
user.rawSkills.map(skill =>
diff --git a/pages/settings/org/members.tsx b/pages/settings/org/members.tsx
new file mode 100644
index 00000000..a14a3caf
--- /dev/null
+++ b/pages/settings/org/members.tsx
@@ -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 (
+ <>
+
+
+
+ {user &&
+
+ {(props) => (
+
+ )}
+
+ }
+
+
+ >
+ )
+}
+export default UserProfilePage
+
diff --git a/pages/settings/org/profile.tsx b/pages/settings/org/profile.tsx
new file mode 100644
index 00000000..6f399602
--- /dev/null
+++ b/pages/settings/org/profile.tsx
@@ -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 (
+ <>
+
+
+
+ {user &&
+
+ {(props) => (
+
+ )}
+
+ }
+
+
+ >
+ )
+}
+export default UserProfilePage
+
diff --git a/pages/settings/orgs.tsx b/pages/settings/orgs.tsx
index 57aaa662..b80a6f53 100644
--- a/pages/settings/orgs.tsx
+++ b/pages/settings/orgs.tsx
@@ -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 = () => {
-
+
)
}
diff --git a/server/internal/api/org.go b/server/internal/api/org.go
index 1d8ec899..50a0655b 100644
--- a/server/internal/api/org.go
+++ b/server/internal/api/org.go
@@ -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) {
diff --git a/server/internal/org/org.go b/server/internal/org/org.go
index 0ba77360..7994bab7 100644
--- a/server/internal/org/org.go
+++ b/server/internal/org/org.go
@@ -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()
+}
diff --git a/server/internal/server.go b/server/internal/server.go
index ee05944c..3ed124ca 100644
--- a/server/internal/server.go
+++ b/server/internal/server.go
@@ -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
diff --git a/src/data/links.tsx b/src/data/links.tsx
index 5c36b479..20baaaef 100644
--- a/src/data/links.tsx
+++ b/src/data/links.tsx
@@ -93,6 +93,21 @@ export const settingLinks: Route[] = [{
},
]
+export const orgSettingLinks: Route[] = [{
+ title: '组织信息',
+ path: `${ReserveUrls.Settings}/org/profile`,
+ icon: ,
+ disabled: false
+},
+{
+ title: '成员管理',
+ path: `${ReserveUrls.Settings}/org/members`,
+ icon: ,
+ disabled: false
+},
+]
+
+
export const navLinks = [{
title: '主页',