ok
This commit is contained in:
100
src/ChatBot.js
100
src/ChatBot.js
@@ -9,17 +9,13 @@ const ChatBot = ({ existingConversation }) => {
|
|||||||
sender: 'bot',
|
sender: 'bot',
|
||||||
text: 'Hai! Saya Alle, asisten virtual Dermalounge Clinic. Ada yang bisa Alle bantu hari ini?',
|
text: 'Hai! Saya Alle, asisten virtual Dermalounge Clinic. Ada yang bisa Alle bantu hari ini?',
|
||||||
time: getTime(),
|
time: getTime(),
|
||||||
quickReplies: [
|
quickReplies: ['Konsultasi Estetik', 'Konsultasi Kulit & Kelamin'],
|
||||||
'Konsultasi Estetik',
|
|
||||||
'Konsultasi Kulit & Kelamin'
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const isOpenCamera = searchParams.get('camera') === 'open';
|
||||||
const isOpenCamera = searchParams.get('camera') === 'open';
|
|
||||||
|
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState('');
|
const [isLoading, setIsLoading] = useState('');
|
||||||
@@ -45,8 +41,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
|
|
||||||
const sessionId = generateUUID();
|
const sessionId = generateUUID();
|
||||||
const dateNow = new Date().toISOString();
|
const dateNow = new Date().toISOString();
|
||||||
|
sessionStorage.setItem('session', JSON.stringify({ sessionId, lastSeen: dateNow }));
|
||||||
sessionStorage.setItem('session', JSON.stringify({ sessionId: sessionId, lastSeen: dateNow }))
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -56,11 +51,9 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
const bstr = atob(arr[1]);
|
const bstr = atob(arr[1]);
|
||||||
let n = bstr.length;
|
let n = bstr.length;
|
||||||
const u8arr = new Uint8Array(n);
|
const u8arr = new Uint8Array(n);
|
||||||
|
|
||||||
while (n--) {
|
while (n--) {
|
||||||
u8arr[n] = bstr.charCodeAt(n);
|
u8arr[n] = bstr.charCodeAt(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new File([u8arr], filename, { type: mime });
|
return new File([u8arr], filename, { type: mime });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +63,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
|
|
||||||
let body;
|
let body;
|
||||||
let headers;
|
let headers;
|
||||||
|
|
||||||
const isBase64Image = type === 'image' && typeof content === 'string' && content.startsWith('data:image/');
|
const isBase64Image = type === 'image' && typeof content === 'string' && content.startsWith('data:image/');
|
||||||
|
|
||||||
if (isBase64Image) {
|
if (isBase64Image) {
|
||||||
@@ -82,7 +74,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
formData.append('phoneNumber', session.phoneNumber || '');
|
formData.append('phoneNumber', session.phoneNumber || '');
|
||||||
formData.append('type', type);
|
formData.append('type', type);
|
||||||
formData.append('image', file);
|
formData.append('image', file);
|
||||||
|
|
||||||
body = formData;
|
body = formData;
|
||||||
headers = {};
|
headers = {};
|
||||||
} else {
|
} else {
|
||||||
@@ -103,7 +94,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
headers,
|
headers,
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -119,7 +109,14 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadImage = async (img) => {
|
const handleUploadImage = async (img) => {
|
||||||
setSearchParams({ })
|
const session = JSON.parse(sessionStorage.getItem('session'));
|
||||||
|
if (!session?.name || !session?.phoneNumber) {
|
||||||
|
setIsPoppedUp(img); // simpan gambar untuk dikirim setelah user isi data
|
||||||
|
setSearchParams({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchParams({});
|
||||||
const newMessages = [
|
const newMessages = [
|
||||||
...messages,
|
...messages,
|
||||||
{ sender: 'user', img: img, time: getTime() },
|
{ sender: 'user', img: img, time: getTime() },
|
||||||
@@ -128,14 +125,12 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
setIsLoading('Menganalisa gambar anda...');
|
setIsLoading('Menganalisa gambar anda...');
|
||||||
|
|
||||||
const data = await askToBot({ type: 'image', content: img });
|
const data = await askToBot({ type: 'image', content: img });
|
||||||
|
|
||||||
const botAnswer = data.jawaban || 'Maaf, saya tidak bisa menganalisis gambar tersebut.';
|
const botAnswer = data.jawaban || 'Maaf, saya tidak bisa menganalisis gambar tersebut.';
|
||||||
|
|
||||||
setMessages((prev) => [
|
setMessages((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{ sender: 'bot', text: botAnswer, time: getTime() },
|
{ sender: 'bot', text: botAnswer, time: getTime() },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setIsLoading('');
|
setIsLoading('');
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,25 +145,19 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newMessages = [
|
const newMessages = [...messages, { sender: 'user', text: message, time: getTime() }];
|
||||||
...messages,
|
|
||||||
{ sender: 'user', text: message, time: getTime() },
|
|
||||||
];
|
|
||||||
|
|
||||||
setMessages(newMessages);
|
setMessages(newMessages);
|
||||||
setInput('');
|
setInput('');
|
||||||
setIsLoading('Mengetik...');
|
setIsLoading('Mengetik...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await askToBot({ type: 'text', content: message, tryCount });
|
const data = await askToBot({ type: 'text', content: message, tryCount });
|
||||||
|
|
||||||
const botAnswer = data.jawaban || 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti';
|
const botAnswer = data.jawaban || 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti';
|
||||||
|
|
||||||
setMessages(prev => [
|
setMessages(prev => [
|
||||||
...prev,
|
...prev,
|
||||||
{ sender: 'bot', text: botAnswer, time: getTime() },
|
{ sender: 'bot', text: botAnswer, time: getTime() },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setIsLoading('');
|
setIsLoading('');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error sending message:', error);
|
console.error('Error sending message:', error);
|
||||||
@@ -186,10 +175,8 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
|
|
||||||
function formatBoldText(text) {
|
function formatBoldText(text) {
|
||||||
const parts = text.split(/(\*\*[^\*]+\*\*)/g);
|
const parts = text.split(/(\*\*[^\*]+\*\*)/g);
|
||||||
|
|
||||||
return parts.flatMap((part, index) => {
|
return parts.flatMap((part, index) => {
|
||||||
const elements = [];
|
const elements = [];
|
||||||
|
|
||||||
if (part.startsWith('**') && part.endsWith('**')) {
|
if (part.startsWith('**') && part.endsWith('**')) {
|
||||||
part = part.slice(2, -2);
|
part = part.slice(2, -2);
|
||||||
part.split('\n').forEach((line, i) => {
|
part.split('\n').forEach((line, i) => {
|
||||||
@@ -202,7 +189,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
elements.push(<span key={`text-${index}-${i}`}>{line}</span>);
|
elements.push(<span key={`text-${index}-${i}`}>{line}</span>);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return elements;
|
return elements;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -215,7 +201,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.chatBody}>
|
<div className={styles.chatBody}>
|
||||||
{isLoading != '' && (
|
{isLoading !== '' && (
|
||||||
<div className={`${styles.messageRow} ${styles.bot}`}>
|
<div className={`${styles.messageRow} ${styles.bot}`}>
|
||||||
<div className={`${styles.message} ${styles.bot}`}>
|
<div className={`${styles.message} ${styles.bot}`}>
|
||||||
<em>{isLoading}</em>
|
<em>{isLoading}</em>
|
||||||
@@ -223,39 +209,30 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{messages.slice().reverse().map((msg, index) => (
|
{messages.slice().reverse().map((msg, index) => (
|
||||||
<div
|
<div key={index} className={`${styles.messageRow} ${styles[msg.sender]}`}>
|
||||||
key={index}
|
|
||||||
className={`${styles.messageRow} ${styles[msg.sender]}`}
|
|
||||||
>
|
|
||||||
<div className={`${styles.message} ${styles[msg.sender]}`}>
|
<div className={`${styles.message} ${styles[msg.sender]}`}>
|
||||||
{msg.sender !== 'bot'
|
{msg.sender !== 'bot' ? (
|
||||||
? (msg.text ?
|
msg.text ? msg.text : <img style={{ maxHeight: '300px', maxWidth: '240px', borderRadius: '12px' }} src={msg.img} />
|
||||||
msg.text
|
) : (
|
||||||
:
|
(() => {
|
||||||
<img style={{ maxHeight: '300px', maxWidth: '240px', borderRadius: '12px' }} src={msg.img} />
|
|
||||||
)
|
|
||||||
: (() => {
|
|
||||||
try {
|
try {
|
||||||
return formatBoldText(msg.text);
|
return formatBoldText(msg.text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return msg.text;
|
return msg.text;
|
||||||
}
|
}
|
||||||
})()}
|
})()
|
||||||
|
)}
|
||||||
{msg.quickReplies && (
|
{msg.quickReplies && (
|
||||||
<div className={styles.quickReplies}>
|
<div className={styles.quickReplies}>
|
||||||
{msg.quickReplies.map((reply, i) => (
|
{msg.quickReplies.map((reply, i) => (
|
||||||
<div
|
<div key={i} className={styles.quickReply} onClick={() => sendMessage(reply)}>
|
||||||
key={i}
|
|
||||||
className={styles.quickReply}
|
|
||||||
onClick={() => sendMessage(reply)}
|
|
||||||
>
|
|
||||||
{reply}
|
{reply}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div
|
<div
|
||||||
className={styles.quickReply}
|
className={styles.quickReply}
|
||||||
onClick={() => setSearchParams({ camera: 'open' })}
|
onClick={() => setSearchParams({ camera: 'open' })}
|
||||||
style={{ color: 'white', backgroundColor: '#075e54', display: 'flex', flexDirection: 'row', alignItems: 'center' }}
|
style={{ color: 'white', backgroundColor: '#075e54', display: 'flex', alignItems: 'center' }}
|
||||||
>
|
>
|
||||||
<img style={{ marginRight: '5px', height: '14px', filter: 'invert(1)' }} src={'/camera.png'} />
|
<img style={{ marginRight: '5px', height: '14px', filter: 'invert(1)' }} src={'/camera.png'} />
|
||||||
Analisa Gambar
|
Analisa Gambar
|
||||||
@@ -275,27 +252,17 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
|
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
|
||||||
disabled={isLoading != ''}
|
disabled={isLoading !== ''}
|
||||||
/>
|
/>
|
||||||
|
<button onClick={() => sendMessage()} style={{ marginLeft: '-40px' }} disabled={isLoading !== ''}>
|
||||||
<button onClick={() => sendMessage()} style={{marginLeft: '-40px'}}disabled={isLoading!=''}>
|
<img src="/send.png" alt="Kirim" style={{ height: '20px', filter: 'invert(1)' }} />
|
||||||
<img
|
|
||||||
src="/send.png"
|
|
||||||
alt="Kirim"
|
|
||||||
style={{ height: '20px', filter: 'invert(1)' }}
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
|
<button onClick={() => setSearchParams({ camera: 'open' })} disabled={isLoading !== ''}>
|
||||||
<button onClick={() => setSearchParams({ camera: 'open' })} disabled={isLoading!=''}>
|
<img src="/camera.png" alt="Kamera" style={{ height: '18px', filter: 'invert(1)' }} />
|
||||||
<img
|
|
||||||
src="/camera.png"
|
|
||||||
alt="Kamera"
|
|
||||||
style={{ height: '18px', filter: 'invert(1)' }}
|
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isPoppedUp !== '' &&
|
{isPoppedUp !== '' && (
|
||||||
<div className={styles.PopUp}>
|
<div className={styles.PopUp}>
|
||||||
<div className={`${styles.message} ${styles['bot']}`}>
|
<div className={`${styles.message} ${styles['bot']}`}>
|
||||||
Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda?
|
Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda?
|
||||||
@@ -326,7 +293,6 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.nextButton}
|
className={styles.nextButton}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -336,7 +302,11 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
sessionData.phoneNumber = phoneNumber;
|
sessionData.phoneNumber = phoneNumber;
|
||||||
sessionStorage.setItem('session', JSON.stringify(sessionData));
|
sessionStorage.setItem('session', JSON.stringify(sessionData));
|
||||||
setIsPoppedUp('');
|
setIsPoppedUp('');
|
||||||
sendMessage(isPoppedUp);
|
if (typeof isPoppedUp === 'string' && isPoppedUp.startsWith('data:image/')) {
|
||||||
|
handleUploadImage(isPoppedUp);
|
||||||
|
} else {
|
||||||
|
sendMessage(isPoppedUp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -345,7 +315,7 @@ const isOpenCamera = searchParams.get('camera') === 'open';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
{isOpenCamera && (
|
{isOpenCamera && (
|
||||||
<Camera
|
<Camera
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
const [weeklyData, setWeeklyData] = useState([]);
|
const [weeklyData, setWeeklyData] = useState([]);
|
||||||
const [allTimeData, setAllTimeData] = useState([]);
|
const [allTimeData, setAllTimeData] = useState([]);
|
||||||
|
const [faceAnalystList, setFaceAnalystList] = useState([]);
|
||||||
|
|
||||||
const [conversations, setConversations] = useState([]);
|
const [conversations, setConversations] = useState([]);
|
||||||
const [followUps, setFollowUps] = useState([]);
|
const [followUps, setFollowUps] = useState([]);
|
||||||
@@ -318,6 +319,8 @@ function parseGraphData(graph) {
|
|||||||
try{
|
try{
|
||||||
console.log(data);
|
console.log(data);
|
||||||
setDiscussedTopics(data[0]?.graph[0]?.json?.result?.topics)
|
setDiscussedTopics(data[0]?.graph[0]?.json?.result?.topics)
|
||||||
|
setFaceAnalystList(data[0]?.graph[0]?.json?.result?.analyst_counter);
|
||||||
|
|
||||||
setFollowUps(data[0]?.graph[0]?.json?.result?.interested_users)
|
setFollowUps(data[0]?.graph[0]?.json?.result?.interested_users)
|
||||||
setFileList(data[0]?.files)
|
setFileList(data[0]?.files)
|
||||||
setUpdateDetected(data[1]?.updateDetected)
|
setUpdateDetected(data[1]?.updateDetected)
|
||||||
@@ -437,7 +440,7 @@ function parseGraphData(graph) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openTopicsModal = () => {
|
const openTopicsModal = () => {
|
||||||
setModalContent(<DiscussedTopics topics={discussedTopics} />);
|
setModalContent(<DiscussedTopics topics={discussedTopics} faceAnalystList={faceAnalystList} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteFile = async (key) => {
|
const handleDeleteFile = async (key) => {
|
||||||
|
|||||||
@@ -1,32 +1,66 @@
|
|||||||
// DiscussedTopics.js
|
// DiscussedTopics.js
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import styles from './DiscussedTopics.module.css';
|
import styles from './DiscussedTopics.module.css';
|
||||||
|
|
||||||
const DiscussedTopics = ({ topics }) => {
|
const DiscussedTopics = ({ topics, faceAnalystList }) => {
|
||||||
|
const [activeTab, setActiveTab] = useState('topics'); // 'topics' or 'face'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<h2 className={styles.title}>Top Topic</h2>
|
<div className={styles.tabButtons}>
|
||||||
|
<button
|
||||||
|
className={`${styles.tabButton} ${activeTab === 'topics' ? styles.activeTab : ''}`}
|
||||||
|
onClick={() => setActiveTab('topics')}
|
||||||
|
>
|
||||||
|
Top Topic
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${styles.tabButton} ${activeTab === 'face' ? styles.activeTab : ''}`}
|
||||||
|
onClick={() => setActiveTab('face')}
|
||||||
|
>
|
||||||
|
Face Analyst Report
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className={styles.resultCount}>
|
<div className={styles.resultCount}>
|
||||||
{topics.length} topik
|
{activeTab === 'topics'
|
||||||
|
? `${topics.length} topik`
|
||||||
|
: `${faceAnalystList.length} report`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{topics.length === 0 ? (
|
{activeTab === 'topics' ? (
|
||||||
|
topics.length === 0 ? (
|
||||||
|
<div className={styles.emptyState}>
|
||||||
|
<div className={styles.emptyIcon}>💬</div>
|
||||||
|
<p>Discussed Topic Is Empty</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
topics.map((topic, idx) => (
|
||||||
|
<div key={idx} className={styles.card}>
|
||||||
|
<div className={styles.cardContent}>
|
||||||
|
<h3 className={styles.topicName}>{topic.topic}</h3>
|
||||||
|
<div className={styles.countBadge}>
|
||||||
|
<span className={styles.countValue}>{topic.count}</span>
|
||||||
|
<span className={styles.countLabel}>times</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
) : faceAnalystList.length === 0 ? (
|
||||||
<div className={styles.emptyState}>
|
<div className={styles.emptyState}>
|
||||||
<div className={styles.emptyIcon}>💬</div>
|
<div className={styles.emptyIcon}>🧑⚕️</div>
|
||||||
<p>Discussed Topic Is Empty</p>
|
<p>No Face Analyst Report</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
topics.map((topic, idx) => (
|
faceAnalystList.map((item, idx) => (
|
||||||
<div key={idx} className={styles.card}>
|
<div key={idx} className={styles.card}>
|
||||||
<div className={styles.cardContent}>
|
<div className={styles.cardContent}>
|
||||||
<h3 className={styles.topicName}>{topic.topic}</h3>
|
<h3 className={styles.topicName}>{item.name}</h3>
|
||||||
<div className={styles.countBadge}>
|
<p className={styles.description}>{item.description}</p>
|
||||||
<span className={styles.countValue}>{topic.count}</span>
|
<p className={styles.phone}>📞 {item.phone_number}</p>
|
||||||
<span className={styles.countLabel}>times</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
@@ -36,4 +70,4 @@ const DiscussedTopics = ({ topics }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DiscussedTopics;
|
export default DiscussedTopics;
|
||||||
|
|||||||
@@ -184,4 +184,35 @@
|
|||||||
.countLabel {
|
.countLabel {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabButtons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabButton {
|
||||||
|
background-color: #eee;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeTab {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user