Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions demos/rtcube-vite-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
"preview": "vite preview"
},
"dependencies": {
"@tencentcloud/chat-uikit-react": "^4.5.3",
"@tencentcloud/uikit-base-component-react": "1.1.4",
"@trtc/calls-uikit-react": "4.4.0",
"tuikit-atomicx-react": "4.5.3",
"@tencentcloud/chat-uikit-react": "~6.0.0",
"@tencentcloud/uikit-base-component-react": "~1.3.0",
"@trtc/calls-uikit-react": "~4.5.1",
"tuikit-atomicx-react": "~6.0.0",
"classnames": "^2.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0"
},
"devDependencies": {
"@tencentcloud/eslint-config-hybrid": "0.0.1",
"@tencentcloud/eslint-config-hybrid": "~0.0.1",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",
Expand Down
8 changes: 1 addition & 7 deletions demos/rtcube-vite-react/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ import { router } from './router';

i18nInit();

const browserLang = navigator.language;
const language = browserLang.startsWith('zh') ? 'zh-CN' : 'en-US';

function App() {
return (
<UIKitProvider
// language={language}
theme="light"
>
<UIKitProvider theme="light">
<RouterProvider router={router} />
</UIKitProvider>
);
Expand Down
11 changes: 11 additions & 0 deletions demos/rtcube-vite-react/src/locales/en-US/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ export const resource = {
'scene.chat.tab.contact': 'Contacts',
'scene.chat.drawer.settings': 'Settings',
'scene.chat.drawer.search': 'Search',
'scene.chat.profile.edit': 'Edit',
'scene.chat.profile.dialogTitle': 'Edit Profile',
'scene.chat.profile.nicknameLabel': 'Nickname',
'scene.chat.profile.nicknamePlaceholder': 'Enter your nickname',
'scene.chat.profile.avatarUrlLabel': 'Avatar URL',
'scene.chat.profile.avatarUrlPlaceholder': 'Enter image URL',
'scene.chat.profile.avatarUrlHint': 'Avatar supports network URLs only. Please use an image URL starting with http or https.',
'scene.chat.profile.saveSuccess': 'Profile updated successfully',
'scene.chat.profile.saveFailed': 'Failed to update profile',
'scene.chat.profile.validation.nicknameRequired': 'Nickname cannot be empty',
'scene.chat.profile.validation.avatarUrlInvalid': 'Avatar URL must be a valid http/https URL',
'scene.chat.placeholder.chat.title': 'No Messages',
'scene.chat.placeholder.chat.description': 'Select a conversation to start chatting, or create a new conversation',
'scene.chat.placeholder.contact.title': 'No Contacts',
Expand Down
11 changes: 11 additions & 0 deletions demos/rtcube-vite-react/src/locales/zh-CN/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ export const resource = {
'scene.chat.tab.contact': '通讯录',
'scene.chat.drawer.settings': '设置',
'scene.chat.drawer.search': '搜索',
'scene.chat.profile.edit': '编辑',
'scene.chat.profile.dialogTitle': '修改个人资料',
'scene.chat.profile.nicknameLabel': '昵称',
'scene.chat.profile.nicknamePlaceholder': '请输入昵称',
'scene.chat.profile.avatarUrlLabel': '头像 URL',
'scene.chat.profile.avatarUrlPlaceholder': '请输入头像图片链接',
'scene.chat.profile.avatarUrlHint': '头像仅支持网络 URL,请使用 http 或 https 开头的图片地址。',
'scene.chat.profile.saveSuccess': '个人资料更新成功',
'scene.chat.profile.saveFailed': '个人资料更新失败',
'scene.chat.profile.validation.nicknameRequired': '昵称不能为空',
'scene.chat.profile.validation.avatarUrlInvalid': '头像 URL 仅支持 http/https 网络地址',
'scene.chat.placeholder.chat.title': '暂无消息',
'scene.chat.placeholder.chat.description': '选择一个对话开始聊天,或创建新的对话',
'scene.chat.placeholder.contact.title': '暂无联系人',
Expand Down
49 changes: 43 additions & 6 deletions demos/rtcube-vite-react/src/pages/StagesPage/StagesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useUIKit } from '@tencentcloud/chat-uikit-react';
import { Button } from '@tencentcloud/uikit-base-component-react';
import { useNavigate, useParams } from 'react-router-dom';
Expand All @@ -13,15 +13,48 @@ import styles from './StagesPage.module.scss';

function StagesPage() {
const navigate = useNavigate();
const { logout, status } = useLoginState();
const { login, logout, status } = useLoginState();
const { sceneId } = useParams();
const { t } = useUIKit();
const [isRestoring, setIsRestoring] = useState(status !== 'success');
const restoreAttempted = useRef(false);

useEffect(() => {
if (status !== 'success') {
navigate('/');
if (status === 'success') {
setIsRestoring(false);
return;
}

if (restoreAttempted.current) {
return;
}
}, []);
restoreAttempted.current = true;

const stored = localStorage.getItem('userInfo');
if (!stored) {
navigate('/', { replace: true });
return;
}

try {
const userInfo = JSON.parse(stored);
if (userInfo.SDKAppID && userInfo.userID && userInfo.userSig) {
login({
SDKAppID: userInfo.SDKAppID,
userID: userInfo.userID,
userSig: userInfo.userSig,
}).catch(() => {
localStorage.removeItem('userInfo');
navigate('/', { replace: true });
});
} else {
navigate('/', { replace: true });
}
} catch {
localStorage.removeItem('userInfo');
navigate('/', { replace: true });
}
}, [status]);

const scenes = getEnabledScenes();

Expand Down Expand Up @@ -49,10 +82,14 @@ function StagesPage() {

function handleLogout() {
logout();
localStorage.removeItem('userinfo');
localStorage.removeItem('userInfo');
navigate('/');
}

if (isRestoring) {
return null;
}

return (
<div className={styles.stagePage}>
<header className={styles.stageHeader}>
Expand Down
37 changes: 30 additions & 7 deletions demos/rtcube-vite-react/src/scenes/ChatPage/ChatPage.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,20 @@
}
}

.chat-content-panel {
.chat-with-search {
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
min-width: 0;
position: relative;
}

.chat-content-panel {
flex: 1;
min-width: 0;
}

.contact-detail-panel {
flex: 1;
}
Expand Down Expand Up @@ -94,8 +103,7 @@
right: 0;
top: 0;
bottom: 0;
min-width: 300px;
max-width: 400px;
width: 358px;
display: flex;
flex-direction: column;
background-color: var(--uikit-theme-light-bg-color-operate);
Expand All @@ -114,7 +122,22 @@
}
}

.chat-sidebar__header {
.search-panel {
width: 358px;
flex-shrink: 0;
display: flex;
flex-direction: column;
background-color: var(--uikit-theme-light-bg-color-operate);
border-left: 1px solid var(--uikit-theme-light-stroke-color-primary);
overflow: auto;

&.dark {
background-color: var(--uikit-theme-dark-bg-color-operate);
border-left-color: var(--uikit-theme-dark-stroke-color-primary);
}
}

.search-panel__header {
position: sticky;
top: 0;
display: flex;
Expand All @@ -125,18 +148,18 @@
border-bottom: 1px solid var(--uikit-theme-light-stroke-color-primary);
z-index: 10;

.chat-sidebar.dark & {
.search-panel.dark & {
background-color: var(--uikit-theme-dark-bg-color-operate);
border-bottom: 1px solid var(--uikit-theme-dark-stroke-color-primary);
}
}

.chat-sidebar__title {
.search-panel__title {
font-size: 16px;
font-weight: 500;
color: var(--uikit-theme-light-text-color-primary);

.chat-sidebar.dark & {
.search-panel.dark & {
color: var(--uikit-theme-dark-text-color-primary);
}
}
69 changes: 28 additions & 41 deletions demos/rtcube-vite-react/src/scenes/ChatPage/ChatPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useLayoutEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import {
ConversationList,
Chat,
Expand All @@ -10,7 +10,7 @@ import {
ContactList,
ContactInfo,
useUIKit,
useConversationListState,
useChatContext,
} from '@tencentcloud/chat-uikit-react';
import { TUICallKit } from '@trtc/calls-uikit-react';
import cs from 'classnames';
Expand All @@ -20,17 +20,11 @@ import type { TabKey } from './components';

function ChatPage() {
const { t, theme } = useUIKit();
const { activeConversation, setActiveConversation } = useConversationListState();
const { activeConversation } = useChatContext();
const [activeTab, setActiveTab] = useState<TabKey>('conversation');
const [isChatSettingShow, setChatSettingShow] = useState(false);
const [isSearchInChatShow, setSearchInChatShow] = useState(false);

useLayoutEffect(() => {
// default open a c2c conversation, you can change it.
const peerUserID = 'administrator';
setActiveConversation(`C2C${peerUserID}`);
}, []);

useEffect(() => {
setChatSettingShow(false);
setSearchInChatShow(false);
Expand All @@ -41,7 +35,7 @@ function ChatPage() {
};

return (
<div className={cs(styles['chat-layout'], { 'dark': theme === 'dark' })}>
<div className={cs(styles['chat-layout'], { dark: theme === 'dark' })}>
<TUICallKit className={styles['call-kit']} />

{/* SideTab Navigation */}
Expand All @@ -55,51 +49,44 @@ function ChatPage() {
{activeTab === 'contact' && <ContactList />}
</div>

{/* Chat Content Panel */}
{/* Chat Content Panel + Search Panel Container */}
{activeTab === 'conversation' && (
<Chat
PlaceholderEmpty={<PlaceholderEmpty type="chat" />}
className={styles['chat-content-panel']}
>
<ChatHeader
onMenuClick={() => setChatSettingShow(!isChatSettingShow)}
onSearchClick={() => setSearchInChatShow(!isSearchInChatShow)}
/>
<MessageList />
<MessageInput />
<div className={styles['chat-with-search']}>
<Chat
PlaceholderEmpty={<PlaceholderEmpty type="chat" />}
className={styles['chat-content-panel']}
>
<ChatHeader
onMenuClick={() => setChatSettingShow(!isChatSettingShow)}
onSearchClick={() => setSearchInChatShow(!isSearchInChatShow)}
/>
<MessageList />
<MessageInput />
</Chat>

{/* Chat Setting Sidebar */}
{isChatSettingShow && (
<div className={cs(styles['chat-sidebar'], { [styles.dark]: theme === 'dark' })}>
<div className={styles['chat-sidebar__header']}>
<span className={styles['chat-sidebar__title']}>{t('scene.chat.drawer.settings')}</span>
{/* Search in Chat Panel (side-by-side with chat, not overlapping) */}
{isSearchInChatShow && (
<div className={cs(styles['search-panel'], { [styles.dark]: theme === 'dark' })}>
<div className={styles['search-panel__header']}>
<span className={styles['search-panel__title']}>{t('scene.chat.drawer.search')}</span>
<button
className={styles['icon-button']}
onClick={() => setChatSettingShow(false)}
onClick={() => setSearchInChatShow(false)}
>
</button>
</div>
<ChatSetting />
<Search variant={VariantType.EMBEDDED} />
</div>
)}

{/* Search in Chat Sidebar */}
{isSearchInChatShow && (
{/* Chat Setting Sidebar (overlay on the entire chat-with-search area) */}
{isChatSettingShow && (
<div className={cs(styles['chat-sidebar'], { [styles.dark]: theme === 'dark' })}>
<div className={styles['chat-sidebar__header']}>
<span className={styles['chat-sidebar__title']}>{t('scene.chat.drawer.search')}</span>
<button
className={styles['icon-button']}
onClick={() => setSearchInChatShow(false)}
>
</button>
</div>
<Search style={{ minWidth: '300px' }} variant={VariantType.EMBEDDED} />
<ChatSetting onClose={() => setChatSettingShow(false)} />
</div>
)}
</Chat>
</div>
)}

{/* Contact Detail Panel */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
justify-content: space-between;
padding: 20px 0 24px;
transition: background 0.3s;

&.dark {
background: var(--uikit-theme-dark-bg-color-function);
}
}

.side-tab__top {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}

.avatar-wrapper {
position: relative;
margin-bottom: 24px;
cursor: pointer;

&:hover {
Expand Down Expand Up @@ -84,6 +91,12 @@
gap: 16px;
}

.profile-action {
.side-tab.dark & {
color: var(--uikit-theme-dark-text-color-primary);
}
}

.tab-item {
width: 48px;
height: 48px;
Expand Down
Loading