chore(frontend): update user module

This commit is contained in:
Steven 2023-11-20 23:14:16 +08:00
parent 832eb7cbf1
commit 35785a1a28
15 changed files with 87 additions and 138 deletions

View File

@ -3,6 +3,7 @@ import { isUndefined } from "lodash-es";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Role, User } from "@/types/proto/api/v2/user_service";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import useUserStore from "../stores/v1/user"; import useUserStore from "../stores/v1/user";
import Icon from "./Icon"; import Icon from "./Icon";
@ -14,11 +15,9 @@ interface Props {
} }
interface State { interface State {
userCreate: UserCreate; userCreate: Pick<User, "email" | "nickname" | "password" | "role">;
} }
const roles: Role[] = ["USER", "ADMIN"];
const CreateUserDialog: React.FC<Props> = (props: Props) => { const CreateUserDialog: React.FC<Props> = (props: Props) => {
const { onClose, onConfirm, user } = props; const { onClose, onConfirm, user } = props;
const { t } = useTranslation(); const { t } = useTranslation();
@ -28,7 +27,7 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => {
email: "", email: "",
nickname: "", nickname: "",
password: "", password: "",
role: "USER", role: Role.USER,
}, },
}); });
const requestState = useLoading(false); const requestState = useLoading(false);
@ -95,7 +94,7 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => {
try { try {
if (user) { if (user) {
const userPatch: UserPatch = { const userPatch: Partial<User> = {
id: user.id, id: user.id,
}; };
if (user.email !== state.userCreate.email) { if (user.email !== state.userCreate.email) {
@ -179,9 +178,8 @@ const CreateUserDialog: React.FC<Props> = (props: Props) => {
</span> </span>
<div className="w-full flex flex-row justify-start items-center text-base"> <div className="w-full flex flex-row justify-start items-center text-base">
<RadioGroup orientation="horizontal" value={state.userCreate.role} onChange={handleRoleInputChange}> <RadioGroup orientation="horizontal" value={state.userCreate.role} onChange={handleRoleInputChange}>
{roles.map((role) => ( <Radio value={Role.USER} label={"User"} />
<Radio key={role} value={role} label={role} /> <Radio value={Role.ADMIN} label={"Admin"} />
))}
</RadioGroup> </RadioGroup>
</div> </div>
</div> </div>

View File

@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import useWorkspaceStore from "@/stores/v1/workspace"; import useWorkspaceStore from "@/stores/v1/workspace";
import { PlanType } from "@/types/proto/api/v2/subscription_service"; import { PlanType } from "@/types/proto/api/v2/subscription_service";
import { Role } from "@/types/proto/api/v2/user_service";
import * as api from "../helpers/api"; import * as api from "../helpers/api";
import useUserStore from "../stores/v1/user"; import useUserStore from "../stores/v1/user";
import AboutDialog from "./AboutDialog"; import AboutDialog from "./AboutDialog";
@ -17,11 +18,12 @@ const Header: React.FC = () => {
const currentUser = useUserStore().getCurrentUser(); const currentUser = useUserStore().getCurrentUser();
const [showAboutDialog, setShowAboutDialog] = useState<boolean>(false); const [showAboutDialog, setShowAboutDialog] = useState<boolean>(false);
const profile = workspaceStore.profile; const profile = workspaceStore.profile;
const isAdmin = currentUser.role === "ADMIN"; const isAdmin = currentUser.role === Role.ADMIN;
const shouldShowRouterSwitch = location.pathname === "/" || location.pathname === "/collections"; const shouldShowRouterSwitch = location.pathname === "/" || location.pathname === "/collections";
const handleSignOutButtonClick = async () => { const handleSignOutButtonClick = async () => {
await api.signout(); await api.signout();
localStorage.removeItem("userId");
window.location.href = "/auth"; window.location.href = "/auth";
}; };

View File

@ -1,6 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { Role } from "@/types/proto/api/v2/user_service";
import { shortcutService } from "../services"; import { shortcutService } from "../services";
import useUserStore from "../stores/v1/user"; import useUserStore from "../stores/v1/user";
import { showCommonDialog } from "./Alert"; import { showCommonDialog } from "./Alert";
@ -20,7 +21,7 @@ const ShortcutActionsDropdown = (props: Props) => {
const currentUser = useUserStore().getCurrentUser(); const currentUser = useUserStore().getCurrentUser();
const [showEditDrawer, setShowEditDrawer] = useState<boolean>(false); const [showEditDrawer, setShowEditDrawer] = useState<boolean>(false);
const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false); const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false);
const havePermission = currentUser.role === "ADMIN" || shortcut.creatorId === currentUser.id; const havePermission = currentUser.role === Role.ADMIN || shortcut.creatorId === currentUser.id;
const handleDeleteShortcutButtonClick = (shortcut: Shortcut) => { const handleDeleteShortcutButtonClick = (shortcut: Shortcut) => {
showCommonDialog({ showCommonDialog({

View File

@ -1,6 +1,7 @@
import { Button } from "@mui/joy"; import { Button } from "@mui/joy";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Role } from "@/types/proto/api/v2/user_service";
import useUserStore from "../../stores/v1/user"; import useUserStore from "../../stores/v1/user";
import ChangePasswordDialog from "../ChangePasswordDialog"; import ChangePasswordDialog from "../ChangePasswordDialog";
import EditUserinfoDialog from "../EditUserinfoDialog"; import EditUserinfoDialog from "../EditUserinfoDialog";
@ -10,7 +11,7 @@ const AccountSection: React.FC = () => {
const currentUser = useUserStore().getCurrentUser(); const currentUser = useUserStore().getCurrentUser();
const [showEditUserinfoDialog, setShowEditUserinfoDialog] = useState<boolean>(false); const [showEditUserinfoDialog, setShowEditUserinfoDialog] = useState<boolean>(false);
const [showChangePasswordDialog, setShowChangePasswordDialog] = useState<boolean>(false); const [showChangePasswordDialog, setShowChangePasswordDialog] = useState<boolean>(false);
const isAdmin = currentUser.role === "ADMIN"; const isAdmin = currentUser.role === Role.ADMIN;
return ( return (
<> <>

View File

@ -2,6 +2,7 @@ import { Button, IconButton } from "@mui/joy";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { User } from "@/types/proto/api/v2/user_service";
import useUserStore from "../../stores/v1/user"; import useUserStore from "../../stores/v1/user";
import { showCommonDialog } from "../Alert"; import { showCommonDialog } from "../Alert";
import CreateUserDialog from "../CreateUserDialog"; import CreateUserDialog from "../CreateUserDialog";

View File

@ -1,5 +1,4 @@
import axios from "axios"; import axios from "axios";
import { userServiceClient } from "@/grpcweb";
export function signin(email: string, password: string) { export function signin(email: string, password: string) {
return axios.post<User>("/api/v1/auth/signin", { return axios.post<User>("/api/v1/auth/signin", {
@ -20,30 +19,6 @@ export function signout() {
return axios.post("/api/v1/auth/logout"); return axios.post("/api/v1/auth/logout");
} }
export function getMyselfUser() {
return axios.get<User>("/api/v1/user/me");
}
export function getUserList() {
return axios.get<User[]>("/api/v1/user");
}
export function getUserById(id: number) {
return axios.get<User>(`/api/v1/user/${id}`);
}
export function createUser(userCreate: UserCreate) {
return axios.post<User>("/api/v1/user", userCreate);
}
export function patchUser(userPatch: UserPatch) {
return axios.patch<User>(`/api/v1/user/${userPatch.id}`, userPatch);
}
export function deleteUser(userId: UserId) {
return userServiceClient.deleteUser({ id: userId });
}
export function getShortcutList(shortcutFind?: ShortcutFind) { export function getShortcutList(shortcutFind?: ShortcutFind) {
const queryList = []; const queryList = [];
if (shortcutFind?.tag) { if (shortcutFind?.tag) {

View File

@ -6,6 +6,7 @@ import toast from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useLoaderData } from "react-router-dom"; import { useLoaderData } from "react-router-dom";
import useNavigateTo from "@/hooks/useNavigateTo"; import useNavigateTo from "@/hooks/useNavigateTo";
import { Role } from "@/types/proto/api/v2/user_service";
import { showCommonDialog } from "../components/Alert"; import { showCommonDialog } from "../components/Alert";
import AnalyticsView from "../components/AnalyticsView"; import AnalyticsView from "../components/AnalyticsView";
import CreateShortcutDrawer from "../components/CreateShortcutDrawer"; import CreateShortcutDrawer from "../components/CreateShortcutDrawer";
@ -31,7 +32,7 @@ const ShortcutDetail = () => {
showEditDrawer: false, showEditDrawer: false,
}); });
const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false); const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false);
const havePermission = currentUser.role === "ADMIN" || shortcut.creatorId === currentUser.id; const havePermission = currentUser.role === Role.ADMIN || shortcut.creatorId === currentUser.id;
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`); const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
const favicon = getFaviconWithGoogleS2(shortcut.link); const favicon = getFaviconWithGoogleS2(shortcut.link);

View File

@ -3,16 +3,12 @@ import React, { FormEvent, useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import useNavigateTo from "@/hooks/useNavigateTo";
import useWorkspaceStore from "@/stores/v1/workspace"; import useWorkspaceStore from "@/stores/v1/workspace";
import * as api from "../helpers/api"; import * as api from "../helpers/api";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import useUserStore from "../stores/v1/user";
const SignIn: React.FC = () => { const SignIn: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigateTo = useNavigateTo();
const userStore = useUserStore();
const workspaceStore = useWorkspaceStore(); const workspaceStore = useWorkspaceStore();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
@ -20,11 +16,7 @@ const SignIn: React.FC = () => {
const allowConfirm = email.length > 0 && password.length > 0; const allowConfirm = email.length > 0 && password.length > 0;
useEffect(() => { useEffect(() => {
if (userStore.getCurrentUser()) { localStorage.removeItem("userId");
return navigateTo("/", {
replace: true,
});
}
if (workspaceStore.profile.mode === "demo") { if (workspaceStore.profile.mode === "demo") {
setEmail("steven@yourselfhosted.com"); setEmail("steven@yourselfhosted.com");
@ -50,12 +42,10 @@ const SignIn: React.FC = () => {
try { try {
actionBtnLoadingState.setLoading(); actionBtnLoadingState.setLoading();
await api.signin(email, password); const { data: user } = await api.signin(email, password);
const user = await userStore.fetchCurrentUser();
if (user) { if (user) {
navigateTo("/", { localStorage.setItem("userId", `${user.id}`);
replace: true, window.location.href = "/";
});
} else { } else {
toast.error("Signin failed"); toast.error("Signin failed");
} }

View File

@ -7,12 +7,10 @@ import useNavigateTo from "@/hooks/useNavigateTo";
import useWorkspaceStore from "@/stores/v1/workspace"; import useWorkspaceStore from "@/stores/v1/workspace";
import * as api from "../helpers/api"; import * as api from "../helpers/api";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import useUserStore from "../stores/v1/user";
const SignUp: React.FC = () => { const SignUp: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigateTo = useNavigateTo(); const navigateTo = useNavigateTo();
const userStore = useUserStore();
const workspaceStore = useWorkspaceStore(); const workspaceStore = useWorkspaceStore();
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [nickname, setNickname] = useState(""); const [nickname, setNickname] = useState("");
@ -21,11 +19,7 @@ const SignUp: React.FC = () => {
const allowConfirm = email.length > 0 && nickname.length > 0 && password.length > 0; const allowConfirm = email.length > 0 && nickname.length > 0 && password.length > 0;
useEffect(() => { useEffect(() => {
if (userStore.getCurrentUser()) { localStorage.removeItem("userId");
return navigateTo("/", {
replace: true,
});
}
if (!workspaceStore.profile.enableSignup) { if (!workspaceStore.profile.enableSignup) {
return navigateTo("/auth", { return navigateTo("/auth", {
@ -57,12 +51,10 @@ const SignUp: React.FC = () => {
try { try {
actionBtnLoadingState.setLoading(); actionBtnLoadingState.setLoading();
await api.signup(email, nickname, password); const { data: user } = await api.signup(email, nickname, password);
const user = await userStore.fetchCurrentUser();
if (user) { if (user) {
navigateTo("/", { localStorage.setItem("userId", `${user.id}`);
replace: true, window.location.href = "/";
});
} else { } else {
toast.error("Signup failed"); toast.error("Signup failed");
} }

View File

@ -7,13 +7,14 @@ import { subscriptionServiceClient } from "@/grpcweb";
import { stringifyPlanType } from "@/stores/v1/subscription"; import { stringifyPlanType } from "@/stores/v1/subscription";
import useWorkspaceStore from "@/stores/v1/workspace"; import useWorkspaceStore from "@/stores/v1/workspace";
import { PlanType } from "@/types/proto/api/v2/subscription_service"; import { PlanType } from "@/types/proto/api/v2/subscription_service";
import { Role } from "@/types/proto/api/v2/user_service";
import useUserStore from "../stores/v1/user"; import useUserStore from "../stores/v1/user";
const SubscriptionSetting: React.FC = () => { const SubscriptionSetting: React.FC = () => {
const workspaceStore = useWorkspaceStore(); const workspaceStore = useWorkspaceStore();
const currentUser = useUserStore().getCurrentUser(); const currentUser = useUserStore().getCurrentUser();
const [licenseKey, setLicenseKey] = useState<string>(""); const [licenseKey, setLicenseKey] = useState<string>("");
const isAdmin = currentUser.role === "ADMIN"; const isAdmin = currentUser.role === Role.ADMIN;
const profile = workspaceStore.profile; const profile = workspaceStore.profile;
const handleUpdateLicenseKey = async () => { const handleUpdateLicenseKey = async () => {

View File

@ -4,6 +4,7 @@ import { Link } from "react-router-dom";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
import { stringifyPlanType } from "@/stores/v1/subscription"; import { stringifyPlanType } from "@/stores/v1/subscription";
import useWorkspaceStore from "@/stores/v1/workspace"; import useWorkspaceStore from "@/stores/v1/workspace";
import { Role } from "@/types/proto/api/v2/user_service";
import MemberSection from "../components/setting/MemberSection"; import MemberSection from "../components/setting/MemberSection";
import WorkspaceSection from "../components/setting/WorkspaceSection"; import WorkspaceSection from "../components/setting/WorkspaceSection";
import useUserStore from "../stores/v1/user"; import useUserStore from "../stores/v1/user";
@ -11,7 +12,7 @@ import useUserStore from "../stores/v1/user";
const WorkspaceSetting: React.FC = () => { const WorkspaceSetting: React.FC = () => {
const workspaceStore = useWorkspaceStore(); const workspaceStore = useWorkspaceStore();
const currentUser = useUserStore().getCurrentUser(); const currentUser = useUserStore().getCurrentUser();
const isAdmin = currentUser.role === "ADMIN"; const isAdmin = currentUser.role === Role.ADMIN;
const profile = workspaceStore.profile; const profile = workspaceStore.profile;
useEffect(() => { useEffect(() => {

View File

@ -1,33 +1,25 @@
import { create } from "zustand"; import { create } from "zustand";
import { userSettingServiceClient } from "@/grpcweb"; import { userServiceClient, userSettingServiceClient } from "@/grpcweb";
import { User } from "@/types/proto/api/v2/user_service";
import { UserSetting } from "@/types/proto/api/v2/user_setting_service"; import { UserSetting } from "@/types/proto/api/v2/user_setting_service";
import * as api from "../../helpers/api";
const convertResponseModelUser = (user: User): User => {
return {
...user,
createdTs: user.createdTs * 1000,
updatedTs: user.updatedTs * 1000,
};
};
interface UserState { interface UserState {
userMapById: Record<UserId, User>; userMapById: Record<number, User>;
userSettingMapById: Record<UserId, UserSetting>; userSettingMapById: Record<number, UserSetting>;
currentUserId?: UserId; currentUserId?: number;
// User related actions. // User related actions.
fetchUserList: () => Promise<User[]>; fetchUserList: () => Promise<User[]>;
fetchCurrentUser: () => Promise<User>; fetchCurrentUser: () => Promise<User>;
getOrFetchUserById: (id: UserId) => Promise<User>; getOrFetchUserById: (id: number) => Promise<User>;
getUserById: (id: UserId) => User; getUserById: (id: number) => User;
getCurrentUser: () => User; getCurrentUser: () => User;
createUser: (userCreate: UserCreate) => Promise<User>; createUser: (create: Partial<User>) => Promise<User>;
patchUser: (userPatch: UserPatch) => Promise<void>; patchUser: (userPatch: Partial<User>) => Promise<void>;
deleteUser: (id: UserId) => Promise<void>; deleteUser: (id: number) => Promise<void>;
// User setting related actions. // User setting related actions.
fetchUserSetting: (userId: UserId) => Promise<UserSetting>; fetchUserSetting: (userId: number) => Promise<UserSetting>;
updateUserSetting: (userSetting: UserSetting, updateMask: string[]) => Promise<UserSetting>; updateUserSetting: (userSetting: UserSetting, updateMask: string[]) => Promise<UserSetting>;
getCurrentUserSetting: () => UserSetting; getCurrentUserSetting: () => UserSetting;
} }
@ -36,65 +28,88 @@ const useUserStore = create<UserState>()((set, get) => ({
userMapById: {}, userMapById: {},
userSettingMapById: {}, userSettingMapById: {},
fetchUserList: async () => { fetchUserList: async () => {
const { data: userList } = await api.getUserList(); const { users } = await userServiceClient.listUsers({});
const userMap = get().userMapById; const userMap = get().userMapById;
userList.forEach((user) => { users.forEach((user) => {
userMap[user.id] = convertResponseModelUser(user); userMap[user.id] = user;
}); });
set(userMap); set(userMap);
return userList; return users;
}, },
fetchCurrentUser: async () => { fetchCurrentUser: async () => {
const { data } = await api.getMyselfUser(); const userId = localStorage.getItem("userId");
const user = convertResponseModelUser(data); if (!userId) {
throw new Error("User id not found in localStorage");
}
const { user } = await userServiceClient.getUser({
id: Number(userId),
});
if (!user) {
throw new Error("User not found");
}
const userMap = get().userMapById; const userMap = get().userMapById;
userMap[user.id] = user; userMap[user.id] = user;
set({ userMapById: userMap, currentUserId: user.id }); set({ userMapById: userMap, currentUserId: user.id });
return user; return user;
}, },
getOrFetchUserById: async (id: UserId) => { getOrFetchUserById: async (id: number) => {
const userMap = get().userMapById; const userMap = get().userMapById;
if (userMap[id]) { if (userMap[id]) {
return userMap[id] as User; return userMap[id] as User;
} }
const { data } = await api.getUserById(id); const { user } = await userServiceClient.getUser({
const user = convertResponseModelUser(data); id: Number(id),
});
if (!user) {
throw new Error("User not found");
}
userMap[id] = user; userMap[id] = user;
set(userMap); set(userMap);
return user; return user;
}, },
createUser: async (userCreate: UserCreate) => { createUser: async (userCreate: Partial<User>) => {
const { data } = await api.createUser(userCreate); const { user } = await userServiceClient.createUser({
const user = convertResponseModelUser(data); user: userCreate,
});
if (!user) {
throw new Error("User not found");
}
const userMap = get().userMapById; const userMap = get().userMapById;
userMap[user.id] = user; userMap[user.id] = user;
set(userMap); set(userMap);
return user; return user;
}, },
patchUser: async (userPatch: UserPatch) => { patchUser: async (userPatch: Partial<User>) => {
const { data } = await api.patchUser(userPatch); const { user } = await userServiceClient.updateUser({
const user = convertResponseModelUser(data); user: userPatch,
updateMask: ["email", "nickname"],
});
if (!user) {
throw new Error("User not found");
}
const userMap = get().userMapById; const userMap = get().userMapById;
userMap[user.id] = user; userMap[user.id] = user;
set(userMap); set(userMap);
}, },
deleteUser: async (userId: UserId) => { deleteUser: async (userId: number) => {
await api.deleteUser(userId); await userServiceClient.deleteUser({
id: userId,
});
const userMap = get().userMapById; const userMap = get().userMapById;
delete userMap[userId]; delete userMap[userId];
set(userMap); set(userMap);
}, },
getUserById: (id: UserId) => { getUserById: (id: number) => {
const userMap = get().userMapById; const userMap = get().userMapById;
return userMap[id] as User; return userMap[id] as User;
}, },
getCurrentUser: () => { getCurrentUser: () => {
const userMap = get().userMapById; const userMap = get().userMapById;
const currentUserId = get().currentUserId; const currentUserId = get().currentUserId;
return userMap[currentUserId as UserId]; return userMap[currentUserId as number];
}, },
fetchUserSetting: async (userId: UserId) => { fetchUserSetting: async (userId: number) => {
const userSetting = ( const userSetting = (
await userSettingServiceClient.getUserSetting({ await userSettingServiceClient.getUserSetting({
id: userId, id: userId,
@ -122,7 +137,7 @@ const useUserStore = create<UserState>()((set, get) => ({
getCurrentUserSetting: () => { getCurrentUserSetting: () => {
const userSettingMap = get().userSettingMapById; const userSettingMap = get().userSettingMapById;
const currentUserId = get().currentUserId; const currentUserId = get().currentUserId;
return userSettingMap[currentUserId as UserId]; return userSettingMap[currentUserId as number];
}, },
})); }));

View File

@ -1,5 +1,6 @@
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
import { User } from "@/types/proto/api/v2/user_service";
export interface Filter { export interface Filter {
tab?: string; tab?: string;

View File

@ -11,7 +11,7 @@ interface OpenGraphMetadata {
interface Shortcut { interface Shortcut {
id: ShortcutId; id: ShortcutId;
creatorId: UserId; creatorId: number;
creator: User; creator: User;
createdTs: TimeStamp; createdTs: TimeStamp;
updatedTs: TimeStamp; updatedTs: TimeStamp;

View File

@ -1,36 +1,6 @@
type UserId = number;
type Role = "ADMIN" | "USER";
interface User { interface User {
id: UserId; id: number;
createdTs: TimeStamp;
updatedTs: TimeStamp;
rowStatus: RowStatus;
email: string; email: string;
nickname: string; nickname: string;
role: Role; role: "ADMIN" | "USER";
}
interface UserCreate {
email: string;
nickname: string;
password: string;
role: Role;
}
interface UserPatch {
id: UserId;
rowStatus?: RowStatus;
email?: string;
nickname?: string;
password?: string;
role?: Role;
}
interface UserDelete {
id: UserId;
} }