🌐 Improve i18n collaboration type and timeSince parsing
This commit is contained in:
46
apps/builder/src/components/TimeSince.tsx
Normal file
46
apps/builder/src/components/TimeSince.tsx
Normal 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) }} />
|
||||||
|
)
|
||||||
|
}
|
@ -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"
|
||||||
|
@ -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')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
|
@ -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" />
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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
|
||||||
)
|
)
|
@ -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",
|
||||||
|
Reference in New Issue
Block a user