From 227be9ac4171136c3c7617fe70b48af3d34e086d Mon Sep 17 00:00:00 2001 From: Stanley Cheung Date: Tue, 24 Feb 2026 11:57:39 +0800 Subject: [PATCH] fix: improve auth --- apps/web/next-env.d.ts | 2 +- apps/web/package-lock.json | 21 +++++++++++ apps/web/package.json | 4 ++- apps/web/src/app/projects/page.tsx | 35 ++++++++++++++++++- .../editor/panels/assets/views/stickers.tsx | 2 +- .../editor/panels/preview/toolbar.tsx | 2 +- .../panels/properties/sections/blending.tsx | 2 +- .../panels/properties/text-properties.tsx | 2 +- apps/web/src/lib/auth/api-client.ts | 4 ++- apps/web/src/proxy.ts | 2 +- apps/web/src/services/storage/service.ts | 12 +++++-- apps/web/src/stores/auth-store.ts | 6 ++++ apps/web/src/ui/icons.tsx | 16 +++++++++ 13 files changed, 99 insertions(+), 11 deletions(-) diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 9edff1c7..c4b7818f 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json index 05b84ccc..0c97d4f2 100644 --- a/apps/web/package-lock.json +++ b/apps/web/package-lock.json @@ -28,6 +28,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "culori": "^4.0.2", "dayjs": "^1.11.13", "drizzle-orm": "^0.44.2", "embla-carousel-react": "^8.5.1", @@ -51,6 +52,7 @@ "react-markdown": "^10.1.0", "react-phone-number-input": "^3.4.11", "react-resizable-panels": "^2.1.7", + "react-window": "^2.2.7", "recharts": "^2.14.1", "rehype-autolink-headings": "^7.1.0", "rehype-parse": "^9.0.1", @@ -5868,6 +5870,15 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/culori": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/culori/-/culori-4.0.2.tgz", + "integrity": "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -9705,6 +9716,16 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-window": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.7.tgz", + "integrity": "sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/recast": { "version": "0.23.11", "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", diff --git a/apps/web/package.json b/apps/web/package.json index 997235cf..9ed3b24a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -45,6 +45,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "culori": "^4.0.2", "dayjs": "^1.11.13", "drizzle-orm": "^0.44.2", "embla-carousel-react": "^8.5.1", @@ -68,6 +69,7 @@ "react-markdown": "^10.1.0", "react-phone-number-input": "^3.4.11", "react-resizable-panels": "^2.1.7", + "react-window": "^2.2.7", "recharts": "^2.14.1", "rehype-autolink-headings": "^7.1.0", "rehype-parse": "^9.0.1", @@ -98,8 +100,8 @@ "@types/react": "^19", "@types/react-dom": "^19", "cross-env": "^7.0.3", - "drizzle-kit": "^0.31.4", "dotenv": "^16.5.0", + "drizzle-kit": "^0.31.4", "postcss": "^8", "storybook": "^8.6.14", "tailwindcss": "^4.1.11", diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index bc7e0ca0..5c70f49a 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -84,7 +84,15 @@ const VIEW_MODE_OPTIONS = [ ]; export default function ProjectsPage() { - const { searchQuery, sortKey, sortOrder, viewMode } = useProjectsStore(); + const { + searchQuery, + sortKey, + sortOrder, + viewMode, + selectedProjectIds, + setSelectedProjects, + clearSelectedProjects, + } = useProjectsStore(); const editor = useEditor(); const authStatus = useAuthStore((state) => state.status); @@ -107,6 +115,31 @@ export default function ProjectsPage() { const isLoading = editor.project.getIsLoading(); const isInitialized = editor.project.getIsInitialized(); + useEffect(() => { + if (authStatus !== "authenticated") { + clearSelectedProjects(); + return; + } + + if (isLoading || !isInitialized) { + return; + } + + if (selectedProjectIds.length > 0 || projectsToDisplay.length === 0) { + return; + } + + setSelectedProjects({ projectIds: [projectsToDisplay[0].id] }); + }, [ + authStatus, + clearSelectedProjects, + isInitialized, + isLoading, + projectsToDisplay, + selectedProjectIds.length, + setSelectedProjects, + ]); + return (
diff --git a/apps/web/src/components/editor/panels/assets/views/stickers.tsx b/apps/web/src/components/editor/panels/assets/views/stickers.tsx index 671effec..b66a8763 100644 --- a/apps/web/src/components/editor/panels/assets/views/stickers.tsx +++ b/apps/web/src/components/editor/panels/assets/views/stickers.tsx @@ -34,7 +34,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { OcSlidersVerticalIcon } from "@opencut/ui/icons"; +import { OcSlidersVerticalIcon } from "@/ui/icons"; import type { StickerCategory } from "@/types/stickers"; import { STICKER_CATEGORIES } from "@/constants/sticker-constants"; import { parseStickerId } from "@/lib/stickers/sticker-id"; diff --git a/apps/web/src/components/editor/panels/preview/toolbar.tsx b/apps/web/src/components/editor/panels/preview/toolbar.tsx index 7f5cd9bc..a5b68f28 100644 --- a/apps/web/src/components/editor/panels/preview/toolbar.tsx +++ b/apps/web/src/components/editor/panels/preview/toolbar.tsx @@ -11,7 +11,7 @@ import { PlayIcon, } from "@hugeicons/core-free-icons"; import { HugeiconsIcon } from "@hugeicons/react"; -import { OcSocialIcon } from "@opencut/ui/icons"; +import { OcSocialIcon } from "@/ui/icons"; import { Separator } from "@/components/ui/separator"; export function PreviewToolbar({ diff --git a/apps/web/src/components/editor/panels/properties/sections/blending.tsx b/apps/web/src/components/editor/panels/properties/sections/blending.tsx index 430efa25..7314783e 100644 --- a/apps/web/src/components/editor/panels/properties/sections/blending.tsx +++ b/apps/web/src/components/editor/panels/properties/sections/blending.tsx @@ -6,7 +6,7 @@ import { DEFAULT_BLEND_MODE, DEFAULT_OPACITY, } from "@/constants/timeline-constants"; -import { OcCheckerboardIcon } from "@opencut/ui/icons"; +import { OcCheckerboardIcon } from "@/ui/icons"; import { Fragment, useRef } from "react"; import { Section, SectionContent, SectionHeader } from "../section"; import { diff --git a/apps/web/src/components/editor/panels/properties/text-properties.tsx b/apps/web/src/components/editor/panels/properties/text-properties.tsx index cfca52dc..9a8adcb9 100644 --- a/apps/web/src/components/editor/panels/properties/text-properties.tsx +++ b/apps/web/src/components/editor/panels/properties/text-properties.tsx @@ -24,7 +24,7 @@ import { SelectContent, SelectItem, } from "@/components/ui/select"; -import { OcFontWeightIcon } from "@opencut/ui/icons"; +import { OcFontWeightIcon } from "@/ui/icons"; import { Label } from "@/components/ui/label"; export function TextProperties({ diff --git a/apps/web/src/lib/auth/api-client.ts b/apps/web/src/lib/auth/api-client.ts index 964423d4..ae8cd0a1 100644 --- a/apps/web/src/lib/auth/api-client.ts +++ b/apps/web/src/lib/auth/api-client.ts @@ -147,6 +147,7 @@ export const backendAuthApi = { body: { username, password }, }), register: ({ + username, email, firstname, lastname, @@ -154,6 +155,7 @@ export const backendAuthApi = { role = 1, organisation_name, }: { + username: string; email: string; firstname: string; lastname: string; @@ -164,7 +166,7 @@ export const backendAuthApi = { request<{ message: string; token: string; user: TBackendUser }>({ path: "/auth/user/register", method: "POST", - body: { email, firstname, lastname, password, role, organisation_name }, + body: { username, email, firstname, lastname, password, role, organisation_name }, }), refresh: () => request<{ token: string; user: TBackendUser }>({ diff --git a/apps/web/src/proxy.ts b/apps/web/src/proxy.ts index 290883e3..58af807d 100644 --- a/apps/web/src/proxy.ts +++ b/apps/web/src/proxy.ts @@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from "next/server"; const PUBLIC_PATH_PREFIXES = ["/auth", "/privacy", "/terms"]; const PUBLIC_EXACT_PATHS = ["/robots.txt", "/sitemap.xml", "/manifest.json"]; -export async function middleware(request: NextRequest) { +export async function proxy(request: NextRequest) { const { pathname } = request.nextUrl; const isPublicPath = PUBLIC_EXACT_PATHS.includes(pathname) || diff --git a/apps/web/src/services/storage/service.ts b/apps/web/src/services/storage/service.ts index 4727a173..27b2eacd 100644 --- a/apps/web/src/services/storage/service.ts +++ b/apps/web/src/services/storage/service.ts @@ -363,8 +363,16 @@ class StorageService { } async clearAllData(): Promise { - await this.projectsAdapter.clear(); - // project-specific media and timelines cleaned up when projects are deleted + const projectIds = await this.projectsAdapter.list(); + + await Promise.all( + projectIds.map((projectId) => this.deleteProjectMedia({ projectId })), + ); + + await Promise.all([ + this.projectsAdapter.clear(), + this.savedSoundsAdapter.clear(), + ]); } async getStorageInfo(): Promise<{ diff --git a/apps/web/src/stores/auth-store.ts b/apps/web/src/stores/auth-store.ts index 4d9f3cbf..17c1c885 100644 --- a/apps/web/src/stores/auth-store.ts +++ b/apps/web/src/stores/auth-store.ts @@ -3,6 +3,7 @@ import { create } from "zustand"; import { backendAuthApi, type TBackendUser } from "@/lib/auth/api-client"; import { authSession } from "@/lib/auth/session"; +import { storageService } from "@/services/storage/service"; type AuthStatus = "idle" | "loading" | "authenticated" | "unauthenticated"; @@ -125,6 +126,11 @@ export const useAuthStore = create()((set, get) => ({ } catch { // Clear local state even if backend logout is unavailable/misconfigured. } finally { + try { + await storageService.clearAllData(); + } catch (error) { + console.error("Failed to clear local data during logout:", error); + } clearAuthState({ set }); } }, diff --git a/apps/web/src/ui/icons.tsx b/apps/web/src/ui/icons.tsx index 86129aae..7d187df8 100644 --- a/apps/web/src/ui/icons.tsx +++ b/apps/web/src/ui/icons.tsx @@ -15,3 +15,19 @@ export function OcDataBuddyIcon(props: IconProps) { return ; } +// Compatibility exports for upstream UI components. +export function OcSocialIcon(props: IconProps) { + return