generated from avior/template-web-astro
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
851ae7a528 | |||
f671a21d2f | |||
3fc900a1e6 | |||
dc58e29760 | |||
40d0d84619 | |||
962ba24f7b | |||
e16134f15b | |||
83d66eb440 | |||
5c49bd2190 | |||
c4e702af67 | |||
30c0ac14a3 | |||
a8ce0b4494 | |||
45e79cf212 | |||
fa8f946ff5 | |||
9ffffcd927 | |||
9a96cb063a | |||
4651debb38 | |||
a641749d45 | |||
898d5a241a | |||
ca799e17b0 | |||
0ff26c24b8 | |||
4a944f9e61 | |||
9a70042506 | |||
0f1a3b7cc8 | |||
fb97ea4a9d |
53
.github/workflows/build_and_check.yml
vendored
53
.github/workflows/build_and_check.yml
vendored
@ -2,9 +2,13 @@ name: Build, check & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
- v*
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
run:
|
||||
@ -13,23 +17,36 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
node-version: 20
|
||||
images: git.dzeio.com/avior/avior.me
|
||||
tags: |
|
||||
type=edge
|
||||
type=ref,event=pr
|
||||
type=ref,event=branch,prefix=branch-
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern=latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.dzeio.com
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Check
|
||||
run: npm run check
|
||||
|
||||
- name: Prepare Tests
|
||||
run: npm run install:test
|
||||
|
||||
- name: Test
|
||||
run : npm run test
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: ./Dockerfile.static
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -4,5 +4,8 @@
|
||||
},
|
||||
"tailwindCSS.includeLanguages": {
|
||||
"astro": "html"
|
||||
},
|
||||
"files.associations": {
|
||||
"*.mdx": "markdown"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# This Dockerfile allows you to run Astro in a static container (no server side)
|
||||
# This Dockerfile allows you to run Astro in a static container using NGINX (no server side)
|
||||
|
||||
#########
|
||||
# Build #
|
||||
@ -36,3 +36,5 @@ ADD ./nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Copy dist fro mthe build image
|
||||
COPY --from=BUILD_IMAGE /home/node/dist ./
|
||||
|
||||
EXPOSE 3000
|
@ -1,44 +1,43 @@
|
||||
import { defineConfig } from 'astro/config'
|
||||
import tailwind from "@astrojs/tailwind"
|
||||
import node from "@astrojs/node"
|
||||
|
||||
// const faviconHook = {
|
||||
// name: 'Favicon',
|
||||
// hooks: {
|
||||
// "astro:build:setup": async () => {
|
||||
// await Manifest.create('./src/assets/favicon.png', {
|
||||
// name: 'Template'
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [tailwind()],
|
||||
compressHTML: true,
|
||||
build: {
|
||||
assets: 'assets',
|
||||
inlineStylesheets: 'auto'
|
||||
},
|
||||
server: {
|
||||
host: true,
|
||||
port: 3000
|
||||
},
|
||||
trailingSlash: 'never',
|
||||
vite: {
|
||||
server: {
|
||||
watch: {
|
||||
// support WSL strange things
|
||||
usePolling: !!process.env.WSL_DISTRO_NAME
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Customizable depending on goal
|
||||
output: 'static',
|
||||
// adapter: node({
|
||||
// mode: "standalone"
|
||||
// }),
|
||||
site: 'https://avior.me',
|
||||
})
|
||||
import { defineConfig } from 'astro/config'
|
||||
import tailwind from "@astrojs/tailwind"
|
||||
import mdx from "@astrojs/mdx"
|
||||
|
||||
// const faviconHook = {
|
||||
// name: 'Favicon',
|
||||
// hooks: {
|
||||
// "astro:build:setup": async () => {
|
||||
// await Manifest.create('./src/assets/favicon.png', {
|
||||
// name: 'Template'
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [tailwind(), mdx()],
|
||||
compressHTML: true,
|
||||
build: {
|
||||
assets: 'assets',
|
||||
inlineStylesheets: 'auto'
|
||||
},
|
||||
server: {
|
||||
host: true,
|
||||
port: 3000
|
||||
},
|
||||
trailingSlash: 'never',
|
||||
vite: {
|
||||
server: {
|
||||
watch: {
|
||||
// support WSL strange things
|
||||
usePolling: !!process.env.WSL_DISTRO_NAME
|
||||
}
|
||||
}
|
||||
},
|
||||
// Customizable depending on goal
|
||||
output: 'static',
|
||||
// adapter: node({
|
||||
// mode: "standalone"
|
||||
// }),
|
||||
site: 'https://avior.me'
|
||||
})
|
||||
|
26
nginx.conf
26
nginx.conf
@ -9,6 +9,16 @@ http {
|
||||
listen 3000;
|
||||
listen [::]:3000;
|
||||
|
||||
# Redirect from non-www to www
|
||||
# if ($host ~ ^(?!www\.)(?<domain>.+)$) {
|
||||
# return 301 $scheme://www.$domain$request_uri;
|
||||
# }
|
||||
|
||||
# Redirect from www to non-www
|
||||
if ($host ~ ^www\.(?<domain>.+)$) {
|
||||
return 301 $scheme://$domain$request_uri;
|
||||
}
|
||||
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
@ -18,13 +28,23 @@ http {
|
||||
gzip on;
|
||||
gzip_min_length 1000;
|
||||
gzip_proxied expired no-cache no-store private auth;
|
||||
gzip_types text/html text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
error_page 404 /404.html;
|
||||
error_page 500 502 503 504 /500.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
|
||||
# Security headers (note: temporaly unvailable "prefetch-src 'self'; ")
|
||||
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none'; form-action 'self'; manifest-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src data: 'self' https://img.shields.io; font-src 'self'; connect-src 'self'; base-uri 'self';";
|
||||
add_header X-Frame-Options "DENY";
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
add_header Referrer-Policy "no-referrer";
|
||||
add_header Permissions-Policy "geolocation=(), microphone=(), interest-cohort=()";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||
add_header X-Download-Options "noopen";
|
||||
|
||||
location / {
|
||||
try_files $uri $uri.html $uri/index.html /$uri /$uri/index.html /index.html;
|
||||
try_files $uri $uri.html $uri/index.html /$uri /$uri/index.html =404;
|
||||
}
|
||||
|
||||
# Plausible script
|
||||
|
799
package-lock.json
generated
799
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,12 +15,12 @@
|
||||
"install:test": "playwright install --with-deps"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^6",
|
||||
"@astrojs/mdx": "^1.1.0",
|
||||
"@astrojs/tailwind": "^5",
|
||||
"@dzeio/logger": "^3",
|
||||
"@dzeio/object-util": "^1",
|
||||
"@dzeio/url-manager": "^1",
|
||||
"@fontsource-variable/lexend": "^5.0.12",
|
||||
"@fontsource-variable/lexend": "^5.1.1",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"astro": "^3",
|
||||
"astro-seo": "^0.8.0",
|
||||
|
5
public/robots.txt
Normal file
5
public/robots.txt
Normal file
@ -0,0 +1,5 @@
|
||||
User-Agent: *
|
||||
Disallow:
|
||||
Disallow: /cv
|
||||
|
||||
Sitemap: https://avior.me/sitemap.xml
|
1
src/assets/pages/404/404.light.svg
Normal file
1
src/assets/pages/404/404.light.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 23 KiB |
1
src/assets/pages/404/404.svg
Normal file
1
src/assets/pages/404/404.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 23 KiB |
BIN
src/assets/pages/cv/me.jpg
Normal file
BIN
src/assets/pages/cv/me.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
89
src/components/Picture.astro
Normal file
89
src/components/Picture.astro
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
import { getImage } from 'astro:assets'
|
||||
import AstroUtils from '../libs/AstroUtils'
|
||||
import { objectOmit } from '@dzeio/object-util'
|
||||
|
||||
const formats = [
|
||||
'avif',
|
||||
'webp'
|
||||
]
|
||||
|
||||
export interface Props extends Omit<astroHTML.JSX.ImgHTMLAttributes, 'src'> {
|
||||
src: ImageMetadata | string
|
||||
srcDark?: ImageMetadata | string
|
||||
width?: number
|
||||
height?: number
|
||||
innerClass?: string
|
||||
}
|
||||
|
||||
type PictureResult = {
|
||||
format: 'new'
|
||||
formats: Array<{format: string, img: Awaited<ReturnType<typeof getImage>>}>
|
||||
src: Awaited<ReturnType<typeof getImage>>
|
||||
} | {
|
||||
format: 'raw'
|
||||
src: string
|
||||
}
|
||||
|
||||
interface Result {
|
||||
light: PictureResult
|
||||
dark?: PictureResult | undefined
|
||||
}
|
||||
|
||||
async function resolvePicture(image: ImageMetadata | string): Promise<PictureResult> {
|
||||
const ext = typeof image === 'string' ? image.substring(image.lastIndexOf('.')) : image.format
|
||||
if (ext === 'svg') {
|
||||
return {
|
||||
format: 'raw',
|
||||
src: typeof image === 'string' ? image : image.src
|
||||
}
|
||||
}
|
||||
|
||||
const imageFormats: Array<{format: string, img: Awaited<ReturnType<typeof getImage>>}> = await Promise.all(
|
||||
formats.map(async (it) => ({
|
||||
img: await getImage({src: Astro.props.src, format: it, width: Astro.props.width, height: Astro.props.height}),
|
||||
format: it
|
||||
}))
|
||||
)
|
||||
|
||||
const orig = await getImage({src: Astro.props.src, format: ext, width: Astro.props.width, height: Astro.props.height})
|
||||
|
||||
return {
|
||||
format: 'new',
|
||||
formats: imageFormats,
|
||||
src: orig
|
||||
}
|
||||
}
|
||||
|
||||
const res = await AstroUtils.wrap<Result>(async () => {
|
||||
return {
|
||||
light: await resolvePicture(Astro.props.src),
|
||||
dark: Astro.props.srcDark ? await resolvePicture(Astro.props.srcDark) : undefined
|
||||
}
|
||||
})
|
||||
|
||||
const props = objectOmit(Astro.props, 'src', 'srcDark', 'class')
|
||||
|
||||
---
|
||||
|
||||
{res.light.format === 'new' && (
|
||||
<picture {...props} {...res.light.src.attributes} class:list={[res.light.src.attributes.class, Astro.props.class, {'dark:hidden': res.dark}]}>
|
||||
{res.light.formats.map((it) => (
|
||||
<source srcset={it.img.src} type={`image/${it.format}`} />
|
||||
))}
|
||||
<img src={res.light.src.src} class={Astro.props.innerClass} />
|
||||
</picture>
|
||||
) || (
|
||||
<img {...props} class:list={[Astro.props.class, {'dark:hidden': res.dark}]} src={res.light.src as string} />
|
||||
)}
|
||||
|
||||
{res.dark && res.dark.format === 'new' && (
|
||||
<picture {...props} {...res.dark.src.attributes} class:list={[res.dark.src.attributes.class, Astro.props.class, 'hidden', 'dark:block']}>
|
||||
{res.dark.formats.map((it) => (
|
||||
<source srcset={it.img.src} type={`image/${it.format}`} />
|
||||
))}
|
||||
<img src={res.dark.src.src} class={Astro.props.innerClass} />
|
||||
</picture>
|
||||
) || (res.dark && (
|
||||
<img {...props} class:list={[Astro.props.class, 'hidden', 'dark:block']} src={res.dark.src as string} />
|
||||
))}
|
@ -1,42 +0,0 @@
|
||||
---
|
||||
import { LocalImageProps, RemoteImageProps, getImage } from 'astro:assets'
|
||||
import AstroUtils from '../libs/AstroUtils'
|
||||
type ImageProps = LocalImageProps | RemoteImageProps
|
||||
export type Props = ImageProps
|
||||
|
||||
const res = await AstroUtils.wrap(async () => {
|
||||
const image = Astro.props.src
|
||||
const ext = typeof image === 'string' ? image.substring(image.lastIndexOf('.')) : image.format
|
||||
if (ext === 'svg') {
|
||||
return {
|
||||
format: 'raw',
|
||||
props: {
|
||||
...Astro.props,
|
||||
src: typeof image === 'string' ? image : image.src
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const avif = await getImage({src: Astro.props.src, format: 'avif'})
|
||||
const webp = await getImage({src: Astro.props.src, format: 'webp'})
|
||||
const orig = await getImage({src: Astro.props.src, format: ext})
|
||||
|
||||
return {
|
||||
format: 'new',
|
||||
avif,
|
||||
webp,
|
||||
orig
|
||||
}
|
||||
})
|
||||
|
||||
---
|
||||
|
||||
{res.format === 'new' && (
|
||||
<picture class:list={[res.orig!.attributes.class, Astro.props.class]}>
|
||||
<source srcset={res.avif!.src} type="image/avif" />
|
||||
<source srcset={res.webp!.src} type="image/webp" />
|
||||
<img src={res.orig!.src} class="" {...res.orig!.attributes} />
|
||||
</picture>
|
||||
) || (
|
||||
<img {...res.props} />
|
||||
)}
|
@ -2,20 +2,20 @@
|
||||
interface Props {
|
||||
items: Array<{
|
||||
text: string
|
||||
href?: string
|
||||
href?: string | undefined
|
||||
}>
|
||||
}
|
||||
---
|
||||
|
||||
<nav>
|
||||
<ol vocab="https://schema.org/" typeof="BreadcrumbList">
|
||||
<ol vocab="https://schema.org/" typeof="BreadcrumbList" class="inline-flex items-center flex-wrap px-0 mb-4">
|
||||
{Astro.props.items.map((el, index) => (
|
||||
<li property="itemListElement" typeof="ListItem">
|
||||
<li property="itemListElement" typeof="ListItem" class="inline-block px-0">
|
||||
{index > 0 && (
|
||||
<span class="text-slate-900 dark:text-slate-100 mx-4">/</span>
|
||||
<span class="text-gray-900 dark:text-gray-100 mx-4">/</span>
|
||||
)}
|
||||
{el.href ? (
|
||||
<a class="text-slate-900 dark:text-slate-100" href={el.href} property="item" typeof="WebPage">
|
||||
<a class="text-gray-900 dark:text-gray-100 font-normal" href={el.href} property="item" typeof="WebPage">
|
||||
<span property="name">{el.text}</span>
|
||||
</a>
|
||||
) : (
|
||||
@ -26,15 +26,3 @@ interface Props {
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
ol {
|
||||
@apply inline-flex items-center flex-wrap px-0 mb-4
|
||||
}
|
||||
li {
|
||||
@apply inline-block px-0
|
||||
}
|
||||
a {
|
||||
@apply font-normal
|
||||
}
|
||||
</style>
|
||||
|
@ -1,18 +1,12 @@
|
||||
---
|
||||
import Logo from 'assets/logo.svg'
|
||||
import LogoDark from 'assets/logo.dark.svg'
|
||||
import { Image } from 'astro:assets'
|
||||
import Picture from 'components/Picture.astro'
|
||||
---
|
||||
|
||||
<header class="bg-white/1 flex justify-center sm:justify-normal">
|
||||
<header class="bg-white/1 z-10 justify-center sm:justify-normal transition-opacity
|
||||
fixed w-full h-20 flex items-center border-b border-slate-900/10 backdrop-blur-md">
|
||||
<a href="/">
|
||||
<Image src={Logo} alt="Avior.me Logo" class="h-9 dark:hidden" />
|
||||
<Image src={LogoDark} alt="Avior.me Logo" class="h-9 hidden dark:block" />
|
||||
<Picture src={Logo} srcDark={LogoDark} alt="Avior.me Logo" class="h-9" />
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
@apply transition-opacity fixed w-full h-20 flex items-center border-b border-slate-900/10 backdrop-blur-md
|
||||
}
|
||||
</style>
|
||||
|
@ -11,11 +11,13 @@ const projectsCollection = defineCollection({
|
||||
link: z.object({
|
||||
href: z.string(),
|
||||
rel: z.string().optional(),
|
||||
text: z.string().optional()
|
||||
text: z.string().optional(),
|
||||
target: z.string().optional()
|
||||
}).optional(),
|
||||
disabled: z.string().optional(),
|
||||
created: z.date().optional(),
|
||||
updated: z.date().optional()
|
||||
updated: z.date().optional(),
|
||||
techs: z.string().array().optional()
|
||||
})
|
||||
})
|
||||
const blogCollection = defineCollection({
|
||||
|
@ -7,6 +7,13 @@ link:
|
||||
image: ./og.png
|
||||
created: 2022-09-03
|
||||
updated: 2023-07-02
|
||||
techs:
|
||||
- NextJS
|
||||
- Typescript
|
||||
- Appwrite
|
||||
- Scrapping
|
||||
- Docker
|
||||
- Stylus
|
||||
---
|
||||
|
||||

|
||||
@ -49,21 +56,21 @@ FI3D propose également des fonctionnalités pratiques telles que le thème somb
|
||||
|
||||
Que l'on travaille sur des prototypes, des maquettes, des objets décoratifs ou des pièces fonctionnelles, FI3D offre un monde de possibilités. Découvrez la polyvalence de l'impression 3D en explorant les différents types de filaments disponibles, des PLA aux ABS en passant par le PETG et bien d'autres.
|
||||
|
||||
<!-- 
|
||||
*Image 7 : Objets imprimés en 3D avec différents filaments* -->
|
||||
{/* 
|
||||
*Image 7 : Objets imprimés en 3D avec différents filaments* */}
|
||||
|
||||
**Notez et Partagez Vos Expériences**
|
||||
|
||||
Il est encouragé de partager ses expériences et ses avis sur les filaments. Si l'on a utilisé un filament particulier et que l'on souhaite aider d'autres membres de la communauté FI3D, il suffit de laisser une note et un commentaire.
|
||||
|
||||
<!-- 
|
||||
*Image 8 : Utilisateur laissant une critique sur un filament* -->
|
||||
{/* 
|
||||
*Image 8 : Utilisateur laissant une critique sur un filament* */}
|
||||
|
||||
**Inscrivez-vous dès aujourd'hui sur FI3D**
|
||||
|
||||
Pour profiter pleinement de toutes les fonctionnalités de FI3D, il est possible de s'inscrire gratuitement sur la plateforme. On pourra ainsi ajouter des filaments à sa liste de favoris, suivre les dernières tendances en matière d'impression 3D et participer à une communauté dynamique de passionnés d'impression 3D.
|
||||
|
||||
<!-- 
|
||||
*Image 9: Processus d'inscription sur FI3D* -->
|
||||
{/* 
|
||||
*Image 9: Processus d'inscription sur FI3D* */}
|
||||
|
||||
Que l'on soit un professionnel de l'industrie, un bricoleur ou simplement curieux de découvrir le monde de l'impression 3D, FI3D est l'outil idéal pour trouver le filament qui répond à ses besoins.
|
@ -6,6 +6,14 @@ link:
|
||||
image: ./og.png
|
||||
created: 2018-09-10
|
||||
updated: 2021-04-29
|
||||
techs:
|
||||
- NextJS
|
||||
- Typescript
|
||||
- MongoDB
|
||||
- Firebase
|
||||
- Symphony
|
||||
- Docker
|
||||
- Stylus
|
||||
---
|
||||
|
||||

|
@ -7,14 +7,14 @@ import Breadcrumb from 'components/global/Breadcrumb.astro'
|
||||
export interface Props extends BaseProps {
|
||||
link?: {
|
||||
href: string
|
||||
rel?: string
|
||||
text?: string
|
||||
target?: string
|
||||
}
|
||||
disabled?: string
|
||||
rel?: string | undefined
|
||||
text?: string | undefined
|
||||
target?: string | undefined
|
||||
} | undefined
|
||||
disabled?: string | undefined
|
||||
breadcrumb: Array<{
|
||||
text: string
|
||||
href?: string
|
||||
href?: string | undefined
|
||||
}>
|
||||
}
|
||||
---
|
||||
|
@ -1,6 +1,8 @@
|
||||
---
|
||||
import Head, { type Props as HeadProps } from './Head.astro'
|
||||
|
||||
import '@fontsource-variable/lexend'
|
||||
|
||||
export interface Props extends HeadProps {}
|
||||
---
|
||||
|
||||
@ -13,3 +15,9 @@ export interface Props extends HeadProps {}
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style is:global>
|
||||
html {
|
||||
font-weight: 350;
|
||||
}
|
||||
</style>
|
||||
|
@ -5,23 +5,23 @@ import IconPNG from '../assets/layouts/Base/favicon.png'
|
||||
import '@fontsource-variable/lexend'
|
||||
|
||||
export interface Props {
|
||||
siteName?: string
|
||||
title?: string
|
||||
description?: string
|
||||
canonical?: string
|
||||
image?: Array<typeof IconPNG>
|
||||
siteName?: string | undefined
|
||||
title?: string | undefined
|
||||
description?: string | undefined
|
||||
canonical?: string | undefined
|
||||
image?: Array<typeof IconPNG> | undefined
|
||||
twitter?: {
|
||||
title?: string
|
||||
card?: "summary" | "summary_large_image" | "app" | "player"
|
||||
site?: string
|
||||
creator?: string
|
||||
}
|
||||
title?: string | undefined
|
||||
card?: "summary" | "summary_large_image" | "app" | "player" | undefined
|
||||
site?: string | undefined
|
||||
creator?: string | undefined
|
||||
} | undefined
|
||||
og?: {
|
||||
title?: string
|
||||
type?: string
|
||||
description?: string
|
||||
url?: string
|
||||
}
|
||||
title?: string | undefined
|
||||
type?: string | undefined
|
||||
description?: string | undefined
|
||||
url?: string | undefined
|
||||
} | undefined
|
||||
}
|
||||
|
||||
const props = Astro.props
|
||||
|
25
src/pages/404.astro
Normal file
25
src/pages/404.astro
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
import Layout from 'layouts/Layout.astro'
|
||||
import I404 from 'assets/pages/404/404.svg'
|
||||
import I404Light from 'assets/pages/404/404.light.svg'
|
||||
import Button from 'components/global/Button.astro'
|
||||
import ButtonLink from 'components/global/ButtonLink.astro'
|
||||
import Picture from 'components/Picture.astro'
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<main class="container flex flex-col gap-24 justify-center items-center md:mt-6">
|
||||
<h1 class="text-6xl text-center font-bold">404 La page recherché n'existe pas :(</h1>
|
||||
<Picture src={I404Light} srcDark={I404} alt="404 error image" />
|
||||
<div class="flex gap-6">
|
||||
<ButtonLink href="/">Retour à la page d'accueil</ButtonLink>
|
||||
<Button id="back_button">Retour à la page précédente</Button>
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
(document.querySelector('button.back_button') as HTMLButtonElement).addEventListener('click', () => {
|
||||
history.back()
|
||||
})
|
||||
</script>
|
158
src/pages/cv.astro
Normal file
158
src/pages/cv.astro
Normal file
@ -0,0 +1,158 @@
|
||||
---
|
||||
import Base from 'layouts/Base.astro'
|
||||
import Picture from 'components/Picture.astro'
|
||||
import Me from 'assets/pages/cv/me.jpg'
|
||||
import { objectMap } from '@dzeio/object-util'
|
||||
|
||||
const skills = {
|
||||
'Langages': 'Typescript/JavaScript, Web (HTML/CSS), Kotlin, C++',
|
||||
'Frameworks': 'Astro, NextJS, Android (MVVM), PlatformIO',
|
||||
'Softwares': 'Git, VSCode, Zed',
|
||||
'Tertiaire': 'Gestion de projet, Encadrement de collaborateurs'
|
||||
} as const
|
||||
|
||||
const skillColor: Record<keyof typeof skills, string> = {
|
||||
'Langages': 'amber',
|
||||
'Frameworks': 'sky',
|
||||
'Softwares': 'teal',
|
||||
'Tertiaire': 'gray'
|
||||
}
|
||||
|
||||
const passions: Array<string> = [
|
||||
'Pokémon',
|
||||
'Tennis',
|
||||
'Programation'
|
||||
]
|
||||
|
||||
const jobs: Array<{
|
||||
company: string
|
||||
start: string
|
||||
end?: string
|
||||
projects: Array<{
|
||||
name: string
|
||||
client?: string
|
||||
description: string
|
||||
technics?: Array<string>
|
||||
}>
|
||||
}> = [{
|
||||
company: 'Aptatio',
|
||||
start: '2019-09-30',
|
||||
projects: [{
|
||||
name: 'Ifremer LOOP',
|
||||
client: 'Ifremer',
|
||||
description: 'Développement d’une application de gestion des réponses des appels à données et participations aux groupes internationaux',
|
||||
technics: [
|
||||
'Gestion de projet',
|
||||
'Conception de logiciel',
|
||||
'Développement de logiciel Web (Framework Astro & Typescript)'
|
||||
]
|
||||
}, {
|
||||
name: 'Gwaleen',
|
||||
client: 'Ifremer & Aptatio',
|
||||
description: 'Développement de plusieurs modules logiciel des Ichtyomètre éléctronique Gwaleen',
|
||||
technics: [
|
||||
'Création et maintenance d\'un Logiciel Android de Gestion de donnée provenant d\'Ichtyomètres Connecté (Kotlin)',
|
||||
'Conception et Création d\'un protocol de communication entre 2 appareil Android en bluetooth pour de la donnée Vidéo (Kotlin)',
|
||||
'Gestion et création du code embarqué dans les Ichtyomètres Gwaleen (C++)'
|
||||
]
|
||||
}, {
|
||||
name: 'ImmerCité',
|
||||
client: 'La cité des congrès de Nantes',
|
||||
description: 'Création d\'ImmerCité, un logiciel immersif pour la présentation d\'événements, primé au concours international des Cités des Congrès et lauréat du 1er prix Innovation Award à la conférence annuelle de l\'AIPC au Costa Rica.',
|
||||
technics: [
|
||||
'Conception de logiciel',
|
||||
'Développement de logiciel Web (Framework NextJS & Typescript)',
|
||||
'Création d\'un engine de gestion 2D pour le placement et l\'automatisation des objets dans les salles de la cité',
|
||||
'Gestion de la communication et synchronisation entre plusieurs ordinateurs (WebSocket & ExpressJS)',
|
||||
'Programation de la 3D dans Unity puis Godot (C#, GDScript)'
|
||||
]
|
||||
}, {
|
||||
name: 'Gestion de l\'infrastructure',
|
||||
description: 'Gestion de l\'infrastructure Réseau de l\'entreprise',
|
||||
technics: [
|
||||
'mise en place d\'un SSO (Single sign-on)',
|
||||
'Gestion de la donnée utilisateur/client',
|
||||
'Mise en ligne des logiciels Client',
|
||||
'Maintenance Préventive des serveurs'
|
||||
]
|
||||
}]
|
||||
}, {
|
||||
company: 'Auto-Entreprise',
|
||||
start: '2019-04-01',
|
||||
projects: [{
|
||||
name: 'TCGdex',
|
||||
description: 'Développement de TCGdex, une API open source multilingue (14 langues) pour le jeu de cartes Pokémon JCC, offrant des fonctionnalités avancées aux développeurs et amateurs du jeu.',
|
||||
technics: [
|
||||
'Récupération des données depuis une multitude de sources',
|
||||
'Création et maintenance d\'une API avec + de 4 Millions de requettes par mois',
|
||||
'Mise en place d\'outillages pour simplifier l\'usage de l\'API aux développeurs',
|
||||
'Maintenance et gestion d\'une communauté autour du jeux de carte Pokémon'
|
||||
]
|
||||
}]
|
||||
}]
|
||||
---
|
||||
|
||||
<Base>
|
||||
<main class="container py-8">
|
||||
<div class="flex gap-4">
|
||||
<div class="w-1/4 flex flex-col gap-4">
|
||||
<Picture src={Me} innerClass="rounded-xl" />
|
||||
<h1 class="font-bold text-5xl text-center">Florian Bouillon</h1>
|
||||
<div>
|
||||
<p>Compétences :</p>
|
||||
<div class="hidden bg-amber-300 bg-sky-300 bg-teal-300"></div>
|
||||
<ul class="pl-6 list-disc">
|
||||
{objectMap(skills, (it, key) => (
|
||||
<li>{key}: {it.split(',').map((it) => <span class:list={[`bg-${skillColor[key]}-300`, "inline-block px-2 py-1 m-1 text-black rounded-xl"]}>{it}</span>)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p>Passions :</p>
|
||||
<ul class="pl-2 list-disc">
|
||||
{passions.map((it) => (
|
||||
<li>{it}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-3/4">
|
||||
<div class="">
|
||||
<h2 class="text-4xl font-bold">Expérience professionnel</h2>
|
||||
{jobs.map((company) => (
|
||||
<div class="mt-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-2xl font-semibold">{company.company}</h2>
|
||||
<div class="italic">{company.start} - {company.end ?? 'Aujourd\'hui'}</div>
|
||||
</div>
|
||||
<div class="pl-2">
|
||||
{company.projects.map((it, idx) => (
|
||||
<div>
|
||||
{idx > 0 && (
|
||||
<hr class="my-2 border-t-2 border-gray-500" />
|
||||
)}
|
||||
<div class="flex justify-between items-center">
|
||||
<p class="text-xl font-[550]">- {it.name}</p>
|
||||
{it.client && (
|
||||
<p>{it.client}</p>
|
||||
)}
|
||||
</div>
|
||||
<p class="mt-2 pl-4">{it.description}</p>
|
||||
{it.technics && (
|
||||
<div class="pl-4 mt-2">
|
||||
<p>Tâches:</p>
|
||||
<ul class="list-disc pl-6">
|
||||
{it.technics?.map((tech) => <li>{tech}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
@ -1,60 +1,60 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content'
|
||||
import { Image } from 'astro:assets'
|
||||
import Layout from 'layouts/Layout.astro'
|
||||
|
||||
const projects = await getCollection('projects')
|
||||
const clients = await Promise.all((await getCollection('clients')).map(async (it) => ({...it, obj: await it.render()})))
|
||||
---
|
||||
|
||||
<Layout title="Avior.me - Developpement de solutions selon vos besoins">
|
||||
<main class="container">
|
||||
|
||||
<div class="flex justify-center items-center h-64">
|
||||
<h1 class="text-4xl font-bold">Développement de solutions selon vos besoins</h1>
|
||||
</div>
|
||||
<h2 class="text-center md:text-left text-2xl font-semibold mb-4">Projets</h2>
|
||||
<div class="md:grid grid-cols-3 gap-4">
|
||||
{projects.map((it) => (
|
||||
<a href={`/projets/${it.slug}`} class="flex flex-col gap-4 mb-6 md:mb-0">
|
||||
{it.data.image && (
|
||||
<Image src={it.data.image} alt="" />
|
||||
)}
|
||||
<p>{it.data.title}</p>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<h2 class="text-center md:text-left text-2xl font-semibold my-8">Clients</h2>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div></div>
|
||||
<div class="prose dark:prose-invert max-w-none">
|
||||
<p class="font-semibold">Tâches :</p>
|
||||
</div>
|
||||
</div>
|
||||
{clients.map((client, index) => {
|
||||
const { Content } = client.obj
|
||||
client.data.logo
|
||||
return (
|
||||
<>
|
||||
{index > 0 && (
|
||||
<div class="hidden md:block w-10/12 mx-auto h-0.5 bg-gray-300 dark:bg-gray-700"></div>
|
||||
)}
|
||||
<div class="mt-6 md:mt-0 md:grid grid-cols-2 items-center">
|
||||
<a href={client.data.site} target="_blank" rel="noreferrer nofollow" class="flex flex-col gap-4">
|
||||
{client.data.logo && (
|
||||
<Image class:list={{'mx-auto': true, 'md:mx-0': true, 'dark:invert': client.data.logo.invert}} src={client.data.logo.src} height={48} alt="" />
|
||||
) || (
|
||||
<div>{client.data.title}</div>
|
||||
)}
|
||||
</a>
|
||||
<div class="prose dark:prose-invert max-w-none">
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
---
|
||||
import { getCollection } from 'astro:content'
|
||||
import Layout from 'layouts/Layout.astro'
|
||||
import Picture from 'components/Picture.astro'
|
||||
|
||||
const projects = await getCollection('projects')
|
||||
const clients = await Promise.all((await getCollection('clients')).map(async (it) => ({...it, obj: await it.render()})))
|
||||
---
|
||||
|
||||
<Layout title="Avior.me - Developpement de solutions selon vos besoins">
|
||||
<main class="container">
|
||||
|
||||
<div class="flex justify-center items-center h-64">
|
||||
<h1 class="text-4xl font-bold">Développement de solutions selon vos besoins</h1>
|
||||
</div>
|
||||
<h2 class="text-center md:text-left text-2xl font-semibold mb-4">Projets</h2>
|
||||
<div class="md:grid grid-cols-3 gap-4">
|
||||
{projects.map((it) => (
|
||||
<a href={`/projets/${it.slug}`} class="flex flex-col gap-4 mb-6 md:mb-0">
|
||||
{it.data.image && (
|
||||
<Picture src={it.data.image} alt="" />
|
||||
)}
|
||||
<p>{it.data.title}</p>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<h2 class="text-center md:text-left text-2xl font-semibold my-8">Clients</h2>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<div></div>
|
||||
<div class="prose dark:prose-invert max-w-none">
|
||||
<p class="font-semibold">Tâches :</p>
|
||||
</div>
|
||||
</div>
|
||||
{clients.map((client, index) => {
|
||||
const { Content } = client.obj
|
||||
client.data.logo
|
||||
return (
|
||||
<>
|
||||
{index > 0 && (
|
||||
<div class="hidden md:block w-10/12 mx-auto h-0.5 bg-gray-300 dark:bg-gray-700"></div>
|
||||
)}
|
||||
<div class="mt-6 md:mt-0 md:grid grid-cols-2 items-center">
|
||||
<a href={client.data.site} target="_blank" rel="noreferrer nofollow" class="flex flex-col gap-4">
|
||||
{client.data.logo && (
|
||||
<Picture class:list={{'mx-auto': true, 'md:mx-0': true, 'dark:invert': client.data.logo.invert}} src={client.data.logo.src} height={48} alt="" />
|
||||
) || (
|
||||
<div>{client.data.title}</div>
|
||||
)}
|
||||
</a>
|
||||
<div class="prose dark:prose-invert max-w-none">
|
||||
<Content />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import Picture from 'components/Picture.astro'
|
||||
import Article from 'layouts/Article.astro'
|
||||
|
||||
export const prerender = true
|
||||
@ -11,16 +12,28 @@ export async function getStaticPaths() {
|
||||
}))
|
||||
}
|
||||
// 2. For your template, you can get the entry directly from the prop
|
||||
const { entry } = Astro.props;
|
||||
const { entry } = Astro.props as Awaited<ReturnType<typeof getStaticPaths>>[0]['props'];
|
||||
const { Content } = await entry.render();
|
||||
---
|
||||
<Article title={entry.data.title} image={[entry.data.image]} description={entry.data.description} link={entry.data.link} breadcrumb={[{text: 'Accueil', href: '/'}, {text: 'Projets', href: '/projets'}, {text: entry.data.title}]}>
|
||||
<Article
|
||||
title={entry.data.title}
|
||||
image={entry.data.image ? [entry.data.image] : undefined}
|
||||
description={entry.data.description}
|
||||
link={entry.data.link}
|
||||
breadcrumb={[
|
||||
{text: 'Accueil', href: '/'},
|
||||
{text: 'Projets', href: '/projets'},
|
||||
{text: entry.data.title}
|
||||
]}
|
||||
>
|
||||
<h1>{entry.data.title}</h1>
|
||||
<p class="flex justify-end font-lights my-0">
|
||||
<span>Sortie initial le {entry.data.created.toLocaleDateString('fr')}</span>
|
||||
{entry.data.created && (
|
||||
<span>Sortie initial le {entry.data.created.toLocaleDateString('fr')}</span>
|
||||
)}
|
||||
<!-- <p>Software updated: {entry.data.updated.toLocaleDateString()}</p> -->
|
||||
</p>
|
||||
|
||||
<Content />
|
||||
<Content components={{img: Picture }}/>
|
||||
|
||||
</Article>
|
||||
|
@ -13,11 +13,11 @@ export const ALL: APIRoute = async () => {
|
||||
sitemap.addEntry('/', {
|
||||
priority: 1
|
||||
})
|
||||
sitemap.addEntry('/projets/', {
|
||||
sitemap.addEntry('/projects/', {
|
||||
priority: 0.5
|
||||
})
|
||||
for (const project of projects) {
|
||||
sitemap.addEntry('/projets/' + project.slug, {
|
||||
sitemap.addEntry('/projects/' + project.slug, {
|
||||
priority: 0.7
|
||||
})
|
||||
}
|
||||
|
@ -15,9 +15,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
fontFamily: {
|
||||
sans: ["Lexend Variable", "Lexend", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
sans: ["Lexend Variable", "Lexend", ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
extend: {
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user