Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/webapp/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const VIEW_MODE_PARAM = 'view-mode';
export const GROUP_PARAM = 'group';
export const MODAL_PARAM = 'modal';
export const ITEM_PARAM = 'item';
export const VIEW_PARAM = 'view';
export const CATEGORY_PARAM = 'category';
export const SUBCATEGORY_PARAM = 'subcategory';
export const PAGE_PARAM = 'page';
Expand Down
79 changes: 49 additions & 30 deletions ui/webapp/src/layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { useSearchParams } from '@solidjs/router';
import isEmpty from 'lodash/isEmpty';
import { createSignal, JSXElement, onMount } from 'solid-js';
import { createSignal, JSXElement, onMount, Show } from 'solid-js';

import { ITEM_PARAM, VIEW_PARAM } from '../data';
import ItemModal from './common/itemModal';
import ZoomModal from './common/zoomModal';
import KioskView from './kiosk';
import styles from './Layout.module.css';
import Header from './navigation/Header';
import MobileHeader from './navigation/MobileHeader';
import { ActiveItemProvider } from './stores/activeItem';
import { FinancesDataProvider } from './stores/financesData';
import { FullDataProvider } from './stores/fullData';
import { FullDataProvider, useFullDataReady } from './stores/fullData';
import { GridWidthProvider } from './stores/gridWidth';
import { GroupActiveProvider } from './stores/groupActive';
import { GuideFileProvider } from './stores/guideFile';
Expand All @@ -23,9 +26,23 @@ interface Props {
children?: JSXElement;
}

// Inner wrapper so useFullDataReady() is called inside FullDataProvider
const KioskWrapper = (props: { itemId: string }) => {
const fullDataReady = useFullDataReady();
return <KioskView itemId={props.itemId} fullDataReady={fullDataReady()} />;
};

const Layout = (props: Props) => {
const [searchParams] = useSearchParams();
const [statsVisible, setStatsVisible] = createSignal<boolean>(true);

const isKioskView = () => searchParams[VIEW_PARAM] === 'kiosk' && !!searchParams[ITEM_PARAM];
const kioskItemId = (): string => {
const val = searchParams[ITEM_PARAM];
if (Array.isArray(val)) return val[0] || '';
return val || '';
};

onMount(() => {
// Check if statsDS is empty, if so, hide the stats link
if (isEmpty(window.statsDS)) {
Expand All @@ -35,34 +52,36 @@ const Layout = (props: Props) => {

return (
<FullDataProvider>
<ActiveItemProvider>
<ViewModeProvider>
<GroupActiveProvider>
<VisibleZoomSectionProvider>
<ZoomProvider>
<GridWidthProvider>
<MobileTOCProvider>
<GuideFileProvider>
<FinancesDataProvider>
<EventsProvider>
<div class={`d-flex flex-column ${styles.container}`}>
<MobileHeader statsVisible={statsVisible()} />
<Header statsVisible={statsVisible()} />
<div class="d-flex flex-column flex-grow-1">{props.children}</div>
</div>
<ItemModal />
<ZoomModal />
<UpcomingEvents />
</EventsProvider>
</FinancesDataProvider>
</GuideFileProvider>
</MobileTOCProvider>
</GridWidthProvider>
</ZoomProvider>
</VisibleZoomSectionProvider>
</GroupActiveProvider>
</ViewModeProvider>
</ActiveItemProvider>
<Show when={!isKioskView()} fallback={<KioskWrapper itemId={kioskItemId()} />}>
<ActiveItemProvider>
<ViewModeProvider>
<GroupActiveProvider>
<VisibleZoomSectionProvider>
<ZoomProvider>
<GridWidthProvider>
<MobileTOCProvider>
<GuideFileProvider>
<FinancesDataProvider>
<EventsProvider>
<div class={`d-flex flex-column ${styles.container}`}>
<MobileHeader statsVisible={statsVisible()} />
<Header statsVisible={statsVisible()} />
<div class="d-flex flex-column flex-grow-1">{props.children}</div>
</div>
<ItemModal />
<ZoomModal />
<UpcomingEvents />
</EventsProvider>
</FinancesDataProvider>
</GuideFileProvider>
</MobileTOCProvider>
</GridWidthProvider>
</ZoomProvider>
</VisibleZoomSectionProvider>
</GroupActiveProvider>
</ViewModeProvider>
</ActiveItemProvider>
</Show>
</FullDataProvider>
);
};
Expand Down
234 changes: 234 additions & 0 deletions ui/webapp/src/layout/kiosk/KioskView.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
.kioskContainer {
min-height: 100vh;
background-color: #f5f7fa;
padding: 2rem;
overflow-y: auto;
}

.header {
text-align: center;
margin-bottom: 2rem;
}

.logoWrapper {
width: 120px;
height: 120px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
}

.logo {
max-width: 100%;
max-height: 100%;
height: auto;
}

.projectName {
font-size: 2rem;
font-weight: bold;
}

.description {
font-size: 1.1rem;
color: #6c757d;
max-width: 700px;
margin: 0 auto;
line-height: 1.6;
}

.statsBar {
display: flex;
flex-direction: row;
justify-content: center;
gap: 2rem;
background-color: #fff;
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
padding: 1rem 2rem;
margin-bottom: 2rem;
}

.statItem {
text-align: center;
min-width: 80px;
}

.statValue {
font-size: 1.5rem;
font-weight: bold;
color: #212529;
}

.statLabel {
font-size: 0.8rem;
text-transform: uppercase;
color: #6c757d;
}

.cardsGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
max-width: 1200px;
margin: 0 auto;
}

.actionCard {
background-color: #fff;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
transition: box-shadow 0.2s ease;
}

.actionCard:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.cardTitle {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 1rem;
}

.cardLink {
display: flex;
flex-direction: row;
align-items: center;
padding: 0.6rem;
border-radius: 8px;
transition: background-color 0.15s ease;
text-decoration: none;
color: inherit;
}

.cardLink:hover {
background-color: #f0f4f8;
}

.cardLinkIcon {
width: 20px;
height: 20px;
margin-right: 0.75rem;
flex-shrink: 0;
}

.badges {
display: flex;
justify-content: center;
gap: 0.5rem;
flex-wrap: wrap;
}

.notFound {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 60vh;
text-align: center;
}

.notFoundTitle {
font-size: 1.8rem;
font-weight: bold;
color: #212529;
margin-bottom: 0.75rem;
}

.notFoundMessage {
font-size: 1.1rem;
color: #6c757d;
margin-bottom: 2rem;
}

.poweredBy {
text-align: center;
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid #dee2e6;
color: #6c757d;
font-size: 0.9rem;
}

@media (prefers-color-scheme: dark) {
.kioskContainer {
background-color: #1a1b1e;
color: #e1e3e6;
}

.projectName {
color: #e1e3e6;
}

.description {
color: #9ca3af;
}

.statsBar {
background-color: #25262b;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

.statValue {
color: #e1e3e6;
}

.statLabel {
color: #9ca3af;
}

.actionCard {
background-color: #25262b;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}

.actionCard:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}

.cardLink {
color: #e1e3e6;
}

.cardLink:hover {
background-color: #2c2d32;
}

.cardTitle {
color: #e1e3e6;
}

.notFoundTitle {
color: #e1e3e6;
}

.notFoundMessage {
color: #9ca3af;
}

.poweredBy {
border-top-color: #373a40;
color: #9ca3af;
}

.poweredBy a {
color: #7cb3f0;
}
}

@media (max-width: 768px) {
.kioskContainer {
padding: 1rem;
}

.cardsGrid {
grid-template-columns: 1fr;
}

.cardLink {
padding: 0.8rem;
}
}
Loading