Skip to content
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ downloads/
eggs/
.eggs/
lib/
!webapp/lib/
lib64/
lib64
parts/
Expand Down
41 changes: 41 additions & 0 deletions webapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
1 change: 1 addition & 0 deletions webapp/.husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
4 changes: 4 additions & 0 deletions webapp/.lintstagedrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"*/**/*.{js,jsx,ts,tsx}": ["eslint"],
"*/**/*.{json,css,md}": ["prettier --write"]
}
1 change: 1 addition & 0 deletions webapp/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.11.1
18 changes: 18 additions & 0 deletions webapp/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"arrowParens": "avoid",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"bracketSameLine": false,
"jsxSingleQuote": true,
"printWidth": 120,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": true,
"plugins": ["prettier-plugin-tailwindcss"]
}
77 changes: 77 additions & 0 deletions webapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).


## Git Hooks Setup (Monorepo Configuration)

In this monorepo setup, `.git` is at the root of the repository while the webapp package is in a subfolder. The git hooks are automatically set up when you run `npm install`:

- The `postinstall` script in `package.json` runs `scripts/setup-hooks.js`
- This script automatically creates `.git/hooks/pre-commit` with the command: `cd webapp && npx lint-staged`
- Each colleague who clones the repo and runs `npm install` will automatically have the hooks configured

## Getting Started

First, run the development server:

```bash
npm run dev
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

je pense qu'il manque aussi un setup pour process.env.NOCODB_BASE_ID? Est-ce que ça lit les info du fichier .env automatiquement?

```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Components and UI libraries

We use [Shadcn](https://ui.shadcn.com/) for components, in combination with [TailwindCSS](https://tailwindcss.com/) to style a component inline.
To add a shadcn component:
```bash
npx shadcn@latest add navigation-menu
```
For icons, we use the default library in Shadcn, [Lucide](https://www.shadcn.io/icons/lucide).

## Internationalisation i18n

For the internationalisation of the app we are using [next-i18next](https://github.com/i18next/next-i18next). Our setup is based on this article from [Locize](https://www.locize.com/blog/i18n-next-app-router/).

## BlogPosts / news markdown

We use the library [react-markdown](https://remarkjs.github.io/react-markdown/) to display blog posts / news. Visit [CommonMark](https://commonmark.org) for Markdown guidelines and tutorial. You can style the components by passing the components props to ReactMarkdown.

```bash
<ReactMarkdown components={components}>{Content}</ReactMarkdown>
```

## Linting and Code Formatting

This repo uses `ESLint` [according to the official next documentation](https://nextjs.org/docs/app/api-reference/config/eslint) and prettier for linting and code formatting. The configuration for `ESLint` is located in the `.eslint.config.mjs` file, and the configuration for `prettier` is in the `.prettierrc.json` file.

With the [ESLint Prettier plugin](https://www.npmjs.com/package/eslint-plugin-prettier/v/4.0.0) Prettier runs within ESLint and doesn't need a separate command. In order to apply `ESLint` to all existing files, run:

```
npx eslint .
```

and to fix fixable errors:
```
npx eslint . --fix
```

Before each commit, our `husky🐶` uses the `lint-staged` package to automatically lint and format the staged files according to the rules specified in the `.lintstagedrc` file:

You can do this manually by running:

```
npx lint-staged
```

This ensures code quality and consistency across our codebase.
59 changes: 59 additions & 0 deletions webapp/app/[locale]/blog/[slug]/components/BlogPost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import NextImage from 'next/image'

import { format } from 'date-fns'

import ReactMarkdown from 'react-markdown'

import { cn } from '@/lib/utils'
import { BlogPost } from '@/types/apiTypes'

interface BlogpostProps {
className?: string
post: BlogPost
}

const components = {
h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
<h1 className='my-4 scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl' {...props} />
),
h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => (
<h2
className='my-3 scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0'
{...props}
/>
),
p: (props: React.HTMLAttributes<HTMLParagraphElement>) => <p className='leading-7 not-first:mt-6' {...props} />
}

const Blogpost = ({ post, className }: BlogpostProps) => {
const { Title, Image, CreatedDate, Subtitle, Content } = post

return (
<section className={cn('flex items-center justify-center py-32', className)}>
<div className='container'>
<div className='mx-auto flex max-w-5xl flex-col items-center gap-4 text-center'>
<h1 className='max-w-3xl text-5xl font-semibold text-pretty md:text-6xl'>{Title}</h1>
<h3 className='text-muted-foreground max-w-3xl text-lg md:text-xl'>{Subtitle}</h3>
<div className='flex items-center gap-3 text-sm md:text-base'>
<span>
<span className='ml-1'>on {format(CreatedDate, 'MMMM d, yyyy')}</span>
</span>
</div>
<NextImage
src={Image[0].signedUrl}
alt={Subtitle}
width={1200}
height={675}
className='mt-4 mb-8 aspect-video w-full rounded-lg border object-cover'
/>
</div>

<div className='dark:prose-invert mx-auto max-w-5xl'>
<ReactMarkdown components={components}>{Content}</ReactMarkdown>
</div>
</div>
</section>
)
}

export { Blogpost }
21 changes: 21 additions & 0 deletions webapp/app/[locale]/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { notFound } from 'next/navigation'

import { fetchBlogPost } from '@/lib/fetchBlogPost'
import { Blogpost } from './components/BlogPost'

interface PageProps {
params: { slug: string }
searchParams?: Record<string, string | string[] | undefined>
}

export default async function Page({ params }: PageProps) {
// eslint-disable-next-line @typescript-eslint/await-thenable
const { slug } = await params
const blogPost = await fetchBlogPost({ slug })

if (!slug || !blogPost) {
notFound()
} else {
return <Blogpost post={blogPost} />
}
}
52 changes: 52 additions & 0 deletions webapp/app/[locale]/blog/components/Blog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Image from 'next/image'

import { ArrowRightIcon } from 'lucide-react'

import { Button } from '@/components/ui/button'
import { Card, CardContent, CardTitle, CardDescription, CardHeader, CardFooter } from '@/components/ui/card'

import { getT } from '@/i18n/server'
import type { Locale } from '@/i18n/i18next.config'
import { BlogPost } from '@/types/apiTypes'

export default async function Blog({ blogCards, locale }: { blogCards: BlogPost[]; locale: Locale }) {
const { t } = await getT('default', { locale })

return (
<section className='py-8 sm:py-16 lg:py-24'>
<div className='mx-auto max-w-7xl px-4 sm:px-6 lg:px-8'>
<div className='mb-12 space-y-4 text-center sm:mb-16 lg:mb-24'>
<p className='text-primary text-sm font-medium uppercase'>{t('blog-list')}</p>
<h2 className='text-2xl font-semibold md:text-3xl lg:text-4xl'>{t('blog-resources')}</h2>
<p className='text-muted-foreground text-xl'>{t('blog-get-info')}</p>
</div>

<div className='grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3'>
{blogCards.map(item => (
<Card className='pt-0 shadow-none max-lg:last:col-span-full' key={item.Slug}>
<CardContent className='px-0'>
<div className='relative aspect-video h-60 w-full overflow-hidden rounded-t-xl'>
<Image src={item.Image[0].signedUrl} alt={item.Alt} fill className='object-cover' />
</div>
</CardContent>
<CardHeader className='mb-2 gap-3'>
<CardTitle className='text-xl'>
<a href={`blog/${item.Slug}`}>{item.Title}</a>
</CardTitle>
<CardDescription className='text-base'>{item.Subtitle}</CardDescription>
</CardHeader>
<CardFooter>
<Button className='group rounded-lg text-base has-[>svg]:px-6' size='lg' asChild>
<a href={`blog/${item.Slug}`}>
{t('blog-read-more')}
<ArrowRightIcon className='transition-transform duration-200 group-hover:translate-x-0.5' />
</a>
</Button>
</CardFooter>
</Card>
))}
</div>
</div>
</section>
)
}
15 changes: 15 additions & 0 deletions webapp/app/[locale]/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { fetchBlogPosts } from '@/lib/fetchBlogPosts'
import Blog from './components/Blog'
import notFound from '../../not-found'
import { Locale } from '@/i18n/i18next.config'

export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
const blogPosts = await fetchBlogPosts({ locale })

if (!blogPosts || blogPosts.length === 0) {
notFound()
}

return <Blog blogCards={blogPosts} locale={locale as Locale} />
}
3 changes: 3 additions & 0 deletions webapp/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AppLayout({ children }: { children: React.ReactNode }) {
return <>{children}</>
}
3 changes: 3 additions & 0 deletions webapp/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {
return <div>Welcome</div>
}
13 changes: 13 additions & 0 deletions webapp/app/[locale]/page/[slug]/components/VcmPollutionHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const VcmPollutionHistory = ({ dictionary }: { dictionary: Record<string, string> }) => {
return (
<div>
{/* TODO: Use https://www.abui.io/components/timeline-steps */}
<h1>{dictionary.main_title}</h1>
<h2>{dictionary.history_heading}</h2>
<div>{dictionary.history_line_1975_title}</div>
<p>{dictionary.history_line_1975_text}</p>
<div>{dictionary.history_line_1978_title}</div>
<p>{dictionary.history_line_1978_text}</p>
</div>
)
}
28 changes: 28 additions & 0 deletions webapp/app/[locale]/page/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { notFound } from 'next/navigation'

import { fetchPageDictionary } from '@/lib/fetchPageDictionary'
import { VcmPollutionHistory } from './components/VcmPollutionHistory'
import { SUB_PAGES } from '@/routes/routes'

interface PageProps {
params: { slug: string; locale: string }
searchParams?: Record<string, string | string[] | undefined>
}

export default async function Page({ params }: PageProps) {
// eslint-disable-next-line @typescript-eslint/await-thenable
const { slug, locale } = await params
const dictionary = await fetchPageDictionary({ slug, locale })

if (!slug || !dictionary) {
notFound()
}

switch (slug) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
case SUB_PAGES.VCM_POLLUTION_HISTORY:
return <VcmPollutionHistory dictionary={dictionary} />
default:
notFound()
}
}
Loading