Skip to content

Commit

Permalink
feat(onboarding): Add onboarding for new users (#746)
Browse files Browse the repository at this point in the history
* feat: Add onboarding for new users

* feat: Overhaul onboarding into a multistep process

* docs: Update README.md with usage of the entrypoint module

* refactor: Remove steppers and move all steps to a single page

* feat: Move onboarding to a separate page and add bounty card

* feat: Redirect to onboarding on launch, and move around content

---------

Co-authored-by: Richard Abrich <[email protected]>
  • Loading branch information
KIRA009 and abrichr authored Jun 20, 2024
1 parent eea048e commit bdf153e
Show file tree
Hide file tree
Showing 16 changed files with 266 additions and 18 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ Using python3.10 (3.10.13)

Notice the environment prefix `(openadapt-py3.10)`.

### Tray
Run the following command to start the system tray icon and launch the web dashboard:

```
python -m openadapt.entrypoint
```
This command will print the config, update the database to the latest migration, start the system tray icon and launch the web dashboard.

### Record

Create a new recording by running the following command:
Expand Down
4 changes: 3 additions & 1 deletion openadapt/app/dashboard/api/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ def attach_routes(self) -> APIRouter:
self.app.add_api_route("", self.set_settings, methods=["POST"])
return self.app

Category = Literal["api_keys", "scrubbing", "record_and_replay", "general"]
Category = Literal[
"api_keys", "scrubbing", "record_and_replay", "general", "onboarding"
]

@staticmethod
def get_settings(category: Category) -> dict[str, Any]:
Expand Down
7 changes: 3 additions & 4 deletions openadapt/app/dashboard/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { ColorSchemeScript, MantineProvider } from '@mantine/core'
import { Notifications } from '@mantine/notifications';
import { Shell } from '@/components/Shell'
import { CSPostHogProvider } from './providers';
import { ModalsProvider } from '@mantine/modals';

export const metadata = {
title: 'OpenAdapt.AI',
Expand All @@ -24,9 +23,9 @@ export default function RootLayout({
<body>
<MantineProvider>
<Notifications />
<ModalsProvider>
<Shell>{children}</Shell>
</ModalsProvider>
<Shell>
{children}
</Shell>
</MantineProvider>
</body>
</CSPostHogProvider>
Expand Down
16 changes: 16 additions & 0 deletions openadapt/app/dashboard/app/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BookACall } from "@/components/Onboarding/steps/BookACall";
import { RegisterForUpdates } from "@/components/Onboarding/steps/RegisterForUpdates";
import { Tutorial } from "@/components/Onboarding/steps/Tutorial";
import { Box, Divider } from "@mantine/core";

export default function Onboarding() {
return (
<Box w="fit-content" mx="auto">
<RegisterForUpdates />
<Divider my={20} w="40vw" mx="auto" />
<BookACall />
<Divider my={20} w="40vw" mx="auto" />
<Tutorial />
</Box>
);
}
4 changes: 2 additions & 2 deletions openadapt/app/dashboard/app/recordings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function Recordings() {
}, []);
return (
<Box>
{recordingStatus === RecordingStatus.RECORDING && (
{/* {recordingStatus === RecordingStatus.RECORDING && (
<Button onClick={stopRecording} variant="outline" color="red">
Stop recording
</Button>
Expand All @@ -64,7 +64,7 @@ export default function Recordings() {
<Button variant="outline" color="blue">
Loading recording status...
</Button>
)}
)} */}
<Tabs defaultValue="regular" mt={40}>
<Tabs.List>
<Tabs.Tab value="regular">
Expand Down
4 changes: 4 additions & 0 deletions openadapt/app/dashboard/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ export const routes: Route[] = [
{
name: 'Scrubbing',
path: '/scrubbing',
},
{
name: 'Onboarding',
path: '/onboarding',
}
]
16 changes: 16 additions & 0 deletions openadapt/app/dashboard/components/Onboarding/steps/BookACall.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Box, Text } from '@mantine/core'
import Link from 'next/link'
import React from 'react'

export const BookACall = () => {
return (
<Box w="fit-content">
<Text>
<Link href="https://www.getclockwise.com/c/richard-abrich/quick-meeting" className='no-underline'>
Book a call with us
</Link>
<Text component='span'> to discuss how OpenAdapt can help your team</Text>
</Text>
</Box>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use client';


import { Box, Button, Stack, Text, TextInput } from '@mantine/core'
import { isNotEmpty, useForm } from '@mantine/form'
import { notifications } from '@mantine/notifications'
import React, { useEffect } from 'react'

export const RegisterForUpdates = () => {
const onboardingForm = useForm({
initialValues: {
email: '',
},
validate: {
email: isNotEmpty('Email is required'),
}
})
useEffect(() => {
fetch('/api/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
REDIRECT_TO_ONBOARDING: false
}),
})
}, [])
function onSubmit({ email }: { email: string }) {
fetch('https://openadapt.ai/form.html', {
method: 'POST',
mode: 'no-cors',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
email,
'form-name': 'email',
'bot-field': '',
}).toString(),
}).then(() => {
notifications.show({
title: 'Thank you!',
message: 'You have been registered for updates',
color: 'green',
})
})
}
return (
<Box maw={600}>
<Stack gap="xs">
<form onSubmit={onboardingForm.onSubmit(onSubmit)}>
<TextInput
placeholder="Email (optional)"
label="Register for updates"
description="We promise not to spam!"
type="email"
{...onboardingForm.getInputProps('email')}
/>
<Button type="submit" mt={10}>
Register
</Button>
</form>
</Stack>
</Box>
)
}
60 changes: 60 additions & 0 deletions openadapt/app/dashboard/components/Onboarding/steps/Tutorial.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';

import { Box, Text } from '@mantine/core'
import { algora, type AlgoraOutput } from '@algora/sdk';
import React, { useEffect } from 'react'
import Link from 'next/link';

type Bounty = AlgoraOutput['bounty']['list']['items'][number];

function BountyCard(props: { bounty: Bounty }) {
return (
<Link
href={props.bounty.task.url}
target="_blank"
rel="noopener"
className="block group relative h-full rounded-lg border border-gray-400/50 dark:border-indigo-500/50 bg-gradient-to-br from-gray-300/30 via-gray-300/40 to-gray-300/50 dark:from-indigo-600/20 dark:via-indigo-600/30 dark:to-indigo-600/40 md:gap-8 transition-colors hover:border-gray-400 hover:dark:border-indigo-500 hover:bg-gray-300/10 hover:dark:bg-gray-600/5 !no-underline"
>
<div className="relative h-full p-4">
<div className="text-2xl font-bold text-green-500 group-hover:text-green-600 dark:text-green-400 dark:group-hover:text-green-300 transition-colors">
{props.bounty.reward_formatted}
</div>
<div className="mt-0.5 text-sm text-gray-700 dark:text-indigo-200 group-hover:text-gray-800 dark:group-hover:text-indigo-100 transition-colors">
{props.bounty.task.repo_name}#{props.bounty.task.number}
</div>
<div className="mt-3 line-clamp-1 break-words text-lg font-medium leading-tight text-gray-800 dark:text-indigo-50 group-hover:text-gray-900 dark:group-hover:text-white">
{props.bounty.task.title}
</div>
</div>
</Link>
);
}

const featuredBountyId = 'clxi7tk210002l20aqlz58ram';

async function getFeaturedBounty() {
const bounty: Bounty = await algora.bounty.get.query({ id: featuredBountyId });
return bounty;
}

export const Tutorial = () => {
const [featuredBounty, setFeaturedBounty] = React.useState<Bounty | null>(null);
useEffect(() => {
getFeaturedBounty().then(setFeaturedBounty);
}, []);
return (
<Box>
<Text my={20} maw={800}>
Welcome to OpenAdapt! Thank you for joining us on our mission to build open source desktop AI. Your feedback is extremely valuable!
</Text>
<Text my={20} maw={800}>
To start, please watch the demonstration below. Then try it yourself! If you have any issues, please submit a <Link href='https://github.com/OpenAdaptAI/OpenAdapt/issues/new/choose' className='no-underline'>Github Issue.</Link>
</Text>
<iframe width="800" height="400" src="https://www.youtube.com/embed/OVERugs50cQ?si=cmi1vY73ADom9EKG" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen className='block mx-auto'></iframe>
<Text my={20} maw={800}>
If you'd like to contribute directly to our development, please consider the following open Bounties (no development experience required):
{featuredBounty && <BountyCard bounty={featuredBounty} />}
</Text>
</Box>
)
}
8 changes: 6 additions & 2 deletions openadapt/app/dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const checkPort = (port) => {
}

// check if both ports are not being used
const { DASHBOARD_CLIENT_PORT, DASHBOARD_SERVER_PORT } = process.env
const { DASHBOARD_CLIENT_PORT, DASHBOARD_SERVER_PORT, REDIRECT_TO_ONBOARDING } = process.env
Promise.all([checkPort(DASHBOARD_CLIENT_PORT), checkPort(DASHBOARD_SERVER_PORT)])
.then(([clientPort, serverPort]) => {
if (clientPort !== DASHBOARD_CLIENT_PORT) {
Expand Down Expand Up @@ -45,7 +45,11 @@ function spawnChildProcess() {
// wait for 3 seconds before opening the browser
setTimeout(() => {
import('open').then(({ default: open }) => {
open(`http://localhost:${DASHBOARD_CLIENT_PORT}`)
let url = `http://localhost:${DASHBOARD_CLIENT_PORT}`
if (REDIRECT_TO_ONBOARDING === 'true') {
url += '/onboarding'
}
open(url)
})
}, 3000)
})
Expand Down
66 changes: 66 additions & 0 deletions openadapt/app/dashboard/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions openadapt/app/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"format": "prettier --write ."
},
"dependencies": {
"@algora/sdk": "^0.2.0",
"@mantine/carousel": "7.7.1",
"@mantine/core": "7.7.1",
"@mantine/form": "7.7.1",
Expand Down
Loading

0 comments on commit bdf153e

Please sign in to comment.