diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6791ab4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,55 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Build outputs +dist +.next +out +build +coverage + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Git +.git +.gitignore + +# Environment +.env +.env.local +.env.*.local + +# Testing +coverage +.nyc_output + +# Logs +logs +*.log + +# Nx +.nx + +# Docker +Dockerfile +docker-compose*.yml +.dockerignore + +# Misc +*.md +!README.md +.editorconfig +.prettierrc +.eslintrc diff --git a/apps/hackathon/eslint.config.mjs b/apps/hackathon/eslint.config.mjs new file mode 100644 index 0000000..e8e4590 --- /dev/null +++ b/apps/hackathon/eslint.config.mjs @@ -0,0 +1,12 @@ +import nx from '@nx/eslint-plugin'; +import baseConfig from '../../eslint.config.mjs'; + +export default [ + ...baseConfig, + ...nx.configs['flat/react'], + { + files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + // Override or add rules here + rules: {}, + }, +]; diff --git a/apps/hackathon/index.html b/apps/hackathon/index.html new file mode 100644 index 0000000..41c9d20 --- /dev/null +++ b/apps/hackathon/index.html @@ -0,0 +1,16 @@ + + + + + Hackathon + + + + + + + +
+ + + diff --git a/apps/hackathon/postcss.config.mjs b/apps/hackathon/postcss.config.mjs new file mode 100644 index 0000000..63252e5 --- /dev/null +++ b/apps/hackathon/postcss.config.mjs @@ -0,0 +1,9 @@ +import { join } from 'path'; + +export default { + plugins: { + '@tailwindcss/postcss': { + base: join(import.meta.dirname, '../../'), + }, + }, +}; diff --git a/apps/hackathon/project.json b/apps/hackathon/project.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/apps/hackathon/project.json @@ -0,0 +1 @@ +{} diff --git a/apps/hackathon/src/app/404.tsx b/apps/hackathon/src/app/404.tsx new file mode 100644 index 0000000..2db4df6 --- /dev/null +++ b/apps/hackathon/src/app/404.tsx @@ -0,0 +1,23 @@ +import { Link } from 'react-router-dom'; + +export default function NotFoundPage() { + return ( +
+
+

404

+

+ Page Not Found +

+

+ The page you are looking for doesn't exist or has been moved. +

+ + Go Back Home + +
+
+ ); +} diff --git a/apps/hackathon/src/app/error.tsx b/apps/hackathon/src/app/error.tsx new file mode 100644 index 0000000..3859b00 --- /dev/null +++ b/apps/hackathon/src/app/error.tsx @@ -0,0 +1,29 @@ +import { useRouteError, isRouteErrorResponse } from 'react-router-dom'; + +export default function ErrorPage() { + const error = useRouteError(); + let errorMessage: string; + + if (isRouteErrorResponse(error)) { + errorMessage = error.statusText; + } else if (error instanceof Error) { + errorMessage = error.message; + } else if (typeof error === 'string') { + errorMessage = error; + } else { + console.error(error); + errorMessage = 'Unknown error'; + } + + return ( +
+
+

Oops!

+

+ Sorry, an unexpected error has occurred. +

+

{errorMessage}

+
+
+ ); +} diff --git a/apps/hackathon/src/app/layout.tsx b/apps/hackathon/src/app/layout.tsx new file mode 100644 index 0000000..d7b3c11 --- /dev/null +++ b/apps/hackathon/src/app/layout.tsx @@ -0,0 +1,10 @@ +import { Outlet, ScrollRestoration } from 'react-router-dom'; + +export default function RootLayout() { + return ( + <> + + + + ); +} diff --git a/apps/hackathon/src/app/page.tsx b/apps/hackathon/src/app/page.tsx new file mode 100644 index 0000000..3faa6a0 --- /dev/null +++ b/apps/hackathon/src/app/page.tsx @@ -0,0 +1,19 @@ +export default function HomePage() { + return ( +
+
+

+ Hackathon App +

+

+ Welcome to the Hackathon platform +

+
+

+ This is a new Vite + React app with file-based routing +

+
+
+
+ ); +} diff --git a/apps/hackathon/src/index.css b/apps/hackathon/src/index.css new file mode 100644 index 0000000..d4b5078 --- /dev/null +++ b/apps/hackathon/src/index.css @@ -0,0 +1 @@ +@import 'tailwindcss'; diff --git a/apps/hackathon/src/main.tsx b/apps/hackathon/src/main.tsx new file mode 100644 index 0000000..ea6d2ba --- /dev/null +++ b/apps/hackathon/src/main.tsx @@ -0,0 +1,45 @@ +import { createRoot } from 'react-dom/client'; +import { middleware } from './middleware'; +import { StrictMode } from 'react'; +import { createBrowserRouter, RouteObject, RouterProvider } from 'react-router'; +import { + add404PageToRoutesChildren, + addErrorElementToRoutes, + convertPagesToRoute, + ModalLoginProvider, + QueryProvider, +} from '@imphnen-frontend-service/utils'; +import { Toaster } from 'sonner'; +import './index.css'; + +const files = import.meta.glob('./app/**/*(page|layout).tsx'); +const errorFiles = import.meta.glob('./app/**/*error.tsx'); +const notFoundFiles = import.meta.glob('./app/**/*404.tsx'); +const loadingFiles = import.meta.glob('./app/**/*loading.tsx'); + +const routes = convertPagesToRoute(files, loadingFiles) as RouteObject; +addErrorElementToRoutes(errorFiles, routes); +add404PageToRoutesChildren(notFoundFiles, routes); + +const router = createBrowserRouter([ + { + ...routes, + loader: middleware, + shouldRevalidate: () => true, + }, +]); + +const rootElement = document.getElementById('root'); + +if (!rootElement) throw new Error('Failed to find the root element'); + +createRoot(rootElement).render( + + + + + + + + +); diff --git a/apps/hackathon/src/middleware.ts b/apps/hackathon/src/middleware.ts new file mode 100644 index 0000000..5e3533a --- /dev/null +++ b/apps/hackathon/src/middleware.ts @@ -0,0 +1,59 @@ +import { + PERMISSIONS, + SessionToken, + SessionUser, +} from '@imphnen-frontend-service/utils'; +import { LoaderFunctionArgs, redirect } from 'react-router'; + +const mappingPublicRoutes = [ + '/', +]; + +const mappingRoutePermissions = [ + { + path: '/dashboard', + permissions: [], + }, +]; + +const mappingPublicPrefixRoutes = [ + '/hackathons', +]; + +export const middleware = async ({ request }: LoaderFunctionArgs) => { + const url = new URL(request.url); + const pathname = url.pathname; + const session = SessionUser.get(); + const session_token = SessionToken.get(); + const token = session_token?.token?.access_token; + const userPermissions = + session?.role?.permissions?.map?.((perm) => perm?.name) ?? []; + + // Allow to access the hackathon pages without authentication + if (mappingPublicPrefixRoutes.some((prefix) => pathname.startsWith(prefix))) { + return null; + } + + if (mappingPublicRoutes.includes(pathname)) { + if (token) return redirect('/dashboard'); + return null; + } + + if (!session) return redirect('/'); + + const matchedRoute = mappingRoutePermissions.find( + (route) => route.path === pathname + ); + + if (matchedRoute) { + const hasPermission = + !matchedRoute.permissions || + matchedRoute.permissions.some((perm) => userPermissions.includes(perm)); + + if (!hasPermission) { + return '/dashboard'; + } + } + + return null; +}; diff --git a/apps/hackathon/tsconfig.app.json b/apps/hackathon/tsconfig.app.json new file mode 100644 index 0000000..6d35def --- /dev/null +++ b/apps/hackathon/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [ + "node", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts", + "vite/client" + ] + }, + "exclude": [ + "src/**/*.spec.ts", + "src/**/*.test.ts", + "src/**/*.spec.tsx", + "src/**/*.test.tsx", + "src/**/*.spec.js", + "src/**/*.test.js", + "src/**/*.spec.jsx", + "src/**/*.test.jsx", + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts" + ], + "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] +} diff --git a/apps/hackathon/tsconfig.json b/apps/hackathon/tsconfig.json new file mode 100644 index 0000000..fdfab6c --- /dev/null +++ b/apps/hackathon/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "types": ["vite/client", "vitest"] + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "extends": "../../tsconfig.base.json" +} diff --git a/apps/hackathon/tsconfig.spec.json b/apps/hackathon/tsconfig.spec.json new file mode 100644 index 0000000..9d3bb72 --- /dev/null +++ b/apps/hackathon/tsconfig.spec.json @@ -0,0 +1,30 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest", + "@nx/react/typings/cssmodule.d.ts", + "@nx/react/typings/image.d.ts" + ] + }, + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/apps/hackathon/vite.config.ts b/apps/hackathon/vite.config.ts new file mode 100644 index 0000000..3c4d082 --- /dev/null +++ b/apps/hackathon/vite.config.ts @@ -0,0 +1,42 @@ +/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; +import { nxCopyAssetsPlugin } from '@nx/vite/plugins/nx-copy-assets.plugin'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/apps/hackathon', + server: { + port: 3004, + host: 'localhost', + }, + preview: { + port: 3005, + host: 'localhost', + }, + plugins: [react(), nxViteTsPaths(), nxCopyAssetsPlugin(['*.md'])], + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + build: { + outDir: '../../dist/apps/hackathon', + emptyOutDir: true, + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + }, + test: { + watch: false, + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/apps/hackathon', + provider: 'v8' as const, + }, + }, +})); diff --git a/docker-compose-backoffice.yml b/docker-compose-backoffice.yml index 4da19f9..738529e 100644 --- a/docker-compose-backoffice.yml +++ b/docker-compose-backoffice.yml @@ -1,10 +1,9 @@ -version: '3.8' services: backoffice: build: context: . dockerfile: docker/backoffice.Dockerfile ports: - - "3001:80" + - '127.0.0.1:3001:80' restart: unless-stopped container_name: backoffice diff --git a/docker-compose-dimentorin.yml b/docker-compose-dimentorin.yml index 049c32a..ae6a152 100644 --- a/docker-compose-dimentorin.yml +++ b/docker-compose-dimentorin.yml @@ -1,10 +1,9 @@ -version: '3.8' services: dimentorin: build: context: . dockerfile: docker/dimentorin.Dockerfile ports: - - "3002:80" + - '127.0.0.1:3002:80' restart: unless-stopped container_name: dimentorin diff --git a/docker-compose-gacha.yml b/docker-compose-gacha.yml index 6bec329..1f73d65 100644 --- a/docker-compose-gacha.yml +++ b/docker-compose-gacha.yml @@ -1,10 +1,9 @@ -version: '3.8' services: gacha: build: context: . dockerfile: docker/gacha.Dockerfile ports: - - "3003:80" + - '127.0.0.1:3003:80' restart: unless-stopped container_name: gacha diff --git a/docker-compose-hackathon.yml b/docker-compose-hackathon.yml new file mode 100644 index 0000000..3890532 --- /dev/null +++ b/docker-compose-hackathon.yml @@ -0,0 +1,9 @@ +services: + hackathon: + build: + context: . + dockerfile: docker/hackathon.Dockerfile + ports: + - '127.0.0.1:3004:80' + restart: unless-stopped + container_name: hackathon diff --git a/docker-compose-landing.yml b/docker-compose-landing.yml index 34feeca..04d8f86 100644 --- a/docker-compose-landing.yml +++ b/docker-compose-landing.yml @@ -1,10 +1,9 @@ -version: '3.8' services: landing: build: context: . dockerfile: docker/landing.Dockerfile ports: - - "3000:3000" + - '127.0.0.1:3005:3000' restart: unless-stopped container_name: landing diff --git a/docker/hackathon.Dockerfile b/docker/hackathon.Dockerfile new file mode 100644 index 0000000..f39fbbf --- /dev/null +++ b/docker/hackathon.Dockerfile @@ -0,0 +1,16 @@ +FROM node:22-alpine AS deps +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm install + +FROM node:22-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN npm run hackathon:build + +FROM nginx:alpine AS runner +RUN rm -rf /usr/share/nginx/html/* +COPY --from=builder /app/dist/apps/hackathon /usr/share/nginx/html +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] diff --git a/package.json b/package.json index 77ddfa9..880ae7c 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ "dimentorin:test": "nx test dimentorin", "dimentorin:build": "nx build dimentorin", "dimentorin:prod": "serve ./dist/apps/dimentorin", + "hackathon:dev": "nx dev hackathon", + "hackathon:build": "nx build hackathon", + "hackathon:test": "nx test hackathon", + "hackathon:prod": "serve ./dist/apps/hackathon", "landing:dev": "nx serve landing", "landing:build": "nx build landing", "landing:prod": "cd ./dist/apps/landing && npx next start",