diff --git a/apps/builder/src/components/Toaster.tsx b/apps/builder/src/components/Toaster.tsx new file mode 100644 index 000000000..4a8d04cc7 --- /dev/null +++ b/apps/builder/src/components/Toaster.tsx @@ -0,0 +1,33 @@ +import { colors } from '@/lib/theme' +import { useColorMode, useColorModeValue } from '@chakra-ui/react' +import { Toaster as SonnerToaster } from 'sonner' + +export const Toaster = () => { + const { colorMode } = useColorMode() + const theme = useColorModeValue( + { + bg: undefined, + actionBg: colors.blue[500], + actionColor: undefined, + }, + { + bg: colors.gray[900], + actionBg: colors.blue[400], + actionColor: 'white', + } + ) + return ( + + ) +} diff --git a/apps/builder/src/components/icons.tsx b/apps/builder/src/components/icons.tsx index 87b4ce210..aea324d2b 100644 --- a/apps/builder/src/components/icons.tsx +++ b/apps/builder/src/components/icons.tsx @@ -643,3 +643,11 @@ export const XCircleIcon = (props: IconProps) => ( ) + +export const LightBulbIcon = (props: IconProps) => ( + + + + + +) diff --git a/apps/builder/src/features/account/UserProvider.tsx b/apps/builder/src/features/account/UserProvider.tsx index df4edefe7..00e1e2b94 100644 --- a/apps/builder/src/features/account/UserProvider.tsx +++ b/apps/builder/src/features/account/UserProvider.tsx @@ -2,7 +2,7 @@ import { signOut, useSession } from 'next-auth/react' import { useRouter } from 'next/router' import { createContext, ReactNode, useEffect, useState } from 'react' import { isDefined, isNotDefined } from '@typebot.io/lib' -import { User } from '@typebot.io/prisma' +import { User } from '@typebot.io/schemas' import { setUser as setSentryUser } from '@sentry/nextjs' import { useToast } from '@/hooks/useToast' import { updateUserQuery } from './queries/updateUserQuery' diff --git a/apps/builder/src/features/editor/components/BoardMenuButton.tsx b/apps/builder/src/features/editor/components/BoardMenuButton.tsx index 750283845..62b0c30a4 100644 --- a/apps/builder/src/features/editor/components/BoardMenuButton.tsx +++ b/apps/builder/src/features/editor/components/BoardMenuButton.tsx @@ -17,25 +17,17 @@ import { SettingsIcon, } from '@/components/icons' import { useTypebot } from '../providers/TypebotProvider' -import { useUser } from '@/features/account/hooks/useUser' -import { useRouter } from 'next/router' -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { EditorSettingsModal } from './EditorSettingsModal' import { parseDefaultPublicId } from '@/features/publish/helpers/parseDefaultPublicId' import { useTranslate } from '@tolgee/react' export const BoardMenuButton = (props: FlexProps) => { - const { query } = useRouter() const { typebot } = useTypebot() - const { user } = useUser() const [isDownloading, setIsDownloading] = useState(false) const { isOpen, onOpen, onClose } = useDisclosure() const { t } = useTranslate() - useEffect(() => { - if (user && !user.graphNavigation && !query.isFirstBot) onOpen() - }, [onOpen, query.isFirstBot, user, user?.graphNavigation]) - const downloadFlow = () => { assert(typebot) setIsDownloading(true) @@ -54,7 +46,7 @@ export const BoardMenuButton = (props: FlexProps) => { } const redirectToDocumentation = () => - window.open('https://docs.typebot.io/get-started/overview', '_blank') + window.open('https://docs.typebot.io/editor/graph', '_blank') return ( { + window.open('https://docs.typebot.io/editor/graph', '_blank') + }, + }, + duration: 30000, + icon: , + }) + updateUser({ + displayedInAppNotifications: { + ...user.displayedInAppNotifications, + [graphGestureNotficationKey]: true, + }, + }) } setSelectBoxCoordinates(undefined) setOpenedBlockId(undefined) diff --git a/apps/builder/src/pages/_app.tsx b/apps/builder/src/pages/_app.tsx index fff4fea00..9ad8542b7 100644 --- a/apps/builder/src/pages/_app.tsx +++ b/apps/builder/src/pages/_app.tsx @@ -22,7 +22,7 @@ import { isCloudProdInstance } from '@/helpers/isCloudProdInstance' import { initPostHogIfEnabled } from '@/features/telemetry/posthog' import { TolgeeProvider, useTolgeeSSR } from '@tolgee/react' import { tolgee } from '@/lib/tolgee' -import { Toaster } from 'sonner' +import { Toaster } from '@/components/Toaster' initPostHogIfEnabled() @@ -63,8 +63,8 @@ const App = ({ Component, pageProps }: AppProps) => { return ( - + diff --git a/apps/builder/src/pages/api/users/[userId].ts b/apps/builder/src/pages/api/users/[userId].ts index 2de569a65..4276d4b3b 100644 --- a/apps/builder/src/pages/api/users/[userId].ts +++ b/apps/builder/src/pages/api/users/[userId].ts @@ -2,7 +2,7 @@ import prisma from '@typebot.io/lib/prisma' import { NextApiRequest, NextApiResponse } from 'next' import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser' import { methodNotAllowed, notAuthenticated } from '@typebot.io/lib/api' -import { User } from '@typebot.io/prisma' +import { Prisma, User } from '@typebot.io/prisma' const handler = async (req: NextApiRequest, res: NextApiResponse) => { const user = await getAuthenticatedUser(req, res) @@ -18,6 +18,8 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { data: { ...data, onboardingCategories: data.onboardingCategories ?? [], + displayedInAppNotifications: + data.displayedInAppNotifications ?? Prisma.DbNull, }, }) return res.send({ typebots }) diff --git a/apps/docs/mint.json b/apps/docs/mint.json index d1d4cb9d9..e035d52c0 100644 --- a/apps/docs/mint.json +++ b/apps/docs/mint.json @@ -64,15 +64,6 @@ "group": "Get Started", "pages": ["get-started/introduction", "get-started/overview"] }, - { - "group": "Workspace", - "pages": [ - "workspace", - "workspace/add-members-and-guests", - "workspace/subscription", - "workspace/preferences" - ] - }, { "group": "Flow", "pages": [ @@ -191,6 +182,15 @@ "group": "Results", "pages": ["results/overview", "results/analytics"] }, + { + "group": "Workspace", + "pages": [ + "workspace", + "workspace/add-members-and-guests", + "workspace/subscription", + "workspace/preferences" + ] + }, { "group": "Guides", "pages": [ diff --git a/packages/lib/mockedUser.ts b/packages/lib/mockedUser.ts index 944815c5e..b7cfea491 100644 --- a/packages/lib/mockedUser.ts +++ b/packages/lib/mockedUser.ts @@ -1,4 +1,5 @@ import { User } from '@typebot.io/prisma' +import { graphGestureNotficationKey } from '@typebot.io/schemas/features/user/constants' export const mockedUser: User = { id: 'userId', @@ -13,4 +14,7 @@ export const mockedUser: User = { lastActivityAt: new Date('2022-01-01'), onboardingCategories: [], updatedAt: new Date('2022-01-01'), + displayedInAppNotifications: { + [graphGestureNotficationKey]: true, + }, } diff --git a/packages/lib/playwright/databaseActions.ts b/packages/lib/playwright/databaseActions.ts index 2f9062d0e..4f433756e 100644 --- a/packages/lib/playwright/databaseActions.ts +++ b/packages/lib/playwright/databaseActions.ts @@ -144,6 +144,8 @@ export const updateUser = (data: Partial) => data: { ...data, onboardingCategories: data.onboardingCategories ?? [], + displayedInAppNotifications: + data.displayedInAppNotifications ?? Prisma.DbNull, }, where: { id: userId, diff --git a/packages/prisma/mysql/schema.prisma b/packages/prisma/mysql/schema.prisma index 4d8782560..67bbda365 100644 --- a/packages/prisma/mysql/schema.prisma +++ b/packages/prisma/mysql/schema.prisma @@ -41,24 +41,25 @@ model Session { } model User { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - lastActivityAt DateTime @default(now()) - name String? @db.VarChar(255) - email String? @unique - emailVerified DateTime? - image String? @db.VarChar(1000) - company String? - onboardingCategories Json - graphNavigation GraphNavigation? - preferredAppAppearance String? - accounts Account[] - apiTokens ApiToken[] - CollaboratorsOnTypebots CollaboratorsOnTypebots[] - workspaces MemberInWorkspace[] - sessions Session[] - bannedIps BannedIp[] + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + lastActivityAt DateTime @default(now()) + name String? @db.VarChar(255) + email String? @unique + emailVerified DateTime? + image String? @db.VarChar(1000) + company String? + onboardingCategories Json + graphNavigation GraphNavigation? + preferredAppAppearance String? + accounts Account[] + apiTokens ApiToken[] + CollaboratorsOnTypebots CollaboratorsOnTypebots[] + workspaces MemberInWorkspace[] + sessions Session[] + bannedIps BannedIp[] + displayedInAppNotifications Json? } model ApiToken { diff --git a/packages/prisma/postgresql/migrations/20240124090203_add_displayed_inapp_notifications/migration.sql b/packages/prisma/postgresql/migrations/20240124090203_add_displayed_inapp_notifications/migration.sql new file mode 100644 index 000000000..2a0f6c750 --- /dev/null +++ b/packages/prisma/postgresql/migrations/20240124090203_add_displayed_inapp_notifications/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "displayedInAppNotifications" JSONB; diff --git a/packages/prisma/postgresql/schema.prisma b/packages/prisma/postgresql/schema.prisma index 6ca462a62..7820c040c 100644 --- a/packages/prisma/postgresql/schema.prisma +++ b/packages/prisma/postgresql/schema.prisma @@ -37,24 +37,25 @@ model Session { } model User { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - lastActivityAt DateTime @default(now()) - name String? - email String? @unique - emailVerified DateTime? - image String? - company String? - onboardingCategories Json - graphNavigation GraphNavigation? - preferredAppAppearance String? - accounts Account[] - apiTokens ApiToken[] - CollaboratorsOnTypebots CollaboratorsOnTypebots[] - workspaces MemberInWorkspace[] - sessions Session[] - bannedIps BannedIp[] + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + lastActivityAt DateTime @default(now()) + name String? + email String? @unique + emailVerified DateTime? + image String? + company String? + onboardingCategories Json + graphNavigation GraphNavigation? + preferredAppAppearance String? + accounts Account[] + apiTokens ApiToken[] + CollaboratorsOnTypebots CollaboratorsOnTypebots[] + workspaces MemberInWorkspace[] + sessions Session[] + bannedIps BannedIp[] + displayedInAppNotifications Json? } model ApiToken { diff --git a/packages/schemas/features/user/constants.ts b/packages/schemas/features/user/constants.ts new file mode 100644 index 000000000..92691fd91 --- /dev/null +++ b/packages/schemas/features/user/constants.ts @@ -0,0 +1 @@ +export const graphGestureNotficationKey = 'graphGestureNotification' diff --git a/packages/schemas/features/user/schema.ts b/packages/schemas/features/user/schema.ts new file mode 100644 index 000000000..e78de94cc --- /dev/null +++ b/packages/schemas/features/user/schema.ts @@ -0,0 +1,22 @@ +import { GraphNavigation, User as PrismaUser } from '@typebot.io/prisma' +import { z } from '../../zod' + +const displayedInAppNotificationsSchema = z.record(z.boolean()) + +export const userSchema = z.object({ + id: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + lastActivityAt: z.date(), + name: z.string().nullable(), + email: z.string().nullable(), + emailVerified: z.date().nullable(), + image: z.string().nullable(), + company: z.string().nullable(), + onboardingCategories: z.array(z.string()), + graphNavigation: z.nativeEnum(GraphNavigation), + preferredAppAppearance: z.string().nullable(), + displayedInAppNotifications: displayedInAppNotificationsSchema.nullable(), +}) satisfies z.ZodType + +export type User = z.infer diff --git a/packages/schemas/index.ts b/packages/schemas/index.ts index f5a64710b..126564fa2 100644 --- a/packages/schemas/index.ts +++ b/packages/schemas/index.ts @@ -13,3 +13,4 @@ export * from './features/workspace' export * from './features/items' export * from './features/analytics' export * from './features/events' +export * from './features/user/schema' diff --git a/packages/scripts/inspectUser.ts b/packages/scripts/inspectUser.ts index 8f103e7eb..f23b8871e 100644 --- a/packages/scripts/inspectUser.ts +++ b/packages/scripts/inspectUser.ts @@ -18,6 +18,7 @@ const inspectUser = async () => { }, select: { name: true, + createdAt: true, lastActivityAt: true, company: true, onboardingCategories: true,