chore: clean up unused imports and parameters across components
This commit is contained in:
parent
0ad75b8f08
commit
d711d724bb
15 changed files with 58 additions and 94 deletions
|
|
@ -4,7 +4,7 @@ import { useCallback } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { useInstance } from "@/contexts/InstanceContext";
|
import { useInstance } from "@/contexts/InstanceContext";
|
||||||
import { memoKeys, useDeleteMemo, useUpdateMemo } from "@/hooks/useMemoQueries";
|
import { useDeleteMemo, useUpdateMemo } from "@/hooks/useMemoQueries";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
import { userKeys } from "@/hooks/useUserQueries";
|
import { userKeys } from "@/hooks/useUserQueries";
|
||||||
import { State } from "@/types/proto/api/v1/common_pb";
|
import { State } from "@/types/proto/api/v1/common_pb";
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { timestampDate } from "@bufbuild/protobuf/wkt";
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { CheckCircleIcon, Code2Icon, HashIcon, LinkIcon } from "lucide-react";
|
import { CheckCircleIcon, Code2Icon, HashIcon, LinkIcon } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Memo, Memo_Property, Memo_PropertySchema, MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb";
|
import { Memo, Memo_PropertySchema, MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import MemoRelationForceGraph from "../MemoRelationForceGraph";
|
import MemoRelationForceGraph from "../MemoRelationForceGraph";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ export const LocationDialog = ({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
state,
|
state,
|
||||||
locationInitialized,
|
locationInitialized: _locationInitialized,
|
||||||
onPositionChange,
|
onPositionChange,
|
||||||
onUpdateCoordinate,
|
onUpdateCoordinate,
|
||||||
onPlaceholderChange,
|
onPlaceholderChange,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ interface UseKeyboardOptions {
|
||||||
onToggleFocusMode?: () => void;
|
onToggleFocusMode?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useKeyboard = (editorRef: React.RefObject<EditorRefActions | null>, options: UseKeyboardOptions) => {
|
export const useKeyboard = (_editorRef: React.RefObject<EditorRefActions | null>, options: UseKeyboardOptions) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
// Cmd/Ctrl + Enter to save
|
// Cmd/Ctrl + Enter to save
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,7 @@ import { memoServiceClient } from "@/connect";
|
||||||
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
|
||||||
import { extractUserIdFromName } from "@/helpers/resource-names";
|
import { extractUserIdFromName } from "@/helpers/resource-names";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import {
|
import { Memo, MemoRelation, MemoRelation_MemoSchema, MemoRelation_Type, MemoRelationSchema } from "@/types/proto/api/v1/memo_service_pb";
|
||||||
Memo,
|
|
||||||
MemoRelation,
|
|
||||||
MemoRelation_Memo,
|
|
||||||
MemoRelation_MemoSchema,
|
|
||||||
MemoRelation_Type,
|
|
||||||
MemoRelationSchema,
|
|
||||||
} from "@/types/proto/api/v1/memo_service_pb";
|
|
||||||
|
|
||||||
interface UseLinkMemoParams {
|
interface UseLinkMemoParams {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { LocalFile } from "@/components/memo-metadata";
|
import type { LocalFile } from "@/components/memo-metadata";
|
||||||
import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb";
|
import type { Attachment } from "@/types/proto/api/v1/attachment_service_pb";
|
||||||
import type { Location, MemoRelation } from "@/types/proto/api/v1/memo_service_pb";
|
import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb";
|
||||||
import type { EditorAction, EditorState, LoadingKey } from "./types";
|
import type { EditorAction, EditorState, LoadingKey } from "./types";
|
||||||
|
|
||||||
export const editorActions = {
|
export const editorActions = {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { create } from "@bufbuild/protobuf";
|
||||||
import { FieldMaskSchema } from "@bufbuild/protobuf/wkt";
|
import { FieldMaskSchema } from "@bufbuild/protobuf/wkt";
|
||||||
import { sortBy } from "lodash-es";
|
import { sortBy } from "lodash-es";
|
||||||
import { MoreVerticalIcon, PlusIcon } from "lucide-react";
|
import { MoreVerticalIcon, PlusIcon } from "lucide-react";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import ConfirmDialog from "@/components/ConfirmDialog";
|
import ConfirmDialog from "@/components/ConfirmDialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { create } from "@bufbuild/protobuf";
|
|
||||||
import { isEqual } from "lodash-es";
|
import { isEqual } from "lodash-es";
|
||||||
import { XIcon } from "lucide-react";
|
import { XIcon } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
@ -12,7 +11,6 @@ import { useInstance } from "@/contexts/InstanceContext";
|
||||||
import { convertFileToBase64 } from "@/helpers/utils";
|
import { convertFileToBase64 } from "@/helpers/utils";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { useUpdateUser } from "@/hooks/useUserQueries";
|
import { useUpdateUser } from "@/hooks/useUserQueries";
|
||||||
import { User as UserPb, UserSchema } from "@/types/proto/api/v1/user_service_pb";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import UserAvatar from "./UserAvatar";
|
import UserAvatar from "./UserAvatar";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,8 @@ const UserMenu = (props: Props) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Then clear user-specific localStorage items
|
// Then clear user-specific localStorage items
|
||||||
// Preserve app-wide settings like theme
|
// Preserve app-wide settings (theme, locale, view preferences, tag view settings)
|
||||||
const keysToPreserve = ["memos-theme", "tag-view-as-tree", "tag-tree-auto-expand", "viewStore"];
|
const keysToPreserve = ["memos-theme", "memos-locale", "memos-view-setting", "tag-view-as-tree", "tag-tree-auto-expand"];
|
||||||
const keysToRemove: string[] = [];
|
const keysToRemove: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < localStorage.length; i++) {
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
|
@ -105,8 +105,8 @@ const UserMenu = (props: Props) => {
|
||||||
// Ignore errors from localStorage operations
|
// Ignore errors from localStorage operations
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always redirect to auth page
|
// Always redirect to auth page (use replace to prevent back navigation)
|
||||||
window.location.href = Routes.AUTH;
|
window.location.replace(Routes.AUTH);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ import { timestampDate } from "@bufbuild/protobuf/wkt";
|
||||||
import { Code, ConnectError, createClient, type Interceptor } from "@connectrpc/connect";
|
import { Code, ConnectError, createClient, type Interceptor } from "@connectrpc/connect";
|
||||||
import { createConnectTransport } from "@connectrpc/connect-web";
|
import { createConnectTransport } from "@connectrpc/connect-web";
|
||||||
import { getAccessToken, setAccessToken } from "./auth-state";
|
import { getAccessToken, setAccessToken } from "./auth-state";
|
||||||
import { getInstanceConfig } from "./instance-config";
|
|
||||||
import { ROUTES } from "./router/routes";
|
|
||||||
import { ActivityService } from "./types/proto/api/v1/activity_service_pb";
|
import { ActivityService } from "./types/proto/api/v1/activity_service_pb";
|
||||||
import { AttachmentService } from "./types/proto/api/v1/attachment_service_pb";
|
import { AttachmentService } from "./types/proto/api/v1/attachment_service_pb";
|
||||||
import { AuthService } from "./types/proto/api/v1/auth_service_pb";
|
import { AuthService } from "./types/proto/api/v1/auth_service_pb";
|
||||||
|
|
@ -12,6 +10,7 @@ import { InstanceService } from "./types/proto/api/v1/instance_service_pb";
|
||||||
import { MemoService } from "./types/proto/api/v1/memo_service_pb";
|
import { MemoService } from "./types/proto/api/v1/memo_service_pb";
|
||||||
import { ShortcutService } from "./types/proto/api/v1/shortcut_service_pb";
|
import { ShortcutService } from "./types/proto/api/v1/shortcut_service_pb";
|
||||||
import { UserService } from "./types/proto/api/v1/user_service_pb";
|
import { UserService } from "./types/proto/api/v1/user_service_pb";
|
||||||
|
import { redirectOnAuthFailure } from "./utils/auth-redirect";
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Constants
|
// Constants
|
||||||
|
|
@ -20,19 +19,6 @@ import { UserService } from "./types/proto/api/v1/user_service_pb";
|
||||||
const RETRY_HEADER = "X-Retry";
|
const RETRY_HEADER = "X-Retry";
|
||||||
const RETRY_HEADER_VALUE = "true";
|
const RETRY_HEADER_VALUE = "true";
|
||||||
|
|
||||||
const ROUTE_CONFIG = {
|
|
||||||
// Routes accessible without authentication (uses prefix matching)
|
|
||||||
public: [
|
|
||||||
ROUTES.AUTH, // Authentication pages
|
|
||||||
ROUTES.EXPLORE, // Explore page
|
|
||||||
"/u/", // User profile pages (dynamic)
|
|
||||||
"/memos/", // Individual memo detail pages (dynamic)
|
|
||||||
],
|
|
||||||
|
|
||||||
// Routes that require authentication (uses exact matching)
|
|
||||||
private: [ROUTES.ROOT, ROUTES.ATTACHMENTS, ROUTES.INBOX, ROUTES.ARCHIVED, ROUTES.SETTING],
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Token Refresh State Management
|
// Token Refresh State Management
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -60,42 +46,6 @@ const createTokenRefreshManager = () => {
|
||||||
|
|
||||||
const tokenRefreshManager = createTokenRefreshManager();
|
const tokenRefreshManager = createTokenRefreshManager();
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Route Access Control
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
function isPublicRoute(path: string): boolean {
|
|
||||||
return ROUTE_CONFIG.public.some((route) => path.startsWith(route));
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPrivateRoute(path: string): boolean {
|
|
||||||
return (ROUTE_CONFIG.private as readonly string[]).includes(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAuthFailureRedirect(currentPath: string): string | null {
|
|
||||||
if (isPublicRoute(currentPath)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getInstanceConfig().memoRelatedSetting.disallowPublicVisibility) {
|
|
||||||
return ROUTES.AUTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPrivateRoute(currentPath)) {
|
|
||||||
return ROUTES.EXPLORE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function performRedirect(redirectUrl: string | null): void {
|
|
||||||
if (redirectUrl) {
|
|
||||||
// Use replace() instead of href to prevent back button from showing cached sensitive data
|
|
||||||
// This removes the current page from browser history after authentication failure
|
|
||||||
window.location.replace(redirectUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Token Refresh
|
// Token Refresh
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -165,8 +115,7 @@ const authInterceptor: Interceptor = (next) => async (req) => {
|
||||||
req.header.set(RETRY_HEADER, RETRY_HEADER_VALUE);
|
req.header.set(RETRY_HEADER, RETRY_HEADER_VALUE);
|
||||||
return await next(req);
|
return await next(req);
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
const redirectUrl = getAuthFailureRedirect(window.location.pathname);
|
redirectOnAuthFailure();
|
||||||
performRedirect(redirectUrl);
|
|
||||||
throw refreshError;
|
throw refreshError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,4 +32,3 @@ const useMediaQuery = (breakpoint: Breakpoint): boolean => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useMediaQuery;
|
export default useMediaQuery;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Suspense, useEffect, useMemo, useState } from "react";
|
import { Suspense, useEffect, useMemo } from "react";
|
||||||
import { Outlet, useLocation, useSearchParams } from "react-router-dom";
|
import { Outlet, useLocation, useSearchParams } from "react-router-dom";
|
||||||
import usePrevious from "react-use/lib/usePrevious";
|
import usePrevious from "react-use/lib/usePrevious";
|
||||||
import Navigation from "@/components/Navigation";
|
import Navigation from "@/components/Navigation";
|
||||||
|
|
@ -8,7 +8,7 @@ import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useMediaQuery from "@/hooks/useMediaQuery";
|
import useMediaQuery from "@/hooks/useMediaQuery";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import Loading from "@/pages/Loading";
|
import Loading from "@/pages/Loading";
|
||||||
import { Routes } from "@/router";
|
import { redirectOnAuthFailure } from "@/utils/auth-redirect";
|
||||||
|
|
||||||
const RootLayout = () => {
|
const RootLayout = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
@ -17,25 +17,14 @@ const RootLayout = () => {
|
||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
const { memoRelatedSetting } = useInstance();
|
const { memoRelatedSetting } = useInstance();
|
||||||
const { removeFilter } = useMemoFilterContext();
|
const { removeFilter } = useMemoFilterContext();
|
||||||
const [initialized, setInitialized] = useState(false);
|
|
||||||
const pathname = useMemo(() => location.pathname, [location.pathname]);
|
const pathname = useMemo(() => location.pathname, [location.pathname]);
|
||||||
const prevPathname = usePrevious(pathname);
|
const prevPathname = usePrevious(pathname);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentUser) {
|
if (!currentUser && memoRelatedSetting.disallowPublicVisibility) {
|
||||||
// If disallowPublicVisibility is enabled, redirect to login
|
redirectOnAuthFailure();
|
||||||
if (memoRelatedSetting.disallowPublicVisibility) {
|
|
||||||
window.location.replace(Routes.AUTH);
|
|
||||||
return;
|
|
||||||
} else if (
|
|
||||||
([Routes.ROOT, Routes.ATTACHMENTS, Routes.INBOX, Routes.ARCHIVED, Routes.SETTING] as string[]).includes(location.pathname)
|
|
||||||
) {
|
|
||||||
window.location.replace(Routes.EXPLORE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setInitialized(true);
|
}, [currentUser, memoRelatedSetting.disallowPublicVisibility]);
|
||||||
}, [currentUser, memoRelatedSetting.disallowPublicVisibility, location.pathname]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// When the route changes and there is no filter in the search params, remove all filters
|
// When the route changes and there is no filter in the search params, remove all filters
|
||||||
|
|
@ -44,9 +33,7 @@ const RootLayout = () => {
|
||||||
}
|
}
|
||||||
}, [prevPathname, pathname, searchParams, removeFilter]);
|
}, [prevPathname, pathname, searchParams, removeFilter]);
|
||||||
|
|
||||||
return !initialized ? (
|
return (
|
||||||
<Loading />
|
|
||||||
) : (
|
|
||||||
<div className="w-full min-h-full flex flex-row justify-center items-start sm:pl-16">
|
<div className="w-full min-h-full flex flex-row justify-center items-start sm:pl-16">
|
||||||
{sm && (
|
{sm && (
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ const MemoDetail = () => {
|
||||||
setShowCommentEditor(true);
|
setShowCommentEditor(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCommentCreated = async (memoCommentName: string) => {
|
const handleCommentCreated = async (_memoCommentName: string) => {
|
||||||
// React Query will auto-refetch due to invalidation in the mutation
|
// React Query will auto-refetch due to invalidation in the mutation
|
||||||
setShowCommentEditor(false);
|
setShowCommentEditor(false);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
36
web/src/utils/auth-redirect.ts
Normal file
36
web/src/utils/auth-redirect.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { getInstanceConfig } from "@/instance-config";
|
||||||
|
import { ROUTES } from "@/router/routes";
|
||||||
|
|
||||||
|
const PUBLIC_ROUTES = [
|
||||||
|
ROUTES.AUTH, // Authentication pages
|
||||||
|
ROUTES.EXPLORE, // Explore page
|
||||||
|
"/u/", // User profile pages (dynamic)
|
||||||
|
"/memos/", // Individual memo detail pages (dynamic)
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const PRIVATE_ROUTES = [ROUTES.ROOT, ROUTES.ATTACHMENTS, ROUTES.INBOX, ROUTES.ARCHIVED, ROUTES.SETTING] as const;
|
||||||
|
|
||||||
|
function isPublicRoute(path: string): boolean {
|
||||||
|
return PUBLIC_ROUTES.some((route) => path.startsWith(route));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrivateRoute(path: string): boolean {
|
||||||
|
return PRIVATE_ROUTES.includes(path as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function redirectOnAuthFailure(): void {
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
|
// Don't redirect if it's a public route
|
||||||
|
if (isPublicRoute(currentPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const disallowPublicVisibility = getInstanceConfig().memoRelatedSetting.disallowPublicVisibility;
|
||||||
|
const target = disallowPublicVisibility ? ROUTES.AUTH : ROUTES.EXPLORE;
|
||||||
|
|
||||||
|
// Only redirect if it's a private route or disallowPublicVisibility is enabled
|
||||||
|
if (disallowPublicVisibility || isPrivateRoute(currentPath)) {
|
||||||
|
window.location.replace(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
"esModuleInterop": false,
|
"esModuleInterop": false,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue