diff --git a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx index 310360f..ed273ae 100644 --- a/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx +++ b/ee/tabby-ui/app/(dashboard)/components/sidebar.tsx @@ -1,5 +1,6 @@ 'use client' +import UserPanel from '@/components/user-panel' import { cn } from '@/lib/utils' import { cva } from 'class-variance-authority' import Link from 'next/link' @@ -16,7 +17,7 @@ export default function Sidebar({ children, className }: SidebarProps) { className={cn('grid overflow-hidden lg:grid-cols-[280px_1fr]', className)} >
-
+
+ +
+ +
{children}
diff --git a/ee/tabby-ui/app/(dashboard)/page.tsx b/ee/tabby-ui/app/(dashboard)/page.tsx index 00c7ff3..fdd777a 100644 --- a/ee/tabby-ui/app/(dashboard)/page.tsx +++ b/ee/tabby-ui/app/(dashboard)/page.tsx @@ -19,17 +19,21 @@ import { WorkerKind } from '@/lib/gql/generates/graphql' import { CopyButton } from '@/components/copy-button' import { graphql } from '@/lib/gql/generates' import { useGraphQLQuery } from '@/lib/tabby/gql' +import { useSession } from '@/lib/tabby/auth' const COMMUNITY_DIALOG_SHOWN_KEY = 'community-dialog-shown' export default function Home() { + const { status } = useSession() const [open, setOpen] = useState(false) useEffect(() => { + if (status !== 'authenticated') return + if (!localStorage.getItem(COMMUNITY_DIALOG_SHOWN_KEY)) { setOpen(true) localStorage.setItem(COMMUNITY_DIALOG_SHOWN_KEY, 'true') } - }, []) + }, [status]) return (
diff --git a/ee/tabby-ui/app/auth/components/signup.tsx b/ee/tabby-ui/app/auth/components/signup.tsx deleted file mode 100644 index d8ab4e6..0000000 --- a/ee/tabby-ui/app/auth/components/signup.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client' - -import { graphql } from '@/lib/gql/generates' -import { UserAuthForm } from './user-auth-form' -import { useSearchParams } from 'next/navigation' -import { useGraphQLQuery } from '@/lib/tabby/gql' - -export const getIsAdminInitialized = graphql(/* GraphQL */ ` - query GetIsAdminInitialized { - isAdminInitialized - } -`) - -export default function Signup() { - const { data } = useGraphQLQuery(getIsAdminInitialized) - const title = data?.isAdminInitialized - ? 'Create an account' - : 'Create an admin account' - - const searchParams = useSearchParams() - const invitationCode = searchParams.get('invitationCode') || undefined - - return ( -
-
-

{title}

-

- Fill form below to create your account -

-
- -
- ) -} diff --git a/ee/tabby-ui/app/auth/layout.tsx b/ee/tabby-ui/app/auth/layout.tsx new file mode 100644 index 0000000..e8cae81 --- /dev/null +++ b/ee/tabby-ui/app/auth/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children +}: { + children: React.ReactNode +}) { + return ( +
+ {children} +
+ ) +} diff --git a/ee/tabby-ui/app/auth/page.tsx b/ee/tabby-ui/app/auth/page.tsx deleted file mode 100644 index f6a9db4..0000000 --- a/ee/tabby-ui/app/auth/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Metadata } from 'next' - -import Signup from './components/signup' - -export const metadata: Metadata = { - title: 'Authentication', - description: 'Authentication forms built using the components.' -} - -export default function AuthenticationPage() { - return ( -
- -
- ) -} diff --git a/ee/tabby-ui/app/auth/signin/components/signin.tsx b/ee/tabby-ui/app/auth/signin/components/signin.tsx new file mode 100644 index 0000000..1fc0929 --- /dev/null +++ b/ee/tabby-ui/app/auth/signin/components/signin.tsx @@ -0,0 +1,17 @@ +'use client' + +import UserSignInForm from './user-signin-form' + +export default function Signin() { + return ( +
+
+

Sign In

+

+ Enter credentials to login to your account +

+
+ +
+ ) +} diff --git a/ee/tabby-ui/app/auth/signin/components/user-signin-form.tsx b/ee/tabby-ui/app/auth/signin/components/user-signin-form.tsx new file mode 100644 index 0000000..fb85323 --- /dev/null +++ b/ee/tabby-ui/app/auth/signin/components/user-signin-form.tsx @@ -0,0 +1,113 @@ +'use client' + +import * as React from 'react' + +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm } from 'react-hook-form' +import * as z from 'zod' + +import { cn } from '@/lib/utils' +import { IconSpinner } from '@/components/ui/icons' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from '@/components/ui/form' +import { graphql } from '@/lib/gql/generates' +import { useGraphQLForm } from '@/lib/tabby/gql' +import { useSignIn } from '@/lib/tabby/auth' +import { useRouter } from 'next/navigation' + +export const tokenAuth = graphql(/* GraphQL */ ` + mutation tokenAuth($email: String!, $password: String!) { + tokenAuth(email: $email, password: $password) { + accessToken + refreshToken + } + } +`) + +const formSchema = z.object({ + email: z.string().email('Invalid email address'), + password: z.string() +}) + +interface UserAuthFormProps extends React.HTMLAttributes { + invitationCode?: string +} + +export default function UserSignInForm({ + className, + invitationCode, + ...props +}: UserAuthFormProps) { + const form = useForm>({ + resolver: zodResolver(formSchema) + }) + + const router = useRouter() + const signIn = useSignIn() + const { isSubmitting } = form.formState + const { onSubmit } = useGraphQLForm(tokenAuth, { + onSuccess: async values => { + if (await signIn(values.tokenAuth)) { + router.replace('/') + } + }, + onError: (path, message) => form.setError(path as any, { message }) + }) + + return ( +
+
+ + ( + + Email + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + + + + +
+ ) +} diff --git a/ee/tabby-ui/app/auth/signin/page.tsx b/ee/tabby-ui/app/auth/signin/page.tsx new file mode 100644 index 0000000..07806cf --- /dev/null +++ b/ee/tabby-ui/app/auth/signin/page.tsx @@ -0,0 +1,10 @@ +import { Metadata } from 'next' +import Signin from './components/signin' + +export const metadata: Metadata = { + title: 'Sign In' +} + +export default function Page() { + return +} diff --git a/ee/tabby-ui/app/auth/signup/components/signup.tsx b/ee/tabby-ui/app/auth/signup/components/signup.tsx new file mode 100644 index 0000000..4b53c70 --- /dev/null +++ b/ee/tabby-ui/app/auth/signup/components/signup.tsx @@ -0,0 +1,50 @@ +'use client' + +import { UserAuthForm } from './user-register-form' +import { useSearchParams } from 'next/navigation' + +export default function Signup() { + const searchParams = useSearchParams() + const invitationCode = searchParams.get('invitationCode') || undefined + const isAdmin = searchParams.get('isAdmin') || false + + const title = isAdmin ? 'Create an admin account' : 'Create an account' + + const description = isAdmin + ? 'The admin account has access to invite collaborators and manage Tabby configuration' + : 'Fill form below to create your account' + + if (isAdmin || invitationCode) { + return + } else { + return ( + + ) + } +} + +function Content({ + title, + description, + show +}: { + title: string + description: string + show?: boolean +}) { + const searchParams = useSearchParams() + const invitationCode = searchParams.get('invitationCode') || undefined + + return ( +
+
+

{title}

+

{description}

+
+ {show && } +
+ ) +} diff --git a/ee/tabby-ui/app/auth/components/user-auth-form.tsx b/ee/tabby-ui/app/auth/signup/components/user-register-form.tsx similarity index 90% rename from ee/tabby-ui/app/auth/components/user-auth-form.tsx rename to ee/tabby-ui/app/auth/signup/components/user-register-form.tsx index 378add8..1eba9c5 100644 --- a/ee/tabby-ui/app/auth/components/user-auth-form.tsx +++ b/ee/tabby-ui/app/auth/signup/components/user-register-form.tsx @@ -20,6 +20,8 @@ import { } from '@/components/ui/form' import { graphql } from '@/lib/gql/generates' import { useGraphQLForm } from '@/lib/tabby/gql' +import { useSignIn } from '@/lib/tabby/auth' +import { useRouter } from 'next/navigation' export const registerUser = graphql(/* GraphQL */ ` mutation register( @@ -59,15 +61,19 @@ export function UserAuthForm({ const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - email: '', - password1: '', - password2: '', invitationCode } }) + const router = useRouter() + const signIn = useSignIn() const { isSubmitting } = form.formState const { onSubmit } = useGraphQLForm(registerUser, { + onSuccess: async values => { + if (await signIn(values.register)) { + router.replace('/') + } + }, onError: (path, message) => form.setError(path as any, { message }) }) @@ -125,14 +131,14 @@ export function UserAuthForm({ control={form.control} name="invitationCode" render={({ field }) => ( - + )} /> -