2
0

🌐 Improve i18n collaboration type and timeSince parsing

This commit is contained in:
Baptiste Arnaud
2023-12-29 11:31:31 +01:00
parent 5124373071
commit f26eafd26f
8 changed files with 116 additions and 66 deletions

View File

@ -0,0 +1,46 @@
import { T } from '@tolgee/react'
type Props = {
date: string
}
export const TimeSince = ({ date }: Props) => {
const seconds = Math.floor(
(new Date().getTime() - new Date(date).getTime()) / 1000
)
let interval = seconds / 31536000
if (interval > 1) {
return (
<T keyName="timeSince.years" params={{ count: Math.floor(interval) }} />
)
}
interval = seconds / 2592000
if (interval > 1) {
return (
<T keyName="timeSince.months" params={{ count: Math.floor(interval) }} />
)
}
interval = seconds / 86400
if (interval > 1) {
return (
<T keyName="timeSince.days" params={{ count: Math.floor(interval) }} />
)
}
interval = seconds / 3600
if (interval > 1) {
return (
<T keyName="timeSince.hours" params={{ count: Math.floor(interval) }} />
)
}
interval = seconds / 60
if (interval > 1) {
return (
<T keyName="timeSince.minutes" params={{ count: Math.floor(interval) }} />
)
}
return (
<T keyName="timeSince.seconds" params={{ count: Math.floor(interval) }} />
)
}

View File

@ -23,9 +23,9 @@ import { byId, isDefined } from '@typebot.io/lib'
import { CreateTokenModal } from './CreateTokenModal' import { CreateTokenModal } from './CreateTokenModal'
import { useApiTokens } from '../hooks/useApiTokens' import { useApiTokens } from '../hooks/useApiTokens'
import { ApiTokenFromServer } from '../types' import { ApiTokenFromServer } from '../types'
import { parseTimeSince } from '@/helpers/parseTimeSince'
import { deleteApiTokenQuery } from '../queries/deleteApiTokenQuery' import { deleteApiTokenQuery } from '../queries/deleteApiTokenQuery'
import { T, useTranslate } from '@tolgee/react' import { T, useTranslate } from '@tolgee/react'
import { TimeSince } from '@/components/TimeSince'
type Props = { user: User } type Props = { user: User }
@ -84,7 +84,9 @@ export const ApiTokensList = ({ user }: Props) => {
{apiTokens?.map((token) => ( {apiTokens?.map((token) => (
<Tr key={token.id}> <Tr key={token.id}>
<Td>{token.name}</Td> <Td>{token.name}</Td>
<Td>{parseTimeSince(t, token.createdAt)}</Td> <Td>
<TimeSince date={token.createdAt} />
</Td>
<Td> <Td>
<Button <Button
size="xs" size="xs"

View File

@ -28,7 +28,8 @@ import { deleteInvitationQuery } from '../queries/deleteInvitationQuery'
import { updateCollaboratorQuery } from '../queries/updateCollaboratorQuery' import { updateCollaboratorQuery } from '../queries/updateCollaboratorQuery'
import { deleteCollaboratorQuery } from '../queries/deleteCollaboratorQuery' import { deleteCollaboratorQuery } from '../queries/deleteCollaboratorQuery'
import { sendInvitationQuery } from '../queries/sendInvitationQuery' import { sendInvitationQuery } from '../queries/sendInvitationQuery'
import { TFnType, useTranslate } from '@tolgee/react' import { useTranslate } from '@tolgee/react'
import { ReadableCollaborationType } from './ReadableCollaborationType'
export const CollaborationList = () => { export const CollaborationList = () => {
const { currentRole, workspace } = useWorkspace() const { currentRole, workspace } = useWorkspace()
@ -180,10 +181,7 @@ export const CollaborationList = () => {
</Text> </Text>
</HStack> </HStack>
<Tag flexShrink={0}> <Tag flexShrink={0}>
{convertCollaborationTypeEnumToReadable( <ReadableCollaborationType type={CollaborationType.FULL_ACCESS} />
t,
CollaborationType.FULL_ACCESS
)}
</Tag> </Tag>
</Flex> </Flex>
)} )}
@ -232,43 +230,25 @@ const CollaborationTypeMenuButton = ({
}: { }: {
type: CollaborationType type: CollaborationType
onChange: (type: CollaborationType) => void onChange: (type: CollaborationType) => void
}) => { }) => (
const { t } = useTranslate() <Menu placement="bottom-end">
<MenuButton
return ( flexShrink={0}
<Menu placement="bottom-end"> size="sm"
<MenuButton as={Button}
flexShrink={0} rightIcon={<ChevronLeftIcon transform={'rotate(-90deg)'} />}
size="sm" >
as={Button} <ReadableCollaborationType type={type} />
rightIcon={<ChevronLeftIcon transform={'rotate(-90deg)'} />} </MenuButton>
> <MenuList minW={0}>
{convertCollaborationTypeEnumToReadable(t, type)} <Stack maxH={'35vh'} overflowY="scroll" spacing="0">
</MenuButton> <MenuItem onClick={() => onChange(CollaborationType.READ)}>
<MenuList minW={0}> <ReadableCollaborationType type={CollaborationType.READ} />
<Stack maxH={'35vh'} overflowY="scroll" spacing="0"> </MenuItem>
<MenuItem onClick={() => onChange(CollaborationType.READ)}> <MenuItem onClick={() => onChange(CollaborationType.WRITE)}>
{convertCollaborationTypeEnumToReadable(t, CollaborationType.READ)} <ReadableCollaborationType type={CollaborationType.WRITE} />
</MenuItem> </MenuItem>
<MenuItem onClick={() => onChange(CollaborationType.WRITE)}> </Stack>
{convertCollaborationTypeEnumToReadable(t, CollaborationType.WRITE)} </MenuList>
</MenuItem> </Menu>
</Stack> )
</MenuList>
</Menu>
)
}
export const convertCollaborationTypeEnumToReadable = (
t: TFnType,
type: CollaborationType
) => {
switch (type) {
case CollaborationType.READ:
return t('collaboration.roles.view.label')
case CollaborationType.WRITE:
return t('collaboration.roles.edit.label')
case CollaborationType.FULL_ACCESS:
return t('collaboration.roles.full.label')
}
}

View File

@ -12,8 +12,8 @@ import {
} from '@chakra-ui/react' } from '@chakra-ui/react'
import { CollaborationType } from '@typebot.io/prisma' import { CollaborationType } from '@typebot.io/prisma'
import React from 'react' import React from 'react'
import { convertCollaborationTypeEnumToReadable } from './CollaborationList'
import { useTranslate } from '@tolgee/react' import { useTranslate } from '@tolgee/react'
import { ReadableCollaborationType } from './ReadableCollaborationType'
type Props = { type Props = {
image?: string image?: string
@ -50,16 +50,16 @@ export const CollaboratorItem = ({
name={name} name={name}
image={image} image={image}
isGuest={isGuest} isGuest={isGuest}
tag={convertCollaborationTypeEnumToReadable(t, type)} type={type}
/> />
</MenuButton> </MenuButton>
{isOwner && ( {isOwner && (
<MenuList shadow="lg"> <MenuList shadow="lg">
<MenuItem onClick={handleEditClick}> <MenuItem onClick={handleEditClick}>
{convertCollaborationTypeEnumToReadable(t, CollaborationType.WRITE)} <ReadableCollaborationType type={CollaborationType.WRITE} />
</MenuItem> </MenuItem>
<MenuItem onClick={handleViewClick}> <MenuItem onClick={handleViewClick}>
{convertCollaborationTypeEnumToReadable(t, CollaborationType.READ)} <ReadableCollaborationType type={CollaborationType.READ} />
</MenuItem> </MenuItem>
<MenuItem color="red.500" onClick={onDeleteClick}> <MenuItem color="red.500" onClick={onDeleteClick}>
{t('remove')} {t('remove')}
@ -72,13 +72,13 @@ export const CollaboratorItem = ({
export const CollaboratorIdentityContent = ({ export const CollaboratorIdentityContent = ({
name, name,
tag, type,
isGuest = false, isGuest = false,
image, image,
email, email,
}: { }: {
name?: string name?: string
tag?: string type: CollaborationType
image?: string image?: string
isGuest?: boolean isGuest?: boolean
email: string email: string
@ -106,7 +106,9 @@ export const CollaboratorIdentityContent = ({
</HStack> </HStack>
<HStack flexShrink={0}> <HStack flexShrink={0}>
{isGuest && <Tag color="gray.400">{t('pending')}</Tag>} {isGuest && <Tag color="gray.400">{t('pending')}</Tag>}
<Tag>{tag}</Tag> <Tag>
<ReadableCollaborationType type={type} />
</Tag>
</HStack> </HStack>
</HStack> </HStack>
) )

View File

@ -0,0 +1,14 @@
import { T } from '@tolgee/react'
import { CollaborationType } from '@typebot.io/prisma'
type Props = { type: CollaborationType }
export const ReadableCollaborationType = ({ type }: Props) => {
switch (type) {
case CollaborationType.READ:
return <T keyName="collaboration.roles.view.label" />
case CollaborationType.WRITE:
return <T keyName="collaboration.roles.edit.label" />
case CollaborationType.FULL_ACCESS:
return <T keyName="collaboration.roles.full.label" />
}
}

View File

@ -24,7 +24,6 @@ import { useRouter } from 'next/router'
import { isNotDefined } from '@typebot.io/lib' import { isNotDefined } from '@typebot.io/lib'
import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal' import { ChangePlanModal } from '@/features/billing/components/ChangePlanModal'
import { isFreePlan } from '@/features/billing/helpers/isFreePlan' import { isFreePlan } from '@/features/billing/helpers/isFreePlan'
import { parseTimeSince } from '@/helpers/parseTimeSince'
import { T, useTranslate } from '@tolgee/react' import { T, useTranslate } from '@tolgee/react'
import { trpc } from '@/lib/trpc' import { trpc } from '@/lib/trpc'
import { useToast } from '@/hooks/useToast' import { useToast } from '@/hooks/useToast'
@ -33,6 +32,7 @@ import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/const
import { ConfirmModal } from '@/components/ConfirmModal' import { ConfirmModal } from '@/components/ConfirmModal'
import { TextLink } from '@/components/TextLink' import { TextLink } from '@/components/TextLink'
import { useUser } from '@/features/account/hooks/useUser' import { useUser } from '@/features/account/hooks/useUser'
import { useTimeSince } from '@/hooks/useTimeSince'
type Props = ButtonProps & { type Props = ButtonProps & {
isMoreMenuDisabled?: boolean isMoreMenuDisabled?: boolean
@ -61,6 +61,9 @@ export const PublishButton = ({
save, save,
publishedTypebotVersion, publishedTypebotVersion,
} = useTypebot() } = useTypebot()
const timeSinceLastPublish = useTimeSince(
publishedTypebot?.updatedAt.toString()
)
const { showToast } = useToast() const { showToast } = useToast()
const { const {
@ -181,14 +184,14 @@ export const PublishButton = ({
label={ label={
<Stack> <Stack>
<Text>{t('publishButton.tooltip.nonPublishedChanges.label')}</Text> <Text>{t('publishButton.tooltip.nonPublishedChanges.label')}</Text>
{publishedTypebot ? ( {timeSinceLastPublish ? (
<Text fontStyle="italic"> <Text fontStyle="italic">
{t('publishButton.tooltip.publishedVersion.from.label', { <T
timeSince: parseTimeSince( keyName="publishButton.tooltip.publishedVersion.from.label"
t, params={{
publishedTypebot.updatedAt.toString() timeSince: timeSinceLastPublish,
), }}
})} />
</Text> </Text>
) : null} ) : null}
</Stack> </Stack>

View File

@ -1,6 +1,10 @@
import { TFnType } from '@tolgee/react' import { useTranslate } from '@tolgee/react'
export const useTimeSince = (date?: string) => {
const { t } = useTranslate()
if (!date) return
export const parseTimeSince = (t: TFnType, date: string) => {
const seconds = Math.floor( const seconds = Math.floor(
(new Date().getTime() - new Date(date).getTime()) / 1000 (new Date().getTime() - new Date(date).getTime()) / 1000
) )

View File

@ -255,7 +255,7 @@
"publishButton.published.label": "Published", "publishButton.published.label": "Published",
"publishButton.tooltip.nonPublishedChanges.label": "There are non published changes.", "publishButton.tooltip.nonPublishedChanges.label": "There are non published changes.",
"publishButton.tooltip.publishedVersion.ago.label": "ago", "publishButton.tooltip.publishedVersion.ago.label": "ago",
"publishButton.tooltip.publishedVersion.from.label": "Published version from {timeSince}.", "publishButton.tooltip.publishedVersion.from.label": "Published version from <timeSince>test</timeSince>.",
"remove": "Remove", "remove": "Remove",
"share.button.label": "Share", "share.button.label": "Share",
"share.button.popover.ariaLabel": "Open share popover", "share.button.popover.ariaLabel": "Open share popover",
@ -310,7 +310,6 @@
"templates.modal.product.userOnboarding.description": "A bot that asks for new user information before he start using your product.", "templates.modal.product.userOnboarding.description": "A bot that asks for new user information before he start using your product.",
"templates.modal.product.userOnboarding.name": "User Onboarding", "templates.modal.product.userOnboarding.name": "User Onboarding",
"templates.modal.useTemplateButton.label": "Use this template", "templates.modal.useTemplateButton.label": "Use this template",
"templates.modal.useTemplateButton.labelt": "Use this templatet",
"timeSince.days": "{count}d ago", "timeSince.days": "{count}d ago",
"timeSince.hours": "{count}h ago", "timeSince.hours": "{count}h ago",
"timeSince.minutes": "{count}m ago", "timeSince.minutes": "{count}m ago",