mirror of
https://github.com/FoggedLens/deflock.git
synced 2026-02-12 15:02:45 +00:00
add chapters, no exposure
This commit is contained in:
48
.github/copilot-instructions.md
vendored
Normal file
48
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Copilot Instructions
|
||||||
|
|
||||||
|
This monorepo contains multiple subprojects. Below are the specific instructions for each.
|
||||||
|
|
||||||
|
## Web Application
|
||||||
|
/webapp
|
||||||
|
|
||||||
|
This is a TypeScript + Vue 3 + Vuetify application.
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- Vue 3 (Composition API)
|
||||||
|
- State management is local-first; avoid global stores unless required
|
||||||
|
- Vuetify for UI components and styling
|
||||||
|
|
||||||
|
### Page Structure
|
||||||
|
- Each page resides in `src/views`
|
||||||
|
- Shared components go in `src/components`
|
||||||
|
- Styles are scoped to components; avoid global styles unless necessary
|
||||||
|
- Prefer helper classes (provided by Vuetify) for styling over CSS rules
|
||||||
|
- Wrap most pages in DefaultLayout.vue for consistent headers/footers
|
||||||
|
- Use the Hero component for prominent page headings
|
||||||
|
|
||||||
|
### Coding Style
|
||||||
|
- Prefer functional patterns
|
||||||
|
|
||||||
|
### Security & Privacy
|
||||||
|
- Never log PII
|
||||||
|
- Do not introduce tracking, analytics, or telemetry
|
||||||
|
|
||||||
|
### General Rules
|
||||||
|
- Do not generate placeholder logic
|
||||||
|
- If something is unknown, leave a TODO comment
|
||||||
|
- Prefer simple, readable solutions over clever ones
|
||||||
|
|
||||||
|
## Backend Service
|
||||||
|
/shotgun
|
||||||
|
|
||||||
|
This is being deprecated. No new features should be added. Backend functionality should be migrated to serverless functions where possible.
|
||||||
|
|
||||||
|
## Terraform
|
||||||
|
/terraform
|
||||||
|
|
||||||
|
This contains Terraform code for infrastructure management.
|
||||||
|
|
||||||
|
## Serverless Functions
|
||||||
|
/serverless
|
||||||
|
|
||||||
|
This contains serverless functions to support the web application.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<v-row
|
<v-row
|
||||||
justify="center"
|
justify="center"
|
||||||
class="hero text-center mb-4"
|
class="hero text-center mb-4"
|
||||||
:class="{ 'hero-image': imageUrl, 'hero-gradient': gradient }"
|
:class="{ 'hero-image': imageUrl }"
|
||||||
:style="heroStyle"
|
:style="heroStyle"
|
||||||
>
|
>
|
||||||
<v-col cols="12" md="8">
|
<v-col cols="12" md="8">
|
||||||
@@ -33,7 +33,10 @@ const props = defineProps({
|
|||||||
title: String,
|
title: String,
|
||||||
description: String,
|
description: String,
|
||||||
imageUrl: String,
|
imageUrl: String,
|
||||||
gradient: String,
|
gradient: {
|
||||||
|
type: String,
|
||||||
|
default: 'linear-gradient(135deg, rgb(var(--v-theme-primary)) 0%, rgb(var(--v-theme-secondary)) 100%)',
|
||||||
|
},
|
||||||
buttonText: String,
|
buttonText: String,
|
||||||
buttonTo: String,
|
buttonTo: String,
|
||||||
buttonHref: String,
|
buttonHref: String,
|
||||||
@@ -51,14 +54,11 @@ const target = computed(() =>
|
|||||||
props.buttonHref && !props.buttonHref.startsWith('#') ? '_blank' : '_self'
|
props.buttonHref && !props.buttonHref.startsWith('#') ? '_blank' : '_self'
|
||||||
);
|
);
|
||||||
|
|
||||||
const heroStyle = computed(() => {
|
const heroStyle = computed(() => (
|
||||||
if (props.gradient) {
|
props.imageUrl ?
|
||||||
return `background: ${props.gradient};`;
|
`background: url('${props.imageUrl}') no-repeat ${props.backgroundPosition} / cover; --hero-opacity: ${props.opacity};` :
|
||||||
} else if (props.imageUrl) {
|
`background: ${props.gradient};`
|
||||||
return `background: url('${props.imageUrl}') no-repeat ${props.backgroundPosition} / cover; --hero-opacity: ${props.opacity};`;
|
));
|
||||||
}
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -31,6 +31,14 @@ const router = createRouter({
|
|||||||
title: 'ALPR Map | DeFlock'
|
title: 'ALPR Map | DeFlock'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/chapters',
|
||||||
|
name: 'chapters',
|
||||||
|
component: () => import('../views/Chapters.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'DeFlock Chapters | Connect Locally'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
name: 'about',
|
name: 'about',
|
||||||
|
|||||||
36
webapp/src/services/cmsService.ts
Normal file
36
webapp/src/services/cmsService.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export interface Chapter {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
website: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChaptersResponse {
|
||||||
|
data: Chapter[];
|
||||||
|
meta?: {
|
||||||
|
total_count: number;
|
||||||
|
filter_count: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CMS_BASE_URL = "https://cms.deflock.me";
|
||||||
|
|
||||||
|
const cmsApiService = axios.create({
|
||||||
|
baseURL: CMS_BASE_URL,
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cmsService = {
|
||||||
|
async getChapters(): Promise<Chapter[]> {
|
||||||
|
try {
|
||||||
|
const response = await cmsApiService.get("/items/chapters?sort=name");
|
||||||
|
return response.data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching chapters:", error);
|
||||||
|
throw new Error("Failed to fetch chapters");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
171
webapp/src/views/Chapters.vue
Normal file
171
webapp/src/views/Chapters.vue
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
<template>
|
||||||
|
<DefaultLayout>
|
||||||
|
<template #header>
|
||||||
|
<Hero
|
||||||
|
title="Local Groups"
|
||||||
|
description="Find and join an independent group fighting mass surveillance near you."
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" class="mx-auto text-center">
|
||||||
|
<p>
|
||||||
|
These groups are <b>independently run</b> and are <b>not affiliated with DeFlock</b>.
|
||||||
|
</p>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6" class="mx-auto">
|
||||||
|
<div v-if="chapters.length">
|
||||||
|
<div v-for="(state, idx) in statesWithChapters" :key="state" class="mb-8">
|
||||||
|
<h3 class="font-weight-bold text-h6 mb-0">{{ abbrevToState[state] }}</h3>
|
||||||
|
<v-divider class="mb-4" />
|
||||||
|
<v-list>
|
||||||
|
<template v-for="(chapter, idx) in chaptersByState[state]" :key="chapter.id">
|
||||||
|
<v-list-item
|
||||||
|
two-line
|
||||||
|
class="chapter-list-item"
|
||||||
|
>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title class="font-weight-bold">
|
||||||
|
{{ chapter.name }}
|
||||||
|
</v-list-item-title>
|
||||||
|
<v-list-item-subtitle class="font-weight-bold">
|
||||||
|
{{ chapter.city }}
|
||||||
|
</v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
|
||||||
|
<template #append>
|
||||||
|
<v-list-item-action>
|
||||||
|
<v-btn :href="chapter.website" target="_blank" rel="noopener" color="primary" variant="outlined" size="small">
|
||||||
|
Visit Website
|
||||||
|
<v-icon end>mdi-open-in-new</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-item-action>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</v-list-item>
|
||||||
|
<v-divider v-if="idx < chaptersByState[state].length - 1" />
|
||||||
|
</template>
|
||||||
|
</v-list>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<v-divider />
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<p>Don't see a group near you?</p>
|
||||||
|
<v-btn color="primary" variant="text" to="/contact">
|
||||||
|
<v-icon start>mdi-plus</v-icon>Submit a Group
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="text-center">
|
||||||
|
<div v-if="!isLoading">
|
||||||
|
<v-alert variant="tonal" color="info">No chapters found.</v-alert>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<v-progress-circular indeterminate color="primary" size="48" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</DefaultLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
||||||
|
import Hero from '@/components/layout/Hero.vue'
|
||||||
|
import { cmsService } from '@/services/cmsService'
|
||||||
|
|
||||||
|
interface Chapter {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
city: string
|
||||||
|
state: string
|
||||||
|
website: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const abbrevToState: Record<string, string> = {
|
||||||
|
"AL": "Alabama",
|
||||||
|
"AK": "Alaska",
|
||||||
|
"AZ": "Arizona",
|
||||||
|
"AR": "Arkansas",
|
||||||
|
"CA": "California",
|
||||||
|
"CO": "Colorado",
|
||||||
|
"CT": "Connecticut",
|
||||||
|
"DE": "Delaware",
|
||||||
|
"DC": "District Of Columbia",
|
||||||
|
"FL": "Florida",
|
||||||
|
"GA": "Georgia",
|
||||||
|
"HI": "Hawaii",
|
||||||
|
"ID": "Idaho",
|
||||||
|
"IL": "Illinois",
|
||||||
|
"IN": "Indiana",
|
||||||
|
"IA": "Iowa",
|
||||||
|
"KS": "Kansas",
|
||||||
|
"KY": "Kentucky",
|
||||||
|
"LA": "Louisiana",
|
||||||
|
"ME": "Maine",
|
||||||
|
"MD": "Maryland",
|
||||||
|
"MA": "Massachusetts",
|
||||||
|
"MI": "Michigan",
|
||||||
|
"MN": "Minnesota",
|
||||||
|
"MS": "Mississippi",
|
||||||
|
"MO": "Missouri",
|
||||||
|
"MT": "Montana",
|
||||||
|
"NE": "Nebraska",
|
||||||
|
"NV": "Nevada",
|
||||||
|
"NH": "New Hampshire",
|
||||||
|
"NJ": "New Jersey",
|
||||||
|
"NM": "New Mexico",
|
||||||
|
"NY": "New York",
|
||||||
|
"NC": "North Carolina",
|
||||||
|
"ND": "North Dakota",
|
||||||
|
"OH": "Ohio",
|
||||||
|
"OK": "Oklahoma",
|
||||||
|
"OR": "Oregon",
|
||||||
|
"PA": "Pennsylvania",
|
||||||
|
"RI": "Rhode Island",
|
||||||
|
"SC": "South Carolina",
|
||||||
|
"SD": "South Dakota",
|
||||||
|
"TN": "Tennessee",
|
||||||
|
"TX": "Texas",
|
||||||
|
"UT": "Utah",
|
||||||
|
"VT": "Vermont",
|
||||||
|
"VA": "Virginia",
|
||||||
|
"WA": "Washington",
|
||||||
|
"WV": "West Virginia",
|
||||||
|
"WI": "Wisconsin",
|
||||||
|
"WY": "Wyoming"
|
||||||
|
}
|
||||||
|
|
||||||
|
const chapters = ref<Chapter[]>([])
|
||||||
|
const isLoading = ref(true)
|
||||||
|
|
||||||
|
const chaptersByState = computed(() => {
|
||||||
|
const grouped: Record<string, Chapter[]> = {}
|
||||||
|
chapters.value.forEach(chapter => {
|
||||||
|
if (!grouped[chapter.state]) grouped[chapter.state] = []
|
||||||
|
grouped[chapter.state].push(chapter)
|
||||||
|
})
|
||||||
|
return grouped
|
||||||
|
})
|
||||||
|
|
||||||
|
const statesWithChapters = computed(() => {
|
||||||
|
return Object.keys(chaptersByState.value).sort()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
cmsService.getChapters()
|
||||||
|
.then(data => {
|
||||||
|
chapters.value = data
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isLoading.value = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
@@ -29,28 +29,6 @@
|
|||||||
|
|
||||||
<!-- Dangers Section -->
|
<!-- Dangers Section -->
|
||||||
<v-container class="pb-10 text-center info-section">
|
<v-container class="pb-10 text-center info-section">
|
||||||
<!-- Flock Warning Alert -->
|
|
||||||
<v-alert
|
|
||||||
type="warning"
|
|
||||||
variant="tonal"
|
|
||||||
class="text-left mx-4 mt-4"
|
|
||||||
prominent
|
|
||||||
border="start"
|
|
||||||
closable
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<v-icon>mdi-alert</v-icon>
|
|
||||||
</template>
|
|
||||||
<v-alert-title class="text-h6 font-weight-bold">
|
|
||||||
Similar Sites are Disappearing
|
|
||||||
</v-alert-title>
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
Following <a target="_blank" href="https://haveibeenflocked.com/news/cyble-downtime">takedown claims submitted on behalf of Flock</a>, sites similar to DeFlock have gone offline. If DeFlock disappears, it's clear why.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</v-alert>
|
|
||||||
|
|
||||||
<h2 class="mb-4">What are ALPRs</h2>
|
<h2 class="mb-4">What are ALPRs</h2>
|
||||||
|
|
||||||
<p class="text-left px-6">
|
<p class="text-left px-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user