diff --git a/packages/backend/server/src/modules/auth/next-auth-options.ts b/packages/backend/server/src/modules/auth/next-auth-options.ts index 81bf7308ab..fa40774a27 100644 --- a/packages/backend/server/src/modules/auth/next-auth-options.ts +++ b/packages/backend/server/src/modules/auth/next-auth-options.ts @@ -258,6 +258,10 @@ export const NextAuthOptionsProvider: FactoryProvider = { return url; }, }; + + nextAuthOptions.pages = { + newUser: '/auth/onboarding', + }; return nextAuthOptions; }, inject: [Config, PrismaService, MailService, SessionService], diff --git a/packages/frontend/component/package.json b/packages/frontend/component/package.json index 5401e7828c..b4885a57e1 100644 --- a/packages/frontend/component/package.json +++ b/packages/frontend/component/package.json @@ -68,6 +68,7 @@ "react-router-dom": "^6.16.0", "react-virtuoso": "^4.6.2", "rxjs": "^7.8.1", + "swr": "^2.2.4", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/packages/frontend/component/src/components/auth-components/index.tsx b/packages/frontend/component/src/components/auth-components/index.tsx index 6f2a661e29..640e1867fc 100644 --- a/packages/frontend/component/src/components/auth-components/index.tsx +++ b/packages/frontend/component/src/components/auth-components/index.tsx @@ -8,6 +8,7 @@ export * from './confirm-change-email'; export * from './count-down-render'; export * from './modal'; export * from './modal-header'; +export * from './onboarding-page'; export * from './password-input'; export * from './set-password-page'; export * from './sign-in-page-container'; diff --git a/packages/frontend/component/src/components/auth-components/onboarding-page.css.ts b/packages/frontend/component/src/components/auth-components/onboarding-page.css.ts new file mode 100644 index 0000000000..997e26696e --- /dev/null +++ b/packages/frontend/component/src/components/auth-components/onboarding-page.css.ts @@ -0,0 +1,141 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const scrollableContainer = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + height: '100vh', + padding: '0 200px', + '@media': { + 'screen and (max-width: 1024px)': { + padding: '80px 36px', + alignItems: 'center', + }, + }, +}); + +export const onboardingContainer = style({ + maxWidth: '600px', + padding: '160px 0', + '@media': { + 'screen and (max-width: 1024px)': { + padding: '40px 0', + width: '100%', + }, + }, +}); + +export const content = style({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + gap: '36px', + minHeight: '450px', +}); + +export const question = style({ + color: 'var(--affine-text-color)', + fontFamily: 'Inter', + fontSize: 'var(--affine-font-h-1)', + fontStyle: 'normal', + fontWeight: 600, + lineHeight: '36px', +}); + +export const optionsWrapper = style({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + gap: '16px', + // flexShrink: 0, + flexGrow: 1, +}); + +export const buttonWrapper = style({ + display: 'flex', + flexDirection: 'row', + alignItems: 'flex-start', + gap: '24px', + flexShrink: 0, +}); + +export const checkBox = style({ + alignItems: 'center', + fontSize: '24px', +}); + +globalStyle(`${checkBox} svg`, { + color: 'var(--affine-brand-color)', + flexShrink: 0, + marginRight: '8px', +}); + +export const label = style({ + fontSize: 'var(--affine-font-base)', + fontWeight: 500, +}); + +export const input = style({ + width: '520px', + '@media': { + 'screen and (max-width: 768px)': { + width: '100%', + }, + }, +}); + +export const button = style({ + fontWeight: 600, + fontSize: 'var(--affine-font-base)', +}); + +export const openAFFiNEButton = style({ + alignSelf: 'flex-start', +}); + +export const rightCornerButton = style({ + position: 'absolute', + top: '24px', + right: '24px', +}); + +export const thankContainer = style({ + display: 'flex', + flexDirection: 'column', + gap: '24px', +}); + +export const thankTitle = style({ + fontSize: 'var(--affine-font-title)', + fontWeight: '700', + lineHeight: '44px', +}); + +export const thankText = style({ + fontSize: 'var(--affine-font-h-6)', + height: '300px', + fontWeight: '600', + lineHeight: '26px', +}); + +export const linkGroup = style({ + display: 'flex', + position: 'absolute', + bottom: '24px', + right: '24px', + fontSize: 'var(--affine-font-xs)', + height: '16px', + gap: '6px', + width: '100%', + justifyContent: 'flex-end', + backgroundColor: 'var(--affine-background-color)', +}); +export const link = style({ + color: 'var(--affine-text-secondary-color)', + selectors: { + '&:visited': { + color: 'var(--affine-text-secondary-color)', + }, + }, +}); diff --git a/packages/frontend/component/src/components/auth-components/onboarding-page.tsx b/packages/frontend/component/src/components/auth-components/onboarding-page.tsx new file mode 100644 index 0000000000..c49af66595 --- /dev/null +++ b/packages/frontend/component/src/components/auth-components/onboarding-page.tsx @@ -0,0 +1,210 @@ +import { fetchWithTraceReport } from '@affine/graphql'; +import { ArrowRightSmallIcon } from '@blocksuite/icons'; +import clsx from 'clsx'; +import { useMemo, useState } from 'react'; +import useSWR from 'swr'; + +import { Button } from '../../ui/button'; +import { Checkbox } from '../../ui/checkbox'; +import { Divider } from '../../ui/divider'; +import Input from '../../ui/input'; +import { ScrollableContainer } from '../../ui/scrollbar'; +import * as styles from './onboarding-page.css'; +import type { User } from './type'; + +type QuestionOption = { + type: 'checkbox' | 'input'; + label: string; + value: string; +}; + +type Question = { + id?: string; + question: string; + options?: QuestionOption[]; +}; + +type QuestionnaireAnswer = { + form: string; + ask: string; + answer: string[]; +}; + +export const ScrollableLayout = ({ + children, +}: { + children: React.ReactNode; +}) => { + return ( + +
{children}
+ +
+ + Terms of Conditions + + + + Privacy Policy + +
+
+ ); +}; + +export const OnboardingPage = ({ + user, + onOpenAffine, +}: { + user: User; + onOpenAffine: () => void; +}) => { + const [questionIdx, setQuestionIdx] = useState(0); + const { data: questions } = useSWR( + '/api/worker/questionnaire', + url => fetchWithTraceReport(url).then(r => r.json()), + { suspense: true, revalidateOnFocus: false } + ); + const [options, setOptions] = useState(new Set()); + const [inputs, setInputs] = useState>({}); + + const question = useMemo( + () => questions?.[questionIdx], + [questionIdx, questions] + ); + + if (!questions) { + return null; + } + + if (question) { + return ( + +
+

{question.question}

+
+ {question.options && + question.options.length > 0 && + question.options.map((option, optionIndex) => { + if (option.type === 'checkbox') { + return ( + { + setOptions(set => { + if (e.target.checked) { + set.add(option.value); + } else { + set.delete(option.value); + } + return new Set(set); + }); + }} + label={option.label} + /> + ); + } else if (option.type === 'input') { + return ( + + setInputs(prev => ({ ...prev, [option.value]: value })) + } + /> + ); + } + return null; + })} +
+
+ + +
+
+
+ ); + } + return ( + +
+

Thank you!

+

+ We will continue to enhance our products based on your feedback. Thank + you once again for your supports. +

+ +
+
+ ); +}; diff --git a/packages/frontend/component/src/ui/checkbox/checkbox.tsx b/packages/frontend/component/src/ui/checkbox/checkbox.tsx index b54c245006..d602083af3 100644 --- a/packages/frontend/component/src/ui/checkbox/checkbox.tsx +++ b/packages/frontend/component/src/ui/checkbox/checkbox.tsx @@ -17,6 +17,10 @@ export type CheckboxProps = Omit< disabled?: boolean; indeterminate?: boolean; animation?: boolean; + name?: string; + label?: string; + inputClassName?: string; + labelClassName?: string; }; export const Checkbox = ({ @@ -25,6 +29,11 @@ export const Checkbox = ({ indeterminate: indeterminate, disabled, animation, + name, + label, + inputClassName, + labelClassName, + className, ...otherProps }: CheckboxProps) => { const inputRef = useRef(null); @@ -56,7 +65,7 @@ export const Checkbox = ({ return (
@@ -64,12 +73,19 @@ export const Checkbox = ({ + {label ? ( + + ) : null}
); }; diff --git a/packages/frontend/component/src/ui/checkbox/icons.tsx b/packages/frontend/component/src/ui/checkbox/icons.tsx index dfcbb34418..4907f63b8b 100644 --- a/packages/frontend/component/src/ui/checkbox/icons.tsx +++ b/packages/frontend/component/src/ui/checkbox/icons.tsx @@ -10,7 +10,7 @@ const unchecked = ( fillRule="evenodd" clipRule="evenodd" d="M6 3.25C4.48122 3.25 3.25 4.48122 3.25 6V18C3.25 19.5188 4.48122 20.75 6 20.75H18C19.5188 20.75 20.75 19.5188 20.75 18V6C20.75 4.48122 19.5188 3.25 18 3.25H6ZM4.75 6C4.75 5.30964 5.30964 4.75 6 4.75H18C18.6904 4.75 19.25 5.30964 19.25 6V18C19.25 18.6904 18.6904 19.25 18 19.25H6C5.30964 19.25 4.75 18.6904 4.75 18V6Z" - fill="var(--affine-icon-color)" + fill="currentColor" /> ); @@ -44,7 +44,7 @@ const indeterminate = ( fillRule="evenodd" clipRule="evenodd" d="M6 3.25C4.48122 3.25 3.25 4.48122 3.25 6V18C3.25 19.5188 4.48122 20.75 6 20.75H18C19.5188 20.75 20.75 19.5188 20.75 18V6C20.75 4.48122 19.5188 3.25 18 3.25H6ZM8.54 11.25C8.12579 11.25 7.79 11.5858 7.79 12C7.79 12.4142 8.12579 12.75 8.54 12.75H15.54C15.9542 12.75 16.29 12.4142 16.29 12C16.29 11.5858 15.9542 11.25 15.54 11.25H8.54Z" - fill="var(--affine-icon-color)" + fill="currentColor" /> ); diff --git a/packages/frontend/component/src/ui/checkbox/index.css.ts b/packages/frontend/component/src/ui/checkbox/index.css.ts index 9af007637f..5a53613240 100644 --- a/packages/frontend/component/src/ui/checkbox/index.css.ts +++ b/packages/frontend/component/src/ui/checkbox/index.css.ts @@ -1,15 +1,17 @@ -import { style } from '@vanilla-extract/css'; +import { globalStyle, style } from '@vanilla-extract/css'; export const root = style({ display: 'inline-flex', alignItems: 'center', position: 'relative', - ':hover': { - opacity: 0.8, - }, - ':active': { - opacity: 0.9, - }, +}); + +globalStyle(`${root}:hover svg`, { + opacity: 0.8, +}); + +globalStyle(`${root}:active svg`, { + opacity: 0.9, }); export const disabled = style({ @@ -23,6 +25,9 @@ export const input = style({ width: '1em', height: '1em', inset: 0, + top: '50%', + transform: 'translateY(-50%)', + cursor: 'pointer', fontSize: 'inherit', }); diff --git a/packages/frontend/component/src/ui/scrollbar/index.css.ts b/packages/frontend/component/src/ui/scrollbar/index.css.ts index a8bf23b435..7e63c7ef13 100644 --- a/packages/frontend/component/src/ui/scrollbar/index.css.ts +++ b/packages/frontend/component/src/ui/scrollbar/index.css.ts @@ -35,7 +35,6 @@ globalStyle(`${scrollableViewport} > div`, { export const scrollableContainer = style({ height: '100%', - marginBottom: '4px', }); export const scrollbar = style({ diff --git a/packages/frontend/core/.webpack/config.ts b/packages/frontend/core/.webpack/config.ts index f50c833df4..228180d684 100644 --- a/packages/frontend/core/.webpack/config.ts +++ b/packages/frontend/core/.webpack/config.ts @@ -389,6 +389,12 @@ export const createConfiguration: ( watch: true, }, proxy: { + '/api/worker/': { + target: 'https://affine-worker.toeverything.workers.dev', + pathRewrite: { '^/api/worker/': '/api/' }, + changeOrigin: true, + secure: false, + }, '/api': 'http://localhost:3010', '/socket.io': { target: 'http://localhost:3010', diff --git a/packages/frontend/core/src/pages/auth.tsx b/packages/frontend/core/src/pages/auth.tsx index e84a7a189e..7faca33bb6 100644 --- a/packages/frontend/core/src/pages/auth.tsx +++ b/packages/frontend/core/src/pages/auth.tsx @@ -2,6 +2,7 @@ import { ChangeEmailPage, ChangePasswordPage, ConfirmChangeEmail, + OnboardingPage, SetPasswordPage, SignInSuccessPage, SignUpPage, @@ -31,6 +32,7 @@ import { useCurrentUser } from '../hooks/affine/use-current-user'; import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper'; const authTypeSchema = z.enum([ + 'onboarding', 'setPassword', 'signIn', 'changePassword', @@ -93,6 +95,8 @@ export const AuthPage = (): ReactElement | null => { }, [jumpToIndex]); switch (authType) { + case 'onboarding': + return ; case 'signUp': { return (