Add current user object that holds user session values instead of standalone getters. Closes #2050

This commit is contained in:
Gabe Kangas 2022-10-10 16:26:09 -07:00
parent d94723bd3a
commit 80a012a3c7
No known key found for this signature in database
GPG Key ID: 9A56337728BC81EA
12 changed files with 103 additions and 98 deletions

View File

@ -9,5 +9,6 @@
"linkify",
"paypal",
"toggleswitch"
]
],
"deepscan.enable": true
}

View File

@ -12,7 +12,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import dynamic from 'next/dynamic';
import {
chatVisibleToggleAtom,
chatDisplayNameAtom,
currentUserAtom,
appStateAtom,
} from '../../stores/ClientConfigStore';
import styles from './UserDropdown.module.scss';
@ -34,12 +34,19 @@ export type UserDropdownProps = {
};
export const UserDropdown: FC<UserDropdownProps> = ({ username: defaultUsername = undefined }) => {
const username = defaultUsername || useRecoilValue(chatDisplayNameAtom);
const [showNameChangeModal, setShowNameChangeModal] = useState<boolean>(false);
const [showAuthModal, setShowAuthModal] = useState<boolean>(false);
const [chatToggleVisible, setChatToggleVisible] = useRecoilState(chatVisibleToggleAtom);
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
const currentUser = useRecoilValue(currentUserAtom);
if (!currentUser) {
return null;
}
const { displayName } = currentUser;
const username = defaultUsername || displayName;
const toggleChatVisibility = () => {
setChatToggleVisible(!chatToggleVisible);
};

View File

@ -9,7 +9,7 @@ import IndieAuthIcon from '../../../assets/images/indieauth.png';
import styles from './AuthModal.module.scss';
import {
chatDisplayNameAtom,
currentUserAtom,
chatAuthenticatedAtom,
accessTokenAtom,
} from '../../stores/ClientConfigStore';
@ -17,10 +17,15 @@ import {
const { TabPane } = Tabs;
export const AuthModal: FC = () => {
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
const currentUser = useRecoilValue(currentUserAtom);
if (!currentUser) {
return null;
}
const authenticated = useRecoilValue<boolean>(chatAuthenticatedAtom);
const accessToken = useRecoilValue<string>(accessTokenAtom);
const federationEnabled = true;
const { displayName } = currentUser;
return (
<div>
@ -41,7 +46,7 @@ export const AuthModal: FC = () => {
>
<IndieAuthModal
authenticated={authenticated}
displayName={chatDisplayName}
displayName={displayName}
accessToken={accessToken}
/>
</TabPane>
@ -56,7 +61,7 @@ export const AuthModal: FC = () => {
>
<FediAuthModal
authenticated={authenticated}
displayName={chatDisplayName}
displayName={displayName}
accessToken={accessToken}
/>
</TabPane>

View File

@ -3,11 +3,7 @@ import { useRecoilValue } from 'recoil';
import { Input, Button, Select } from 'antd';
import { MessageType } from '../../../interfaces/socket-events';
import WebsocketService from '../../../services/websocket-service';
import {
websocketServiceAtom,
chatDisplayNameAtom,
chatDisplayColorAtom,
} from '../../stores/ClientConfigStore';
import { websocketServiceAtom, currentUserAtom } from '../../stores/ClientConfigStore';
const { Option } = Select;
@ -26,10 +22,14 @@ const UserColor: FC<UserColorProps> = ({ color }) => {
};
export const NameChangeModal: FC = () => {
const currentUser = useRecoilValue(currentUserAtom);
if (!currentUser) {
return null;
}
const { displayName, displayColor } = currentUser;
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
const chatDisplayColor = useRecoilValue<number>(chatDisplayColorAtom) || 0;
const [newName, setNewName] = useState<any>(chatDisplayName);
const [newName, setNewName] = useState<any>(displayName);
const handleNameChange = () => {
const nameChange = {
@ -39,8 +39,7 @@ export const NameChangeModal: FC = () => {
websocketService.send(nameChange);
};
const saveEnabled =
newName !== chatDisplayName && newName !== '' && websocketService?.isConnected();
const saveEnabled = newName !== displayName && newName !== '' && websocketService?.isConnected();
const handleColorChange = (color: string) => {
const colorChange = {
@ -63,7 +62,7 @@ export const NameChangeModal: FC = () => {
placeholder="Your chat display name"
maxLength={30}
showCount
defaultValue={chatDisplayName}
defaultValue={displayName}
/>
<Button disabled={!saveEnabled} onClick={handleNameChange}>
Change name
@ -73,7 +72,7 @@ export const NameChangeModal: FC = () => {
<Select
style={{ width: 120 }}
onChange={handleColorChange}
defaultValue={chatDisplayColor.toString()}
defaultValue={displayColor.toString()}
getPopupContainer={triggerNode => triggerNode.parentElement}
>
{colorOptions.map(e => (

View File

@ -6,6 +6,7 @@ import ClientConfigService from '../../services/client-config-service';
import ChatService from '../../services/chat-service';
import WebsocketService from '../../services/websocket-service';
import { ChatMessage } from '../../interfaces/chat-message.model';
import { CurrentUser } from '../../interfaces/current-user';
import { ServerStatus, makeEmptyServerStatus } from '../../interfaces/server-status.model';
import appStateModel, {
AppStateEvent,
@ -44,31 +45,16 @@ export const clientConfigStateAtom = atom({
default: makeEmptyClientConfig(),
});
export const chatDisplayNameAtom = atom<string>({
key: 'chatDisplayName',
default: null,
});
export const chatDisplayColorAtom = atom<number>({
key: 'chatDisplayColor',
default: null,
});
export const chatUserIdAtom = atom<string>({
key: 'chatUserIdAtom',
default: null,
});
export const isChatModeratorAtom = atom<boolean>({
key: 'isModeratorAtom',
default: false,
});
export const accessTokenAtom = atom<string>({
key: 'accessTokenAtom',
default: null,
});
export const currentUserAtom = atom<CurrentUser>({
key: 'currentUserAtom',
default: null,
});
export const chatMessagesAtom = atom<ChatMessage[]>({
key: 'chatMessages',
default: [] as ChatMessage[],
@ -126,7 +112,7 @@ export const isChatVisibleSelector = selector({
get: ({ get }) => {
const state: AppStateOptions = get(appStateAtom);
const userVisibleToggle: boolean = get(chatVisibleToggleAtom);
const accessToken: String = get(accessTokenAtom);
const accessToken: string = get(accessTokenAtom);
return accessToken && state.chatAvailable && userVisibleToggle;
},
});
@ -135,7 +121,7 @@ export const isChatAvailableSelector = selector({
key: 'isChatAvailableSelector',
get: ({ get }) => {
const state: AppStateOptions = get(appStateAtom);
const accessToken: String = get(accessTokenAtom);
const accessToken: string = get(accessTokenAtom);
return accessToken && state.chatAvailable;
},
});
@ -174,12 +160,8 @@ function mergeMeta(meta) {
export const ClientConfigStore: FC = () => {
const [appState, appStateSend, appStateService] = useMachine(appStateModel);
const setChatDisplayName = useSetRecoilState<string>(chatDisplayNameAtom);
const setChatDisplayColor = useSetRecoilState<Number>(chatDisplayColorAtom);
const setChatUserId = useSetRecoilState<string>(chatUserIdAtom);
const [currentUser, setCurrentUser] = useRecoilState(currentUserAtom);
const setChatAuthenticated = useSetRecoilState<boolean>(chatAuthenticatedAtom);
const setIsChatModerator = useSetRecoilState<boolean>(isChatModeratorAtom);
const [clientConfig, setClientConfig] = useRecoilState<ClientConfig>(clientConfigStateAtom);
const setServerStatus = useSetRecoilState<ServerStatus>(serverStatusState);
const setClockSkew = useSetRecoilState<Number>(clockSkewAtom);
@ -264,10 +246,13 @@ export const ClientConfigStore: FC = () => {
}
console.log('setting access token', newAccessToken);
setCurrentUser({
...currentUser,
displayName: newDisplayName,
displayColor,
});
setAccessToken(newAccessToken);
setLocalStorage(ACCESS_TOKEN_KEY, newAccessToken);
setChatDisplayName(newDisplayName);
setChatDisplayColor(displayColor);
} catch (e) {
sendEvent(AppStateEvent.Fail);
console.error(`ChatService -> registerUser() ERROR: \n${e}`);
@ -276,7 +261,7 @@ export const ClientConfigStore: FC = () => {
const resetAndReAuth = () => {
setLocalStorage(ACCESS_TOKEN_KEY, '');
setAccessToken('');
setAccessToken(null);
handleUserRegistration();
};
@ -299,11 +284,8 @@ export const ClientConfigStore: FC = () => {
case MessageType.CONNECTED_USER_INFO:
handleConnectedClientInfoMessage(
message as ConnectedClientInfoEvent,
setChatDisplayName,
setChatDisplayColor,
setChatUserId,
setIsChatModerator,
setChatAuthenticated,
setCurrentUser,
);
setChatMessages(currentState => [...currentState, message as ChatEvent]);
break;

View File

@ -2,18 +2,18 @@ import { ConnectedClientInfoEvent } from '../../../interfaces/socket-events';
export function handleConnectedClientInfoMessage(
message: ConnectedClientInfoEvent,
setChatDisplayName: (string) => void,
setChatDisplayColor: (number) => void,
setChatUserId: (number) => void,
setIsChatModerator: (boolean) => void,
setChatAuthenticated: (boolean) => void,
setCurrentUser: (CurrentUser) => void,
) {
const { user } = message;
const { id, displayName, displayColor, scopes, authenticated } = user;
setChatDisplayName(displayName);
setChatDisplayColor(displayColor);
setChatUserId(id);
setIsChatModerator(scopes?.includes('MODERATOR'));
setChatAuthenticated(authenticated);
setCurrentUser({
id: id.toString(),
displayName,
displayColor,
isModerator: scopes?.includes('MODERATOR'),
});
}
export default handleConnectedClientInfoMessage;

View File

@ -8,8 +8,7 @@ import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../u
import {
clientConfigStateAtom,
chatMessagesAtom,
chatDisplayNameAtom,
chatUserIdAtom,
currentUserAtom,
isChatAvailableSelector,
isChatVisibleSelector,
appStateAtom,
@ -134,12 +133,12 @@ export const Content: FC = () => {
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
const isChatVisible = useRecoilValue<boolean>(isChatVisibleSelector);
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
const currentUser = useRecoilValue(currentUserAtom);
const [isMobile, setIsMobile] = useRecoilState<boolean | undefined>(isMobileAtom);
const messages = useRecoilValue<ChatMessage[]>(chatMessagesAtom);
const online = useRecoilValue<boolean>(isOnlineSelector);
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
const { viewerCount, lastConnectTime, lastDisconnectTime, streamTitle } =
useRecoilValue<ServerStatus>(serverStatusState);
const {
@ -200,6 +199,11 @@ export const Content: FC = () => {
window.addEventListener('resize', checkIfMobile);
}, []);
if (!currentUser) {
return null;
}
const { id: currentUserId, displayName } = currentUser;
const showChat = !chatDisabled && isChatAvailable && isChatVisible;
return (
@ -261,8 +265,8 @@ export const Content: FC = () => {
socialHandles={socialHandles}
extraPageContent={extraPageContent}
messages={messages}
chatDisplayName={chatDisplayName}
chatUserId={chatUserId}
chatDisplayName={displayName}
chatUserId={currentUserId}
showChat={showChat}
/>
) : (

View File

@ -5,26 +5,20 @@ import { ChatMessage } from '../../../interfaces/chat-message.model';
import { ChatContainer } from '../../chat/ChatContainer/ChatContainer';
import styles from './Sidebar.module.scss';
import {
chatDisplayNameAtom,
chatUserIdAtom,
isChatModeratorAtom,
visibleChatMessagesSelector,
} from '../../stores/ClientConfigStore';
import { currentUserAtom, visibleChatMessagesSelector } from '../../stores/ClientConfigStore';
export const Sidebar: FC = () => {
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
const isChatModerator = useRecoilValue<boolean>(isChatModeratorAtom);
const currentUser = useRecoilValue(currentUserAtom);
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
const { id, isModerator, displayName } = currentUser;
return (
<Sider className={styles.root} collapsedWidth={0} width={320}>
<ChatContainer
messages={messages}
usernameToHighlight={chatDisplayName}
chatUserId={chatUserId}
isModerator={isChatModerator}
usernameToHighlight={displayName}
chatUserId={id}
isModerator={isModerator}
/>
</Sider>
);

View File

@ -0,0 +1,6 @@
export interface CurrentUser {
id: string;
displayName: string;
displayColor: number;
isModerator: boolean;
}

View File

@ -3,23 +3,24 @@ import { ChatMessage } from '../../../../interfaces/chat-message.model';
import { ChatContainer } from '../../../../components/chat/ChatContainer/ChatContainer';
import {
ClientConfigStore,
chatDisplayNameAtom,
chatUserIdAtom,
currentUserAtom,
visibleChatMessagesSelector,
} from '../../../../components/stores/ClientConfigStore';
export default function ReadOnlyChatEmbed() {
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
const currentUser = useRecoilValue(currentUserAtom);
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
if (!currentUser) {
return null;
}
const { id, displayName } = currentUser;
return (
<div>
<ClientConfigStore />
<ChatContainer
messages={messages}
usernameToHighlight={chatDisplayName}
chatUserId={chatUserId}
usernameToHighlight={displayName}
chatUserId={id}
isModerator={false}
showInput={false}
height="100vh"

View File

@ -3,32 +3,34 @@ import { ChatMessage } from '../../../../interfaces/chat-message.model';
import { ChatContainer } from '../../../../components/chat/ChatContainer/ChatContainer';
import {
ClientConfigStore,
chatDisplayNameAtom,
chatUserIdAtom,
currentUserAtom,
visibleChatMessagesSelector,
clientConfigStateAtom,
isChatModeratorAtom,
} from '../../../../components/stores/ClientConfigStore';
import Header from '../../../../components/ui/Header/Header';
import { ClientConfig } from '../../../../interfaces/client-config.model';
export default function ReadWriteChatEmbed() {
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
const currentUser = useRecoilValue(currentUserAtom);
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
const isModerator = useRecoilValue<boolean>(isChatModeratorAtom);
const { name, chatDisabled } = clientConfig;
if (!currentUser) {
return null;
}
const { id, displayName, isModerator } = currentUser;
return (
<div>
<ClientConfigStore />
<Header name={name} chatAvailable chatDisabled={chatDisabled} />
<ChatContainer
messages={messages}
usernameToHighlight={chatDisplayName}
chatUserId={chatUserId}
usernameToHighlight={displayName}
chatUserId={id}
isModerator={isModerator}
showInput
height="80vh"

View File

@ -1,11 +1,11 @@
import React, { useEffect } from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { RecoilRoot, useSetRecoilState } from 'recoil';
import { RecoilRoot, useRecoilState, useSetRecoilState } from 'recoil';
import ReadWritePage from '../pages/embed/chat/readwrite/index';
import { ChatMessage } from '../interfaces/chat-message.model';
import {
chatMessagesAtom,
chatDisplayNameAtom,
currentUserAtom,
clientConfigStateAtom,
} from '../components/stores/ClientConfigStore';
import { ClientConfig } from '../interfaces/client-config.model';
@ -21,8 +21,8 @@ const testMessages =
const messages: ChatMessage[] = JSON.parse(testMessages);
const Page = () => {
const [currentUser, setCurrentUser] = useRecoilState(currentUserAtom);
const setMessages = useSetRecoilState(chatMessagesAtom);
const setDisplayName = useSetRecoilState(chatDisplayNameAtom);
const setClientConfig = useSetRecoilState<ClientConfig>(clientConfigStateAtom);
const fakeConfig: ClientConfig = {
@ -45,7 +45,11 @@ const Page = () => {
useEffect(() => {
setMessages(messages);
setDisplayName('fake-chat-user');
setCurrentUser({
...currentUser,
displayName: 'fake-chat-user',
});
setClientConfig(fakeConfig);
}, []);