Skip to content

Commit

Permalink
feat(ui): improve admin setup ux (#1671)
Browse files Browse the repository at this point in the history
* feat(ui): improve admin setup ux

* redirect admin signup to sigin after admin already setup

* polish codes

* disable scrolling

* [autofix.ci] apply automated fixes

* fix: form uncontrol warning

* fix form value warning

* [autofix.ci] apply automated fixes

* fix: admin singup form input active border issue

* update text

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Meng Zhang <[email protected]>
  • Loading branch information
3 people authored Mar 21, 2024
1 parent 0587f8f commit 47fd912
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 18 deletions.
39 changes: 39 additions & 0 deletions ee/tabby-ui/app/auth/signup/components/admin-register.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.admin-register-wrap {
transition: transform 0.35s ease-out;
padding-top: 20vh;
padding-bottom: 20vh;
}

.step-mask {
position: relative;
pointer-events: none;
user-select: none;
border-color: hsl(var(--muted-foreground) / 0.1);

}
.step-mask:before {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: linear-gradient(
0deg,
hsl(var(--background) / 1) 0%,
hsl(var(--background) / 1) 30%,
hsl(var(--background) / 0.5) 100%
);
pointer-events: none;
user-select: none;
z-index: 10;
width: 110%;
}
.step-mask.remote:before {
background: linear-gradient(
0deg,
hsl(var(--background) / 1) 0%,
hsl(var(--background) / 1) 30%,
hsl(var(--background) / 0.9) 100%
);
}
96 changes: 96 additions & 0 deletions ee/tabby-ui/app/auth/signup/components/admin-register.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use client'

import { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'

import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'

import { UserAuthForm } from './user-register-form'

import './admin-register.css'

function AdminRegisterStep({
step,
currentStep,
children
}: {
step: number
currentStep: number
children: React.ReactNode
}) {
return (
<div
id={`step-${step}`}
className={cn('border-l border-foreground py-8 pl-12 pr-2', {
'step-mask': step !== currentStep,
remote: Math.abs(currentStep - step) > 1
})}
>
{children}
</div>
)
}

export default function AdminRegister() {
const router = useRouter()
const [currentStep, setCurrentStep] = useState(1)

useEffect(() => {
if (currentStep === 1) return
document
.getElementById(`step-${currentStep}`)
?.scrollIntoView({ behavior: 'smooth' })
}, [currentStep])

return (
<div className="admin-register-wrap h-screen w-[600px] overflow-hidden">
<AdminRegisterStep step={1} currentStep={currentStep}>
<h2 className="text-3xl font-semibold tracking-tight first:mt-0">
Welcome!
</h2>
<p className="mt-2 leading-7 text-muted-foreground">
Your tabby server is live and ready to use. This step by step guide
will help you set up your admin account.
</p>
<p className="leading-7 text-muted-foreground">
Admin account is the highest level of access in your server. Once
created, you can invite other members to join your server.
</p>
<Button className="mt-5 w-48" onClick={() => setCurrentStep(2)}>
Start
</Button>
</AdminRegisterStep>

<AdminRegisterStep step={2} currentStep={currentStep}>
<h3 className="text-2xl font-semibold tracking-tight">
Create Admin Account
</h3>
<p className="mb-3 leading-7 text-muted-foreground">
Please store your password in a safe place. We do not store your
password and cannot recover it for you.
</p>
<UserAuthForm
onSuccess={() => setCurrentStep(3)}
buttonClass="self-start w-48"
/>
</AdminRegisterStep>

<AdminRegisterStep step={3} currentStep={currentStep}>
<h3 className="text-2xl font-semibold tracking-tight">
Congratulations!
</h3>
<p className="leading-7 text-muted-foreground">
You have successfully created an admin account.
</p>
<p className="mb-3 leading-7 text-muted-foreground">
To start, navigate to the dashboard and invite other members to join
your server.
</p>
<Button className="mt-5 w-48" onClick={() => router.replace('/')}>
Go to dashboard
</Button>
</AdminRegisterStep>
</div>
)
}
23 changes: 12 additions & 11 deletions ee/tabby-ui/app/auth/signup/components/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@

import { useSearchParams } from 'next/navigation'

import AdminRegister from './admin-register'
import { UserAuthForm } from './user-register-form'

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
? 'Your instance will be secured, only registered users can access it.'
: 'Fill form below to create your account'

if (isAdmin || invitationCode) {
return <Content title={title} description={description} show />
} else {
if (isAdmin) return <AdminRegister />
if (invitationCode) {
return (
<Content
title="No invitation code"
description="Please contact your Tabby admin for an invitation code to register"
title="Create an account"
description="Fill form below to create your account"
show
/>
)
}
return (
<Content
title="No invitation code"
description="Please contact your Tabby admin for an invitation code to register"
/>
)
}

function Content({
Expand Down
21 changes: 17 additions & 4 deletions ee/tabby-ui/app/auth/signup/components/user-register-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ const formSchema = z.object({

interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {
invitationCode?: string
onSuccess?: () => void
buttonClass?: string
}

export function UserAuthForm({
className,
invitationCode,
onSuccess,
buttonClass,
...props
}: UserAuthFormProps) {
const form = useForm<z.infer<typeof formSchema>>({
Expand All @@ -71,7 +75,11 @@ export function UserAuthForm({
const onSubmit = useMutation(registerUser, {
async onCompleted(values) {
if (await signIn(values?.register)) {
router.replace('/')
if (onSuccess) {
onSuccess()
} else {
router.replace('/')
}
}
},
form
Expand All @@ -95,6 +103,7 @@ export function UserAuthForm({
autoComplete="email"
autoCorrect="off"
{...field}
value={field.value ?? ''}
/>
</FormControl>
<FormMessage />
Expand All @@ -108,7 +117,7 @@ export function UserAuthForm({
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input type="password" {...field} />
<Input type="password" {...field} value={field.value ?? ''} />
</FormControl>
<FormMessage />
</FormItem>
Expand All @@ -121,7 +130,7 @@ export function UserAuthForm({
<FormItem>
<FormLabel>Confirm Password</FormLabel>
<FormControl>
<Input type="password" {...field} />
<Input type="password" {...field} value={field.value ?? ''} />
</FormControl>
<FormMessage />
</FormItem>
Expand All @@ -138,7 +147,11 @@ export function UserAuthForm({
</FormItem>
)}
/>
<Button type="submit" className="mt-2" disabled={isSubmitting}>
<Button
type="submit"
className={cn('mt-2', buttonClass)}
disabled={isSubmitting}
>
{isSubmitting && (
<IconSpinner className="mr-2 h-4 w-4 animate-spin" />
)}
Expand Down
11 changes: 8 additions & 3 deletions ee/tabby-ui/lib/tabby/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react'
import { usePathname, useRouter } from 'next/navigation'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import useLocalStorage from 'use-local-storage'

import { graphql } from '@/lib/gql/generates'
Expand Down Expand Up @@ -282,6 +282,7 @@ function useAuthenticatedSession() {
const isAdminInitialized = useIsAdminInitialized()
const router = useRouter()
const pathName = usePathname()
const searchParams = useSearchParams()
const { data: session, status } = useSession()

React.useEffect(() => {
Expand All @@ -290,8 +291,12 @@ function useAuthenticatedSession() {
if (isAdminInitialized === undefined) return

if (!isAdminInitialized) {
router.replace('/auth/signup?isAdmin=true')
} else if (!redirectWhitelist.includes(pathName)) {
return router.replace('/auth/signup?isAdmin=true')
}

const isAdminSignup =
pathName === '/auth/signup' && searchParams.get('isAdmin') === 'true'
if (!redirectWhitelist.includes(pathName) || isAdminSignup) {
router.replace('/auth/signin')
}
}, [isAdminInitialized, status])
Expand Down

0 comments on commit 47fd912

Please sign in to comment.