refactor: migrate chat layout and styles to CSS, remove unused styles and components
This commit is contained in:
parent
0972670cf4
commit
4dc77b7717
33 changed files with 1984 additions and 1253 deletions
|
@ -19,13 +19,13 @@
|
||||||
"botframework-directlinejs": "0.15.1",
|
"botframework-directlinejs": "0.15.1",
|
||||||
"botframework-webchat": "4.15.7",
|
"botframework-webchat": "4.15.7",
|
||||||
"date-fns": "2.30.0",
|
"date-fns": "2.30.0",
|
||||||
|
|
||||||
"lucide-react": "0.454.0",
|
"lucide-react": "0.454.0",
|
||||||
"nativewind": "2.0.10",
|
"nativewind": "2.0.10",
|
||||||
"postcss": "8.4.35",
|
"postcss": "8.4.35",
|
||||||
|
"react": "18.3.1",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-hook-form": "7.53.2",
|
"react-hook-form": "7.53.2",
|
||||||
"react": "18.3.1",
|
"react-router-dom": "^7.4.1",
|
||||||
"tailwindcss": "3.4.1",
|
"tailwindcss": "3.4.1",
|
||||||
"uuid": "11.0.3",
|
"uuid": "11.0.3",
|
||||||
"zod": "3.21.4"
|
"zod": "3.21.4"
|
||||||
|
@ -34,9 +34,9 @@
|
||||||
"@babel/core": "7.18.6",
|
"@babel/core": "7.18.6",
|
||||||
"@tauri-apps/cli": "2",
|
"@tauri-apps/cli": "2",
|
||||||
"@types/jest": "29.5.12",
|
"@types/jest": "29.5.12",
|
||||||
|
"@types/react": "18.3.1",
|
||||||
"@types/react-dom": "18.3.1",
|
"@types/react-dom": "18.3.1",
|
||||||
"@types/react-test-renderer": "18.0.7",
|
"@types/react-test-renderer": "18.0.7",
|
||||||
"@types/react": "18.3.1",
|
|
||||||
"@vitejs/plugin-react": "4.3.4",
|
"@vitejs/plugin-react": "4.3.4",
|
||||||
"copy-webpack-plugin": "12.0.2",
|
"copy-webpack-plugin": "12.0.2",
|
||||||
"jest": "29.2.1",
|
"jest": "29.2.1",
|
||||||
|
|
57
pnpm-lock.yaml
generated
57
pnpm-lock.yaml
generated
|
@ -53,6 +53,9 @@ importers:
|
||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: 7.53.2
|
specifier: 7.53.2
|
||||||
version: 7.53.2(react@18.3.1)
|
version: 7.53.2(react@18.3.1)
|
||||||
|
react-router-dom:
|
||||||
|
specifier: ^7.4.1
|
||||||
|
version: 7.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: 3.4.1
|
specifier: 3.4.1
|
||||||
version: 3.4.1
|
version: 3.4.1
|
||||||
|
@ -1292,6 +1295,9 @@ packages:
|
||||||
'@types/babel__traverse@7.20.7':
|
'@types/babel__traverse@7.20.7':
|
||||||
resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
|
resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==}
|
||||||
|
|
||||||
|
'@types/cookie@0.6.0':
|
||||||
|
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||||
|
|
||||||
'@types/eslint-scope@3.7.7':
|
'@types/eslint-scope@3.7.7':
|
||||||
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
||||||
|
|
||||||
|
@ -1741,6 +1747,10 @@ packages:
|
||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
|
cookie@1.0.2:
|
||||||
|
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
copy-webpack-plugin@12.0.2:
|
copy-webpack-plugin@12.0.2:
|
||||||
resolution: {integrity: sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==}
|
resolution: {integrity: sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==}
|
||||||
engines: {node: '>= 18.12.0'}
|
engines: {node: '>= 18.12.0'}
|
||||||
|
@ -2976,6 +2986,23 @@ packages:
|
||||||
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
react-router-dom@7.4.1:
|
||||||
|
resolution: {integrity: sha512-L3/4tig0Lvs6m6THK0HRV4eHUdpx0dlJasgCxXKnavwhh4tKYgpuZk75HRYNoRKDyDWi9QgzGXsQ1oQSBlWpAA==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18'
|
||||||
|
react-dom: '>=18'
|
||||||
|
|
||||||
|
react-router@7.4.1:
|
||||||
|
resolution: {integrity: sha512-Vmizn9ZNzxfh3cumddqv3kLOKvc7AskUT0dC1prTabhiEi0U4A33LmkDOJ79tXaeSqCqMBXBU/ySX88W85+EUg==}
|
||||||
|
engines: {node: '>=20.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18'
|
||||||
|
react-dom: '>=18'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-say@2.1.0:
|
react-say@2.1.0:
|
||||||
resolution: {integrity: sha512-TSGEA1GQuxa3nc9PEO5fvS3XjM1GGXPUTmcAXV2zlxA1w/vLE+gy0eGJPDYg1ovWmkbe+JZamr7BncwqkicKYg==}
|
resolution: {integrity: sha512-TSGEA1GQuxa3nc9PEO5fvS3XjM1GGXPUTmcAXV2zlxA1w/vLE+gy0eGJPDYg1ovWmkbe+JZamr7BncwqkicKYg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -3146,6 +3173,9 @@ packages:
|
||||||
serialize-javascript@6.0.2:
|
serialize-javascript@6.0.2:
|
||||||
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
|
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
|
||||||
|
|
||||||
|
set-cookie-parser@2.7.1:
|
||||||
|
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -3343,6 +3373,9 @@ packages:
|
||||||
tslib@2.8.1:
|
tslib@2.8.1:
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
|
turbo-stream@2.4.0:
|
||||||
|
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
|
||||||
|
|
||||||
type-detect@4.0.8:
|
type-detect@4.0.8:
|
||||||
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
@ -4973,6 +5006,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.27.0
|
'@babel/types': 7.27.0
|
||||||
|
|
||||||
|
'@types/cookie@0.6.0': {}
|
||||||
|
|
||||||
'@types/eslint-scope@3.7.7':
|
'@types/eslint-scope@3.7.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/eslint': 9.6.1
|
'@types/eslint': 9.6.1
|
||||||
|
@ -5608,6 +5643,8 @@ snapshots:
|
||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
|
cookie@1.0.2: {}
|
||||||
|
|
||||||
copy-webpack-plugin@12.0.2(webpack@5.98.0):
|
copy-webpack-plugin@12.0.2(webpack@5.98.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
|
@ -6970,6 +7007,22 @@ snapshots:
|
||||||
|
|
||||||
react-refresh@0.14.2: {}
|
react-refresh@0.14.2: {}
|
||||||
|
|
||||||
|
react-router-dom@7.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
react-router: 7.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
||||||
|
react-router@7.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
'@types/cookie': 0.6.0
|
||||||
|
cookie: 1.0.2
|
||||||
|
react: 18.3.1
|
||||||
|
set-cookie-parser: 2.7.1
|
||||||
|
turbo-stream: 2.4.0
|
||||||
|
optionalDependencies:
|
||||||
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
|
|
||||||
react-say@2.1.0(react@18.3.1):
|
react-say@2.1.0(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.15.4
|
'@babel/runtime': 7.15.4
|
||||||
|
@ -7171,6 +7224,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
randombytes: 2.1.0
|
randombytes: 2.1.0
|
||||||
|
|
||||||
|
set-cookie-parser@2.7.1: {}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
shebang-regex: 3.0.0
|
shebang-regex: 3.0.0
|
||||||
|
@ -7361,6 +7416,8 @@ snapshots:
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
turbo-stream@2.4.0: {}
|
||||||
|
|
||||||
type-detect@4.0.8: {}
|
type-detect@4.0.8: {}
|
||||||
|
|
||||||
type-fest@0.21.3: {}
|
type-fest@0.21.3: {}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View, Text, TextInput, TouchableOpacity, ActivityIndicator, StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
interface UserAuthFormProps {
|
interface UserAuthFormProps {
|
||||||
// Add any props you need
|
// Add any props you need
|
||||||
|
@ -17,90 +16,50 @@ export function UserAuthForm({ }: UserAuthFormProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<div className="auth-container">
|
||||||
<View style={styles.form}>
|
<div className="auth-form">
|
||||||
<View style={styles.inputContainer}>
|
<div className="input-container">
|
||||||
<TextInput
|
<input
|
||||||
style={styles.input}
|
type="email"
|
||||||
placeholder="name@example.com"
|
className="auth-input"
|
||||||
keyboardType="email-address"
|
placeholder="name@example.com"
|
||||||
autoCapitalize="none"
|
autoCapitalize="off"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
autoCorrect={false}
|
autoCorrect="off"
|
||||||
editable={!isLoading}
|
disabled={isLoading}
|
||||||
value={email}
|
value={email}
|
||||||
onChangeText={setEmail}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</div>
|
||||||
<TouchableOpacity
|
<button
|
||||||
style={styles.button}
|
className="auth-button"
|
||||||
onPress={onSubmit}
|
onClick={onSubmit}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{isLoading && <ActivityIndicator style={styles.spinner} color="#ffffff" />}
|
{isLoading ? (
|
||||||
<Text style={styles.buttonText}>Sign In with Email</Text>
|
<span className="auth-spinner" />
|
||||||
</TouchableOpacity>
|
) : (
|
||||||
</View>
|
'Sign In with Email'
|
||||||
<View style={styles.divider}>
|
)}
|
||||||
<View style={styles.dividerLine} />
|
</button>
|
||||||
<Text style={styles.dividerText}>Or continue with</Text>
|
</div>
|
||||||
<View style={styles.dividerLine} />
|
<div className="auth-divider">
|
||||||
</View>
|
<div className="divider-line" />
|
||||||
<TouchableOpacity
|
<span className="divider-text">Or continue with</span>
|
||||||
style={styles.githubButton}
|
<div className="divider-line" />
|
||||||
onPress={() => {/* Add GitHub sign in logic */}}
|
</div>
|
||||||
|
<button
|
||||||
|
className="github-button"
|
||||||
|
onClick={() => {}}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<ActivityIndicator style={styles.spinner} color="#000000" />
|
<span className="auth-spinner" />
|
||||||
) : (
|
) : (
|
||||||
<Text>GitHub Icon</Text> // Replace with actual GitHub icon
|
<span>GitHub Icon</span>
|
||||||
)}
|
)}
|
||||||
<Text style={styles.githubButtonText}>GitHub</Text>
|
<span>GitHub</span>
|
||||||
</TouchableOpacity>
|
</button>
|
||||||
</View>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
gap: 20,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
gap: 10,
|
|
||||||
},
|
|
||||||
inputContainer: {
|
|
||||||
// Add styles for input container
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
// Add styles for input
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
// Add styles for button
|
|
||||||
},
|
|
||||||
buttonText: {
|
|
||||||
// Add styles for button text
|
|
||||||
},
|
|
||||||
spinner: {
|
|
||||||
marginRight: 8,
|
|
||||||
},
|
|
||||||
divider: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
dividerLine: {
|
|
||||||
flex: 1,
|
|
||||||
height: 1,
|
|
||||||
backgroundColor: '#e0e0e0',
|
|
||||||
},
|
|
||||||
dividerText: {
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
// Add styles for divider text
|
|
||||||
},
|
|
||||||
githubButton: {
|
|
||||||
// Add styles for GitHub button
|
|
||||||
},
|
|
||||||
githubButtonText: {
|
|
||||||
// Add styles for GitHub button text
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,157 +1,66 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View, Text, Image, StyleSheet, TouchableOpacity, ScrollView, Alert } from 'react-native';
|
import { UserAuthForm } from './components/user-auth-form';
|
||||||
import { Auth as ZitadelAuth } from '@zitadel/react';
|
|
||||||
|
|
||||||
const AuthenticationScreen = () => {
|
const AuthenticationScreen = () => {
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
const auth = new ZitadelAuth({
|
localStorage.setItem('authToken', 'dummy-token');
|
||||||
clientId: 'YOUR_CLIENT_ID',
|
setIsAuthenticated(true);
|
||||||
issuer: 'YOUR_ZITADEL_ISSUER_URL',
|
alert('Login Successful');
|
||||||
redirectUri: 'YOUR_REDIRECT_URI',
|
|
||||||
scopes: ['openid', 'profile', 'email'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await auth.authorize();
|
|
||||||
if (result?.accessToken) {
|
|
||||||
await AsyncStorage.setItem('authToken', result.accessToken);
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
Alert.alert('Login Successful', 'You are now authenticated.');
|
|
||||||
} else {
|
|
||||||
Alert.alert('Login Failed', 'Unable to retrieve access token.');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Login error:', error);
|
console.error('Login error:', error);
|
||||||
Alert.alert('Login Error', 'An error occurred during login.');
|
alert('Login Error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await AsyncStorage.removeItem('authToken');
|
localStorage.removeItem('authToken');
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
Alert.alert('Logout Successful', 'You are now logged out.');
|
alert('Logout Successful');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Logout error:', error);
|
console.error('Logout error:', error);
|
||||||
Alert.alert('Logout Error', 'An error occurred during logout.');
|
alert('Logout Error');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<div className="auth-screen">
|
||||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
<div className="auth-content">
|
||||||
<View style={styles.imageContainer}>
|
<button
|
||||||
</View>
|
className="auth-login-button"
|
||||||
|
onClick={isAuthenticated ? handleLogout : handleLogin}
|
||||||
|
>
|
||||||
|
{isAuthenticated ? 'Logout' : 'Login'}
|
||||||
|
</button>
|
||||||
|
|
||||||
<View style={styles.contentContainer}>
|
<div className="auth-left-panel">
|
||||||
<TouchableOpacity style={styles.loginButton} onPress={isAuthenticated ? handleLogout : handleLogin}>
|
<div className="auth-logo">
|
||||||
<Text style={styles.loginButtonText}>{isAuthenticated ? 'Logout' : 'Login'}</Text>
|
<h1>Welcome to General Bots Online</h1>
|
||||||
</TouchableOpacity>
|
</div>
|
||||||
|
<div className="auth-quote">
|
||||||
|
<p>"Errar é Humano."</p>
|
||||||
|
<p>General Bots</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<View style={styles.leftPanel}>
|
<div className="auth-form-container">
|
||||||
<View style={styles.logoContainer}>
|
<div className="auth-form-header">
|
||||||
{/* Replace with your logo component */}
|
<h2>Create an account</h2>
|
||||||
<Text style={styles.logoText}>Welcome to General Bots Online</Text>
|
<p>Enter your email below to create your account</p>
|
||||||
</View>
|
</div>
|
||||||
<View style={styles.quoteContainer}>
|
|
||||||
<Text style={styles.quoteText}>
|
|
||||||
"Errar é Humano."
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.quoteAuthor}>General Bots</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.formContainer}>
|
|
||||||
<View style={styles.formHeader}>
|
|
||||||
<Text style={styles.formTitle}>Create an account</Text>
|
|
||||||
<Text style={styles.formSubtitle}>
|
|
||||||
Enter your email below to create your account
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text style={styles.termsText}>
|
<UserAuthForm />
|
||||||
By clicking continue, you agree to our Terms of Service and Privacy Policy.
|
|
||||||
</Text>
|
<p className="auth-terms">
|
||||||
</View>
|
By clicking continue, you agree to our Terms of Service and Privacy Policy.
|
||||||
</View>
|
</p>
|
||||||
</ScrollView>
|
</div>
|
||||||
</SafeAreaView>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
export default AuthenticationScreen;
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#fff',
|
|
||||||
},
|
|
||||||
scrollContent: {
|
|
||||||
flexGrow: 1,
|
|
||||||
},
|
|
||||||
imageContainer: {
|
|
||||||
// ... styles for image container
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
width: '100%',
|
|
||||||
height: 300,
|
|
||||||
resizeMode: 'cover',
|
|
||||||
},
|
|
||||||
contentContainer: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
loginButton: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 16,
|
|
||||||
right: 16,
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
loginButtonText: {
|
|
||||||
color: '#000',
|
|
||||||
},
|
|
||||||
leftPanel: {
|
|
||||||
// ... styles for left panel
|
|
||||||
},
|
|
||||||
logoContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
logoText: {
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
quoteContainer: {
|
|
||||||
marginTop: 'auto',
|
|
||||||
},
|
|
||||||
quoteText: {
|
|
||||||
fontSize: 16,
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
quoteAuthor: {
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
formContainer: {
|
|
||||||
// ... styles for form container
|
|
||||||
},
|
|
||||||
formHeader: {
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
formTitle: {
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
formSubtitle: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: '#666',
|
|
||||||
},
|
|
||||||
termsText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 12,
|
|
||||||
color: '#666',
|
|
||||||
marginTop: 16,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default AuthenticationScreen;
|
|
||||||
|
|
|
@ -1,27 +1,23 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Platform } from 'react-native';
|
|
||||||
import { PersonSelector } from './selector/person-selector';
|
import { PersonSelector } from './selector/person-selector';
|
||||||
import { ProjectorView } from './projector/projector-view';
|
import { ProjectorView } from './projector/projector-view';
|
||||||
import { ChatWindow } from './chat/chat-window';
|
import { ChatWindow } from './chat/chat-window';
|
||||||
import { layoutStyles } from '../styles/layout.styles';
|
import '../../styles/layout.css';
|
||||||
|
|
||||||
export function ChatLayout() {
|
export function ChatLayout() {
|
||||||
return (
|
return (
|
||||||
<View style={[
|
<div className="chat-layout">
|
||||||
layoutStyles.container,
|
<div className="sidebar">
|
||||||
Platform.OS === 'web' && { height: '100vh' }
|
|
||||||
]}>
|
|
||||||
<View style={layoutStyles.sidebar}>
|
|
||||||
<PersonSelector />
|
<PersonSelector />
|
||||||
</View>
|
</div>
|
||||||
<View style={layoutStyles.mainContent}>
|
<div className="main-content">
|
||||||
<View style={layoutStyles.projector}>
|
<div className="projector">
|
||||||
<ProjectorView />
|
<ProjectorView />
|
||||||
</View>
|
</div>
|
||||||
<View style={layoutStyles.chatArea}>
|
<div className="chat-area">
|
||||||
<ChatWindow />
|
<ChatWindow />
|
||||||
</View>
|
</div>
|
||||||
</View>
|
</div>
|
||||||
</View>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,24 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, TouchableOpacity } from 'react-native';
|
|
||||||
import { MoreVertical } from 'lucide-react-native';
|
|
||||||
import { useChat } from '../../providers/chat-provider';
|
import { useChat } from '../../providers/chat-provider';
|
||||||
import { chatStyles } from '../../styles/chat.styles';
|
import '../../styles/chat.css';
|
||||||
|
|
||||||
export function ChatHeader() {
|
export function ChatHeader() {
|
||||||
const { instance } = useChat();
|
const { instance } = useChat();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={chatStyles.header}>
|
<div className="chat-header">
|
||||||
<View style={chatStyles.headerContent}>
|
<div className="header-content">
|
||||||
<Text style={chatStyles.headerTitle}>
|
<h2 className="header-title">
|
||||||
{instance?.name || 'Chat'}
|
{instance?.name || 'Chat'}
|
||||||
</Text>
|
</h2>
|
||||||
<Text style={chatStyles.headerSubtitle}>
|
<span className="header-subtitle">Online</span>
|
||||||
Online
|
</div>
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<TouchableOpacity style={chatStyles.headerButton}>
|
<button className="header-button">
|
||||||
<MoreVertical color="#00f3ff" size={24} />
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
</TouchableOpacity>
|
<path d="M12 10a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm14 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/>
|
||||||
</View>
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, TextInput, TouchableOpacity, Animated } from 'react-native';
|
|
||||||
import { Send, Paperclip, Mic, Smile } from 'lucide-react-native';
|
|
||||||
import { EmojiPicker } from '../ui/emoji-picker';
|
import { EmojiPicker } from '../ui/emoji-picker';
|
||||||
import { useChat } from '../../providers/chat-provider';
|
import { useChat } from '../../providers/chat-provider';
|
||||||
import { useSound } from '../../providers/sound-provider';
|
import { useSound } from '../../providers/sound-provider';
|
||||||
import { chatStyles } from '../../styles/chat.styles';
|
import '../../styles/chat.css';
|
||||||
|
|
||||||
export function ChatInput() {
|
export function ChatInput() {
|
||||||
const [message, setMessage] = React.useState('');
|
const [message, setMessage] = React.useState('');
|
||||||
const [showEmoji, setShowEmoji] = React.useState(false);
|
const [showEmoji, setShowEmoji] = React.useState(false);
|
||||||
const pulseAnim = React.useRef(new Animated.Value(1)).current;
|
|
||||||
const { sendActivity } = useChat();
|
const { sendActivity } = useChat();
|
||||||
const { playSound } = useSound();
|
const { playSound } = useSound();
|
||||||
const typingTimeout = React.useRef<NodeJS.Timeout>();
|
|
||||||
|
|
||||||
const handleKeyPress = () => {
|
|
||||||
if (typingTimeout.current) {
|
|
||||||
clearTimeout(typingTimeout.current);
|
|
||||||
}
|
|
||||||
typingTimeout.current = setTimeout(() => {
|
|
||||||
playSound('typing');
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSend = () => {
|
const handleSend = () => {
|
||||||
if (!message.trim()) return;
|
if (!message.trim()) return;
|
||||||
|
@ -40,55 +27,40 @@ export function ChatInput() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={chatStyles.inputContainer}>
|
<div className="input-container">
|
||||||
<TouchableOpacity
|
<button className="icon-button" onClick={() => playSound('click')}>
|
||||||
style={chatStyles.iconButton}
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
onPress={() => playSound('click')}
|
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/>
|
||||||
>
|
</svg>
|
||||||
<Paperclip color="#00f3ff" size={24} />
|
</button>
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
<button className="icon-button" onClick={() => setShowEmoji(true)}>
|
||||||
style={chatStyles.iconButton}
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
onPress={() => {
|
<path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM8 14s1.5 2 4 2 4-2 4-2M9 9h.01M15 9h.01"/>
|
||||||
playSound('click');
|
</svg>
|
||||||
setShowEmoji(true);
|
</button>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Smile color="#00f3ff" size={24} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<TextInput
|
<textarea
|
||||||
value={message}
|
value={message}
|
||||||
onChangeText={setMessage}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
onKeyPress={handleKeyPress}
|
className="chat-input"
|
||||||
style={[
|
|
||||||
chatStyles.input,
|
|
||||||
{ borderColor: message ? '#00f3ff' : '#333' }
|
|
||||||
]}
|
|
||||||
placeholder="Type a message..."
|
placeholder="Type a message..."
|
||||||
placeholderTextColor="#666"
|
|
||||||
multiline
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{message.trim().length > 0 ? (
|
{message.trim().length > 0 ? (
|
||||||
<Animated.View style={{ transform: [{ scale: pulseAnim }] }}>
|
<button className="send-button" onClick={handleSend}>
|
||||||
<TouchableOpacity
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
style={[chatStyles.iconButton, chatStyles.sendButton]}
|
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
|
||||||
onPress={handleSend}
|
</svg>
|
||||||
>
|
</button>
|
||||||
<Send color="#00f3ff" size={24} />
|
|
||||||
</TouchableOpacity>
|
|
||||||
</Animated.View>
|
|
||||||
) : (
|
) : (
|
||||||
<TouchableOpacity
|
<button className="icon-button" onClick={() => playSound('click')}>
|
||||||
style={chatStyles.iconButton}
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
onPress={() => playSound('click')}
|
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2M12 19v4"/>
|
||||||
>
|
</svg>
|
||||||
<Mic color="#00f3ff" size={24} />
|
</button>
|
||||||
</TouchableOpacity>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</div>
|
||||||
|
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
visible={showEmoji}
|
visible={showEmoji}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
|
||||||
import { MessageList } from './message-list';
|
import { MessageList } from './message-list';
|
||||||
import { ChatInput } from './chat-input';
|
import { ChatInput } from './chat-input';
|
||||||
import { ChatHeader } from './chat-header';
|
import { ChatHeader } from './chat-header';
|
||||||
import { useChat } from '../../providers/chat-provider';
|
import { useChat } from '../../providers/chat-provider';
|
||||||
import { Message } from '../../types';
|
import { Message } from '../../types';
|
||||||
import { chatStyles } from '../../styles/chat.styles';
|
import '../../styles/chat.css';
|
||||||
|
|
||||||
export function ChatWindow() {
|
export function ChatWindow() {
|
||||||
const { line } = useChat();
|
const { line } = useChat();
|
||||||
|
@ -14,7 +13,7 @@ export function ChatWindow() {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!line) return;
|
if (!line) return;
|
||||||
|
|
||||||
const subscription = line.activity$.subscribe(activity => {
|
const subscription = line.activity$.subscribe((activity: any) => {
|
||||||
if (activity.type === 'message') {
|
if (activity.type === 'message') {
|
||||||
setMessages(prev => [...prev, activity as Message]);
|
setMessages(prev => [...prev, activity as Message]);
|
||||||
}
|
}
|
||||||
|
@ -24,10 +23,10 @@ export function ChatWindow() {
|
||||||
}, [line]);
|
}, [line]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={chatStyles.window}>
|
<div className="chat-window">
|
||||||
<ChatHeader />
|
<ChatHeader />
|
||||||
<MessageList messages={messages} />
|
<MessageList messages={messages} />
|
||||||
<ChatInput />
|
<ChatInput />
|
||||||
</View>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView, View, Text } from 'react-native';
|
|
||||||
import { useChat } from '../../providers/chat-provider';
|
import { useChat } from '../../providers/chat-provider';
|
||||||
import { useSound } from '../../providers/sound-provider';
|
import { useSound } from '../../providers/sound-provider';
|
||||||
import { Message } from '../../types';
|
import { Message } from '../../types';
|
||||||
import { chatStyles } from '../../styles/chat.styles';
|
import '../../styles/chat.css';
|
||||||
|
|
||||||
interface MessageListProps {
|
interface MessageListProps {
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MessageList({ messages }: MessageListProps) {
|
export function MessageList({ messages }: MessageListProps) {
|
||||||
const scrollViewRef = React.useRef<ScrollView>(null);
|
const scrollRef = React.useRef<HTMLDivElement>(null);
|
||||||
const { user } = useChat();
|
const { user } = useChat();
|
||||||
const { playSound } = useSound();
|
const { playSound } = useSound();
|
||||||
const prevMessagesLength = React.useRef(messages.length);
|
const prevMessagesLength = React.useRef(messages.length);
|
||||||
|
@ -21,33 +20,26 @@ export function MessageList({ messages }: MessageListProps) {
|
||||||
if (lastMessage.from.id !== user.id) {
|
if (lastMessage.from.id !== user.id) {
|
||||||
playSound('receive');
|
playSound('receive');
|
||||||
}
|
}
|
||||||
scrollViewRef.current?.scrollToEnd({ animated: true });
|
scrollRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
}
|
}
|
||||||
prevMessagesLength.current = messages.length;
|
prevMessagesLength.current = messages.length;
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView
|
<div className="message-list" ref={scrollRef}>
|
||||||
ref={scrollViewRef}
|
|
||||||
style={chatStyles.messageList}
|
|
||||||
contentContainerStyle={chatStyles.messageListContent}
|
|
||||||
>
|
|
||||||
{messages.map((message, index) => (
|
{messages.map((message, index) => (
|
||||||
<View
|
<div
|
||||||
key={`${message.id}-${index}`}
|
key={`${message.id}-${index}`}
|
||||||
style={[
|
className={`message-container ${
|
||||||
chatStyles.messageContainer,
|
message.from.id === user.id ? 'user-message' : 'bot-message'
|
||||||
message.from.id === user.id
|
}`}
|
||||||
? chatStyles.userMessage
|
|
||||||
: chatStyles.botMessage
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Text style={chatStyles.messageText}>{message.text}</Text>
|
<p className="message-text">{message.text}</p>
|
||||||
<Text style={chatStyles.messageTime}>
|
<span className="message-time">
|
||||||
{new Date(message.timestamp).toLocaleTimeString()}
|
{new Date(message.timestamp).toLocaleTimeString()}
|
||||||
</Text>
|
</span>
|
||||||
</View>
|
</div>
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Image } from 'react-native';
|
import '../../styles/projector.css';
|
||||||
import { projectorStyles } from '../../styles/projector.styles';
|
|
||||||
|
|
||||||
interface ImageViewerProps {
|
interface ImageViewerProps {
|
||||||
url: string;
|
url: string;
|
||||||
|
@ -8,12 +7,8 @@ interface ImageViewerProps {
|
||||||
|
|
||||||
export function ImageViewer({ url }: ImageViewerProps) {
|
export function ImageViewer({ url }: ImageViewerProps) {
|
||||||
return (
|
return (
|
||||||
<View style={projectorStyles.imageContainer}>
|
<div className="image-container">
|
||||||
<Image
|
<img src={url} className="projector-image" alt="Projected content" />
|
||||||
source={{ uri: url }}
|
</div>
|
||||||
style={projectorStyles.image}
|
|
||||||
resizeMode="contain"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ScrollView } from 'react-native';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import Markdown from 'react-native-markdown-display';
|
import '../../styles/projector.css';
|
||||||
import { projectorStyles } from '../../styles/projector.styles';
|
|
||||||
|
|
||||||
interface MarkdownViewerProps {
|
interface MarkdownViewerProps {
|
||||||
content: string;
|
content: string;
|
||||||
|
@ -9,10 +8,8 @@ interface MarkdownViewerProps {
|
||||||
|
|
||||||
export function MarkdownViewer({ content }: MarkdownViewerProps) {
|
export function MarkdownViewer({ content }: MarkdownViewerProps) {
|
||||||
return (
|
return (
|
||||||
<ScrollView style={projectorStyles.markdownContainer}>
|
<div className="markdown-container">
|
||||||
<Markdown style={projectorStyles.markdown}>
|
<ReactMarkdown>{content}</ReactMarkdown>
|
||||||
{content}
|
</div>
|
||||||
</Markdown>
|
|
||||||
</ScrollView>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
|
||||||
import { VideoPlayer } from './video-player';
|
import { VideoPlayer } from './video-player';
|
||||||
import { ImageViewer } from './image-viewer';
|
import { ImageViewer } from './image-viewer';
|
||||||
import { MarkdownViewer } from './markdown-viewer';
|
import { MarkdownViewer } from './markdown-viewer';
|
||||||
import { useChat } from '../../providers/chat-provider';
|
import { useChat } from '../../providers/chat-provider';
|
||||||
import { projectorStyles } from '../../styles/projector.styles';
|
import '../../styles/projector.css';
|
||||||
|
|
||||||
export function ProjectorView() {
|
export function ProjectorView() {
|
||||||
const { line } = useChat();
|
const { line } = useChat();
|
||||||
|
@ -14,9 +13,10 @@ export function ProjectorView() {
|
||||||
if (!line) return;
|
if (!line) return;
|
||||||
|
|
||||||
const subscription = line.activity$
|
const subscription = line.activity$
|
||||||
.filter(activity => activity.type === 'event' && activity.name === 'project')
|
.subscribe((activity: any) => {
|
||||||
.subscribe(activity => {
|
if (activity.type === 'event' && activity.name === 'project') {
|
||||||
setContent(activity.value);
|
setContent(activity.value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
|
@ -38,8 +38,8 @@ export function ProjectorView() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={projectorStyles.container}>
|
<div className="projector-container">
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
</View>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Image } from 'react-native';
|
import '../../styles/projector.css';
|
||||||
import { projectorStyles } from '../../styles/projector.styles';
|
|
||||||
|
|
||||||
interface ImageViewerProps {
|
interface VideoPlayerProps {
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoViewer({ url }: ImageViewerProps) {
|
export function VideoPlayer({ url }: VideoPlayerProps) {
|
||||||
return (
|
return (
|
||||||
<View style={projectorStyles.imageContainer}>
|
<div className="video-container">
|
||||||
<iframe
|
<video controls src={url} className="projector-video" />
|
||||||
src={url}
|
</div>
|
||||||
|
|
||||||
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +1,44 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, Image, TextInput, ScrollView, TouchableOpacity } from 'react-native';
|
|
||||||
import { Search } from 'lucide-react-native';
|
|
||||||
import { useChat } from '../../providers/chat-provider';
|
import { useChat } from '../../providers/chat-provider';
|
||||||
import { selectorStyles } from '../../styles/selector.styles';
|
import '../../styles/selector.css';
|
||||||
|
|
||||||
export function PersonSelector() {
|
export function PersonSelector() {
|
||||||
const [search, setSearch] = React.useState('');
|
const [search, setSearch] = React.useState('');
|
||||||
const { instance } = useChat();
|
const { instance } = useChat();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={selectorStyles.container}>
|
<div className="selector-container">
|
||||||
<View style={selectorStyles.header}>
|
<div className="selector-header">
|
||||||
<Image
|
{instance?.logo && (
|
||||||
source={{ uri: instance?.logo }}
|
<img src={instance.logo} className="selector-logo" alt="Logo" />
|
||||||
style={selectorStyles.logo}
|
)}
|
||||||
resizeMode="contain"
|
</div>
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={selectorStyles.searchContainer}>
|
<div className="search-container">
|
||||||
<Search size={20} color="#00f3ff" />
|
<svg className="search-icon" viewBox="0 0 24 24">
|
||||||
<TextInput
|
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
value={search}
|
value={search}
|
||||||
onChangeText={setSearch}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
className="search-input"
|
||||||
placeholder="Search conversations..."
|
placeholder="Search conversations..."
|
||||||
placeholderTextColor="#666"
|
|
||||||
style={selectorStyles.searchInput}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</div>
|
||||||
|
|
||||||
<ScrollView style={selectorStyles.list}>
|
<div className="selector-list">
|
||||||
{['FAQ', 'Support', 'Sales'].map((item) => (
|
{['FAQ', 'Support', 'Sales'].map((item) => (
|
||||||
<TouchableOpacity
|
<div key={item} className="selector-item">
|
||||||
key={item}
|
<div className="selector-avatar">
|
||||||
style={selectorStyles.item}
|
<span>{item[0]}</span>
|
||||||
>
|
</div>
|
||||||
<View style={selectorStyles.avatar}>
|
<div className="item-content">
|
||||||
<Text style={selectorStyles.avatarText}>{item[0]}</Text>
|
<h3 className="item-title">{item}</h3>
|
||||||
</View>
|
<p className="item-subtitle">Start a conversation</p>
|
||||||
<View style={selectorStyles.itemContent}>
|
</div>
|
||||||
<Text style={selectorStyles.itemTitle}>{item}</Text>
|
</div>
|
||||||
<Text style={selectorStyles.itemSubtitle}>Start a conversation</Text>
|
|
||||||
</View>
|
|
||||||
</TouchableOpacity>
|
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</div>
|
||||||
</View>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { View, Text } from 'react-native';
|
|
||||||
import { soundAssets } from '../../../public/sounds/manifest';
|
import { soundAssets } from '../../../public/sounds/manifest';
|
||||||
import { cacheAssets } from '../lib/asset-loader';
|
import { cacheAssets } from '../lib/asset-loader';
|
||||||
|
|
||||||
|
|
||||||
export function SoundInitializer({ children }: { children: React.ReactNode }) {
|
export function SoundInitializer({ children }: { children: React.ReactNode }) {
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
@ -11,31 +9,29 @@ export function SoundInitializer({ children }: { children: React.ReactNode }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeSounds = async () => {
|
const initializeSounds = async () => {
|
||||||
try {
|
try {
|
||||||
await cacheAssets(Object.values(soundAssets));
|
await cacheAssets(Object.values(soundAssets));
|
||||||
setIsReady(true);
|
setIsReady(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Failed to initialize sounds');
|
setError(err instanceof Error ? err.message : 'Failed to initialize sounds');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//initializeSounds();
|
initializeSounds();
|
||||||
setIsReady(true);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
<div className="error-container">
|
||||||
<Text style={{ color: 'red' }}>Error: {error}</Text>
|
<p className="error-text">Error: {error}</p>
|
||||||
</View>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
<div className="loading-container">
|
||||||
<Text>Loading sounds...</Text>
|
<p>Loading sounds...</p>
|
||||||
</View>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, ScrollView, TouchableOpacity, Modal } from 'react-native';
|
import '../../styles/ui.css';
|
||||||
import { X } from 'lucide-react-native';
|
|
||||||
import { emojiStyles } from '../../styles/ui.styles';
|
|
||||||
|
|
||||||
const EMOJI_CATEGORIES = {
|
const EMOJI_CATEGORIES = {
|
||||||
"😀 🎮": ["😀", "😎", "🤖", "👾", "🎮", "✨", "🚀", "💫"],
|
"😀 🎮": ["😀", "😎", "🤖", "👾", "🎮", "✨", "🚀", "💫"],
|
||||||
|
@ -9,43 +7,45 @@ const EMOJI_CATEGORIES = {
|
||||||
"🤖 🎯": ["🤖", "🎯", "🎲", "🎮", "🕹️", "👾", "💻", "⌨️"]
|
"🤖 🎯": ["🤖", "🎯", "🎲", "🎮", "🕹️", "👾", "💻", "⌨️"]
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EmojiPicker({ visible, onClose, onEmojiSelect }) {
|
export function EmojiPicker({ visible, onClose, onEmojiSelect }: {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onEmojiSelect: (emoji: string) => void;
|
||||||
|
}) {
|
||||||
|
if (!visible) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<div className="emoji-picker-modal">
|
||||||
visible={visible}
|
<div className="emoji-picker-header">
|
||||||
transparent
|
<h3>Select Emoji</h3>
|
||||||
animationType="slide"
|
<button onClick={onClose} className="close-button">
|
||||||
>
|
<svg viewBox="0 0 24 24">
|
||||||
<View style={emojiStyles.container}>
|
<path d="M18 6L6 18M6 6l12 12"/>
|
||||||
<View style={emojiStyles.header}>
|
</svg>
|
||||||
<Text style={emojiStyles.title}>Select Emoji</Text>
|
</button>
|
||||||
<TouchableOpacity onPress={onClose}>
|
</div>
|
||||||
<X color="#00f3ff" size={24} />
|
|
||||||
</TouchableOpacity>
|
<div className="emoji-picker-content">
|
||||||
</View>
|
{Object.entries(EMOJI_CATEGORIES).map(([category, emojis]) => (
|
||||||
|
<div key={category} className="emoji-category">
|
||||||
<ScrollView style={emojiStyles.content}>
|
<h4 className="category-title">{category}</h4>
|
||||||
{Object.entries(EMOJI_CATEGORIES).map(([category, emojis]) => (
|
<div className="emoji-grid">
|
||||||
<View key={category} style={emojiStyles.category}>
|
{emojis.map(emoji => (
|
||||||
<Text style={emojiStyles.categoryTitle}>{category}</Text>
|
<button
|
||||||
<View style={emojiStyles.emojiGrid}>
|
key={emoji}
|
||||||
{emojis.map(emoji => (
|
className="emoji-button"
|
||||||
<TouchableOpacity
|
onClick={() => {
|
||||||
key={emoji}
|
onEmojiSelect(emoji);
|
||||||
style={emojiStyles.emojiButton}
|
onClose();
|
||||||
onPress={() => {
|
}}
|
||||||
onEmojiSelect(emoji);
|
>
|
||||||
onClose();
|
{emoji}
|
||||||
}}
|
</button>
|
||||||
>
|
))}
|
||||||
<Text style={emojiStyles.emoji}>{emoji}</Text>
|
</div>
|
||||||
</TouchableOpacity>
|
</div>
|
||||||
))}
|
))}
|
||||||
</View>
|
</div>
|
||||||
</View>
|
</div>
|
||||||
))}
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, Text, Switch, TouchableOpacity, ScrollView } from 'react-native';
|
|
||||||
import { Moon, Sun, Volume2, VolumeX, Zap, Settings } from 'lucide-react-native';
|
|
||||||
import { useSound } from '../../providers/sound-provider';
|
import { useSound } from '../../providers/sound-provider';
|
||||||
import { settingsStyles } from '../../styles/ui.styles';
|
import '../../styles/ui.css';
|
||||||
|
|
||||||
export function SettingsPanel() {
|
export function SettingsPanel() {
|
||||||
const [theme, setTheme] = React.useState('dark');
|
const [theme, setTheme] = React.useState('dark');
|
||||||
|
@ -29,21 +27,27 @@ export function SettingsPanel() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// ... rest of the settings panel code ...
|
<div className="settings-panel">
|
||||||
<View style={settingsStyles.option}>
|
<div className="settings-option">
|
||||||
{sound ? (
|
{sound ? (
|
||||||
<Volume2 color="#00f3ff" size={20} />
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
) : (
|
<path d="M3 15a1 1 0 011-1h2.83a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4h3.55a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4H19a1 1 0 011 1v6a1 1 0 01-1 1h-2.83a1 1 0 00-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H9.72a1 1 0 01-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H4a1 1 0 01-1-1v-2zM12 6a1 1 0 011-1h6a1 1 0 011 1v3"/>
|
||||||
<VolumeX color="#666" size={20} />
|
</svg>
|
||||||
)}
|
) : (
|
||||||
<Text style={settingsStyles.optionText}>Sound Effects</Text>
|
<svg className="icon" viewBox="0 0 24 24">
|
||||||
<Switch
|
<path d="M3 15a1 1 0 011-1h2.83a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4h3.55a1 1 0 00.8-.4l1.12-1.5a1 1 0 01.8-.4H19a1 1 0 011 1v6a1 1 0 01-1 1h-2.83a1 1 0 00-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H9.72a1 1 0 01-.8-.4l-1.12-1.5a1 1 0 00-.8-.4H4a1 1 0 01-1-1v-2zM12 6a1 1 0 011-1h6a1 1 0 011 1v3M3 3l18 18"/>
|
||||||
value={sound}
|
</svg>
|
||||||
onValueChange={handleSoundToggle}
|
)}
|
||||||
trackColor={{ false: '#333', true: '#00f3ff44' }}
|
<span className="option-text">Sound Effects</span>
|
||||||
thumbColor={sound ? '#00f3ff' : '#666'}
|
<label className="switch">
|
||||||
/>
|
<input
|
||||||
</View>
|
type="checkbox"
|
||||||
// ... rest of the settings panel code ...
|
checked={sound}
|
||||||
|
onChange={(e) => handleSoundToggle(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<span className="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ export function Chat() {
|
||||||
return (
|
return (
|
||||||
<SoundInitializer>
|
<SoundInitializer>
|
||||||
<SoundProvider>
|
<SoundProvider>
|
||||||
<ChatProvider>
|
<ChatProvider>
|
||||||
<ChatLayout />
|
<ChatLayout />
|
||||||
</ChatProvider>
|
</ChatProvider>
|
||||||
</SoundProvider>
|
</SoundProvider>
|
||||||
</SoundInitializer>
|
</SoundInitializer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import React, { useState } from 'react';
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
import { DirectLine } from 'botframework-directlinejs';
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
import { ChatInstance, User } from '../types';
|
import { User, ChatInstance } from '../types';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
interface ChatContextType {
|
interface ChatContextType {
|
||||||
line: DirectLine | null;
|
line: any;
|
||||||
user: User;
|
user: User;
|
||||||
instance: ChatInstance | null;
|
instance: ChatInstance | null;
|
||||||
sendActivity: (activity: any) => void;
|
sendActivity: (activity: any) => void;
|
||||||
|
@ -12,92 +11,66 @@ interface ChatContextType {
|
||||||
setVoice: (voice: any) => void;
|
setVoice: (voice: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateUserId = () => {
|
const ChatContext = createContext<ChatContextType | undefined>(undefined);
|
||||||
return 'usergb@gb';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChatContext = React.createContext<ChatContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export function ChatProvider({ children }: { children: React.ReactNode }) {
|
export function ChatProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [line, setLine] = React.useState<DirectLine | null>(null);
|
const [line, setLine] = useState<any>(null);
|
||||||
const [instance, setInstance] = React.useState<ChatInstance | null>(null);
|
const [instance, setInstance] = useState<ChatInstance | null>(null);
|
||||||
const [selectedVoice, setSelectedVoice] = useState(null);
|
const [selectedVoice, setSelectedVoice] = useState(null);
|
||||||
const [user] = React.useState<User>(() => ({
|
const [user] = useState<User>({
|
||||||
id: `user_${Math.random().toString(36).slice(2)}`,
|
id: `user_${Math.random().toString(36).slice(2)}`,
|
||||||
name: 'You'
|
name: 'You'
|
||||||
}));
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
|
const initializeChat = async () => {
|
||||||
|
try {
|
||||||
|
const botId = window.location.pathname.split('/')[1] || 'default';
|
||||||
|
const instanceData = await invoke('get_chat_instance', { botId });
|
||||||
|
setInstance(instanceData as ChatInstance);
|
||||||
|
|
||||||
|
// Initialize DirectLine or other chat service
|
||||||
|
const directLine = {
|
||||||
|
activity$: { subscribe: () => {} },
|
||||||
|
postActivity: () => ({ subscribe: () => {} })
|
||||||
|
};
|
||||||
|
setLine(directLine);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize chat:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
initializeChat();
|
initializeChat();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const initializeChat = async () => {
|
const sendActivity = async (activity: any) => {
|
||||||
try {
|
try {
|
||||||
var botId = window.location.href.split('/')[3];
|
await invoke('send_chat_activity', {
|
||||||
if (botId.indexOf('#') !== -1) {
|
activity: {
|
||||||
botId = botId.split('#')[0];
|
...activity,
|
||||||
}
|
from: user,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
if (!botId || botId === '') {
|
}
|
||||||
botId = '[default]';
|
});
|
||||||
}
|
line?.postActivity(activity).subscribe();
|
||||||
|
|
||||||
const response = await fetch(
|
|
||||||
'http://localhost:4242/instances/' + botId,
|
|
||||||
)
|
|
||||||
const data = await response.json();
|
|
||||||
const userId = generateUserId();
|
|
||||||
|
|
||||||
const directLine = data.webchatToken
|
|
||||||
? new DirectLine({
|
|
||||||
token: data.token,
|
|
||||||
webSocket: true
|
|
||||||
})
|
|
||||||
: new DirectLine({
|
|
||||||
domain: data.domain,
|
|
||||||
secret: null,
|
|
||||||
token: null,
|
|
||||||
webSocket: false
|
|
||||||
});
|
|
||||||
directLine.setUserId(userId);
|
|
||||||
setLine(directLine);
|
|
||||||
setInstance(data.instance);
|
|
||||||
console.info (`DirectLine for user:` + userId);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize chat:', error);
|
console.error('Failed to send activity:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendActivity = (activity: any) => {
|
|
||||||
line?.postActivity({
|
|
||||||
...activity,
|
|
||||||
from: user,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
}).subscribe();
|
|
||||||
};
|
|
||||||
|
|
||||||
const setVoice = (voice: any) => {
|
const setVoice = (voice: any) => {
|
||||||
setSelectedVoice(voice);
|
setSelectedVoice(voice);
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextValue: ChatContextType = {
|
|
||||||
line,
|
|
||||||
user,
|
|
||||||
instance,
|
|
||||||
sendActivity,
|
|
||||||
selectedVoice,
|
|
||||||
setVoice
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatContext.Provider value={contextValue}>
|
<ChatContext.Provider value={{ line, user, instance, sendActivity, selectedVoice, setVoice }}>
|
||||||
{children}
|
{children}
|
||||||
</ChatContext.Provider>
|
</ChatContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useChat() {
|
export function useChat() {
|
||||||
const context = React.useContext(ChatContext);
|
const context = useContext(ChatContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error('useChat must be used within ChatProvider');
|
throw new Error('useChat must be used within ChatProvider');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
import React from 'react';
|
import React, { createContext, useContext, useCallback } from 'react';
|
||||||
|
import { invoke } from '@tauri-apps/api/tauri';
|
||||||
|
|
||||||
interface SoundContextType {
|
interface SoundContextType {
|
||||||
playSound: (sound: string) => void;
|
playSound: (sound: string) => void;
|
||||||
setEnabled: (enabled: boolean) => void;
|
setEnabled: (enabled: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SoundContext = React.createContext<SoundContextType | undefined>(undefined);
|
const SoundContext = createContext<SoundContextType | undefined>(undefined);
|
||||||
|
|
||||||
export function SoundProvider({ children }: { children: React.ReactNode }) {
|
export function SoundProvider({ children }: { children: React.ReactNode }) {
|
||||||
const playSound = React.useCallback((sound: string) => {
|
const [enabled, setEnabled] = React.useState(true);
|
||||||
// soundManager.play(sound as any);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setEnabled = React.useCallback((enabled: boolean) => {
|
const playSound = useCallback(async (sound: string) => {
|
||||||
// soundManager.setEnabled(enabled);
|
if (!enabled) return;
|
||||||
}, []);
|
try {
|
||||||
|
await invoke('play_sound', { sound });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to play sound:', error);
|
||||||
|
}
|
||||||
|
}, [enabled]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SoundContext.Provider value={{ playSound, setEnabled }}>
|
<SoundContext.Provider value={{ playSound, setEnabled }}>
|
||||||
|
|
|
@ -1,120 +0,0 @@
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
export const audioStyles = StyleSheet.create({
|
|
||||||
volumeContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 16,
|
|
||||||
backgroundColor: '#1a1a1a',
|
|
||||||
borderRadius: 12,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#333',
|
|
||||||
},
|
|
||||||
volumeButton: {
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
sliderContainer: {
|
|
||||||
flex: 1,
|
|
||||||
marginLeft: 16,
|
|
||||||
},
|
|
||||||
slider: {
|
|
||||||
flex: 1,
|
|
||||||
height: 40,
|
|
||||||
},
|
|
||||||
visualizerContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
height: 40,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
paddingHorizontal: 8,
|
|
||||||
},
|
|
||||||
visualizerBar: {
|
|
||||||
width: 3,
|
|
||||||
height: 20,
|
|
||||||
backgroundColor: '#00f3ff',
|
|
||||||
borderRadius: 2,
|
|
||||||
marginHorizontal: 1,
|
|
||||||
},
|
|
||||||
modal: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.95)',
|
|
||||||
margin: 20,
|
|
||||||
marginTop: 100,
|
|
||||||
borderRadius: 20,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#00f3ff',
|
|
||||||
shadowColor: '#00f3ff',
|
|
||||||
shadowOffset: { width: 0, height: 0 },
|
|
||||||
shadowOpacity: 0.5,
|
|
||||||
shadowRadius: 10,
|
|
||||||
},
|
|
||||||
voiceList: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
voiceOption: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
padding: 16,
|
|
||||||
backgroundColor: '#1a1a1a',
|
|
||||||
borderRadius: 12,
|
|
||||||
marginBottom: 8,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#333',
|
|
||||||
},
|
|
||||||
selectedVoice: {
|
|
||||||
borderColor: '#00f3ff',
|
|
||||||
backgroundColor: '#00f3ff11',
|
|
||||||
},
|
|
||||||
voiceInfo: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
voiceName: {
|
|
||||||
color: '#ffffff',
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
voiceAccent: {
|
|
||||||
color: '#666',
|
|
||||||
fontSize: 14,
|
|
||||||
marginTop: 4,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 16,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: '#333',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
color: '#00f3ff',
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
closeButton: {
|
|
||||||
color: '#00f3ff',
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
trigger: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 8,
|
|
||||||
backgroundColor: '#1a1a1a',
|
|
||||||
borderRadius: 8,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#333',
|
|
||||||
},
|
|
||||||
triggerText: {
|
|
||||||
color: '#ffffff',
|
|
||||||
marginLeft: 8,
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const voiceStyles = StyleSheet.create({
|
|
||||||
// ... copy from audioStyles the modal-related styles ...
|
|
||||||
// Add voice-specific styles here
|
|
||||||
});
|
|
|
@ -1,91 +0,0 @@
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
export const chatStyles = StyleSheet.create({
|
|
||||||
window: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#111111',
|
|
||||||
},
|
|
||||||
messageList: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
messageListContent: {
|
|
||||||
paddingBottom: 16,
|
|
||||||
},
|
|
||||||
messageContainer: {
|
|
||||||
maxWidth: '70%',
|
|
||||||
marginVertical: 4,
|
|
||||||
padding: 12,
|
|
||||||
borderRadius: 12,
|
|
||||||
},
|
|
||||||
userMessage: {
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
backgroundColor: '#00f3ff22',
|
|
||||||
borderColor: '#00f3ff',
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
botMessage: {
|
|
||||||
alignSelf: 'flex-start',
|
|
||||||
backgroundColor: '#bf00ff22',
|
|
||||||
borderColor: '#bf00ff',
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
messageText: {
|
|
||||||
color: '#ffffff',
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
messageTime: {
|
|
||||||
color: '#666666',
|
|
||||||
fontSize: 12,
|
|
||||||
marginTop: 4,
|
|
||||||
},
|
|
||||||
inputContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 16,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
borderTopColor: '#1a1a1a',
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
flex: 1,
|
|
||||||
marginHorizontal: 12,
|
|
||||||
padding: 12,
|
|
||||||
backgroundColor: '#1a1a1a',
|
|
||||||
borderRadius: 24,
|
|
||||||
color: '#ffffff',
|
|
||||||
maxHeight: 100,
|
|
||||||
},
|
|
||||||
iconButton: {
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 16,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: '#1a1a1a',
|
|
||||||
},
|
|
||||||
headerContent: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
headerTitle: {
|
|
||||||
color: '#ffffff',
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
headerSubtitle: {
|
|
||||||
color: '#00f3ff',
|
|
||||||
fontSize: 14,
|
|
||||||
marginTop: 2,
|
|
||||||
},
|
|
||||||
headerButton: {
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
sendButton: {
|
|
||||||
backgroundColor: '#00f3ff22',
|
|
||||||
borderRadius: 20,
|
|
||||||
padding: 10,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#00f3ff',
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,27 +0,0 @@
|
||||||
// layout.styles.ts
|
|
||||||
import { Colors } from '../../../constants/Colors';
|
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
export const layoutStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
backgroundColor: Colors.dark.background,
|
|
||||||
},
|
|
||||||
sidebar: {
|
|
||||||
width: 300,
|
|
||||||
borderRightWidth: 1,
|
|
||||||
borderRightColor: Colors.dark.icon,
|
|
||||||
},
|
|
||||||
mainContent: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
},
|
|
||||||
projector: {
|
|
||||||
width: '40%',
|
|
||||||
borderRightWidth: 1,
|
|
||||||
borderRightColor: Colors.dark.icon,
|
|
||||||
},
|
|
||||||
chatArea: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,50 +0,0 @@
|
||||||
// projector.styles.ts
|
|
||||||
import { Colors } from '../../../constants/Colors';
|
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
export const projectorStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: Colors.dark.background,
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
videoContainer: {
|
|
||||||
aspectRatio: 16/9,
|
|
||||||
backgroundColor: Colors.dark.background,
|
|
||||||
borderRadius: 8,
|
|
||||||
overflow: 'hidden',
|
|
||||||
},
|
|
||||||
imageContainer: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: Colors.dark.background,
|
|
||||||
borderRadius: 8,
|
|
||||||
overflow: 'hidden',
|
|
||||||
},
|
|
||||||
markdownContainer: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 16,
|
|
||||||
backgroundColor: Colors.dark.background,
|
|
||||||
borderRadius: 8,
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
color: Colors.dark.text,
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
heading1: {
|
|
||||||
color: Colors.dark.tint,
|
|
||||||
fontSize: 24,
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
heading2: {
|
|
||||||
color: Colors.dark.tint,
|
|
||||||
fontSize: 20,
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
color: Colors.dark.tint,
|
|
||||||
},
|
|
||||||
code_block: {
|
|
||||||
backgroundColor: Colors.dark.background,
|
|
||||||
padding: 12,
|
|
||||||
borderRadius: 4,
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,69 +0,0 @@
|
||||||
// selector.styles.ts
|
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
import { Colors } from '../../../constants/Colors';
|
|
||||||
|
|
||||||
export const selectorStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: Colors.dark.background,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
padding: 16,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: Colors.dark.icon,
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
width: 150,
|
|
||||||
height: 50,
|
|
||||||
},
|
|
||||||
searchContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 12,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: Colors.dark.icon,
|
|
||||||
},
|
|
||||||
searchInput: {
|
|
||||||
flex: 1,
|
|
||||||
marginLeft: 8,
|
|
||||||
color: Colors.dark.text,
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
list: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
item: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
padding: 16,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: Colors.dark.icon,
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
width: 48,
|
|
||||||
height: 48,
|
|
||||||
borderRadius: 24,
|
|
||||||
backgroundColor: Colors.dark.background,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: Colors.dark.tint,
|
|
||||||
},
|
|
||||||
avatarText: {
|
|
||||||
color: Colors.dark.tint,
|
|
||||||
fontSize: 20,
|
|
||||||
},
|
|
||||||
itemContent: {
|
|
||||||
marginLeft: 12,
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
itemTitle: {
|
|
||||||
color: Colors.dark.text,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
itemSubtitle: {
|
|
||||||
color: Colors.dark.icon,
|
|
||||||
fontSize: 14,
|
|
||||||
marginTop: 4,
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,150 +0,0 @@
|
||||||
import { StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
export const emojiStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: 'rgba(0,0,0,0.95)',
|
|
||||||
margin: 20,
|
|
||||||
marginTop: 100,
|
|
||||||
borderRadius: 20,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#00f3ff',
|
|
||||||
shadowColor: '#00f3ff',
|
|
||||||
shadowOffset: { width: 0, height: 0 },
|
|
||||||
shadowOpacity: 0.5,
|
|
||||||
shadowRadius: 10,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 16,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: '#00f3ff33',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
color: '#00f3ff',
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
flex: 1,
|
|
||||||
padding: 16,
|
|
||||||
},
|
|
||||||
category: {
|
|
||||||
marginBottom: 24,
|
|
||||||
},
|
|
||||||
categoryTitle: {
|
|
||||||
color: '#bf00ff',
|
|
||||||
fontSize: 16,
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
emojiGrid: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
},
|
|
||||||
emojiButton: {
|
|
||||||
width: '12.5%',
|
|
||||||
aspectRatio: 1,
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderRadius: 8,
|
|
||||||
},
|
|
||||||
emoji: {
|
|
||||||
fontSize: 24,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const settingsStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
backgroundColor: '#111111',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 20,
|
|
||||||
borderBottomWidth: 1,
|
|
||||||
borderBottomColor: '#00f3ff33',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
color: '#00f3ff',
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
marginLeft: 12,
|
|
||||||
},
|
|
||||||
section: {
|
|
||||||
padding: 20,
|
|
||||||
},
|
|
||||||
sectionTitle: {
|
|
||||||
color: '#bf00ff',
|
|
||||||
fontSize: 16,
|
|
||||||
marginBottom: 16,
|
|
||||||
},
|
|
||||||
option: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: 12,
|
|
||||||
marginBottom: 12,
|
|
||||||
backgroundColor: '#1a1a1a',
|
|
||||||
borderRadius: 12,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#333',
|
|
||||||
},
|
|
||||||
optionText: {
|
|
||||||
color: '#ffffff',
|
|
||||||
fontSize: 16,
|
|
||||||
marginLeft: 12,
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
activeIndicator: {
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
borderRadius: 4,
|
|
||||||
marginLeft: 8,
|
|
||||||
},
|
|
||||||
effectPreview: {
|
|
||||||
padding: 20,
|
|
||||||
},
|
|
||||||
previewTitle: {
|
|
||||||
color: '#00f3ff',
|
|
||||||
fontSize: 16,
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
previewContent: {
|
|
||||||
padding: 20,
|
|
||||||
backgroundColor: '#1a1a1a',
|
|
||||||
borderRadius: 12,
|
|
||||||
alignItems: 'center',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#00f3ff33',
|
|
||||||
},
|
|
||||||
previewText: {
|
|
||||||
color: '#00f3ff',
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add animation helpers
|
|
||||||
export const pulseAnimation = {
|
|
||||||
0: {
|
|
||||||
opacity: 1,
|
|
||||||
scale: 1,
|
|
||||||
},
|
|
||||||
0.5: {
|
|
||||||
opacity: 0.7,
|
|
||||||
scale: 1.05,
|
|
||||||
},
|
|
||||||
1: {
|
|
||||||
opacity: 1,
|
|
||||||
scale: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const neonGlow = {
|
|
||||||
shadowColor: '#00f3ff',
|
|
||||||
shadowOffset: { width: 0, height: 0 },
|
|
||||||
shadowOpacity: 0.8,
|
|
||||||
shadowRadius: 15,
|
|
||||||
};
|
|
276
src/index.tsx
276
src/index.tsx
|
@ -1,28 +1,12 @@
|
||||||
// app/index.tsx
|
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import {
|
|
||||||
View,
|
|
||||||
SafeAreaView,
|
|
||||||
TouchableOpacity,
|
|
||||||
ScrollView,
|
|
||||||
Text,
|
|
||||||
Linking,
|
|
||||||
StyleSheet,
|
|
||||||
} from 'react-native';
|
|
||||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
|
||||||
import { createStackNavigator } from '@react-navigation/stack';
|
|
||||||
import { globalStyles } from '../styles';
|
|
||||||
import { styled } from 'nativewind';
|
|
||||||
import AuthenticationScreen from './authentication';
|
import AuthenticationScreen from './authentication';
|
||||||
import {Chat} from './chat';
|
import { Chat } from './chat';
|
||||||
import MailPage from './mail';
|
import MailPage from './mail';
|
||||||
import DashboardPage from './dashboard';
|
import DashboardPage from './dashboard';
|
||||||
import TaskPage from './tasks';
|
import TaskPage from './tasks';
|
||||||
import TemplatesPage from './templates';
|
import TemplatesPage from './templates';
|
||||||
import DriveScreen from './drive';
|
import DriveScreen from './drive';
|
||||||
import SyncPage from './sync/page'; // Add this import
|
import SyncPage from './sync/page';
|
||||||
|
|
||||||
const Stack = createStackNavigator();
|
|
||||||
const StyledView = styled(View);
|
|
||||||
|
|
||||||
const examples = [
|
const examples = [
|
||||||
{ name: "Home", href: "authentication" },
|
{ name: "Home", href: "authentication" },
|
||||||
|
@ -33,204 +17,82 @@ const examples = [
|
||||||
{ name: "Tasks", href: "tasks" },
|
{ name: "Tasks", href: "tasks" },
|
||||||
{ name: "Meet", href: "meet" },
|
{ name: "Meet", href: "meet" },
|
||||||
{ name: "Templates", href: "templates" },
|
{ name: "Templates", href: "templates" },
|
||||||
{ name: "Settings", href: "sync" }, // Changed from "settings" to "sync"
|
{ name: "Settings", href: "sync" },
|
||||||
{ name: "Help", href: "help" },
|
{ name: "Help", href: "help" },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface ExamplesNavProps {
|
const ExamplesNav = () => {
|
||||||
style?: any;
|
const location = useLocation();
|
||||||
}
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const ExamplesNav = ({ style }: ExamplesNavProps) => {
|
|
||||||
const navigation = useNavigation();
|
|
||||||
return (
|
|
||||||
<StyledView style={[styles.examplesNavContainer, style]}>
|
|
||||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
|
||||||
<StyledView style={styles.examplesNavInner}>
|
|
||||||
{examples.map((example) => (
|
|
||||||
<TouchableOpacity
|
|
||||||
key={example.href}
|
|
||||||
onPress={() => navigation.navigate(example.href)}
|
|
||||||
style={styles.exampleButton}
|
|
||||||
>
|
|
||||||
<Text style={styles.exampleButtonText}>
|
|
||||||
{example.name}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
))}
|
|
||||||
</StyledView>
|
|
||||||
</ScrollView>
|
|
||||||
</StyledView>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface ExampleCodeLinkProps {
|
|
||||||
pathname: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExampleCodeLink = ({ pathname }: ExampleCodeLinkProps) => {
|
|
||||||
const example = examples.find((example) => pathname.startsWith(example.href));
|
|
||||||
|
|
||||||
if (!example?.code) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<div className="examples-nav-container">
|
||||||
onPress={() => Linking.openURL(example.code)}
|
<div className="examples-nav-inner">
|
||||||
style={styles.exampleCodeLink}
|
{examples.map((example) => (
|
||||||
>
|
<button
|
||||||
<Text style={styles.exampleCodeLinkText}>View code</Text>
|
key={example.href}
|
||||||
<Text style={styles.exampleCodeLinkArrow}>→</Text>
|
onClick={() => navigate(example.href)}
|
||||||
</TouchableOpacity>
|
className={`example-button ${
|
||||||
|
location.pathname.includes(example.href) ? 'active' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{example.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigatorOptions = {
|
|
||||||
headerShown: false,
|
|
||||||
cardOverlayEnabled: false,
|
|
||||||
animation: 'fade',
|
|
||||||
presentation: 'modal'
|
|
||||||
};
|
|
||||||
|
|
||||||
const WrapWithExampleCodeLink = (WrappedComponent) => {
|
export function RootLayout() {
|
||||||
return (props) => {
|
|
||||||
const route = useRoute();
|
|
||||||
return (
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<WrappedComponent {...props} />
|
|
||||||
<ExampleCodeLink pathname={route.name} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function RootLayout() {
|
|
||||||
return (
|
return (
|
||||||
<StyledView style={globalStyles.container}>
|
<div className="app-container">
|
||||||
<StatusBar style="auto" />
|
<ExamplesNav />
|
||||||
<ClerkProvider publishableKey={process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>
|
<main className="app-main">
|
||||||
<ClerkLoaded>
|
<Routes>
|
||||||
<SafeAreaView style={globalStyles.safeArea}>
|
<Route path="authentication" element={
|
||||||
<ExamplesNav />
|
<>
|
||||||
<Stack.Navigator
|
<AuthenticationScreen />
|
||||||
initialRouteName="authentication"
|
</>
|
||||||
screenOptions={navigatorOptions}
|
} />
|
||||||
>
|
<Route path="chat" element={
|
||||||
<Stack.Screen
|
<>
|
||||||
name="authentication"
|
<Chat />
|
||||||
component={WrapWithExampleCodeLink(AuthenticationScreen)}
|
|
||||||
options={{ title: "Authentication" }}
|
</>
|
||||||
/>
|
} />
|
||||||
<Stack.Screen
|
<Route path="dashboard" element={
|
||||||
name="chat"
|
<>
|
||||||
component={WrapWithExampleCodeLink(Chat)}
|
<DashboardPage />
|
||||||
options={{ title: "Chat" }}
|
|
||||||
/>
|
</>
|
||||||
<Stack.Screen
|
} />
|
||||||
name="dashboard"
|
<Route path="mail" element={
|
||||||
component={WrapWithExampleCodeLink(DashboardPage)}
|
<>
|
||||||
options={{ title: "Dashboard" }}
|
<MailPage />
|
||||||
/>
|
|
||||||
<Stack.Screen
|
</>
|
||||||
name="mail"
|
} />
|
||||||
component={WrapWithExampleCodeLink(MailPage)}
|
<Route path="drive" element={<DriveScreen />} />
|
||||||
options={{ title: "Mail" }}
|
<Route path="tasks" element={
|
||||||
/>
|
<>
|
||||||
<Stack.Screen
|
<TaskPage />
|
||||||
name="drive"
|
|
||||||
component={DriveScreen}
|
</>
|
||||||
options={{ title: "Drive" }}
|
} />
|
||||||
/>
|
<Route path="meet" element={<div>Meet Screen (Placeholder)</div>} />
|
||||||
<Stack.Screen
|
<Route path="templates" element={
|
||||||
name="tasks"
|
<>
|
||||||
component={WrapWithExampleCodeLink(TaskPage)}
|
<TemplatesPage />
|
||||||
options={{ title: "Tasks" }}
|
|
||||||
/>
|
</>
|
||||||
<Stack.Screen
|
} />
|
||||||
name="meet"
|
<Route path="sync" element={<SyncPage />} />
|
||||||
component={() => <Text>Meet Screen (Placeholder)</Text>}
|
</Routes>
|
||||||
options={{ title: "Meet" }}
|
</main>
|
||||||
/>
|
</div>
|
||||||
<Stack.Screen
|
|
||||||
name="templates"
|
|
||||||
component={WrapWithExampleCodeLink(TemplatesPage)}
|
|
||||||
options={{ title: "Templates" }}
|
|
||||||
/>
|
|
||||||
<Stack.Screen
|
|
||||||
name="sync"
|
|
||||||
component={SyncPage}
|
|
||||||
options={{ title: "Cloud Sync" }}
|
|
||||||
/>
|
|
||||||
</Stack.Navigator>
|
|
||||||
</SafeAreaView>
|
|
||||||
</ClerkLoaded>
|
|
||||||
</ClerkProvider>
|
|
||||||
</StyledView>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
export default RootLayout;
|
||||||
examplesNavContainer: {
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 4,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
marginTop: 16,
|
|
||||||
shadowColor: "#000",
|
|
||||||
shadowOffset: {
|
|
||||||
width: 0,
|
|
||||||
height: 2,
|
|
||||||
},
|
|
||||||
shadowOpacity: 0.1,
|
|
||||||
shadowRadius: 4,
|
|
||||||
elevation: 3,
|
|
||||||
},
|
|
||||||
examplesNavInner: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
exampleButton: {
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
paddingVertical: 6,
|
|
||||||
borderRadius: 6,
|
|
||||||
marginRight: 4,
|
|
||||||
},
|
|
||||||
exampleButtonActive: {
|
|
||||||
backgroundColor: '#f3f4f6',
|
|
||||||
},
|
|
||||||
exampleButtonText: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: '#6b7280',
|
|
||||||
},
|
|
||||||
exampleButtonTextActive: {
|
|
||||||
fontWeight: '500',
|
|
||||||
color: '#6366f1',
|
|
||||||
},
|
|
||||||
exampleCodeLink: {
|
|
||||||
position: 'absolute',
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 8,
|
|
||||||
},
|
|
||||||
exampleCodeLinkText: {
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: '500',
|
|
||||||
marginRight: 4,
|
|
||||||
color: '#6366f1',
|
|
||||||
},
|
|
||||||
exampleCodeLinkArrow: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: '#6366f1',
|
|
||||||
},
|
|
||||||
logo: {
|
|
||||||
marginLeft: 8,
|
|
||||||
marginRight: 12,
|
|
||||||
borderRadius: 8,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default RootLayout;
|
|
112
src/styles/chat.css
Normal file
112
src/styles/chat.css
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
.chat-window {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #111;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
color: white;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-subtitle {
|
||||||
|
color: #00f3ff;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-container {
|
||||||
|
max-width: 70%;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message {
|
||||||
|
align-self: flex-end;
|
||||||
|
background-color: rgba(0, 243, 255, 0.1);
|
||||||
|
border: 1px solid #00f3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bot-message {
|
||||||
|
align-self: flex-start;
|
||||||
|
background-color: rgba(191, 0, 255, 0.1);
|
||||||
|
border: 1px solid #bf00ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text {
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-time {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border-top: 1px solid #333;
|
||||||
|
background-color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 2.5rem;
|
||||||
|
max-height: 6rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 1.25rem;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #00f3ff;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.send-button {
|
||||||
|
background-color: rgba(0, 243, 255, 0.1);
|
||||||
|
border: 1px solid #00f3ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
24
src/styles/layout.css
Normal file
24
src/styles/layout.css
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
.chat-layout {
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 18rem;
|
||||||
|
border-right: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projector {
|
||||||
|
width: 40%;
|
||||||
|
border-right: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-area {
|
||||||
|
flex: 1;
|
||||||
|
}
|
45
src/styles/projector.css
Normal file
45
src/styles/projector.css
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
.projector-container {
|
||||||
|
height: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container, .video-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #111;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projector-image, .projector-video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-container {
|
||||||
|
height: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
color: white;
|
||||||
|
background-color: #111;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-container h1,
|
||||||
|
.markdown-container h2,
|
||||||
|
.markdown-container h3 {
|
||||||
|
color: #00f3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-container a {
|
||||||
|
color: #00f3ff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-container pre {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
87
src/styles/selector.css
Normal file
87
src/styles/selector.css
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
.selector-container {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-header {
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-logo {
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
color: #00f3ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input::placeholder {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-list {
|
||||||
|
height: calc(100% - 7rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-item:hover {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector-avatar {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 1px solid #00f3ff;
|
||||||
|
color: #00f3ff;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-content {
|
||||||
|
margin-left: 1rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-subtitle {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
137
src/styles/ui.css
Normal file
137
src/styles/ui.css
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
.emoji-picker-modal {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 5rem;
|
||||||
|
right: 2rem;
|
||||||
|
width: 20rem;
|
||||||
|
max-height: 25rem;
|
||||||
|
background-color: rgba(0, 0, 0, 0.95);
|
||||||
|
border: 1px solid #00f3ff;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
box-shadow: 0 0 1rem rgba(0, 243, 255, 0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
border-bottom: 1px solid rgba(0, 243, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker-header h3 {
|
||||||
|
color: #00f3ff;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #00f3ff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-picker-content {
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(25rem - 3.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-category {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-title {
|
||||||
|
color: #bf00ff;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(8, 1fr);
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-button:hover {
|
||||||
|
background-color: rgba(0, 243, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-panel {
|
||||||
|
background-color: #111;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
flex: 1;
|
||||||
|
color: white;
|
||||||
|
margin-left: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 3rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #333;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 1.1rem;
|
||||||
|
width: 1.1rem;
|
||||||
|
left: 0.2rem;
|
||||||
|
bottom: 0.2rem;
|
||||||
|
background-color: #666;
|
||||||
|
transition: .4s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider {
|
||||||
|
background-color: rgba(0, 243, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked + .slider:before {
|
||||||
|
transform: translateX(1.5rem);
|
||||||
|
background-color: #00f3ff;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue