优化
This commit is contained in:
414
client/shared/redux/slices/chat.ts
Normal file
414
client/shared/redux/slices/chat.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { ChatConverseInfo } from '../../model/converse';
|
||||
import type {
|
||||
ChatMessage,
|
||||
ChatMessageReaction,
|
||||
LocalChatMessage,
|
||||
SendMessagePayload,
|
||||
} from '../../model/message';
|
||||
import _uniqBy from 'lodash/uniqBy';
|
||||
import _orderBy from 'lodash/orderBy';
|
||||
import _last from 'lodash/last';
|
||||
import { isLocalMessageId, isValidStr } from '../../utils/string-helper';
|
||||
import type { InboxItem } from '../../model/inbox';
|
||||
|
||||
export interface ChatConverseState extends ChatConverseInfo {
|
||||
messages: LocalChatMessage[];
|
||||
hasFetchedHistory: boolean;
|
||||
/**
|
||||
* 判定是否还有更多的信息
|
||||
*/
|
||||
hasMoreMessage: boolean;
|
||||
}
|
||||
|
||||
export interface ChatState {
|
||||
currentConverseId: string | null; // 当前活跃的会话id
|
||||
converses: Record<string, ChatConverseState>; // <会话Id, 会话信息>
|
||||
ack: Record<string, string>; // <会话Id, 本地最后一条会话Id>
|
||||
inbox: InboxItem[];
|
||||
|
||||
/**
|
||||
* 会话最新消息mapping
|
||||
* <会话Id, 远程会话列表最后一条会话Id>
|
||||
*/
|
||||
lastMessageMap: Record<string, string>;
|
||||
}
|
||||
|
||||
const initialState: ChatState = {
|
||||
currentConverseId: null,
|
||||
converses: {},
|
||||
ack: {},
|
||||
inbox: [],
|
||||
lastMessageMap: {},
|
||||
};
|
||||
|
||||
const chatSlice = createSlice({
|
||||
name: 'chat',
|
||||
initialState,
|
||||
reducers: {
|
||||
updateCurrentConverseId(state, action: PayloadAction<string | null>) {
|
||||
state.currentConverseId = action.payload;
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置会话信息
|
||||
*/
|
||||
setConverseInfo(state, action: PayloadAction<ChatConverseInfo>) {
|
||||
const converseId = action.payload._id;
|
||||
|
||||
const originInfo = state.converses[converseId]
|
||||
? { ...state.converses[converseId] }
|
||||
: { messages: [], hasFetchedHistory: false, hasMoreMessage: true };
|
||||
|
||||
state.converses[converseId] = {
|
||||
...originInfo,
|
||||
...action.payload,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 追加消息
|
||||
* 会根据id进行一次排序以确保顺序
|
||||
*/
|
||||
appendConverseMessage(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
converseId: string;
|
||||
messages: ChatMessage[];
|
||||
}>
|
||||
) {
|
||||
const { converseId, messages } = action.payload;
|
||||
|
||||
if (!state.converses[converseId]) {
|
||||
// 没有会话信息, 请先设置会话信息
|
||||
console.error('没有会话信息, 请先设置会话信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTICE: 按照该规则能确保本地消息一直在最后,因为l大于任何ObjectId
|
||||
const newMessages = _orderBy(
|
||||
_uniqBy([...state.converses[converseId].messages, ...messages], '_id'),
|
||||
'_id',
|
||||
'asc'
|
||||
);
|
||||
|
||||
state.converses[converseId].messages = newMessages;
|
||||
|
||||
/**
|
||||
* 如果在当前会话中,则暂时不更新最后收到的消息的本地状态,避免可能出现的瞬间更新最后消息(出现小红点) 但是会立即已读(小红点消失)
|
||||
* 所以仅对非当前会话的消息进行更新最后消息
|
||||
*/
|
||||
if (state.currentConverseId !== converseId) {
|
||||
const lastMessageId = _last(
|
||||
newMessages.filter((m) => !isLocalMessageId(m._id))
|
||||
)?._id;
|
||||
if (isValidStr(lastMessageId)) {
|
||||
state.lastMessageMap[converseId] = lastMessageId;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 追加本地消息消息
|
||||
*/
|
||||
appendLocalMessage(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
author?: string;
|
||||
localMessageId: string;
|
||||
payload: SendMessagePayload;
|
||||
}>
|
||||
) {
|
||||
const { author, localMessageId, payload } = action.payload;
|
||||
const { converseId, groupId, content, meta } = payload;
|
||||
|
||||
if (!state.converses[converseId]) {
|
||||
// 没有会话信息, 请先设置会话信息
|
||||
console.error('没有会话信息, 请先设置会话信息');
|
||||
return;
|
||||
}
|
||||
|
||||
const message: LocalChatMessage = {
|
||||
_id: localMessageId,
|
||||
author,
|
||||
groupId,
|
||||
converseId,
|
||||
content,
|
||||
meta: meta as Record<string, unknown>,
|
||||
isLocal: true,
|
||||
};
|
||||
|
||||
const newMessages = _orderBy(
|
||||
_uniqBy([...state.converses[converseId].messages, message], '_id'),
|
||||
'_id',
|
||||
'asc'
|
||||
);
|
||||
|
||||
state.converses[converseId].messages = newMessages;
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化历史信息
|
||||
*/
|
||||
initialHistoryMessage(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
converseId: string;
|
||||
historyMessages: ChatMessage[];
|
||||
}>
|
||||
) {
|
||||
const { converseId, historyMessages } = action.payload;
|
||||
if (!state.converses[converseId]) {
|
||||
// 没有会话信息, 请先设置会话信息
|
||||
console.error('没有会话信息, 请先设置会话信息');
|
||||
return;
|
||||
}
|
||||
|
||||
chatSlice.caseReducers.appendConverseMessage(
|
||||
state,
|
||||
chatSlice.actions.appendConverseMessage({
|
||||
converseId,
|
||||
messages: [...historyMessages],
|
||||
})
|
||||
);
|
||||
|
||||
if (historyMessages.length < 50) {
|
||||
state.converses[converseId].hasMoreMessage = false;
|
||||
}
|
||||
|
||||
state.converses[converseId].hasFetchedHistory = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* 追加历史信息
|
||||
*/
|
||||
appendHistoryMessage(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
converseId: string;
|
||||
historyMessages: ChatMessage[];
|
||||
}>
|
||||
) {
|
||||
const { converseId, historyMessages } = action.payload;
|
||||
if (!state.converses[converseId]) {
|
||||
// 没有会话信息, 请先设置会话信息
|
||||
console.error('没有会话信息, 请先设置会话信息');
|
||||
return;
|
||||
}
|
||||
|
||||
chatSlice.caseReducers.appendConverseMessage(
|
||||
state,
|
||||
chatSlice.actions.appendConverseMessage({
|
||||
converseId,
|
||||
messages: [...historyMessages],
|
||||
})
|
||||
);
|
||||
|
||||
if (historyMessages.length < 50) {
|
||||
state.converses[converseId].hasMoreMessage = false;
|
||||
}
|
||||
state.converses[converseId].hasFetchedHistory = true;
|
||||
},
|
||||
|
||||
removeConverse(state, action: PayloadAction<{ converseId: string }>) {
|
||||
const { converseId } = action.payload;
|
||||
|
||||
if (!state.converses[converseId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete state.converses[converseId];
|
||||
},
|
||||
|
||||
/**
|
||||
* 清理所有会话信息
|
||||
*/
|
||||
clearAllConverses(state) {
|
||||
state.converses = {};
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置已读消息
|
||||
*/
|
||||
setConverseAck(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
converseId: string;
|
||||
lastMessageId: string;
|
||||
}>
|
||||
) {
|
||||
const { converseId, lastMessageId } = action.payload;
|
||||
state.ack[converseId] = lastMessageId;
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新消息信息
|
||||
*/
|
||||
updateMessageInfo(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
messageId?: string;
|
||||
message: Partial<LocalChatMessage>;
|
||||
}>
|
||||
) {
|
||||
const { message } = action.payload;
|
||||
const messageId = action.payload.messageId ?? message._id;
|
||||
const converseId = message.converseId;
|
||||
if (!converseId) {
|
||||
console.warn('Not found converse id,', message);
|
||||
return;
|
||||
}
|
||||
|
||||
const converse = state.converses[converseId];
|
||||
if (!converse) {
|
||||
console.warn('Not found converse,', converseId);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = converse.messages.findIndex((m) => m._id === messageId);
|
||||
if (index >= 0) {
|
||||
converse.messages[index] = {
|
||||
...converse.messages[index],
|
||||
...message,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除消息
|
||||
*/
|
||||
deleteMessageById(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
converseId: string;
|
||||
messageId: string;
|
||||
}>
|
||||
) {
|
||||
const { converseId, messageId } = action.payload;
|
||||
const converse = state.converses[converseId];
|
||||
if (!converse) {
|
||||
console.warn('Not found converse,', converseId);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = converse.messages.findIndex((m) => m._id === messageId);
|
||||
if (index >= 0) {
|
||||
converse.messages.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置远程的最后一条会话的id
|
||||
*/
|
||||
setLastMessageMap(
|
||||
state,
|
||||
action: PayloadAction<
|
||||
{
|
||||
converseId: string;
|
||||
lastMessageId: string;
|
||||
}[]
|
||||
>
|
||||
) {
|
||||
const list = action.payload;
|
||||
|
||||
if (Array.isArray(list)) {
|
||||
list.forEach((item) => {
|
||||
state.lastMessageMap[item.converseId] = item.lastMessageId;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 追加消息反应
|
||||
*/
|
||||
appendMessageReaction(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
converseId: string;
|
||||
messageId: string;
|
||||
reaction: ChatMessageReaction;
|
||||
}>
|
||||
) {
|
||||
const { converseId, messageId, reaction } = action.payload;
|
||||
const converse = state.converses[converseId];
|
||||
if (!converse) {
|
||||
console.warn('Not found converse,', converseId);
|
||||
return;
|
||||
}
|
||||
|
||||
const message = converse.messages.find((m) => m._id === messageId);
|
||||
if (!message) {
|
||||
console.warn('Not found message,', messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(message.reactions)) {
|
||||
message.reactions = [];
|
||||
}
|
||||
|
||||
message.reactions.push(reaction);
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除消息反应
|
||||
*/
|
||||
removeMessageReaction(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
converseId: string;
|
||||
messageId: string;
|
||||
reaction: ChatMessageReaction;
|
||||
}>
|
||||
) {
|
||||
const { converseId, messageId, reaction } = action.payload;
|
||||
const converse = state.converses[converseId];
|
||||
if (!converse) {
|
||||
console.warn('Not found converse,', converseId);
|
||||
return;
|
||||
}
|
||||
|
||||
const message = converse.messages.find((m) => m._id === messageId);
|
||||
if (!message) {
|
||||
console.warn('Not found message,', messageId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Array.isArray(message.reactions)) {
|
||||
message.reactions = [];
|
||||
}
|
||||
|
||||
const reactionIndex = message.reactions.findIndex(
|
||||
(r) => r.name === reaction.name && r.author === reaction.author
|
||||
);
|
||||
message.reactions.splice(reactionIndex, 1);
|
||||
},
|
||||
/**
|
||||
* 设置收件箱
|
||||
*/
|
||||
setInboxList(state, action: PayloadAction<InboxItem[]>) {
|
||||
const list = action.payload;
|
||||
state.inbox = list;
|
||||
},
|
||||
|
||||
/**
|
||||
* 增加收件箱项目
|
||||
*/
|
||||
appendInboxItem(state, action: PayloadAction<InboxItem>) {
|
||||
state.inbox.push(action.payload);
|
||||
},
|
||||
/**
|
||||
* 设置收件箱
|
||||
*/
|
||||
setInboxItemAck(state, action: PayloadAction<string>) {
|
||||
const inboxItemId = action.payload;
|
||||
const item = state.inbox.find((item) => item._id === inboxItemId);
|
||||
|
||||
if (item) {
|
||||
item.readed = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const chatActions = chatSlice.actions;
|
||||
export const chatReducer = chatSlice.reducer;
|
||||
33
client/shared/redux/slices/global.ts
Normal file
33
client/shared/redux/slices/global.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
export interface GlobalState {
|
||||
/**
|
||||
* 网络状态
|
||||
*/
|
||||
networkStatus: 'initial' | 'connected' | 'reconnecting' | 'disconnected';
|
||||
reconnectNum: number;
|
||||
}
|
||||
|
||||
const initialState: GlobalState = {
|
||||
networkStatus: 'initial',
|
||||
reconnectNum: 0,
|
||||
};
|
||||
|
||||
const globalSlice = createSlice({
|
||||
name: 'global',
|
||||
initialState,
|
||||
reducers: {
|
||||
setNetworkStatus(
|
||||
state,
|
||||
action: PayloadAction<GlobalState['networkStatus']>
|
||||
) {
|
||||
state.networkStatus = action.payload;
|
||||
},
|
||||
incReconnectNum(state) {
|
||||
state.reconnectNum += 1;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const globalActions = globalSlice.actions;
|
||||
export const globalReducer = globalSlice.reducer;
|
||||
103
client/shared/redux/slices/group.ts
Normal file
103
client/shared/redux/slices/group.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { GroupInfo } from '../../model/group';
|
||||
|
||||
export interface GroupState {
|
||||
groups: Record<string, GroupInfo>;
|
||||
}
|
||||
|
||||
const initialState: GroupState = {
|
||||
groups: {},
|
||||
};
|
||||
|
||||
const groupSlice = createSlice({
|
||||
name: 'group',
|
||||
initialState,
|
||||
reducers: {
|
||||
/**
|
||||
* 追加或更新群组信息
|
||||
*/
|
||||
appendGroups(state, action: PayloadAction<GroupInfo[]>) {
|
||||
const groups = action.payload;
|
||||
|
||||
for (const group of groups) {
|
||||
state.groups[group._id] = {
|
||||
...state.groups[group._id],
|
||||
...group,
|
||||
};
|
||||
}
|
||||
},
|
||||
updateGroup(state, action: PayloadAction<GroupInfo>) {
|
||||
const group = action.payload;
|
||||
const groupId = group._id;
|
||||
|
||||
if (state.groups[groupId]) {
|
||||
// NOTICE: updateGroup 只会去更新,不会去添加新的
|
||||
state.groups[groupId] = {
|
||||
...state.groups[groupId],
|
||||
...group,
|
||||
};
|
||||
}
|
||||
},
|
||||
removeGroup(state, action: PayloadAction<string>) {
|
||||
const groupId = action.payload;
|
||||
delete state.groups[groupId];
|
||||
},
|
||||
pinGroupPanel(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
groupId: string;
|
||||
panelId: string;
|
||||
}>
|
||||
) {
|
||||
const { groupId, panelId } = action.payload;
|
||||
|
||||
if (state.groups[groupId]) {
|
||||
// NOTICE: updateGroup 只会去更新,不会去添加新的
|
||||
state.groups[groupId] = {
|
||||
...state.groups[groupId],
|
||||
pinnedPanelId: panelId,
|
||||
};
|
||||
}
|
||||
},
|
||||
unpinGroupPanel(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
groupId: string;
|
||||
}>
|
||||
) {
|
||||
const { groupId } = action.payload;
|
||||
|
||||
if (state.groups[groupId]) {
|
||||
// NOTICE: updateGroup 只会去更新,不会去添加新的
|
||||
state.groups[groupId] = {
|
||||
...state.groups[groupId],
|
||||
pinnedPanelId: undefined,
|
||||
};
|
||||
}
|
||||
},
|
||||
updateGroupConfig(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
groupId: string;
|
||||
configName: string;
|
||||
configValue: any;
|
||||
}>
|
||||
) {
|
||||
const { groupId, configName, configValue } = action.payload;
|
||||
|
||||
const groupInfo = state.groups[groupId];
|
||||
if (groupInfo) {
|
||||
state.groups[groupId] = {
|
||||
...groupInfo,
|
||||
config: {
|
||||
...(groupInfo.config ?? {}),
|
||||
[configName]: configValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const groupActions = groupSlice.actions;
|
||||
export const groupReducer = groupSlice.reducer;
|
||||
22
client/shared/redux/slices/index.ts
Normal file
22
client/shared/redux/slices/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { combineReducers } from '@reduxjs/toolkit';
|
||||
import { userReducer } from './user';
|
||||
import { chatReducer } from './chat';
|
||||
import { groupReducer } from './group';
|
||||
import { uiReducer } from './ui';
|
||||
import { globalReducer } from './global';
|
||||
|
||||
export const appReducer = combineReducers({
|
||||
global: globalReducer,
|
||||
user: userReducer,
|
||||
chat: chatReducer,
|
||||
group: groupReducer,
|
||||
ui: uiReducer,
|
||||
});
|
||||
|
||||
export type AppState = ReturnType<typeof appReducer>;
|
||||
|
||||
export { globalActions } from './global';
|
||||
export { userActions } from './user';
|
||||
export { chatActions } from './chat';
|
||||
export { groupActions } from './group';
|
||||
export { uiActions } from './ui';
|
||||
28
client/shared/redux/slices/ui.ts
Normal file
28
client/shared/redux/slices/ui.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
export interface UIState {
|
||||
panelWinUrls: string[];
|
||||
}
|
||||
|
||||
const initialState: UIState = {
|
||||
panelWinUrls: [],
|
||||
};
|
||||
|
||||
const uiSlice = createSlice({
|
||||
name: 'ui',
|
||||
initialState,
|
||||
reducers: {
|
||||
addPanelWindowUrl(state, action: PayloadAction<{ url: string }>) {
|
||||
const panelUrl = action.payload.url;
|
||||
state.panelWinUrls.push(panelUrl);
|
||||
},
|
||||
deletePanelWindowUrl(state, action: PayloadAction<{ url: string }>) {
|
||||
const panelUrl = action.payload.url;
|
||||
const index = state.panelWinUrls.indexOf(panelUrl);
|
||||
state.panelWinUrls.splice(index, 1);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const uiActions = uiSlice.actions;
|
||||
export const uiReducer = uiSlice.reducer;
|
||||
101
client/shared/redux/slices/user.ts
Normal file
101
client/shared/redux/slices/user.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import _set from 'lodash/set';
|
||||
import type { UserLoginInfo } from '../../model/user';
|
||||
import type { FriendRequest } from '../../model/friend';
|
||||
|
||||
export interface FriendInfo {
|
||||
id: string;
|
||||
nickname?: string;
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
info: UserLoginInfo | null;
|
||||
friends: FriendInfo[]; // 好友的id列表
|
||||
friendRequests: FriendRequest[];
|
||||
}
|
||||
|
||||
const initialState: UserState = {
|
||||
info: null,
|
||||
friends: [],
|
||||
friendRequests: [],
|
||||
};
|
||||
|
||||
const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState,
|
||||
reducers: {
|
||||
setUserInfo(state, action: PayloadAction<UserLoginInfo>) {
|
||||
state.info = action.payload;
|
||||
},
|
||||
setUserInfoField(
|
||||
state,
|
||||
action: PayloadAction<{ fieldName: keyof UserLoginInfo; fieldValue: any }>
|
||||
) {
|
||||
const { fieldName, fieldValue } = action.payload;
|
||||
if (state.info === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_set(state.info, [fieldName], fieldValue);
|
||||
},
|
||||
setUserInfoExtra(
|
||||
state,
|
||||
action: PayloadAction<{ fieldName: string; fieldValue: any }>
|
||||
) {
|
||||
const { fieldName, fieldValue } = action.payload;
|
||||
if (state.info === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_set(state.info, ['extra', fieldName], fieldValue);
|
||||
},
|
||||
setFriendList(state, action: PayloadAction<FriendInfo[]>) {
|
||||
state.friends = action.payload;
|
||||
},
|
||||
setFriendRequests(state, action: PayloadAction<FriendRequest[]>) {
|
||||
state.friendRequests = action.payload;
|
||||
},
|
||||
appendFriend(state, action: PayloadAction<FriendInfo>) {
|
||||
if (state.friends.some((id) => id === action.payload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.friends.push(action.payload);
|
||||
},
|
||||
removeFriend(state, action: PayloadAction<string>) {
|
||||
const friendId = action.payload;
|
||||
const index = state.friends.findIndex((item) => item.id === friendId);
|
||||
if (index >= 0) {
|
||||
state.friends.splice(index, 1);
|
||||
}
|
||||
},
|
||||
appendFriendRequest(state, action: PayloadAction<FriendRequest>) {
|
||||
if (state.friendRequests.some(({ _id }) => _id === action.payload._id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.friendRequests.push(action.payload);
|
||||
},
|
||||
removeFriendRequest(state, action: PayloadAction<string>) {
|
||||
const index = state.friendRequests.findIndex(
|
||||
({ _id }) => _id === action.payload
|
||||
);
|
||||
if (index >= 0) {
|
||||
state.friendRequests.splice(index, 1);
|
||||
}
|
||||
},
|
||||
setFriendNickname(
|
||||
state,
|
||||
action: PayloadAction<{ friendId: string; nickname: string }>
|
||||
) {
|
||||
const { friendId, nickname } = action.payload;
|
||||
const target = state.friends.find((f) => f.id === friendId);
|
||||
if (target) {
|
||||
target.nickname = nickname;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const userActions = userSlice.actions;
|
||||
export const userReducer = userSlice.reducer;
|
||||
Reference in New Issue
Block a user