@ant-design/icons#CommentOutlined TypeScript Examples
The following examples show how to use
@ant-design/icons#CommentOutlined.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx From RareCamp with Apache License 2.0 | 6 votes |
export default function ServiceProvider({ serviceProvider, task }) {
const [isModalVisible, setIsModalVisible] = useState(false)
return (
<Container>
<Space size={16}>
<img
width="60px"
src={serviceProvider.logoURL}
alt={`${serviceProvider.name} logo`}
/>
<div>
<div className="sp-name">{serviceProvider.name}</div>
<div className="sp-type">{serviceProvider.type}</div>
</div>
</Space>
<Button
type="link"
onClick={() => setIsModalVisible(true)}
icon={<CommentOutlined />}
style={{
border: '1px solid',
borderRadius: 0,
}}
>
Reach out
</Button>
<ContactServiceProviderModal
task={task}
serviceProvider={serviceProvider}
visible={isModalVisible}
hide={() => setIsModalVisible(false)}
/>
</Container>
)
}
Example #2
Source File: index.tsx From memex with MIT License | 6 votes |
renderExtraOptions(id) {
return (
<div className="extra-options">
<Tooltip
title="Edit card"
onClick={() => {
this.editNote(id);
}}
>
<FormOutlined className="option-icon" />
</Tooltip>
<Tooltip title="Add to card">
<CommentOutlined className="option-icon" />
</Tooltip>
<Tooltip title="Add tags">
<TagsOutlined className="option-icon" />
</Tooltip>
<Tooltip title="Delete">
<DeleteOutlined
onClick={() => {
this.deleteNote(id);
}}
className="option-icon"
/>
</Tooltip>
</div>
);
}
Example #3
Source File: NotificationList.tsx From foodie with MIT License | 5 votes |
NotificationList: React.FC<IProps> = (props) => {
const { toggleNotification, notifications, readNotification, infiniteScrollRef } = props;
const history = useHistory();
const handleNotificationClick = (link: string, id: string) => {
readNotification(id);
toggleNotification(false);
history.push(link);
};
return (
<div className="relative">
{notifications.length === 0 ? (
<div className="text-center p-4">
<p className="text-gray-400 italic">No new notifications</p>
</div>
) : (
<div
className="max-h-80vh laptop:max-h-70vh relative overflow-y-scroll divide-y divide-gray-100 scrollbar"
>
<TransitionGroup component={null}>
<div ref={infiniteScrollRef as React.RefObject<HTMLDivElement>}>
{notifications.map((notif) => notif.initiator && (
<CSSTransition
timeout={500}
classNames="fade"
key={notif.id}
>
<div
className={`border border-transparent dark:hover:border-indigo-700 ${notif.unread ? 'bg-indigo-100 dark:bg-indigo-1100 hover:bg-indigo-200' : 'bg-white dark:bg-indigo-1000 dark:hover:bg-indigo-1100'} p-4 hover:bg-gray-100 dark:hover:bg-indigo-1100 hover:opacity-95 divide-y divide-y-2 divide-gray-100`}
key={notif.id}
onClick={() => handleNotificationClick(notif.link, notif.id)}
>
<div className="relative">
<div className="flex items-start">
<Avatar
url={notif.initiator.profilePicture?.url}
size={window.screen.width < 1024 ? 'sm' : 'lg'}
className="mr-2 flex-shrink-0"
/>
<div>
<span className="text-indigo-700 dark:text-indigo-400 font-medium break-all">
{notif.initiator.username}
</span>
<span className="text-gray-600 dark:text-gray-400 break-all">
{
notif.type === 'like' ? 'likes your post.'
: notif.type === 'comment' ? 'commented on your post.'
: notif.type === 'comment-like' ? 'likes your comment.'
: notif.type === 'follow' ? 'started following you.'
: notif.type === 'reply' ? 'replied to your comment'
: ''
}
</span>
<br />
<span className="text-gray-500 text-1xs block">
{displayTime(notif.createdAt)}
</span>
</div>
</div>
{(notif.type === 'like' || notif.type === 'comment-like') ? (
<LikeOutlined className="text-2xl text-indigo-700 dark:text-indigo-400 absolute right-4 top-0 bottom-0 my-auto" />
) : (notif.type === 'comment' || notif.type === 'reply') ? (
<CommentOutlined className="text-2xl text-indigo-700 dark:text-indigo-400 absolute right-4 top-0 bottom-0 my-auto" />
) : (
<UserAddOutlined className="text-2xl text-indigo-700 dark:text-indigo-400 absolute right-4 top-0 bottom-0 my-auto" />
)}
</div>
</div>
</CSSTransition>
))}
</div>
</TransitionGroup>
</div>
)}
</div>
);
}
Example #4
Source File: Posts.tsx From foodie with MIT License | 5 votes |
Posts: React.FC<IProps> = ({ posts, searchQuery }) => {
const history = useHistory();
useDocumentTitle(`Search Posts | Foodie`);
const onClickPost = (id: string) => {
history.push(`/post/${id}`);
};
const onClickAuthor = (e: React.MouseEvent, username: string) => {
e.stopPropagation();
history.push(`/user/${username}`);
}
return (
<div className="space-y-4">
{posts.map((post) => post.author && (
<div
className="h-28 laptop:h-24 flex justify-start bg-white dark:bg-indigo-1100 rounded-md shadow-lg overflow-hidden cursor-pointer border border-transparent hover:border-indigo-700"
key={post.id}
onClick={(e) => onClickPost(post.id)}
>
<div
className="w-24 laptop:w-32 h-full !bg-cover !bg-no-repeat !bg-center"
style={{
background: `#f7f7f7 url(${post.photos[0]?.url || thumbnail})`
}}
/>
<div className="flex-grow p-2 pb-4 max-w-sm">
<h4
className="break-all overflow-hidden overflow-ellipsis h-12 font-normal dark:text-white"
dangerouslySetInnerHTML={{ __html: boldString(post.description, searchQuery) }}
>
</h4>
<div className="flex flex-col laptop:flex-row space-y-2 laptop:space-y-0 self-end">
<div className="flex">
<h6 className="text-xs text-gray-400 laptop:mr-4">
Posted by
<span
className="underline text-indigo-700 dark:text-indigo-400 cursor-pointer hover:text-indigo-400"
onClick={(e) => onClickAuthor(e, post.author.username)}
>
{post.author.username}
</span>
{dayjs(post.createdAt).fromNow()}
</h6>
</div>
<div className="flex space-x-2">
<h6 className="text-sm text-gray-600 flex items-center">
{post.likesCount}
<LikeOutlined />
</h6>
<h6 className="text-sm text-gray-600 flex items-center">
{post.commentsCount}
<CommentOutlined />
</h6>
</div>
</div>
</div>
</div>
))}
</div>
);
}
Example #5
Source File: commandPaletteLogic.ts From posthog-foss with MIT License | 4 votes |
commandPaletteLogic = kea<
commandPaletteLogicType<
Command,
CommandFlow,
CommandRegistrations,
CommandResult,
CommandResultDisplayable,
RegExpCommandPairs
>
>({
path: ['lib', 'components', 'CommandPalette', 'commandPaletteLogic'],
connect: {
actions: [personalAPIKeysLogic, ['createKey']],
values: [teamLogic, ['currentTeam'], userLogic, ['user']],
logic: [preflightLogic], // used in afterMount, which does not auto-connect
},
actions: {
hidePalette: true,
showPalette: true,
togglePalette: true,
setInput: (input: string) => ({ input }),
onArrowUp: true,
onArrowDown: (maxIndex: number) => ({ maxIndex }),
onMouseEnterResult: (index: number) => ({ index }),
onMouseLeaveResult: true,
executeResult: (result: CommandResult) => ({ result }),
activateFlow: (flow: CommandFlow | null) => ({ flow }),
backFlow: true,
registerCommand: (command: Command) => ({ command }),
deregisterCommand: (commandKey: string) => ({ commandKey }),
setCustomCommand: (commandKey: string) => ({ commandKey }),
deregisterScope: (scope: string) => ({ scope }),
},
reducers: {
isPaletteShown: [
false,
{
hidePalette: () => false,
showPalette: () => true,
togglePalette: (previousState) => !previousState,
},
],
keyboardResultIndex: [
0,
{
setInput: () => 0,
executeResult: () => 0,
activateFlow: () => 0,
backFlow: () => 0,
onArrowUp: (previousIndex) => (previousIndex > 0 ? previousIndex - 1 : 0),
onArrowDown: (previousIndex, { maxIndex }) => (previousIndex < maxIndex ? previousIndex + 1 : maxIndex),
},
],
hoverResultIndex: [
null as number | null,
{
activateFlow: () => null,
backFlow: () => null,
onMouseEnterResult: (_, { index }) => index,
onMouseLeaveResult: () => null,
onArrowUp: () => null,
onArrowDown: () => null,
},
],
input: [
'',
{
setInput: (_, { input }) => input,
activateFlow: () => '',
backFlow: () => '',
executeResult: () => '',
},
],
activeFlow: [
null as CommandFlow | null,
{
activateFlow: (currentFlow, { flow }) =>
flow ? { ...flow, scope: flow.scope ?? currentFlow?.scope, previousFlow: currentFlow } : null,
backFlow: (currentFlow) => currentFlow?.previousFlow ?? null,
},
],
rawCommandRegistrations: [
{} as CommandRegistrations,
{
registerCommand: (commands, { command }) => {
return { ...commands, [command.key]: command }
},
deregisterCommand: (commands, { commandKey }) => {
const { [commandKey]: _, ...cleanedCommands } = commands // eslint-disable-line
return cleanedCommands
},
},
],
},
listeners: ({ actions, values }) => ({
showPalette: () => {
posthog.capture('palette shown', { isMobile: isMobile() })
},
togglePalette: () => {
if (values.isPaletteShown) {
posthog.capture('palette shown', { isMobile: isMobile() })
}
},
executeResult: ({ result }: { result: CommandResult }) => {
if (result.executor === true) {
actions.activateFlow(null)
actions.hidePalette()
} else {
const possibleFlow = result.executor?.() || null
actions.activateFlow(possibleFlow)
if (!possibleFlow) {
actions.hidePalette()
}
}
// Capture command execution, without useless data
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { icon, index, ...cleanedResult }: Record<string, any> = result
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { resolver, ...cleanedCommand } = cleanedResult.source
cleanedResult.source = cleanedCommand
cleanedResult.isMobile = isMobile()
posthog.capture('palette command executed', cleanedResult)
},
deregisterScope: ({ scope }) => {
for (const command of Object.values(values.commandRegistrations)) {
if (command.scope === scope) {
actions.deregisterCommand(command.key)
}
}
},
setInput: async ({ input }, breakpoint) => {
await breakpoint(300)
if (input.length > 8) {
const response = await api.get('api/person/?key_identifier=' + encodeURIComponent(input))
const person = response.results[0]
if (person) {
actions.registerCommand({
key: `person-${person.distinct_ids[0]}`,
resolver: [
{
icon: UserOutlined,
display: `View person ${input}`,
executor: () => {
const { push } = router.actions
push(urls.person(person.distinct_ids[0]))
},
},
],
scope: GLOBAL_COMMAND_SCOPE,
})
}
}
},
}),
selectors: {
isSqueak: [
(selectors) => [selectors.input],
(input: string) => {
return input.trim().toLowerCase() === 'squeak'
},
],
activeResultIndex: [
(selectors) => [selectors.keyboardResultIndex, selectors.hoverResultIndex],
(keyboardResultIndex: number, hoverResultIndex: number | null) => {
return hoverResultIndex ?? keyboardResultIndex
},
],
commandRegistrations: [
(selectors) => [
selectors.rawCommandRegistrations,
dashboardsModel.selectors.nameSortedDashboards,
teamLogic.selectors.currentTeam,
],
(rawCommandRegistrations: CommandRegistrations, dashboards: DashboardType[]): CommandRegistrations => ({
...rawCommandRegistrations,
custom_dashboards: {
key: 'custom_dashboards',
resolver: dashboards.map((dashboard: DashboardType) => ({
key: `dashboard_${dashboard.id}`,
icon: LineChartOutlined,
display: `Go to Dashboard: ${dashboard.name}`,
executor: () => {
const { push } = router.actions
push(urls.dashboard(dashboard.id))
},
})),
scope: GLOBAL_COMMAND_SCOPE,
},
}),
],
regexpCommandPairs: [
(selectors) => [selectors.commandRegistrations],
(commandRegistrations: CommandRegistrations) => {
const array: RegExpCommandPairs = []
for (const command of Object.values(commandRegistrations)) {
if (command.prefixes) {
array.push([new RegExp(`^\\s*(${command.prefixes.join('|')})(?:\\s+(.*)|$)`, 'i'), command])
} else {
array.push([null, command])
}
}
return array
},
],
commandSearchResults: [
(selectors) => [
selectors.isPaletteShown,
selectors.regexpCommandPairs,
selectors.input,
selectors.activeFlow,
selectors.isSqueak,
],
(
isPaletteShown: boolean,
regexpCommandPairs: RegExpCommandPairs,
argument: string,
activeFlow: CommandFlow | null,
isSqueak: boolean
) => {
if (!isPaletteShown || isSqueak) {
return []
}
if (activeFlow) {
return resolveCommand(activeFlow, argument)
}
let directResults: CommandResult[] = []
let prefixedResults: CommandResult[] = []
for (const [regexp, command] of regexpCommandPairs) {
if (regexp) {
const match = argument.match(regexp)
if (match && match[1]) {
prefixedResults = [...prefixedResults, ...resolveCommand(command, match[2], match[1])]
}
}
directResults = [...directResults, ...resolveCommand(command, argument)]
}
const allResults = directResults.concat(prefixedResults)
let fusableResults: CommandResult[] = []
let guaranteedResults: CommandResult[] = []
for (const result of allResults) {
if (result.guarantee) {
guaranteedResults.push(result)
} else {
fusableResults.push(result)
}
}
fusableResults = uniqueBy(fusableResults, (result) => result.display)
guaranteedResults = uniqueBy(guaranteedResults, (result) => result.display)
const fusedResults = argument
? new Fuse(fusableResults, {
keys: ['display', 'synonyms'],
})
.search(argument)
.slice(0, RESULTS_MAX)
.map((result) => result.item)
: sample(fusableResults, RESULTS_MAX - guaranteedResults.length)
return guaranteedResults.concat(fusedResults)
},
],
commandSearchResultsGrouped: [
(selectors) => [selectors.commandSearchResults, selectors.activeFlow],
(commandSearchResults: CommandResult[], activeFlow: CommandFlow | null) => {
const resultsGrouped: {
[scope: string]: CommandResult[]
} = {}
if (activeFlow) {
resultsGrouped[activeFlow.scope ?? '?'] = []
}
for (const result of commandSearchResults) {
const scope: string = result.source.scope ?? '?'
if (!(scope in resultsGrouped)) {
resultsGrouped[scope] = []
} // Ensure there's an array to push to
resultsGrouped[scope].push({ ...result })
}
let rollingGroupIndex = 0
let rollingResultIndex = 0
const resultsGroupedInOrder: [string, CommandResultDisplayable[]][] = []
for (const [group, results] of Object.entries(resultsGrouped)) {
resultsGroupedInOrder.push([group, []])
for (const result of results) {
resultsGroupedInOrder[rollingGroupIndex][1].push({ ...result, index: rollingResultIndex++ })
}
rollingGroupIndex++
}
return resultsGroupedInOrder
},
],
},
events: ({ actions }) => ({
afterMount: () => {
const { push } = router.actions
const goTo: Command = {
key: 'go-to',
scope: GLOBAL_COMMAND_SCOPE,
prefixes: ['open', 'visit'],
resolver: [
{
icon: FundOutlined,
display: 'Go to Dashboards',
executor: () => {
push(urls.dashboards())
},
},
{
icon: RiseOutlined,
display: 'Go to Insights',
executor: () => {
push(urls.savedInsights())
},
},
{
icon: RiseOutlined,
display: 'Go to Trends',
executor: () => {
// TODO: Don't reset insight on change
push(urls.insightNew({ insight: InsightType.TRENDS }))
},
},
{
icon: FunnelPlotOutlined,
display: 'Go to Funnels',
executor: () => {
// TODO: Don't reset insight on change
push(urls.insightNew({ insight: InsightType.FUNNELS }))
},
},
{
icon: GatewayOutlined,
display: 'Go to Retention',
executor: () => {
// TODO: Don't reset insight on change
push(urls.insightNew({ insight: InsightType.RETENTION }))
},
},
{
icon: InteractionOutlined,
display: 'Go to Paths',
executor: () => {
// TODO: Don't reset insight on change
push(urls.insightNew({ insight: InsightType.PATHS }))
},
},
{
icon: ContainerOutlined,
display: 'Go to Events',
executor: () => {
push(urls.events())
},
},
{
icon: AimOutlined,
display: 'Go to Actions',
executor: () => {
push(urls.actions())
},
},
{
icon: UserOutlined,
display: 'Go to Persons',
synonyms: ['people'],
executor: () => {
push(urls.persons())
},
},
{
icon: UsergroupAddOutlined,
display: 'Go to Cohorts',
executor: () => {
push(urls.cohorts())
},
},
{
icon: FlagOutlined,
display: 'Go to Feature Flags',
synonyms: ['feature flags', 'a/b tests'],
executor: () => {
push(urls.featureFlags())
},
},
{
icon: MessageOutlined,
display: 'Go to Annotations',
executor: () => {
push(urls.annotations())
},
},
{
icon: TeamOutlined,
display: 'Go to Team members',
synonyms: ['organization', 'members', 'invites', 'teammates'],
executor: () => {
push(urls.organizationSettings())
},
},
{
icon: ProjectOutlined,
display: 'Go to Project settings',
executor: () => {
push(urls.projectSettings())
},
},
{
icon: SmileOutlined,
display: 'Go to My settings',
synonyms: ['account'],
executor: () => {
push(urls.mySettings())
},
},
{
icon: ApiOutlined,
display: 'Go to Plugins',
synonyms: ['integrations'],
executor: () => {
push(urls.plugins())
},
},
{
icon: DatabaseOutlined,
display: 'Go to System status page',
synonyms: ['redis', 'celery', 'django', 'postgres', 'backend', 'service', 'online'],
executor: () => {
push(urls.systemStatus())
},
},
{
icon: PlusOutlined,
display: 'Create action',
executor: () => {
push(urls.createAction())
},
},
{
icon: LogoutOutlined,
display: 'Log out',
executor: () => {
userLogic.actions.logout()
},
},
],
}
const debugClickhouseQueries: Command = {
key: 'debug-clickhouse-queries',
scope: GLOBAL_COMMAND_SCOPE,
resolver:
userLogic.values.user?.is_staff ||
userLogic.values.user?.is_impersonated ||
preflightLogic.values.preflight?.is_debug ||
preflightLogic.values.preflight?.instance_preferences?.debug_queries
? {
icon: PlusOutlined,
display: 'Debug queries (ClickHouse)',
executor: () => {
debugCHQueries()
},
}
: [],
}
const calculator: Command = {
key: 'calculator',
scope: GLOBAL_COMMAND_SCOPE,
resolver: (argument) => {
// don't try evaluating if there's no argument or if it's a plain number already
if (!argument || !isNaN(+argument)) {
return null
}
try {
const result = +Parser.evaluate(argument)
return isNaN(result)
? null
: {
icon: CalculatorOutlined,
display: `= ${result}`,
guarantee: true,
executor: () => {
copyToClipboard(result.toString(), 'calculation result')
},
}
} catch {
return null
}
},
}
const openUrls: Command = {
key: 'open-urls',
scope: GLOBAL_COMMAND_SCOPE,
prefixes: ['open', 'visit'],
resolver: (argument) => {
const results: CommandResultTemplate[] = (teamLogic.values.currentTeam?.app_urls ?? []).map(
(url: string) => ({
icon: LinkOutlined,
display: `Open ${url}`,
synonyms: [`Visit ${url}`],
executor: () => {
open(url)
},
})
)
if (argument && isURL(argument)) {
results.push({
icon: LinkOutlined,
display: `Open ${argument}`,
synonyms: [`Visit ${argument}`],
executor: () => {
open(argument)
},
})
}
results.push({
icon: LinkOutlined,
display: 'Open PostHog Docs',
synonyms: ['technical documentation'],
executor: () => {
open('https://posthog.com/docs')
},
})
return results
},
}
const createPersonalApiKey: Command = {
key: 'create-personal-api-key',
scope: GLOBAL_COMMAND_SCOPE,
resolver: {
icon: KeyOutlined,
display: 'Create Personal API Key',
executor: () => ({
instruction: 'Give your key a label',
icon: TagOutlined,
scope: 'Creating Personal API Key',
resolver: (argument) => {
if (argument?.length) {
return {
icon: KeyOutlined,
display: `Create Key "${argument}"`,
executor: () => {
personalAPIKeysLogic.actions.createKey(argument)
push(urls.mySettings(), {}, 'personal-api-keys')
},
}
}
return null
},
}),
},
}
const createDashboard: Command = {
key: 'create-dashboard',
scope: GLOBAL_COMMAND_SCOPE,
resolver: {
icon: FundOutlined,
display: 'Create Dashboard',
executor: () => ({
instruction: 'Name your new dashboard',
icon: TagOutlined,
scope: 'Creating Dashboard',
resolver: (argument) => {
if (argument?.length) {
return {
icon: FundOutlined,
display: `Create Dashboard "${argument}"`,
executor: () => {
dashboardsModel.actions.addDashboard({ name: argument, show: true })
},
}
}
return null
},
}),
},
}
const shareFeedback: Command = {
key: 'share-feedback',
scope: GLOBAL_COMMAND_SCOPE,
resolver: {
icon: CommentOutlined,
display: 'Share Feedback',
synonyms: ['send opinion', 'ask question', 'message posthog', 'github issue'],
executor: () => ({
scope: 'Sharing Feedback',
resolver: [
{
display: 'Send Message Directly to PostHog',
icon: CommentOutlined,
executor: () => ({
instruction: "What's on your mind?",
icon: CommentOutlined,
resolver: (argument) => ({
icon: SendOutlined,
display: 'Send',
executor: !argument?.length
? undefined
: () => {
posthog.capture('palette feedback', { message: argument })
return {
resolver: {
icon: CheckOutlined,
display: 'Message Sent!',
executor: true,
},
}
},
}),
}),
},
{
icon: VideoCameraOutlined,
display: 'Schedule Quick Call',
executor: () => {
open('https://calendly.com/posthog-feedback')
},
},
{
icon: ExclamationCircleOutlined,
display: 'Create GitHub Issue',
executor: () => {
open('https://github.com/PostHog/posthog/issues/new/choose')
},
},
],
}),
},
}
actions.registerCommand(goTo)
actions.registerCommand(openUrls)
actions.registerCommand(debugClickhouseQueries)
actions.registerCommand(calculator)
actions.registerCommand(createPersonalApiKey)
actions.registerCommand(createDashboard)
actions.registerCommand(shareFeedback)
},
beforeUnmount: () => {
actions.deregisterCommand('go-to')
actions.deregisterCommand('open-urls')
actions.deregisterCommand('debug-clickhouse-queries')
actions.deregisterCommand('calculator')
actions.deregisterCommand('create-personal-api-key')
actions.deregisterCommand('create-dashboard')
actions.deregisterCommand('share-feedback')
},
}),
})
Example #6
Source File: FunnelCorrelation.tsx From posthog-foss with MIT License | 4 votes |
FunnelCorrelation = (): JSX.Element | null => {
const { insightProps } = useValues(insightLogic)
const {
isSkewed,
stepsWithCount,
correlationFeedbackHidden,
correlationDetailedFeedbackVisible,
correlationFeedbackRating,
correlationAnalysisAvailable,
aggregationTargetLabel,
} = useValues(funnelLogic(insightProps))
const {
sendCorrelationAnalysisFeedback,
hideSkewWarning,
hideCorrelationAnalysisFeedback,
setCorrelationFeedbackRating,
setCorrelationDetailedFeedback,
} = useActions(funnelLogic(insightProps))
const { preflight } = useValues(preflightLogic)
const detailedFeedbackRef = useRef<HTMLTextAreaElement>(null)
if (stepsWithCount.length <= 1) {
return null
}
if (!correlationAnalysisAvailable && !preflight?.instance_preferences?.disable_paid_fs) {
return (
<PayCard
identifier={AvailableFeature.CORRELATION_ANALYSIS}
title={`Get a deeper understanding of why your ${aggregationTargetLabel.plural} are not converting`}
caption={`Correlation analysis automatically finds signals for why ${aggregationTargetLabel.plural} are converting or dropping off.`}
/>
)
}
return (
<>
<div className="funnel-correlation">
{isSkewed && (
<Card className="skew-warning">
<h4>
<IconFeedbackWarning style={{ fontSize: 24, marginRight: 4, color: 'var(--warning)' }} />{' '}
Adjust your funnel definition to improve correlation analysis
<CloseOutlined className="close-button" onClick={hideSkewWarning} />
</h4>
<div>
<b>Tips for adjusting your funnel:</b>
<ol>
<li>
Adjust your first funnel step to be more specific. For example, choose a page or an
event that occurs less frequently.
</li>
<li>Choose an event that happens more frequently for subsequent funnels steps.</li>
</ol>
</div>
</Card>
)}
<FunnelCorrelationTable />
{/* Feedback Form */}
{!correlationFeedbackHidden && (
<Card className="correlation-feedback">
<Row className="row-initial">
<Col span={14}>
<h4>
<CommentOutlined style={{ marginRight: 4 }} />
Was this correlation analysis report useful?
</h4>
</Col>
<Col span={9} style={{ alignContent: 'right' }}>
{!!correlationFeedbackRating && (
<i style={{ color: 'var(--success)', marginRight: 8 }}>Thanks for your feedback!</i>
)}
{(
[
[5, '?'],
[4, '?'],
[3, '?'],
[2, '?'],
[1, '?'],
] as const
).map((content, index) => (
<Button
key={index}
className="emoji-button"
style={
correlationFeedbackRating === content[0]
? { background: '#5375FF' }
: correlationFeedbackRating
? { display: 'none' }
: {}
}
onClick={() => {
if (correlationFeedbackRating === content[0]) {
setCorrelationFeedbackRating(0)
} else {
setCorrelationFeedbackRating(content[0])
setTimeout(() => detailedFeedbackRef.current?.focus(), 100)
}
}}
>
{content[1]}
</Button>
))}
</Col>
<Col span={1}>
<CloseOutlined className="close-button" onClick={hideCorrelationAnalysisFeedback} />
</Col>
</Row>
<div style={{ display: correlationDetailedFeedbackVisible ? undefined : 'none' }}>
<form onSubmit={sendCorrelationAnalysisFeedback}>
<TextArea
onBlur={(e) => setCorrelationDetailedFeedback(e.target.value)}
placeholder="Optional. Help us by sharing details around your experience..."
style={{ marginTop: 16 }}
ref={detailedFeedbackRef}
onKeyDown={(e) => {
if (e.key === 'Enter' && e.metaKey) {
detailedFeedbackRef.current?.blur()
sendCorrelationAnalysisFeedback()
}
}}
/>
<div className="text-right">
<Button
className="feedback-button"
data-attr="correlation-analysis-share-feedback"
type="primary"
htmlType="submit"
>
Share feedback
</Button>
</div>
</form>
</div>
</Card>
)}
<FunnelPropertyCorrelationTable />
</div>
</>
)
}
Example #7
Source File: Icon.tsx From html2sketch with MIT License | 4 votes |
IconSymbol: FC = () => {
return (
<Row>
{/*<CaretUpOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
{/*/>*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
{/*/>*/}
{/*<StepBackwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
{/*<StepForwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
<StepForwardOutlined />
<ShrinkOutlined />
<ArrowsAltOutlined />
<DownOutlined />
<UpOutlined />
<LeftOutlined />
<RightOutlined />
<CaretUpOutlined />
<CaretDownOutlined />
<CaretLeftOutlined />
<CaretRightOutlined />
<VerticalAlignTopOutlined />
<RollbackOutlined />
<FastBackwardOutlined />
<FastForwardOutlined />
<DoubleRightOutlined />
<DoubleLeftOutlined />
<VerticalLeftOutlined />
<VerticalRightOutlined />
<VerticalAlignMiddleOutlined />
<VerticalAlignBottomOutlined />
<ForwardOutlined />
<BackwardOutlined />
<EnterOutlined />
<RetweetOutlined />
<SwapOutlined />
<SwapLeftOutlined />
<SwapRightOutlined />
<ArrowUpOutlined />
<ArrowDownOutlined />
<ArrowLeftOutlined />
<ArrowRightOutlined />
<LoginOutlined />
<LogoutOutlined />
<MenuFoldOutlined />
<MenuUnfoldOutlined />
<BorderBottomOutlined />
<BorderHorizontalOutlined />
<BorderInnerOutlined />
<BorderOuterOutlined />
<BorderLeftOutlined />
<BorderRightOutlined />
<BorderTopOutlined />
<BorderVerticleOutlined />
<PicCenterOutlined />
<PicLeftOutlined />
<PicRightOutlined />
<RadiusBottomleftOutlined />
<RadiusBottomrightOutlined />
<RadiusUpleftOutlined />
<RadiusUprightOutlined />
<FullscreenOutlined />
<FullscreenExitOutlined />
<QuestionOutlined />
<PauseOutlined />
<MinusOutlined />
<PauseCircleOutlined />
<InfoOutlined />
<CloseOutlined />
<ExclamationOutlined />
<CheckOutlined />
<WarningOutlined />
<IssuesCloseOutlined />
<StopOutlined />
<EditOutlined />
<CopyOutlined />
<ScissorOutlined />
<DeleteOutlined />
<SnippetsOutlined />
<DiffOutlined />
<HighlightOutlined />
<AlignCenterOutlined />
<AlignLeftOutlined />
<AlignRightOutlined />
<BgColorsOutlined />
<BoldOutlined />
<ItalicOutlined />
<UnderlineOutlined />
<StrikethroughOutlined />
<RedoOutlined />
<UndoOutlined />
<ZoomInOutlined />
<ZoomOutOutlined />
<FontColorsOutlined />
<FontSizeOutlined />
<LineHeightOutlined />
<SortAscendingOutlined />
<SortDescendingOutlined />
<DragOutlined />
<OrderedListOutlined />
<UnorderedListOutlined />
<RadiusSettingOutlined />
<ColumnWidthOutlined />
<ColumnHeightOutlined />
<AreaChartOutlined />
<PieChartOutlined />
<BarChartOutlined />
<DotChartOutlined />
<LineChartOutlined />
<RadarChartOutlined />
<HeatMapOutlined />
<FallOutlined />
<RiseOutlined />
<StockOutlined />
<BoxPlotOutlined />
<FundOutlined />
<SlidersOutlined />
<AndroidOutlined />
<AppleOutlined />
<WindowsOutlined />
<IeOutlined />
<ChromeOutlined />
<GithubOutlined />
<AliwangwangOutlined />
<DingdingOutlined />
<WeiboSquareOutlined />
<WeiboCircleOutlined />
<TaobaoCircleOutlined />
<Html5Outlined />
<WeiboOutlined />
<TwitterOutlined />
<WechatOutlined />
<AlipayCircleOutlined />
<TaobaoOutlined />
<SkypeOutlined />
<FacebookOutlined />
<CodepenOutlined />
<CodeSandboxOutlined />
<AmazonOutlined />
<GoogleOutlined />
<AlipayOutlined />
<AntDesignOutlined />
<AntCloudOutlined />
<ZhihuOutlined />
<SlackOutlined />
<SlackSquareOutlined />
<BehanceSquareOutlined />
<DribbbleOutlined />
<DribbbleSquareOutlined />
<InstagramOutlined />
<YuqueOutlined />
<AlibabaOutlined />
<YahooOutlined />
<RedditOutlined />
<SketchOutlined />
<AccountBookOutlined />
<AlertOutlined />
<ApartmentOutlined />
<ApiOutlined />
<QqOutlined />
<MediumWorkmarkOutlined />
<GitlabOutlined />
<MediumOutlined />
<GooglePlusOutlined />
<AppstoreAddOutlined />
<AppstoreOutlined />
<AudioOutlined />
<AudioMutedOutlined />
<AuditOutlined />
<BankOutlined />
<BarcodeOutlined />
<BarsOutlined />
<BellOutlined />
<BlockOutlined />
<BookOutlined />
<BorderOutlined />
<BranchesOutlined />
<BuildOutlined />
<BulbOutlined />
<CalculatorOutlined />
<CalendarOutlined />
<CameraOutlined />
<CarOutlined />
<CarryOutOutlined />
<CiCircleOutlined />
<CiOutlined />
<CloudOutlined />
<ClearOutlined />
<ClusterOutlined />
<CodeOutlined />
<CoffeeOutlined />
<CompassOutlined />
<CompressOutlined />
<ContactsOutlined />
<ContainerOutlined />
<ControlOutlined />
<CopyrightCircleOutlined />
<CopyrightOutlined />
<CreditCardOutlined />
<CrownOutlined />
<CustomerServiceOutlined />
<DashboardOutlined />
<DatabaseOutlined />
<DeleteColumnOutlined />
<DeleteRowOutlined />
<DisconnectOutlined />
<DislikeOutlined />
<DollarCircleOutlined />
<DollarOutlined />
<DownloadOutlined />
<EllipsisOutlined />
<EnvironmentOutlined />
<EuroCircleOutlined />
<EuroOutlined />
<ExceptionOutlined />
<ExpandAltOutlined />
<ExpandOutlined />
<ExperimentOutlined />
<ExportOutlined />
<EyeOutlined />
<FieldBinaryOutlined />
<FieldNumberOutlined />
<FieldStringOutlined />
<DesktopOutlined />
<DingtalkOutlined />
<FileAddOutlined />
<FileDoneOutlined />
<FileExcelOutlined />
<FileExclamationOutlined />
<FileOutlined />
<FileImageOutlined />
<FileJpgOutlined />
<FileMarkdownOutlined />
<FilePdfOutlined />
<FilePptOutlined />
<FileProtectOutlined />
<FileSearchOutlined />
<FileSyncOutlined />
<FileTextOutlined />
<FileUnknownOutlined />
<FileWordOutlined />
<FilterOutlined />
<FireOutlined />
<FlagOutlined />
<FolderAddOutlined />
<FolderOutlined />
<FolderOpenOutlined />
<ForkOutlined />
<FormatPainterOutlined />
<FrownOutlined />
<FunctionOutlined />
<FunnelPlotOutlined />
<GatewayOutlined />
<GifOutlined />
<GiftOutlined />
<GlobalOutlined />
<GoldOutlined />
<GroupOutlined />
<HddOutlined />
<HeartOutlined />
<HistoryOutlined />
<HomeOutlined />
<HourglassOutlined />
<IdcardOutlined />
<ImportOutlined />
<InboxOutlined />
<InsertRowAboveOutlined />
<InsertRowBelowOutlined />
<InsertRowLeftOutlined />
<InsertRowRightOutlined />
<InsuranceOutlined />
<InteractionOutlined />
<KeyOutlined />
<LaptopOutlined />
<LayoutOutlined />
<LikeOutlined />
<LineOutlined />
<LinkOutlined />
<Loading3QuartersOutlined />
<LoadingOutlined />
<LockOutlined />
<MailOutlined />
<ManOutlined />
<MedicineBoxOutlined />
<MehOutlined />
<MenuOutlined />
<MergeCellsOutlined />
<MessageOutlined />
<MobileOutlined />
<MoneyCollectOutlined />
<MonitorOutlined />
<MoreOutlined />
<NodeCollapseOutlined />
<NodeExpandOutlined />
<NodeIndexOutlined />
<NotificationOutlined />
<NumberOutlined />
<PaperClipOutlined />
<PartitionOutlined />
<PayCircleOutlined />
<PercentageOutlined />
<PhoneOutlined />
<PictureOutlined />
<PoundCircleOutlined />
<PoundOutlined />
<PoweroffOutlined />
<PrinterOutlined />
<ProfileOutlined />
<ProjectOutlined />
<PropertySafetyOutlined />
<PullRequestOutlined />
<PushpinOutlined />
<QrcodeOutlined />
<ReadOutlined />
<ReconciliationOutlined />
<RedEnvelopeOutlined />
<ReloadOutlined />
<RestOutlined />
<RobotOutlined />
<RocketOutlined />
<SafetyCertificateOutlined />
<SafetyOutlined />
<ScanOutlined />
<ScheduleOutlined />
<SearchOutlined />
<SecurityScanOutlined />
<SelectOutlined />
<SendOutlined />
<SettingOutlined />
<ShakeOutlined />
<ShareAltOutlined />
<ShopOutlined />
<ShoppingCartOutlined />
<ShoppingOutlined />
<SisternodeOutlined />
<SkinOutlined />
<SmileOutlined />
<SolutionOutlined />
<SoundOutlined />
<SplitCellsOutlined />
<StarOutlined />
<SubnodeOutlined />
<SyncOutlined />
<TableOutlined />
<TabletOutlined />
<TagOutlined />
<TagsOutlined />
<TeamOutlined />
<ThunderboltOutlined />
<ToTopOutlined />
<ToolOutlined />
<TrademarkCircleOutlined />
<TrademarkOutlined />
<TransactionOutlined />
<TrophyOutlined />
<UngroupOutlined />
<UnlockOutlined />
<UploadOutlined />
<UsbOutlined />
<UserAddOutlined />
<UserDeleteOutlined />
<UserOutlined />
<UserSwitchOutlined />
<UsergroupAddOutlined />
<UsergroupDeleteOutlined />
<VideoCameraOutlined />
<WalletOutlined />
<WifiOutlined />
<BorderlessTableOutlined />
<WomanOutlined />
<BehanceOutlined />
<DropboxOutlined />
<DeploymentUnitOutlined />
<UpCircleOutlined />
<DownCircleOutlined />
<LeftCircleOutlined />
<RightCircleOutlined />
<UpSquareOutlined />
<DownSquareOutlined />
<LeftSquareOutlined />
<RightSquareOutlined />
<PlayCircleOutlined />
<QuestionCircleOutlined />
<PlusCircleOutlined />
<PlusSquareOutlined />
<MinusSquareOutlined />
<MinusCircleOutlined />
<InfoCircleOutlined />
<ExclamationCircleOutlined />
<CloseCircleOutlined />
<CloseSquareOutlined />
<CheckCircleOutlined />
<CheckSquareOutlined />
<ClockCircleOutlined />
<FormOutlined />
<DashOutlined />
<SmallDashOutlined />
<YoutubeOutlined />
<CodepenCircleOutlined />
<AliyunOutlined />
<PlusOutlined />
<LinkedinOutlined />
<AimOutlined />
<BugOutlined />
<CloudDownloadOutlined />
<CloudServerOutlined />
<CloudSyncOutlined />
<CloudUploadOutlined />
<CommentOutlined />
<ConsoleSqlOutlined />
<EyeInvisibleOutlined />
<FileGifOutlined />
<DeliveredProcedureOutlined />
<FieldTimeOutlined />
<FileZipOutlined />
<FolderViewOutlined />
<FundProjectionScreenOutlined />
<FundViewOutlined />
<MacCommandOutlined />
<PlaySquareOutlined />
<OneToOneOutlined />
<RotateLeftOutlined />
<RotateRightOutlined />
<SaveOutlined />
<SwitcherOutlined />
<TranslationOutlined />
<VerifiedOutlined />
<VideoCameraAddOutlined />
<WhatsAppOutlined />
{/*</Col>*/}
</Row>
);
}
Example #8
Source File: ChatBox.tsx From foodie with MIT License | 4 votes |
ChatBox: React.FC<IProps> = ({ user, target }) => {
const [text, setText] = useState('');
const dispatch = useDispatch();
const [error, setError] = useState<IError | null>(null);
const [isLoading, setLoading] = useState(false);
const [isSending, setSending] = useState(false);
const didMount = useDidMount(true);
const [isTyping, setIsTyping] = useState(false);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
let dummyEl = useRef<HTMLSpanElement | null>(null);
useEffect(() => {
if (target.chats.length === 0) fetchMessages();
socket.on('newMessage', (message: IMessage) => {
dispatch(newMessageArrived(target.username, message));
if (dummyEl.current) {
dummyEl.current.scrollIntoView();
}
});
socket.on('typing', (state: boolean) => {
setIsTyping(state);
});
if (dummyEl.current) {
dummyEl.current.scrollIntoView();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onMinimize = () => {
dispatch(minimizeChat(target.username));
}
const onCloseChat = () => {
dispatch(closeChat(target.username))
}
const fetchMessages = async () => {
try {
setLoading(true);
const messages = await getUserMessages(target.id, { offset: target.offset });
dispatch(getMessagesSuccess(target.username, messages.reverse()));
if (didMount) {
setText('');
setLoading(false);
setError(null);
}
if (!target.offset || target.offset < 1) {
dummyEl.current?.scrollIntoView();
}
} catch (e) {
if (didMount) {
setLoading(false);
setError(e);
}
}
}
const dispatchSendMessage = async () => {
if (text) {
try {
setSending(true);
await sendMessage(text, target.id);
if (didMount) {
setSending(false);
setText('');
setError(null);
}
} catch (e) {
if (didMount) {
setSending(false);
setError(e);
}
}
}
}
const handleTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
}
const handleTextKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
if (text) {
dispatchSendMessage();
socket.emit('user-typing', { user: target, state: false });
}
} else {
socket.emit('user-typing', { user: target, state: true });
if (timeoutRef.current) clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
socket.emit('user-typing', { user: target, state: false })
}, 2000);
}
}
return (
<div className={`bg-white dark:bg-indigo-1100 p-3 relative w-full h-full laptop:w-20rem laptop:shadow-lg laptop:rounded-t-xl laptop:bottom-0 laptop:right-24 laptop:border laptop:border-gray-400 dark:border-gray-800`}>
<div className="flex justify-between pb-3 border-b border-gray-200 dark:border-gray-800">
<Link className="dark:text-indigo-400" to={`/user/${target.username}`}>
<div className="flex items-center">
<Avatar url={target.profilePicture?.url} className="mr-2" />
<h5>{target.username}</h5>
</div>
</Link>
<div className="hidden laptop:flex laptop:items-center">
<div
className="post-option-toggle p-2 rounded-full flex items-center justify-center cursor-pointer hover:bg-gray-200 dark:text-white dark:hover:bg-indigo-1000"
title="Minimize Chat"
onClick={onMinimize}
>
<LineOutlined className="flex items-center justify-center" />
</div>
<div
className="post-option-toggle p-2 rounded-full flex items-center justify-center cursor-pointer hover:bg-gray-200 dark:text-white dark:hover:bg-indigo-1000"
title="Close Chat"
onClick={onCloseChat}
>
<CloseOutlined className="flex items-center justify-center" />
</div>
</div>
</div>
{/* --- MESSAGES BODY ---- */}
<div className="min-h-80% laptop:min-h-24rem max-h-85% laptop:max-h-80 bg-gray-100 dark:bg-indigo-1000 overflow-y-scroll pb-4 scrollbar relative">
{(isLoading && target.chats.length === 0 && !error) && (
<div className="flex justify-center min-h-18rem py-2">
<Loader />
</div>
)}
{(!isLoading && target.chats.length === 0 && error) && (
<div className="flex flex-col items-center min-h-18rem justify-center py-2">
{error.status_code === 404 ? (
<>
<CommentOutlined className="text-gray-300 text-6xl" />
<span className="text-gray-400 mb-2">No messages.</span>
<span className="text-gray-400 text-sm">Send a message to {target.username}</span>
</>
) : (
<span className="text-gray-400 mb-4">
{error?.error?.message || 'Unable to process your request.'}
</span>
)}
</div>
)}
{(!error && target.chats.length >= 10) && (
<>
{!isLoading ? (
<div
className={`flex justify-center items-center py-2 mb-4 bg-indigo-100 cursor-pointer`}
onClick={fetchMessages}
>
<span className="text-indigo-700 text-xs">Older messages</span>
</div>
) : (
<div className="flex justify-center py-2 mb-4">
<Loader />
</div>
)}
</>
)}
{(target.chats.length !== 0) && (
<>
<TransitionGroup component={null}>
{target.chats.map((msg, i) => (
<CSSTransition
timeout={500}
classNames="fade"
key={msg.id}
>
<div className="flex flex-col">
<div
className={`flex p-2 ${msg.isOwnMessage ? 'justify-end' : 'justify-start'}`}
key={`${msg.id}_${msg.from.id}`}
>
<div className="flex">
{/* -- AVATAR --- */}
<Avatar
url={msg.isOwnMessage ? user.profilePicture?.url : target.profilePicture?.url}
size="xs"
className={`self-end flex-shrink-0 !bg-cover !bg-no-repeat rounded-full ${msg.isOwnMessage ? 'ml-1 order-2' : 'mr-1 order-1'}`}
/>
{/* -- MESSAGE-- */}
<span
className={`py-2 px-3 break-all text-sm rounded-xl ${msg.isOwnMessage ? 'bg-indigo-700 text-white order-1' : 'bg-gray-300 order-2'}`}>
{msg.text}
</span>
<span ref={dummyEl}></span>
</div>
</div>
<div className={`flex pb-2 ${msg.isOwnMessage ? 'justify-end mr-8' : 'justify-start ml-8'}`}>
{/* ---DATE ---- */}
<span className={`text-gray-400 text-1xs ${msg.isOwnMessage ? 'order-2' : 'order-1'}`}>
{displayTime(msg.createdAt, true)}
</span>
{/* ---- SEEN ---- */}
{(msg.isOwnMessage && msg.seen && i === target.chats.length - 1) && (
<span className={`text-gray-400 mx-2 text-1xs italic flex-grow ${msg.isOwnMessage ? 'order-1' : 'order-2'}`}>
Seen
</span>
)}
</div>
</div>
</CSSTransition>
))}
</TransitionGroup>
{isTyping && (
<div className="flex justify-center py-2">
<span className="text-xs text-gray-400">{target.username} is typing...</span>
</div>
)}
</>
)}
</div>
<div className="relative bottom-0 left-0 bg-white dark:bg-indigo-1100 w-full flex p-2 border-t border-gray-200 dark:border-gray-800">
<input
className="flex-grow !border-gray-400 !rounded-r-none !py-0 dark:bg-indigo-1000 dark:text-white dark:!border-gray-800"
type="text"
onChange={handleTextChange}
onKeyDown={handleTextKeyDown}
readOnly={isSending || isLoading}
placeholder="Send a message..."
value={text}
/>
<button
className="!rounded-l-none flex items-center justify-center"
disabled={isSending || isLoading}
onClick={dispatchSendMessage}
>
<SendOutlined className="flex items-center justify-center text-xl" />
</button>
</div>
</div>
);
}
Example #9
Source File: index.tsx From foodie with MIT License | 4 votes |
PostItem: React.FC<IProps> = (props) => {
const { post, likeCallback, isAuth } = props;
const [isCommentVisible, setCommentVisible] = useState(false);
const deleteModal = useModal();
const updateModal = useModal();
const commentInputRef = useRef<HTMLInputElement | null>(null);
const dispatch = useDispatch();
const handleToggleComment = () => {
if (!isAuth) return;
if (!isCommentVisible) setCommentVisible(true);
if (commentInputRef.current) commentInputRef.current.focus();
}
const displayLikeMetric = (likesCount: number, isLiked: boolean) => {
const like = likesCount > 1 ? 'like' : 'likes';
const likeMinusSelf = (likesCount - 1) > 1 ? 'like' : 'likes';
const people = likesCount > 1 ? 'people' : 'person';
const peopleMinusSelf = (likesCount - 1) > 1 ? 'people' : 'person';
if (isLiked && likesCount <= 1) {
return 'You like this.'
} else if (isLiked && likesCount > 1) {
return `You and ${likesCount - 1} other ${peopleMinusSelf} ${likeMinusSelf} this.`;
} else {
return `${likesCount} ${people} ${like} this.`;
}
}
const handleClickLikes = () => {
if (isAuth) {
dispatch(showModal(EModalType.POST_LIKES));
dispatch(setTargetPost(props.post));
}
}
const handleClickPrivacyChange = () => {
if (post.isOwnPost) {
dispatch(setTargetPost(post));
dispatch(showModal(EModalType.EDIT_POST));
}
}
return (
<div className="flex flex-col bg-white rounded-lg my-4 p-4 first:mt-0 shadow-lg dark:bg-indigo-1000">
{/* --- AVATAR AND OPTIONS */}
<div className="flex justify-between items-center w-full">
<div className="flex">
<Avatar
url={post.author.profilePicture?.url}
className="mr-3"
/>
<div className="flex flex-col">
<Link className="dark:text-indigo-400" to={`/user/${post.author.username}`}>
<h5 className="font-bold">{post.author.username}</h5>
</Link>
<div className="flex items-center space-x-1">
<span className="text-sm text-gray-500">{dayjs(post.createdAt).fromNow()}</span>
<div
className={`w-4 h-4 rounded-full flex items-center justify-center ${post.isOwnPost && 'cursor-pointer hover:bg-gray-100 dark:hover:bg-indigo-900'}`}
onClick={handleClickPrivacyChange}
title={post.isOwnPost ? 'Change Privacy' : ''}
>
{post.privacy === 'private'
? <LockOutlined className="text-xs text-gray-500 dark:text-white" />
: post.privacy === 'follower'
? <UserOutlined className="text-xs text-gray-500 dark:text-white" />
: <GlobalOutlined className="text-xs text-gray-500 dark:text-white" />
}
</div>
</div>
</div>
</div>
{isAuth && (
<PostOptions
openDeleteModal={deleteModal.openModal}
openUpdateModal={updateModal.openModal}
post={post}
/>
)}
</div>
{/* --- DESCRIPTION */}
<div className="mb-3 mt-2">
<p className="text-gray-700 dark:text-gray-300 break-words">{post.description}</p>
</div>
{/* --- IMAGE GRID ----- */}
{post.photos.length !== 0 && <ImageGrid images={post.photos.map(img => img.url)} />}
{/* ---- LIKES/COMMENTS DETAILS ---- */}
<div className="flex justify-between px-2 my-2">
<div onClick={handleClickLikes}>
{post.likesCount > 0 && (
<span className="text-gray-500 text-sm cursor-pointer hover:underline hover:text-gray-800 dark:hover:text-white">
{displayLikeMetric(post.likesCount, post.isLiked)}
</span>
)}
</div>
{/* --- COMMENTS COUNT ----- */}
<div>
{post.commentsCount > 0 && (
<span
className="text-gray-500 hover:text-gray-800 cursor-pointer text-sm hover:underline dark:text-gray-500 dark:hover:text-white"
onClick={handleToggleComment}
>
{post.commentsCount} {post.commentsCount === 1 ? 'comment' : 'comments'}
</span>
)}
</div>
</div>
{/* --- LIKE/COMMENT BUTTON */}
{isAuth ? (
<div className="flex items-center justify-around py-2 border-t border-gray-200 dark:border-gray-800">
<LikeButton postID={post.id} isLiked={post.isLiked} likeCallback={likeCallback} />
<span
className="py-2 rounded-md flex items-center justify-center text-gray-700 hover:text-gray-800 700 dark:text-gray-400 dark:hover:text-white dark:hover:bg-indigo-1100 cursor-pointer hover:bg-gray-100 text-l w-2/4"
onClick={handleToggleComment}
>
<CommentOutlined /> Comment
</span>
</div>
) : (
<div className="text-center py-2">
<span className="text-gray-400 text-sm">
<Link className="font-medium underline dark:text-indigo-400" to={LOGIN}>Login</Link> to like or comment on post.
</span>
</div>
)}
{isAuth && (
<Suspense fallback={<LoadingOutlined className="text-gray-800 dark:text-white" />}>
<Comments
postID={post.id}
authorID={post.author.id}
isCommentVisible={isCommentVisible}
commentInputRef={commentInputRef}
setInputCommentVisible={setCommentVisible}
/>
</Suspense>
)}
</div>
);
}