Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

404 페이지, 존재하지 않는 스페이스 페이지 핸들링 #166

Merged
merged 9 commits into from
Nov 28, 2024
2 changes: 2 additions & 0 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BrowserRouter, Route, Routes } from "react-router-dom";
import "./App.css";
import Editor from "./components/note/Editor.tsx";
import Home from "./pages/Home.tsx";
import NotFoundPage from "./pages/NotFound.tsx";
import SpacePage from "./pages/Space.tsx";

function App() {
Expand All @@ -12,6 +13,7 @@ function App() {
<Route path="/" element={<Home />} />
<Route path="/space/:spaceId" element={<SpacePage />} />
<Route path="/note/:noteId" element={<Editor />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
Expand Down
30 changes: 30 additions & 0 deletions packages/frontend/src/assets/error-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions packages/frontend/src/components/ErrorSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ReactNode } from "react";

import buzzyLogo from "@/assets/error-logo.svg";

type ErrorSectionProps = {
description: string;
RestoreActions?: () => ReactNode;
};

export default function ErrorSection({
description,
RestoreActions,
}: ErrorSectionProps) {
return (
<div className="container mx-auto px-6 h-full text-center">
<div className="flex flex-col w-full h-full justify-center items-center">
<img src={buzzyLogo} className="w-36" />
<div className="mt-8 text-base">
<p>{description}</p>
</div>
{RestoreActions && (
<div className="mt-12">
<RestoreActions />
</div>
)}
</div>
Comment on lines +21 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RestoreAction을 따로 받아서 더욱 재사용성이 높아진 것 같습니다 !

</div>
);
}
3 changes: 2 additions & 1 deletion packages/frontend/src/components/space/SpaceView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ export default function SpaceView({ spaceId, autofitTo }: SpaceViewProps) {
}}
>
<PaletteMenu
items={["note", "image", "url", "subspace"]}
// items={["note", "image", "url", "subspace"]}
items={["note", "subspace"]}
onSelect={handlePaletteSelect}
/>
</div>
Expand Down
16 changes: 15 additions & 1 deletion packages/frontend/src/hooks/yjs/useYjsConnection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ import * as Y from "yjs";
import { generateUserColor } from "@/lib/utils";

export default function useYjsConnection(docName: string) {
const [status, setStatus] = useState<
"connecting" | "connected" | "disconnected"
>("connecting");
const [error, setError] = useState<Error>();
const [yDoc, setYDoc] = useState<Y.Doc>();
const [yProvider, setYProvider] = useState<Y.AbstractConnector>();

useEffect(() => {
setStatus("connecting");

const doc = new Y.Doc();
const provider = new WebsocketProvider(
`ws://${import.meta.env.DEV ? "localhost" : "www.honeyflow.life"}/ws/space`,
Expand All @@ -28,9 +34,17 @@ export default function useYjsConnection(docName: string) {
if (event.status === "connected") {
awareness.setLocalStateField("color", generateUserColor());
}
setStatus(event.status);
},
);

provider.once("connection-close", (event: CloseEvent) => {
if (event.code === 1008) {
provider.shouldConnect = false;
setError(new Error("찾을 수 없거나 접근할 수 없는 스페이스예요."));
}
});

Comment on lines +41 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(0.3점) 1008 코드 이외의 코드가 발생할 시의 대응을 추가해볼 수도 있을 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵! 현재 백엔드에서 존재하지 않는 스페이스는 1008 코드 주면서 연결을 끊고 있는데, 다른 경우엔 "알 수 없는 오류"처럼 처리해야 할 것 같아요.

대신 이렇게 shouldConnect = false를 해줘도 될지 모르겠네요. 기본적으로 계속 연결 재시도를 하더라고요. 1008번의 경우 확실히 재연결이 필요가 없어서 이렇게 했습니다

return () => {
if (provider.bcconnected || provider.wsconnected) {
provider.disconnect();
Expand All @@ -41,5 +55,5 @@ export default function useYjsConnection(docName: string) {
};
}, [docName]);

return { yProvider, yDoc, setYProvider, setYDoc };
return { status, error, yProvider, yDoc, setYProvider, setYDoc };
}
2 changes: 1 addition & 1 deletion packages/frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function createSafeContext<T>(defaultValue?: T) {
}

export function generateUniqueId() {
return Math.random().toString(36);
return Math.random().toString(36).slice(2);
}

// 노출과 명도는 유지, 색상만 랜덤
Expand Down
23 changes: 21 additions & 2 deletions packages/frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ type CreateSpaceButtonProps = {

function CreateSpaceButton({ navigate }: CreateSpaceButtonProps) {
const [spaceName, setSpaceName] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");

const handleCreateSpace = (e: FormEvent) => {
e.preventDefault();
const targetSpaceName = spaceName.trim();
Expand All @@ -41,13 +43,18 @@ function CreateSpaceButton({ navigate }: CreateSpaceButtonProps) {
return;
}

setIsLoading(true);

requestCreateSpace(targetSpaceName)
.then((res) => {
const { urlPath } = res;
navigate(`/space/${urlPath}`);
})
.catch((error) => {
setError(`스페이스 생성에 실패했어요. (${error})`);
})
.finally(() => {
setIsLoading(false);
});
};

Expand Down Expand Up @@ -77,9 +84,21 @@ function CreateSpaceButton({ navigate }: CreateSpaceButtonProps) {
{error && <p className="text-red-500 text-sm mt-2">{error}</p>}
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button variant="ghost">취소</Button>
<Button
variant="ghost"
disabled={isLoading}
aria-disabled={isLoading}
>
취소
</Button>
</DialogClose>
<Button type="submit">생성</Button>
<Button
type="submit"
disabled={isLoading}
aria-disabled={isLoading}
>
생성
</Button>
</DialogFooter>
</form>
</DialogContent>
Expand Down
22 changes: 22 additions & 0 deletions packages/frontend/src/pages/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Link } from "react-router-dom";

import { MoveLeftIcon } from "lucide-react";

import ErrorSection from "@/components/ErrorSection";
import { Button } from "@/components/ui/button";

export default function NotFoundPage() {
return (
<ErrorSection
description="찾을 수 없는 페이지예요"
RestoreActions={() => (
<Button asChild>
<Link to="/">
<MoveLeftIcon />
처음으로 돌아가기
</Link>
</Button>
)}
/>
);
}
34 changes: 32 additions & 2 deletions packages/frontend/src/pages/Space.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { useRef } from "react";
import { useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom";

import { CircleDashedIcon, MoveLeftIcon } from "lucide-react";

import ErrorSection from "@/components/ErrorSection";
import SpacePageHeader from "@/components/space/SpacePageHeader";
import SpaceView from "@/components/space/SpaceView";
import { Button } from "@/components/ui/button";
import useYjsConnection from "@/hooks/yjs/useYjsConnection";
import { YjsStoreProvider } from "@/store/yjs";

Expand All @@ -11,15 +15,41 @@ interface SpacePageParams extends Record<string, string | undefined> {
}

export default function SpacePage() {
const navigate = useNavigate();
const { spaceId } = useParams<SpacePageParams>();

if (!spaceId) {
throw new Error("");
}

const { yDoc, yProvider, setYDoc, setYProvider } = useYjsConnection(spaceId);
const { error, status, yDoc, yProvider, setYDoc, setYProvider } =
useYjsConnection(spaceId);
const containerRef = useRef<HTMLDivElement>(null);

if (error) {
return (
<ErrorSection
description={error.message}
RestoreActions={() => (
<>
<Button variant="outline" onClick={() => navigate("/")}>
<MoveLeftIcon />
처음으로 돌아가기
</Button>
</>
)}
/>
);
}

if (status === "connecting") {
return (
<div className="flex justify-center items-center h-full">
<CircleDashedIcon className="animate-spin w-32 h-32 text-primary" />
</div>
);
}

return (
<YjsStoreProvider value={{ yDoc, yProvider, setYDoc, setYProvider }}>
<div className="w-full h-full" ref={containerRef}>
Expand Down