add chapters, no exposure

This commit is contained in:
Will Freeman
2025-12-31 12:25:29 -06:00
parent ab64d3283d
commit fcbc5df149
6 changed files with 273 additions and 32 deletions

View File

@@ -3,7 +3,7 @@
<v-row
justify="center"
class="hero text-center mb-4"
:class="{ 'hero-image': imageUrl, 'hero-gradient': gradient }"
:class="{ 'hero-image': imageUrl }"
:style="heroStyle"
>
<v-col cols="12" md="8">
@@ -33,7 +33,10 @@ const props = defineProps({
title: String,
description: 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,
buttonTo: String,
buttonHref: String,
@@ -51,14 +54,11 @@ const target = computed(() =>
props.buttonHref && !props.buttonHref.startsWith('#') ? '_blank' : '_self'
);
const heroStyle = computed(() => {
if (props.gradient) {
return `background: ${props.gradient};`;
} else if (props.imageUrl) {
return `background: url('${props.imageUrl}') no-repeat ${props.backgroundPosition} / cover; --hero-opacity: ${props.opacity};`;
}
return '';
});
const heroStyle = computed(() => (
props.imageUrl ?
`background: url('${props.imageUrl}') no-repeat ${props.backgroundPosition} / cover; --hero-opacity: ${props.opacity};` :
`background: ${props.gradient};`
));
</script>
<style scoped>

View File

@@ -31,6 +31,14 @@ const router = createRouter({
title: 'ALPR Map | DeFlock'
}
},
{
path: '/chapters',
name: 'chapters',
component: () => import('../views/Chapters.vue'),
meta: {
title: 'DeFlock Chapters | Connect Locally'
}
},
{
path: '/about',
name: 'about',

View 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");
}
}
};

View 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>

View File

@@ -29,28 +29,6 @@
<!-- Dangers 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>
<p class="text-left px-6">