Import source: app/teknav-app
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
// teknav-app.jsx — Main App Router (ES module)
|
||||
import { useEffect } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import {
|
||||
NavProvider, AuthProvider, ToastProvider,
|
||||
Header, Footer, EmptyState, useNav, InstallPrompt, PushNotificationPrompt, useAuth,
|
||||
} from './teknav-ui.jsx';
|
||||
import { HomePage } from './teknav-home.jsx';
|
||||
import { EnglishHomePage } from './teknav-en.jsx';
|
||||
import { ArticleListPage, ArticleDetail } from './teknav-articles.jsx';
|
||||
import { CategoryPage, TagPage, GlossaryPage, SearchPage, AuthorsListPage, AuthorProfilePage, SeriesPage, TopicHubPage, NewsletterArchivePage, NewsletterIssuePage, JobsPage, CoursesPage, MembershipPage, MembershipSuccessPage, PrivacyPolicyPage, TermsOfUsePage, AboutPage, ContactPage, ToolsPage, NlpNormalizerPage, RagCalculatorPage, TekRagHubPage, ComparisonPage, DirectoryListPage, CompanyProfilePage, ComparisonsListPage, StoriesListPage } from './teknav-pages.jsx';
|
||||
import { LoginPage } from './teknav-auth.jsx';
|
||||
import { AdminPanel, WriterWorkspace } from './teknav-admin.jsx';
|
||||
import { UserProfilePage } from './teknav-profile.jsx';
|
||||
import { SeoManager } from './src/lib/seo.jsx';
|
||||
|
||||
function App() {
|
||||
const { page } = useNav();
|
||||
|
||||
const route = () => {
|
||||
if (page === '/' || page === '') return <HomePage />;
|
||||
if (page === '/en' || page === '/en/') return <EnglishHomePage />;
|
||||
if (page === '/articles') return <ArticleListPage />;
|
||||
if (page.startsWith('/article/')) return <ArticleDetail slug={page.replace('/article/', '')} />;
|
||||
if (page.startsWith('/category/')) return <CategoryPage slug={page.replace('/category/', '')} />;
|
||||
if (page.startsWith('/tag/')) return <TagPage tag={page.replace('/tag/', '')} />;
|
||||
if (page.startsWith('/glossary/')) return <GlossaryPage slug={page.replace('/glossary/', '')} />;
|
||||
if (page.startsWith('/topics/')) return <TopicHubPage slug={page.replace('/topics/', '')} />;
|
||||
if (page.startsWith('/series/')) return <SeriesPage slug={page.replace('/series/', '')} />;
|
||||
if (page === '/newsletter') return <NewsletterArchivePage />;
|
||||
if (page === '/jobs') return <JobsPage />;
|
||||
if (page === '/courses') return <CoursesPage />;
|
||||
if (page === '/membership') return <MembershipPage />;
|
||||
if (page === '/tools') return <ToolsPage />;
|
||||
if (page === '/tools/nlp-normalizer') return <NlpNormalizerPage />;
|
||||
if (page === '/tools/rag-calculator') return <RagCalculatorPage />;
|
||||
if (page === '/tekrag-persian') return <TekRagHubPage />;
|
||||
if (page.startsWith('/newsletter/')) return <NewsletterIssuePage slug={page.replace('/newsletter/', '')} />;
|
||||
if (page === '/search') return <SearchPage />;
|
||||
if (page.startsWith('/search?q=')) {
|
||||
const q = decodeURIComponent(page.replace('/search?q=', ''));
|
||||
return <SearchPage initQ={q} />;
|
||||
}
|
||||
if (page === '/authors') return <AuthorsListPage />;
|
||||
if (page.startsWith('/author/')) return <AuthorProfilePage slug={page.replace('/author/', '')} />;
|
||||
if (page.startsWith('/profile/@')) return <UserProfilePage username={page.replace('/profile/@', '')} />;
|
||||
if (page === '/login') return <LoginPage />;
|
||||
if (page === '/writer') return <WriterWorkspace />;
|
||||
if (page === '/admin') return <AdminPanel />;
|
||||
if (page === '/privacy') return <PrivacyPolicyPage />;
|
||||
if (page === '/terms') return <TermsOfUsePage />;
|
||||
if (page === '/about') return <AboutPage />;
|
||||
if (page === '/contact') return <ContactPage />;
|
||||
if (page === '/compare') return <ComparisonsListPage />;
|
||||
if (page.startsWith('/compare/')) return <ComparisonPage slug={page.replace('/compare/', '')} />;
|
||||
if (page === '/stories') return <StoriesListPage />;
|
||||
if (page === '/directory') return <DirectoryListPage />;
|
||||
if (page.startsWith('/directory/')) return <CompanyProfilePage slug={page.replace('/directory/', '')} />;
|
||||
// 404
|
||||
return (
|
||||
<div style={{ paddingTop: 120, textAlign: 'center', fontFamily: 'Vazirmatn,sans-serif', direction: 'rtl' }}>
|
||||
<EmptyState title="صفحه یافت نشد" subtitle="آدرس واردشده وجود ندارد" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// /embed/* is served by embed.html (second Vite entry) — not handled in this SPA
|
||||
|
||||
const isAdmin = page === '/admin';
|
||||
const isLogin = page === '/login';
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
<AuthProvider>
|
||||
<NavProvider>
|
||||
<NavConsumer isAdmin={isAdmin} isLogin={isLogin} route={route} />
|
||||
</NavProvider>
|
||||
</AuthProvider>
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function NavConsumer({ isAdmin, isLogin, route }) {
|
||||
const { page } = useNav();
|
||||
const isAdminPage = page === '/admin';
|
||||
const isLoginPage = page === '/login';
|
||||
const isEnglishPage = page === '/en' || page === '/en/';
|
||||
return (
|
||||
<>
|
||||
{!isAdminPage && !isLoginPage && !isEnglishPage && <Header />}
|
||||
<main>{route()}</main>
|
||||
{!isAdminPage && !isLoginPage && !isEnglishPage && <Footer />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Mount — wrap everything in providers from the outside
|
||||
function Root() {
|
||||
return (
|
||||
<NavProvider>
|
||||
<AuthProvider>
|
||||
<ToastProvider>
|
||||
<InnerApp />
|
||||
</ToastProvider>
|
||||
</AuthProvider>
|
||||
</NavProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function InnerApp() {
|
||||
const { page } = useNav();
|
||||
const { user } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
const report = (payload) => {
|
||||
try {
|
||||
navigator.sendBeacon?.('/api/v1/errors', new Blob([JSON.stringify(payload)], { type: 'application/json' })) ||
|
||||
fetch('/api/v1/errors', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), keepalive: true });
|
||||
} catch {}
|
||||
};
|
||||
const onError = (event) => report({
|
||||
message: event.message || 'window.error',
|
||||
source: event.filename,
|
||||
stack: event.error?.stack,
|
||||
path: window.location.pathname + window.location.hash,
|
||||
userAgent: navigator.userAgent,
|
||||
});
|
||||
const onRejection = (event) => report({
|
||||
message: String(event.reason?.message || event.reason || 'unhandledrejection'),
|
||||
stack: event.reason?.stack,
|
||||
path: window.location.pathname + window.location.hash,
|
||||
userAgent: navigator.userAgent,
|
||||
});
|
||||
window.addEventListener('error', onError);
|
||||
window.addEventListener('unhandledrejection', onRejection);
|
||||
return () => {
|
||||
window.removeEventListener('error', onError);
|
||||
window.removeEventListener('unhandledrejection', onRejection);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isAdmin = page === '/admin';
|
||||
const isLogin = page === '/login';
|
||||
const isEnglish = page === '/en' || page === '/en/';
|
||||
|
||||
const renderPage = () => {
|
||||
if (page === '/' || page === '') return <HomePage />;
|
||||
if (page === '/en' || page === '/en/') return <EnglishHomePage />;
|
||||
if (page === '/articles') return <ArticleListPage />;
|
||||
if (page.startsWith('/article/')) return <ArticleDetail slug={page.replace('/article/', '')} />;
|
||||
if (page.startsWith('/category/')) return <CategoryPage slug={page.replace('/category/', '')} />;
|
||||
if (page.startsWith('/tag/')) return <TagPage tag={page.replace('/tag/', '')} />;
|
||||
if (page.startsWith('/glossary/')) return <GlossaryPage slug={page.replace('/glossary/', '')} />;
|
||||
if (page.startsWith('/topics/')) return <TopicHubPage slug={page.replace('/topics/', '')} />;
|
||||
if (page.startsWith('/series/')) return <SeriesPage slug={page.replace('/series/', '')} />;
|
||||
if (page === '/newsletter') return <NewsletterArchivePage />;
|
||||
if (page === '/jobs') return <JobsPage />;
|
||||
if (page === '/courses') return <CoursesPage />;
|
||||
if (page === '/membership') return <MembershipPage />;
|
||||
if (page === '/tools') return <ToolsPage />;
|
||||
if (page === '/tools/nlp-normalizer') return <NlpNormalizerPage />;
|
||||
if (page === '/tools/rag-calculator') return <RagCalculatorPage />;
|
||||
if (page === '/tekrag-persian') return <TekRagHubPage />;
|
||||
if (page === '/membership/success' || page.startsWith('/membership/success?')) return <MembershipSuccessPage />;
|
||||
if (page.startsWith('/newsletter/')) return <NewsletterIssuePage slug={page.replace('/newsletter/', '')} />;
|
||||
if (page === '/search') return <SearchPage />;
|
||||
if (page.startsWith('/search?q=')) return <SearchPage initQ={decodeURIComponent(page.replace('/search?q=', ''))} />;
|
||||
if (page === '/authors') return <AuthorsListPage />;
|
||||
if (page.startsWith('/author/')) return <AuthorProfilePage slug={page.replace('/author/', '')} />;
|
||||
if (page.startsWith('/profile/@')) return <UserProfilePage username={page.replace('/profile/@', '')} />;
|
||||
if (page === '/login') return <LoginPage />;
|
||||
if (page === '/writer') return <WriterWorkspace />;
|
||||
if (page === '/admin') return <AdminPanel />;
|
||||
if (page === '/privacy') return <PrivacyPolicyPage />;
|
||||
if (page === '/terms') return <TermsOfUsePage />;
|
||||
if (page === '/about') return <AboutPage />;
|
||||
if (page === '/contact') return <ContactPage />;
|
||||
if (page.startsWith('/compare/')) return <ComparisonPage slug={page.replace('/compare/', '')} />;
|
||||
return (
|
||||
<div style={{ paddingTop: 120, textAlign: 'center', fontFamily: 'Vazirmatn,sans-serif', direction: 'rtl' }}>
|
||||
<EmptyState title="صفحه یافت نشد" subtitle="آدرس واردشده وجود ندارد" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// /embed/* is served by embed.html (second Vite entry) — not handled in this SPA
|
||||
|
||||
return (
|
||||
<>
|
||||
<SeoManager />
|
||||
{!isAdmin && !isLogin && !isEnglish && <Header />}
|
||||
<main>{renderPage()}</main>
|
||||
{!isAdmin && !isLogin && !isEnglish && <Footer />}
|
||||
<InstallPrompt />
|
||||
<PushNotificationPrompt user={user} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const reactRoot = createRoot(container);
|
||||
reactRoot.render(<Root />);
|
||||
Reference in New Issue
Block a user