Structure
ireka-ui apps follow a layered layout: main.jsx mounts the app
inside a stack of providers, the framework/ layer supplies the
AppShell — which also mounts the global Alert and Snackbar hosts —
along with TabNav and the navigation primitives, and App.jsx
declares the routes and tabs that render page components from
pages/.
The same source is Capacitor-ready — it runs in the browser during development and ships unchanged inside the Capacitor shell that powers the iOS and Android builds, so what you see on the web is what runs on device.
main.jsx is the entry point. It mounts the app on #root and
wraps it in the provider stack — language, toasts, then the
AppShell (configured for mobile) that every screen renders inside.
AppShell is the shared frame. It sets up the HashRouter (so the app
works inside Capacitor's file:// context), the AuthProvider, and the
phone-sized container every screen renders into. It also mounts the
global overlay hosts — Alert.Host, Snackbar.Host, and
AndroidBackHost — so imperative calls like Alert.show() and
Snackbar.show() work anywhere with no extra wiring.
App.jsx holds the route table. Guest-only routes (/login,
/register) and the authenticated app are split by guard components
that read auth state via useAuth. The catch-all route renders the
main tabbed experience.
The authenticated app is a TabNav with one Tab per section. Each
tab declares an id, a label, a lucide icon, and the page
component it renders — optionally a badge. Labels run through
useLanguage for translation.
Pages are plain React components — one file per screen, importing ireka-ui primitives as needed.
src/
├── main.jsx # Entry — mounts providers + AppShell
├── App.jsx # Routes, auth guards, TabNav
├── framework/ # AppShell (+ Alert/Snackbar hosts), TabNav, Tab,
│ # Page, StackNav, ModalPage, ActionSheet, BottomSheet
├── i18n/ # LanguageProvider + useLanguage
├── hooks/ # shared hooks (keyboard insets, …)
├── pages/ # One component per screen
│ ├── HomePage.jsx
│ ├── MenuPage.jsx
│ └── auth/
│ ├── LoginPage.jsx
│ └── RegisterPage.jsx
└── components/
└── ui/ # ireka-ui components (Button, Card, Alert, Snackbar…)
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { AppShell } from './framework';
import { LanguageProvider } from './i18n/LanguageContext';
import { ToastProvider } from './components/ui/Toast';
import './index.css';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
<LanguageProvider>
<ToastProvider>
<AppShell isMobile disableTextSelection>
<App />
</AppShell>
</ToastProvider>
</LanguageProvider>
</StrictMode>,
);
// framework/AppShell.jsx (simplified)
import { HashRouter } from 'react-router-dom';
import { AuthProvider } from './AuthContext';
import AndroidBackHost from './AndroidBackHost';
import Alert from '../components/ui/Alert';
import Snackbar from '../components/ui/Snackbar';
export default function AppShell({ children, isMobile }) {
return (
<HashRouter>
<AuthProvider>
<Shell isMobile={isMobile}>
{children}
<AndroidBackHost />
</Shell>
{/* Global overlay hosts — mounted once, callable anywhere */}
<Alert.Host />
<Snackbar.Host />
</AuthProvider>
</HashRouter>
);
}
import { Routes, Route, Navigate } from 'react-router-dom';
import { useAuth } from './framework';
import LoginPage from './pages/auth/LoginPage';
import RegisterPage from './pages/auth/RegisterPage';
function AuthGuard({ children }) {
const { isAuthenticated } = useAuth();
return isAuthenticated ? children : <Navigate to="/login" replace />;
}
function GuestGuard({ children }) {
const { isAuthenticated } = useAuth();
return isAuthenticated ? <Navigate to="/" replace /> : children;
}
export default function App() {
return (
<Routes>
<Route path="/login" element={<GuestGuard><LoginPage /></GuestGuard>} />
<Route path="/register" element={<GuestGuard><RegisterPage /></GuestGuard>} />
<Route path="/*" element={<AuthGuard><MainApp /></AuthGuard>} />
</Routes>
);
}
import { Home, Search, User, Layers, UtensilsCrossed, ClipboardList } from 'lucide-react';
import { TabNav, Tab } from './framework';
import { useLanguage } from './i18n/LanguageContext';
function MainApp() {
const { t } = useLanguage();
return (
<TabNav>
<Tab id="home" label={t('home')} icon={Home} component={HomePage} />
<Tab id="menu" label={t('menu')} icon={UtensilsCrossed} component={MenuPage} />
<Tab id="search" label={t('Search')} icon={Search} component={SearchPage} />
<Tab id="orders" label={t('orders')} icon={ClipboardList} component={MyOrdersPage} badge={1} />
<Tab id="rewards" label={t('rewards')} icon={Layers} component={RewardsPage} />
<Tab id="profile" label={t('profile')} icon={User} component={ProfilePage} />
</TabNav>
);
}
import { Card, SectionLabel } from '../components/ui';
export default function OrdersPage() {
return (
<>
<SectionLabel>Orders</SectionLabel>
<Card>
<Card.Body>Page content lives here.</Card.Body>
</Card>
</>
);
}