add new user controller
|
@ -9,6 +9,7 @@ import {
|
|||
import { organizationQueryKeys } from '@/api/query_keys/organization';
|
||||
import { userQueryKeys } from '@/api/query_keys/users';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import { timeout } from '@/lib/timeout';
|
||||
import type { RustApiError } from '../../errors';
|
||||
import { useCreateOrganization } from '../organizations/queryRequests';
|
||||
import {
|
||||
|
@ -100,12 +101,12 @@ export const useInviteUser = () => {
|
|||
};
|
||||
|
||||
export const useCreateUserOrganization = () => {
|
||||
const { data: userResponse, refetch: refetchUserResponse } = useGetMyUserInfo({});
|
||||
const { data: userResponse, refetch: refetchUserResponse } = useGetMyUserInfo();
|
||||
const { mutateAsync: createOrganization } = useCreateOrganization();
|
||||
const { mutateAsync: updateUserInfo } = useUpdateUser();
|
||||
|
||||
const onCreateUserOrganization = useMemoizedFn(
|
||||
async ({ name, company }: { name: string; company: string }) => {
|
||||
return useMutation({
|
||||
mutationFn: async ({ name, company }: { name: string; company: string }) => {
|
||||
const alreadyHasOrganization = !!userResponse?.organizations?.[0];
|
||||
if (!alreadyHasOrganization) await createOrganization({ name: company });
|
||||
if (userResponse) {
|
||||
|
@ -113,13 +114,10 @@ export const useCreateUserOrganization = () => {
|
|||
userId: userResponse.user.id,
|
||||
name,
|
||||
});
|
||||
await refetchUserResponse();
|
||||
}
|
||||
await refetchUserResponse();
|
||||
}
|
||||
);
|
||||
|
||||
return onCreateUserOrganization;
|
||||
await Promise.all([timeout(450), refetchUserResponse()]);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetSuggestedPrompts = (params: Parameters<typeof getSuggestedPrompts>[0]) => {
|
||||
|
|
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 881 B |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 545 B |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 985 B |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 114 KiB |
|
@ -41,8 +41,6 @@ import {
|
|||
toggleInviteModal,
|
||||
useInviteModalStore,
|
||||
} from '@/context/GlobalStore/useInviteModalStore';
|
||||
import { useGetSelectedAssetTypeLoose } from '@/context/Routes/useAppRoutes';
|
||||
import { useWhyDidYouUpdate } from '@/hooks/useWhyDidYouUpdate';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
import { InvitePeopleModal } from '../../modals/InvitePeopleModal';
|
||||
import { SupportModal } from '../../modals/SupportModal';
|
||||
|
|
|
@ -75,7 +75,9 @@ const PageLayout: React.FC<
|
|||
className
|
||||
)}
|
||||
>
|
||||
<div className={cn('bg-background h-full overflow-hidden', floating && 'rounded border')}>
|
||||
<div
|
||||
className={cn('bg-page-background h-full overflow-hidden', floating && 'rounded border')}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCreateUserOrganization } from '@/api/buster_rest/users';
|
||||
import {
|
||||
useGetUserBasicInfo,
|
||||
useGetUserOrganization,
|
||||
} from '@/api/buster_rest/users/useGetUserInfo';
|
||||
import { Button } from '@/components/ui/buttons';
|
||||
import { Input } from '@/components/ui/inputs';
|
||||
import { Paragraph, Title } from '@/components/ui/typography';
|
||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||
import { useConfetti } from '@/hooks/useConfetti';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import { inputHasText } from '@/lib/text';
|
||||
|
||||
export const NewUserController = () => {
|
||||
const navigate = useNavigate();
|
||||
const user = useGetUserBasicInfo();
|
||||
const userOrganizations = useGetUserOrganization();
|
||||
const { mutateAsync: onCreateUserOrganization, isPending: submitting } =
|
||||
useCreateUserOrganization();
|
||||
|
||||
const [started, setStarted] = useState(false);
|
||||
const [name, setName] = useState<string | undefined>(user?.name);
|
||||
const [company, setCompany] = useState<string | undefined>(userOrganizations?.name);
|
||||
const { fireConfetti } = useConfetti();
|
||||
|
||||
const { openInfoMessage } = useBusterNotifications();
|
||||
|
||||
const canSubmit = useMemo(() => inputHasText(name) && inputHasText(company), [name, company]);
|
||||
|
||||
const handleSubmit = useMemoizedFn(async () => {
|
||||
if (!canSubmit || !name || !company) {
|
||||
openInfoMessage('Please fill in all fields');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await onCreateUserOrganization({
|
||||
name,
|
||||
company,
|
||||
});
|
||||
fireConfetti();
|
||||
await navigate({
|
||||
to: '/app/home',
|
||||
replace: true,
|
||||
});
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<AnimatePresence mode="wait" initial={false}>
|
||||
{!started && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -10, filter: 'blur(4px)' }}
|
||||
animate={{ opacity: 1, y: 0, filter: 'blur(0px)' }}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
y: 10,
|
||||
filter: 'blur(4px)',
|
||||
transition: {
|
||||
duration: 0.2,
|
||||
opacity: { duration: 0.175 },
|
||||
filter: { duration: 0.18 },
|
||||
},
|
||||
}}
|
||||
key={'no-started'}
|
||||
className="flex h-full w-full flex-col items-start justify-center space-y-5 p-12"
|
||||
>
|
||||
<Title as={'h3'}>Welcome to Buster</Title>
|
||||
<Paragraph variant="secondary">
|
||||
With Buster, you can ask data questions in plain english & instantly get back data.
|
||||
</Paragraph>
|
||||
<Button variant="black" onClick={() => setStarted(true)}>
|
||||
Get Started
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{started && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
key={'started'}
|
||||
className="flex h-full w-full flex-col items-start justify-center space-y-5 p-12"
|
||||
>
|
||||
<Title as={'h4'}>Tell us about yourself</Title>
|
||||
<Input
|
||||
placeholder="What is your full name"
|
||||
className="w-full"
|
||||
value={name || ''}
|
||||
name="name"
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onPressEnter={handleSubmit}
|
||||
/>
|
||||
<Input
|
||||
placeholder="What is the name of your company"
|
||||
className="w-full"
|
||||
name="company"
|
||||
disabled={!!userOrganizations?.name}
|
||||
value={company || ''}
|
||||
onChange={(e) => setCompany(e.target.value)}
|
||||
onPressEnter={handleSubmit}
|
||||
/>
|
||||
<Button
|
||||
variant="black"
|
||||
loading={submitting}
|
||||
onClick={async () => {
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
Create your account
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from './NewUserController';
|
|
@ -29,10 +29,12 @@ import { Route as EmbedMetricMetricIdRouteImport } from './routes/embed/metric.$
|
|||
import { Route as EmbedDashboardDashboardIdRouteImport } from './routes/embed/dashboard.$dashboardId'
|
||||
import { Route as AppSettingsRestricted_layoutRouteImport } from './routes/app/_settings/_restricted_layout'
|
||||
import { Route as AppSettingsPermissionsRouteImport } from './routes/app/_settings/_permissions'
|
||||
import { Route as AppAppNewUserRouteImport } from './routes/app/_app/new-user'
|
||||
import { Route as AppAppHomeRouteImport } from './routes/app/_app/home'
|
||||
import { Route as AppAppAssetRouteImport } from './routes/app/_app/_asset'
|
||||
import { Route as AppSettingsSettingsIndexRouteImport } from './routes/app/_settings/settings.index'
|
||||
import { Route as AppAppReportsIndexRouteImport } from './routes/app/_app/reports.index'
|
||||
import { Route as AppAppNewUserIndexRouteImport } from './routes/app/_app/new-user/index'
|
||||
import { Route as AppAppMetricsIndexRouteImport } from './routes/app/_app/metrics.index'
|
||||
import { Route as AppAppLogsIndexRouteImport } from './routes/app/_app/logs.index'
|
||||
import { Route as AppAppDatasetsIndexRouteImport } from './routes/app/_app/datasets.index'
|
||||
|
@ -245,6 +247,11 @@ const AppSettingsPermissionsRoute = AppSettingsPermissionsRouteImport.update({
|
|||
id: '/_permissions',
|
||||
getParentRoute: () => AppSettingsRoute,
|
||||
} as any)
|
||||
const AppAppNewUserRoute = AppAppNewUserRouteImport.update({
|
||||
id: '/new-user',
|
||||
path: '/new-user',
|
||||
getParentRoute: () => AppAppRoute,
|
||||
} as any)
|
||||
const AppAppHomeRoute = AppAppHomeRouteImport.update({
|
||||
id: '/home',
|
||||
path: '/home',
|
||||
|
@ -265,6 +272,11 @@ const AppAppReportsIndexRoute = AppAppReportsIndexRouteImport.update({
|
|||
path: '/reports/',
|
||||
getParentRoute: () => AppAppRoute,
|
||||
} as any)
|
||||
const AppAppNewUserIndexRoute = AppAppNewUserIndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => AppAppNewUserRoute,
|
||||
} as any)
|
||||
const AppAppMetricsIndexRoute = AppAppMetricsIndexRouteImport.update({
|
||||
id: '/metrics/',
|
||||
path: '/metrics/',
|
||||
|
@ -945,6 +957,7 @@ export interface FileRoutesByFullPath {
|
|||
'/info/getting-started': typeof InfoGettingStartedRoute
|
||||
'/app/': typeof AppIndexRoute
|
||||
'/app/home': typeof AppAppHomeRoute
|
||||
'/app/new-user': typeof AppAppNewUserRouteWithChildren
|
||||
'/embed/dashboard/$dashboardId': typeof EmbedDashboardDashboardIdRoute
|
||||
'/embed/metric/$metricId': typeof EmbedMetricMetricIdRoute
|
||||
'/embed/report/$reportId': typeof EmbedReportReportIdRoute
|
||||
|
@ -955,6 +968,7 @@ export interface FileRoutesByFullPath {
|
|||
'/app/datasets': typeof AppAppDatasetsIndexRoute
|
||||
'/app/logs': typeof AppAppLogsIndexRoute
|
||||
'/app/metrics': typeof AppAppMetricsIndexRoute
|
||||
'/app/new-user/': typeof AppAppNewUserIndexRoute
|
||||
'/app/reports': typeof AppAppReportsIndexRoute
|
||||
'/app/settings': typeof AppSettingsSettingsIndexRoute
|
||||
'/app/chats/$chatId': typeof AppAppAssetChatsChatIdRouteWithChildren
|
||||
|
@ -1062,6 +1076,7 @@ export interface FileRoutesByTo {
|
|||
'/app/datasets': typeof AppAppDatasetsIndexRoute
|
||||
'/app/logs': typeof AppAppLogsIndexRoute
|
||||
'/app/metrics': typeof AppAppMetricsIndexRoute
|
||||
'/app/new-user': typeof AppAppNewUserIndexRoute
|
||||
'/app/reports': typeof AppAppReportsIndexRoute
|
||||
'/app/settings': typeof AppSettingsSettingsIndexRoute
|
||||
'/app/datasets/$datasetId/editor': typeof AppAppDatasetsDatasetIdEditorRoute
|
||||
|
@ -1147,6 +1162,7 @@ export interface FileRoutesById {
|
|||
'/app/': typeof AppIndexRoute
|
||||
'/app/_app/_asset': typeof AppAppAssetRouteWithChildren
|
||||
'/app/_app/home': typeof AppAppHomeRoute
|
||||
'/app/_app/new-user': typeof AppAppNewUserRouteWithChildren
|
||||
'/app/_settings/_permissions': typeof AppSettingsPermissionsRouteWithChildren
|
||||
'/app/_settings/_restricted_layout': typeof AppSettingsRestricted_layoutRouteWithChildren
|
||||
'/embed/dashboard/$dashboardId': typeof EmbedDashboardDashboardIdRoute
|
||||
|
@ -1160,6 +1176,7 @@ export interface FileRoutesById {
|
|||
'/app/_app/datasets/': typeof AppAppDatasetsIndexRoute
|
||||
'/app/_app/logs/': typeof AppAppLogsIndexRoute
|
||||
'/app/_app/metrics/': typeof AppAppMetricsIndexRoute
|
||||
'/app/_app/new-user/': typeof AppAppNewUserIndexRoute
|
||||
'/app/_app/reports/': typeof AppAppReportsIndexRoute
|
||||
'/app/_settings/settings/': typeof AppSettingsSettingsIndexRoute
|
||||
'/app/_app/_asset/chats/$chatId': typeof AppAppAssetChatsChatIdRouteWithChildren
|
||||
|
@ -1270,6 +1287,7 @@ export interface FileRouteTypes {
|
|||
| '/info/getting-started'
|
||||
| '/app/'
|
||||
| '/app/home'
|
||||
| '/app/new-user'
|
||||
| '/embed/dashboard/$dashboardId'
|
||||
| '/embed/metric/$metricId'
|
||||
| '/embed/report/$reportId'
|
||||
|
@ -1280,6 +1298,7 @@ export interface FileRouteTypes {
|
|||
| '/app/datasets'
|
||||
| '/app/logs'
|
||||
| '/app/metrics'
|
||||
| '/app/new-user/'
|
||||
| '/app/reports'
|
||||
| '/app/settings'
|
||||
| '/app/chats/$chatId'
|
||||
|
@ -1387,6 +1406,7 @@ export interface FileRouteTypes {
|
|||
| '/app/datasets'
|
||||
| '/app/logs'
|
||||
| '/app/metrics'
|
||||
| '/app/new-user'
|
||||
| '/app/reports'
|
||||
| '/app/settings'
|
||||
| '/app/datasets/$datasetId/editor'
|
||||
|
@ -1471,6 +1491,7 @@ export interface FileRouteTypes {
|
|||
| '/app/'
|
||||
| '/app/_app/_asset'
|
||||
| '/app/_app/home'
|
||||
| '/app/_app/new-user'
|
||||
| '/app/_settings/_permissions'
|
||||
| '/app/_settings/_restricted_layout'
|
||||
| '/embed/dashboard/$dashboardId'
|
||||
|
@ -1484,6 +1505,7 @@ export interface FileRouteTypes {
|
|||
| '/app/_app/datasets/'
|
||||
| '/app/_app/logs/'
|
||||
| '/app/_app/metrics/'
|
||||
| '/app/_app/new-user/'
|
||||
| '/app/_app/reports/'
|
||||
| '/app/_settings/settings/'
|
||||
| '/app/_app/_asset/chats/$chatId'
|
||||
|
@ -1736,6 +1758,13 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof AppSettingsPermissionsRouteImport
|
||||
parentRoute: typeof AppSettingsRoute
|
||||
}
|
||||
'/app/_app/new-user': {
|
||||
id: '/app/_app/new-user'
|
||||
path: '/new-user'
|
||||
fullPath: '/app/new-user'
|
||||
preLoaderRoute: typeof AppAppNewUserRouteImport
|
||||
parentRoute: typeof AppAppRoute
|
||||
}
|
||||
'/app/_app/home': {
|
||||
id: '/app/_app/home'
|
||||
path: '/home'
|
||||
|
@ -1764,6 +1793,13 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof AppAppReportsIndexRouteImport
|
||||
parentRoute: typeof AppAppRoute
|
||||
}
|
||||
'/app/_app/new-user/': {
|
||||
id: '/app/_app/new-user/'
|
||||
path: '/'
|
||||
fullPath: '/app/new-user/'
|
||||
preLoaderRoute: typeof AppAppNewUserIndexRouteImport
|
||||
parentRoute: typeof AppAppNewUserRoute
|
||||
}
|
||||
'/app/_app/metrics/': {
|
||||
id: '/app/_app/metrics/'
|
||||
path: '/metrics'
|
||||
|
@ -2912,6 +2948,18 @@ const AppAppAssetRouteWithChildren = AppAppAssetRoute._addFileChildren(
|
|||
AppAppAssetRouteChildren,
|
||||
)
|
||||
|
||||
interface AppAppNewUserRouteChildren {
|
||||
AppAppNewUserIndexRoute: typeof AppAppNewUserIndexRoute
|
||||
}
|
||||
|
||||
const AppAppNewUserRouteChildren: AppAppNewUserRouteChildren = {
|
||||
AppAppNewUserIndexRoute: AppAppNewUserIndexRoute,
|
||||
}
|
||||
|
||||
const AppAppNewUserRouteWithChildren = AppAppNewUserRoute._addFileChildren(
|
||||
AppAppNewUserRouteChildren,
|
||||
)
|
||||
|
||||
interface AppAppDatasetsDatasetIdPermissionsRouteChildren {
|
||||
AppAppDatasetsDatasetIdPermissionsDatasetGroupsRoute: typeof AppAppDatasetsDatasetIdPermissionsDatasetGroupsRoute
|
||||
AppAppDatasetsDatasetIdPermissionsOverviewRoute: typeof AppAppDatasetsDatasetIdPermissionsOverviewRoute
|
||||
|
@ -2961,6 +3009,7 @@ const AppAppDatasetsDatasetIdRouteWithChildren =
|
|||
interface AppAppRouteChildren {
|
||||
AppAppAssetRoute: typeof AppAppAssetRouteWithChildren
|
||||
AppAppHomeRoute: typeof AppAppHomeRoute
|
||||
AppAppNewUserRoute: typeof AppAppNewUserRouteWithChildren
|
||||
AppAppDatasetsDatasetIdRoute: typeof AppAppDatasetsDatasetIdRouteWithChildren
|
||||
AppAppChatsIndexRoute: typeof AppAppChatsIndexRoute
|
||||
AppAppCollectionsIndexRoute: typeof AppAppCollectionsIndexRoute
|
||||
|
@ -2974,6 +3023,7 @@ interface AppAppRouteChildren {
|
|||
const AppAppRouteChildren: AppAppRouteChildren = {
|
||||
AppAppAssetRoute: AppAppAssetRouteWithChildren,
|
||||
AppAppHomeRoute: AppAppHomeRoute,
|
||||
AppAppNewUserRoute: AppAppNewUserRouteWithChildren,
|
||||
AppAppDatasetsDatasetIdRoute: AppAppDatasetsDatasetIdRouteWithChildren,
|
||||
AppAppChatsIndexRoute: AppAppChatsIndexRoute,
|
||||
AppAppCollectionsIndexRoute: AppAppCollectionsIndexRoute,
|
||||
|
|
|
@ -30,15 +30,20 @@ export const Route = createFileRoute('/app')({
|
|||
if (user && user?.organizations?.length === 0) {
|
||||
throw redirect({ href: BUSTER_SIGN_UP_URL, replace: true, statusCode: 307 });
|
||||
}
|
||||
|
||||
if (user && !user.user.name) {
|
||||
throw redirect({ to: '/app/new-user', replace: true, statusCode: 307 });
|
||||
}
|
||||
|
||||
return {
|
||||
supabaseSession,
|
||||
};
|
||||
} catch (error) {
|
||||
// Re-throw redirect Responses so the router can handle them (e.g., getting-started)
|
||||
if (error instanceof Response) {
|
||||
throw error;
|
||||
if (error instanceof Response && error.status === 307) {
|
||||
return {
|
||||
supabaseSession,
|
||||
};
|
||||
}
|
||||
console.error('Error in app route loader:', error);
|
||||
throw redirect({ to: '/auth/login', replace: true, statusCode: 307 });
|
||||
}
|
||||
},
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { createFileRoute, Outlet } from '@tanstack/react-router';
|
||||
import NewUserWelcome from '@/assets/png/new-user-welcome.png';
|
||||
|
||||
export const Route = createFileRoute('/app/_app/new-user')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<section className="h-[100vh]">
|
||||
<div className="flex h-[100vh] items-center">
|
||||
<div className="mx-auto flex min-h-full w-full">
|
||||
<div className="hidden w-1/2 min-w-[400px] max-w-[650px] md:flex">
|
||||
<Outlet />
|
||||
</div>
|
||||
<div className="relative flex w-full flex-col items-center justify-center">
|
||||
<div
|
||||
className="w-full bg-backgroud"
|
||||
style={{
|
||||
height: '85vh',
|
||||
background: `url(${NewUserWelcome}) no-repeat left center`,
|
||||
backgroundSize: 'cover',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { NewUserController } from '@/controllers/NewUserController';
|
||||
|
||||
export const Route = createFileRoute('/app/_app/new-user/')({
|
||||
component: RouteComponent,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
return <NewUserController />;
|
||||
}
|