Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9fc2c50

Browse files
committedFeb 11, 2025
react-chat-ui-kit-demo-app
1 parent 7fb436d commit 9fc2c50

37 files changed

+21033
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
# misc
15+
.DS_Store
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## MIT License
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.
20+
21+
You may obtain a copy of the License at https://opensource.org/licenses/MIT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Table of Content
2+
3+
- [About](#about)
4+
- [Requirements](#requirements)
5+
- [Screenshots](#screenshots)
6+
- [Features](#features)
7+
- [How to launch](#how-to-launch)
8+
* [1. Install Node.js and NPM integration](#1-install-nodejs-and-npm-integration)
9+
* [2. Run on development server](#4-run-on-development-server)
10+
+ [2.1 Get application credentials](#21-get-application-credentials)
11+
+ [2.2 Set application credentials](#22-set-application-credentials)
12+
+ [2.3 Run the application](#23-run-the-application)
13+
- [Documentation](#documentation)
14+
- [License](#license)
15+
16+
# About
17+
[](#about)
18+
QuickBlox UIKit React Sample
19+
20+
This is a code sample for [QuickBlox](http://quickblox.com/) platform. It is a great way for developers using QuickBlox platform to learn how to integrate private and group chat, add text and image attachments sending into your application.
21+
22+
# Requirements
23+
[](#requirements)
24+
25+
The minimum requirements for QuickBlox UIKit for React sample are:
26+
27+
- JS QuickBlox SDK v2.15.5
28+
- QuickBlox React UIKit library v0.2.8
29+
- React JS v.18.0
30+
- TypeScript v.4.9.3
31+
32+
# Screenshots
33+
1. Sign In page;
34+
35+
![sign_in](./src/assets/screenshorts/sign-in-react-chat-sample.png)
36+
37+
2. Sign Up page;
38+
39+
![sign_in](./src/assets/screenshorts/sign-up-react-chat-sample.png)
40+
41+
3. UIKit;
42+
43+
![quickblox-ui-kit](./src/assets/screenshorts/react-qb-uikitweb-doc.png)
44+
45+
# Features
46+
47+
* Sign in/Sign up and Log out
48+
* Set up custom theme
49+
* Send and receive message/attachment
50+
* Create and leave a 1-to-1 and group chat
51+
* Create a public chat
52+
* Display users who have received/read the message
53+
* Mark messages as read/delivered
54+
* Send typing indicators
55+
* List and delete chats
56+
* Display chat history
57+
* Display a list with chat participants
58+
59+
# How to launch
60+
## 1. Install Node.js and NPM integration
61+
62+
You should use QuickBlox JavaScript SDK and QuickBlox UIKit with server-side applications on NodeJS through the native node package.
63+
Just install the package in your application project:
64+
65+
Navigate the current project folder ***\samples\react-chat*** and type
66+
67+
```bash
68+
npm install
69+
```
70+
71+
## 2. Run on development server
72+
73+
### 2.1 Get application credentials
74+
75+
QuickBlox application includes everything that brings messaging right
76+
into your application - chat, video calling, users, push notifications,
77+
etc. To create a QuickBlox application, follow the steps below:
78+
79+
1. Register a new account following [this
80+
link](https://admin.quickblox.com/signup). Type in your email and
81+
password to sign in. You can also sign in with your Google or Github
82+
accounts.
83+
2. Create the app clicking **New app** button.
84+
3. Configure the app. Type in the information about your organization
85+
into corresponding fields and click **Add** button.
86+
4. Go to **Dashboard =\> *YOUR\_APP* =\> Overview** section and copy
87+
your **Application ID**, **Authorization Key**, **Authorization
88+
Secret**, and **Account Key**.
89+
90+
### 2.2 Set application credentials
91+
92+
Before run a code sample:
93+
1. get appId, authKey, authSecret, and accountKey
94+
2. put these values in file **QBconfig.ts** following **samples =\>
95+
react-chat =\> src\** directory.
96+
97+
TypeScript
98+
```ts
99+
export const QBconfig = {
100+
credentials: {
101+
appId: '',
102+
authKey: '',
103+
authSecret: '',
104+
accountKey: ''
105+
}
106+
}
107+
```
108+
109+
### 2.3 Run the application
110+
111+
Run `npm start` for a dev server. Navigate to `http://localhost:3000/`. The app will automatically reload if you change any of the source files.
112+
113+
# Documentation
114+
[](#documentation)
115+
Sample documentation is available [here](https://docs.quickblox.com/docs/react-uikit).
116+
117+
# License
118+
[](#license)
119+
MIT License [here](https://github.com/QuickBlox/react-ui-kit/blob/main/LICENSE.md).
120+
121+
Copyright © 2023 QuickBlox

‎Articles/js/react-chat-ui-kit-demo-app/package-lock.json

+19,109
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"name": "react-chat",
3+
"version": "1.0.18",
4+
"private": true,
5+
"dependencies": {
6+
"@emotion/react": "^11.11.0",
7+
"@emotion/styled": "^11.11.0",
8+
"@mui/icons-material": "^5.11.16",
9+
"@mui/material": "^5.13.3",
10+
"@mui/styled-engine-sc": "^5.12.0",
11+
"@types/node": "^16.18.28",
12+
"@types/react": "^18.2.6",
13+
"@types/react-dom": "^18.2.4",
14+
"quickblox": "2.19.1",
15+
"quickblox-react-ui-kit": "0.4.2-beta.4",
16+
"react": "^17.0.0 || ^18.0.0",
17+
"react-dom": "^17.0.0 || ^18.0.0",
18+
"react-router-dom": "^6.4.3",
19+
"react-scripts": "5.0.1",
20+
"sass": "^1.84.0",
21+
"styled-components": "^5.3.11",
22+
"typescript": "^4.9.5",
23+
"web-vitals": "^2.1.4"
24+
},
25+
"scripts": {
26+
"start": "react-scripts start",
27+
"build": "react-scripts build",
28+
"test": "react-scripts test",
29+
"eject": "react-scripts eject"
30+
},
31+
"eslintConfig": {
32+
"extends": [
33+
"react-app",
34+
"react-app/jest"
35+
]
36+
},
37+
"browserslist": {
38+
"production": [
39+
">0.2%",
40+
"not dead",
41+
"not op_mini all"
42+
],
43+
"development": [
44+
"last 1 chrome version",
45+
"last 1 firefox version",
46+
"last 1 safari version"
47+
]
48+
},
49+
"peerDependencies": {
50+
"react": "^17.0.0 || ^18.0.0",
51+
"react-dom": "^17.0.0 || ^18.0.0"
52+
}
53+
}
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta name="theme-color" content="#000000" />
8+
<meta
9+
name="description"
10+
content="Web site created using create-react-app"
11+
/>
12+
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
13+
<!--
14+
manifest.json provides metadata used when your web app is installed on a
15+
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
16+
-->
17+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
18+
<!--
19+
Notice the use of %PUBLIC_URL% in the tags above.
20+
It will be replaced with the URL of the `public` folder during the build.
21+
Only files inside the `public` folder can be referenced from the HTML.
22+
23+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
24+
work correctly both with client-side routing and a non-root public URL.
25+
Learn how to configure a non-root public URL by running `npm run build`.
26+
-->
27+
<title>React App</title>
28+
</head>
29+
<body>
30+
<noscript>You need to enable JavaScript to run this app.</noscript>
31+
<div id="root"></div>
32+
<!--
33+
This HTML file is a template.
34+
If you open it directly in the browser, you will see an empty page.
35+
36+
You can add webfonts, meta tags, or analytics to this file.
37+
The build step will place the bundled scripts into the <body> tag.
38+
39+
To begin the development, run `npm start` or `yarn start`.
40+
To create a production bundle, use `npm run build` or `yarn build`.
41+
-->
42+
</body>
43+
</html>
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"short_name": "React App",
3+
"name": "Create React App Sample",
4+
"icons": [
5+
{
6+
"src": "favicon.ico",
7+
"sizes": "64x64 32x32 24x24 16x16",
8+
"type": "image/x-icon"
9+
},
10+
{
11+
"src": "logo192.png",
12+
"type": "image/png",
13+
"sizes": "192x192"
14+
},
15+
{
16+
"src": "logo512.png",
17+
"type": "image/png",
18+
"sizes": "512x512"
19+
}
20+
],
21+
"start_url": ".",
22+
"display": "standalone",
23+
"theme_color": "#000000",
24+
"background_color": "#ffffff"
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# https://www.robotstxt.org/robotstxt.html
2+
User-agent: *
3+
Disallow:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
@import "styles/_theme_colors_scheme.scss";
2+
@import "styles/_variables.scss";
3+
4+
.main-buttons-wrapper {
5+
margin: 10px;
6+
padding-left: 10px;
7+
display: flex;
8+
flex-flow: row wrap;
9+
gap: 10px;
10+
}
11+
12+
body{
13+
font-family: 'Roboto';
14+
justify-content: center;
15+
align-content: center;
16+
width: 100%;
17+
max-width: 100%;
18+
//to hide the scrollbars if DialogInfo clicked
19+
overflow: hidden !important;
20+
scrollbar-width: none !important;
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
import React, {useEffect, useState} from 'react';
2+
import {prepareSDK, createUserAction, logout, createUserSession, connectToChatServer,
3+
UserCreationStatus, UserData} from './QBHeplers';
4+
import {
5+
LoginData,
6+
MainButton,
7+
useQbUIKitDataContext,
8+
QuickBloxUIKitDesktopLayout,
9+
QuickBloxUIKitProvider,
10+
TypeButton, QBDataContextType,
11+
} from 'quickblox-react-ui-kit';
12+
import './App.scss';
13+
import {QBConfig} from "./QBconfig";
14+
import {Route, Routes, useNavigate} from "react-router-dom";
15+
import Auth from "./layout/Auth/Auth";
16+
import SignIn from "./SignIn/SignIn";
17+
import SignUp from "./SignUp/SignUp";
18+
19+
function App() {
20+
const qbUIKitContext: QBDataContextType = useQbUIKitDataContext();
21+
22+
const [isOnline, setIsOnline] = useState<boolean>(
23+
navigator.onLine
24+
);
25+
26+
qbUIKitContext.storage.CONNECTION_REPOSITORY.subscribe((status) => {
27+
console.log(`Connection status: ${status ? 'CONNECTED' : 'DISCONNECTED'}`);
28+
if (status) {setIsOnline(true);}
29+
else {
30+
setIsOnline(false);
31+
setErrorMessage('Error! No Connection.');
32+
}
33+
});
34+
35+
useEffect(() => {
36+
const handleOnline = () => setIsOnline(true);
37+
const handleOffline = () => setIsOnline(false);
38+
39+
window.addEventListener('online', handleOnline);
40+
window.addEventListener('offline', handleOffline);
41+
42+
return () => {
43+
window.removeEventListener('online', handleOnline);
44+
window.removeEventListener('offline', handleOffline);
45+
};
46+
}, []);
47+
48+
useEffect(() => {
49+
if (isOnline) {
50+
setErrorMessage('');
51+
}
52+
}, [isOnline]);
53+
54+
const [isUserAuthorized, setUserAuthorized] = React.useState(false);
55+
const [isSDKInitialized, setSDKInitialized] = React.useState(false);
56+
57+
const [theme, setTheme] = useState('lightTheme');
58+
const [errorMessage, setErrorMessage] = useState('');
59+
60+
const initLoginData: LoginData = {
61+
login: '',
62+
password: '',
63+
};
64+
65+
const [currentUser, setCurrentUser] = React.useState(initLoginData);
66+
const navigate = useNavigate();
67+
68+
const loginHandler = async (data: any): Promise<void> => {
69+
if (isOnline) {
70+
setErrorMessage('');
71+
const loginData: LoginData = {
72+
login: data.login,
73+
password: data.password
74+
}
75+
setCurrentUser(loginData);
76+
setTheme(data.nameTheme);
77+
await loginAction(loginData);
78+
document.documentElement.setAttribute('data-theme', data.nameTheme);
79+
} else {
80+
setErrorMessage('Error! No connection.')
81+
}
82+
};
83+
84+
const createUserHandler = async (data: UserData): Promise<void> => {
85+
if (isOnline) {
86+
setErrorMessage('');
87+
88+
const resultCreateUser = await createUserAction(data);
89+
90+
logout();
91+
switch (resultCreateUser) {
92+
case UserCreationStatus.UserCreated:
93+
setUserAuthorized(false);
94+
navigate('/sign-in');
95+
break;
96+
case UserCreationStatus.UserExists:
97+
setErrorMessage('User already exists');
98+
setUserAuthorized(false);
99+
navigate('/sign-up');
100+
break;
101+
default:
102+
setErrorMessage('Auth Fail');
103+
setUserAuthorized(false);
104+
navigate('/sign-up');
105+
break;
106+
}
107+
} else {
108+
setErrorMessage('Error! No connection.');
109+
}
110+
};
111+
112+
const logoutUIKitHandler = async () => {
113+
if (isOnline) {
114+
qbUIKitContext.release();
115+
setCurrentUser({login: '', password: ''});
116+
setUserAuthorized(false);
117+
document.documentElement.setAttribute('data-theme', 'light');
118+
navigate('/sign-in');
119+
} else {
120+
setErrorMessage('Error! No connection.')
121+
}
122+
}
123+
124+
const loginAction = async (loginData: LoginData): Promise<void> => {
125+
if (isSDKInitialized && !isUserAuthorized) {
126+
if (loginData.login.length > 0 && loginData.password.length > 0) {
127+
await createUserSession(loginData)
128+
.then( async resultUserSession => {
129+
await connectToChatServer(
130+
resultUserSession,
131+
currentUser.login)
132+
.then( async authData => {
133+
await qbUIKitContext.authorize(authData);
134+
qbUIKitContext.setSubscribeOnSessionExpiredListener(() => {
135+
console.timeLog('call OnSessionExpiredListener ... start')
136+
logoutUIKitHandler();
137+
console.log('OnSessionExpiredListener ... end');
138+
});
139+
setSDKInitialized(true);
140+
setUserAuthorized(true);
141+
document.documentElement.setAttribute('data-theme', theme);
142+
navigate('/');
143+
})
144+
.catch( errorChatConnection => {
145+
handleError(errorChatConnection);
146+
});
147+
})
148+
.catch( errorUserSession => {
149+
handleError(errorUserSession);
150+
});
151+
}
152+
}
153+
};
154+
155+
const handleError = (error: any): void => {
156+
console.log('error:', JSON.stringify(error));
157+
const errorToShow = error.message.errors[0] || 'Unexpected error';
158+
setErrorMessage(errorToShow );
159+
setUserAuthorized(false);
160+
navigate('/sign-in');
161+
};
162+
163+
useEffect(() => {
164+
if (isSDKInitialized) {
165+
if (currentUser
166+
&& currentUser.login.length > 0
167+
&& currentUser.password.length > 0) {
168+
loginAction(currentUser);
169+
} else {
170+
console.log('auth flow has canceled ...');
171+
}
172+
}
173+
174+
}, [isSDKInitialized]);
175+
176+
useEffect(() => {
177+
if (!isSDKInitialized) {
178+
prepareSDK().then(result => {
179+
setSDKInitialized(true);
180+
setUserAuthorized(false);
181+
}).catch(
182+
e => {
183+
console.log('init SDK has error: ', e)
184+
});
185+
}
186+
}, []);
187+
188+
189+
return (
190+
<QuickBloxUIKitProvider
191+
maxFileSize={100 * 1000000}
192+
accountData={{ ...QBConfig.credentials }}
193+
loginData={{
194+
login: currentUser.login,
195+
password: currentUser.password,
196+
}}
197+
qbConfig={{...QBConfig}}
198+
>
199+
<div>
200+
201+
<Routes>
202+
<Route
203+
path="/" element={
204+
isUserAuthorized
205+
?
206+
<div>
207+
<div className="main-buttons-wrapper">
208+
<MainButton
209+
typeButton={TypeButton.outlined}
210+
title="Light Theme"
211+
styleBox={{width: "200px", height: "20px"}}
212+
clickHandler = {() => {
213+
document.documentElement.setAttribute('data-theme', 'light');
214+
}}
215+
/>
216+
<MainButton
217+
typeButton={TypeButton.defaultDisabled}
218+
title="Dark Theme"
219+
styleBox={{width: "200px", height: "20px"}}
220+
clickHandler = {() => {
221+
document.documentElement.setAttribute('data-theme', 'dark');
222+
}}
223+
/>
224+
<MainButton
225+
typeButton={TypeButton.danger}
226+
title="Log Out"
227+
styleBox={{width: "200px", height: "20px"}}
228+
clickHandler = { logoutUIKitHandler }
229+
/>
230+
</div>
231+
{/*<QuickBloxUIKitDesktopLayout theme={new CustomTheme()} />*/}
232+
<QuickBloxUIKitDesktopLayout uikitHeightOffset="32px"/>
233+
</div>
234+
:
235+
<Auth children={<SignIn signInHandler={loginHandler} errorMessage={errorMessage} isOnline={isOnline}/>} />} />
236+
<Route path="/sign-in" element={<Auth children={<SignIn signInHandler={loginHandler} errorMessage={errorMessage} isOnline={isOnline}/>} />}/>
237+
<Route path="/sign-up" element={<Auth children={<SignUp signUpHandler={createUserHandler} errorMessage={errorMessage} isOnline={isOnline} />} />}/>
238+
</Routes>
239+
</div>
240+
</QuickBloxUIKitProvider>
241+
);
242+
}
243+
244+
export default App;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import {AuthorizationData, LoginData, stringifyError} from "quickblox-react-ui-kit";
2+
import QB, {QBUser} from "quickblox/quickblox";
3+
import {QBConfig as QBConf} from "./QBconfig";
4+
5+
export type UserData = {
6+
login: string;
7+
password: string;
8+
fullName?: string;
9+
};
10+
11+
export type ParamsConnect = {
12+
userId: number;
13+
password: string;
14+
};
15+
16+
export enum UserCreationStatus {
17+
UserSessionCreationError = -3,
18+
AppSessionCreationError = -2,
19+
UserCreationError = -1,
20+
UserCreated = 0,
21+
UserExists = 1,
22+
}
23+
24+
export const prepareSDK = async (): Promise<void> => {
25+
// check if we have installed SDK
26+
if ((window as any).QB === undefined) {
27+
if (QB !== undefined) {
28+
(window as any).QB = QB;
29+
} else {
30+
let QBLib = require('quickblox/quickblox.min');
31+
(window as any).QB = QBLib;
32+
}
33+
}
34+
35+
const APPLICATION_ID = QBConf.credentials.appId;
36+
const AUTH_KEY = QBConf.credentials.authKey;
37+
const AUTH_SECRET = QBConf.credentials.authSecret;
38+
const ACCOUNT_KEY = QBConf.credentials.accountKey;
39+
const CONFIG = QBConf.appConfig ;
40+
41+
QB.init(APPLICATION_ID, AUTH_KEY, AUTH_SECRET, ACCOUNT_KEY, CONFIG);
42+
43+
};
44+
45+
export const createUser = (user: QBUserExtended): Promise<QBUser> => {
46+
const QBLib = (window as any).QB;
47+
return new Promise((resolve, reject) => {
48+
const userLoginData = {
49+
login: user.login,
50+
password: user.password,
51+
full_name: user.full_name,
52+
custom_data:
53+
user.custom_data ||
54+
'You could store in this field any string value or null',
55+
};
56+
57+
QBLib.users.create(userLoginData, (createErr: any, createRes: any) => {
58+
if (createErr) {
59+
reject(createErr);
60+
} else {
61+
resolve(createRes);
62+
}
63+
});
64+
});
65+
};
66+
67+
export const createUserSession = async (loginData: LoginData): Promise<ParamsConnect> => {
68+
// @ts-ignore
69+
return new Promise((resolve, reject) => {
70+
QB.createSession(loginData, async (errorCreateSession: any, session: any) => {
71+
if (errorCreateSession) {
72+
reject(errorCreateSession)
73+
} else {
74+
const userId: number = session.user_id;
75+
const password: string = session.token;
76+
const paramsConnect: ParamsConnect = { userId, password };
77+
resolve(paramsConnect);
78+
}
79+
});
80+
});
81+
};
82+
83+
export const createAppSession = ():Promise<any> => {
84+
const QBLib = (window as any).QB;
85+
return new Promise((resolve, reject) => {
86+
QBLib.createSession((sessionErr: any, sessionRes: any) => {
87+
if (sessionErr) {
88+
reject(sessionErr);
89+
} else {
90+
resolve(sessionRes);
91+
}
92+
});
93+
});
94+
};
95+
96+
export const connectToChatServer = async (paramsConnect: ParamsConnect, userLogin: string): Promise<AuthorizationData> => {
97+
// @ts-ignore
98+
return new Promise((resolve, reject) => {
99+
QB.chat.connect(paramsConnect, async (errorConnect: any, resultConnect: any) => {
100+
if (errorConnect) {
101+
reject(errorConnect);
102+
} else {
103+
const authData: AuthorizationData = {
104+
userId: paramsConnect.userId,
105+
password: paramsConnect.password,
106+
userName: userLogin,
107+
sessionToken: paramsConnect.password
108+
};
109+
resolve(authData);
110+
}
111+
});
112+
});
113+
};
114+
115+
export const canLogin = async (user: QBUserExtended) => {
116+
const QBLib = (window as any).QB;
117+
return new Promise((resolve, reject) => {
118+
QBLib.login(user, (loginErr: any, loginRes: any) => {
119+
if (loginErr) {
120+
reject(loginErr);
121+
} else {
122+
resolve(loginRes);
123+
}
124+
});
125+
});
126+
}
127+
128+
export const logout = () => {
129+
const QBLib = (window as any).QB;
130+
QBLib.chat.disconnect();
131+
QBLib.destroySession(() => null);
132+
}
133+
export type QBUserExtended = QBUser & {password?: string};
134+
const isUserExist = async (user: QBUserExtended): Promise<boolean> => {
135+
let userExists = true;
136+
await canLogin(user).catch(() => {
137+
userExists = false;
138+
});
139+
return userExists;
140+
}
141+
142+
export const qbDefaultUser: QBUserExtended = {
143+
id: 0,
144+
full_name: '',
145+
email: '',
146+
login: '',
147+
phone: '',
148+
website: '',
149+
created_at: '',
150+
updated_at: '',
151+
last_request_at: '',
152+
external_user_id: null,
153+
facebook_id: null,
154+
blob_id: null,
155+
custom_data: null,
156+
age_over16: false,
157+
allow_statistics_analysis: false,
158+
allow_sales_activities: false,
159+
parents_contacts: '',
160+
user_tags: null
161+
};
162+
163+
export const createUserAction = async (data: UserData): Promise<UserCreationStatus> => {
164+
let resultCreateUserAction: UserCreationStatus = UserCreationStatus.UserCreated;
165+
createAppSession().then(async () => {
166+
const user: QBUserExtended = qbDefaultUser;
167+
user.login = data.login;
168+
user.full_name = data.fullName || '';
169+
user.password = data.password;
170+
171+
const userExists: boolean = await isUserExist(user);
172+
173+
if (userExists) {
174+
resultCreateUserAction = UserCreationStatus.UserExists;
175+
}else{
176+
createUser(user)
177+
.then((createRes) => {
178+
resultCreateUserAction = UserCreationStatus.UserCreated;
179+
})
180+
.catch((createErr) => {
181+
console.log(stringifyError(createErr));
182+
resultCreateUserAction = UserCreationStatus.UserCreationError;
183+
});
184+
}
185+
}).catch((reason)=>{
186+
console.log(stringifyError(reason));
187+
resultCreateUserAction = UserCreationStatus.AppSessionCreationError;
188+
});
189+
190+
return resultCreateUserAction;
191+
};
192+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import {QBUIKitConfig} from "quickblox-react-ui-kit/dist/CommonTypes/CommonTypes";
2+
3+
export const QBConfig: QBUIKitConfig = {
4+
credentials: {
5+
appId: -1,
6+
accountKey: '',
7+
authKey: '',
8+
authSecret: '',
9+
sessionToken: '',
10+
},
11+
configAIApi: {
12+
AIAnswerAssistWidgetConfig: {
13+
organizationName: 'Quickblox',
14+
openAIModel: 'gpt-3.5-turbo',
15+
smartChatAssistantId: '',
16+
apiKey: '',
17+
maxTokens: 3584,
18+
useDefault: true,
19+
proxyConfig: {
20+
api: 'v1/chat/completions',
21+
servername: 'https://api.openai.com/',
22+
port: '',
23+
},
24+
},
25+
AITranslateWidgetConfig: {
26+
organizationName: 'Quickblox',
27+
openAIModel: 'gpt-3.5-turbo',
28+
smartChatAssistantId: '',
29+
apiKey: '',
30+
maxTokens: 3584,
31+
useDefault: true,
32+
defaultLanguage: 'Ukrainian',
33+
languages: ['Ukrainian', 'English', 'French', 'Portuguese', 'German'],
34+
proxyConfig: {
35+
api: 'v1/chat/completions',
36+
servername: '',
37+
port: '',
38+
},
39+
// proxyConfig: {
40+
// api: 'v1/chat/completions',
41+
// servername: 'http://localhost',
42+
// port: '3012',
43+
// },
44+
},
45+
AIRephraseWidgetConfig: {
46+
organizationName: 'Quickblox',
47+
openAIModel: 'gpt-3.5-turbo',
48+
smartChatAssistantId: '',
49+
apiKey: '',
50+
maxTokens: 3584,
51+
useDefault: true,
52+
defaultTone: 'Professional',
53+
Tones: [
54+
{
55+
name: 'Professional Tone',
56+
description:
57+
'This would edit messages to sound more formal, using technical vocabulary, clear sentence structures, and maintaining a respectful tone. It would avoid colloquial language and ensure appropriate salutations and sign-offs',
58+
iconEmoji: '👔',
59+
},
60+
{
61+
name: 'Friendly Tone',
62+
description:
63+
'This would adjust messages to reflect a casual, friendly tone. It would incorporate casual language, use emoticons, exclamation points, and other informalities to make the message seem more friendly and approachable.',
64+
iconEmoji: '🤝',
65+
},
66+
{
67+
name: 'Encouraging Tone',
68+
description:
69+
'This tone would be useful for motivation and encouragement. It would include positive words, affirmations, and express support and belief in the recipient.',
70+
iconEmoji: '💪',
71+
},
72+
{
73+
name: 'Empathetic Tone',
74+
description:
75+
'This tone would be utilized to display understanding and empathy. It would involve softer language, acknowledging feelings, and demonstrating compassion and support.',
76+
iconEmoji: '🤲',
77+
},
78+
{
79+
name: 'Neutral Tone',
80+
description:
81+
'For times when you want to maintain an even, unbiased, and objective tone. It would avoid extreme language and emotive words, opting for clear, straightforward communication.',
82+
iconEmoji: '😐',
83+
},
84+
{
85+
name: 'Assertive Tone',
86+
description:
87+
'This tone is beneficial for making clear points, standing ground, or in negotiations. It uses direct language, is confident, and does not mince words.',
88+
iconEmoji: '🔨',
89+
},
90+
{
91+
name: 'Instructive Tone',
92+
description:
93+
'This tone would be useful for tutorials, guides, or other teaching and training materials. It is clear, concise, and walks the reader through steps or processes in a logical manner.',
94+
iconEmoji: '📖',
95+
},
96+
{
97+
name: 'Persuasive Tone',
98+
description:
99+
'This tone can be used when trying to convince someone or argue a point. It uses persuasive language, powerful words, and logical reasoning.',
100+
iconEmoji: '☝️',
101+
},
102+
{
103+
name: 'Sarcastic/Ironic Tone',
104+
description:
105+
'This tone can make the communication more humorous or show an ironic stance. It is harder to implement as it requires the AI to understand nuanced language and may not always be taken as intended by the reader.',
106+
iconEmoji: '😏',
107+
},
108+
{
109+
name: 'Poetic Tone',
110+
description:
111+
'This would add an artistic touch to messages, using figurative language, rhymes, and rhythm to create a more expressive text.',
112+
iconEmoji: '🎭',
113+
},
114+
],
115+
proxyConfig: {
116+
api: 'v1/chat/completions',
117+
servername: 'https://api.openai.com/',
118+
port: '',
119+
},
120+
},
121+
},
122+
appConfig: {
123+
maxFileSize: 10 * 1024 * 1024,
124+
sessionTimeOut: 122,
125+
chatProtocol: {
126+
active: 2,
127+
},
128+
debug: true,
129+
enableForwarding: true,
130+
enableReplying: true,
131+
regexUserName: '^(?=[a-zA-Z])[-a-zA-Z_ ]{3,49}(?<! )$',
132+
endpoints: {
133+
api: 'api.quickblox.com',
134+
chat: 'chat.quickblox.com',
135+
},
136+
// bot server endpoints:
137+
// endpoints: {
138+
// api: 'apitest.quickblox.com',
139+
// chat: 'chattest.quickblox.com',
140+
// },
141+
// on: {
142+
// // eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/require-await
143+
// async sessionExpired(handleResponse: any, retry: any) {
144+
// console.log(
145+
// // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
146+
// `QBconfig sessionExpired handle: ${handleResponse} ${retry}`,
147+
// );
148+
// },
149+
// },
150+
streamManagement: {
151+
enable: true,
152+
},
153+
},
154+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.main-container{
2+
width:100%;
3+
}
4+
5+
.form-col{
6+
border: 10px solid #0275d8;
7+
border-radius: .5rem;
8+
padding:3%;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import './SignIn.scss';
2+
import React, {useEffect, useState} from "react";
3+
import {Link as RouterLink} from "react-router-dom";
4+
import {
5+
Alert,
6+
Avatar,
7+
Box,
8+
Button,
9+
CssBaseline, FormControl, FormControlLabel, FormLabel,
10+
Grid,
11+
Paper, Radio, RadioGroup,
12+
TextField,
13+
Typography
14+
} from "@mui/material";
15+
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
16+
import {
17+
FunctionTypeLoginDataToVoid,
18+
messageValidatorUserName, messageValidatorUserPassword,
19+
nicknameValidate,
20+
passwordValidate
21+
} from "../layout/Auth/Auth";
22+
23+
type LoginProps = {
24+
signInHandler?: FunctionTypeLoginDataToVoid;
25+
errorMessage?: string;
26+
isOnline: boolean
27+
};
28+
29+
const SignIn: React.FC<LoginProps> = ({signInHandler, errorMessage, isOnline}: LoginProps) => {
30+
document.title = 'Login';
31+
const [userName, setUserName] = useState('');
32+
const [userPassword, setUserPassword] = useState('');
33+
const [validInputValues, setValidInputValues] = useState(false);
34+
const [stateTheme, setStateTheme] = useState({theme: 'light'});
35+
const [validValue, setValidValue] = useState(
36+
{
37+
userName: {isTouched: false, isNotValid: true},
38+
userPassword: {isTouched: false, isNotValid: true}
39+
}
40+
);
41+
42+
useEffect(() => {
43+
setValidInputValues(
44+
!validValue.userName.isNotValid && validValue.userName.isTouched
45+
&&
46+
!validValue.userPassword.isNotValid && validValue.userPassword.isTouched
47+
);
48+
}, [validValue]);
49+
50+
const onChangeThemeHandler = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
51+
event.persist();
52+
const {id} = event.target;
53+
setStateTheme(prev => ({...prev, theme: id}))
54+
};
55+
56+
const submitForm = (event: React.SyntheticEvent) => {
57+
event.preventDefault();
58+
const data = {
59+
login: userName,
60+
password: userPassword,
61+
nameTheme: stateTheme.theme
62+
};
63+
64+
if (signInHandler) {
65+
signInHandler(data);
66+
}
67+
};
68+
69+
return (
70+
<Grid container component="main">
71+
<CssBaseline/>
72+
<Grid item xs={12} sm={12} md={12} component={Paper} elevation={6} square>
73+
<Box
74+
sx={{
75+
my: 2,
76+
mx: 8,
77+
display: 'flex',
78+
flexDirection: 'column',
79+
alignItems: 'center',
80+
}}
81+
>
82+
<Avatar sx={{m: 1, bgcolor: 'secondary.main'}}>
83+
<LockOutlinedIcon/>
84+
</Avatar>
85+
<Typography component="h1" variant="h5">
86+
Member sign in
87+
</Typography>
88+
<Box component="form" onSubmit={submitForm} sx={{mt: 1}}>
89+
<TextField
90+
size='small'
91+
margin="normal"
92+
required
93+
fullWidth
94+
id="userName"
95+
label="User Name"
96+
name="userName"
97+
value={userName}
98+
onChange={(e) => {
99+
setUserName(e.target.value);
100+
const validValueUserName = {...validValue.userName};
101+
validValueUserName.isNotValid = !nicknameValidate(e.target.value);
102+
setValidValue({...validValue, userName: validValueUserName})
103+
}}
104+
onFocus={() => setValidValue({
105+
...validValue,
106+
userName: {...validValue.userName, isTouched: true}
107+
})}
108+
autoComplete="off"
109+
helperText={
110+
validValue.userName.isTouched && validValue.userName.isNotValid
111+
?
112+
messageValidatorUserName
113+
:
114+
null
115+
}
116+
/>
117+
<TextField
118+
value={userPassword}
119+
onChange={(e) => {
120+
setUserPassword(e.target.value);
121+
const validValueUserPassword = {...validValue.userPassword};
122+
validValueUserPassword.isNotValid = !passwordValidate(e.target.value);
123+
setValidValue({...validValue, userPassword: validValueUserPassword})
124+
}}
125+
onFocus={() => setValidValue({
126+
...validValue,
127+
userPassword: {...validValue.userPassword, isTouched: true}
128+
})}
129+
size='small'
130+
margin="normal"
131+
required
132+
fullWidth
133+
name="userPassword"
134+
label="Password"
135+
type="password"
136+
id="userPassword"
137+
autoComplete="off"
138+
helperText={
139+
validValue.userPassword.isTouched && validValue.userPassword.isNotValid
140+
?
141+
messageValidatorUserPassword
142+
:
143+
null
144+
}
145+
/>
146+
<FormControl>
147+
<FormLabel id="demo-row-radio-buttons-group-label">Theme</FormLabel>
148+
<RadioGroup
149+
row
150+
aria-labelledby="demo-row-radio-buttons-group-label"
151+
name="row-radio-buttons-group"
152+
>
153+
<FormControlLabel value="light" control={
154+
<Radio
155+
id="light"
156+
name="theme"
157+
checked={stateTheme.theme === 'light'}
158+
onChange={onChangeThemeHandler}/>}
159+
label="Light"/>
160+
<FormControlLabel value="dark" control={
161+
<Radio
162+
id="dark"
163+
name="theme"
164+
checked={stateTheme.theme === 'dark'}
165+
onChange={onChangeThemeHandler}/>}
166+
label="Dark"/>
167+
</RadioGroup>
168+
</FormControl>
169+
{/*Alert*/}
170+
<Button
171+
size='small'
172+
type="submit"
173+
fullWidth
174+
variant="contained"
175+
sx={{mt: 3, mb: 2}}
176+
disabled={!validInputValues || !isOnline}
177+
>
178+
Sign In
179+
</Button>
180+
<Grid container
181+
direction="row"
182+
justifyContent="center"
183+
alignItems="center"
184+
>
185+
<RouterLink to='/sign-up'>
186+
{"Sign Up"}
187+
</RouterLink>
188+
</Grid>
189+
{errorMessage || !isOnline
190+
?
191+
<Grid container
192+
direction="row"
193+
justifyContent="center"
194+
alignItems="center"
195+
>
196+
<Alert severity="error">{!isOnline ? 'No connection.' : errorMessage}</Alert>
197+
</Grid>
198+
:
199+
null
200+
}
201+
</Box>
202+
</Box>
203+
</Grid>
204+
</Grid>
205+
);
206+
}
207+
208+
export default SignIn;

‎Articles/js/react-chat-ui-kit-demo-app/src/SignUp/SignUp.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import './SignUp.scss';
2+
import React, {useEffect, useState} from "react";
3+
import {Link as RouterLink} from "react-router-dom";
4+
import {
5+
Alert,
6+
Avatar,
7+
Box,
8+
Button,
9+
CssBaseline,
10+
Grid,
11+
Paper,
12+
TextField,
13+
Typography
14+
} from "@mui/material";
15+
import AppRegistrationOutlinedIcon from '@mui/icons-material/AppRegistrationOutlined';
16+
import {
17+
fullNameValidate,
18+
FunctionTypeLoginDataToVoid, messageValidatorFullName,
19+
messageValidatorUserName, messageValidatorUserPassword,
20+
nicknameValidate,
21+
passwordValidate
22+
} from "../layout/Auth/Auth";
23+
import {UserData} from "../QBHeplers";
24+
25+
type SignUpProps = {
26+
signUpHandler?: FunctionTypeLoginDataToVoid;
27+
errorMessage?: string;
28+
isOnline: boolean;
29+
};
30+
31+
const SignUp: React.FC<SignUpProps> = ({signUpHandler, errorMessage, isOnline}: SignUpProps) => {
32+
document.title = 'Login';
33+
const [userName, setUserName] = useState('');
34+
const [fullName, setFullName] = useState('');
35+
const [userPassword, setUserPassword] = useState('');
36+
const [validInputValues, setValidInputValues] = useState(false);
37+
const [validValue, setValidValue] = useState(
38+
{
39+
userName: {isTouched: false, isNotValid: true},
40+
fullName: {isTouched: false, isNotValid: true},
41+
userPassword: {isTouched: false, isNotValid: true}
42+
}
43+
);
44+
45+
46+
useEffect( ()=> {
47+
setValidInputValues(
48+
!validValue.userName.isNotValid && validValue.userName.isTouched
49+
&&
50+
!validValue.fullName.isNotValid && validValue.fullName.isTouched
51+
&&
52+
!validValue.userPassword.isNotValid && validValue.userPassword.isTouched
53+
);
54+
}, [validValue]);
55+
56+
57+
const submitForm = (event: React.SyntheticEvent) => {
58+
event.preventDefault();
59+
const data: UserData = {
60+
login: userName,
61+
password: userPassword,
62+
fullName: fullName,
63+
};
64+
65+
if (signUpHandler) {
66+
signUpHandler(data);
67+
}
68+
};
69+
70+
return (
71+
<Grid container component="main">
72+
<CssBaseline/>
73+
<Grid item xs={12} sm={12} md={12} component={Paper} elevation={6} square>
74+
<Box
75+
sx={{
76+
my: 2,
77+
mx: 8,
78+
display: 'flex',
79+
flexDirection: 'column',
80+
alignItems: 'center',
81+
}}
82+
>
83+
<Avatar sx={{m: 1, bgcolor: 'secondary.main'}}>
84+
<AppRegistrationOutlinedIcon/>
85+
</Avatar>
86+
<Typography component="h1" variant="h5">
87+
Member sign in
88+
</Typography>
89+
<Box component="form" onSubmit={submitForm} sx={{mt: 1}}>
90+
<TextField
91+
size='small'
92+
margin="normal"
93+
required
94+
fullWidth
95+
id="userName"
96+
label="User Name"
97+
name="userName"
98+
value={userName}
99+
onChange={(e) => {
100+
setUserName(e.target.value);
101+
const validValueUserName = {...validValue.userName};
102+
validValueUserName.isNotValid = !nicknameValidate(e.target.value);
103+
setValidValue({...validValue, userName: validValueUserName})
104+
}}
105+
onFocus={() => setValidValue({
106+
...validValue,
107+
userName: {...validValue.userName, isTouched: true}
108+
})}
109+
autoComplete="off"
110+
helperText={
111+
validValue.userName.isTouched && validValue.userName.isNotValid
112+
?
113+
messageValidatorUserName
114+
:
115+
null
116+
}
117+
/>
118+
<TextField
119+
size='small'
120+
margin="normal"
121+
required
122+
fullWidth
123+
id="fullName"
124+
label="Full Name"
125+
name="fullName"
126+
value={fullName}
127+
onChange={(e) => {
128+
setFullName(e.target.value);
129+
const validValueFullName = {...validValue.fullName};
130+
validValueFullName.isNotValid = !fullNameValidate(e.target.value);
131+
setValidValue({...validValue, fullName: validValueFullName})
132+
}}
133+
onFocus={() => setValidValue({
134+
...validValue,
135+
fullName: {...validValue.fullName, isTouched: true}
136+
})}
137+
autoComplete="off"
138+
helperText={
139+
validValue.fullName.isTouched && validValue.fullName.isNotValid
140+
?
141+
messageValidatorFullName
142+
:
143+
null
144+
}
145+
/>
146+
<TextField
147+
value={userPassword}
148+
onChange={(e) => {
149+
setUserPassword(e.target.value);
150+
const validValueUserPassword = {...validValue.userPassword};
151+
validValueUserPassword.isNotValid = !passwordValidate(e.target.value);
152+
setValidValue({...validValue, userPassword: validValueUserPassword})
153+
}}
154+
onFocus={() => setValidValue({
155+
...validValue,
156+
userPassword: {...validValue.userPassword, isTouched: true}
157+
})}
158+
size='small'
159+
margin="normal"
160+
required
161+
fullWidth
162+
name="userPassword"
163+
label="Password"
164+
type="password"
165+
id="userPassword"
166+
autoComplete="off"
167+
helperText={
168+
validValue.userPassword.isTouched && validValue.userPassword.isNotValid
169+
?
170+
messageValidatorUserPassword
171+
:
172+
null
173+
}
174+
/>
175+
<Button
176+
size='small'
177+
type="submit"
178+
fullWidth
179+
variant="contained"
180+
sx={{mt: 3, mb: 2}}
181+
disabled={!validInputValues || !isOnline}
182+
>
183+
Sign Up
184+
</Button>
185+
<Grid container
186+
direction="row"
187+
justifyContent="center"
188+
alignItems="center"
189+
>
190+
<RouterLink to='/sign-in'>
191+
{"Sign In"}
192+
</RouterLink>
193+
</Grid>
194+
{errorMessage || !isOnline
195+
?
196+
<Grid container
197+
direction="row"
198+
justifyContent="center"
199+
alignItems="center"
200+
>
201+
<Alert severity="error">{!isOnline ? 'No connection.' : errorMessage}</Alert>
202+
</Grid>
203+
:
204+
null
205+
}
206+
</Box>
207+
</Box>
208+
</Grid>
209+
</Grid>
210+
);
211+
}
212+
213+
export default SignUp;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {DefaultTheme} from "quickblox-react-ui-kit";
2+
3+
export default class CustomTheme extends DefaultTheme {
4+
divider = (): string => '#E7EFFF';
5+
mainText = (): string => '#0B121B';
6+
fontFamily = (): string => 'Roboto';
7+
//
8+
caption = (): string => '#90979F';
9+
chatInput = (): string => '#F7F9FF';
10+
disabledElements = (): string => '#BCC1C5';
11+
dropdownBackground = (): string => '#FFFFFF';
12+
error = (): string => '#FF3B30';
13+
fieldBorder = (): string => '#90979F';
14+
hightlight = (): string => '#FFFDC1';
15+
incomingBackground = (): string => '#E4E6E8';
16+
inputElements = (): string => '#202F3E';
17+
mainBackground = (): string => '#FFFFFF';
18+
mainElements = (): string => '#3978FC';
19+
outgoingBackground = (): string => '#E7EFFF';
20+
secondaryBackground = (): string => '#FFFFFF';
21+
secondaryElements = (): string => '#202F3E';
22+
secondaryText = (): string => '#636D78';
23+
}
Loading
Loading
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom/client';
3+
import {HashRouter} from 'react-router-dom';
4+
import './index.scss';
5+
import App from './App';
6+
import reportWebVitals from './reportWebVitals';
7+
8+
const root = ReactDOM.createRoot(
9+
document.getElementById('root') as HTMLElement
10+
);
11+
root.render(
12+
<React.StrictMode>
13+
<HashRouter>
14+
<App />
15+
</HashRouter>
16+
</React.StrictMode>
17+
);
18+
19+
reportWebVitals();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
.login__wrapper {
2+
width: 100%;
3+
text-align: center;
4+
font-size: 0;
5+
letter-spacing: 0;
6+
height: 100%;
7+
}
8+
9+
.login__container {
10+
display: flex;
11+
height: 100%;
12+
max-width: 640px;
13+
flex-direction: column;
14+
}
15+
16+
.login__inner {
17+
display: flex;
18+
flex-direction: column;
19+
justify-content: center;
20+
padding: 5px;
21+
background: #3978fc;
22+
box-sizing: border-box;
23+
max-width: 520px;
24+
}
25+
26+
.login__logo {
27+
display: block;
28+
width: 100px;
29+
margin: 0 auto 20px;
30+
}
31+
32+
.login__logo img {
33+
width: 100%;
34+
}
35+
36+
.login__top {
37+
margin: 0 0 20px;
38+
text-align: center;
39+
}
40+
41+
.login__top h1 {
42+
display: inline-block;
43+
vertical-align: middle;
44+
font-size: 27px;
45+
color: #FFFFFF;
46+
}
47+
48+
.login_form__row {
49+
position: relative;
50+
height: 56px;
51+
width: 100%;
52+
margin: 0 0 25px;
53+
}
54+
55+
.login_form__row-rbtn {
56+
position: relative;
57+
height: 56px;
58+
width: 100%;
59+
margin: 0 0 8px;
60+
}
61+
62+
.login__button_wrap {
63+
padding: 22px 0 0;
64+
}
65+
66+
.login_form__row input{
67+
border: none;
68+
border-radius: 4px;
69+
width: 100%;
70+
height: 48px;
71+
font-size: 16px;
72+
line-height: 19px;
73+
color: #4A4A4A;
74+
padding: 7px 0 0 15px;
75+
border-bottom: 1px solid #C5CACE;
76+
}
77+
78+
.login_form__row-rbtn label {
79+
border: none;
80+
width: 100%;
81+
height: 26px;
82+
font-size: 16px;
83+
line-height: 19px;
84+
color: #ffffff;
85+
padding: 7px 0 0 15px;
86+
}
87+
88+
.login_form__row-link a {
89+
border: none;
90+
width: 100%;
91+
height: 26px;
92+
font-size: 16px;
93+
line-height: 19px;
94+
color: #ffffff;
95+
text-decoration: underline;
96+
}
97+
98+
.login_form__row.filled input {
99+
border-bottom: 2px solid #17D04B;
100+
}
101+
102+
.login_form__row.filled label {
103+
top: 0;
104+
font-size: 12px;
105+
line-height: 14px;
106+
color: #17D04B;
107+
}
108+
109+
.login_form__row.error input {
110+
border-bottom: 2px solid red !important;
111+
}
112+
113+
.login_form__row--error span{
114+
top: 0;
115+
font-size: 12px;
116+
line-height: 14px;
117+
color: #ffd421 !important;
118+
}
119+
120+
.m-login__button {
121+
width: 100%;
122+
}
123+
124+
.login__button:active {
125+
background-color: #3dbb42;
126+
}
127+
128+
.login__button:disabled {
129+
background-color: #80E491 !important;
130+
}
131+
132+
.login__footer {
133+
flex: 0 0 100px;
134+
135+
text-align: left;
136+
padding: 16px 20px 0;
137+
}
138+
139+
.login__footer:after {
140+
content: '';
141+
display: table;
142+
clear: both;
143+
}
144+
145+
.footer__logo_wrap {
146+
float: left;
147+
}
148+
149+
.footer__logo {
150+
display: inline-block;
151+
vertical-align: middle;
152+
margin: 0 6px 0 0;
153+
}
154+
155+
.footer__logo_wrap p {
156+
font-size: 13px;
157+
line-height: 15px;
158+
color: #C5CACE;
159+
display: inline-block;
160+
vertical-align: middle;
161+
}
162+
163+
.footer__version {
164+
float: right;
165+
font-size: 13px;
166+
line-height: 15px;
167+
color: #C5CACE;
168+
}
169+
170+
@media screen and (min-width: 440px) {
171+
.login__wrapper {
172+
display: flex;
173+
justify-content: center;
174+
align-items: center;
175+
}
176+
177+
.login__container {
178+
height: auto;
179+
}
180+
181+
.login__inner {
182+
flex: 0 0;
183+
padding: 5px 50px 50px;
184+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2), 0 1px 3px 0 rgba(0, 0, 0, 0.1);
185+
border-radius: 4px;
186+
}
187+
188+
.login__top {
189+
margin: 0 0 5px 0;
190+
}
191+
192+
.login__logo {
193+
display: inline-block;
194+
vertical-align: middle;
195+
width: 200px;
196+
}
197+
198+
.login__top h1 {
199+
font-size: 22px;
200+
line-height: 25px;
201+
}
202+
203+
h3 {
204+
margin-top: 20px;
205+
margin-bottom: 10px;
206+
max-width: 304px;
207+
font-size: 12px;
208+
line-height: 20px;
209+
text-align: center;
210+
color: #FFFFFF;
211+
}
212+
213+
h2 {
214+
margin-top: 30px;
215+
max-width: 304px;
216+
font-size: 16px;
217+
font-weight: bold;
218+
line-height: 20px;
219+
text-align: center;
220+
color: #FFFFFF;
221+
}
222+
223+
.login__footer {
224+
padding: 16px 0 0;
225+
}
226+
}
227+
228+
229+
.login__form .btn:disabled {
230+
background-color: #99A9C6;
231+
box-shadow: none;
232+
}
233+
234+
.login__form .btn {
235+
border-radius: 4px;
236+
width: 320px;
237+
color: #FFFFFF;
238+
font-size: 18px;
239+
font-weight: 500;
240+
letter-spacing: 0;
241+
line-height: 24px;
242+
text-align: center;
243+
background-color: #3978FC;
244+
box-shadow: 3px 3px 3mm 4px rgba(57, 120, 252, 0.3);
245+
font-family: Open Sans Regular;
246+
border: 1px solid white;
247+
}
248+
249+
.btn:disabled {
250+
cursor: no-drop;
251+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
import './Auth.scss';
3+
import qbLogoGray from "../../assets/img/qblogo-grey.svg";
4+
import {Grid, Typography} from "@mui/material";
5+
import Link from "@mui/material/Link";
6+
import {UserData} from '../../QBHeplers';
7+
import packageJson from '../../../package.json';
8+
9+
export type FunctionTypeLoginDataToVoid = (data: UserData) => void;
10+
11+
export const nicknameValidate = (nickname: string) =>
12+
/^(?=[a-zA-Z])[0-9a-zA-Z]{3,20}$/.test(nickname);
13+
14+
export const fullNameValidate = (fullName: string) =>
15+
/^((?! {2})[0-9a-zA-Z ]){3,20}$/.test(fullName);
16+
17+
export const passwordValidate = (password: string) =>
18+
password.length > 0;
19+
20+
export const messageValidatorUserName = "The field should contain alphanumeric characters only in a range 3 to 20. The first character must be a letter.";
21+
export const messageValidatorFullName = "The field should contain alphanumeric characters only in a range 3 to 20. The first character must be a letter.";
22+
export const messageValidatorUserPassword = "The field cannot be empty.";
23+
24+
25+
interface AuthProps {
26+
children?: React.ReactNode
27+
}
28+
29+
function Copyright(props: any) {
30+
return (
31+
<Typography variant="body2" color="text.secondary" align="center" {...props}>
32+
{'Copyright © '}
33+
<Link color="inherit" href="https://quickblox.com/">
34+
QuickBlox <img alt="QuickBlox" src={qbLogoGray} />
35+
</Link>{' '}
36+
{new Date().getFullYear()}
37+
{'.'}
38+
</Typography>
39+
);
40+
}
41+
42+
const Auth = ({children} : AuthProps) => {
43+
return (
44+
<Grid container
45+
direction="column"
46+
justifyContent="center"
47+
alignItems="center"
48+
>
49+
<div className="login__inner">
50+
<div className="login__top">
51+
<a className="login__logo" href="https://quickblox.com/">
52+
<img alt="QuickBlox" src="https://quickblox.com/wp-content/themes/QuickbloxTheme2021/img/header-logo.svg" />
53+
</a>
54+
<h1>QB UIKit React Sample</h1>
55+
</div>
56+
{children ?? children}
57+
</div>
58+
<div className="login__footer">
59+
<div className="footer__logo_wrap">
60+
<p>Sample React Chat UIKit DemoApp v.{packageJson.version}</p>
61+
<br />
62+
<p>React Chat UIKit v.{packageJson.dependencies["quickblox-react-ui-kit"]}</p>
63+
<br />
64+
<p><Copyright /></p>
65+
</div>
66+
</div>
67+
</Grid>
68+
);
69+
}
70+
71+
export default Auth;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="react-scripts" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ReportHandler } from 'web-vitals';
2+
3+
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4+
if (onPerfEntry && onPerfEntry instanceof Function) {
5+
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6+
getCLS(onPerfEntry);
7+
getFID(onPerfEntry);
8+
getFCP(onPerfEntry);
9+
getLCP(onPerfEntry);
10+
getTTFB(onPerfEntry);
11+
});
12+
}
13+
};
14+
15+
export default reportWebVitals;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// jest-dom adds custom jest matchers for asserting on DOM nodes.
2+
// allows you to do things like:
3+
// expect(element).toHaveTextContent(/react/i)
4+
// learn more: https://github.com/testing-library/jest-dom
5+
import '@testing-library/jest-dom';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
$qb-light-primary: #3978FC;
2+
$qb-light-color-background: #FFFFFF;
3+
$qb-light-color-divider: #E7EFFF;
4+
$qb-light-color-font: #0B121B;
5+
6+
$qb-dark-primary: #202F3E;
7+
$qb-dark-color-background: #414E5B;
8+
$qb-dark-color-divider: #FFFFFF;
9+
$qb-dark-color-font: #3978FC;
10+
11+
$background-overlay-light: rgba(19, 29, 40, .80);
12+
$background-overlay-dark: rgba(144, 151, 159, .80);
13+
$primary-50: #E7EFFF;
14+
$primary-100: #C4D7FE;
15+
$primary-200: #9CBCFE;
16+
$primary-300: #74A1FD;
17+
$primary-400: #578CFC;
18+
$primary-500: #3978FC;
19+
$primary-600: #3370FC;
20+
$primary-700: #2C65FB;
21+
$primary-800: #245BFB;
22+
$primary-900: #1748FA;
23+
$primary-a-100: #FFFFFF;
24+
$primary-a-200: #F7F9FF;
25+
$primary-a-400: #C4CFFF;
26+
$primary-a-700: #ABBAFF;
27+
28+
$secondary-50: #E4E6E8;
29+
$secondary-100: #BCC1C5;
30+
$secondary-200: #90979F;
31+
$secondary-300: #636D78;
32+
$secondary-400: #414E5B;
33+
$secondary-500: #202F3E;
34+
$secondary-600: #1C2A38;
35+
$secondary-700: #182330;
36+
$secondary-800: #131D28;
37+
$secondary-900: #0B121B;
38+
$secondary-a-100: #74A1FD;
39+
$secondary-a-200: #3978FC;
40+
$secondary-a-400: #245BFB;
41+
$secondary-a-700: #0050DC;
42+
43+
$system-green-100: #C8F1D6;
44+
$system-green-200: #A4E7BB;
45+
$system-green-300: #80DDA0;
46+
$system-green-400: #64D68B;
47+
$system-green-500: #49CF77;
48+
49+
$error-100: #FFC4C1;
50+
$error-200: #FF9D98;
51+
$error-300: #FF766E;
52+
$error-400: #FF584F;
53+
$error-500: #FF3B30;
54+
55+
$information: #FDB0FF;
56+
$highlight: #FFFDC1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@import "theme_colors_scheme";
2+
// https://sass-lang.com/documentation/breaking-changes/css-vars
3+
4+
html[data-theme="dark"]{
5+
--primary: #{$primary-500};
6+
--color-background: #{$secondary-900};
7+
--color-background-modal: #{$secondary-200};
8+
--color-background-overlay: #{$secondary-900};
9+
--color-divider: #{$secondary-400};
10+
--color-font: #{$primary-a-100};
11+
--color-icon: #{$primary-500};
12+
13+
14+
--color-background-info: #{$primary-500};
15+
--tertiary-elements: #{$background-overlay-dark};
16+
--main-elements: #{$primary-300};
17+
--secondary-elements: #{$primary-a-100};
18+
--input-elements: #{$secondary-200};
19+
--disabled-elements: #{$secondary-300};
20+
--field-border: #{$secondary-200};
21+
--main-text: #{$primary-a-100};
22+
--secondary-text: #{$secondary-200};
23+
--caption: #{$secondary-100};
24+
--main-background: #{$secondary-500};
25+
--secondary-background: #{$secondary-800};
26+
--incoming-background: #{$secondary-400};
27+
--outgoing-background: #{$primary-500};
28+
--dropdown-background: #{$secondary-400};
29+
--chat-input: #{$secondary-800};
30+
--divider: #{$secondary-400};
31+
--error: #{$error-300};
32+
--hightlight: #{$highlight};
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@import "theme_colors_scheme";
2+
3+
:root{
4+
--primary: #{$primary-500};
5+
--color-background: #{$primary-a-100};
6+
--color-background-modal: #{$primary-100};
7+
--color-background-overlay: #{$secondary-900};
8+
--color-divider: #{$primary-50};
9+
--color-font: #{$secondary-900};
10+
--color-icon: #{$primary-500};
11+
12+
--color-background-info:#{$primary-100};
13+
--tertiary-elements: #{$secondary-300};
14+
--main-elements: #{$primary-500};
15+
--secondary-elements: #{$secondary-500};
16+
--input-elements: #{$secondary-500};
17+
--disabled-elements: #{$secondary-100};
18+
--field-border: #{$secondary-200};
19+
--main-text: #{$secondary-900};
20+
--secondary-text: #{$secondary-300};
21+
--caption: #{$secondary-200};
22+
--main-background: #{$primary-a-100};
23+
--secondary-background: #{$primary-a-100}; // #{$primary-50};
24+
--secondary-background-modal: #{$background-overlay-light};
25+
--incoming-background: #{$secondary-50};
26+
--outgoing-background: #{$primary-50};
27+
--dropdown-background: #{$primary-a-100};
28+
--chat-input: #{$primary-a-200};
29+
--divider: #{$primary-50};
30+
--error: #{$error-500};
31+
--hightlight: #{$highlight};
32+
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@import "theme_light";
2+
@import "theme_dark";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"lib": [
5+
"dom",
6+
"dom.iterable",
7+
"esnext"
8+
],
9+
"allowJs": true,
10+
"skipLibCheck": true,
11+
"esModuleInterop": true,
12+
"allowSyntheticDefaultImports": true,
13+
"strict": true,
14+
"forceConsistentCasingInFileNames": true,
15+
"noFallthroughCasesInSwitch": true,
16+
"module": "esnext",
17+
"moduleResolution": "node",
18+
"resolveJsonModule": true,
19+
"isolatedModules": true,
20+
"noEmit": true,
21+
"jsx": "react-jsx"
22+
},
23+
"include": [
24+
"src",
25+
"node_modules/quickblox-react-ui-kit/global.d.ts",
26+
]
27+
}

0 commit comments

Comments
 (0)
Please sign in to comment.