feat: Initial Nuxt app
Some checks failed
NuxtHub deployment / deploy (push) Failing after 21s
WordPress deployment / deploy (push) Successful in 8s

This commit is contained in:
2025-08-27 13:37:40 -04:00
parent 677d367226
commit 312a6f1b62
43 changed files with 40550 additions and 2 deletions

View File

@@ -4,6 +4,7 @@
.nuxt
.nitro
.cache
.wrangler
dist
# Node dependencies
@@ -22,5 +23,3 @@ logs
.env
.env.*
!.env.example
.wrangler

View File

@@ -0,0 +1 @@
shamefully-hoist=true

View File

@@ -0,0 +1,3 @@
# cultureat.ca
Site web du Conseil de la culture de l'Abitibi-Témiscamingue.

View File

@@ -0,0 +1,10 @@
import { defineAppConfig } from "#imports";
export default defineAppConfig({
ui: {
colors: {
primary: "indigo",
neutral: "neutral",
},
},
});

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { fr } from "@nuxt/ui/locale";
useHead({
htmlAttrs: {
lang: "fr-CA",
},
meta: [
{ charset: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
],
link: [
{ rel: "icon", href: "/favicon.ico" },
],
});
useSeoMeta({
titleTemplate: "%s - Conseil de la culture de l'Abitibi-Témiscamingue",
});
</script>
<template>
<UApp :locale="fr">
<NuxtRouteAnnouncer />
<NuxtLoadingIndicator />
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</UApp>
</template>

View File

@@ -0,0 +1,9 @@
@layer components {
.container-fluid {
@apply w-full max-w-screen px-4 sm:px-6 lg:px-8;
}
.flex-break-words {
@apply min-w-0 break-words;
}
}

View File

@@ -0,0 +1,7 @@
@import "tailwindcss";
@import "@nuxt/ui";
@import "./helpers.css";
@theme {
--z-index-main: 10;
}

View File

@@ -0,0 +1,170 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="4.04in" height="1.52in" viewBox="0 0 290.92 109.68">
<defs>
<style>
.cls-1 {
fill: url(#linear-gradient);
}
.cls-2 {
fill: url(#linear-gradient-2);
}
.cls-3 {
fill: url(#linear-gradient-3);
}
.cls-4 {
fill: url(#linear-gradient-4);
}
.cls-5 {
fill: url(#linear-gradient-5);
}
.cls-6 {
fill: url(#linear-gradient-6);
}
.cls-7 {
fill: url(#linear-gradient-7);
}
.cls-8 {
fill: url(#linear-gradient-8);
}
.cls-9 {
fill: url(#linear-gradient-12);
}
.cls-10 {
fill: url(#linear-gradient-15);
}
.cls-11 {
fill: url(#linear-gradient-16);
}
.cls-12 {
fill: url(#linear-gradient-22);
}
.cls-13 {
fill: url(#linear-gradient-25);
}
.cls-14 {
fill: url(#linear-gradient-26);
}
.cls-15 {
fill: url(#linear-gradient-33);
}
.cls-16 {
fill: url(#linear-gradient-35);
}
.cls-17 {
fill: url(#linear-gradient-45);
}
.cls-18 {
fill: url(#linear-gradient-47);
}
.cls-19 {
fill: url(#linear-gradient-49);
}
.cls-20 {
fill: url(#linear-gradient-50);
}
</style>
<linearGradient id="linear-gradient" x1="-19.84" y1="71.96" x2="296.65" y2="71.96" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#cf8837"/>
<stop offset="0.41" stop-color="#bd4931"/>
<stop offset="1" stop-color="#2e3191"/>
</linearGradient>
<linearGradient id="linear-gradient-2" x1="-19.84" y1="32.09" x2="296.65" y2="32.09" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-3" x1="-19.84" y1="27.58" x2="296.65" y2="27.58" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-4" x1="-19.84" y1="20.21" x2="296.65" y2="20.21" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-5" x1="-19.84" y1="12.87" x2="296.65" y2="12.87" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-6" x1="-19.84" y1="12.38" x2="296.65" y2="12.38" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-7" x1="-19.84" y1="48.34" x2="296.65" y2="48.34" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-8" x1="-19.84" y1="31.96" x2="296.65" y2="31.96" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-12" x1="-19.84" y1="31.96" x2="296.65" y2="31.96" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-15" x1="-19.84" y1="71.7" x2="296.65" y2="71.7" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-16" x1="-19.84" y1="71.92" x2="296.65" y2="71.92" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-22" x1="-19.84" y1="103.92" x2="296.65" y2="103.92" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-25" x1="-19.84" y1="100.47" x2="296.65" y2="100.47" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-26" x1="-19.84" y1="103.85" x2="296.65" y2="103.85" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-33" x1="-19.84" y1="104.11" x2="296.65" y2="104.11" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-35" x1="-19.84" y1="102.27" x2="296.65" y2="102.27" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-45" x1="-19.84" y1="104" x2="296.65" y2="104" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-47" x1="-19.84" y1="63.12" x2="296.65" y2="63.12" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-49" x1="-19.84" y1="80.6" x2="296.65" y2="80.6" xlink:href="#linear-gradient"/>
<linearGradient id="linear-gradient-50" x1="-19.84" y1="80.51" x2="296.65" y2="80.51" xlink:href="#linear-gradient"/>
</defs>
<g>
<path class="cls-1" d="M49.39,34.25a1.09,1.09,0,0,0-1.09,1.09v.79a22.3,22.3,0,0,1-6.1,15.35L36,58.07V44.3a1.1,1.1,0,0,0-2.19,0V74.93l-.5-.32A22.26,22.26,0,0,1,22.94,55.74v-10a1.1,1.1,0,1,0-2.19,0v10A24.43,24.43,0,0,0,32.12,76.45l1.68,1.07v31.07a1.1,1.1,0,0,0,2.19,0V61.26L43.79,53a24.44,24.44,0,0,0,6.7-16.85v-.79A1.09,1.09,0,0,0,49.39,34.25Z"/>
<path class="cls-2" d="M22.41,41.52S20,31.13,15.85,26.94,0,22.63,0,22.63s1.53,8.89,6.3,14.11c4,4.4,12.28,4.8,15.12,4.8C22.05,41.54,22.41,41.52,22.41,41.52ZM7.1,36C3.69,32.26,2,26.4,1.36,23.78c3.14.18,10.75.91,13.71,3.93S20.2,37.56,21,40.44C18.17,40.4,10.67,39.91,7.1,36Z"/>
<path class="cls-3" d="M59.91,30c-2.12-1.29-5.25-.86-7-.44,1.67-.56,4.42-1.63,5.78-3,2-2.1,2-7.95,2-7.95s-4.4.88-7,3.32-2.23,7.61-2.21,8h0v0h0c.22.37,2.74,4.68,5.2,6.06s8.25-.13,8.25-.13S62.94,31.81,59.91,30Zm-5.47-7.31A13.21,13.21,0,0,1,59.49,20c-.14,2.08-.62,4.71-1.62,5.74s-3.6,2.17-5.32,2.75C52.62,26.65,53,24,54.44,22.65Z"/>
<path class="cls-4" d="M44.36,20.52C44.54,12.42,34.8,0,34.8,0s-8.65,9.47-9.18,19.2c-.55,10.1,9.3,21.22,9.3,21.22S44.17,28.94,44.36,20.52Z"/>
<path class="cls-5" d="M20,19s.41-5.24-.89-7.82-6.78-4.4-6.78-4.4-.61,4.39.83,7.56S20,19,20,19ZM13.32,8.3c1.89.8,4.19,2.09,4.83,3.35.71,1.43.85,4,.83,5.83-1.67-.65-4-1.89-4.81-3.62A13,13,0,0,1,13.32,8.3Z"/>
<path class="cls-6" d="M53.17,12.38a2,2,0,1,0-2,2A2,2,0,0,0,53.17,12.38Zm-2,.89a.89.89,0,1,1,.89-.89A.89.89,0,0,1,51.19,13.27Z"/>
<path class="cls-7" d="M10.25,47.25a1.55,1.55,0,1,0,2.19,0A1.55,1.55,0,0,0,10.25,47.25Z"/>
<g>
<g>
<path class="cls-8" d="M88.15,17.82a13.85,13.85,0,0,1,9.5,3.65.77.77,0,0,1,0,1.1l-1.06,1.06c-.32.4-.63.36-1,0a11.93,11.93,0,0,0-7.5-2.94,11.29,11.29,0,0,0,0,22.58c3.41,0,5.42-1.38,7.5-2.95a.75.75,0,0,1,.94-.12l1.18,1.06a.76.76,0,0,1,0,1.06,13.45,13.45,0,0,1-9.54,3.81,14.14,14.14,0,1,1,0-28.27Z"/>
<path class="cls-8" d="M115.71,17.82A14.14,14.14,0,1,1,101.61,32,14.12,14.12,0,0,1,115.71,17.82Zm0,25.52A11.39,11.39,0,1,0,104.36,32,11.41,11.41,0,0,0,115.71,43.34Z"/>
<path class="cls-8" d="M135.85,18.53a.75.75,0,0,1,.75-.71h1l17.71,22.07h.08V19a.74.74,0,0,1,.74-.74h1.38a.77.77,0,0,1,.74.74V45.38a.73.73,0,0,1-.74.71h-.71l-18-22.5h0V45a.74.74,0,0,1-.75.75H136.6a.78.78,0,0,1-.75-.75Z"/>
<path class="cls-8" d="M164.48,41.58c.19-.24.39-.51.59-.75.39-.51.82-.82,1.37-.35.27.23,3.14,3,6.64,3,3.18,0,5.26-2,5.26-4.32,0-2.71-2.36-4.32-6.87-6.2-4.32-1.89-6.91-3.65-6.91-8.13,0-2.67,2.12-7,8.36-7a12.78,12.78,0,0,1,6.71,2,.86.86,0,0,1,.24,1.33c-.16.24-.32.51-.47.75a.9.9,0,0,1-1.38.35,10.93,10.93,0,0,0-5.14-1.81c-4.16,0-5.42,2.67-5.42,4.32,0,2.63,2,4.16,5.3,5.54,5.3,2.16,8.72,4.16,8.72,8.72,0,4.08-3.89,7.06-8.48,7.06a12.81,12.81,0,0,1-8.29-3.18A.86.86,0,0,1,164.48,41.58Z"/>
<path class="cls-9" d="M187.17,19a.74.74,0,0,1,.75-.74h15.47a.74.74,0,0,1,.74.74V20.1a.75.75,0,0,1-.74.75h-13.2v9.58h11.27a.77.77,0,0,1,.75.74v1.14a.75.75,0,0,1-.75.75H190.19v10h13.2a.74.74,0,0,1,.74.74V45a.74.74,0,0,1-.74.75H187.92a.74.74,0,0,1-.75-.75Z"/>
<path class="cls-9" d="M210.42,19a.77.77,0,0,1,.74-.74h1.57a.77.77,0,0,1,.75.74V45a.77.77,0,0,1-.75.75h-1.57a.77.77,0,0,1-.74-.75Z"/>
<path class="cls-9" d="M221.49,19a.74.74,0,0,1,.74-.74h1.53a.77.77,0,0,1,.75.74V43.07h11.31a.75.75,0,0,1,.75.74V45a.75.75,0,0,1-.75.75H222.23a.74.74,0,0,1-.74-.75Z"/>
<path class="cls-10" d="M124.62,56.16a15.13,15.13,0,0,1,10.44,4,.83.83,0,0,1,.05,1.2l-1.17,1.17c-.34.43-.69.39-1.12,0a13.05,13.05,0,0,0-8.25-3.24,12.42,12.42,0,0,0,0,24.82c3.76,0,6-1.51,8.25-3.24a.82.82,0,0,1,1-.13l1.3,1.17a.81.81,0,0,1,0,1.16,14.83,14.83,0,0,1-10.49,4.19,15.54,15.54,0,1,1,0-31.08Z"/>
<path class="cls-11" d="M141.37,57.41a.84.84,0,0,1,.82-.82h1.64a.82.82,0,0,1,.82.82v18c0,4.92,3.06,8.76,8.11,8.76s8.2-3.75,8.2-8.67V57.41a.82.82,0,0,1,.82-.82h1.64a.85.85,0,0,1,.82.82V75.67c0,6.56-4.66,11.57-11.48,11.57s-11.39-5-11.39-11.57Z"/>
<path class="cls-10" d="M172.66,57.41a.82.82,0,0,1,.82-.82h1.68a.85.85,0,0,1,.82.82V83.92h12.43a.81.81,0,0,1,.82.82V86a.82.82,0,0,1-.82.82H173.48a.82.82,0,0,1-.82-.82Z"/>
<path class="cls-10" d="M196.23,59.49h-7.51a.82.82,0,0,1-.82-.82V57.41a.82.82,0,0,1,.82-.82H207a.82.82,0,0,1,.82.82v1.26a.82.82,0,0,1-.82.82h-7.51V86a.85.85,0,0,1-.82.82h-1.64a.85.85,0,0,1-.82-.82Z"/>
<path class="cls-11" d="M212.2,57.41a.85.85,0,0,1,.82-.82h1.64a.82.82,0,0,1,.82.82v18c0,4.92,3.06,8.76,8.11,8.76s8.2-3.75,8.2-8.67V57.41a.82.82,0,0,1,.82-.82h1.64a.84.84,0,0,1,.82.82V75.67c0,6.56-4.66,11.57-11.48,11.57s-11.39-5-11.39-11.57Z"/>
<path class="cls-10" d="M243.49,57.41a.82.82,0,0,1,.82-.82h11.18a9.28,9.28,0,0,1,9.41,9.2,9.62,9.62,0,0,1-6.39,8.8l5.92,11a.82.82,0,0,1-.74,1.25h-2.2a.75.75,0,0,1-.69-.39L255.06,75h-8.25V86a.84.84,0,0,1-.82.82h-1.68a.82.82,0,0,1-.82-.82Zm11.79,14.72a6.2,6.2,0,1,0,0-12.39H246.9V72.13Z"/>
<path class="cls-10" d="M272,57.41a.82.82,0,0,1,.82-.82h17a.82.82,0,0,1,.82.82v1.26a.82.82,0,0,1-.82.82h-14.5V70h12.38a.84.84,0,0,1,.82.82v1.25a.82.82,0,0,1-.82.82H275.35v11h14.5a.82.82,0,0,1,.82.82V86a.82.82,0,0,1-.82.82h-17A.82.82,0,0,1,272,86Z"/>
<path class="cls-12" d="M75.27,98.73a.3.3,0,0,1,.28-.3h3.52a5.5,5.5,0,1,1,0,11H75.55a.3.3,0,0,1-.28-.3Zm3.55,9.61a4.42,4.42,0,1,0,0-8.84H76.46v8.84Z"/>
<path class="cls-12" d="M87,98.73a.3.3,0,0,1,.3-.3h6.19a.3.3,0,0,1,.3.3v.45a.3.3,0,0,1-.3.3H88.21v3.83h4.51a.31.31,0,0,1,.3.3v.46a.29.29,0,0,1-.3.29H88.21v4h5.28a.3.3,0,0,1,.3.3v.45a.3.3,0,0,1-.3.3H87.3a.3.3,0,0,1-.3-.3Z"/>
<path class="cls-12" d="M99.79,98.73a.3.3,0,0,1,.3-.3h.61a.31.31,0,0,1,.3.3v9.64h4.52a.3.3,0,0,1,.3.3v.45a.3.3,0,0,1-.3.3h-5.43a.3.3,0,0,1-.3-.3Z"/>
<path class="cls-13" d="M106.06,102.39c-.08-.08-.07-.24.09-.38a2.66,2.66,0,0,0,1-2,.55.55,0,0,1-.18,0,.91.91,0,0,1-.9-.9.89.89,0,0,1,.9-.89c.5,0,1,.28,1,1.41a4.32,4.32,0,0,1-1.32,2.9c-.15.15-.28.13-.37,0Z"/>
<path class="cls-14" d="M108.6,109l4.77-10.56a.29.29,0,0,1,.27-.18h.16a.26.26,0,0,1,.26.18L118.81,109a.28.28,0,0,1-.27.41h-.63a.3.3,0,0,1-.27-.17l-1.16-2.59h-5.57l-1.15,2.59a.27.27,0,0,1-.26.17h-.63A.28.28,0,0,1,108.6,109Zm7.46-3.33c-.77-1.71-1.52-3.44-2.29-5.15h-.13l-2.29,5.15Z"/>
<path class="cls-12" d="M120.52,98.73a.3.3,0,0,1,.3-.3h3.44a2.87,2.87,0,0,1,1.68,5.4,2.78,2.78,0,0,1,1.88,2.56,3.11,3.11,0,0,1-3.42,3h-3.58a.3.3,0,0,1-.3-.3Zm4,9.64a1.94,1.94,0,0,0,2-2,2.08,2.08,0,0,0-2.24-1.93h-2.59v3.93Zm-.27-5a1.81,1.81,0,0,0,1.88-2,1.74,1.74,0,0,0-1.88-1.89H121.7v3.85Z"/>
<path class="cls-12" d="M130.34,98.73a.31.31,0,0,1,.3-.3h.62a.31.31,0,0,1,.3.3v10.39a.31.31,0,0,1-.3.3h-.62a.31.31,0,0,1-.3-.3Z"/>
<path class="cls-12" d="M136.32,99.48h-2.73a.3.3,0,0,1-.3-.3v-.45a.3.3,0,0,1,.3-.3h6.66a.29.29,0,0,1,.29.3v.45a.29.29,0,0,1-.29.3h-2.74v9.64a.31.31,0,0,1-.29.3h-.6a.31.31,0,0,1-.3-.3Z"/>
<path class="cls-12" d="M142.27,98.73a.31.31,0,0,1,.3-.3h.63a.31.31,0,0,1,.3.3v10.39a.31.31,0,0,1-.3.3h-.63a.31.31,0,0,1-.3-.3Z"/>
<path class="cls-12" d="M146.7,98.73a.3.3,0,0,1,.3-.3h3.44a2.87,2.87,0,0,1,1.68,5.4,2.78,2.78,0,0,1,1.88,2.56,3.11,3.11,0,0,1-3.42,3H147a.3.3,0,0,1-.3-.3Zm4,9.64a2,2,0,0,0,2-2,2.09,2.09,0,0,0-2.25-1.93h-2.59v3.93Zm-.27-5a1.81,1.81,0,0,0,1.88-2,1.74,1.74,0,0,0-1.88-1.89h-2.56v3.85Z"/>
<path class="cls-12" d="M156.52,98.73a.31.31,0,0,1,.3-.3h.63a.31.31,0,0,1,.3.3v10.39a.31.31,0,0,1-.3.3h-.63a.31.31,0,0,1-.3-.3Z"/>
<path class="cls-15" d="M160.35,104.27V104a.29.29,0,0,1,.3-.3h3.28a.29.29,0,0,1,.3.3v.3a.28.28,0,0,1-.3.28h-3.28A.28.28,0,0,1,160.35,104.27Z"/>
<path class="cls-12" d="M168.16,99.48h-2.73a.29.29,0,0,1-.3-.3v-.45a.29.29,0,0,1,.3-.3h6.65a.29.29,0,0,1,.3.3v.45a.29.29,0,0,1-.3.3h-2.73v9.64a.31.31,0,0,1-.3.3h-.59a.31.31,0,0,1-.3-.3Z"/>
<path class="cls-16" d="M174.11,98.73a.3.3,0,0,1,.3-.3h6.19a.29.29,0,0,1,.29.3v.45a.29.29,0,0,1-.29.3h-5.28v3.83h4.51a.31.31,0,0,1,.3.3v.46a.3.3,0,0,1-.3.29h-4.51v4h5.28a.29.29,0,0,1,.29.3v.45a.29.29,0,0,1-.29.3h-6.19a.3.3,0,0,1-.3-.3Zm2-2a.22.22,0,0,1,.1-.3l2.35-1.32c.1-.06.33-.09.41,0l.28.57a.22.22,0,0,1-.09.33l-2.53,1c-.22.09-.3.09-.36,0Z"/>
<path class="cls-12" d="M184.65,98.49a.31.31,0,0,1,.28-.22h.25a.3.3,0,0,1,.27.18l3.11,8.65h.08l3.06-8.65a.3.3,0,0,1,.27-.18h.25a.31.31,0,0,1,.28.22l2,10.55c0,.22-.05.38-.29.38h-.62a.33.33,0,0,1-.29-.22l-1.39-8.09h-.07L189,109.4a.31.31,0,0,1-.26.18h-.29a.31.31,0,0,1-.26-.18l-2.92-8.29h-.07l-1.36,8.09a.31.31,0,0,1-.28.22h-.63c-.24,0-.33-.16-.29-.38Z"/>
<path class="cls-12" d="M197,98.73a.31.31,0,0,1,.3-.3h.62a.31.31,0,0,1,.3.3v10.39a.31.31,0,0,1-.3.3h-.62a.31.31,0,0,1-.3-.3Z"/>
<path class="cls-12" d="M200.67,107.77c.08-.09.16-.2.24-.3s.33-.33.55-.14a4.28,4.28,0,0,0,2.65,1.2,1.9,1.9,0,0,0,2.1-1.73c0-1.09-.94-1.73-2.74-2.48s-2.77-1.46-2.77-3.25c0-1.07.85-2.8,3.35-2.8a5.11,5.11,0,0,1,2.68.8.36.36,0,0,1,.1.54c-.07.09-.13.2-.19.29a.35.35,0,0,1-.55.15,4.3,4.3,0,0,0-2.06-.73c-1.66,0-2.17,1.07-2.17,1.73,0,1,.81,1.67,2.12,2.22,2.12.86,3.49,1.66,3.49,3.48a3.12,3.12,0,0,1-3.39,2.83,5.15,5.15,0,0,1-3.31-1.27A.35.35,0,0,1,200.67,107.77Z"/>
<path class="cls-12" d="M214.62,98.27a5.54,5.54,0,0,1,3.8,1.46.31.31,0,0,1,0,.44l-.42.43c-.13.15-.25.14-.41,0a4.75,4.75,0,0,0-3-1.18,4.52,4.52,0,0,0,0,9,4.55,4.55,0,0,0,3-1.18.3.3,0,0,1,.38,0l.47.42a.3.3,0,0,1,0,.42,5.38,5.38,0,0,1-3.81,1.53,5.66,5.66,0,1,1,0-11.31Z"/>
<path class="cls-14" d="M219.35,109l4.77-10.56a.29.29,0,0,1,.27-.18h.15a.29.29,0,0,1,.27.18L229.55,109a.28.28,0,0,1-.27.41h-.62a.29.29,0,0,1-.27-.17l-1.16-2.59h-5.58l-1.14,2.59a.29.29,0,0,1-.27.17h-.63A.27.27,0,0,1,219.35,109Zm7.45-3.33c-.77-1.71-1.52-3.44-2.29-5.15h-.12l-2.3,5.15Z"/>
<path class="cls-12" d="M232.51,98.49a.3.3,0,0,1,.28-.22H233a.3.3,0,0,1,.27.18l3.11,8.65h.07l3.07-8.65a.28.28,0,0,1,.26-.18h.25a.31.31,0,0,1,.29.22l2,10.55c.05.22,0,.38-.28.38h-.63a.32.32,0,0,1-.28-.22l-1.4-8.09h-.06l-2.89,8.29a.31.31,0,0,1-.27.18h-.28a.34.34,0,0,1-.27-.18l-2.92-8.29H233l-1.37,8.09a.29.29,0,0,1-.28.22h-.63c-.23,0-.33-.16-.28-.38Z"/>
<path class="cls-12" d="M244.81,98.73a.31.31,0,0,1,.29-.3h.63a.31.31,0,0,1,.3.3v10.39a.31.31,0,0,1-.3.3h-.63a.31.31,0,0,1-.29-.3Z"/>
<path class="cls-12" d="M249.24,98.56a.3.3,0,0,1,.29-.29h.4L257,107.1h0V98.73a.29.29,0,0,1,.3-.3h.55a.31.31,0,0,1,.29.3v10.56a.3.3,0,0,1-.29.29h-.29l-7.2-9h0v8.54a.29.29,0,0,1-.3.3h-.55a.31.31,0,0,1-.29-.3Z"/>
<path class="cls-12" d="M266.26,98.27a5.54,5.54,0,0,1,3.8,1.46.31.31,0,0,1,0,.44c-.14.14-.31.3-.44.44s-.23.14-.4,0a4.78,4.78,0,0,0-3-1.23,4.53,4.53,0,0,0,0,9,5.92,5.92,0,0,0,2.83-.66v-2.2h-1.81a.29.29,0,0,1-.3-.28v-.59a.29.29,0,0,1,.3-.29H270a.29.29,0,0,1,.28.29v3.6a.39.39,0,0,1-.12.25,7.73,7.73,0,0,1-3.88,1,5.66,5.66,0,1,1,0-11.31Z"/>
<path class="cls-17" d="M272.75,98.73a.31.31,0,0,1,.3-.3h.59a.3.3,0,0,1,.3.3v6.55a3,3,0,1,0,5.94,0V98.73a.29.29,0,0,1,.29-.3h.6a.31.31,0,0,1,.3.3v6.64a4.16,4.16,0,1,1-8.32,0Z"/>
<path class="cls-12" d="M284.13,98.73a.3.3,0,0,1,.3-.3h6.19a.3.3,0,0,1,.3.3v.45a.3.3,0,0,1-.3.3h-5.28v3.83h4.51a.31.31,0,0,1,.3.3v.46a.29.29,0,0,1-.3.29h-4.51v4h5.28a.3.3,0,0,1,.3.3v.45a.3.3,0,0,1-.3.3h-6.19a.3.3,0,0,1-.3-.3Z"/>
</g>
<g>
<path class="cls-18" d="M75.21,56.76a.35.35,0,0,1,.34-.36h4.31a6.73,6.73,0,1,1,0,13.45H75.55a.36.36,0,0,1-.34-.37Zm4.34,11.76a5.41,5.41,0,1,0,0-10.82H76.67V68.52Z"/>
<path class="cls-18" d="M89.55,56.76a.37.37,0,0,1,.37-.36h7.57a.37.37,0,0,1,.37.36v.56a.37.37,0,0,1-.37.36H91v4.69h5.52a.38.38,0,0,1,.36.37v.56a.36.36,0,0,1-.36.36H91v4.9h6.46a.37.37,0,0,1,.37.37v.55a.38.38,0,0,1-.37.37H89.92a.38.38,0,0,1-.37-.37Z"/>
</g>
<g>
<path class="cls-19" d="M75.29,74.75a.34.34,0,0,1,.34-.34h.69a.35.35,0,0,1,.33.34V85.6h5.09a.34.34,0,0,1,.34.33v.51a.34.34,0,0,1-.34.34H75.63a.34.34,0,0,1-.34-.34Z"/>
<path class="cls-20" d="M86.35,86.32l5.37-11.89a.3.3,0,0,1,.3-.19h.17a.33.33,0,0,1,.31.19l5.33,11.89a.31.31,0,0,1-.3.46h-.71a.35.35,0,0,1-.3-.19l-1.3-2.92H88.94l-1.29,2.92a.33.33,0,0,1-.3.19h-.7A.31.31,0,0,1,86.35,86.32Zm8.39-3.74c-.87-1.93-1.71-3.87-2.58-5.8H92l-2.58,5.8Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
const { loginSchema, loginFields, onLoginSubmit } = useLogin();
</script>
<template>
<UAuthForm
:schema="loginSchema"
:fields="loginFields"
title="Connexion"
description="Veuillez vous identifier."
icon="i-lucide-user"
loading-auto
@submit="onLoginSubmit"
/>
</template>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
const { loggedIn } = useUserSession();
defineProps<{ showLabels: boolean }>();
</script>
<template>
<AuthState>
<UButton
:icon="loggedIn ? 'i-lucide-log-out' : 'i-lucide-log-in'"
color="neutral"
to="/auth"
:label="showLabels ? (loggedIn ? 'Déconnexion' : 'Connexion') : undefined"
/>
</AuthState>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
const { onLogoutClick } = useLogout();
</script>
<template>
<div class="w-full space-y-6">
<div class="flex flex-col text-center">
<div class="text-xl text-pretty font-semibold text-highlighted">
Déconnexion
</div>
<div class="mt-1 text-base text-pretty text-muted">
Veuillez confirmer la déconnexion.
</div>
</div>
<UButton
icon="i-lucide-log-out"
block
loading-auto
to="/auth"
label="Déconnexion"
@click="onLogoutClick"
/>
</div>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
defineProps<{ showLabels: boolean }>();
</script>
<template>
<UButton
icon="i-lucide-user"
color="primary"
to="#"
:label="showLabels ? 'Devenir membre' : undefined"
/>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<UFooter>
TODO
</UFooter>
</template>

View File

@@ -0,0 +1,11 @@
<template>
<div class="container-fluid flex flex-col lg:flex-row justify-between items-center gap-x-6 gap-y-3 py-3 text-muted text-sm text-center">
<p class="flex-break-words">
© {{ new Date().getFullYear() }} Conseil de la culture de l'Abitibi-Témiscamingue.
</p>
<p class="flex items-center gap-1">
Fait avec <UIcon name="i-lucide-heart" /> par
<ULink href="https://websimple.com" target="_blank" external title="Site web développé par Websimple">Websimple</ULink>
</p>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
const { menuItems } = await useMenuItems("MAIN");
</script>
<template>
<UHeader title="CCAT" mode="slideover">
<template #left>
<NuxtLink to="/">
<SvgLogoCcat class="h-12 w-auto shrink-0" />
</NuxtLink>
</template>
<template #right>
<UNavigationMenu :items="menuItems" variant="link" content-orientation="vertical" class="hidden lg:block" :ui="{ list: '-mx-2.5', link: 'text-base', childLink: 'text-base' }" />
</template>
<template #body>
<UNavigationMenu :items="menuItems" orientation="vertical" />
</template>
</UHeader>
</template>

View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
const { profiles } = await useSiteOptions();
const { menuItems } = await useMenuItems("TOP");
const { breakpoints } = useResponsive();
const showLabels = breakpoints.greaterOrEqual("sm");
</script>
<template>
<UContainer class="flex items-center gap-1.5">
<UiSocialProfiles :profiles="profiles" class="mr-auto" />
<UNavigationMenu :items="menuItems" variant="link" content-orientation="vertical" />
<LoginLogoutButton :show-labels="showLabels" />
<SignUpButton :show-labels="showLabels" />
</UContainer>
</template>

View File

@@ -0,0 +1,39 @@
<script setup lang="ts">
import type { SiteOptionsFragment } from "#graphql-operations";
defineProps<{ profiles: SiteOptionsFragment["profiles"] }>();
const socialIconMap = {
"facebook.com": "i-cib-facebook-f",
"twitter.com": "i-cib-twitter",
"x.com": "i-cib-twitter",
"instagram.com": "i-cib-instagram",
"youtube.com": "i-cib-youtube",
"linkedin.com": "i-cib-linkedin",
"tiktok.com": "i-cib-tiktok",
};
function getIconFromUrl(url: string): string {
try {
const domain = new URL(url).hostname.toLowerCase().replace(/^www\./, "");
return socialIconMap[domain as keyof typeof socialIconMap] || "i-lucide-globe";
}
catch {
return "i-lucide-globe";
}
}
</script>
<template>
<div class="flex gap-1">
<UButton
v-for="(profile, key) in profiles" :key="key"
:icon="getIconFromUrl(profile!.url)"
:to="profile!.url"
variant="link"
color="neutral"
external
target="_blank"
/>
</div>
</template>

View File

@@ -0,0 +1,48 @@
import type { FormSubmitEvent } from "@nuxt/ui";
import * as z from "zod";
const loginSchema = z.object({
email: z.email("Courriel invalide"),
password: z.string("Veuillez saisir votre mot de passe"),
});
type LoginSchema = z.infer<typeof loginSchema>;
const loginFields = [
{
name: "email",
type: "text" as const,
label: "Courriel",
placeholder: "Entrez votre courriel",
required: true,
}, {
name: "password",
label: "Mot de passe",
type: "password" as const,
placeholder: "Entrez votre mot de passe",
required: true,
},
];
export function useLogin() {
const toast = useToast();
const redirect = useRoute().query.redirect as string || "/";
const router = useRouter();
const { fetch: refreshUserSession } = useUserSession();
async function onLoginSubmit({ data }: FormSubmitEvent<LoginSchema>) {
try {
const result = await $fetch<{ success: boolean; message?: string }>("/api/login", { method: "POST", body: data });
if (!result.success) {
throw new Error(result.message || "Une erreur est survenue.");
}
await router.push(redirect);
await refreshUserSession();
}
catch (error) {
const message = error instanceof Error ? error.message : "Une erreur est survenue.";
toast.add({ title: "Échec de la connexion", description: message, color: "error" });
}
}
return { loginSchema, loginFields, onLoginSubmit };
}

View File

@@ -0,0 +1,23 @@
export function useLogout() {
const toast = useToast();
const redirect = useRoute().query.redirect as string || "/";
const router = useRouter();
const { fetch: refreshUserSession } = useUserSession();
async function onLogoutClick() {
try {
const result = await $fetch("/api/logout", { method: "POST" });
if (!result.success) {
throw new Error("Une erreur est survenue.");
}
await router.push(redirect);
await refreshUserSession();
}
catch (error) {
const message = error instanceof Error ? error.message : "Une erreur est survenue.";
toast.add({ title: "Échec de la déconnexion", description: message, color: "error" });
}
}
return { onLogoutClick };
}

View File

@@ -0,0 +1,19 @@
fragment MenuItem on MenuItem {
id
label
to: path
target
}
query menuItems($location: MenuLocationEnum!) {
menuItems(where: {location: $location, parentDatabaseId: 0}) {
nodes {
...MenuItem
childItems {
nodes {
...MenuItem
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
import type { MenuLocationEnum } from "#graphql-operations";
import type { NavigationMenuItem } from "@nuxt/ui";
export async function useMenuItems(location: MenuLocationEnum) {
const { data } = await useAsyncGraphqlQuery("menuItems", { location }, { graphqlCaching: { client: true } });
if (data.value?.errors?.length) {
throw createError({ statusCode: 500, message: "Erreur lors de la récupération des éléments de menu" });
}
const menuItems: NavigationMenuItem[] = (data.value?.data.menuItems?.nodes || []).map(({ childItems, ...menuItem }) => ({
...menuItem,
children: childItems?.nodes || [],
}));
return { menuItems };
}

View File

@@ -0,0 +1,11 @@
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
export function useResponsive() {
const { isMobileOrTablet } = useDevice();
const breakpoints = useBreakpoints(breakpointsTailwind, { ssrWidth: isMobileOrTablet ? 375 : 1024 });
return {
breakpoints,
isDesktop: breakpoints.greaterOrEqual("lg"),
};
}

View File

@@ -0,0 +1,13 @@
fragment SiteOptions on Ccat {
profiles {
url
}
}
query siteOptions {
siteOptions {
ccat {
...SiteOptions
}
}
}

View File

@@ -0,0 +1,7 @@
export async function useSiteOptions() {
const { data } = await useAsyncGraphqlQuery("siteOptions", {}, { graphqlCaching: { client: true } });
if (data.value?.errors?.length || !data.value?.data.siteOptions?.ccat) {
throw createError({ statusCode: 500, message: "Erreur lors de la récupération des options du site" });
}
return { ...data.value?.data.siteOptions?.ccat };
}

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
import type { NuxtError } from "#app";
defineProps<{ error: NuxtError }>();
</script>
<template>
<UApp>
<UError :error="error" />
</UApp>
</template>

View File

@@ -0,0 +1,19 @@
<template>
<div class="h-screen flex items-center justify-center px-4">
<UButton
icon="i-lucide-chevron-left"
to="/"
size="xl"
color="neutral"
variant="subtle"
class="absolute left-8 top-8 rounded-full z-10"
/>
<UPageCard
variant="subtle"
class="max-w-sm w-full"
>
<slot />
</UPageCard>
</div>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
const refSiteFooterBottom = useTemplateRef("refSiteFooterBottom");
const { height } = useElementSize(refSiteFooterBottom);
watch(height, (h) => {
document.documentElement.style.setProperty("--footer-bottom-height", `${h}px`);
});
</script>
<template>
<div>
<SiteHeaderTop />
<SiteHeader />
<div class="relative z-main bg-white border-b border-muted mb-[var(--footer-bottom-height)]">
<UMain>
<slot />
</UMain>
<SiteFooter />
</div>
<div ref="refSiteFooterBottom" class="fixed bottom-0 w-full bg-muted">
<SiteFooterBottom />
</div>
</div>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
definePageMeta({ layout: "auth" });
const { loggedIn } = useUserSession();
useSeoMeta({ title: loggedIn.value ? "Déconnexion" : "Connexion" });
</script>
<template>
<LogoutForm v-if="loggedIn" />
<LoginForm v-else />
</template>

View File

@@ -0,0 +1,9 @@
<script setup lang="ts">
useSeoMeta({ title: "Accueil" });
</script>
<template>
<UPage>
<UPageHero title="Nuxt UI rocks" />
</UPage>
</template>

View File

@@ -0,0 +1,8 @@
// @ts-check
import withNuxt from "./.nuxt/eslint.config.mjs";
export default withNuxt({
rules: {
"vue/max-attributes-per-line": "off",
},
});

View File

@@ -0,0 +1,3 @@
import config from "./.nuxt/nuxt-graphql-middleware/graphql.config";
export default config;

View File

@@ -0,0 +1,81 @@
const isDev = process.env.NODE_ENV === "development";
export default defineNuxtConfig({
modules: [
"@nuxt/eslint",
"@nuxt/ui",
"@nuxthub/core",
"@nuxtjs/device",
"@nuxtjs/robots",
"@nuxtjs/sitemap",
"@vueuse/nuxt",
"nuxt-auth-utils",
"nuxt-graphql-middleware",
"nuxt-svgo",
],
components: {
dirs: [
{ path: "~/components", pathPrefix: false },
],
},
devtools: { enabled: true },
css: ["~/assets/css/main.css"],
site: {
indexable: true,
url: "https://cultureat.ca",
name: "Conseil de la culture de l'Abitibi-Témiscamingue",
},
ui: {
colorMode: false,
},
runtimeConfig: {
public: {
},
},
future: { compatibilityVersion: 4 },
compatibilityDate: "2025-03-01",
nitro: {
preset: "cloudflare_module",
},
hub: {},
eslint: {
config: {
stylistic: {
arrowParens: true,
commaDangle: "always-multiline",
indent: 2,
quotes: "double",
semi: true,
},
},
},
graphqlMiddleware: {
graphqlEndpoint: process.env.NUXT_GRAPHQL_ENDPOINT || "https://wp.cultureat.ca/graphql",
downloadSchema: isDev,
schemaPath: "server/graphql/schema.graphql",
},
robots: {
sitemap: [
"/sitemap.xml",
],
},
sitemap: {},
svgo: {
autoImportPath: "~/assets/svg/",
componentPrefix: "svg",
defaultImport: "component",
},
});

View File

@@ -0,0 +1,54 @@
{
"name": "@lewebsimple/cultureat.ca",
"version": "0.2.0",
"private": true,
"type": "module",
"scripts": {
"build": "pnpm --sequential /build:.*/",
"build:typecheck": "nuxi typecheck",
"build:nuxt": "nuxt build",
"deploy": "pnpx nuxthub deploy",
"dev": "pnpm /dev:.*/",
"dev:nuxt": "nuxt dev --host",
"lint": "eslint --fix .",
"postinstall": "pnpm --sequential /postinstall:.*/",
"postinstall:nuxt": "nuxt prepare",
"preview": "pnpx nuxthub preview"
},
"dependencies": {
"@iconify-json/cib": "^1.2.3",
"@iconify-json/lucide": "^1.2.63",
"@nuxt/eslint": "^1.9.0",
"@nuxt/ui": "4.0.0-alpha.0",
"@nuxthub/core": "^0.9.0",
"@nuxtjs/device": "3.2.4",
"@nuxtjs/robots": "5.5.0",
"@nuxtjs/sitemap": "7.4.3",
"@vueuse/nuxt": "13.7.0",
"nuxt": "^4.0.3",
"nuxt-auth-utils": "0.5.23",
"nuxt-graphql-middleware": "5.2.0",
"nuxt-svgo": "4.2.6",
"vue": "^3.5.19",
"vue-router": "^4.5.1",
"zod": "^4.0.17"
},
"devDependencies": {
"eslint": "^9.33.0",
"typescript": "^5.9.2",
"vue-tsc": "^3.0.6",
"wrangler": "^4.32.0"
},
"packageManager": "pnpm@10.15.0",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"@tailwindcss/oxide",
"esbuild",
"sharp",
"unrs-resolver",
"vue-demi",
"workerd"
]
}
}

13939
wp-content/themes/ccat/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,9 @@
mutation login($email: String!, $password: String!) {
login(input: { username: $email, password: $password }) {
authToken
refreshToken
user {
email
}
}
}

View File

@@ -0,0 +1,29 @@
import { defineEventHandler, readBody } from "h3";
export default defineEventHandler(async (event) => {
const { email, password } = await readBody(event);
try {
const response = await useGraphqlMutation("login", { email, password });
if (response.errors.length) {
throw new Error(response.errors[0]?.message);
}
if (!response.data.login) {
throw new Error("Login failed: Invalid credentials");
}
const { authToken, refreshToken, user } = response.data.login;
await setUserSession(event, {
user,
secure: { authToken, refreshToken },
loggedInAt: new Date().toISOString(),
});
return { success: true };
}
catch (error) {
const messages: Record<string, string> = {
invalid_email: "Courriel et/ou mot de passe invalide(s).",
incorrect_password: "Courriel et/ou mot de passe invalide(s).",
};
const message = error instanceof Error && messages[error.message] ? messages[error.message] : "Une erreur est survenue.";
return { success: false, message };
}
});

View File

@@ -0,0 +1,6 @@
import { defineEventHandler } from "h3";
export default defineEventHandler(async (event) => {
await clearUserSession(event);
return { success: true };
});

View File

@@ -0,0 +1,5 @@
mutation refreshJwtAuthToken($refreshToken: String!) {
refreshJwtAuthToken(input: { jwtRefreshToken: $refreshToken }) {
authToken
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
import { defineGraphqlServerOptions } from "nuxt-graphql-middleware/server-options";
import { jwtDecode } from "jwt-decode";
interface DecodedToken {
exp: number;
}
async function refreshAuthToken(refreshToken: string): Promise<string | null> {
const refreshResponse = await useGraphqlMutation("refreshJwtAuthToken", { refreshToken });
return refreshResponse.data?.refreshJwtAuthToken?.authToken || null;
}
export default defineGraphqlServerOptions({
async serverFetchOptions(event) {
const session = await getUserSession(event);
if (!session?.secure?.authToken) return {};
const decoded = jwtDecode<DecodedToken>(session.secure.authToken);
const isExpired = decoded.exp * 1000 < Date.now();
if (isExpired) {
const newToken = await refreshAuthToken(session.secure.authToken);
if (newToken) {
session.secure.authToken = newToken;
}
}
return { headers: { Authorization: `Bearer ${session.secure.authToken}` } };
},
});

17
wp-content/themes/ccat/shared/auth.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
// auth.d.ts
declare module "#auth-utils" {
interface User {
email: string;
}
interface UserSession {
loggedInAt: string;
}
interface SecureSessionData {
authToken: string;
refreshToken: string;
}
}
export { };

View File

@@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}