From b4cd046b53dab684c7a63821fc9d209664c8ec79 Mon Sep 17 00:00:00 2001 From: Hakan Shehu Date: Thu, 13 Nov 2025 22:38:19 -0800 Subject: [PATCH] Improve authentication flow (#251) --- .gitignore | 12 +- apps/desktop/assets/fonts/neotrax.otf | Bin 36508 -> 0 bytes .../client/routes/accounts/account-update.ts | 2 +- .../src/api/client/routes/accounts/index.ts | 20 -- .../routes/{accounts => auth}/email-login.ts | 2 +- .../email-password-reset-complete.ts | 2 +- .../email-password-reset-init.ts | 2 +- .../{accounts => auth}/email-register.ts | 2 +- .../routes/{accounts => auth}/email-verify.ts | 2 +- .../routes/{accounts => auth}/google-login.ts | 0 .../src/api/client/routes/auth/index.ts | 32 +++ .../routes/{accounts => auth}/logout.ts | 0 apps/server/src/api/client/routes/index.ts | 2 + assets/fonts/antonio.ttf | Bin 0 -> 73780 bytes assets/fonts/neotrax.otf | Bin 36508 -> 0 bytes assets/fonts/satoshi-variable-italic.woff2 | Bin 0 -> 43844 bytes assets/fonts/satoshi-variable.woff2 | Bin 0 -> 42588 bytes .../mutations/accounts/account-update.ts | 2 +- .../mutations/{accounts => auth}/base.ts | 2 +- .../{accounts => auth}/email-login.ts | 8 +- .../email-password-reset-complete.ts | 15 +- .../email-password-reset-init.ts | 8 +- .../{accounts => auth}/email-register.ts | 8 +- .../{accounts => auth}/email-verify.ts | 8 +- .../{accounts => auth}/google-login.ts | 6 +- .../client/src/handlers/mutations/index.ts | 12 +- packages/client/src/jobs/token-delete.ts | 2 +- .../{accounts => auth}/email-login.ts | 0 .../email-password-reset-complete.ts | 0 .../email-password-reset-init.ts | 0 .../{accounts => auth}/email-register.ts | 0 .../{accounts => auth}/email-verify.ts | 0 .../{accounts => auth}/google-login.ts | 0 packages/client/src/mutations/index.ts | 12 +- packages/core/src/index.ts | 1 + packages/core/src/types/accounts.ts | 102 -------- packages/core/src/types/auth.ts | 104 ++++++++ .../ui/src/components/accounts/login-form.tsx | 241 ------------------ .../ui/src/components/accounts/login-tab.tsx | 5 - packages/ui/src/components/accounts/login.tsx | 24 -- packages/ui/src/components/app/app-assets.tsx | 35 ++- .../ui/src/components/app/app-loading.tsx | 2 +- .../ui/src/components/auth/auth-cancel.tsx | 44 ++++ .../ui/src/components/auth/auth-layout.tsx | 38 +++ .../ui/src/components/auth/auth-server.tsx | 114 +++++++++ .../email-login-form.tsx} | 55 +--- .../email-password-reset-complete-form.tsx} | 82 +----- .../email-password-reset-init-form.tsx} | 47 +--- .../email-register-form.tsx} | 43 +--- .../email-verify-form.tsx} | 55 +--- .../{accounts => auth}/google-login.tsx | 47 ++-- packages/ui/src/components/auth/login-tab.tsx | 6 + packages/ui/src/components/auth/login.tsx | 198 ++++++++++++++ .../logout-container.tsx} | 2 +- .../logout-header.tsx} | 2 +- .../logout-tab.tsx} | 2 +- .../ui/src/components/auth/register-tab.tsx | 6 + packages/ui/src/components/auth/register.tsx | 201 +++++++++++++++ packages/ui/src/components/auth/reset-tab.tsx | 6 + packages/ui/src/components/auth/reset.tsx | 174 +++++++++++++ .../layouts/sidebars/sidebar-menu-footer.tsx | 2 +- .../layouts/sidebars/sidebar-settings.tsx | 2 +- .../servers/server-create-dialog.tsx | 12 +- .../components/servers/server-dropdown.tsx | 168 ------------ .../servers/server-settings-dialog.tsx | 27 +- packages/ui/src/components/ui/logo.tsx | 25 ++ packages/ui/src/contexts/auth.ts | 17 ++ packages/ui/src/lib/assets.ts | 1 + packages/ui/src/routes/auth/index.tsx | 10 + packages/ui/src/routes/auth/login.tsx | 16 ++ packages/ui/src/routes/auth/register.tsx | 16 ++ packages/ui/src/routes/auth/reset.tsx | 23 ++ packages/ui/src/routes/home.tsx | 2 +- packages/ui/src/routes/index.tsx | 19 +- packages/ui/src/routes/login.tsx | 16 -- packages/ui/src/routes/masks.tsx | 4 +- .../src/routes/workspace/account-logout.tsx | 38 --- packages/ui/src/routes/workspace/logout.tsx | 38 +++ scripts/src/postinstall/README.md | 4 +- scripts/src/postinstall/index.ts | 31 ++- 80 files changed, 1281 insertions(+), 987 deletions(-) delete mode 100644 apps/desktop/assets/fonts/neotrax.otf rename apps/server/src/api/client/routes/{accounts => auth}/email-login.ts (98%) rename apps/server/src/api/client/routes/{accounts => auth}/email-password-reset-complete.ts (98%) rename apps/server/src/api/client/routes/{accounts => auth}/email-password-reset-init.ts (98%) rename apps/server/src/api/client/routes/{accounts => auth}/email-register.ts (99%) rename apps/server/src/api/client/routes/{accounts => auth}/email-verify.ts (98%) rename apps/server/src/api/client/routes/{accounts => auth}/google-login.ts (100%) create mode 100644 apps/server/src/api/client/routes/auth/index.ts rename apps/server/src/api/client/routes/{accounts => auth}/logout.ts (100%) create mode 100644 assets/fonts/antonio.ttf delete mode 100644 assets/fonts/neotrax.otf create mode 100644 assets/fonts/satoshi-variable-italic.woff2 create mode 100644 assets/fonts/satoshi-variable.woff2 rename packages/client/src/handlers/mutations/{accounts => auth}/base.ts (98%) rename packages/client/src/handlers/mutations/{accounts => auth}/email-login.ts (86%) rename packages/client/src/handlers/mutations/{accounts => auth}/email-password-reset-complete.ts (80%) rename packages/client/src/handlers/mutations/{accounts => auth}/email-password-reset-init.ts (83%) rename packages/client/src/handlers/mutations/{accounts => auth}/email-register.ts (86%) rename packages/client/src/handlers/mutations/{accounts => auth}/email-verify.ts (87%) rename packages/client/src/handlers/mutations/{accounts => auth}/google-login.ts (87%) rename packages/client/src/mutations/{accounts => auth}/email-login.ts (100%) rename packages/client/src/mutations/{accounts => auth}/email-password-reset-complete.ts (100%) rename packages/client/src/mutations/{accounts => auth}/email-password-reset-init.ts (100%) rename packages/client/src/mutations/{accounts => auth}/email-register.ts (100%) rename packages/client/src/mutations/{accounts => auth}/email-verify.ts (100%) rename packages/client/src/mutations/{accounts => auth}/google-login.ts (100%) create mode 100644 packages/core/src/types/auth.ts delete mode 100644 packages/ui/src/components/accounts/login-form.tsx delete mode 100644 packages/ui/src/components/accounts/login-tab.tsx delete mode 100644 packages/ui/src/components/accounts/login.tsx create mode 100644 packages/ui/src/components/auth/auth-cancel.tsx create mode 100644 packages/ui/src/components/auth/auth-layout.tsx create mode 100644 packages/ui/src/components/auth/auth-server.tsx rename packages/ui/src/components/{accounts/email-login.tsx => auth/email-login-form.tsx} (64%) rename packages/ui/src/components/{accounts/email-password-reset-complete.tsx => auth/email-password-reset-complete-form.tsx} (62%) rename packages/ui/src/components/{accounts/email-password-reset-init.tsx => auth/email-password-reset-init-form.tsx} (56%) rename packages/ui/src/components/{accounts/email-register.tsx => auth/email-register-form.tsx} (75%) rename packages/ui/src/components/{accounts/email-verify.tsx => auth/email-verify-form.tsx} (63%) rename packages/ui/src/components/{accounts => auth}/google-login.tsx (54%) create mode 100644 packages/ui/src/components/auth/login-tab.tsx create mode 100644 packages/ui/src/components/auth/login.tsx rename packages/ui/src/components/{accounts/account-logout-container.tsx => auth/logout-container.tsx} (97%) rename packages/ui/src/components/{accounts/account-logout-header.tsx => auth/logout-header.tsx} (85%) rename packages/ui/src/components/{accounts/account-logout-tab.tsx => auth/logout-tab.tsx} (84%) create mode 100644 packages/ui/src/components/auth/register-tab.tsx create mode 100644 packages/ui/src/components/auth/register.tsx create mode 100644 packages/ui/src/components/auth/reset-tab.tsx create mode 100644 packages/ui/src/components/auth/reset.tsx delete mode 100644 packages/ui/src/components/servers/server-dropdown.tsx create mode 100644 packages/ui/src/components/ui/logo.tsx create mode 100644 packages/ui/src/contexts/auth.ts create mode 100644 packages/ui/src/routes/auth/index.tsx create mode 100644 packages/ui/src/routes/auth/login.tsx create mode 100644 packages/ui/src/routes/auth/register.tsx create mode 100644 packages/ui/src/routes/auth/reset.tsx delete mode 100644 packages/ui/src/routes/login.tsx delete mode 100644 packages/ui/src/routes/workspace/account-logout.tsx create mode 100644 packages/ui/src/routes/workspace/logout.tsx diff --git a/.gitignore b/.gitignore index 53a7010f..2d1171b3 100644 --- a/.gitignore +++ b/.gitignore @@ -155,7 +155,9 @@ src/scripts/icons/temp/ # Ignore desktop assets apps/desktop/assets/emojis.db apps/desktop/assets/icons.db -apps/desktop/assets/fonts/neotrax.otf +apps/desktop/assets/fonts/satoshi-variable.woff2 +apps/desktop/assets/fonts/satoshi-variable-italic.woff2 +apps/desktop/assets/fonts/antonio.ttf apps/desktop/assets/colanode-logo.png apps/desktop/assets/colanode-logo.ico apps/desktop/assets/colanode-logo.icns @@ -165,7 +167,9 @@ apps/web/public/assets/emojis.db apps/web/public/assets/icons.db apps/web/public/assets/emojis.svg apps/web/public/assets/icons.svg -apps/web/public/assets/fonts/neotrax.otf +apps/web/public/assets/fonts/satoshi-variable.woff2 +apps/web/public/assets/fonts/satoshi-variable-italic.woff2 +apps/web/public/assets/fonts/antonio.ttf apps/web/public/assets/colanode-logo-192.jpg apps/web/public/assets/colanode-logo-512.jpg @@ -173,7 +177,9 @@ apps/web/public/assets/colanode-logo-512.jpg apps/mobile/assets/ui/index.html apps/mobile/assets/emojis.db apps/mobile/assets/icons.db -apps/mobile/assets/fonts/neotrax.otf +apps/mobile/assets/fonts/satoshi-variable.woff2 +apps/mobile/assets/fonts/satoshi-variable-italic.woff2 +apps/mobile/assets/fonts/antonio.ttf .expo web-build/ diff --git a/apps/desktop/assets/fonts/neotrax.otf b/apps/desktop/assets/fonts/neotrax.otf deleted file mode 100644 index fb2c0fb721e3c18d0c9839e463afcc0312cead64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36508 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 hx5FNxN|$1Nv6p5;*+Za=ww;6 { instance.route({ method: 'PATCH', - url: '/', + url: '/me', schema: { body: accountUpdateInputSchema, response: { diff --git a/apps/server/src/api/client/routes/accounts/index.ts b/apps/server/src/api/client/routes/accounts/index.ts index ac9e4017..3a4533d1 100644 --- a/apps/server/src/api/client/routes/accounts/index.ts +++ b/apps/server/src/api/client/routes/accounts/index.ts @@ -1,36 +1,16 @@ import { FastifyPluginCallback } from 'fastify'; import { accountAuthenticator } from '@colanode/server/api/client/plugins/account-auth'; -import { authIpRateLimiter } from '@colanode/server/api/client/plugins/auth-ip-rate-limit'; import { accountSyncRoute } from './account-sync'; import { accountUpdateRoute } from './account-update'; -import { emailLoginRoute } from './email-login'; -import { emailPasswordResetCompleteRoute } from './email-password-reset-complete'; -import { emailPasswordResetInitRoute } from './email-password-reset-init'; -import { emailRegisterRoute } from './email-register'; -import { emailVerifyRoute } from './email-verify'; -import { googleLoginRoute } from './google-login'; -import { logoutRoute } from './logout'; export const accountRoutes: FastifyPluginCallback = (instance, _, done) => { - instance.register((subInstance) => { - subInstance.register(authIpRateLimiter); - - subInstance.register(emailLoginRoute); - subInstance.register(emailRegisterRoute); - subInstance.register(emailVerifyRoute); - subInstance.register(emailPasswordResetInitRoute); - subInstance.register(emailPasswordResetCompleteRoute); - subInstance.register(googleLoginRoute); - }); - instance.register((subInstance) => { subInstance.register(accountAuthenticator); subInstance.register(accountSyncRoute); subInstance.register(accountUpdateRoute); - subInstance.register(logoutRoute); }); done(); diff --git a/apps/server/src/api/client/routes/accounts/email-login.ts b/apps/server/src/api/client/routes/auth/email-login.ts similarity index 98% rename from apps/server/src/api/client/routes/accounts/email-login.ts rename to apps/server/src/api/client/routes/auth/email-login.ts index 036455bb..f142c5b9 100644 --- a/apps/server/src/api/client/routes/accounts/email-login.ts +++ b/apps/server/src/api/client/routes/auth/email-login.ts @@ -23,7 +23,7 @@ export const emailLoginRoute: FastifyPluginCallbackZod = ( ) => { instance.route({ method: 'POST', - url: '/emails/login', + url: '/email/login', schema: { body: emailLoginInputSchema, response: { diff --git a/apps/server/src/api/client/routes/accounts/email-password-reset-complete.ts b/apps/server/src/api/client/routes/auth/email-password-reset-complete.ts similarity index 98% rename from apps/server/src/api/client/routes/accounts/email-password-reset-complete.ts rename to apps/server/src/api/client/routes/auth/email-password-reset-complete.ts index 2fcf4786..9fcdc9b4 100644 --- a/apps/server/src/api/client/routes/accounts/email-password-reset-complete.ts +++ b/apps/server/src/api/client/routes/auth/email-password-reset-complete.ts @@ -21,7 +21,7 @@ export const emailPasswordResetCompleteRoute: FastifyPluginCallbackZod = ( ) => { instance.route({ method: 'POST', - url: '/emails/passwords/reset/complete', + url: '/email/password-reset/complete', schema: { body: emailPasswordResetCompleteInputSchema, response: { diff --git a/apps/server/src/api/client/routes/accounts/email-password-reset-init.ts b/apps/server/src/api/client/routes/auth/email-password-reset-init.ts similarity index 98% rename from apps/server/src/api/client/routes/accounts/email-password-reset-init.ts rename to apps/server/src/api/client/routes/auth/email-password-reset-init.ts index 3a1e8f7c..ff49228b 100644 --- a/apps/server/src/api/client/routes/accounts/email-password-reset-init.ts +++ b/apps/server/src/api/client/routes/auth/email-password-reset-init.ts @@ -27,7 +27,7 @@ export const emailPasswordResetInitRoute: FastifyPluginCallbackZod = ( ) => { instance.route({ method: 'POST', - url: '/emails/passwords/reset/init', + url: '/email/password-reset/init', schema: { body: emailPasswordResetInitInputSchema, response: { diff --git a/apps/server/src/api/client/routes/accounts/email-register.ts b/apps/server/src/api/client/routes/auth/email-register.ts similarity index 99% rename from apps/server/src/api/client/routes/accounts/email-register.ts rename to apps/server/src/api/client/routes/auth/email-register.ts index 37d4f71e..a928a339 100644 --- a/apps/server/src/api/client/routes/accounts/email-register.ts +++ b/apps/server/src/api/client/routes/auth/email-register.ts @@ -26,7 +26,7 @@ export const emailRegisterRoute: FastifyPluginCallbackZod = ( ) => { instance.route({ method: 'POST', - url: '/emails/register', + url: '/email/register', schema: { body: emailRegisterInputSchema, response: { diff --git a/apps/server/src/api/client/routes/accounts/email-verify.ts b/apps/server/src/api/client/routes/auth/email-verify.ts similarity index 98% rename from apps/server/src/api/client/routes/accounts/email-verify.ts rename to apps/server/src/api/client/routes/auth/email-verify.ts index 83e43501..487a0413 100644 --- a/apps/server/src/api/client/routes/accounts/email-verify.ts +++ b/apps/server/src/api/client/routes/auth/email-verify.ts @@ -20,7 +20,7 @@ export const emailVerifyRoute: FastifyPluginCallbackZod = ( ) => { instance.route({ method: 'POST', - url: '/emails/verify', + url: '/email/verify', schema: { body: emailVerifyInputSchema, response: { diff --git a/apps/server/src/api/client/routes/accounts/google-login.ts b/apps/server/src/api/client/routes/auth/google-login.ts similarity index 100% rename from apps/server/src/api/client/routes/accounts/google-login.ts rename to apps/server/src/api/client/routes/auth/google-login.ts diff --git a/apps/server/src/api/client/routes/auth/index.ts b/apps/server/src/api/client/routes/auth/index.ts new file mode 100644 index 00000000..321ae416 --- /dev/null +++ b/apps/server/src/api/client/routes/auth/index.ts @@ -0,0 +1,32 @@ +import { FastifyPluginCallback } from 'fastify'; + +import { accountAuthenticator } from '@colanode/server/api/client/plugins/account-auth'; +import { authIpRateLimiter } from '@colanode/server/api/client/plugins/auth-ip-rate-limit'; + +import { emailLoginRoute } from './email-login'; +import { emailPasswordResetCompleteRoute } from './email-password-reset-complete'; +import { emailPasswordResetInitRoute } from './email-password-reset-init'; +import { emailRegisterRoute } from './email-register'; +import { emailVerifyRoute } from './email-verify'; +import { googleLoginRoute } from './google-login'; +import { logoutRoute } from './logout'; + +export const authRoutes: FastifyPluginCallback = (instance, _, done) => { + instance.register((subInstance) => { + subInstance.register(authIpRateLimiter); + + subInstance.register(emailLoginRoute); + subInstance.register(emailRegisterRoute); + subInstance.register(emailVerifyRoute); + subInstance.register(emailPasswordResetInitRoute); + subInstance.register(emailPasswordResetCompleteRoute); + subInstance.register(googleLoginRoute); + }); + + instance.register((subInstance) => { + subInstance.register(accountAuthenticator); + subInstance.register(logoutRoute); + }); + + done(); +}; diff --git a/apps/server/src/api/client/routes/accounts/logout.ts b/apps/server/src/api/client/routes/auth/logout.ts similarity index 100% rename from apps/server/src/api/client/routes/accounts/logout.ts rename to apps/server/src/api/client/routes/auth/logout.ts diff --git a/apps/server/src/api/client/routes/index.ts b/apps/server/src/api/client/routes/index.ts index ccb09132..b769cbf4 100644 --- a/apps/server/src/api/client/routes/index.ts +++ b/apps/server/src/api/client/routes/index.ts @@ -1,6 +1,7 @@ import { FastifyPluginCallback } from 'fastify'; import { accountRoutes } from '@colanode/server/api/client/routes/accounts'; +import { authRoutes } from '@colanode/server/api/client/routes/auth'; import { avatarRoutes } from '@colanode/server/api/client/routes/avatars'; import { socketRoutes } from '@colanode/server/api/client/routes/sockets'; import { workspaceRoutes } from '@colanode/server/api/client/routes/workspaces'; @@ -8,6 +9,7 @@ import { workspaceRoutes } from '@colanode/server/api/client/routes/workspaces'; export const clientRoutes: FastifyPluginCallback = (instance, _, done) => { instance.register(socketRoutes, { prefix: '/sockets' }); instance.register(accountRoutes, { prefix: '/accounts' }); + instance.register(authRoutes, { prefix: '/auth' }); instance.register(avatarRoutes, { prefix: '/avatars' }); instance.register(workspaceRoutes, { prefix: '/workspaces' }); diff --git a/assets/fonts/antonio.ttf b/assets/fonts/antonio.ttf new file mode 100644 index 0000000000000000000000000000000000000000..581a3f4aa6b2f9957e2436a3cd360cd8f95ea93c GIT binary patch literal 73780 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

@pzl$>v2+!caOtzyn5{R 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 literal 0 HcmV?d00001 diff --git a/assets/fonts/neotrax.otf b/assets/fonts/neotrax.otf deleted file mode 100644 index fb2c0fb721e3c18d0c9839e463afcc0312cead64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36508 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ˍBtdqZY63+qWu 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 literal 0 HcmV?d00001 diff --git a/packages/client/src/handlers/mutations/accounts/account-update.ts b/packages/client/src/handlers/mutations/accounts/account-update.ts index c36bdf86..84a5d935 100644 --- a/packages/client/src/handlers/mutations/accounts/account-update.ts +++ b/packages/client/src/handlers/mutations/accounts/account-update.ts @@ -38,7 +38,7 @@ export class AccountUpdateMutationHandler }; const response = await accountService.client - .patch(`v1/accounts`, { + .patch(`v1/accounts/me`, { json: body, }) .json(); diff --git a/packages/client/src/handlers/mutations/accounts/base.ts b/packages/client/src/handlers/mutations/auth/base.ts similarity index 98% rename from packages/client/src/handlers/mutations/accounts/base.ts rename to packages/client/src/handlers/mutations/auth/base.ts index c1009e50..f836c99c 100644 --- a/packages/client/src/handlers/mutations/accounts/base.ts +++ b/packages/client/src/handlers/mutations/auth/base.ts @@ -5,7 +5,7 @@ import { AppService } from '@colanode/client/services/app-service'; import { ServerService } from '@colanode/client/services/server-service'; import { LoginSuccessOutput } from '@colanode/core'; -export abstract class AccountMutationHandlerBase { +export abstract class AuthMutationHandlerBase { protected readonly app: AppService; constructor(app: AppService) { diff --git a/packages/client/src/handlers/mutations/accounts/email-login.ts b/packages/client/src/handlers/mutations/auth/email-login.ts similarity index 86% rename from packages/client/src/handlers/mutations/accounts/email-login.ts rename to packages/client/src/handlers/mutations/auth/email-login.ts index 2fcaedee..d29aef49 100644 --- a/packages/client/src/handlers/mutations/accounts/email-login.ts +++ b/packages/client/src/handlers/mutations/auth/email-login.ts @@ -1,13 +1,13 @@ -import { AccountMutationHandlerBase } from '@colanode/client/handlers/mutations/accounts/base'; +import { AuthMutationHandlerBase } from '@colanode/client/handlers/mutations/auth/base'; import { parseApiError } from '@colanode/client/lib/ky'; import { MutationHandler } from '@colanode/client/lib/types'; import { MutationError, MutationErrorCode } from '@colanode/client/mutations'; -import { EmailLoginMutationInput } from '@colanode/client/mutations/accounts/email-login'; +import { EmailLoginMutationInput } from '@colanode/client/mutations/auth/email-login'; import { AppService } from '@colanode/client/services/app-service'; import { EmailLoginInput, LoginOutput } from '@colanode/core'; export class EmailLoginMutationHandler - extends AccountMutationHandlerBase + extends AuthMutationHandlerBase implements MutationHandler { constructor(appService: AppService) { @@ -31,7 +31,7 @@ export class EmailLoginMutationHandler }; const response = await this.app.client - .post(`${server.httpBaseUrl}/v1/accounts/emails/login`, { + .post(`${server.httpBaseUrl}/v1/auth/email/login`, { json: body, }) .json(); diff --git a/packages/client/src/handlers/mutations/accounts/email-password-reset-complete.ts b/packages/client/src/handlers/mutations/auth/email-password-reset-complete.ts similarity index 80% rename from packages/client/src/handlers/mutations/accounts/email-password-reset-complete.ts rename to packages/client/src/handlers/mutations/auth/email-password-reset-complete.ts index bca9d70b..e8559d3c 100644 --- a/packages/client/src/handlers/mutations/accounts/email-password-reset-complete.ts +++ b/packages/client/src/handlers/mutations/auth/email-password-reset-complete.ts @@ -1,11 +1,11 @@ -import { AccountMutationHandlerBase } from '@colanode/client/handlers/mutations/accounts/base'; +import { AuthMutationHandlerBase } from '@colanode/client/handlers/mutations/auth/base'; import { MutationHandler } from '@colanode/client/lib'; import { parseApiError } from '@colanode/client/lib/ky'; import { MutationError, MutationErrorCode } from '@colanode/client/mutations'; import { EmailPasswordResetCompleteMutationInput, EmailPasswordResetCompleteMutationOutput, -} from '@colanode/client/mutations/accounts/email-password-reset-complete'; +} from '@colanode/client/mutations/auth/email-password-reset-complete'; import { AppService } from '@colanode/client/services/app-service'; import { EmailPasswordResetCompleteInput, @@ -13,7 +13,7 @@ import { } from '@colanode/core'; export class EmailPasswordResetCompleteMutationHandler - extends AccountMutationHandlerBase + extends AuthMutationHandlerBase implements MutationHandler { constructor(appService: AppService) { @@ -40,12 +40,9 @@ export class EmailPasswordResetCompleteMutationHandler }; const response = await this.app.client - .post( - `${server.httpBaseUrl}/v1/accounts/emails/passwords/reset/complete`, - { - json: body, - } - ) + .post(`${server.httpBaseUrl}/v1/auth/email/password-reset/complete`, { + json: body, + }) .json(); return response; diff --git a/packages/client/src/handlers/mutations/accounts/email-password-reset-init.ts b/packages/client/src/handlers/mutations/auth/email-password-reset-init.ts similarity index 83% rename from packages/client/src/handlers/mutations/accounts/email-password-reset-init.ts rename to packages/client/src/handlers/mutations/auth/email-password-reset-init.ts index 551d9394..0d89061a 100644 --- a/packages/client/src/handlers/mutations/accounts/email-password-reset-init.ts +++ b/packages/client/src/handlers/mutations/auth/email-password-reset-init.ts @@ -1,11 +1,11 @@ -import { AccountMutationHandlerBase } from '@colanode/client/handlers/mutations/accounts/base'; +import { AuthMutationHandlerBase } from '@colanode/client/handlers/mutations/auth/base'; import { parseApiError } from '@colanode/client/lib/ky'; import { MutationHandler } from '@colanode/client/lib/types'; import { MutationError, MutationErrorCode } from '@colanode/client/mutations'; import { EmailPasswordResetInitMutationInput, EmailPasswordResetInitMutationOutput, -} from '@colanode/client/mutations/accounts/email-password-reset-init'; +} from '@colanode/client/mutations/auth/email-password-reset-init'; import { AppService } from '@colanode/client/services/app-service'; import { EmailPasswordResetInitInput, @@ -13,7 +13,7 @@ import { } from '@colanode/core'; export class EmailPasswordResetInitMutationHandler - extends AccountMutationHandlerBase + extends AuthMutationHandlerBase implements MutationHandler { constructor(appService: AppService) { @@ -38,7 +38,7 @@ export class EmailPasswordResetInitMutationHandler }; const response = await this.app.client - .post(`${server.httpBaseUrl}/v1/accounts/emails/passwords/reset/init`, { + .post(`${server.httpBaseUrl}/v1/auth/email/password-reset/init`, { json: body, }) .json(); diff --git a/packages/client/src/handlers/mutations/accounts/email-register.ts b/packages/client/src/handlers/mutations/auth/email-register.ts similarity index 86% rename from packages/client/src/handlers/mutations/accounts/email-register.ts rename to packages/client/src/handlers/mutations/auth/email-register.ts index 045a9938..55d92b25 100644 --- a/packages/client/src/handlers/mutations/accounts/email-register.ts +++ b/packages/client/src/handlers/mutations/auth/email-register.ts @@ -1,13 +1,13 @@ -import { AccountMutationHandlerBase } from '@colanode/client/handlers/mutations/accounts/base'; +import { AuthMutationHandlerBase } from '@colanode/client/handlers/mutations/auth/base'; import { parseApiError } from '@colanode/client/lib/ky'; import { MutationHandler } from '@colanode/client/lib/types'; import { MutationError, MutationErrorCode } from '@colanode/client/mutations'; -import { EmailRegisterMutationInput } from '@colanode/client/mutations/accounts/email-register'; +import { EmailRegisterMutationInput } from '@colanode/client/mutations/auth/email-register'; import { AppService } from '@colanode/client/services/app-service'; import { EmailRegisterInput, LoginOutput } from '@colanode/core'; export class EmailRegisterMutationHandler - extends AccountMutationHandlerBase + extends AuthMutationHandlerBase implements MutationHandler { constructor(appService: AppService) { @@ -34,7 +34,7 @@ export class EmailRegisterMutationHandler }; const response = await this.app.client - .post(`${server.httpBaseUrl}/v1/accounts/emails/register`, { + .post(`${server.httpBaseUrl}/v1/auth/email/register`, { json: body, }) .json(); diff --git a/packages/client/src/handlers/mutations/accounts/email-verify.ts b/packages/client/src/handlers/mutations/auth/email-verify.ts similarity index 87% rename from packages/client/src/handlers/mutations/accounts/email-verify.ts rename to packages/client/src/handlers/mutations/auth/email-verify.ts index ff8628f1..17f7539d 100644 --- a/packages/client/src/handlers/mutations/accounts/email-verify.ts +++ b/packages/client/src/handlers/mutations/auth/email-verify.ts @@ -1,13 +1,13 @@ -import { AccountMutationHandlerBase } from '@colanode/client/handlers/mutations/accounts/base'; +import { AuthMutationHandlerBase } from '@colanode/client/handlers/mutations/auth/base'; import { parseApiError } from '@colanode/client/lib/ky'; import { MutationHandler } from '@colanode/client/lib/types'; import { MutationError, MutationErrorCode } from '@colanode/client/mutations'; -import { EmailVerifyMutationInput } from '@colanode/client/mutations/accounts/email-verify'; +import { EmailVerifyMutationInput } from '@colanode/client/mutations/auth/email-verify'; import { AppService } from '@colanode/client/services/app-service'; import { EmailVerifyInput, LoginOutput } from '@colanode/core'; export class EmailVerifyMutationHandler - extends AccountMutationHandlerBase + extends AuthMutationHandlerBase implements MutationHandler { constructor(appService: AppService) { @@ -31,7 +31,7 @@ export class EmailVerifyMutationHandler }; const response = await this.app.client - .post(`${server.httpBaseUrl}/v1/accounts/emails/verify`, { + .post(`${server.httpBaseUrl}/v1/auth/email/verify`, { json: body, }) .json(); diff --git a/packages/client/src/handlers/mutations/accounts/google-login.ts b/packages/client/src/handlers/mutations/auth/google-login.ts similarity index 87% rename from packages/client/src/handlers/mutations/accounts/google-login.ts rename to packages/client/src/handlers/mutations/auth/google-login.ts index 2586f76c..20f0e48c 100644 --- a/packages/client/src/handlers/mutations/accounts/google-login.ts +++ b/packages/client/src/handlers/mutations/auth/google-login.ts @@ -1,4 +1,4 @@ -import { AccountMutationHandlerBase } from '@colanode/client/handlers/mutations/accounts/base'; +import { AuthMutationHandlerBase } from '@colanode/client/handlers/mutations/auth/base'; import { parseApiError } from '@colanode/client/lib/ky'; import { MutationHandler } from '@colanode/client/lib/types'; import { @@ -10,7 +10,7 @@ import { AppService } from '@colanode/client/services/app-service'; import { GoogleLoginInput, LoginOutput } from '@colanode/core'; export class GoogleLoginMutationHandler - extends AccountMutationHandlerBase + extends AuthMutationHandlerBase implements MutationHandler { constructor(appService: AppService) { @@ -33,7 +33,7 @@ export class GoogleLoginMutationHandler }; const response = await this.app.client - .post(`${server.httpBaseUrl}/v1/accounts/google/login`, { + .post(`${server.httpBaseUrl}/v1/auth/google/login`, { json: body, }) .json(); diff --git a/packages/client/src/handlers/mutations/index.ts b/packages/client/src/handlers/mutations/index.ts index f4d0b16c..a97decad 100644 --- a/packages/client/src/handlers/mutations/index.ts +++ b/packages/client/src/handlers/mutations/index.ts @@ -4,17 +4,17 @@ import { AppService } from '@colanode/client/services'; import { AccountLogoutMutationHandler } from './accounts/account-logout'; import { AccountUpdateMutationHandler } from './accounts/account-update'; -import { EmailLoginMutationHandler } from './accounts/email-login'; -import { EmailPasswordResetCompleteMutationHandler } from './accounts/email-password-reset-complete'; -import { EmailPasswordResetInitMutationHandler } from './accounts/email-password-reset-init'; -import { EmailRegisterMutationHandler } from './accounts/email-register'; -import { EmailVerifyMutationHandler } from './accounts/email-verify'; -import { GoogleLoginMutationHandler } from './accounts/google-login'; import { MetadataDeleteMutationHandler } from './apps/metadata-delete'; import { MetadataUpdateMutationHandler } from './apps/metadata-update'; import { TabCreateMutationHandler } from './apps/tab-create'; import { TabDeleteMutationHandler } from './apps/tab-delete'; import { TabUpdateMutationHandler } from './apps/tab-update'; +import { EmailLoginMutationHandler } from './auth/email-login'; +import { EmailPasswordResetCompleteMutationHandler } from './auth/email-password-reset-complete'; +import { EmailPasswordResetInitMutationHandler } from './auth/email-password-reset-init'; +import { EmailRegisterMutationHandler } from './auth/email-register'; +import { EmailVerifyMutationHandler } from './auth/email-verify'; +import { GoogleLoginMutationHandler } from './auth/google-login'; import { AvatarUploadMutationHandler } from './avatars/avatar-upload'; import { ChannelCreateMutationHandler } from './channels/channel-create'; import { ChannelDeleteMutationHandler } from './channels/channel-delete'; diff --git a/packages/client/src/jobs/token-delete.ts b/packages/client/src/jobs/token-delete.ts index ac2079e7..27ac4b0e 100644 --- a/packages/client/src/jobs/token-delete.ts +++ b/packages/client/src/jobs/token-delete.ts @@ -52,7 +52,7 @@ export class TokenDeleteJobHandler implements JobHandler { } try { - await this.app.client.delete(`${server.httpBaseUrl}/v1/accounts/logout`, { + await this.app.client.delete(`${server.httpBaseUrl}/v1/auth/logout`, { headers: { Authorization: `Bearer ${input.token}`, }, diff --git a/packages/client/src/mutations/accounts/email-login.ts b/packages/client/src/mutations/auth/email-login.ts similarity index 100% rename from packages/client/src/mutations/accounts/email-login.ts rename to packages/client/src/mutations/auth/email-login.ts diff --git a/packages/client/src/mutations/accounts/email-password-reset-complete.ts b/packages/client/src/mutations/auth/email-password-reset-complete.ts similarity index 100% rename from packages/client/src/mutations/accounts/email-password-reset-complete.ts rename to packages/client/src/mutations/auth/email-password-reset-complete.ts diff --git a/packages/client/src/mutations/accounts/email-password-reset-init.ts b/packages/client/src/mutations/auth/email-password-reset-init.ts similarity index 100% rename from packages/client/src/mutations/accounts/email-password-reset-init.ts rename to packages/client/src/mutations/auth/email-password-reset-init.ts diff --git a/packages/client/src/mutations/accounts/email-register.ts b/packages/client/src/mutations/auth/email-register.ts similarity index 100% rename from packages/client/src/mutations/accounts/email-register.ts rename to packages/client/src/mutations/auth/email-register.ts diff --git a/packages/client/src/mutations/accounts/email-verify.ts b/packages/client/src/mutations/auth/email-verify.ts similarity index 100% rename from packages/client/src/mutations/accounts/email-verify.ts rename to packages/client/src/mutations/auth/email-verify.ts diff --git a/packages/client/src/mutations/accounts/google-login.ts b/packages/client/src/mutations/auth/google-login.ts similarity index 100% rename from packages/client/src/mutations/accounts/google-login.ts rename to packages/client/src/mutations/auth/google-login.ts diff --git a/packages/client/src/mutations/index.ts b/packages/client/src/mutations/index.ts index b09631a4..e43f6427 100644 --- a/packages/client/src/mutations/index.ts +++ b/packages/client/src/mutations/index.ts @@ -1,11 +1,11 @@ export * from './accounts/account-logout'; export * from './accounts/account-update'; -export * from './accounts/email-login'; -export * from './accounts/email-password-reset-complete'; -export * from './accounts/email-password-reset-init'; -export * from './accounts/email-register'; -export * from './accounts/email-verify'; -export * from './accounts/google-login'; +export * from './auth/email-login'; +export * from './auth/email-password-reset-complete'; +export * from './auth/email-password-reset-init'; +export * from './auth/email-register'; +export * from './auth/email-verify'; +export * from './auth/google-login'; export * from './apps/metadata-delete'; export * from './apps/metadata-update'; export * from './avatars/avatar-upload'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 13eb405c..a64cb4c0 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,3 +38,4 @@ export * from './types/avatars'; export * from './types/build'; export * from './lib/servers'; export * from './types/storage'; +export * from './types/auth'; diff --git a/packages/core/src/types/accounts.ts b/packages/core/src/types/accounts.ts index adb7ee17..96f0b242 100644 --- a/packages/core/src/types/accounts.ts +++ b/packages/core/src/types/accounts.ts @@ -1,7 +1,5 @@ import { z } from 'zod/v4'; -import { workspaceOutputSchema } from '@colanode/core/types/workspaces'; - export enum AccountStatus { Pending = 0, Active = 1, @@ -31,103 +29,3 @@ export const accountUpdateOutputSchema = z.object({ }); export type AccountUpdateOutput = z.infer; - -export const emailRegisterInputSchema = z.object({ - name: z.string({ error: 'Name is required' }), - email: z.string({ error: 'Email is required' }).email({ - message: 'Invalid email address', - }), - password: z.string({ error: 'Password is required' }), -}); - -export type EmailRegisterInput = z.infer; - -export const emailLoginInputSchema = z.object({ - email: z.string({ error: 'Email is required' }).email({ - message: 'Invalid email address', - }), - password: z.string({ error: 'Password is required' }), -}); - -export type EmailLoginInput = z.infer; - -export const loginSuccessOutputSchema = z.object({ - type: z.literal('success'), - account: accountOutputSchema, - workspaces: z.array(workspaceOutputSchema), - deviceId: z.string(), - token: z.string(), -}); - -export type LoginSuccessOutput = z.infer; - -export const loginVerifyOutputSchema = z.object({ - type: z.literal('verify'), - id: z.string(), - expiresAt: z.date(), -}); - -export type LoginVerifyOutput = z.infer; - -export const loginOutputSchema = z.discriminatedUnion('type', [ - loginSuccessOutputSchema, - loginVerifyOutputSchema, -]); - -export type LoginOutput = z.infer; - -export const accountSyncOutputSchema = z.object({ - account: accountOutputSchema, - workspaces: z.array(workspaceOutputSchema), - token: z.string().optional(), -}); - -export type AccountSyncOutput = z.infer; - -export const emailVerifyInputSchema = z.object({ - id: z.string(), - otp: z.string(), -}); - -export type EmailVerifyInput = z.infer; - -export const emailPasswordResetInitInputSchema = z.object({ - email: z.email(), -}); - -export type EmailPasswordResetInitInput = z.infer< - typeof emailPasswordResetInitInputSchema ->; - -export const emailPasswordResetCompleteInputSchema = z.object({ - id: z.string(), - otp: z.string(), - password: z.string(), -}); - -export type EmailPasswordResetCompleteInput = z.infer< - typeof emailPasswordResetCompleteInputSchema ->; - -export const emailPasswordResetInitOutputSchema = z.object({ - id: z.string(), - expiresAt: z.date(), -}); - -export type EmailPasswordResetInitOutput = z.infer< - typeof emailPasswordResetInitOutputSchema ->; - -export const emailPasswordResetCompleteOutputSchema = z.object({ - success: z.boolean(), -}); - -export type EmailPasswordResetCompleteOutput = z.infer< - typeof emailPasswordResetCompleteOutputSchema ->; - -export const googleLoginInputSchema = z.object({ - code: z.string(), -}); - -export type GoogleLoginInput = z.infer; diff --git a/packages/core/src/types/auth.ts b/packages/core/src/types/auth.ts new file mode 100644 index 00000000..906084a3 --- /dev/null +++ b/packages/core/src/types/auth.ts @@ -0,0 +1,104 @@ +import { z } from 'zod/v4'; + +import { accountOutputSchema } from '@colanode/core/types/accounts'; +import { workspaceOutputSchema } from '@colanode/core/types/workspaces'; + +export const emailRegisterInputSchema = z.object({ + name: z.string({ error: 'Name is required' }), + email: z.string({ error: 'Email is required' }).email({ + message: 'Invalid email address', + }), + password: z.string({ error: 'Password is required' }), +}); + +export type EmailRegisterInput = z.infer; + +export const emailLoginInputSchema = z.object({ + email: z.string({ error: 'Email is required' }).email({ + message: 'Invalid email address', + }), + password: z.string({ error: 'Password is required' }), +}); + +export type EmailLoginInput = z.infer; + +export const loginSuccessOutputSchema = z.object({ + type: z.literal('success'), + account: accountOutputSchema, + workspaces: z.array(workspaceOutputSchema), + deviceId: z.string(), + token: z.string(), +}); + +export type LoginSuccessOutput = z.infer; + +export const loginVerifyOutputSchema = z.object({ + type: z.literal('verify'), + id: z.string(), + expiresAt: z.date(), +}); + +export type LoginVerifyOutput = z.infer; + +export const loginOutputSchema = z.discriminatedUnion('type', [ + loginSuccessOutputSchema, + loginVerifyOutputSchema, +]); + +export type LoginOutput = z.infer; + +export const accountSyncOutputSchema = z.object({ + account: accountOutputSchema, + workspaces: z.array(workspaceOutputSchema), + token: z.string().optional(), +}); + +export type AccountSyncOutput = z.infer; + +export const emailVerifyInputSchema = z.object({ + id: z.string(), + otp: z.string(), +}); + +export type EmailVerifyInput = z.infer; + +export const emailPasswordResetInitInputSchema = z.object({ + email: z.email(), +}); + +export type EmailPasswordResetInitInput = z.infer< + typeof emailPasswordResetInitInputSchema +>; + +export const emailPasswordResetCompleteInputSchema = z.object({ + id: z.string(), + otp: z.string(), + password: z.string(), +}); + +export type EmailPasswordResetCompleteInput = z.infer< + typeof emailPasswordResetCompleteInputSchema +>; + +export const emailPasswordResetInitOutputSchema = z.object({ + id: z.string(), + expiresAt: z.date(), +}); + +export type EmailPasswordResetInitOutput = z.infer< + typeof emailPasswordResetInitOutputSchema +>; + +export const emailPasswordResetCompleteOutputSchema = z.object({ + success: z.boolean(), +}); + +export type EmailPasswordResetCompleteOutput = z.infer< + typeof emailPasswordResetCompleteOutputSchema +>; + +export const googleLoginInputSchema = z.object({ + code: z.string(), +}); + +export type GoogleLoginInput = z.infer; diff --git a/packages/ui/src/components/accounts/login-form.tsx b/packages/ui/src/components/accounts/login-form.tsx deleted file mode 100644 index 29d311f9..00000000 --- a/packages/ui/src/components/accounts/login-form.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import { useLiveQuery } from '@tanstack/react-db'; -import { useRouter } from '@tanstack/react-router'; -import { HouseIcon } from 'lucide-react'; -import { useState, Fragment, useEffect, useCallback } from 'react'; -import { match } from 'ts-pattern'; - -import { isFeatureSupported } from '@colanode/client/lib'; -import { LoginSuccessOutput } from '@colanode/core'; -import { collections } from '@colanode/ui/collections'; -import { EmailLogin } from '@colanode/ui/components/accounts/email-login'; -import { EmailPasswordResetComplete } from '@colanode/ui/components/accounts/email-password-reset-complete'; -import { EmailPasswordResetInit } from '@colanode/ui/components/accounts/email-password-reset-init'; -import { EmailRegister } from '@colanode/ui/components/accounts/email-register'; -import { EmailVerify } from '@colanode/ui/components/accounts/email-verify'; -import { ServerDropdown } from '@colanode/ui/components/servers/server-dropdown'; -import { Button } from '@colanode/ui/components/ui/button'; -import { Separator } from '@colanode/ui/components/ui/separator'; -import { ServerContext } from '@colanode/ui/contexts/server'; - -type LoginPanelState = { - type: 'login'; -}; - -type RegisterPanelState = { - type: 'register'; -}; - -type VerifyPanelState = { - type: 'verify'; - id: string; - expiresAt: Date; -}; - -type PasswordResetInitPanelState = { - type: 'password_reset_init'; -}; - -type PasswordResetCompletePanelState = { - type: 'password_reset_complete'; - id: string; - expiresAt: Date; -}; - -type PanelState = - | LoginPanelState - | RegisterPanelState - | VerifyPanelState - | PasswordResetInitPanelState - | PasswordResetCompletePanelState; - -export const LoginForm = () => { - const router = useRouter(); - const serversQuery = useLiveQuery((q) => - q.from({ servers: collections.servers }) - ); - const servers = serversQuery.data; - - const workspacesQuery = useLiveQuery((q) => - q.from({ workspaces: collections.workspaces }).select(({ workspaces }) => ({ - userId: workspaces.userId, - })) - ); - const workspaces = workspacesQuery.data; - - const [serverDomain, setServerDomain] = useState( - servers[0]?.domain ?? null - ); - - const [panel, setPanel] = useState({ - type: 'login', - }); - - useEffect(() => { - const serverExists = - serverDomain !== null && servers.some((s) => s.domain === serverDomain); - if (!serverExists && servers.length > 0) { - setServerDomain(servers[0]!.domain); - } - }, [serverDomain, servers]); - - const server = serverDomain - ? servers.find((s) => s.domain === serverDomain) - : null; - - const handleLoginSuccess = useCallback( - (output: LoginSuccessOutput) => { - const workspace = output.workspaces[0]; - if (workspace) { - router.navigate({ - to: '/workspace/$userId', - params: { userId: workspace.user.id }, - }); - } else { - router.navigate({ - to: '/create', - }); - } - }, - [router] - ); - - return ( -

- { - setServerDomain(serverDomain); - }} - servers={servers} - readonly={panel.type === 'verify'} - /> - {server && ( - { - return isFeatureSupported(feature, server.version); - }, - }} - > -
- {match(panel) - .with({ type: 'login' }, () => ( - { - if (output.type === 'success') { - handleLoginSuccess(output); - } else if (output.type === 'verify') { - setPanel({ - type: 'verify', - id: output.id, - expiresAt: new Date(output.expiresAt), - }); - } - }} - onForgotPassword={() => { - setPanel({ - type: 'password_reset_init', - }); - }} - onRegister={() => { - setPanel({ - type: 'register', - }); - }} - /> - )) - .with({ type: 'register' }, () => ( - { - if (output.type === 'success') { - handleLoginSuccess(output); - } else if (output.type === 'verify') { - setPanel({ - type: 'verify', - id: output.id, - expiresAt: new Date(output.expiresAt), - }); - } - }} - onLogin={() => { - setPanel({ - type: 'login', - }); - }} - /> - )) - .with({ type: 'verify' }, (p) => ( - { - if (output.type === 'success') { - handleLoginSuccess(output); - } - }} - onBack={() => { - setPanel({ - type: 'login', - }); - }} - /> - )) - .with({ type: 'password_reset_init' }, () => ( - { - setPanel({ - type: 'password_reset_complete', - id: output.id, - expiresAt: new Date(output.expiresAt), - }); - }} - onBack={() => { - setPanel({ - type: 'login', - }); - }} - /> - )) - .with({ type: 'password_reset_complete' }, (p) => ( - { - setPanel({ - type: 'login', - }); - }} - /> - )) - .exhaustive()} -
-
- )} - - {workspaces.length > 0 && ( - - - - - )} -
- ); -}; diff --git a/packages/ui/src/components/accounts/login-tab.tsx b/packages/ui/src/components/accounts/login-tab.tsx deleted file mode 100644 index 1ddde3ff..00000000 --- a/packages/ui/src/components/accounts/login-tab.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Tab } from '@colanode/ui/components/layouts/tabs/tab'; - -export const LoginTab = () => { - return ; -}; diff --git a/packages/ui/src/components/accounts/login.tsx b/packages/ui/src/components/accounts/login.tsx deleted file mode 100644 index ee6f33b4..00000000 --- a/packages/ui/src/components/accounts/login.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { LoginForm } from '@colanode/ui/components/accounts/login-form'; - -export const Login = () => { - return ( -
-
-

colanode

-
-
-
-
-

- Login to Colanode -

-

- Use one of the following methods to login -

-
- -
-
-
- ); -}; diff --git a/packages/ui/src/components/app/app-assets.tsx b/packages/ui/src/components/app/app-assets.tsx index e4255cd8..6eff7fbe 100644 --- a/packages/ui/src/components/app/app-assets.tsx +++ b/packages/ui/src/components/app/app-assets.tsx @@ -2,22 +2,41 @@ import { useApp } from '@colanode/ui/contexts/app'; export const AppAssets = () => { const app = useApp(); - const fontUrl = - app.type === 'web' - ? `/assets/fonts/neotrax.otf` - : `local://fonts/neotrax.otf`; + const fontPrefix = app.type === 'web' ? `/assets/fonts` : `local://fonts`; return ( ); diff --git a/packages/ui/src/components/app/app-loading.tsx b/packages/ui/src/components/app/app-loading.tsx index 35ace32f..f78f7da9 100644 --- a/packages/ui/src/components/app/app-loading.tsx +++ b/packages/ui/src/components/app/app-loading.tsx @@ -6,7 +6,7 @@ export const AppLoading = () => {
-

loading your workspaces

+

loading your workspaces

diff --git a/packages/ui/src/components/auth/auth-cancel.tsx b/packages/ui/src/components/auth/auth-cancel.tsx new file mode 100644 index 00000000..ceca4d96 --- /dev/null +++ b/packages/ui/src/components/auth/auth-cancel.tsx @@ -0,0 +1,44 @@ +import { useLiveQuery } from '@tanstack/react-db'; +import { useRouter } from '@tanstack/react-router'; +import { Home } from 'lucide-react'; + +import { collections } from '@colanode/ui/collections'; +import { Button } from '@colanode/ui/components/ui/button'; +import { getDefaultWorkspaceUserId } from '@colanode/ui/routes/utils'; + +export const AuthCancel = () => { + const router = useRouter(); + + const workspacesQuery = useLiveQuery((q) => + q.from({ workspaces: collections.workspaces }).select(({ workspaces }) => ({ + userId: workspaces.userId, + })) + ); + const workspaces = workspacesQuery.data; + + if (workspaces.length === 0) { + return null; + } + + return ( + + ); +}; diff --git a/packages/ui/src/components/auth/auth-layout.tsx b/packages/ui/src/components/auth/auth-layout.tsx new file mode 100644 index 00000000..a353bf5a --- /dev/null +++ b/packages/ui/src/components/auth/auth-layout.tsx @@ -0,0 +1,38 @@ +import { Outlet } from '@tanstack/react-router'; +import { useState } from 'react'; + +import { Server } from '@colanode/client/types'; +import { AuthCancel } from '@colanode/ui/components/auth/auth-cancel'; +import { AuthServer } from '@colanode/ui/components/auth/auth-server'; +import { ColanodeLogo } from '@colanode/ui/components/ui/logo'; +import { AuthContext } from '@colanode/ui/contexts/auth'; + +export const AuthLayout = () => { + const [server, setServer] = useState(null); + + return ( +
+ +
+
+
+ +

+ Your all-in-one
collaboration platform +

+
+
+ +
+ {server ? ( + + + + ) : ( + + )} +
+
+
+ ); +}; diff --git a/packages/ui/src/components/auth/auth-server.tsx b/packages/ui/src/components/auth/auth-server.tsx new file mode 100644 index 00000000..68e797ed --- /dev/null +++ b/packages/ui/src/components/auth/auth-server.tsx @@ -0,0 +1,114 @@ +import { useLiveQuery } from '@tanstack/react-db'; +import { PlusIcon, SettingsIcon } from 'lucide-react'; +import { useState } from 'react'; + +import { Server } from '@colanode/client/types'; +import { collections } from '@colanode/ui/collections'; +import { ServerAvatar } from '@colanode/ui/components/servers/server-avatar'; +import { ServerCreateDialog } from '@colanode/ui/components/servers/server-create-dialog'; +import { ServerDeleteDialog } from '@colanode/ui/components/servers/server-delete-dialog'; +import { ServerSettingsDialog } from '@colanode/ui/components/servers/server-settings-dialog'; + +interface AuthServerProps { + onSelect: (server: Server) => void; +} + +export const AuthServer = ({ onSelect }: AuthServerProps) => { + const [openCreate, setOpenCreate] = useState(false); + const [settingsDomain, setSettingsDomain] = useState(null); + const [deleteDomain, setDeleteDomain] = useState(null); + const serversQuery = useLiveQuery((q) => + q.from({ servers: collections.servers }) + ); + const servers = serversQuery.data ?? []; + const settingsServer = servers.find( + (server) => server.domain === settingsDomain + ); + const deleteServer = servers.find((server) => server.domain === deleteDomain); + + return ( +
+
+

+ {servers.length > 0 ? 'Select a server' : 'Add a server'} +

+

+ {servers.length > 0 + ? 'Choose the server you want to connect to' + : 'Add a server to get started'} +

+
+
+ {servers.map((server) => ( + + + ))} + +
+ {openCreate && ( + setOpenCreate(false)} /> + )} + {deleteServer && ( + { + if (!open) { + setDeleteDomain(null); + } + }} + /> + )} + {settingsServer && ( + { + if (!open) { + setSettingsDomain(null); + } + }} + onDelete={() => { + setSettingsDomain(null); + setDeleteDomain(settingsServer.domain); + }} + /> + )} +
+ ); +}; diff --git a/packages/ui/src/components/accounts/email-login.tsx b/packages/ui/src/components/auth/email-login-form.tsx similarity index 64% rename from packages/ui/src/components/accounts/email-login.tsx rename to packages/ui/src/components/auth/email-login-form.tsx index dda3b279..349961c6 100644 --- a/packages/ui/src/components/accounts/email-login.tsx +++ b/packages/ui/src/components/auth/email-login-form.tsx @@ -1,11 +1,9 @@ import { zodResolver } from '@hookform/resolvers/zod'; +import { useNavigate } from '@tanstack/react-router'; import { Mail } from 'lucide-react'; import { useForm } from 'react-hook-form'; -import { toast } from 'sonner'; import { z } from 'zod/v4'; -import { LoginOutput } from '@colanode/core'; -import { GoogleLogin } from '@colanode/ui/components/accounts/google-login'; import { Button } from '@colanode/ui/components/ui/button'; import { Form, @@ -17,28 +15,19 @@ import { import { Input } from '@colanode/ui/components/ui/input'; import { Label } from '@colanode/ui/components/ui/label'; import { Spinner } from '@colanode/ui/components/ui/spinner'; -import { useServer } from '@colanode/ui/contexts/server'; -import { useMutation } from '@colanode/ui/hooks/use-mutation'; const formSchema = z.object({ email: z.string().min(2).email(), password: z.string().min(8), }); -interface EmailLoginProps { - onSuccess: (output: LoginOutput) => void; - onForgotPassword: () => void; - onRegister: () => void; +interface LoginFormProps { + isPending: boolean; + onSubmit: (values: z.infer) => void; } -export const EmailLogin = ({ - onSuccess, - onForgotPassword, - onRegister, -}: EmailLoginProps) => { - const server = useServer(); - const { mutate, isPending } = useMutation(); - +export const LoginForm = ({ isPending, onSubmit }: LoginFormProps) => { + const navigate = useNavigate(); const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { @@ -47,26 +36,9 @@ export const EmailLogin = ({ }, }); - const handleSubmit = async (values: z.infer) => { - mutate({ - input: { - type: 'email.login', - email: values.email, - password: values.password, - server: server.domain, - }, - onSuccess(output) { - onSuccess(output); - }, - onError(error) { - toast.error(error.message); - }, - }); - }; - return (
- + Password

{ + navigate({ to: '/auth/reset' }); + }} > Forgot password?

@@ -123,15 +97,6 @@ export const EmailLogin = ({ )} Login - - ); diff --git a/packages/ui/src/components/accounts/email-password-reset-complete.tsx b/packages/ui/src/components/auth/email-password-reset-complete-form.tsx similarity index 62% rename from packages/ui/src/components/accounts/email-password-reset-complete.tsx rename to packages/ui/src/components/auth/email-password-reset-complete-form.tsx index db25964d..13c0856c 100644 --- a/packages/ui/src/components/accounts/email-password-reset-complete.tsx +++ b/packages/ui/src/components/auth/email-password-reset-complete-form.tsx @@ -1,8 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; -import { CheckCircle, Lock } from 'lucide-react'; -import { useState } from 'react'; +import { Lock } from 'lucide-react'; import { useForm } from 'react-hook-form'; -import { toast } from 'sonner'; import { z } from 'zod/v4'; import { Button } from '@colanode/ui/components/ui/button'; @@ -16,9 +14,7 @@ import { import { Input } from '@colanode/ui/components/ui/input'; import { Label } from '@colanode/ui/components/ui/label'; import { Spinner } from '@colanode/ui/components/ui/spinner'; -import { useServer } from '@colanode/ui/contexts/server'; import { useCountdown } from '@colanode/ui/hooks/use-countdown'; -import { useMutation } from '@colanode/ui/hooks/use-mutation'; const formSchema = z .object({ @@ -39,20 +35,17 @@ const formSchema = z path: ['confirmPassword'], // path of error }); -interface EmailPasswordResetCompleteProps { - id: string; +interface PasswordResetCompleteFormProps { expiresAt: Date; - onBack: () => void; + isPending: boolean; + onSubmit: (values: z.infer) => void; } -export const EmailPasswordResetComplete = ({ - id, +export const PasswordResetCompleteForm = ({ expiresAt, - onBack, -}: EmailPasswordResetCompleteProps) => { - const server = useServer(); - const { mutate, isPending } = useMutation(); - + isPending, + onSubmit, +}: PasswordResetCompleteFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { @@ -62,57 +55,8 @@ export const EmailPasswordResetComplete = ({ }, }); - const [showSuccess, setShowSuccess] = useState(false); const [remainingSeconds, formattedTime] = useCountdown(expiresAt); - const onSubmit = async (values: z.infer) => { - if (remainingSeconds <= 0) { - toast.error('Code has expired'); - return; - } - - mutate({ - input: { - type: 'email.password.reset.complete', - otp: values.otp, - password: values.password, - server: server.domain, - id: id, - }, - onSuccess() { - setShowSuccess(true); - }, - onError(error) { - toast.error(error.message); - }, - }); - }; - - if (showSuccess) { - return ( -
-
- -

- Your password has been reset. You can now login with your new - password. -

-

- You have been logged out of all devices. -

-
- -
- ); - } - return (
@@ -172,7 +116,7 @@ export const EmailPasswordResetComplete = ({ type="submit" variant="outline" className="w-full" - disabled={isPending} + disabled={isPending || remainingSeconds <= 0} > {isPending ? ( @@ -181,14 +125,6 @@ export const EmailPasswordResetComplete = ({ )} Reset password - ); diff --git a/packages/ui/src/components/accounts/email-password-reset-init.tsx b/packages/ui/src/components/auth/email-password-reset-init-form.tsx similarity index 56% rename from packages/ui/src/components/accounts/email-password-reset-init.tsx rename to packages/ui/src/components/auth/email-password-reset-init-form.tsx index b74b7056..25c4b06e 100644 --- a/packages/ui/src/components/accounts/email-password-reset-init.tsx +++ b/packages/ui/src/components/auth/email-password-reset-init-form.tsx @@ -1,10 +1,8 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { Mail } from 'lucide-react'; import { useForm } from 'react-hook-form'; -import { toast } from 'sonner'; import { z } from 'zod/v4'; -import { EmailPasswordResetInitOutput } from '@colanode/core'; import { Button } from '@colanode/ui/components/ui/button'; import { Form, @@ -16,25 +14,20 @@ import { import { Input } from '@colanode/ui/components/ui/input'; import { Label } from '@colanode/ui/components/ui/label'; import { Spinner } from '@colanode/ui/components/ui/spinner'; -import { useServer } from '@colanode/ui/contexts/server'; -import { useMutation } from '@colanode/ui/hooks/use-mutation'; const formSchema = z.object({ email: z.string().min(2).email(), }); -interface EmailPasswordResetInitProps { - onSuccess: (output: EmailPasswordResetInitOutput) => void; - onBack: () => void; +interface PasswordResetInitFormProps { + isPending: boolean; + onSubmit: (values: z.infer) => void; } -export const EmailPasswordResetInit = ({ - onSuccess, - onBack, -}: EmailPasswordResetInitProps) => { - const server = useServer(); - const { mutate, isPending } = useMutation(); - +export const PasswordResetInitForm = ({ + isPending, + onSubmit, +}: PasswordResetInitFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { @@ -42,25 +35,9 @@ export const EmailPasswordResetInit = ({ }, }); - const handleSubmit = async (values: z.infer) => { - mutate({ - input: { - type: 'email.password.reset.init', - email: values.email, - server: server.domain, - }, - onSuccess(output) { - onSuccess(output); - }, - onError(error) { - toast.error(error.message); - }, - }); - }; - return (
- + - ); diff --git a/packages/ui/src/components/accounts/email-register.tsx b/packages/ui/src/components/auth/email-register-form.tsx similarity index 75% rename from packages/ui/src/components/accounts/email-register.tsx rename to packages/ui/src/components/auth/email-register-form.tsx index 0f7f097f..8a573f08 100644 --- a/packages/ui/src/components/accounts/email-register.tsx +++ b/packages/ui/src/components/auth/email-register-form.tsx @@ -1,11 +1,8 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { Mail } from 'lucide-react'; import { useForm } from 'react-hook-form'; -import { toast } from 'sonner'; import { z } from 'zod/v4'; -import { LoginOutput } from '@colanode/core'; -import { GoogleLogin } from '@colanode/ui/components/accounts/google-login'; import { Button } from '@colanode/ui/components/ui/button'; import { Form, @@ -17,8 +14,6 @@ import { import { Input } from '@colanode/ui/components/ui/input'; import { Label } from '@colanode/ui/components/ui/label'; import { Spinner } from '@colanode/ui/components/ui/spinner'; -import { useServer } from '@colanode/ui/contexts/server'; -import { useMutation } from '@colanode/ui/hooks/use-mutation'; const formSchema = z .object({ @@ -40,15 +35,12 @@ const formSchema = z path: ['confirmPassword'], // path of error }); -interface EmailRegisterProps { - onSuccess: (output: LoginOutput) => void; - onLogin: () => void; +interface RegisterFormProps { + isPending: boolean; + onSubmit: (values: z.infer) => void; } -export const EmailRegister = ({ onSuccess, onLogin }: EmailRegisterProps) => { - const server = useServer(); - const { mutate, isPending } = useMutation(); - +export const RegisterForm = ({ isPending, onSubmit }: RegisterFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { @@ -59,24 +51,6 @@ export const EmailRegister = ({ onSuccess, onLogin }: EmailRegisterProps) => { }, }); - const onSubmit = async (values: z.infer) => { - mutate({ - input: { - type: 'email.register', - name: values.name, - email: values.email, - password: values.password, - server: server.domain, - }, - onSuccess(output) { - onSuccess(output); - }, - onError(error) { - toast.error(error.message); - }, - }); - }; - return (
@@ -159,15 +133,6 @@ export const EmailRegister = ({ onSuccess, onLogin }: EmailRegisterProps) => { )} Register - - ); diff --git a/packages/ui/src/components/accounts/email-verify.tsx b/packages/ui/src/components/auth/email-verify-form.tsx similarity index 63% rename from packages/ui/src/components/accounts/email-verify.tsx rename to packages/ui/src/components/auth/email-verify-form.tsx index 778e25a6..4be5b855 100644 --- a/packages/ui/src/components/accounts/email-verify.tsx +++ b/packages/ui/src/components/auth/email-verify-form.tsx @@ -1,10 +1,8 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { Mail } from 'lucide-react'; import { useForm } from 'react-hook-form'; -import { toast } from 'sonner'; import { z } from 'zod/v4'; -import { LoginOutput } from '@colanode/core'; import { Button } from '@colanode/ui/components/ui/button'; import { Form, @@ -16,30 +14,23 @@ import { import { Input } from '@colanode/ui/components/ui/input'; import { Label } from '@colanode/ui/components/ui/label'; import { Spinner } from '@colanode/ui/components/ui/spinner'; -import { useServer } from '@colanode/ui/contexts/server'; import { useCountdown } from '@colanode/ui/hooks/use-countdown'; -import { useMutation } from '@colanode/ui/hooks/use-mutation'; const formSchema = z.object({ otp: z.string().min(2), }); -interface EmailVerifyProps { - id: string; +interface EmailVerifyFormProps { expiresAt: Date; - onSuccess: (output: LoginOutput) => void; - onBack: () => void; + isPending: boolean; + onSubmit: (values: z.infer) => void; } -export const EmailVerify = ({ - id, +export const EmailVerifyForm = ({ expiresAt, - onSuccess, - onBack, -}: EmailVerifyProps) => { - const server = useServer(); - const { mutate, isPending } = useMutation(); - + isPending, + onSubmit, +}: EmailVerifyFormProps) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { @@ -49,31 +40,9 @@ export const EmailVerify = ({ const [remainingSeconds, formattedTime] = useCountdown(expiresAt); - const handleSubmit = async (values: z.infer) => { - if (remainingSeconds <= 0) { - toast.error('Code has expired'); - return; - } - - mutate({ - input: { - type: 'email.verify', - otp: values.otp, - server: server.domain, - id, - }, - onSuccess(output) { - onSuccess(output); - }, - onError(error) { - toast.error(error.message); - }, - }); - }; - return (
- + - ); diff --git a/packages/ui/src/components/accounts/google-login.tsx b/packages/ui/src/components/auth/google-login.tsx similarity index 54% rename from packages/ui/src/components/accounts/google-login.tsx rename to packages/ui/src/components/auth/google-login.tsx index ea1fe1ed..403573c7 100644 --- a/packages/ui/src/components/accounts/google-login.tsx +++ b/packages/ui/src/components/auth/google-login.tsx @@ -1,38 +1,25 @@ import { GoogleOAuthProvider, useGoogleLogin } from '@react-oauth/google'; -import { toast } from 'sonner'; -import { LoginOutput } from '@colanode/core'; import { Button } from '@colanode/ui/components/ui/button'; import { GoogleIcon } from '@colanode/ui/components/ui/icons'; import { Spinner } from '@colanode/ui/components/ui/spinner'; import { useApp } from '@colanode/ui/contexts/app'; -import { useServer } from '@colanode/ui/contexts/server'; -import { useMutation } from '@colanode/ui/hooks/use-mutation'; +import { useAuth } from '@colanode/ui/contexts/auth'; interface GoogleLoginProps { context: 'login' | 'register'; - onSuccess: (output: LoginOutput) => void; + onLogin: (code: string) => void; + isPending: boolean; } -const GoogleLoginButton = ({ context, onSuccess }: GoogleLoginProps) => { - const server = useServer(); - const { mutate, isPending } = useMutation(); - +const GoogleLoginButton = ({ + context, + onLogin, + isPending, +}: GoogleLoginProps) => { const login = useGoogleLogin({ onSuccess: async (response) => { - mutate({ - input: { - type: 'google.login', - code: response.code, - server: server.domain, - }, - onSuccess(output) { - onSuccess(output); - }, - onError(error) { - toast.error(error.message); - }, - }); + onLogin(response.code); }, flow: 'auth-code', }); @@ -55,15 +42,23 @@ const GoogleLoginButton = ({ context, onSuccess }: GoogleLoginProps) => { ); }; -export const GoogleLogin = ({ context, onSuccess }: GoogleLoginProps) => { +export const GoogleLogin = ({ + context, + onLogin, + isPending, +}: GoogleLoginProps) => { const app = useApp(); - const server = useServer(); - const config = server.attributes.account?.google; + const auth = useAuth(); + const config = auth.server.attributes.account?.google; if (app.type === 'web' && config && config.enabled && config.clientId) { return ( - + ); } diff --git a/packages/ui/src/components/auth/login-tab.tsx b/packages/ui/src/components/auth/login-tab.tsx new file mode 100644 index 00000000..3eb95c95 --- /dev/null +++ b/packages/ui/src/components/auth/login-tab.tsx @@ -0,0 +1,6 @@ +import { Tab } from '@colanode/ui/components/layouts/tabs/tab'; +import { defaultIcons } from '@colanode/ui/lib/assets'; + +export const LoginTab = () => { + return ; +}; diff --git a/packages/ui/src/components/auth/login.tsx b/packages/ui/src/components/auth/login.tsx new file mode 100644 index 00000000..921ace69 --- /dev/null +++ b/packages/ui/src/components/auth/login.tsx @@ -0,0 +1,198 @@ +import { useNavigate } from '@tanstack/react-router'; +import { useState } from 'react'; +import { toast } from 'sonner'; + +import { LoginOutput } from '@colanode/core'; +import { LoginForm } from '@colanode/ui/components/auth/email-login-form'; +import { EmailVerifyForm } from '@colanode/ui/components/auth/email-verify-form'; +import { GoogleLogin } from '@colanode/ui/components/auth/google-login'; +import { Button } from '@colanode/ui/components/ui/button'; +import { useAuth } from '@colanode/ui/contexts/auth'; +import { useMutation } from '@colanode/ui/hooks/use-mutation'; + +type LoginState = + | { + type: 'login'; + } + | { + type: 'verify'; + id: string; + expiresAt: Date; + }; + +export const Login = () => { + const navigate = useNavigate(); + const auth = useAuth(); + + const [state, setState] = useState({ type: 'login' }); + + const { mutate: mutateEmailLogin, isPending: isEmailLoginPending } = + useMutation(); + const { mutate: mutateEmailVerify, isPending: isEmailVerifyPending } = + useMutation(); + const { mutate: mutateGoogleLogin, isPending: isGoogleLoginPending } = + useMutation(); + + const handleLoginSubmit = async (values: { + email: string; + password: string; + }) => { + if (isEmailLoginPending) return; + if (isEmailVerifyPending || isGoogleLoginPending) return; + if (state.type !== 'login') return; + + mutateEmailLogin({ + input: { + type: 'email.login', + email: values.email, + password: values.password, + server: auth.server.domain, + }, + onSuccess(output) { + if (output.type === 'success') { + if (output.workspaces.length > 0) { + navigate({ + to: '/workspace/$userId', + params: { userId: output.workspaces[0]!.user.id }, + }); + } else { + navigate({ to: '/create' }); + } + } else if (output.type === 'verify') { + setState({ + type: 'verify', + id: output.id, + expiresAt: output.expiresAt, + }); + } + }, + onError(error) { + toast.error(error.message); + }, + }); + }; + + const handleVerifySubmit = async (values: { otp: string }) => { + if (isEmailVerifyPending) return; + if (isEmailLoginPending || isGoogleLoginPending) return; + if (state.type !== 'verify') return; + + mutateEmailVerify({ + input: { + type: 'email.verify', + otp: values.otp, + server: auth.server.domain, + id: state.id, + }, + onSuccess(output: LoginOutput) { + if (output.type === 'success') { + navigate({ + to: '/workspace/$userId', + params: { userId: output.workspaces[0]!.user.id }, + }); + } else if (output.type === 'verify') { + setState({ + type: 'verify', + id: output.id, + expiresAt: output.expiresAt, + }); + } + }, + onError(error) { + toast.error(error.message); + }, + }); + }; + + const handleGoogleLogin = async (code: string) => { + if (isGoogleLoginPending) return; + if (isEmailLoginPending || isEmailVerifyPending) return; + if (state.type !== 'login') return; + + mutateGoogleLogin({ + input: { type: 'google.login', code, server: auth.server.domain }, + onSuccess(output) { + if (output.type === 'success') { + if (output.workspaces.length > 0) { + navigate({ + to: '/workspace/$userId', + params: { userId: output.workspaces[0]!.user.id }, + }); + } else { + navigate({ to: '/create' }); + } + } else if (output.type === 'verify') { + setState({ + type: 'verify', + id: output.id, + expiresAt: output.expiresAt, + }); + } + }, + onError(error) { + toast.error(error.message); + }, + }); + }; + + return ( +
+
+

+ {state.type === 'login' + ? 'Login to your account' + : 'Verify your email'} +

+

+ {state.type === 'login' + ? 'Enter your email and password to login to your account' + : 'Enter the code sent to your email'} +

+
+
+ {state.type === 'login' && ( + <> + + + + + )} + {state.type === 'verify' && ( + <> + + + + )} +
+
+ ); +}; diff --git a/packages/ui/src/components/accounts/account-logout-container.tsx b/packages/ui/src/components/auth/logout-container.tsx similarity index 97% rename from packages/ui/src/components/accounts/account-logout-container.tsx rename to packages/ui/src/components/auth/logout-container.tsx index 90d1ad12..d94b9dae 100644 --- a/packages/ui/src/components/accounts/account-logout-container.tsx +++ b/packages/ui/src/components/auth/logout-container.tsx @@ -7,7 +7,7 @@ import { Spinner } from '@colanode/ui/components/ui/spinner'; import { useWorkspace } from '@colanode/ui/contexts/workspace'; import { useMutation } from '@colanode/ui/hooks/use-mutation'; -export const AccountLogoutContainer = () => { +export const LogoutContainer = () => { const workspace = useWorkspace(); const navigate = useNavigate(); const { mutate, isPending } = useMutation(); diff --git a/packages/ui/src/components/accounts/account-logout-header.tsx b/packages/ui/src/components/auth/logout-header.tsx similarity index 85% rename from packages/ui/src/components/accounts/account-logout-header.tsx rename to packages/ui/src/components/auth/logout-header.tsx index a1e937e4..50b1f92f 100644 --- a/packages/ui/src/components/accounts/account-logout-header.tsx +++ b/packages/ui/src/components/auth/logout-header.tsx @@ -1,7 +1,7 @@ import { BreadcrumbItem } from '@colanode/ui/components/layouts/containers/breadcrumb-item'; import { defaultIcons } from '@colanode/ui/lib/assets'; -export const AccountLogoutHeader = () => { +export const LogoutHeader = () => { return ( ); diff --git a/packages/ui/src/components/accounts/account-logout-tab.tsx b/packages/ui/src/components/auth/logout-tab.tsx similarity index 84% rename from packages/ui/src/components/accounts/account-logout-tab.tsx rename to packages/ui/src/components/auth/logout-tab.tsx index ad74bba6..ac50c08b 100644 --- a/packages/ui/src/components/accounts/account-logout-tab.tsx +++ b/packages/ui/src/components/auth/logout-tab.tsx @@ -1,6 +1,6 @@ import { TabItem } from '@colanode/ui/components/layouts/tabs/tab-item'; import { defaultIcons } from '@colanode/ui/lib/assets'; -export const AccountLogoutTab = () => { +export const LogoutTab = () => { return ; }; diff --git a/packages/ui/src/components/auth/register-tab.tsx b/packages/ui/src/components/auth/register-tab.tsx new file mode 100644 index 00000000..69545a55 --- /dev/null +++ b/packages/ui/src/components/auth/register-tab.tsx @@ -0,0 +1,6 @@ +import { Tab } from '@colanode/ui/components/layouts/tabs/tab'; +import { defaultIcons } from '@colanode/ui/lib/assets'; + +export const RegisterTab = () => { + return ; +}; diff --git a/packages/ui/src/components/auth/register.tsx b/packages/ui/src/components/auth/register.tsx new file mode 100644 index 00000000..85e182da --- /dev/null +++ b/packages/ui/src/components/auth/register.tsx @@ -0,0 +1,201 @@ +import { useNavigate } from '@tanstack/react-router'; +import { useState } from 'react'; +import { toast } from 'sonner'; + +import { LoginOutput } from '@colanode/core'; +import { RegisterForm } from '@colanode/ui/components/auth/email-register-form'; +import { EmailVerifyForm } from '@colanode/ui/components/auth/email-verify-form'; +import { GoogleLogin } from '@colanode/ui/components/auth/google-login'; +import { Button } from '@colanode/ui/components/ui/button'; +import { useAuth } from '@colanode/ui/contexts/auth'; +import { useMutation } from '@colanode/ui/hooks/use-mutation'; + +type RegisterState = + | { + type: 'register'; + } + | { + type: 'verify'; + id: string; + expiresAt: Date; + }; + +export const Register = () => { + const navigate = useNavigate(); + const auth = useAuth(); + + const [state, setState] = useState({ type: 'register' }); + + const { mutate: mutateEmailRegister, isPending: isEmailRegisterPending } = + useMutation(); + const { mutate: mutateEmailVerify, isPending: isEmailVerifyPending } = + useMutation(); + const { mutate: mutateGoogleRegister, isPending: isGoogleRegisterPending } = + useMutation(); + + const handleRegisterSubmit = async (values: { + name: string; + email: string; + password: string; + confirmPassword: string; + }) => { + if (isEmailRegisterPending) return; + if (isEmailVerifyPending || isGoogleRegisterPending) return; + if (state.type !== 'register') return; + + mutateEmailRegister({ + input: { + type: 'email.register', + name: values.name, + email: values.email, + password: values.password, + server: auth.server.domain, + }, + onSuccess(output) { + if (output.type === 'success') { + if (output.workspaces.length > 0) { + navigate({ + to: '/workspace/$userId', + params: { userId: output.workspaces[0]!.user.id }, + }); + } else { + navigate({ to: '/create' }); + } + } else if (output.type === 'verify') { + setState({ + type: 'verify', + id: output.id, + expiresAt: output.expiresAt, + }); + } + }, + onError(error) { + toast.error(error.message); + }, + }); + }; + + const handleVerifySubmit = async (values: { otp: string }) => { + if (isEmailVerifyPending) return; + if (isEmailRegisterPending || isGoogleRegisterPending) return; + if (state.type !== 'verify') return; + + mutateEmailVerify({ + input: { + type: 'email.verify', + otp: values.otp, + server: auth.server.domain, + id: state.id, + }, + onSuccess(output: LoginOutput) { + if (output.type === 'success') { + navigate({ + to: '/workspace/$userId', + params: { userId: output.workspaces[0]!.user.id }, + }); + } else if (output.type === 'verify') { + setState({ + type: 'verify', + id: output.id, + expiresAt: output.expiresAt, + }); + } + }, + onError(error) { + toast.error(error.message); + }, + }); + }; + + const handleGoogleRegister = async (code: string) => { + if (isGoogleRegisterPending) return; + if (isEmailRegisterPending || isEmailVerifyPending) return; + if (state.type !== 'register') return; + + mutateGoogleRegister({ + input: { type: 'google.login', code, server: auth.server.domain }, + onSuccess(output) { + if (output.type === 'success') { + if (output.workspaces.length > 0) { + navigate({ + to: '/workspace/$userId', + params: { userId: output.workspaces[0]!.user.id }, + }); + } else { + navigate({ to: '/create' }); + } + } else if (output.type === 'verify') { + setState({ + type: 'verify', + id: output.id, + expiresAt: output.expiresAt, + }); + } + }, + onError(error) { + toast.error(error.message); + }, + }); + }; + + return ( +
+
+

+ {state.type === 'register' + ? 'Create an account' + : 'Verify your email'} +

+

+ {state.type === 'register' + ? 'Sign up to get started with Colanode' + : 'Enter the code sent to your email'} +

+
+
+ {state.type === 'register' && ( + <> + + + + + )} + {state.type === 'verify' && ( + <> + + + + )} +
+
+ ); +}; diff --git a/packages/ui/src/components/auth/reset-tab.tsx b/packages/ui/src/components/auth/reset-tab.tsx new file mode 100644 index 00000000..cb82f559 --- /dev/null +++ b/packages/ui/src/components/auth/reset-tab.tsx @@ -0,0 +1,6 @@ +import { Tab } from '@colanode/ui/components/layouts/tabs/tab'; +import { defaultIcons } from '@colanode/ui/lib/assets'; + +export const ResetTab = () => { + return ; +}; diff --git a/packages/ui/src/components/auth/reset.tsx b/packages/ui/src/components/auth/reset.tsx new file mode 100644 index 00000000..8afe67f0 --- /dev/null +++ b/packages/ui/src/components/auth/reset.tsx @@ -0,0 +1,174 @@ +import { useNavigate } from '@tanstack/react-router'; +import { CheckCircle } from 'lucide-react'; +import { useState } from 'react'; +import { toast } from 'sonner'; + +import { EmailPasswordResetInitOutput } from '@colanode/core'; +import { PasswordResetCompleteForm } from '@colanode/ui/components/auth/email-password-reset-complete-form'; +import { PasswordResetInitForm } from '@colanode/ui/components/auth/email-password-reset-init-form'; +import { Button } from '@colanode/ui/components/ui/button'; +import { useAuth } from '@colanode/ui/contexts/auth'; +import { useMutation } from '@colanode/ui/hooks/use-mutation'; + +type ResetState = + | { + type: 'init'; + } + | { + type: 'complete'; + id: string; + expiresAt: Date; + } + | { + type: 'success'; + }; + +export const Reset = () => { + const navigate = useNavigate(); + const auth = useAuth(); + + const [state, setState] = useState({ type: 'init' }); + + const { + mutate: mutatePasswordResetInit, + isPending: isPasswordResetInitPending, + } = useMutation(); + const { + mutate: mutatePasswordResetComplete, + isPending: isPasswordResetCompletePending, + } = useMutation(); + + const handleInitSubmit = async (values: { email: string }) => { + if (isPasswordResetInitPending) return; + if (isPasswordResetCompletePending) return; + if (state.type !== 'init') return; + + mutatePasswordResetInit({ + input: { + type: 'email.password.reset.init', + email: values.email, + server: auth.server.domain, + }, + onSuccess(output: EmailPasswordResetInitOutput) { + setState({ + type: 'complete', + id: output.id, + expiresAt: output.expiresAt, + }); + }, + onError(error) { + toast.error(error.message); + }, + }); + }; + + const handleCompleteSubmit = async (values: { + otp: string; + password: string; + confirmPassword: string; + }) => { + if (isPasswordResetCompletePending) return; + if (isPasswordResetInitPending) return; + if (state.type !== 'complete') return; + + mutatePasswordResetComplete({ + input: { + type: 'email.password.reset.complete', + otp: values.otp, + password: values.password, + server: auth.server.domain, + id: state.id, + }, + onSuccess() { + setState({ type: 'success' }); + }, + onError(error) { + toast.error(error.message); + }, + }); + }; + + return ( +
+
+

+ {state.type === 'init' + ? 'Reset your password' + : state.type === 'complete' + ? 'Reset your password' + : 'Password reset successful'} +

+

+ {state.type === 'init' + ? 'Enter your email to receive a password reset code' + : state.type === 'complete' + ? 'Enter the code sent to your email and your new password' + : 'Your password has been reset. You can now login with your new password.'} +

+
+
+ {state.type === 'init' && ( + <> + + + + )} + {state.type === 'complete' && ( + <> + + + + )} + {state.type === 'success' && ( + <> +
+ +

+ Your password has been reset. You can now login with your new + password. +

+

+ You have been logged out of all devices. +

+
+ + + )} +
+
+ ); +}; diff --git a/packages/ui/src/components/layouts/sidebars/sidebar-menu-footer.tsx b/packages/ui/src/components/layouts/sidebars/sidebar-menu-footer.tsx index ef15fa4f..d3d78a62 100644 --- a/packages/ui/src/components/layouts/sidebars/sidebar-menu-footer.tsx +++ b/packages/ui/src/components/layouts/sidebars/sidebar-menu-footer.tsx @@ -120,7 +120,7 @@ export function SidebarMenuFooter() { { - navigate({ to: '/login' }); + navigate({ to: '/auth/login' }); }} > diff --git a/packages/ui/src/components/layouts/sidebars/sidebar-settings.tsx b/packages/ui/src/components/layouts/sidebars/sidebar-settings.tsx index cd81bc9b..281a65a0 100644 --- a/packages/ui/src/components/layouts/sidebars/sidebar-settings.tsx +++ b/packages/ui/src/components/layouts/sidebars/sidebar-settings.tsx @@ -121,7 +121,7 @@ export const SidebarSettings = () => {
- + {({ isActive }) => ( void; - onCreate: (server: Server) => void; } -export const ServerCreateDialog = ({ - onCancel, - onCreate, -}: ServerCreateDialogProps) => { +export const ServerCreateDialog = ({ onCancel }: ServerCreateDialogProps) => { const [open, setOpen] = useState(true); const { mutate, isPending } = useMutation(); const [url, setUrl] = useState(''); @@ -63,9 +58,8 @@ export const ServerCreateDialog = ({ type: 'server.create', url, }, - onSuccess(output) { - onCreate(output.server); - toast.success('Server added successfully'); + onSuccess() { + setOpen(false); }, onError(error) { toast.error(error.message); diff --git a/packages/ui/src/components/servers/server-dropdown.tsx b/packages/ui/src/components/servers/server-dropdown.tsx deleted file mode 100644 index aaa1ff20..00000000 --- a/packages/ui/src/components/servers/server-dropdown.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { - ChevronDown, - PlusIcon, - ServerOffIcon, - SettingsIcon, -} from 'lucide-react'; -import { Fragment, useState } from 'react'; - -import { Server } from '@colanode/client/types'; -import { ServerAvatar } from '@colanode/ui/components/servers/server-avatar'; -import { ServerCreateDialog } from '@colanode/ui/components/servers/server-create-dialog'; -import { ServerDeleteDialog } from '@colanode/ui/components/servers/server-delete-dialog'; -import { ServerSettingsDialog } from '@colanode/ui/components/servers/server-settings-dialog'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from '@colanode/ui/components/ui/dropdown-menu'; - -interface ServerDropdownProps { - value: string | null; - onChange: (server: string) => void; - servers: Server[]; - readonly?: boolean; -} - -export const ServerDropdown = ({ - value, - onChange, - servers, - readonly = false, -}: ServerDropdownProps) => { - const [open, setOpen] = useState(false); - const [openCreate, setOpenCreate] = useState(false); - const [settingsDomain, setSettingsDomain] = useState(null); - const [deleteDomain, setDeleteDomain] = useState(null); - - const server = value - ? servers.find((server) => server.domain === value) - : null; - const settingsServer = servers.find( - (server) => server.domain === settingsDomain - ); - const deleteServer = servers.find((server) => server.domain === deleteDomain); - - return ( - - { - if (!readonly) { - setOpen(openValue); - } - }} - > - -
- {server ? ( - - ) : ( - - )} -
- {server ? ( - -

{server.name}

-

- {server.domain} -

-
- ) : ( -

Select a server

- )} -
- -
-
- - {servers.map((server) => ( - { - if (value !== server.domain) { - onChange(server.domain); - } - }} - className="group/server flex w-full grow flex-row items-center gap-3 p-2 cursor-pointer hover:bg-accent" - > -
- -
-

{server.name}

-

- {server.domain} -

-
-
- -
- ))} - - { - setOpenCreate(true); - }} - className="py-2" - > - - Add new server - -
-
- {openCreate && ( - setOpenCreate(false)} - onCreate={() => { - setOpenCreate(false); - }} - /> - )} - {deleteServer && ( - { - if (!open) { - setDeleteDomain(null); - } - }} - /> - )} - {settingsServer && ( - { - if (!open) { - setSettingsDomain(null); - } - }} - onDelete={() => { - setSettingsDomain(null); - setDeleteDomain(settingsServer.domain); - }} - /> - )} -
- ); -}; diff --git a/packages/ui/src/components/servers/server-settings-dialog.tsx b/packages/ui/src/components/servers/server-settings-dialog.tsx index fdfd1e04..af2af448 100644 --- a/packages/ui/src/components/servers/server-settings-dialog.tsx +++ b/packages/ui/src/components/servers/server-settings-dialog.tsx @@ -175,16 +175,23 @@ export const ServerSettingsDialog = ({ {canDelete && (
-

Delete server from this device

- +
+

+ Delete server from this device +

+
+ +
+
)} diff --git a/packages/ui/src/components/ui/logo.tsx b/packages/ui/src/components/ui/logo.tsx new file mode 100644 index 00000000..445abf47 --- /dev/null +++ b/packages/ui/src/components/ui/logo.tsx @@ -0,0 +1,25 @@ +type ColanodeLogoProps = React.HTMLAttributes; + +export const ColanodeLogo = (props: ColanodeLogoProps) => { + return ( + + + + + ); +}; diff --git a/packages/ui/src/contexts/auth.ts b/packages/ui/src/contexts/auth.ts new file mode 100644 index 00000000..8238079d --- /dev/null +++ b/packages/ui/src/contexts/auth.ts @@ -0,0 +1,17 @@ +import { createContext, useContext } from 'react'; + +import { Server } from '@colanode/client/types'; + +export interface AuthContextValue { + server: Server; +} + +export const AuthContext = createContext(null); + +export const useAuth = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within AuthLayout'); + } + return context; +}; diff --git a/packages/ui/src/lib/assets.ts b/packages/ui/src/lib/assets.ts index 951d17f2..e111ea05 100644 --- a/packages/ui/src/lib/assets.ts +++ b/packages/ui/src/lib/assets.ts @@ -12,6 +12,7 @@ export const defaultIcons = { bookmark: '01jhzfk3g4q40x7927qcm0hrjdic', folder: '01jhzfk3jrgc276z2gdabm4cwmic', apps: '01jhzfk4m7djqd1pw0e1671cmric', + login: '01jhzfk4ppvzdambh6hyw0mv91ic', logout: '01jhzfk4pv13qxjprqgqfeqp73ic', settings: '01jhzfk4ra4fvcay6qgrydgsf5ic', appearance: '01jhzfk39qxa7xtr7z69fyrb2pic', diff --git a/packages/ui/src/routes/auth/index.tsx b/packages/ui/src/routes/auth/index.tsx new file mode 100644 index 00000000..9553c6d8 --- /dev/null +++ b/packages/ui/src/routes/auth/index.tsx @@ -0,0 +1,10 @@ +import { createRoute } from '@tanstack/react-router'; + +import { AuthLayout } from '@colanode/ui/components/auth/auth-layout'; +import { rootRoute } from '@colanode/ui/routes/root'; + +export const authRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/auth', + component: AuthLayout, +}); diff --git a/packages/ui/src/routes/auth/login.tsx b/packages/ui/src/routes/auth/login.tsx new file mode 100644 index 00000000..4142e460 --- /dev/null +++ b/packages/ui/src/routes/auth/login.tsx @@ -0,0 +1,16 @@ +import { createRoute } from '@tanstack/react-router'; + +import { Login } from '@colanode/ui/components/auth/login'; +import { LoginTab } from '@colanode/ui/components/auth/login-tab'; +import { authRoute } from '@colanode/ui/routes/auth'; + +export const loginRoute = createRoute({ + getParentRoute: () => authRoute, + path: '/login', + component: Login, + context: () => { + return { + tab: , + }; + }, +}); diff --git a/packages/ui/src/routes/auth/register.tsx b/packages/ui/src/routes/auth/register.tsx new file mode 100644 index 00000000..77b84ebd --- /dev/null +++ b/packages/ui/src/routes/auth/register.tsx @@ -0,0 +1,16 @@ +import { createRoute } from '@tanstack/react-router'; + +import { Register } from '@colanode/ui/components/auth/register'; +import { RegisterTab } from '@colanode/ui/components/auth/register-tab'; +import { authRoute } from '@colanode/ui/routes/auth'; + +export const registerRoute = createRoute({ + getParentRoute: () => authRoute, + path: '/register', + component: Register, + context: () => { + return { + tab: , + }; + }, +}); diff --git a/packages/ui/src/routes/auth/reset.tsx b/packages/ui/src/routes/auth/reset.tsx new file mode 100644 index 00000000..b3483f06 --- /dev/null +++ b/packages/ui/src/routes/auth/reset.tsx @@ -0,0 +1,23 @@ +import { createRoute } from '@tanstack/react-router'; +import { z } from 'zod/v4'; + +import { Reset } from '@colanode/ui/components/auth/reset'; +import { ResetTab } from '@colanode/ui/components/auth/reset-tab'; +import { authRoute } from '@colanode/ui/routes/auth'; + +const resetSearchSchema = z.object({ + id: z.string().optional(), + expiresAt: z.string().optional(), +}); + +export const resetRoute = createRoute({ + getParentRoute: () => authRoute, + path: '/reset', + component: Reset, + validateSearch: resetSearchSchema, + context: () => { + return { + tab: , + }; + }, +}); diff --git a/packages/ui/src/routes/home.tsx b/packages/ui/src/routes/home.tsx index a67864dd..09157125 100644 --- a/packages/ui/src/routes/home.tsx +++ b/packages/ui/src/routes/home.tsx @@ -17,6 +17,6 @@ export const homeRoute = createRoute({ }); } - throw redirect({ to: '/login', replace: true }); + throw redirect({ to: '/auth/login', replace: true }); }, }); diff --git a/packages/ui/src/routes/index.tsx b/packages/ui/src/routes/index.tsx index ccfc3cc7..ac000609 100644 --- a/packages/ui/src/routes/index.tsx +++ b/packages/ui/src/routes/index.tsx @@ -1,17 +1,16 @@ import { createRouter } from '@tanstack/react-router'; +import { authRoute } from '@colanode/ui/routes/auth'; +import { loginRoute } from '@colanode/ui/routes/auth/login'; +import { registerRoute } from '@colanode/ui/routes/auth/register'; +import { resetRoute } from '@colanode/ui/routes/auth/reset'; import { workspaceCreateRoute } from '@colanode/ui/routes/create'; import { homeRoute } from '@colanode/ui/routes/home'; -import { loginRoute } from '@colanode/ui/routes/login'; import { rootRoute } from '@colanode/ui/routes/root'; import { workspaceRoute, workspaceMaskRoute, } from '@colanode/ui/routes/workspace'; -import { - accountLogoutMaskRoute, - accountLogoutRoute, -} from '@colanode/ui/routes/workspace/account-logout'; import { accountSettingsMaskRoute, accountSettingsRoute, @@ -28,6 +27,10 @@ import { workspaceHomeMaskRoute, workspaceHomeRoute, } from '@colanode/ui/routes/workspace/home'; +import { + logoutMaskRoute, + logoutRoute, +} from '@colanode/ui/routes/workspace/logout'; import { nodeMaskRoute, nodeRoute } from '@colanode/ui/routes/workspace/node'; import { workspaceRedirectMaskRoute, @@ -52,7 +55,7 @@ import { export const routeTree = rootRoute.addChildren([ homeRoute, - loginRoute, + authRoute.addChildren([loginRoute, registerRoute, resetRoute]), workspaceCreateRoute, workspaceRoute.addChildren([ workspaceRedirectRoute, @@ -64,7 +67,7 @@ export const routeTree = rootRoute.addChildren([ workspaceUsersRoute, workspaceSettingsRoute, accountSettingsRoute, - accountLogoutRoute, + logoutRoute, appAppearanceRoute, ]), workspaceMaskRoute.addChildren([ @@ -77,7 +80,7 @@ export const routeTree = rootRoute.addChildren([ workspaceUploadsMaskRoute, workspaceDownloadsMaskRoute, accountSettingsMaskRoute, - accountLogoutMaskRoute, + logoutMaskRoute, appAppearanceMaskRoute, ]), ]); diff --git a/packages/ui/src/routes/login.tsx b/packages/ui/src/routes/login.tsx deleted file mode 100644 index 5faefb48..00000000 --- a/packages/ui/src/routes/login.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { createRoute } from '@tanstack/react-router'; - -import { Login } from '@colanode/ui/components/accounts/login'; -import { LoginTab } from '@colanode/ui/components/accounts/login-tab'; -import { rootRoute } from '@colanode/ui/routes/root'; - -export const loginRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/login', - component: Login, - context: () => { - return { - tab: , - }; - }, -}); diff --git a/packages/ui/src/routes/masks.tsx b/packages/ui/src/routes/masks.tsx index bccf7dd8..c3a3a354 100644 --- a/packages/ui/src/routes/masks.tsx +++ b/packages/ui/src/routes/masks.tsx @@ -113,8 +113,8 @@ export const accountSettingsRouteMask = createRouteMask({ export const accountLogoutRouteMask = createRouteMask({ routeTree: routeTree, - from: '/workspace/$userId/account/logout', - to: '/$workspaceId/account/logout', + from: '/workspace/$userId/logout', + to: '/$workspaceId/logout', params: (ctx) => { const workspace = collections.workspaces.get(ctx.userId); return { diff --git a/packages/ui/src/routes/workspace/account-logout.tsx b/packages/ui/src/routes/workspace/account-logout.tsx deleted file mode 100644 index 6bbeea42..00000000 --- a/packages/ui/src/routes/workspace/account-logout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { createRoute, redirect } from '@tanstack/react-router'; - -import { AccountLogoutContainer } from '@colanode/ui/components/accounts/account-logout-container'; -import { AccountLogoutHeader } from '@colanode/ui/components/accounts/account-logout-header'; -import { AccountLogoutTab } from '@colanode/ui/components/accounts/account-logout-tab'; -import { getWorkspaceUserId } from '@colanode/ui/routes/utils'; -import { - workspaceRoute, - workspaceMaskRoute, -} from '@colanode/ui/routes/workspace'; - -export const accountLogoutRoute = createRoute({ - getParentRoute: () => workspaceRoute, - path: '/account/logout', - component: AccountLogoutContainer, - context: () => { - return { - tab: , - header: , - }; - }, -}); - -export const accountLogoutMaskRoute = createRoute({ - getParentRoute: () => workspaceMaskRoute, - path: '/account/logout', - component: () => null, - beforeLoad: (ctx) => { - const userId = getWorkspaceUserId(ctx.params.workspaceId); - if (userId) { - throw redirect({ - to: '/workspace/$userId/account/logout', - params: { userId }, - replace: true, - }); - } - }, -}); diff --git a/packages/ui/src/routes/workspace/logout.tsx b/packages/ui/src/routes/workspace/logout.tsx new file mode 100644 index 00000000..6720df95 --- /dev/null +++ b/packages/ui/src/routes/workspace/logout.tsx @@ -0,0 +1,38 @@ +import { createRoute, redirect } from '@tanstack/react-router'; + +import { LogoutContainer } from '@colanode/ui/components/auth/logout-container'; +import { LogoutHeader } from '@colanode/ui/components/auth/logout-header'; +import { LogoutTab } from '@colanode/ui/components/auth/logout-tab'; +import { getWorkspaceUserId } from '@colanode/ui/routes/utils'; +import { + workspaceRoute, + workspaceMaskRoute, +} from '@colanode/ui/routes/workspace'; + +export const logoutRoute = createRoute({ + getParentRoute: () => workspaceRoute, + path: '/logout', + component: LogoutContainer, + context: () => { + return { + tab: , + header: , + }; + }, +}); + +export const logoutMaskRoute = createRoute({ + getParentRoute: () => workspaceMaskRoute, + path: '/logout', + component: () => null, + beforeLoad: (ctx) => { + const userId = getWorkspaceUserId(ctx.params.workspaceId); + if (userId) { + throw redirect({ + to: '/workspace/$userId/logout', + params: { userId }, + replace: true, + }); + } + }, +}); diff --git a/scripts/src/postinstall/README.md b/scripts/src/postinstall/README.md index 407d2b8b..5b8855b4 100644 --- a/scripts/src/postinstall/README.md +++ b/scripts/src/postinstall/README.md @@ -10,14 +10,14 @@ The script copies different types of assets to different Colanode apps based on - **Full databases**: `emojis.db` and `icons.db` (with SVG BLOBs) for offline capability - **SVG sprites**: `emojis.svg` and `icons.svg` for efficient rendering -- **Fonts**: Custom fonts like `neotrax.otf` +- **Fonts**: Custom fonts like `satoshi-variable.woff2` - **Images**: Application logos and icons in various formats (.png, .ico, .icns) **Web Application (`apps/web/public/assets/`)**: - **Minimal databases**: `emojis.min.db` and `icons.min.db` (renamed to `emojis.db` and `icons.db`) without SVG BLOBs for faster loading - **SVG sprites**: `emojis.svg` and `icons.svg` for rendering (since SVG data isn't in the minimal databases) -- **Fonts**: Custom fonts like `neotrax.otf` +- **Fonts**: Custom fonts like `satoshi-variable.woff2` - **Images**: Web-specific logo files in PNG format ## Why This Approach? diff --git a/scripts/src/postinstall/index.ts b/scripts/src/postinstall/index.ts index f2a0a39c..0ae16c6b 100644 --- a/scripts/src/postinstall/index.ts +++ b/scripts/src/postinstall/index.ts @@ -14,9 +14,16 @@ const ICONS_DB_PATH = path.resolve(ICONS_DIR, 'icons.db'); const ICONS_MIN_DB_PATH = path.resolve(ICONS_DIR, 'icons.min.db'); const ICONS_SVG_PATH = path.resolve(ICONS_DIR, 'icons.svg'); -const NEOTRAX_FONT_NAME = 'neotrax.otf'; +const SATOSHI_FONT_NAME = 'satoshi-variable.woff2'; +const SATOSHI_ITALIC_FONT_NAME = 'satoshi-variable-italic.woff2'; +const ANTONIO_FONT_NAME = 'antonio.ttf'; const FONTS_DIR = path.resolve(ASSETS_DIR, 'fonts'); -const FONTS_OTF_PATH = path.resolve(FONTS_DIR, NEOTRAX_FONT_NAME); +const FONTS_SATOSHI_PATH = path.resolve(FONTS_DIR, SATOSHI_FONT_NAME); +const FONTS_SATOSHI_ITALIC_PATH = path.resolve( + FONTS_DIR, + SATOSHI_ITALIC_FONT_NAME +); +const FONTS_ANTONIO_PATH = path.resolve(FONTS_DIR, ANTONIO_FONT_NAME); const DESKTOP_ASSETS_DIR = path.resolve('apps', 'desktop', 'assets'); const WEB_PUBLIC_DIR = path.resolve('apps', 'web', 'public'); @@ -51,10 +58,22 @@ const execute = () => { copyFile(ICONS_MIN_DB_PATH, path.resolve(WEB_ASSETS_DIR, 'icons.db')); copyFile(ICONS_SVG_PATH, path.resolve(WEB_ASSETS_DIR, 'icons.svg')); - copyFile(FONTS_OTF_PATH, [ - path.resolve(DESKTOP_ASSETS_DIR, 'fonts', NEOTRAX_FONT_NAME), - path.resolve(WEB_ASSETS_DIR, 'fonts', NEOTRAX_FONT_NAME), - path.resolve(MOBILE_ASSETS_DIR, 'fonts', NEOTRAX_FONT_NAME), + copyFile(FONTS_SATOSHI_PATH, [ + path.resolve(DESKTOP_ASSETS_DIR, 'fonts', SATOSHI_FONT_NAME), + path.resolve(WEB_ASSETS_DIR, 'fonts', SATOSHI_FONT_NAME), + path.resolve(MOBILE_ASSETS_DIR, 'fonts', SATOSHI_FONT_NAME), + ]); + + copyFile(FONTS_SATOSHI_ITALIC_PATH, [ + path.resolve(DESKTOP_ASSETS_DIR, 'fonts', SATOSHI_ITALIC_FONT_NAME), + path.resolve(WEB_ASSETS_DIR, 'fonts', SATOSHI_ITALIC_FONT_NAME), + path.resolve(MOBILE_ASSETS_DIR, 'fonts', SATOSHI_ITALIC_FONT_NAME), + ]); + + copyFile(FONTS_ANTONIO_PATH, [ + path.resolve(DESKTOP_ASSETS_DIR, 'fonts', ANTONIO_FONT_NAME), + path.resolve(WEB_ASSETS_DIR, 'fonts', ANTONIO_FONT_NAME), + path.resolve(MOBILE_ASSETS_DIR, 'fonts', ANTONIO_FONT_NAME), ]); copyFile(