1 Commits

Author SHA1 Message Date
Alexis
b86cf52839 Base nuxt layout 2024-03-21 17:14:11 +01:00
36 changed files with 9886 additions and 3462 deletions

View File

@@ -1,5 +1,13 @@
root = true
[*/**]
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@@ -1 +0,0 @@
VITE_API_URL = "http://localhost:3000"

View File

@@ -1 +0,0 @@
VITE_API_URL = "https://api-deputes.alexcreates.fr"

40
.gitignore vendored
View File

@@ -1,28 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
# Misc
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.fleet
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Local env files
.env
.env.*
!.env.example

View File

@@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

18
app.vue Normal file
View File

@@ -0,0 +1,18 @@
<template>
<div class="wrapper">
<Navbar />
<main class="p-4">
<NuxtPage />
</main>
<Footer />
</div>
</template>
<style lang="scss" scoped>
.wrapper {
min-height: 100dvh;
min-height: 100vh;
display: grid;
grid-template-rows: auto 1fr auto;
}
</style>

76
assets/css/main.css Normal file
View File

@@ -0,0 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark-mode {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--ring: 212.7 26.8% 83.9%;
}

16
components.json Normal file
View File

@@ -0,0 +1,16 @@
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "default",
"typescript": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "assets/css/main.css",
"baseColor": "slate",
"cssVariables": true
},
"framework": "nuxt",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

7
components/footer.vue Normal file
View File

@@ -0,0 +1,7 @@
<template>
<footer class="p-4">
<span>
Données issues de <a href="https://data.assemblee-nationale.fr/">data.assemblee-nationale.fr</a>
</span>
</footer>
</template>

9
components/navbar.vue Normal file
View File

@@ -0,0 +1,9 @@
<script setup lang="ts">
</script>
<template>
<nav>
Testos
</nav>
</template>

3
env.d.ts vendored
View File

@@ -1,3 +0,0 @@
/// <reference types="vite/client" />
declare module "geojson-geometries-lookup";
declare module "pako";

View File

@@ -1,44 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Trouver mon député</title>
<!--theme colors -->
<meta name="theme-color" content="#020617" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<!--Basic meta info -->
<meta name="keywords" content="député, géolocalisation, assemblée nationale, france, représentant, démocratie, voter, élections, parlementaires, parlementarisme">
<meta name="author" content="Alexis Pelé" />
<meta name="description" content="Trouvez facilement votre député parlementaires ; Localisez facilement votre circonscription et découvrez qui vous représente sans vous arrachez les cheveux !">
<!--OpenGraph meta -->
<meta property="og:description" content="Application de géolocalisation des députés par circonscription" />
<meta property="og:title" content="Trouver mon député" />
<!--meta for twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:creator" content="@bhupeshimself">
<meta name="twitter:title" content="">
<meta name="twitter:image" content="logo.png">
<meta name="twitter:site" content="@bhupeshimself">
<meta name="twitter:description" content="">
<!--Favicon-->
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="/favicon.ico">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="/browserconfig.xml">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

6
lib/utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

21
nuxt.config.ts Normal file
View File

@@ -0,0 +1,21 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
modules: [
'@nuxtjs/tailwindcss',
'@nuxtjs/color-mode',
'shadcn-nuxt',
'@vueuse/nuxt',
],
css: ['~/assets/css/main.css'],
shadcn: {
prefix: '',
componentDir: './components/ui'
},
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
})

12156
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +1,35 @@
{
"name": "rep-lookup-fr",
"version": "0.1.0",
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check build-only",
"preview": "vite preview --port 4173",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@tailwindcss/typography": "^0.5.4",
"@vueuse/core": "^10.1.0",
"pinia": "^2.0.35",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
"@vueuse/core": "^10.9.0",
"@vueuse/nuxt": "^10.9.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"lucide-vue-next": "^0.359.0",
"nuxt": "^3.11.1",
"radix-vue": "^1.5.3",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.1.0",
"@types/node": "^16.11.45",
"@vitejs/plugin-vue": "^4.1.0",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^11.0.2",
"@vue/tsconfig": "^0.1.3",
"autoprefixer": "^10.4.8",
"eslint": "^8.39.0",
"eslint-plugin-vue": "^9.0.0",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.23",
"prettier": "^2.5.1",
"sass": "^1.62.0",
"tailwindcss": "^3.3.1",
"typescript": "~4.7.4",
"vite": "^4.3.1",
"vue-tsc": "^0.38.8"
"@nuxtjs/color-mode": "^3.3.3",
"@nuxtjs/tailwindcss": "^6.11.4",
"autoprefixer": "^10.4.18",
"postcss": "^8.4.37",
"sass": "^1.72.0",
"shadcn-nuxt": "^0.10.2",
"tailwindcss": "^3.4.1",
"typescript": "^5.4.2"
}
}

5
pages/index.vue Normal file
View File

@@ -0,0 +1,5 @@
<template>
<div class="container">
<h1>Trouver mon député</h1>
</div>
</template>

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

3
server/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

View File

@@ -1,17 +0,0 @@
<template>
<div id="wrapper">
<div class="flex justify-center items-center">
<RouterView />
</div>
</div>
</template>
<style scoped>
#wrapper {
min-height: 100dvh;
min-height: 100vh;
display: grid;
grid-template-rows: 1fr;
}
</style>

View File

@@ -1,27 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply text-slate-100 bg-slate-950
}
@layer components {
.btn-red {
@apply bg-red-600 outline-red-700 hover:bg-red-700 disabled:bg-slate-400 disabled:outline-slate-400 disabled:hover:bg-slate-400
}
.btn-twitter {
@apply bg-twitter-500 outline-twitter-600 hover:bg-twitter-600
}
.btn-facebook {
@apply bg-facebook-500 outline-facebook-600 hover:bg-facebook-600
}
.btn-instagram {
@apply bg-instagram-500 outline-instagram-600 hover:bg-instagram-600
}
.btn-linkedin {
@apply bg-linkedin-500 outline-linkedin-600 hover:bg-linkedin-600
}
}

View File

@@ -1,54 +0,0 @@
<script lang="ts" setup>
import { useRepStore } from "@/stores/repStore";
import { useGeolocation } from "@vueuse/core";
import RepButton from "./RepButton.vue";
import { computed, ref } from "vue";
const { getRep, setRep } = useRepStore();
const { coords } = useGeolocation({
enableHighAccuracy: true,
});
const isFetching = ref<boolean>(false);
const lastError = ref<string>("");
const buttonIcon = computed(() =>
isFetching.value ? "spinner" : "location-dot"
);
async function handleGeolocClick() {
isFetching.value = true;
try {
const rep = await getRep(coords.value.longitude, coords.value.latitude);
setRep(rep);
} catch (err) {
const error = (await err) as Error;
lastError.value = error.message;
} finally {
isFetching.value = false;
}
}
</script>
<template>
<RepButton @click="handleGeolocClick" :style="'btn-red'">
<font-awesome-icon
:icon="['fass', buttonIcon]"
size="lg"
:class="{ 'fa-spin': isFetching }"
/>
<span>Géolocalisation</span>
</RepButton>
<!-- <div
v-if="!lastError && coords.accuracy > 1000"
class="mt-4 max-w-lg mx-auto text-sm text-red-400"
>
Position peu précise ; votre député sera peut être différement de notre
résultat
</div> -->
<div v-if="lastError" class="mt-4 max-w-lg mx-auto text-sm text-red-400">
{{ lastError }}
</div>
</template>

View File

@@ -1,29 +0,0 @@
<script lang="ts" setup>
import { computed } from "vue";
const props = defineProps<{
href?: string;
target?: string;
rel?: string;
style?:
| "btn-red"
| "btn-twitter"
| "btn-facebook"
| "btn-instagram"
| "btn-linkedin";
disabled?: boolean;
}>();
const elementTag = computed(() => (props.href ? "a" : "button"));
</script>
<template>
<component
:is="elementTag"
v-bind="props"
class="w-full md:w-max py-[10px] px-4 flex md:inline-flex text-white justify-center items-center gap-2 rounded-md font-semibold tracking-wide outline-2 outline-offset-4 focus-visible:outline transition-colors"
:class="props.style"
>
<slot />
</component>
</template>

View File

@@ -1,63 +0,0 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import GeolocButton from "./GeolocButton.vue";
import RepButton from "./RepButton.vue";
const hasAcceptedGeoloc = ref(false);
const hasRefusedGeoloc = ref(false);
onMounted(() => {
navigator.permissions.query({ name: "geolocation" }).then((result) => {
if (result.state === "granted") {
hasAcceptedGeoloc.value = true;
hasRefusedGeoloc.value = false;
} else if (result.state === "denied") {
hasAcceptedGeoloc.value = false;
hasRefusedGeoloc.value = true;
}
result.addEventListener("change", () => {
if (result.state === "granted") {
hasAcceptedGeoloc.value = true;
hasRefusedGeoloc.value = false;
} else if (result.state === "denied") {
hasAcceptedGeoloc.value = false;
hasRefusedGeoloc.value = true;
}
});
});
});
function acceptGeoloc() {
navigator.permissions.query({ name: "geolocation" }).then((result) => {
if (result.state === "granted" || result.state === "prompt") {
hasAcceptedGeoloc.value = true;
hasRefusedGeoloc.value = false;
} else if (result.state === "denied") {
hasAcceptedGeoloc.value = false;
hasRefusedGeoloc.value = true;
}
});
}
</script>
<template>
<section class="text-center">
<header class="mb-4">
<h1 class="mt-4 text-center font-bold text-3xl">
Qui me représente à l'Assemblée Nationale ?
</h1>
</header>
<RepButton
@click="acceptGeoloc"
:style="'btn-red'"
:disabled="hasRefusedGeoloc"
v-if="!hasAcceptedGeoloc"
>
<font-awesome-icon :icon="['fas', 'square']" size="lg" />
<span>Me géolocaliser</span>
</RepButton>
<GeolocButton v-else />
</section>
</template>

View File

@@ -1,190 +0,0 @@
<script lang="ts" setup>
import type { Organisation } from "@/models/org";
import RepButton from "./RepButton.vue";
import type { Depute } from "@/models/rep";
import { computed, onMounted, ref } from "vue";
const apiUrl = import.meta.env.VITE_API_URL;
const props = defineProps<{
rep: Depute;
}>();
const politicalPartyId = props.rep.acteur.mandats.mandat.find(
(m) => m.typeOrgane === "GP" && !m.dateFin
)?.organes.organeRef;
const repPoliticalParty = ref<Organisation | null>(null);
onMounted(async () => {
repPoliticalParty.value = await fetch(
`${apiUrl}/org?id=${politicalPartyId}`
).then((t) => t.json());
});
const repImage = computed(() => {
const imageRef = props.rep.acteur.uid["#text"].replace("PA", "");
return `https://www2.assemblee-nationale.fr/static/tribun/16/photos/carre/${imageRef}.jpg`;
});
const repFullName = computed(
() =>
`${props.rep.acteur.etatCivil.ident.prenom} ${props.rep.acteur.etatCivil.ident.nom}`
);
const repMandate = computed(() => {
const parliamentMandate = props.rep.acteur.mandats.mandat.find(
(mandate) => mandate.typeOrgane === "ASSEMBLEE"
);
return parliamentMandate;
});
const repTwitter = computed(() =>
props.rep.acteur.adresses.adresse.find(
(adr) => adr["typeLibelle"] === "Twitter"
)
);
const repFacebook = computed(() => {
const data = props.rep.acteur.adresses.adresse.find(
(adr) => adr["typeLibelle"] === "Facebook"
);
if (data) {
data.valElec = String(data.valElec).replace(/^(.*[\\/])/, "");
}
return data;
});
const repInstagram = computed(() => {
const data = props.rep.acteur.adresses.adresse.find(
(adr) => adr["typeLibelle"] === "Instagram"
);
return data;
});
const repLinkedin = computed(() => {
const data = props.rep.acteur.adresses.adresse.find(
(adr) => adr["typeLibelle"] === "Linkedin"
);
if (data) {
data.valElec = String(data.valElec).replace(/^(.*[\\/])/, "");
}
return data;
});
const regionBgFile = computed(() => {
if (
repMandate.value?.election?.lieu.departement ===
"Français établis hors de France"
) {
return `regions/fr-etranger-${String(
repMandate.value?.election?.lieu.numCirco
)}.svg`;
} else {
return `regions/${String(repMandate.value?.election?.lieu.region)
.toLocaleLowerCase()
.replace(/\s/g, "-")}.svg`;
}
});
</script>
<template>
<section class="py-8 md:py-0 w-96 max-w-full">
<header class="relative">
<div class="text-center">
<small class="text-base font-bold"> Votre député·e est... </small>
</div>
<figure class="mt-4 mx-auto w-44 h-44 rounded-full overflow-hidden">
<img
:src="repImage"
:alt="repFullName"
class="w-full h-full object-contain"
/>
</figure>
<h1 class="mt-4 text-center font-bold text-3xl">
{{ repFullName }}
</h1>
<div
v-if="repPoliticalParty"
class="text-center font-bold"
:style="`color: ${repPoliticalParty.organe.couleurAssociee}`"
>
{{ repPoliticalParty.organe.libelle }}
</div>
<h2 class="mt-2 text-center font-bold text-xl">
{{ repMandate?.election?.lieu.departement }} -
{{ repMandate?.election?.lieu.numCirco }}e circonscription
</h2>
<img
:src="regionBgFile"
class="absolute top-0 left-0 w-full h-full object-contain select-none -z-20 pointer-events-none"
/>
</header>
<footer class="mt-8">
<div class="mt-4 text-center">
<RepButton
:href="`https://www.assemblee-nationale.fr/dyn/deputes/${rep.acteur.uid['#text']}`"
target="_blank"
rel="noopener"
:style="'btn-red'"
>
<font-awesome-icon :icon="['fass', 'landmark']" size="lg" />
<span>Fiche Assemblée</span>
</RepButton>
</div>
<menu
class="mt-4 flex flex-col gap-y-4 md:justify-center md:flex-row md:gap-x-3 md:gap-y-0"
>
<li v-if="repTwitter">
<RepButton
:href="`https://twitter.com/${repTwitter.valElec}`"
target="_blank"
rel="noopener"
:style="'btn-twitter'"
>
<font-awesome-icon :icon="['fab', 'twitter']" size="lg" />
<span>Twitter</span>
</RepButton>
</li>
<li v-if="repFacebook">
<RepButton
:href="`https://www.facebook.com/${repFacebook.valElec}`"
target="_blank"
rel="noopener"
:style="'btn-facebook'"
>
<font-awesome-icon :icon="['fab', 'facebook']" size="lg" />
<span>Facebook</span>
</RepButton>
</li>
<li v-if="repLinkedin">
<RepButton
:href="`https://www.linkedin.com/${repLinkedin.valElec}`"
target="_blank"
rel="noopener"
:style="'btn-linkedin'"
>
<font-awesome-icon :icon="['fab', 'linkedin']" size="lg" />
<span>Linkedin</span>
</RepButton>
</li>
<li v-if="repInstagram">
<RepButton
:href="`https://www.instagram.com/${repInstagram.valElec}`"
target="_blank"
rel="noopener"
:style="'btn-instagram'"
>
<font-awesome-icon :icon="['fab', 'instagram']" size="lg" />
<span>Instagram</span>
</RepButton>
</li>
</menu>
</footer>
</section>
</template>

View File

@@ -1,5 +0,0 @@
<template>
<nav class="bg-slate-900">
<div class="container py-4">Mon Député</div>
</nav>
</template>

View File

@@ -1,45 +0,0 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "./router";
import "./assets/main.css";
import { library } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import {
faTwitter,
faFacebook,
faLinkedin,
faInstagram,
} from "@fortawesome/free-brands-svg-icons";
import {
faLandmark,
faLocationDot,
faSpinner,
faSquare,
faSquareCheck,
} from "@fortawesome/free-solid-svg-icons";
library.add(
faTwitter,
faFacebook,
faLinkedin,
faInstagram,
faLandmark,
faLocationDot,
faSpinner,
faSquare,
faSquareCheck
);
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.component("font-awesome-icon", FontAwesomeIcon);
app.mount("#app");

View File

@@ -1,33 +0,0 @@
export interface Organisation {
organe: {
"@xmlns": string;
"@xmlns:xsi": string;
"@xsi:type": string;
uid: string;
codeType: string;
libelle: string;
libelleEdition: string;
libelleAbrege: string;
libelleAbrev: string;
viMoDe: ViMoDe;
organeParent: null;
chambre: null;
regime: string;
legislature: string;
secretariat: Secretariat;
positionPolitique: string;
preseance: string;
couleurAssociee: string;
};
}
export interface Secretariat {
secretaire01: null;
secretaire02: null;
}
export interface ViMoDe {
dateDebut: Date;
dateAgrement: null;
dateFin: null;
}

View File

@@ -1,146 +0,0 @@
export interface Depute {
acteur: Acteur;
}
export interface Acteur {
"@xmlns:xsi": string;
uid: Uid;
etatCivil: EtatCivil;
profession: Profession;
uri_hatvp: string;
adresses: Adresses;
mandats: Mandats;
}
export interface Adresses {
adresse: { [key: string]: null | string }[];
}
export interface EtatCivil {
ident: Ident;
infoNaissance: InfoNaissance;
dateDeces: null;
}
export interface Ident {
civ: string;
prenom: string;
nom: string;
alpha: string;
trigramme: string;
}
export interface InfoNaissance {
dateNais: Date;
villeNais: string;
depNais: string;
paysNais: string;
}
export interface Mandats {
mandat: Mandat[];
}
export interface Mandat {
"@xsi:type": XsiType;
uid: string;
acteurRef: Text;
legislature: null | string;
typeOrgane: string;
dateDebut: Date;
datePublication: Date | null;
dateFin: Date | null;
preseance: string;
nominPrincipale: string;
infosQualite: InfosQualite;
organes: Organes;
suppleants?: Suppleants | null;
libelle?: null;
missionSuivanteRef?: null;
missionPrecedenteRef?: null;
chambre?: null;
election?: Election;
mandature?: Mandature;
collaborateurs?: Collaborateurs;
}
export enum XsiType {
MandatAvecSuppleantType = "MandatAvecSuppleant_Type",
MandatMissionType = "MandatMission_Type",
MandatParlementaireType = "MandatParlementaire_type",
MandatSimpleType = "MandatSimple_Type",
}
export enum Text {
Pa794094 = "PA794094",
}
export interface Collaborateurs {
collaborateur: Collaborateur[];
}
export interface Collaborateur {
qualite: string;
prenom: string;
nom: string;
dateDebut: null;
dateFin: null;
}
export interface Election {
lieu: Lieu;
causeMandat: string;
refCirconscription: string;
}
export interface Lieu {
region: string;
regionType: string;
departement: string;
numDepartement: string;
numCirco: string;
}
export interface InfosQualite {
codeQualite: string;
libQualite: string;
libQualiteSex: string;
}
export interface Mandature {
datePriseFonction: Date;
causeFin: null;
premiereElection: string;
placeHemicycle: string;
matriculeVote: string;
mandatRemplaceRef: null;
}
export interface Organes {
organeRef: string;
}
export interface Suppleants {
suppleant: Suppleant;
}
export interface Suppleant {
dateDebut: Date;
dateFin: null;
suppleantRef: string;
}
export interface Profession {
libelleCourant: string;
socProcINSEE: SocProcINSEE;
}
export interface SocProcINSEE {
catSocPro: string;
famSocPro: string;
}
export interface Uid {
"@xsi:type": string;
"#text": Text;
}

View File

@@ -1,15 +0,0 @@
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView,
},
],
});
export default router;

View File

@@ -1,25 +0,0 @@
import type { Depute } from "@/models/rep";
import { defineStore } from "pinia";
import { ref } from "vue";
const apiUrl = import.meta.env.VITE_API_URL;
export const useRepStore = defineStore("repStore", () => {
const currentRep = ref<Depute | null>(null);
async function getRep(lon: number, lat: number): Promise<Depute> {
return fetch(`${apiUrl}/rep?lat=${lat}&lon=${lon}`).then(async (res) => {
if (res.ok) {
return res.json();
} else {
throw res.json().then((res) => new Error(res.message));
}
});
}
function setRep(rep: Depute) {
currentRep.value = rep;
}
return { getRep, setRep, currentRep };
});

View File

@@ -1,42 +0,0 @@
<script lang="ts" setup>
import RepForm from "@/components/RepForm.vue";
import RepInfo from "@/components/RepInfo.vue";
import { useRepStore } from "@/stores/repStore";
import { storeToRefs } from "pinia";
const { currentRep } = storeToRefs(useRepStore());
</script>
<template>
<div>
<main class="container py-4">
<Transition name="fade" mode="out-in">
<RepForm v-if="!currentRep" />
<Suspense v-else>
<RepInfo :rep="currentRep" />
</Suspense>
</Transition>
</main>
</div>
</template>
<style scoped>
.fade-enter-from {
opacity: 0;
}
.fade-enter-to {
opacity: 1;
}
.fade-enter-active {
transition: all 0.3s ease;
}
.fade-leave-from {
opacity: 1;
}
.fade-leave-to {
opacity: 0;
}
.fade-leave-active {
transition: all 0.3s ease;
}
</style>

View File

@@ -1,74 +1,86 @@
const animate = require("tailwindcss-animate")
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
darkMode: ["class"],
safelist: ["dark"],
prefix: "",
theme: {
fontFamily: {
sans: ["Open Sans", "system-ui", "Lato", "sans-serif"],
},
container: {
padding: "2rem",
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
twitter: {
DEFAULT: "#1DA1F2",
50: "#CBE9FC",
100: "#B7E1FB",
200: "#91D1F9",
300: "#6AC1F6",
400: "#44B1F4",
500: "#1DA1F2",
600: "#0C82CB",
700: "#096096",
800: "#063E61",
900: "#031C2C",
950: "#010B12",
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
facebook: {
DEFAULT: "#4267B2",
50: "#C3CFE9",
100: "#B4C4E3",
200: "#96ACD8",
300: "#7894CD",
400: "#5B7DC2",
500: "#4267B2",
600: "#334F89",
700: "#243860",
800: "#142037",
900: "#05080E",
950: "#000000",
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
instagram: {
DEFAULT: "#C13584",
50: "#EEC0DA",
100: "#E9B0D0",
200: "#E090BD",
300: "#D870AA",
400: "#CF5098",
500: "#C13584",
600: "#952966",
700: "#691D48",
800: "#3D112A",
900: "#11050C",
950: "#000000",
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
linkedin: {
DEFAULT: "#0072B1",
50: "#6ACAFF",
100: "#55C3FF",
200: "#2CB4FF",
300: "#04A6FF",
400: "#008CDA",
500: "#0072B1",
600: "#004E79",
700: "#002A41",
800: "#000609",
900: "#000000",
950: "#000000",
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
xl: "calc(var(--radius) + 4px)",
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
"collapsible-down": {
from: { height: 0 },
to: { height: 'var(--radix-collapsible-content-height)' },
},
"collapsible-up": {
from: { height: 'var(--radix-collapsible-content-height)' },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"collapsible-down": "collapsible-down 0.2s ease-in-out",
"collapsible-up": "collapsible-up 0.2s ease-in-out",
},
},
},
plugins: [],
};
plugins: [animate],
}

View File

@@ -1,8 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.node.json",
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
"compilerOptions": {
"composite": true,
"types": ["node"]
}
}

View File

@@ -1,16 +1,4 @@
{
"extends": "@vue/tsconfig/tsconfig.web.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"references": [
{
"path": "./tsconfig.config.json"
}
]
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

View File

@@ -1,14 +0,0 @@
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});