Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
696d56a
Layout for application created, documentation added.
mroki58 Jun 16, 2025
1a45249
First idea for running test container. Created image with dockerfile…
mroki58 Jun 17, 2025
38f90ed
Session data taken correctly but it rerenders page again which throws…
mroki58 Jun 18, 2025
6b93174
Merge branch 'dev' into feature/TKN/OGUI-1711/implement-overall-layout
mroki58 Aug 20, 2025
6faffd1
- Added eslint configuration with established assumptions
mroki58 Aug 21, 2025
8231106
Merge remote-tracking branch 'origin/dev' into feature/TKN/OGUI-1724/…
mroki58 Aug 22, 2025
3374b5b
- Moved test files for UI to webapp folder
mroki58 Aug 22, 2025
8643941
Merge branch 'feature/TKN/OGUI-1711/implement-overall-layout' into fe…
mroki58 Aug 22, 2025
f0700da
- Proposed session checking logic with context api and hooks.
mroki58 Aug 24, 2025
5311ebf
- Proposed Tokenization project github workflow for UI tests ( eslint…
mroki58 Aug 25, 2025
5232c19
Double import fix
mroki58 Sep 3, 2025
ef7ecfb
Test commit for checking pushing with cuurent tokenization.yml
mroki58 Sep 3, 2025
7ae3345
npm ci => npm i because of some errors in lock file
mroki58 Sep 3, 2025
b75be95
- dependencies for building application taken from dev to normal depe…
mroki58 Sep 3, 2025
8fe9693
Actions for lint-checking in backend (waiting for guys for their chan…
mroki58 Sep 3, 2025
c2f5922
Merge branch 'feature/TKN/OGUI-1724/prepare-testing-env' into feature…
mroki58 Sep 3, 2025
05f7465
Typo correct
mroki58 Sep 3, 2025
7505c32
Updating .dockerignore
mroki58 Sep 3, 2025
d473dcf
Adjust test file
mroki58 Sep 3, 2025
a2a4648
Fixed type error in sidebar.tsx - changed wrong Props type
mroki58 Sep 19, 2025
37694f8
Delete Tokenization/docker-compose.test.yml
mroki58 Oct 31, 2025
c612d2b
Delete .github/workflows/tokenization.yml
mroki58 Oct 31, 2025
a93b0b7
Delete Tokenization/webapp/tests/main.test.js
mroki58 Oct 31, 2025
dd5b031
Delete test commands and devdependencies - mocha and puppeteer
mroki58 Oct 31, 2025
874ac16
Merge remote-tracking branch 'origin/dev' into feature/TKN/OGUI-1725/…
mroki58 Oct 31, 2025
77f1fbe
Documentation changes
mroki58 Nov 4, 2025
ae46b46
Merge branch 'dev' into feature/TKN/OGUI-1725/session-checking
mroki58 Nov 4, 2025
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
2 changes: 2 additions & 0 deletions Tokenization/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.react-router/
53 changes: 53 additions & 0 deletions Tokenization/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# ---- Base ----
FROM node:22-alpine AS base
WORKDIR /var/workspace

# ---- Dependencies (for production) ----
FROM base AS dependencies
COPY webapp/package*.json ./
RUN npm ci --only=production && npm cache clean --force

# ---- Dev Dependencies (for tests) ----
FROM base AS dev-dependencies

# Installs packages required for Puppeteer
RUN apk add --no-cache \
chromium \
freetype \
freetype-dev \
harfbuzz \
ca-certificates

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

COPY webapp .

RUN npm --silent install

# ---- Build ----
FROM base AS build
COPY webapp/package*.json ./
RUN npm i --only=production && npm cache clean --force
COPY webapp .
RUN npm run build

# ---- Test ----
FROM dev-dependencies AS test
CMD ["npm", "run", "test"]

# ---- Coverage ----
FROM dev-dependencies AS coverage
CMD ["npm", "run", "coverage"]

# ---- Production ----
FROM nginx:alpine AS production
COPY --from=build /var/workspace/build/client /usr/share/nginx/html
COPY docker/provisioning/nginx/conf.d/production.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

FROM nginx:1.27 AS reverse-proxy
COPY ./docker/provisioning/nginx/conf.d/default.conf /etc/nginx/conf.d
EXPOSE 8080

5 changes: 2 additions & 3 deletions Tokenization/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,8 @@ services:
condition: service_completed_successfully

reverse-proxy:
image: nginx:1.27
volumes:
- ./docker/provisioning/nginx/conf.d/:/etc/nginx/conf.d/
build:
target: reverse-proxy
ports:
- "8080:8080"
depends_on:
Expand Down
26 changes: 26 additions & 0 deletions Tokenization/docker/provisioning/nginx/conf.d/production.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
events {}

http {
include mime.types;
default_type application/octet-stream;
sendfile on;

server {
listen 80;
listen [::]:80;

server_name localhost;

root /usr/share/nginx/html;
index index.html;

location /api {
proxy_pass http://backend:8080;
}

location / {
try_files $uri /index.html;
}

}
}
2 changes: 1 addition & 1 deletion Tokenization/webapp/app/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@

.scale25 {
transform: scale(2.5);
}
}
108 changes: 108 additions & 0 deletions Tokenization/webapp/app/contexts/sessionContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/
import React, { createContext, useState, useEffect, useMemo, useCallback } from 'react';
import { useLocation, useNavigate } from 'react-router';

/**
* Session context provides information about the current user session.
*
* The session object contains the following properties:
* - personid: User's person ID
* - name: User's display name
* - token: Authentication token
* - username: User's username
* - access: Array of user's access roles
*
*/
interface Session {
personid: string | null;
name: string | null;
token: string | null;
username: string | null;
access: string[] | null;
}

type SessionKey = keyof Session;

const defaultSession = {
personid: null,
name: null,
token: null,
username: null,
access: null,
};

// List ["personid", "name", "token", ...]
const sessionKeys = Object.keys(defaultSession) as SessionKey[];

interface SessionContextType {
session: Session;
hasAccess: (role: string) => boolean;
}

/**
* React context for managing user session state.
* Provides session data and access control functionality.
*/
export const SessionContext = createContext<SessionContextType>({
session: defaultSession,
hasAccess: () => false,
});

/**
* Session provider component that manages user session state.
*
* Automatically extracts session data from URL parameters on mount and
* provides session context to child components.
*
* @param children - React components that need access to session context
*
* @example
* ```tsx
* <SessionProvider>
* <App />
* </SessionProvider>
* ```
*/
export const SessionProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [session, setSession] = useState<Session>(defaultSession);
const location = useLocation();
const navigate = useNavigate();

useEffect(() => {
let sessionLoad: Session = { ...defaultSession };
const params = new URLSearchParams(location.search);
for (const sessionKey of sessionKeys) {
const value = params.get(sessionKey);
if (value && sessionKey === 'access') {
sessionLoad = { ...sessionLoad, [sessionKey]: value.split(',') };
} else if (value) {
sessionLoad = { ...sessionLoad, [sessionKey]: value };
}
}
setSession(sessionLoad);
navigate(location.pathname, { replace: true });
// It should run only once when we start page
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const hasAccess = useCallback(
(role: string) => session.access?.includes(role) ?? false,
[session],
);

const value = useMemo(() => ({ session, hasAccess }), [session, hasAccess]);

return <SessionContext.Provider value={value}>{children}</SessionContext.Provider>;
};
64 changes: 64 additions & 0 deletions Tokenization/webapp/app/hooks/session.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

import { useContext } from 'react';

import { SessionContext } from '../contexts/sessionContext';

/**
* Custom hook to access the current user session data.
*
* @returns {Session} The current session object
*
* @throws {Error} If the hook is used outside of SessionProvider
*
* @example
* ```tsx
* const session = useSession();
* console.log(`Welcome, ${session.name}!`);
* ```
*/
export function useSession() {
const obj = useContext(SessionContext);
if (!obj) {
throw new Error('Session wasnt created');
}
return obj.session;
}

/**
* Custom hook to check if the current user has access to a specific role.
*
* @param {string} role - The role to check access for
* @returns {boolean} True if the user has the specified role, false otherwise
*
* @throws {Error} If the hook is used outside of SessionProvider
*
* @example
* ```tsx
* const hasAdminAccess = useAuth('admin');
* const canEditTokens = useAuth('token-editor');
*
* if (hasAdminAccess) {
* // Render admin-only content
* }
* ```
*/
export function useAuth(role: string) {
const obj = useContext(SessionContext);
if (!obj) {
throw new Error('Session wasnt created');
}
return obj.hasAccess(role);
}
18 changes: 9 additions & 9 deletions Tokenization/webapp/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,18 @@ import {
Meta,
Outlet,
Scripts,
ScrollRestoration, useNavigation,
ScrollRestoration
} from 'react-router';

import { SessionProvider } from './contexts/sessionContext';
import { Spinner } from '~/ui/spinner';
import AppLayout from '~/ui/layout';

import '@aliceo2/web-ui/Frontend/css/src/bootstrap.css';
import './app.css';
import './styles/components-styles.css'
import './styles/ui-styles.css'

export function Layout({ children }: { children: React.ReactNode }) {
const { state } = useNavigation();

return (
<html lang='en'>
<head>
Expand All @@ -42,18 +40,20 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Links />
</head>
<body>
<AppLayout state={state}>
{children}
</AppLayout>
{children}
<ScrollRestoration />
<Scripts />
<Scripts />
</body>
</html>
);
}

export default function App() {
return <Outlet />;

return (
<SessionProvider>
<Outlet/>
</SessionProvider>);
}

export function HydrateFallback() {
Expand Down
12 changes: 8 additions & 4 deletions Tokenization/webapp/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
import { type RouteConfig, index, route, prefix } from '@react-router/dev/routes';

export default [
index('routes/home.tsx'),
...prefix('tokens', [
index('routes/tokens/overview.tsx'),
route(':tokenId', 'routes/tokens/details.tsx'),
route('', 'ui/layout.tsx', [
index('routes/home.tsx'),
...prefix('tokens', [
index('routes/tokens/overview.tsx'),
route(':tokenId', 'routes/tokens/details.tsx'),
]),
route('*', 'routes/404.tsx'),
]),

] satisfies RouteConfig;
26 changes: 26 additions & 0 deletions Tokenization/webapp/app/routes/404.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

/**
* 404 Not Found page component.
* Displays a user-friendly error message when a requested page doesn't exist.
*/
export default function NotFound() {
return (
<div style={{ textAlign: 'center', marginTop: '10vh' }}>
<h1>404 - Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
</div>
);
}
Loading