Compare commits
1 Commits
0.4.0
...
features/n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b86cf52839 |
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
VITE_API_URL = "http://localhost:3000"
|
||||
@@ -1 +0,0 @@
|
||||
VITE_API_URL = "https://api-deputes.alexcreates.fr"
|
||||
40
.gitignore
vendored
40
.gitignore
vendored
@@ -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
|
||||
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
||||
18
app.vue
Normal file
18
app.vue
Normal 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
76
assets/css/main.css
Normal 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
16
components.json
Normal 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
7
components/footer.vue
Normal 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
9
components/navbar.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav>
|
||||
Testos
|
||||
</nav>
|
||||
</template>
|
||||
3
env.d.ts
vendored
3
env.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
declare module "geojson-geometries-lookup";
|
||||
declare module "pako";
|
||||
44
index.html
44
index.html
@@ -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
6
lib/utils.ts
Normal 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
21
nuxt.config.ts
Normal 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
12156
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -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
5
pages/index.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<h1>Trouver mon député</h1>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
3
server/tsconfig.json
Normal file
3
server/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../.nuxt/tsconfig.server.json"
|
||||
}
|
||||
17
src/App.vue
17
src/App.vue
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<nav class="bg-slate-900">
|
||||
<div class="container py-4">Mon Député</div>
|
||||
</nav>
|
||||
</template>
|
||||
45
src/main.ts
45
src/main.ts
@@ -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");
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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 };
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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],
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user