fix: improve auth

This commit is contained in:
Stanley Cheung 2026-02-24 11:57:39 +08:00
parent 26ac7c1003
commit 227be9ac41
13 changed files with 99 additions and 11 deletions

View File

@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
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.

View File

@ -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",

View File

@ -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",

View File

@ -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 (
<div className="bg-background min-h-screen">
<MigrationDialog />

View File

@ -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";

View File

@ -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({

View File

@ -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 {

View File

@ -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({

View File

@ -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 }>({

View File

@ -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) ||

View File

@ -363,8 +363,16 @@ class StorageService {
}
async clearAllData(): Promise<void> {
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<{

View File

@ -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<AuthState>()((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 });
}
},

View File

@ -15,3 +15,19 @@ export function OcDataBuddyIcon(props: IconProps) {
return <ChartArea {...props} />;
}
// Compatibility exports for upstream UI components.
export function OcSocialIcon(props: IconProps) {
return <Video {...props} />;
}
export function OcFontWeightIcon(props: IconProps) {
return <ChartArea {...props} />;
}
export function OcSlidersVerticalIcon(props: IconProps) {
return <ChartArea {...props} />;
}
export function OcCheckerboardIcon(props: IconProps) {
return <Gem {...props} />;
}