@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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
                                                    &nbsp;
                                                <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 vote down vote up
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
                                    &nbsp;
                                    <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>
                                    &nbsp;
                                    {dayjs(post.createdAt).fromNow()}
                                </h6>
                            </div>
                            <div className="flex space-x-2">
                                <h6 className="text-sm text-gray-600 flex items-center">
                                    {post.likesCount}
                                    &nbsp;
                                    <LikeOutlined />
                                </h6>
                                <h6 className="text-sm text-gray-600 flex items-center">
                                    {post.commentsCount}
                                    &nbsp;
                                    <CommentOutlined />
                                </h6>
                            </div>
                        </div>
                    </div>
                </div>
            ))}
        </div>
    );
}
Example #5
Source File: commandPaletteLogic.ts    From posthog-foss with MIT License 4 votes vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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'}`}>
                                                    &nbsp;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 vote down vote up
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 />&nbsp;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>
    );
}