abstracted navigation logic

This commit is contained in:
2025-11-18 16:53:36 +05:30
parent f52c4a5287
commit 459fa5855c
4 changed files with 122 additions and 78 deletions

View File

@@ -15,6 +15,32 @@ import Profile from './components/Profile';
import { useArticles } from './providers/Article';
import { useAuth } from './providers/Author';
import { View, useViewRouter } from "./types/views";
import { ArticleModel } from "./types/models";
import { ArticleViewProps, ArticleEditorProps } from "./types/props";
function HomeView({ currentUser, open_login, open_profile, open_create, articles, openArticle }: any) {
return (
<>
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 2, gap: 1 }}>
{!currentUser ? (
<Button variant="outlined" onClick={open_login}>Login</Button>
) : (
<>
<Button variant="outlined" onClick={open_profile}>
{currentUser.username}
</Button>
<Button variant="contained" onClick={open_create}>
New Article
</Button>
</>
)}
</Box>
<MainContent articles={articles} onSelectArticle={openArticle} />
<Latest articles={articles} onSelectArticle={openArticle} />
</>
);
}
export default function Blog(props: { disableCustomTheme?: boolean }) {
const { articles, loading, error } = useArticles();
@@ -27,72 +53,86 @@ export default function Blog(props: { disableCustomTheme?: boolean }) {
const {
goBack,
openLogin,
openRegister,
openProfile,
openCreate,
openEditor,
navigateToChildren,
openArticle,
} = useViewRouter(setUI);
type RouterContext = {
ui: any;
articles: ArticleModel[];
currentUser: any;
openArticle: (index: number) => void;
};
type ViewComponentEntry<P> = {
component: React.ComponentType<P>;
extraProps?: (ctx: RouterContext) => Partial<P>;
};
const VIEW_COMPONENTS: Record<View, ViewComponentEntry<any>> = {
home: {
component: HomeView,
},
login: {
component: Login,
},
register: {
component: Register,
},
profile: {
component: Profile,
},
article: {
component: ArticleView,
extraProps: ({ ui, articles }) => ({
article: articles[ui.selectedArticle!],
}) satisfies Partial<ArticleViewProps>,
},
editor: {
component: ArticleEditor,
extraProps: ({ ui, articles }) => ({
article: ui.selectedArticle !== null ? articles[ui.selectedArticle] : null,
}) satisfies Partial<ArticleEditorProps>,
},
create: {
component: ArticleEditor,
extraProps: () => ({
article: null,
}) satisfies Partial<ArticleEditorProps>,
},
};
const renderView = () => {
switch (ui.view) {
case "login":
return <Login onBack={() => goBack(ui.view)} onRegister={openRegister} />;
const entry = VIEW_COMPONENTS[ui.view];
const ViewComponent = entry.component;
case "register":
return <Register onBack={() => goBack(ui.view)} />;
const childNav = navigateToChildren(ui.view);
case "profile":
return <Profile onBack={() => goBack(ui.view)} />;
const ctx: RouterContext = {
ui,
articles,
currentUser,
case "article":
return (
<ArticleView
article={articles[ui.selectedArticle!]}
onBack={() => goBack(ui.view)}
onEdit={openEditor}
/>
);
openArticle,
};
case "editor":
return (
<ArticleEditor
article={ui.selectedArticle !== null ? articles[ui.selectedArticle] : null}
onBack={() => goBack(ui.view)}
/>
);
const extraProps = entry.extraProps ? entry.extraProps(ctx) : {};
case "create":
return (
<ArticleEditor
onBack={() => goBack(ui.view)}
/>
);
default:
return (
<>
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 2, gap: 1 }}>
{!currentUser ? (
<Button variant="outlined" onClick={openLogin}>Login</Button>
) : (
<>
<Button variant="outlined" onClick={openProfile}>
{currentUser.username}
</Button>
<Button variant="contained" onClick={openCreate}>
New Article
</Button>
</>
)}
</Box>
<MainContent articles={articles} onSelectArticle={openArticle} />
<Latest articles={articles} onSelectArticle={openArticle} />
</>
);
}
return (
<ViewComponent
{...ctx}
{...childNav}
onBack={() => goBack(ui.view)}
{...extraProps}
/>
);
};
if (loading) {

View File

@@ -5,7 +5,7 @@ import { styled } from '@mui/material/styles';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
import EditRoundedIcon from '@mui/icons-material/EditRounded';
import { ArticleMeta } from "../ArticleMeta";
import { ArticleProps } from '../../types/props';
import { ArticleViewProps } from '../../types/props';
import {useAuth} from "../../providers/Author";
const ArticleContainer = styled(Box)(({ theme }) => ({
@@ -28,10 +28,12 @@ const CoverImage = styled('img')({
export default function ArticleView({
article,
onBack,
onEdit,
}: ArticleProps) {
open_editor,
}: ArticleViewProps) {
const { currentUser } = useAuth();
const onEdit = open_editor;
return (
<ArticleContainer>
<Box

View File

@@ -6,12 +6,13 @@ export interface LatestProps {
onLoadMore?: (offset: number, limit: number) => Promise<void>; // optional async callback
}
export interface ArticleProps {
export interface ArticleViewProps {
article: ArticleModel;
onBack: () => void;
onEdit: () => void;
open_editor?: () => void; // optional because home → article must still work
}
export interface ArticleEditorProps {
article?: ArticleModel | null;
onBack: () => void;

View File

@@ -1,4 +1,3 @@
// types.ts
export type View =
| "home"
| "login"
@@ -40,17 +39,16 @@ export const VIEW_TREE: Record<View, ViewNode> = {
},
};
export function useViewRouter(setUI: React.Dispatch<any>) {
export function useViewRouter(setUI: any) {
const navigate = (view: View) => {
setUI((prev: any) => ({ ...prev, view }));
window.scrollTo({ top: 0, behavior: "smooth" });
};
const goBack = (currentView: View) => {
const parent = VIEW_TREE[currentView].parent;
if (parent) {
setUI((prev: any) => ({ ...prev, view: parent }));
}
// auto back logic from parent
const goBack = (view: View) => {
const parent = VIEW_TREE[view].parent;
if (parent) navigate(parent);
};
const openArticle = (i: number) => {
@@ -58,14 +56,17 @@ export function useViewRouter(setUI: React.Dispatch<any>) {
window.scrollTo({ top: 0, behavior: "smooth" });
};
return {
navigate,
goBack,
openLogin: () => navigate("login"),
openRegister: () => navigate("register"),
openProfile: () => navigate("profile"),
openCreate: () => navigate("create"),
openEditor: () => navigate("editor"),
openArticle,
// auto child navigators from children[]
const navigateToChildren = (view: View) => {
const node = VIEW_TREE[view];
const funcs: Record<string, () => void> = {};
node.children?.forEach((child) => {
funcs[`open_${child}`] = () => navigate(child);
});
return funcs;
};
return { navigate, goBack, openArticle, navigateToChildren };
}