Compare commits

...

10 Commits

Author SHA1 Message Date
1c3e2f9662 fix 2026-04-08 18:02:56 -05:00
f91bfbdd65 Bitte nochmal machen 2025-10-08 22:26:33 +02:00
e939543f3b . 2025-10-08 19:31:11 +02:00
6048a8f613 -our beliefs 2025-10-08 19:28:01 +02:00
9e73aea090 removed 2025-10-08 10:22:01 -05:00
1ec928d964 CT 2025-10-08 16:17:56 +02:00
495bfe71a3 Uhrzeit fix 2025-10-08 12:07:56 +02:00
e378ce4a97 Deploy ready 2025-10-03 18:50:40 +02:00
d64695225c Merge branch 'main' of git.bizmatch.net:aknuth/annaville-sda-site 2025-10-03 18:48:34 +02:00
de2d82da75 Launch 2025-10-03 18:27:44 +02:00
32 changed files with 1052 additions and 1646 deletions

View File

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
# API Base URL for production
# Set this to your production domain when building
VITE_API_BASE_URL=https://annavillesda.org

4
.gitignore vendored
View File

@@ -142,3 +142,7 @@ temp/
# Local configuration files
config.local.js
config.local.json
# Server data files
server/data/events.json
server/uploads/

View File

@@ -4,6 +4,8 @@ WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
ARG VITE_API_BASE_URL=https://annavillesda.org
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run build
FROM nginx:alpine

View File

@@ -1,4 +1,13 @@
services:
frontend:
build: . # Nutzt dein vorhandenes Dockerfile (Node Build + Nginx)
container_name: annaville-frontend
restart: unless-stopped
ports:
- "3071:80"
networks:
- annaville-network
backend:
image: node:22-alpine
container_name: annaville-backend

View File

@@ -4,8 +4,33 @@ server {
server_name _;
root /usr/share/nginx/html;
index index.html;
# API proxy to backend
location /api/ {
proxy_pass http://localhost:3070;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# Uploads proxy to backend
location /uploads/ {
proxy_pass http://localhost:3070;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Frontend routes
location / {
try_files $uri /index.html;
add_header Cache-Control "public, max-age=31536000, immutable";
}
add_header Cache-Control "public, max-age=31536000, immutable";
}

264
package-lock.json generated
View File

@@ -845,9 +845,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
"integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz",
"integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==",
"cpu": [
"arm"
],
@@ -859,9 +859,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
"integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz",
"integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==",
"cpu": [
"arm64"
],
@@ -873,9 +873,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
"integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz",
"integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==",
"cpu": [
"arm64"
],
@@ -887,9 +887,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
"integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz",
"integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==",
"cpu": [
"x64"
],
@@ -901,9 +901,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
"integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz",
"integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==",
"cpu": [
"arm64"
],
@@ -915,9 +915,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
"integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz",
"integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==",
"cpu": [
"x64"
],
@@ -929,9 +929,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
"integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz",
"integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==",
"cpu": [
"arm"
],
@@ -943,9 +943,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
"integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz",
"integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==",
"cpu": [
"arm"
],
@@ -957,9 +957,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
"integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz",
"integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==",
"cpu": [
"arm64"
],
@@ -971,9 +971,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
"integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz",
"integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==",
"cpu": [
"arm64"
],
@@ -985,9 +985,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
"integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz",
"integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==",
"cpu": [
"loong64"
],
@@ -999,9 +999,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
"integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz",
"integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==",
"cpu": [
"ppc64"
],
@@ -1013,9 +1013,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
"integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz",
"integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==",
"cpu": [
"riscv64"
],
@@ -1027,9 +1027,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
"integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz",
"integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==",
"cpu": [
"riscv64"
],
@@ -1041,9 +1041,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
"integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz",
"integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==",
"cpu": [
"s390x"
],
@@ -1055,9 +1055,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
"integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz",
"integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==",
"cpu": [
"x64"
],
@@ -1069,9 +1069,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
"integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz",
"integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==",
"cpu": [
"x64"
],
@@ -1083,9 +1083,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
"integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz",
"integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==",
"cpu": [
"arm64"
],
@@ -1097,9 +1097,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
"integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz",
"integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==",
"cpu": [
"arm64"
],
@@ -1111,9 +1111,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
"integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz",
"integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==",
"cpu": [
"ia32"
],
@@ -1125,9 +1125,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
"integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz",
"integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==",
"cpu": [
"x64"
],
@@ -1139,9 +1139,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
"integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz",
"integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==",
"cpu": [
"x64"
],
@@ -1347,9 +1347,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz",
"integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==",
"version": "2.8.13",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.13.tgz",
"integrity": "sha512-7s16KR8io8nIBWQyCYhmFhd+ebIzb9VKTzki+wOJXHTxTnV6+mFGH3+Jwn1zoKaY9/H9T/0BcKCZnzXljPnpSQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -1413,9 +1413,9 @@
}
},
"node_modules/browserslist": {
"version": "4.26.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz",
"integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==",
"version": "4.26.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
"integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
"dev": true,
"funding": [
{
@@ -1433,9 +1433,9 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001741",
"electron-to-chromium": "^1.5.218",
"baseline-browser-mapping": "^2.8.9",
"caniuse-lite": "^1.0.30001746",
"electron-to-chromium": "^1.5.227",
"node-releases": "^2.0.21",
"update-browserslist-db": "^1.1.3"
},
@@ -1512,9 +1512,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001743",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz",
"integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==",
"version": "1.0.30001749",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001749.tgz",
"integrity": "sha512-0rw2fJOmLfnzCRbkm8EyHL8SvI2Apu5UbnQuTsJ0ClgrH8hcwFooJ1s5R0EP8o8aVrFu8++ae29Kt9/gZAZp/Q==",
"dev": true,
"funding": [
{
@@ -1903,9 +1903,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.223",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.223.tgz",
"integrity": "sha512-qKm55ic6nbEmagFlTFczML33rF90aU+WtrJ9MdTCThrcvDNdUHN4p6QfVN78U06ZmguqXIyMPyYhw2TrbDUwPQ==",
"version": "1.5.233",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.233.tgz",
"integrity": "sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==",
"dev": true,
"license": "ISC"
},
@@ -2817,9 +2817,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.21",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
"integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==",
"version": "2.0.23",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
"integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
"dev": true,
"license": "MIT"
},
@@ -3076,9 +3076,9 @@
}
},
"node_modules/postcss-load-config": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
"dev": true,
"funding": [
{
@@ -3092,21 +3092,28 @@
],
"license": "MIT",
"dependencies": {
"lilconfig": "^3.0.0",
"yaml": "^2.3.4"
"lilconfig": "^3.1.1"
},
"engines": {
"node": ">= 14"
"node": ">= 18"
},
"peerDependencies": {
"jiti": ">=1.21.0",
"postcss": ">=8.0.9",
"ts-node": ">=9.0.0"
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"jiti": {
"optional": true
},
"postcss": {
"optional": true
},
"ts-node": {
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
@@ -3414,9 +3421,9 @@
}
},
"node_modules/rollup": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
"version": "4.52.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz",
"integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3430,28 +3437,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.52.2",
"@rollup/rollup-android-arm64": "4.52.2",
"@rollup/rollup-darwin-arm64": "4.52.2",
"@rollup/rollup-darwin-x64": "4.52.2",
"@rollup/rollup-freebsd-arm64": "4.52.2",
"@rollup/rollup-freebsd-x64": "4.52.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
"@rollup/rollup-linux-arm-musleabihf": "4.52.2",
"@rollup/rollup-linux-arm64-gnu": "4.52.2",
"@rollup/rollup-linux-arm64-musl": "4.52.2",
"@rollup/rollup-linux-loong64-gnu": "4.52.2",
"@rollup/rollup-linux-ppc64-gnu": "4.52.2",
"@rollup/rollup-linux-riscv64-gnu": "4.52.2",
"@rollup/rollup-linux-riscv64-musl": "4.52.2",
"@rollup/rollup-linux-s390x-gnu": "4.52.2",
"@rollup/rollup-linux-x64-gnu": "4.52.2",
"@rollup/rollup-linux-x64-musl": "4.52.2",
"@rollup/rollup-openharmony-arm64": "4.52.2",
"@rollup/rollup-win32-arm64-msvc": "4.52.2",
"@rollup/rollup-win32-ia32-msvc": "4.52.2",
"@rollup/rollup-win32-x64-gnu": "4.52.2",
"@rollup/rollup-win32-x64-msvc": "4.52.2",
"@rollup/rollup-android-arm-eabi": "4.52.4",
"@rollup/rollup-android-arm64": "4.52.4",
"@rollup/rollup-darwin-arm64": "4.52.4",
"@rollup/rollup-darwin-x64": "4.52.4",
"@rollup/rollup-freebsd-arm64": "4.52.4",
"@rollup/rollup-freebsd-x64": "4.52.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.52.4",
"@rollup/rollup-linux-arm-musleabihf": "4.52.4",
"@rollup/rollup-linux-arm64-gnu": "4.52.4",
"@rollup/rollup-linux-arm64-musl": "4.52.4",
"@rollup/rollup-linux-loong64-gnu": "4.52.4",
"@rollup/rollup-linux-ppc64-gnu": "4.52.4",
"@rollup/rollup-linux-riscv64-gnu": "4.52.4",
"@rollup/rollup-linux-riscv64-musl": "4.52.4",
"@rollup/rollup-linux-s390x-gnu": "4.52.4",
"@rollup/rollup-linux-x64-gnu": "4.52.4",
"@rollup/rollup-linux-x64-musl": "4.52.4",
"@rollup/rollup-openharmony-arm64": "4.52.4",
"@rollup/rollup-win32-arm64-msvc": "4.52.4",
"@rollup/rollup-win32-ia32-msvc": "4.52.4",
"@rollup/rollup-win32-x64-gnu": "4.52.4",
"@rollup/rollup-win32-x64-msvc": "4.52.4",
"fsevents": "~2.3.2"
}
},
@@ -3913,9 +3920,9 @@
}
},
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"version": "3.4.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3927,7 +3934,7 @@
"fast-glob": "^3.3.2",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.21.6",
"jiti": "^1.21.7",
"lilconfig": "^3.1.3",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
@@ -3936,7 +3943,7 @@
"postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.2",
"postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
"postcss-nested": "^6.2.0",
"postcss-selector-parser": "^6.1.2",
"resolve": "^1.22.8",
@@ -4297,19 +4304,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",

View File

@@ -1,28 +0,0 @@
[
{
"id": "57fb4ac3-3896-44a0-a61b-42f3bed558d5",
"slug": "test",
"createdAt": "2025-09-24T15:57:31.513Z",
"updatedAt": "2025-09-24T15:57:31.513Z",
"title": "test",
"description": "Test",
"date": "2025-10-10",
"time": "10:00 AM",
"location": "Fellowship Hall",
"category": "Youth",
"image": "/uploads/michael-peskov-magier-taschendieb-453624-jpeg-1758729451448.jpeg"
},
{
"id": "6e1b9995-e153-478a-a2d6-713e48d05891",
"slug": "eg",
"createdAt": "2025-09-24T18:18:07.806Z",
"updatedAt": "2025-09-24T18:18:07.806Z",
"title": "eg",
"description": "HSAHA",
"date": "2025-10-12",
"time": "12:30 PM",
"location": "Erkrath",
"category": "hi",
"image": "/uploads/atos-logo-blau-jpg-1758737887080.jpg"
}
]

View File

@@ -1,62 +1,63 @@
import express from 'express'
import cors from 'cors'
import { fileURLToPath } from 'url'
import { promises as fs } from 'fs'
import path from 'path'
import crypto from 'crypto'
import multer from 'multer'
import express from "express";
import cors from "cors";
import { fileURLToPath } from "url";
import { promises as fs } from "fs";
import path from "path";
import crypto from "crypto";
import multer from "multer";
const app = express()
const port = process.env.PORT || 3070
const adminToken = process.env.ADMIN_TOKEN || 'Driver1'
const app = express();
const port = process.env.PORT || 3070;
const adminToken = process.env.ADMIN_TOKEN || "Driver1";
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const dataDir = path.join(__dirname, 'data')
const dataPath = path.join(dataDir, 'events.json')
const uploadsDir = path.join(__dirname, 'uploads')
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const dataDir = path.join(__dirname, "data");
const dataPath = path.join(dataDir, "events.json");
const uploadsDir = path.join(__dirname, "uploads");
await fs.mkdir(uploadsDir, { recursive: true })
await fs.mkdir(uploadsDir, { recursive: true });
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, uploadsDir),
filename: (req, file, cb) => {
const ext = path.extname(file.originalname || '') || '.png'
const base = (file.originalname || 'upload')
.replace(/[^a-z0-9]+/gi, '-')
.replace(/^-+|-+$/g, '')
.toLowerCase() || 'upload'
const unique = `${base}-${Date.now()}${ext}`
cb(null, unique)
}
})
const ext = path.extname(file.originalname || "") || ".png";
const base =
(file.originalname || "upload")
.replace(/[^a-z0-9]+/gi, "-")
.replace(/^-+|-+$/g, "")
.toLowerCase() || "upload";
const unique = `${base}-${Date.now()}${ext}`;
cb(null, unique);
},
});
const allowedMimeTypes = new Set([
'image/png',
'image/jpeg',
'image/gif',
'image/webp',
'image/svg+xml'
])
"image/png",
"image/jpeg",
"image/gif",
"image/webp",
"image/svg+xml",
]);
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
if (allowedMimeTypes.has(file.mimetype)) {
cb(null, true)
cb(null, true);
} else {
cb(new Error('Only image uploads are allowed'))
cb(new Error("Only image uploads are allowed"));
}
}
})
},
});
async function ensureDataFile() {
try {
await fs.access(dataPath)
await fs.access(dataPath);
} catch {
await fs.mkdir(dataDir, { recursive: true })
await fs.writeFile(dataPath, '[]', 'utf-8')
await fs.mkdir(dataDir, { recursive: true });
await fs.writeFile(dataPath, "[]", "utf-8");
}
}
@@ -65,170 +66,200 @@ function slugify(text) {
.toString()
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
async function readEvents() {
await ensureDataFile()
const raw = await fs.readFile(dataPath, 'utf-8')
await ensureDataFile();
const raw = await fs.readFile(dataPath, "utf-8");
try {
return JSON.parse(raw)
return JSON.parse(raw);
} catch (error) {
console.error('Failed to parse events file, resetting to []', error)
await fs.writeFile(dataPath, '[]', 'utf-8')
return []
console.error("Failed to parse events file, resetting to []", error);
await fs.writeFile(dataPath, "[]", "utf-8");
return [];
}
}
async function writeEvents(events) {
await fs.writeFile(dataPath, JSON.stringify(events, null, 2), 'utf-8')
await fs.writeFile(dataPath, JSON.stringify(events, null, 2), "utf-8");
}
function requireAuth(req, res, next) {
const token = req.header('x-admin-token')
const token = req.header("x-admin-token");
if (!token || token !== adminToken) {
return res.status(401).json({ error: 'Unauthorized' })
return res.status(401).json({ error: "Unauthorized" });
}
return next()
return next();
}
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next)
}
Promise.resolve(fn(req, res, next)).catch(next);
};
}
function buildEventPayload(input, base = {}) {
const allowed = ['title', 'description', 'date', 'time', 'location', 'category', 'image']
const payload = { ...base }
const allowed = [
"title",
"description",
"date",
"time",
"location",
"category",
"image",
];
const payload = { ...base };
for (const key of allowed) {
if (key in input && input[key] !== undefined) {
payload[key] = typeof input[key] === 'string' ? input[key].trim() : input[key]
payload[key] =
typeof input[key] === "string" ? input[key].trim() : input[key];
}
}
return payload
return payload;
}
app.use(cors())
app.use(express.json({ limit: '1mb' }))
app.use('/uploads', express.static(uploadsDir))
app.use(cors());
app.use(express.json({ limit: "1mb" }));
app.use("/uploads", express.static(uploadsDir));
app.get('/api/health', (req, res) => {
res.json({ status: 'ok' })
})
app.get("/api/health", (req, res) => {
res.json({ status: "ok" });
});
app.get('/api/events', asyncHandler(async (req, res) => {
const events = await readEvents()
const sorted = events.slice().sort((a, b) => new Date(a.date) - new Date(b.date))
res.json(sorted)
}))
app.get(
"/api/events",
asyncHandler(async (req, res) => {
const events = await readEvents();
const sorted = events
.slice()
.sort((a, b) => a.date.localeCompare(b.date));
res.json(sorted);
})
);
app.get('/api/events/:slug', asyncHandler(async (req, res) => {
const events = await readEvents()
const event = events.find(e => e.slug === req.params.slug)
if (!event) {
return res.status(404).json({ error: 'Event not found' })
}
res.json(event)
}))
app.post('/api/events', requireAuth, asyncHandler(async (req, res) => {
const data = buildEventPayload(req.body)
if (!data.title || !data.date) {
return res.status(400).json({ error: 'title and date are required' })
}
const events = await readEvents()
const baseSlug = slugify(req.body.slug || data.title || '') || `event-${Date.now()}`
let uniqueSlug = baseSlug
let suffix = 1
while (events.some(event => event.slug === uniqueSlug)) {
uniqueSlug = `${baseSlug}-${suffix++}`
}
const now = new Date().toISOString()
const newEvent = {
id: crypto.randomUUID(),
slug: uniqueSlug,
createdAt: now,
updatedAt: now,
...data,
}
events.push(newEvent)
await writeEvents(events)
res.status(201).json(newEvent)
}))
app.patch('/api/events/:slug', requireAuth, asyncHandler(async (req, res) => {
const events = await readEvents()
const index = events.findIndex(event => event.slug === req.params.slug)
if (index === -1) {
return res.status(404).json({ error: 'Event not found' })
}
const event = events[index]
const updated = buildEventPayload(req.body, event)
let slugToUse = event.slug
if (req.body.slug && req.body.slug !== event.slug) {
const requestedSlug = slugify(req.body.slug)
let uniqueSlug = requestedSlug || event.slug
let suffix = 1
while (events.some((e, i) => i !== index && e.slug === uniqueSlug)) {
uniqueSlug = `${requestedSlug}-${suffix++}`
app.get(
"/api/events/:slug",
asyncHandler(async (req, res) => {
const events = await readEvents();
const event = events.find((e) => e.slug === req.params.slug);
if (!event) {
return res.status(404).json({ error: "Event not found" });
}
slugToUse = uniqueSlug
}
res.json(event);
})
);
const merged = {
...event,
...updated,
slug: slugToUse,
updatedAt: new Date().toISOString(),
}
app.post(
"/api/events",
requireAuth,
asyncHandler(async (req, res) => {
const data = buildEventPayload(req.body);
if (!data.title || !data.date) {
return res.status(400).json({ error: "title and date are required" });
}
events[index] = merged
await writeEvents(events)
res.json(merged)
}))
const events = await readEvents();
const baseSlug =
slugify(req.body.slug || data.title || "") || `event-${Date.now()}`;
let uniqueSlug = baseSlug;
let suffix = 1;
while (events.some((event) => event.slug === uniqueSlug)) {
uniqueSlug = `${baseSlug}-${suffix++}`;
}
app.delete('/api/events/:slug', requireAuth, asyncHandler(async (req, res) => {
const events = await readEvents()
const index = events.findIndex(event => event.slug === req.params.slug)
if (index === -1) {
return res.status(404).json({ error: 'Event not found' })
}
const now = new Date().toISOString();
const newEvent = {
id: crypto.randomUUID(),
slug: uniqueSlug,
createdAt: now,
updatedAt: now,
...data,
};
const [removed] = events.splice(index, 1)
await writeEvents(events)
res.json({ success: true, removed })
}))
events.push(newEvent);
await writeEvents(events);
res.status(201).json(newEvent);
})
);
app.post('/api/uploads', requireAuth, upload.single('file'), (req, res) => {
app.patch(
"/api/events/:slug",
requireAuth,
asyncHandler(async (req, res) => {
const events = await readEvents();
const index = events.findIndex((event) => event.slug === req.params.slug);
if (index === -1) {
return res.status(404).json({ error: "Event not found" });
}
const event = events[index];
const updated = buildEventPayload(req.body, event);
let slugToUse = event.slug;
if (req.body.slug && req.body.slug !== event.slug) {
const requestedSlug = slugify(req.body.slug);
let uniqueSlug = requestedSlug || event.slug;
let suffix = 1;
while (events.some((e, i) => i !== index && e.slug === uniqueSlug)) {
uniqueSlug = `${requestedSlug}-${suffix++}`;
}
slugToUse = uniqueSlug;
}
const merged = {
...event,
...updated,
slug: slugToUse,
updatedAt: new Date().toISOString(),
};
events[index] = merged;
await writeEvents(events);
res.json(merged);
})
);
app.delete(
"/api/events/:slug",
requireAuth,
asyncHandler(async (req, res) => {
const events = await readEvents();
const index = events.findIndex((event) => event.slug === req.params.slug);
if (index === -1) {
return res.status(404).json({ error: "Event not found" });
}
const [removed] = events.splice(index, 1);
await writeEvents(events);
res.json({ success: true, removed });
})
);
app.post("/api/uploads", requireAuth, upload.single("file"), (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' })
return res.status(400).json({ error: "No file uploaded" });
}
const relativeUrl = `/uploads/${req.file.filename}`
const absoluteUrl = `${req.protocol}://${req.get('host')}${relativeUrl}`
res.status(201).json({ url: absoluteUrl, path: relativeUrl })
})
const relativeUrl = `/uploads/${req.file.filename}`;
const absoluteUrl = `${req.protocol}://${req.get("host")}${relativeUrl}`;
res.status(201).json({ url: absoluteUrl, path: relativeUrl });
});
app.get('/api/admin/verify', requireAuth, (req, res) => {
res.json({ ok: true })
})
app.get("/api/admin/verify", requireAuth, (req, res) => {
res.json({ ok: true });
});
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ error: err.message })
return res.status(400).json({ error: err.message });
}
if (err && err.message === 'Only image uploads are allowed') {
return res.status(400).json({ error: err.message })
if (err && err.message === "Only image uploads are allowed") {
return res.status(400).json({ error: err.message });
}
console.error(err)
res.status(500).json({ error: 'Internal server error' })
})
console.error(err);
res.status(500).json({ error: "Internal server error" });
});
app.listen(port, () => {
console.log(`Events API listening on port ${port}`)
})
console.log(`Events API listening on port ${port}`);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { Link } from 'react-router-dom'
const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:4001').replace(/\/$/, '')
const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || '').replace(/\/$/, '')
function resolveEventImage(value, fallback) {
if (typeof value === 'string') {
@@ -20,50 +20,28 @@ function resolveEventImage(value, fallback) {
}
export function EventCard({ e }) {
const dt = new Date(e.date)
const dt = new Date(e.date + 'T00:00:00')
const hasValidDate = !Number.isNaN(dt)
const mon = hasValidDate ? dt.toLocaleString('en', { month: 'short' }) : ''
const day = hasValidDate ? dt.getDate() : ''
const mon = hasValidDate ? dt.toLocaleString('en', { month: 'short', timeZone: 'America/Chicago' }) : ''
const day = hasValidDate ? new Intl.DateTimeFormat('en', { day: 'numeric', timeZone: 'America/Chicago' }).format(dt) : ''
const details = [e.time, e.location].filter(Boolean).join(' | ')
const getEventImage = (title) => {
const lowerTitle = `${title || ''}`.toLowerCase()
if (lowerTitle.includes('community sabbath lunch')) {
return '/assets/potluck.png'
}
if (lowerTitle.includes('youth vespers')) {
return '/assets/youth_vespers.png'
}
if (lowerTitle.includes('neighborhood food drive')) {
return '/assets/family_entry.png'
}
if (lowerTitle.includes('potluck') || lowerTitle.includes('lunch') || lowerTitle.includes('dinner')) {
return '/assets/potluck.png'
}
if (lowerTitle.includes('family') || lowerTitle.includes('community')) {
return '/assets/family_entry.png'
}
if (lowerTitle.includes('welcome') || lowerTitle.includes('committee')) {
return '/assets/welcome_commite.png'
}
return '/assets/potluck.png'
}
const defaultImage = getEventImage(e.title)
const imageSrc = resolveEventImage(e.image, defaultImage)
const imageSrc = resolveEventImage(e.image, '')
return (
<article className="card p-10 flex flex-col" style={{ aspectRatio: '4/3' }}>
<div className="flex items-center gap-6 mb-8">
<div className="w-20 h-20 rounded-full overflow-hidden bg-primary text-white flex items-center justify-center">
<img
src={imageSrc}
alt={`${e.title} event`}
className="w-full h-full object-cover"
loading="lazy"
/>
</div>
{imageSrc && (
<div className="w-20 h-20 rounded-full overflow-hidden bg-primary text-white flex items-center justify-center">
<img
src={imageSrc}
alt={`${e.title} event`}
className="w-full h-full object-cover"
loading="lazy"
/>
</div>
)}
<div>
<h3 className="font-heading text-h3">{e.title}</h3>
<div className="text-muted text-small mt-2">
@@ -162,7 +140,7 @@ export function SermonCard({ s }) {
</div>
<div>
<h3 className="font-heading text-h3">{s.title}</h3>
<div className="text-muted text-small mt-2">{s.speaker} | {new Date(s.date).toLocaleDateString()}</div>
<div className="text-muted text-small mt-2">{s.speaker} | {new Date(s.date + 'T00:00:00').toLocaleDateString('en-US', { timeZone: 'America/Chicago' })}</div>
</div>
</div>
<p className="mt-6 text-body mb-8">{s.summary}</p>

View File

@@ -2,82 +2,46 @@
import React from 'react'
import { Link } from 'react-router-dom'
export default function Footer(){
export default function Footer() {
const year = new Date().getFullYear()
return (
<footer id="footer" role="contentinfo" className="bg-sand border-t border-subtle mt-12">
<div className="container grid md:grid-cols-3 gap-8 py-16">
<div className="container grid md:grid-cols-2 gap-8 py-16">
<div>
<h3 className="font-heading text-h3 mb-4">Annaville Seventh-day Adventist Church</h3>
<p className="text-body mb-2">2710 Violet Rd<br/>Corpus Christi, TX 78410</p>
<p className="text-body mb-2">2710 Violet Rd<br />Corpus Christi, TX 78410</p>
<p className="mb-2">
<a href="tel:+13612415501" className="text-primary underline hover:text-primaryHover">
Call (361) 241-5501
</a>
</p>
<p className="mb-2">
<a href="https://maps.google.com/?q=2710+Violet+Rd,+Corpus+Christi,+TX+78410"
className="text-primary underline hover:text-primaryHover">
<a href="https://maps.google.com/?q=2710+Violet+Rd,+Corpus+Christi,+TX+78410"
className="text-primary underline hover:text-primaryHover">
Get Directions
</a>
</p>
<p className="text-muted text-small">Sabbath School 9:30 AM Divine Worship 11:00 AM</p>
<p className="text-muted text-small">Sabbath School 9:45 AM CT Divine Worship 11:00 AM CT</p>
{/* Leadership Information */}
<div className="mt-6 text-small text-muted">
<p><strong>Pastor:</strong> Matt McMearty</p>
<p><strong>Head Elder:</strong> Regena Simms</p>
</div>
</div>
<div>
<h3 className="font-heading text-h3 mb-4">Quick Links</h3>
<ul className="space-y-3">
<li><Link to="/about" className="text-body hover:text-primary transition-colors">About Us</Link></li>
<li><Link to="/services" className="text-body hover:text-primary transition-colors">Services</Link></li>
<li><Link to="/resources" className="text-body hover:text-primary transition-colors">Resources</Link></li>
<li><Link to="/prayer-requests" className="text-body hover:text-primary transition-colors">Prayer Requests</Link></li>
<li><Link to="/calendar" className="text-body hover:text-primary transition-colors">Calendar</Link></li>
<li><Link to="/beliefs" className="text-body hover:text-primary transition-colors">Our Beliefs</Link></li>
<li><Link to="/contact" className="text-body hover:text-primary transition-colors">Contact</Link></li>
</ul>
</div>
<div>
<h3 className="font-heading text-h3 mb-4">Newsletter</h3>
<p className="text-body mb-4">
If you would like to receive our newsletter please fill out the form below.
</p>
<form className="space-y-4">
<div>
<label htmlFor="footer-newsletter-name" className="block text-sm font-medium text-ink mb-2">
Name:
</label>
<input
id="footer-newsletter-name"
type="text"
className="w-full border border-subtle rounded-lg px-4 py-3 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="Your name"
/>
</div>
<div>
<label htmlFor="footer-newsletter-email" className="block text-sm font-medium text-ink mb-2">
Email Address:
</label>
<input
id="footer-newsletter-email"
type="email"
className="w-full border border-subtle rounded-lg px-4 py-3 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="your.email@example.com"
/>
</div>
<button type="submit" className="btn w-full">
Subscribe to Newsletter
</button>
</form>
</div>
</div>
<div className="border-t border-subtle py-6">
<div className="container flex flex-wrap items-center gap-4 justify-between">
<div className="text-small text-muted">
@@ -86,7 +50,6 @@ export default function Footer(){
<div className="text-small flex flex-wrap items-center gap-4">
<Link className="text-muted hover:text-primary transition-colors" to="/privacy">Privacy Policy</Link>
<Link className="text-muted hover:text-primary transition-colors" to="/terms">Terms of Use</Link>
<Link className="text-muted hover:text-primary transition-colors" to="/accessibility">Accessibility</Link>
<Link to="/admin/events" className="btn-outline text-xs px-3 py-1">Admin Events</Link>
</div>
</div>

View File

@@ -4,25 +4,23 @@ import { Link, NavLink } from 'react-router-dom'
import { track, events } from '../utils/analytics'
const navItems = [
{ to:'/about', label:'ABOUT US' },
{ to:'/services', label:'SERVICES' },
{ to:'/resources', label:'RESOURCES' },
{ to:'/events', label:'EVENTS' },
{ to:'/prayer-requests', label:'PRAYER REQUESTS' },
{ to:'/calendar', label:'CALENDAR' },
{ to:'/beliefs', label:'OUR BELIEFS' },
{ to: '/about', label: 'ABOUT US' },
{ to: '/services', label: 'SERVICES' },
{ to: '/resources', label: 'RESOURCES' },
{ to: '/events', label: 'EVENTS' },
{ to: '/calendar', label: 'CALENDAR' },
]
export default function Header(){
export default function Header() {
const [open, setOpen] = useState(false)
return (
<header role="banner" className="z-50 bg-white/90 backdrop-blur border-b border-subtle">
<nav id="navigation" aria-label="Main navigation" className="container flex items-center justify-between h-20">
<div className="flex items-center gap-4">
<Link to="/" className="flex items-center gap-4 font-heading text-lg font-semibold tracking-tight text-ink leading-tight">
<img
src="/assets/favicon.ico.gif"
alt="Annaville SDA Church Logo"
<img
src="/assets/favicon.ico.gif"
alt="Annaville SDA Church Logo"
className="w-20 h-20 rounded-lg"
/>
<span>
@@ -32,60 +30,62 @@ export default function Header(){
</div>
<div className="hidden md:flex items-center gap-6">
{navItems.map(item => (
<NavLink
key={item.to}
to={item.to}
className={({isActive})=>`text-sm font-medium transition-colors ${isActive?'text-primary font-semibold':'text-ink hover:text-primary'}`}
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) => `text-sm font-medium transition-colors ${isActive ? 'text-primary font-semibold' : 'text-ink hover:text-primary'}`}
>
{item.label}
</NavLink>
))}
<Link
to="/contact"
className="btn text-sm px-4 py-2"
onClick={()=>track(events.CTA_CLICK,{label:'contact'})}
<Link
to="/contact"
className="btn text-sm px-4 py-2"
onClick={() => track(events.CTA_CLICK, { label: 'contact' })}
>
Contact Us
</Link>
</div>
<button
aria-label="Open menu"
className="md:hidden btn-ghost"
onClick={()=>setOpen(true)}
<button
aria-label="Open menu"
className="md:hidden btn-ghost"
onClick={() => setOpen(true)}
>
<span aria-hidden className="text-3xl"></span>
</button>
</nav>
{open && (
<div role="dialog" aria-modal="true" className="md:hidden fixed inset-0 bg-black/30" onClick={()=>setOpen(false)}>
<div className="absolute top-0 right-0 w-[80%] h-full bg-white shadow-level1 p-8" onClick={e=>e.stopPropagation()}>
<div className="flex justify-between items-center mb-12">
<span className="font-heading text-h2">Menu</span>
<button className="btn-ghost" onClick={()=>setOpen(false)} aria-label="Close menu"></button>
<div role="dialog" aria-modal="true" className="md:hidden fixed inset-0 z-[100] bg-white" onClick={() => setOpen(false)}>
<div className="h-full w-full bg-white flex flex-col" onClick={e => e.stopPropagation()}>
<div className="bg-white border-b border-gray-200 p-4 flex justify-between items-center flex-shrink-0">
<span className="font-heading text-2xl font-bold text-gray-900">Menu</span>
<button className="text-3xl text-gray-700 leading-none p-2 hover:bg-gray-100 rounded-lg transition-colors" onClick={() => setOpen(false)} aria-label="Close menu"></button>
</div>
<ul className="space-y-6">
{navItems.map(item => (
<li key={item.to}>
<NavLink
to={item.to}
onClick={()=>setOpen(false)}
className={({isActive})=>`block py-5 text-body ${isActive?'text-primary font-semibold':'text-ink hover:text-primary'}`}
<nav className="flex-1 flex flex-col justify-center px-8 py-8 bg-white">
<ul className="space-y-4">
{navItems.map(item => (
<li key={item.to}>
<NavLink
to={item.to}
onClick={() => setOpen(false)}
className={({ isActive }) => `block py-3 font-semibold text-lg text-center transition-colors ${isActive ? 'text-primary' : 'text-gray-900 hover:text-primary'}`}
>
{item.label}
</NavLink>
</li>
))}
<li className="pt-4 border-t border-gray-200">
<Link
to="/contact"
className="block py-3 font-semibold text-lg text-center text-gray-900 hover:text-primary transition-colors"
onClick={() => { setOpen(false); track(events.CTA_CLICK, { label: 'contact' }) }}
>
{item.label}
</NavLink>
Contact Us
</Link>
</li>
))}
<li>
<Link
to="/contact"
className="btn w-full"
onClick={()=>{setOpen(false); track(events.CTA_CLICK,{label:'contact'})}}
>
Contact Us
</Link>
</li>
</ul>
</ul>
</nav>
</div>
</div>
)}

View File

@@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'
import { track, events } from '../utils/analytics'
import LazyImage from './LazyImage'
export function TextHero(){
export function TextHero() {
const scrollToServiceTimes = () => {
const element = document.getElementById('service-times')
if (element) {
@@ -13,26 +13,36 @@ export function TextHero(){
track(events.CTA_CLICK, { label: 'service_times' })
}
const scrollToUpcomingEvents = () => {
const element = document.getElementById('upcoming-events')
if (element) {
element.scrollIntoView({ behavior: 'smooth' })
}
track(events.CTA_CLICK, { label: 'plan_visit' })
}
return (
<section className="relative bg-gradient-to-br from-sand to-surface">
<section className="relative bg-gradient-to-br from-primary/15 via-sand/20 to-white">
<div className="container py-20">
<div className="grid lg:grid-cols-2 gap-16 items-center">
{/* Content on the Left */}
<div className="order-1 lg:order-1">
<h1 className="font-heading text-h1 mb-8">Welcome to Annaville Seventh-day Adventist Church</h1>
<p className="text-xs uppercase tracking-[0.3em] text-primary font-semibold mb-6">
Welcome to Annaville SDA Church
</p>
<h1 className="font-heading text-h1 mb-8 text-ink">Welcome to Annaville Seventh-day Adventist Church</h1>
<p className="text-ink text-body mb-10">The Annaville SDA Church offers worship services for members, non-members, or anyone interested in learning more about practical Christian living from the Word of God.</p>
<div className="mb-10 text-muted text-small">Sabbath School 9:30 AM Divine Worship 11:00 AM 2710 Violet Rd</div>
<div className="mb-10 text-muted text-small">Sabbath School 9:45 AM CT Divine Worship 11:00 AM CT 2710 Violet Rd</div>
<div className="flex flex-wrap gap-6">
<Link
to="/visit"
className="btn"
onClick={() => track(events.CTA_CLICK, { label: 'plan_visit' })}
<button
className="btn"
onClick={scrollToUpcomingEvents}
data-cta="plan_visit"
>
Plan Your Visit
</Link>
<button
className="btn-outline"
</button>
<button
className="btn-outline"
onClick={scrollToServiceTimes}
data-cta="service_times"
>
@@ -40,13 +50,13 @@ export function TextHero(){
</button>
</div>
</div>
{/* Large Hero Image on the Right */}
<div className="order-2 lg:order-2">
<LazyImage
src="/assets/hero_golden_hour.png"
alt="Annaville SDA Church building during golden hour with people walking on the path"
className="w-full h-[400px] object-cover rounded-lg shadow-lg"
<LazyImage
src="/assets/hero_golden_hour.png"
alt="Annaville SDA Church building during golden hour with people walking on the path"
className="w-full h-[600px] object-cover rounded-lg shadow-lg"
/>
</div>
</div>
@@ -56,7 +66,7 @@ export function TextHero(){
}
// Safe photo hero variant with dim overlay; H1 remains text-first
export function PhotoHeroSafe(){
export function PhotoHeroSafe() {
const scrollToServiceTimes = () => {
const element = document.getElementById('service-times')
if (element) {
@@ -65,6 +75,14 @@ export function PhotoHeroSafe(){
track(events.CTA_CLICK, { label: 'service_times' })
}
const scrollToUpcomingEvents = () => {
const element = document.getElementById('upcoming-events')
if (element) {
element.scrollIntoView({ behavior: 'smooth' })
}
track(events.CTA_CLICK, { label: 'plan_visit' })
}
return (
<section className="relative">
<LazyImage src="/assets/hero_golden_hour.png" alt="Annaville SDA Church building during golden hour" className="absolute right-0 top-8 w-1/4 h-full" />
@@ -72,17 +90,17 @@ export function PhotoHeroSafe(){
<div className="relative container py-40 text-white">
<h1 className="font-heading text-h1 mb-8">Welcome to Annaville Seventh-day Adventist Church</h1>
<p className="text-white text-body mb-10">The Annaville SDA Church offers worship services for members, non-members, or anyone interested in learning more about practical Christian living from the Word of God.</p>
<div className="mb-10 text-white/90 text-small">Sabbath School 9:30 AM Divine Worship 11:00 AM 2710 Violet Rd</div>
<div className="mb-10 text-white/90 text-small">Sabbath School 9:45 AM CT Divine Worship 11:00 AM CT 2710 Violet Rd</div>
<div className="flex flex-wrap gap-6">
<Link
to="/visit"
<button
className="btn"
onClick={scrollToUpcomingEvents}
data-cta="plan_visit"
>
Plan Your Visit
</Link>
<button
className="btn-outline text-white border-white hover:bg-white/10"
</button>
<button
className="btn-outline text-white border-white hover:bg-white/10"
onClick={scrollToServiceTimes}
data-cta="service_times"
>

View File

@@ -2,12 +2,10 @@ import React from 'react'
import { NavLink } from 'react-router-dom'
const navItems = [
{ to:'/about', label:'ABOUT US' },
{ to:'/services', label:'SERVICES' },
{ to:'/resources', label:'RESOURCES' },
{ to:'/prayer-requests', label:'PRAYER REQUESTS' },
{ to:'/calendar', label:'CALENDAR' },
{ to:'/beliefs', label:'OUR BELIEFS' },
{ to: '/about', label: 'ABOUT US' },
{ to: '/services', label: 'SERVICES' },
{ to: '/resources', label: 'RESOURCES' },
{ to: '/calendar', label: 'CALENDAR' },
]
export default function SidebarNav() {
@@ -15,10 +13,10 @@ export default function SidebarNav() {
<div className="bg-yellow-100 p-4 min-h-screen w-64">
<nav className="space-y-2">
{navItems.map(item => (
<NavLink
key={item.to}
to={item.to}
className={({isActive}) => `
<NavLink
key={item.to}
to={item.to}
className={({ isActive }) => `
block w-full text-left px-4 py-3 text-sm font-bold uppercase
border border-gray-300 bg-gray-200 hover:bg-gray-300
${isActive ? 'bg-gray-400' : ''}
@@ -28,13 +26,13 @@ export default function SidebarNav() {
</NavLink>
))}
</nav>
<div className="mt-6">
<button className="w-full px-4 py-2 text-sm font-medium text-red-600 bg-yellow-100 border border-red-600 rounded-full hover:bg-red-50">
Subscribe to our Newsletter &gt;&gt;
</button>
</div>
<div className="mt-4">
<a href="#" className="text-blue-600 underline text-sm">Message Center</a>
</div>

View File

@@ -8,9 +8,7 @@ import Home from './pages/Home'
import About from './pages/About'
import Services from './pages/Services'
import Resources from './pages/Resources'
import PrayerRequests from './pages/PrayerRequests'
import Calendar from './pages/Calendar'
import Beliefs from './pages/Beliefs'
import Contact from './pages/Contact'
import Privacy from './pages/Privacy'
import Terms from './pages/Terms'
@@ -33,9 +31,7 @@ const router = createBrowserRouter([
{ path: 'about', element: <About /> },
{ path: 'services', element: <Services /> },
{ path: 'resources', element: <Resources /> },
{ path: 'prayer-requests', element: <PrayerRequests /> },
{ path: 'calendar', element: <Calendar /> },
{ path: 'beliefs', element: <Beliefs /> },
{ path: 'contact', element: <Contact /> },
{ path: 'privacy', element: <Privacy /> },
{ path: 'terms', element: <Terms /> },
@@ -64,7 +60,7 @@ const router = createBrowserRouter([
// Initialize analytics after DOM is ready
document.addEventListener('DOMContentLoaded', () => {
initAnalytics()
// Initialize Google Analytics and GTM
// Uncomment these lines and add your tracking IDs in analytics-config.js
// initGA()

View File

@@ -3,7 +3,7 @@ import { Helmet } from 'react-helmet-async'
import { Link } from 'react-router-dom'
import StaticMap from '../components/StaticMap'
export default function About(){
export default function About() {
return (
<>
<Helmet>
@@ -33,7 +33,7 @@ export default function About(){
<img
src="/assets/pray_heart.png"
alt="Church members praying together"
className="w-full h-64 object-cover"
className="w-full h-96 object-cover"
/>
</div>
</div>
@@ -60,11 +60,11 @@ export default function About(){
<div className="space-y-4">
<div className="p-6 bg-sand/60 rounded-xl border border-subtle">
<p className="font-semibold text-ink">Sabbath School</p>
<p className="text-sm text-muted">Saturdays at 9:30 AM</p>
<p className="text-sm text-muted">Saturdays at 9:45 AM CT</p>
</div>
<div className="p-6 bg-sand/60 rounded-xl border border-subtle">
<p className="font-semibold text-ink">Divine Worship</p>
<p className="text-sm text-muted">Saturdays at 11:00 AM</p>
<p className="text-sm text-muted">Saturdays at 11:00 AM CT</p>
</div>
<div className="p-6 bg-sand/60 rounded-xl border border-subtle">
<p className="font-semibold text-ink">Potluck Fellowship Dinner</p>

View File

@@ -1,275 +0,0 @@
import React from 'react'
import { Helmet } from 'react-helmet-async'
export default function Beliefs() {
const beliefs = [
{
id: 1,
title: "The Holy Scriptures",
category: "foundation",
summary: "The Holy Scriptures, Old and New Testaments, are the written Word of God, given by divine inspiration through holy men of God who spoke and wrote as they were moved by the Holy Spirit.",
fullText: "The Holy Scriptures, Old and New Testaments, are the written Word of God, given by divine inspiration through holy men of God who spoke and wrote as they were moved by the Holy Spirit. In this Word, God has committed to man the knowledge necessary for salvation. The Holy Scriptures are the infallible revelation of His will. They are the standard of character, the test of experience, the authoritative revealer of doctrines, and the trustworthy record of God's acts in history.",
references: "2 Peter 1:20, 21; 2 Tim. 3:16, 17; Ps. 119:105; Prov. 30:5, 6; Isa. 8:20; John 17:17; 1 Thess. 2:13; Heb. 4:12."
},
{
id: 2,
title: "The Trinity",
category: "foundation",
summary: "There is one God: Father, Son, and Holy Spirit, a unity of three co-eternal Persons.",
fullText: "There is one God: Father, Son, and Holy Spirit, a unity of three co-eternal Persons. God is immortal, all-powerful, all-knowing, above all, and ever present. He is infinite and beyond human comprehension, yet known through His self-revelation. He is forever worthy of worship, adoration, and service by the whole creation.",
references: "Deut. 6:4; Matt. 28:19; 2 Cor. 13:14; Eph. 4:4-6; 1 Peter 1:2; 1 Tim. 1:17; Rev. 14:7."
},
{
id: 3,
title: "The Father",
category: "foundation",
summary: "God the eternal Father is the Creator, Source, Sustainer, and Sovereign of all creation.",
fullText: "God the eternal Father is the Creator, Source, Sustainer, and Sovereign of all creation. He is just and holy, merciful and gracious, slow to anger, and abounding in steadfast love and faithfulness. The qualities and powers exhibited in the Son and the Holy Spirit are also revelations of the Father.",
references: "Gen. 1:1; Rev. 4:11; 1 Cor. 15:28; John 3:16; 1 John 4:8; 1 Tim. 1:17; Ex. 34:6, 7; John 14:9."
},
{
id: 4,
title: "The Son",
category: "foundation",
summary: "God the eternal Son became incarnate in Jesus Christ.",
fullText: "God the eternal Son became incarnate in Jesus Christ. Through Him all things were created, the character of God is revealed, the salvation of humanity is accomplished, and the world is judged. Forever truly God, He became also truly man, Jesus the Christ. He was conceived of the Holy Spirit and born of the virgin Mary. He lived and experienced temptation as a human being, but perfectly exemplified the righteousness and love of God. By His miracles He manifested God's power and was attested as God's promised Messiah. He suffered and died voluntarily on the cross for our sins and in our place, was raised from the dead, and ascended to minister in the heavenly sanctuary in our behalf. He will come again in glory for the final deliverance of His people and the restoration of all things.",
references: "John 1:1-3, 14; Col. 1:15-19; John 10:30; 14:9; Rom. 6:23; 2 Cor. 5:17-19; John 5:22; Luke 1:35; Phil. 2:5-11; Heb. 2:9-18; 1 Cor. 15:3, 4; Heb. 8:1, 2; John 14:1-3."
},
{
id: 5,
title: "The Holy Spirit",
category: "foundation",
summary: "God the eternal Spirit was active with the Father and the Son in Creation, incarnation, and redemption.",
fullText: "God the eternal Spirit was active with the Father and the Son in Creation, incarnation, and redemption. He inspired the writers of Scripture. He filled Christ's life with power. He draws and convicts human beings; and those who respond He renews and transforms into the image of God. Sent by the Father and the Son to be always with His children, He extends spiritual gifts to the church, empowers it to bear witness to Christ, and in harmony with the Scriptures leads it into all truth.",
references: "Gen. 1:1, 2; Luke 1:35; 4:18; Acts 10:38; 2 Peter 1:21; 2 Cor. 3:18; Eph. 4:11, 12; Acts 1:8; John 14:16-18, 26; 15:26, 27; 16:7-13."
},
{
id: 6,
title: "Creation",
category: "foundation",
summary: "God is Creator of all things, and has revealed in Scripture the authentic account of His creative activity.",
fullText: "God is Creator of all things, and has revealed in Scripture the authentic account of His creative activity. In six days the Lord made \"the heaven and the earth\" and all living things upon the earth, and rested on the seventh day of that first week. Thus He established the Sabbath as a perpetual memorial of His completed creative work. The first man and woman were made in the image of God as the crowning work of Creation, given dominion over the world, and charged with responsibility to care for it. When the world was finished it was ``very good,'' declaring the glory of God.",
references: "Gen. 1; 2; Ex. 20:8-11; Ps. 19:1-6; 33:6, 9; 104; Heb. 11:3."
},
{
id: 7,
title: "The Nature of Man",
category: "humanity",
summary: "Man and woman were made in the image of God with individuality, the power and freedom to think and to do.",
fullText: "Man and woman were made in the image of God with individuality, the power and freedom to think and to do. Though created free beings, each is an indivisible unity of body, mind, and spirit, dependent upon God for life and breath and all else. When our first parents disobeyed God, they denied their dependence upon Him and fell from their high position under God. The image of God in them was marred and they became subject to death. Their descendants share this fallen nature and its consequences. They are born with weaknesses and tendencies to evil. But God in Christ reconciled the world to Himself and by His Spirit restores in penitent mortals the image of their Maker. Created for the glory of God, they are called to love Him and one another, and to care for their environment.",
references: "Gen. 1:26-28; 2:7; Ps. 8:4-8; Acts 17:24-28; Gen. 3; Ps. 51:5; Rom. 5:12-17; 2 Cor. 5:19, 20; Ps. 51:10; 1 John 4:7, 8, 11, 20; Gen. 2:15."
},
{
id: 8,
title: "The Great Controversy",
category: "salvation",
summary: "All humanity is now involved in a great controversy between Christ and Satan regarding the character of God, His law, and His sovereignty over the universe.",
fullText: "All humanity is now involved in a great controversy between Christ and Satan regarding the character of God, His law, and His sovereignty over the universe. This conflict originated in heaven when a created being, endowed with freedom of choice, in self-exaltation became Satan, God's adversary, and led into rebellion a portion of the angels. He introduced the spirit of rebellion into this world when he led Adam and Eve into sin. This human sin resulted in the distortion of the image of God in humanity, the disordering of the created world, and its eventual devastation at the time of the worldwide flood. Observed by the whole creation, this world became the arena of the universal conflict, out of which the God of love will ultimately be vindicated. To assist His people in this controversy, Christ sends the Holy Spirit and the loyal angels to guide, protect, and sustain them in the way of salvation.",
references: "Rev. 12:4-9; Isa. 14:12-14; Eze. 28:12-18; Gen. 3; Rom. 1:19-32; 5:12-21; 8:19-22; Gen. 6-8; 2 Peter 3:6; 1 Cor. 4:9; Heb. 1:14."
},
{
id: 9,
title: "The Life, Death, and Resurrection of Christ",
category: "salvation",
summary: "In Christ's life of perfect obedience to God's will, His suffering, death, and resurrection, God provided the only means of atonement for human sin.",
fullText: "In Christ's life of perfect obedience to God's will, His suffering, death, and resurrection, God provided the only means of atonement for human sin, so that those who by faith accept this atonement may have eternal life, and the whole creation may better understand the infinite and holy love of the Creator. This perfect atonement vindicates the righteousness of God's law and the graciousness of His character; for it both condemns our sin and provides for our forgiveness. The death of Christ is substitutionary and expiatory, reconciling and transforming. The resurrection of Christ proclaims God's triumph over the forces of evil, and for those who accept the atonement assures their final victory over sin and death. It declares the Lordship of Jesus Christ, before whom every knee in heaven and on earth will bow.",
references: "John 3:16; Isa. 53; 1 Peter 2:21, 22; 1 Cor. 15:3, 4, 20-22; 2 Cor. 5:14, 15, 19-21; Rom. 1:4; 3:25; 4:25; 8:3, 4; 1 John 2:2; 4:10; Col. 2:15; Phil. 2:6-11."
},
{
id: 10,
title: "The Experience of Salvation",
category: "salvation",
summary: "In infinite love and mercy God made Christ, who knew no sin, to be sin for us, so that in Him we might be made the righteousness of God.",
fullText: "In infinite love and mercy God made Christ, who knew no sin, to be sin for us, so that in Him we might be made the righteousness of God. Led by the Holy Spirit we sense our need, acknowledge our sinfulness, repent of our transgressions, and exercise faith in Jesus as Lord and Christ, as Substitute and Example. This faith which receives salvation comes through the divine power of the Word and is the gift of God's grace. Through Christ we are justified, adopted as God's sons and daughters, and delivered from the lordship of sin. Through the Spirit we are born again and sanctified; the Spirit renews our minds, writes God's law of love in our hearts, and we are given the power to live a holy life. Abiding in Him we become partakers of the divine nature and have the assurance of salvation now and in the judgment.",
references: "2 Cor. 5:17-21; John 3:16; Gal. 1:4; 4:4-7; Titus 3:3-7; John 16:8; Gal. 3:13, 14; 1 Peter 2:21, 22; Rom. 10:17; Luke 17:5; Mark 9:23, 24; Eph. 2:5-10; Rom. 3:21-26; Col. 1:13, 14; Rom. 8:14-17; Gal. 3:26; John 3:3-8; 1 Peter 1:23; Rom. 12:2; Heb. 8:7-12; Eze. 36:25-27; 2 Peter 1:3, 4; Rom. 8:1-4; 5:6-10."
},
{
id: 11,
title: "The Church",
category: "church",
summary: "The church is the community of believers who confess Jesus Christ as Lord and Saviour.",
fullText: "The church is the community of believers who confess Jesus Christ as Lord and Saviour. In continuity with the people of God in Old Testament times, we are called out from the world; and we join together for worship, for fellowship, for instruction in the Word, for the celebration of the Lord's Supper, for service to all mankind, and for the worldwide proclamation of the gospel. The church derives its authority from Christ, who is the incarnate Word, and from the Scriptures, which are the written Word. The church is God's family; adopted by Him as children, its members live on the basis of the new covenant. The church is the body of Christ, a community of faith of which Christ Himself is the Head. The church is the bride for whom Christ died that He might sanctify and cleanse her. At His return in triumph, He will present her to Himself a glorious church, the faithful of all the ages, the purchase of His blood, not having spot or wrinkle, but holy and without blemish.",
references: "Gen. 12:3; Acts 7:38; Eph. 4:11-15; 3:8-11; Matt. 28:19, 20; 16:13-20; 18:18; Eph. 2:19-22; 1:22, 23; 5:23-27; Col. 1:17, 18."
},
{
id: 12,
title: "The Remnant and Its Mission",
category: "church",
summary: "The universal church is composed of all who truly believe in Christ, but in the last days, a time of widespread apostasy, a remnant has been called out.",
fullText: "The universal church is composed of all who truly believe in Christ, but in the last days, a time of widespread apostasy, a remnant has been called out to keep the commandments of God and the faith of Jesus. This remnant announces the arrival of the judgment hour, proclaims salvation through Christ, and heralds the approach of His second advent. This proclamation is symbolized by the three angels of Revelation 14; it coincides with the work of judgment in heaven and results in a work of repentance and reform on earth. Every believer is called to have a personal part in this worldwide witness.",
references: "Rev. 12:17; 14:6-12; 18:1-4; 2 Cor. 5:10; Jude 3, 14; 1 Peter 1:16-19; 2 Peter 3:10-14; Rev. 21:1-14."
},
{
id: 13,
title: "Unity in the Body of Christ",
category: "church",
summary: "The church is one body with many members, called from every nation, kindred, tongue, and people.",
fullText: "The church is one body with many members, called from every nation, kindred, tongue, and people. In Christ we are a new creation; distinctions of race, culture, learning, and nationality, and differences between high and low, rich and poor, male and female, must not be divisive among us. We are all equal in Christ, who by one Spirit has bonded us into one fellowship with Him and with one another; we are to serve and be served without partiality or reservation. Through the revelation of Jesus Christ in the Scriptures we share the same faith and hope, and reach out in one witness to all. This unity has its source in the oneness of the triune God, who has adopted us as His children.",
references: "Rom. 12:4, 5; 1 Cor. 12:12-14; Matt. 28:19, 20; Ps. 133:1; 2 Cor. 5:16, 17; Acts 17:26, 27; Gal. 3:27, 29; Col. 3:10-15; Eph. 4:14-16; 4:1-6; John 17:20-23."
},
{
id: 14,
title: "Baptism",
category: "ordinances",
summary: "By baptism we confess our faith in the death and resurrection of Jesus Christ, and testify of our death to sin and of our purpose to walk in newness of life.",
fullText: "By baptism we confess our faith in the death and resurrection of Jesus Christ, and testify of our death to sin and of our purpose to walk in newness of life. Thus we acknowledge Christ as Lord and Saviour, become His people, and are received as members by His church. Baptism is a symbol of our union with Christ, the forgiveness of our sins, and our reception of the Holy Spirit. It is by immersion in water and is contingent on an affirmation of faith in Jesus and evidence of repentance of sin. It follows instruction in the Holy Scriptures and acceptance of their teachings.",
references: "Rom. 6:1-6; Col. 2:12, 13; Acts 16:30-33; 22:16; 2:38; Matt. 28:19, 20."
},
{
id: 15,
title: "The Lord's Supper",
category: "ordinances",
summary: "The Lord's Supper is a participation in the emblems of the body and blood of Jesus as an expression of faith in Him, our Lord and Saviour.",
fullText: "The Lord's Supper is a participation in the emblems of the body and blood of Jesus as an expression of faith in Him, our Lord and Saviour. In this experience of communion Christ is present to meet and strengthen His people. As we partake, we joyfully proclaim the Lord's death until He comes again. Preparation for the Supper includes self-examination, repentance, and confession. The Master ordained the service of foot washing to signify renewed cleansing, to express a willingness to serve one another in Christlike humility, and to unite our hearts in love. The communion service is open to all believing Christians.",
references: "1 Cor. 10:16, 17; 11:23-30; Matt. 26:17-30; Rev. 3:20; John 6:48-63; 13:1-17."
},
{
id: 16,
title: "Spiritual Gifts and Ministries",
category: "church",
summary: "God bestows upon all members of His church in every age spiritual gifts which each member is to employ in loving ministry for the common good of the church and of humanity.",
fullText: "God bestows upon all members of His church in every age spiritual gifts which each member is to employ in loving ministry for the common good of the church and of humanity. Given by the agency of the Holy Spirit, who apportions to each member as He wills, the gifts provide all abilities and ministries needed by the church to fulfill its divinely ordained functions. According to the Scriptures, these gifts include such ministries as faith, healing, prophecy, proclamation, teaching, administration, reconciliation, compassion, and self-sacrificing service and charity for the help and encouragement of people. Some members are called of God and endowed by the Spirit for functions recognized by the church in pastoral, evangelistic, apostolic, and teaching ministries particularly needed to equip the members for service, to build up the church to spiritual maturity, and to foster unity of the faith and knowledge of God. When members employ these spiritual gifts as faithful stewards of God's varied grace, the church is protected from the destructive influence of false doctrine, grows with a growth that is from God, and is built up in faith and love.",
references: "Rom. 12:4-8; 1 Cor. 12:9-11, 27, 28; Eph. 4:8, 11-16; Acts 6:1-7; 1 Tim. 3:1-13; 1 Peter 4:10, 11."
},
{
id: 17,
title: "The Gift of Prophecy",
category: "church",
summary: "One of the gifts of the Holy Spirit is prophecy. This gift is an identifying mark of the remnant church and was manifested in the ministry of Ellen G. White.",
fullText: "One of the gifts of the Holy Spirit is prophecy. This gift is an identifying mark of the remnant church and was manifested in the ministry of Ellen G. White. As the Lord's messenger, her writings are a continuing and authoritative source of truth which provide for the church comfort, guidance, instruction, and correction. They also make clear that the Bible is the standard by which all teaching and experience must be tested.",
references: "Joel 2:28, 29; Acts 2:14-21; Heb. 1:1-3; Rev. 12:17; 19:10."
},
{
id: 18,
title: "The Law of God",
category: "lifestyle",
summary: "The great principles of God's law are embodied in the Ten Commandments and exemplified in the life of Christ.",
fullText: "The great principles of God's law are embodied in the Ten Commandments and exemplified in the life of Christ. They express God's love, will, and purposes concerning human conduct and relationships and are binding upon all people in every age. These precepts are the basis of God's covenant with His people and the standard in God's judgment. Through the agency of the Holy Spirit they point out sin and awaken a sense of need for a Saviour. Salvation is all of grace and not of works, but its fruitage is obedience to the Commandments. This obedience develops Christian character and results in a sense of well-being. It is an evidence of our love for the Lord and our concern for our fellow men. The obedience of faith demonstrates the power of Christ to transform lives, and therefore strengthens Christian witness.",
references: "Ex. 20:1-17; Ps. 40:7, 8; Matt. 22:36-40; Deut. 28:1-14; Matt. 5:17-20; Heb. 8:8-10; John 15:7-10; Eph. 2:8-10; 1 John 5:3; Rom. 8:3, 4; Ps. 19:7-14."
},
{
id: 19,
title: "The Sabbath",
category: "lifestyle",
summary: "The beneficent Creator, after the six days of Creation, rested on the seventh day and instituted the Sabbath for all people as a memorial of Creation.",
fullText: "The beneficent Creator, after the six days of Creation, rested on the seventh day and instituted the Sabbath for all people as a memorial of Creation. The fourth commandment of God's unchangeable law requires the observance of this seventh-day Sabbath as the day of rest, worship, and ministry in harmony with the teaching and practice of Jesus, the Lord of the Sabbath. The Sabbath is a day of delightful communion with God and one another. It is a symbol of our redemption in Christ, a sign of our sanctification, a token of our allegiance, and a foretaste of our eternal future in God's kingdom. The Sabbath is God's perpetual sign of His eternal covenant between Him and His people. Joyful observance of this holy time from evening to evening, sunset to sunset, is a celebration of God's creative and redemptive acts.",
references: "Gen. 2:1-3; Ex. 20:8-11; Luke 4:16; Isa. 56:5, 6; 58:13, 14; Matt. 12:1-12; Ex. 31:13-17; Eze. 20:12, 20; Deut. 5:12-15; Heb. 4:1-11; Lev. 23:32; Mark 1:32."
},
{
id: 20,
title: "Stewardship",
category: "lifestyle",
summary: "We are God's stewards, entrusted by Him with time and opportunities, abilities and possessions, and the blessings of the earth and its resources.",
fullText: "We are God's stewards, entrusted by Him with time and opportunities, abilities and possessions, and the blessings of the earth and its resources. We are responsible to Him for their proper use. We acknowledge God's ownership by faithful service to Him and our fellow men, and by returning tithes and giving offerings for the proclamation of His gospel and the support and growth of His church. Stewardship is a privilege given to us by God for nurture in love and the victory over selfishness and covetousness. The steward rejoices in the blessings that come to others as a result of his faithfulness.",
references: "Gen. 1:26-28; 2:15; 1 Chron. 29:14; Haggai 1:3-11; Mal. 3:8-12; 1 Cor. 9:9-14; Matt. 23:23; 2 Cor. 8:1-15; Rom. 15:26, 27."
},
{
id: 21,
title: "Christian Behavior",
category: "lifestyle",
summary: "We are called to be a godly people who think, feel, and act in harmony with the principles of heaven.",
fullText: "We are called to be a godly people who think, feel, and act in harmony with the principles of heaven. For the Spirit to recreate in us the character of our Lord we involve ourselves only in those things which will produce Christlike purity, health, and joy in our lives. This means that our amusement and entertainment should meet the highest standards of Christian taste and beauty. While recognizing cultural differences, our dress is to be simple, modest, and neat, befitting those whose true beauty does not consist of outward adornment but in the imperishable ornament of a gentle and quiet spirit. It also means that because our bodies are the temples of the Holy Spirit, we are to care for them intelligently. Along with adequate exercise and rest, we are to adopt the most healthful diet possible and abstain from the unclean foods identified in the Scriptures. Since alcoholic beverages, tobacco, and the irresponsible use of drugs and narcotics are harmful to our bodies, we are to abstain from them as well. Instead, we are to engage in whatever brings our thoughts and bodies into the discipline of Christ, who desires our wholesomeness, joy, and goodness.",
references: "Rom. 12:1, 2; 1 John 2:6; Eph. 5:1-21; Phil. 4:8; 2 Cor. 10:5; 6:14-7:1; 1 Peter 3:1-4; 1 Cor. 6:19, 20; 10:31; Lev. 11:1-47; 3 John 2."
},
{
id: 22,
title: "Marriage and the Family",
category: "lifestyle",
summary: "Marriage was divinely established in Eden and affirmed by Jesus to be a lifelong union between a man and a woman in loving companionship.",
fullText: "Marriage was divinely established in Eden and affirmed by Jesus to be a lifelong union between a man and a woman in loving companionship. For the Christian a marriage commitment is to God as well as to the spouse, and should be entered into only between partners who share a common faith. Mutual love, honor, respect, and responsibility are the fabric of this relationship, which is to reflect the love, sanctity, closeness, and permanence of the relationship between Christ and His church. Regarding divorce, Jesus taught that the person who divorces a spouse, except for fornication, and marries another, commits adultery. Although some family relationships may fall short of the ideal, marriage partners who fully commit themselves to each other in Christ may achieve loving unity through the guidance of the Spirit and the nurture of the church. God blesses the family and intends that its members shall assist each other toward complete maturity. Parents are to bring up their children to love and obey the Lord. By their example and their words they are to teach them that Christ is a loving disciplinarian, ever tender and caring, who wants them to become members of His body, the family of God. Increasing family closeness is one of the earmarks of the final gospel message.",
references: "Gen. 2:18-25; Matt. 19:3-9; John 2:1-11; 2 Cor. 6:14; Eph. 5:21-33; Matt. 5:31, 32; Mark 10:11, 12; Luke 16:18; 1 Cor. 7:10, 11; Ex. 20:12; Eph. 6:1-4; Deut. 6:5-9; Prov. 22:6; Mal. 4:5, 6."
},
{
id: 23,
title: "Christ's Ministry in the Heavenly Sanctuary",
category: "eschatology",
summary: "There is a sanctuary in heaven, the true tabernacle which the Lord set up and not man. In it Christ ministers on our behalf.",
fullText: "There is a sanctuary in heaven, the true tabernacle which the Lord set up and not man. In it Christ ministers on our behalf, making available to believers the benefits of His atoning sacrifice offered once for all on the cross. He was inaugurated as our great High Priest and began His intercessory ministry at the time of His ascension. In 1844, at the end of the prophetic period of 2300 days, He entered the second and last phase of His atoning ministry. It is a work of investigative judgment which is part of the ultimate disposition of all sin, typified by the cleansing of the ancient Hebrew sanctuary on the Day of Atonement. In that typical service the sanctuary was cleansed with the blood of animal sacrifices, but the heavenly things are purified with the perfect sacrifice of the blood of Jesus. The investigative judgment reveals to heavenly intelligences who among the dead are asleep in Christ and therefore, in Him, are deemed worthy to have part in the first resurrection. It also makes manifest who among the living are abiding in Christ, keeping the commandments of God and the faith of Jesus, and in Him, therefore, are ready for translation into His everlasting kingdom. This judgment vindicates the justice of God in saving those who believe in Jesus. It declares that those who have remained loyal to God shall receive the kingdom. The completion of this ministry of Christ will mark the close of human probation before the Second Advent.",
references: "Heb. 8:1-5; 4:14-16; 9:11-28; 10:19-22; 1:3; 2:16, 17; Dan. 7:9-27; 8:13, 14; 9:24-27; Num. 14:34; Eze. 4:6; Lev. 16; Rev. 14:6, 7; 20:12; 14:12; 22:12."
},
{
id: 24,
title: "The Second Coming of Christ",
category: "eschatology",
summary: "The second coming of Christ is the blessed hope of the church, the grand climax of the gospel.",
fullText: "The second coming of Christ is the blessed hope of the church, the grand climax of the gospel. The Saviour's coming will be literal, personal, visible, and worldwide. When He returns, the righteous dead will be resurrected, and together with the righteous living will be glorified and taken to heaven, but the unrighteous will die. The almost complete fulfillment of most lines of prophecy, together with the present condition of the world, indicates that Christ's coming is imminent. The time of that event has not been revealed, and we are therefore exhorted to be ready at all times.",
references: "Titus 2:13; Heb. 9:28; John 14:1-3; Acts 1:9-11; Matt. 24:14; Rev. 1:7; Matt. 24:43, 44; 1 Thess. 4:13-18; 1 Cor. 15:51-54; 2 Thess. 1:7-10; 2:8; Rev. 14:14-20; 19:11-21; Matt. 24; Mark 13; Luke 21; 2 Tim. 3:1-5; 1 Thess. 5:1-6."
},
{
id: 25,
title: "Death and Resurrection",
category: "eschatology",
summary: "The wages of sin is death. But God, who alone is immortal, will grant eternal life to His redeemed.",
fullText: "The wages of sin is death. But God, who alone is immortal, will grant eternal life to His redeemed. Until that day death is an unconscious state for all people. When Christ, who is our life, appears, the resurrected righteous and the living righteous will be glorified and caught up to meet their Lord. The second resurrection, the resurrection of the unrighteous, will take place a thousand years later.",
references: "Rom. 6:23; 1 Tim. 6:15, 16; Eccl. 9:5, 6; Ps. 146:3, 4; John 11:11-14; Col. 3:4; 1 Cor. 15:51-54; 1 Thess. 4:13-17; John 5:28, 29; Rev. 20:1-10."
},
{
id: 26,
title: "The Millennium and the End of Sin",
category: "eschatology",
summary: "The millennium is the thousand-year reign of Christ with His saints in heaven between the first and second resurrections.",
fullText: "The millennium is the thousand-year reign of Christ with His saints in heaven between the first and second resurrections. During this time the wicked dead will be judged; the earth will be utterly desolate, without living human inhabitants, but occupied by Satan and his angels. At its close Christ with His saints and the Holy City will descend from heaven to earth. The unrighteous dead will then be resurrected, and with Satan and his angels will surround the city; but fire from God will consume them and cleanse the earth. The universe will thus be freed of sin and sinners forever.",
references: "Rev. 20; 1 Cor. 6:2, 3; Jer. 4:23-26; Rev. 21:1-5; Mal. 4:1; Eze. 28:18, 19."
},
{
id: 27,
title: "The New Earth",
category: "eschatology",
summary: "On the new earth, in which righteousness dwells, God will provide an eternal home for the redeemed and a perfect environment for everlasting life, love, joy, and learning in His presence.",
fullText: "On the new earth, in which righteousness dwells, God will provide an eternal home for the redeemed and a perfect environment for everlasting life, love, joy, and learning in His presence. For here God Himself will dwell with His people, and suffering and death will have passed away. The great controversy will be ended, and sin will be no more. All things, animate and inanimate, will declare that God is love; and He shall reign forever. Amen.",
references: "2 Peter 3:13; Isa. 35; 65:17-25; Matt. 5:5; Rev. 21:1-7; 22:1-5; 11:15."
}
]
return (
<>
<Helmet>
<title>Our Beliefs - Annaville Seventh-day Adventist Church</title>
<meta name="description" content="Explore the 27 Fundamental Beliefs of the Seventh-day Adventist Church. Learn about our foundational doctrines, salvation, church life, and end-time teachings." />
<meta name="keywords" content="Seventh-day Adventist beliefs, fundamental beliefs, SDA doctrine, Adventist theology, church beliefs" />
</Helmet>
{/* Hero Section */}
<section className="bg-gradient-to-br from-primary to-primaryDeep text-white py-20">
<div className="container">
<div className="max-w-4xl mx-auto text-center">
<h1 className="font-heading text-h1 mb-6">Our Beliefs</h1>
<p className="text-xl text-white/90 leading-relaxed">
The 27 Fundamental Beliefs of the Seventh-day Adventist Church form the foundation
of our faith and guide our understanding of God's Word and His plan for humanity.
</p>
</div>
</div>
</section>
{/* Beliefs Grid */}
<section className="section">
<div className="container">
<div className="max-w-6xl mx-auto">
<div className="grid gap-8">
{beliefs.map((belief) => (
<div key={belief.id} className="bg-white rounded-xl shadow-sm border border-subtle p-8">
<div className="flex items-start gap-6">
<div className="flex-shrink-0 w-12 h-12 bg-primary/10 rounded-lg flex items-center justify-center">
<span className="text-primary font-bold text-lg">{belief.id}</span>
</div>
<div className="flex-1">
<h3 className="font-heading text-h3 text-ink mb-4">{belief.title}</h3>
<p className="text-body text-muted leading-relaxed mb-6">
{belief.fullText}
</p>
<div className="bg-sand rounded-lg p-4">
<h4 className="font-semibold text-ink mb-2">Biblical References:</h4>
<p className="text-sm text-muted font-mono">{belief.references}</p>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</section>
</>
)
}

View File

@@ -4,15 +4,16 @@ import SEOHead from '../components/SEOHead'
import { useEvents } from '../hooks/useEvents'
function formatDateRange(dateStr, timeStr) {
const date = new Date(dateStr)
const date = new Date(dateStr + 'T00:00:00')
if (Number.isNaN(date)) {
return `${dateStr}${timeStr ? ` - ${timeStr}` : ''}`
}
return `${date.toLocaleDateString(undefined, {
return `${date.toLocaleDateString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
})}${timeStr ? ` - ${timeStr}` : ''}`
timeZone: 'America/Chicago',
})}${timeStr ? ` - ${timeStr} CT` : ''}`
}
export default function Calendar() {
@@ -22,7 +23,7 @@ export default function Calendar() {
const today = new Date()
today.setHours(0, 0, 0, 0)
return events.filter(event => {
const date = new Date(event.date)
const date = new Date(event.date + 'T00:00:00')
if (Number.isNaN(date)) return true
return date >= today
})
@@ -30,26 +31,28 @@ export default function Calendar() {
return (
<>
<SEOHead
<SEOHead
title="Calendar - Annaville Seventh-day Adventist Church"
description="Church events and calendar"
/>
<div className="max-w-4xl">
<h1 className="text-3xl font-bold text-blue-600 mb-6 flex items-center">
Church Events
<div className="ml-4 flex space-x-1">
<div className="w-2 h-2 bg-blue-400"></div>
<div className="w-2 h-2 bg-blue-400"></div>
<div className="w-2 h-2 bg-blue-400"></div>
<div className="w-2 h-2 bg-blue-400"></div>
<div className="w-2 h-2 bg-blue-400"></div>
<section className="bg-gradient-to-br from-primary/15 via-sand/20 to-white py-20">
<div className="container">
<div className="max-w-4xl mx-auto text-center space-y-6">
<p className="text-xs uppercase tracking-[0.3em] text-primary font-semibold">
Stay Connected
</p>
<h1 className="font-heading text-h1 text-ink">Church Events</h1>
<p className="text-xl text-muted leading-relaxed">
Join us for upcoming gatherings, worship services, and community activities.
</p>
</div>
</h1>
<h2 className="text-xl font-semibold text-red-600 mb-6">
Upcoming events:
</h2>
</div>
</section>
<section className="section">
<div className="container">
<div className="max-w-4xl mx-auto">
<div className="bg-gray-50 p-8 rounded-lg border border-gray-200">
{loading && (
@@ -78,7 +81,9 @@ export default function Calendar() {
</ul>
)}
</div>
</div>
</div>
</div>
</section>
</>
)
}

View File

@@ -1,457 +1,235 @@
import React, { useState } from 'react'
import { Helmet } from 'react-helmet-async'
import React from 'react'
import { Helmet } from 'react-helmet-async'
export default function Contact() {
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
department: '',
subject: '',
message: '',
preferredContact: 'email'
})
export default function Contact() {
return (
<>
<Helmet>
<title>Contact Us - Annaville Seventh-day Adventist Church</title>
<meta name="description" content="Contact Annaville SDA Church in Corpus Christi, TX. Get in touch with our pastoral team, ministries, or general inquiries. We're here to help!" />
<script type="application/ld+json">
{JSON.stringify({
"@context": "https://schema.org",
"@type": "Church",
"name": "Annaville Seventh-day Adventist Church",
"address": {
"@type": "PostalAddress",
"streetAddress": "2710 Violet Rd",
"addressLocality": "Corpus Christi",
"addressRegion": "TX",
"postalCode": "78410"
},
"telephone": "+1-361-241-5501",
"email": "info@annavillesda.org",
"url": "https://annavillesda.org",
"openingHours": "Sa 09:45-12:00",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+1-361-241-5501",
"contactType": "customer service",
"availableLanguage": "English"
}
})}
</script>
</Helmet>
const [isSubmitting, setIsSubmitting] = useState(false)
const [submitStatus, setSubmitStatus] = useState(null)
{/* Hero Section */}
<section className="relative bg-gradient-to-br from-primary/15 via-sand/20 to-white py-24">
<div className="container">
<div className="max-w-4xl mx-auto text-center space-y-6">
<p className="text-xs uppercase tracking-[0.3em] text-primary font-semibold">
We're Here to Help
</p>
<h1 className="font-heading text-h1 text-ink mb-8">Get in Touch</h1>
<p className="text-muted text-lg mb-10">
We'd love to hear from you! Whether you have questions, need prayer,
or want to get involved, we're here to help.
</p>
</div>
</div>
</section>
const departments = [
{ value: '', label: 'Select a Department' },
{ value: 'general', label: 'General Information', email: 'info@annavillesda.org' },
{ value: 'pastor', label: 'Pastoral Care', email: 'pastor@annavillesda.org' },
{ value: 'youth', label: 'Youth Ministry', email: 'youth@annavillesda.org' },
{ value: 'children', label: 'Children\'s Ministry', email: 'children@annavillesda.org' },
{ value: 'outreach', label: 'Community Outreach', email: 'outreach@annavillesda.org' },
{ value: 'treasurer', label: 'Financial/Treasurer', email: 'treasurer@annavillesda.org' },
{ value: 'events', label: 'Events & Activities', email: 'events@annavillesda.org' },
{ value: 'prayer', label: 'Prayer Requests', email: 'prayer@annavillesda.org' }
]
<section className="section">
<div className="container">
<div className="grid lg:grid-cols-2 gap-24">
const handleInputChange = (e) => {
const { name, value } = e.target
setFormData(prev => ({
...prev,
[name]: value
}))
}
{/* Contact Information */}
<div className="space-y-16">
<div>
<h2 className="font-heading text-h2 mb-10">Contact Information</h2>
<div className="space-y-10">
<div className="flex items-start gap-4 p-6 bg-white border border-subtle rounded-lg">
<div className="bg-primary/10 p-3 rounded-full">
<span className="text-primary text-2xl">📍</span>
</div>
<div>
<h3 className="font-heading text-h3 mb-2">Visit Us</h3>
<p className="text-body">
2710 Violet Rd<br />
Corpus Christi, TX 78410
</p>
<a
href="https://maps.google.com/?q=2710+Violet+Rd,+Corpus+Christi,+TX+78410"
className="text-primary hover:text-primaryHover transition-colors text-sm"
target="_blank"
rel="noreferrer"
>
Get Directions →
</a>
</div>
</div>
const handleSubmit = async (e) => {
e.preventDefault()
setIsSubmitting(true)
// Simulate form submission
setTimeout(() => {
setIsSubmitting(false)
setSubmitStatus('success')
setFormData({
name: '',
email: '',
phone: '',
department: '',
subject: '',
message: '',
preferredContact: 'email'
})
}, 2000)
}
<div className="flex items-start gap-4 p-6 bg-white border border-subtle rounded-lg">
<div className="bg-primary/10 p-3 rounded-full">
<span className="text-primary text-2xl">📞</span>
</div>
<div>
<h3 className="font-heading text-h3 mb-2">Call Us</h3>
<p className="text-body">
<a
href="tel:+13612415501"
className="text-primary hover:text-primaryHover transition-colors"
>
(361) 241-5501
</a>
</p>
<p className="text-muted text-sm">Available during office hours</p>
</div>
</div>
const selectedDepartment = departments.find(dept => dept.value === formData.department)
<div className="flex items-start gap-4 p-6 bg-white border border-subtle rounded-lg">
<div className="bg-primary/10 p-3 rounded-full">
<span className="text-primary text-2xl">✉️</span>
</div>
<div>
<h3 className="font-heading text-h3 mb-2">Email Us</h3>
<p className="text-body">
<a
href="mailto:info@annavillesda.org"
className="text-primary hover:text-primaryHover transition-colors"
>
info@annavillesda.org
</a>
</p>
<p className="text-muted text-sm">We'll respond within 24 hours</p>
</div>
</div>
</div>
</div>
return (
<>
<Helmet>
<title>Contact Us - Annaville Seventh-day Adventist Church</title>
<meta name="description" content="Contact Annaville SDA Church in Corpus Christi, TX. Get in touch with our pastoral team, ministries, or general inquiries. We're here to help!" />
<script type="application/ld+json">
{JSON.stringify({
"@context": "https://schema.org",
"@type": "Church",
"name": "Annaville Seventh-day Adventist Church",
"address": {
"@type": "PostalAddress",
"streetAddress": "2710 Violet Rd",
"addressLocality": "Corpus Christi",
"addressRegion": "TX",
"postalCode": "78410"
},
"telephone": "+1-361-241-5501",
"email": "info@annavillesda.org",
"url": "https://annavillesda.org",
"openingHours": "Sa 09:30-12:00",
"contactPoint": {
"@type": "ContactPoint",
"telephone": "+1-361-241-5501",
"contactType": "customer service",
"availableLanguage": "English"
}
})}
</script>
</Helmet>
{/* Service Times */}
<div className="p-6 bg-sand rounded-lg">
<h3 className="font-heading text-h3 mb-4">Service Times</h3>
<div className="space-y-2">
<div className="flex justify-between">
<span className="font-medium">Sabbath School:</span>
<span>9:45 AM CT</span>
</div>
<div className="flex justify-between">
<span className="font-medium">Divine Worship:</span>
<span>11:00 AM CT</span>
</div>
<div className="flex justify-between">
<span className="font-medium">Office Hours:</span>
<span>Mon-Fri 9AM-5PM CT</span>
</div>
</div>
</div>
{/* Hero Section */}
<section className="relative bg-gradient-to-br from-sand to-surface py-24">
<div className="container">
<div className="max-w-4xl mx-auto text-center">
<h1 className="font-heading text-h1 mb-8">Get in Touch</h1>
<p className="text-body text-lg mb-10">
We'd love to hear from you! Whether you have questions, need prayer,
or want to get involved, we're here to help.
{/* Map */}
<div>
<h3 className="font-heading text-h3 mb-4">Find Us</h3>
<iframe
title="Map to Annaville Seventh-day Adventist Church"
className="w-full h-64 rounded-lg border border-subtle"
loading="lazy"
src="https://www.google.com/maps?q=2710+Violet+Rd,+Corpus+Christi,+TX+78410&output=embed"
aria-label="Interactive map showing the location of Annaville Seventh-day Adventist Church"
/>
</div>
</div>
{/* Facebook Message */}
<div>
<h2 className="font-heading text-h2 mb-10">Send Us a Message</h2>
<div className="bg-white p-12 rounded-xl shadow-sm border border-subtle text-center space-y-8">
<div className="text-6xl mb-4">💬</div>
<h3 className="font-heading text-h3">Message Us on Facebook</h3>
<p className="text-body text-muted max-w-md mx-auto">
The best way to reach us is through Facebook Messenger.
Send us a message and we'll respond as soon as possible.
</p>
<a
href="https://www.facebook.com/p/Annaville-Seventh-Day-Adventist-Church-100064540276273/"
target="_blank"
rel="noreferrer"
className="btn inline-flex items-center gap-3 text-lg px-8 py-4"
>
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
</svg>
Send Message on Facebook
</a>
<p className="text-sm text-muted">
You'll need to be logged into Facebook to send us a message
</p>
</div>
</div>
</section>
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="grid lg:grid-cols-2 gap-24">
{/* Contact Information */}
<div className="space-y-16">
<div>
<h2 className="font-heading text-h2 mb-10">Contact Information</h2>
<div className="space-y-10">
<div className="flex items-start gap-4 p-6 bg-white border border-subtle rounded-lg">
<div className="bg-primary/10 p-3 rounded-full">
<span className="text-primary text-2xl">📍</span>
</div>
<div>
<h3 className="font-heading text-h3 mb-2">Visit Us</h3>
<p className="text-body">
2710 Violet Rd<br />
Corpus Christi, TX 78410
</p>
<a
href="https://maps.google.com/?q=2710+Violet+Rd,+Corpus+Christi,+TX+78410"
className="text-primary hover:text-primaryHover transition-colors text-sm"
target="_blank"
rel="noreferrer"
>
Get Directions
</a>
</div>
</div>
{/* Discussion Forum Section */}
<section className="section bg-sand">
<div className="container">
<div className="max-w-4xl mx-auto">
<h2 className="font-heading text-h2 mb-8">Discussion</h2>
<div className="flex items-start gap-4 p-6 bg-white border border-subtle rounded-lg">
<div className="bg-primary/10 p-3 rounded-full">
<span className="text-primary text-2xl">📞</span>
</div>
<div>
<h3 className="font-heading text-h3 mb-2">Call Us</h3>
<p className="text-body">
<a
href="tel:+13612415501"
className="text-primary hover:text-primaryHover transition-colors"
>
(361) 241-5501
</a>
</p>
<p className="text-muted text-sm">Available during office hours</p>
</div>
</div>
<div className="flex items-start gap-4 p-6 bg-white border border-subtle rounded-lg">
<div className="bg-primary/10 p-3 rounded-full">
<span className="text-primary text-2xl"></span>
</div>
<div>
<h3 className="font-heading text-h3 mb-2">Email Us</h3>
<p className="text-body">
<a
href="mailto:info@annavillesda.org"
className="text-primary hover:text-primaryHover transition-colors"
>
info@annavillesda.org
</a>
</p>
<p className="text-muted text-sm">We'll respond within 24 hours</p>
</div>
</div>
</div>
<div className="max-w-2xl mx-auto">
{/* Contents Section */}
<div className="bg-white p-8 rounded-lg border border-subtle">
<h3 className="font-heading text-h3 mb-6">CONTENTS</h3>
<div className="space-y-3">
<div className="text-sm">
<a href="#" className="text-primary hover:text-primaryHover font-medium">Jerry Stout</a>
<span className="text-muted ml-2">Elvin 27 Feb 2003</span>
</div>
{/* Service Times */}
<div className="p-6 bg-sand rounded-lg">
<h3 className="font-heading text-h3 mb-4">Service Times</h3>
<div className="space-y-2">
<div className="flex justify-between">
<span className="font-medium">Sabbath School:</span>
<span>9:30 AM</span>
</div>
<div className="flex justify-between">
<span className="font-medium">Divine Worship:</span>
<span>11:00 AM</span>
</div>
<div className="flex justify-between">
<span className="font-medium">Office Hours:</span>
<span>Mon-Fri 9AM-5PM</span>
</div>
</div>
<div className="text-sm ml-4">
<a href="#" className="text-primary hover:text-primaryHover">Re: Jerry Stout</a>
<span className="text-muted ml-2">Elvin 27 Feb 2003</span>
</div>
{/* Map */}
<div>
<h3 className="font-heading text-h3 mb-4">Find Us</h3>
<iframe
title="Map to Annaville Seventh-day Adventist Church"
className="w-full h-64 rounded-lg border border-subtle"
loading="lazy"
src="https://www.google.com/maps?q=2710+Violet+Rd,+Corpus+Christi,+TX+78410&output=embed"
aria-label="Interactive map showing the location of Annaville Seventh-day Adventist Church"
/>
<div className="text-sm">
<a href="#" className="text-primary hover:text-primaryHover font-medium">Shelly Family</a>
<span className="text-muted ml-2">Elvin/Wallie 27 Feb 2003</span>
</div>
</div>
{/* Contact Form */}
<div>
<h2 className="font-heading text-h2 mb-10">Send Us a Message</h2>
{submitStatus === 'success' && (
<div className="p-8 bg-green-50 border border-green-200 rounded-lg mb-10">
<p className="text-green-800">
Thank you! Your message has been sent successfully. We'll get back to you soon.
</p>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-10">
<div className="grid md:grid-cols-2 gap-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-ink mb-3">
Full Name *
</label>
<input
id="name"
name="name"
type="text"
required
value={formData.name}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-5 py-4 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="Your full name"
aria-describedby="name-error"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-ink mb-3">
Email Address *
</label>
<input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-5 py-4 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="your.email@example.com"
aria-describedby="email-error"
/>
</div>
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-ink mb-3">
Phone Number
</label>
<input
id="phone"
name="phone"
type="tel"
value={formData.phone}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-5 py-4 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="(361) 555-0123"
/>
</div>
<div>
<label htmlFor="department" className="block text-sm font-medium text-ink mb-3">
Department *
</label>
<select
id="department"
name="department"
required
value={formData.department}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-5 py-4 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
>
{departments.map(dept => (
<option key={dept.value} value={dept.value}>
{dept.label}
</option>
))}
</select>
{selectedDepartment?.email && (
<p className="text-sm text-muted mt-2">
This will be sent to: {selectedDepartment.email}
</p>
)}
</div>
<div>
<label htmlFor="subject" className="block text-sm font-medium text-ink mb-3">
Subject *
</label>
<input
id="subject"
name="subject"
type="text"
required
value={formData.subject}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-5 py-4 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="Brief subject of your message"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium text-ink mb-3">
Message *
</label>
<textarea
id="message"
name="message"
required
rows={6}
value={formData.message}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-5 py-4 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors resize-vertical"
placeholder="Please share your message, question, or prayer request..."
aria-describedby="message-help"
/>
<p id="message-help" className="text-sm text-muted mt-2">
For prayer requests, please indicate if you'd like us to keep it confidential.
</p>
</div>
<div>
<fieldset>
<legend className="block text-sm font-medium text-ink mb-3">
Preferred Contact Method
</legend>
<div className="space-y-3">
<label className="flex items-center">
<input
type="radio"
name="preferredContact"
value="email"
checked={formData.preferredContact === 'email'}
onChange={handleInputChange}
className="mr-3"
/>
Email
</label>
<label className="flex items-center">
<input
type="radio"
name="preferredContact"
value="phone"
checked={formData.preferredContact === 'phone'}
onChange={handleInputChange}
className="mr-3"
/>
Phone
</label>
</div>
</fieldset>
</div>
<button
type="submit"
disabled={isSubmitting}
className="btn w-full py-5 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? 'Sending...' : 'Send Message'}
</button>
</form>
</div>
</div>
</div>
</section>
{/* Discussion Forum Section */}
<section className="section bg-sand">
<div className="container">
<div className="max-w-4xl mx-auto">
<h2 className="font-heading text-h2 mb-8">Discussion</h2>
<div className="grid lg:grid-cols-2 gap-12">
{/* Search Section */}
<div className="bg-white p-8 rounded-lg border border-subtle">
<h3 className="font-heading text-h3 mb-4">SEARCH FOR ARTICLE</h3>
<p className="text-body mb-6">
Find articles posted to this discussion containing matching words or patterns.
</p>
<form className="space-y-4">
<div>
<label htmlFor="search-term" className="block text-sm font-medium text-ink mb-2">
Search for:
</label>
<input
id="search-term"
type="text"
className="w-full border border-subtle rounded-lg px-4 py-3 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
/>
</div>
<div className="flex gap-4">
<button type="submit" className="btn">
Start Search
</button>
<button type="reset" className="btn-outline">
Reset
</button>
</div>
</form>
<p className="text-sm text-muted mt-6">
Last changed: October 28, 2012
</p>
<div className="text-sm">
<a href="#" className="text-primary hover:text-primaryHover font-medium">Bill & Pat</a>
<span className="text-muted ml-2">Scarboroughs 26 Feb 2003</span>
</div>
{/* Contents Section */}
<div className="bg-white p-8 rounded-lg border border-subtle">
<h3 className="font-heading text-h3 mb-6">CONTENTS</h3>
<div className="space-y-3">
<div className="text-sm">
<a href="#" className="text-primary hover:text-primaryHover font-medium">Jerry Stout</a>
<span className="text-muted ml-2">Elvin 27 Feb 2003</span>
</div>
<div className="text-sm ml-4">
<a href="#" className="text-primary hover:text-primaryHover">Re: Jerry Stout</a>
<span className="text-muted ml-2">Elvin 27 Feb 2003</span>
</div>
<div className="text-sm">
<a href="#" className="text-primary hover:text-primaryHover font-medium">Shelly Family</a>
<span className="text-muted ml-2">Elvin/Wallie 27 Feb 2003</span>
</div>
<div className="text-sm">
<a href="#" className="text-primary hover:text-primaryHover font-medium">Bill & Pat</a>
<span className="text-muted ml-2">Scarboroughs 26 Feb 2003</span>
</div>
<div className="text-sm ml-4">
<a href="#" className="text-primary hover:text-primaryHover">Bill & Pat</a>
<span className="text-muted ml-2">Elvin 27 Feb 2003</span>
</div>
<div className="text-sm">
<a href="#" className="text-primary hover:text-primaryHover font-medium">Potluck</a>
<span className="text-muted ml-2">Wallie 24 Feb 2003</span>
</div>
<div className="text-sm ml-4">
<a href="#" className="text-primary hover:text-primaryHover">Re: Potluck</a>
<span className="text-muted ml-2">bob 25 Feb 2003</span>
</div>
<div className="text-sm ml-4">
<a href="#" className="text-primary hover:text-primaryHover">Re: Potluck</a>
<span className="text-muted ml-2">bob 25 Feb 2003</span>
</div>
</div>
<div className="text-sm ml-4">
<a href="#" className="text-primary hover:text-primaryHover">Bill & Pat</a>
<span className="text-muted ml-2">Elvin 27 Feb 2003</span>
</div>
<div className="text-sm">
<a href="#" className="text-primary hover:text-primaryHover font-medium">Potluck</a>
<span className="text-muted ml-2">Wallie 24 Feb 2003</span>
</div>
<div className="text-sm ml-4">
<a href="#" className="text-primary hover:text-primaryHover">Re: Potluck</a>
<span className="text-muted ml-2">bob 25 Feb 2003</span>
</div>
<div className="text-sm ml-4">
<a href="#" className="text-primary hover:text-primaryHover">Re: Potluck</a>
<span className="text-muted ml-2">bob 25 Feb 2003</span>
</div>
</div>
</div>
</div>
</section>
</>
)
}
</div>
</div>
</section>
</>
)
}

View File

@@ -5,7 +5,7 @@ import { getEvent } from '../utils/api'
import { googleCalendarUrl, downloadICS } from '../utils/calendar'
import { track, events as ga } from '../utils/analytics'
const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:4001').replace(/\/$/, '')
const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || '').replace(/\/$/, '')
function parseTime(timeStr) {
if (!timeStr) return '10:00'
@@ -33,27 +33,28 @@ function resolveImageUrl(value, fallback) {
if (apiBaseUrl) {
return `${apiBaseUrl}${path}`
}
// Fallback to current domain if no API base URL is set
if (typeof window !== 'undefined') {
return `${window.location.origin}${path}`
}
return path
}
return fallback
}
function getFallbackImage(event) {
const title = `${event?.title || ''}`.toLowerCase()
if (title.includes('vespers')) return '/assets/youth_vespers.png'
if (title.includes('food') || title.includes('community')) return '/assets/family_entry.png'
if (title.includes('lunch') || title.includes('dinner') || title.includes('potluck')) return '/assets/potluck.png'
return '/assets/potluck.png'
return ''
}
function formatDate(dateStr) {
const date = new Date(dateStr)
const date = new Date(dateStr + 'T00:00:00')
if (Number.isNaN(date.getTime())) return dateStr
return date.toLocaleDateString(undefined, {
return date.toLocaleDateString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric',
timeZone: 'America/Chicago',
})
}
@@ -167,14 +168,16 @@ export default function EventDetail() {
</Link>
<div className="grid lg:grid-cols-[2fr,1fr] gap-10 items-start">
<article className="bg-white rounded-2xl shadow-level1 overflow-hidden">
<div className="relative h-80 md:h-96 bg-sand">
<img
src={coverImage}
alt={`${event.title} hero image`}
className="absolute inset-0 h-full w-full object-cover"
loading="lazy"
/>
</div>
{coverImage && (
<div className="relative h-80 md:h-96 bg-sand">
<img
src={coverImage}
alt={`${event.title} hero image`}
className="absolute inset-0 h-full w-full object-cover"
loading="lazy"
/>
</div>
)}
<div className="p-10 space-y-6">
<header className="space-y-3">
<p className="text-xs uppercase tracking-[0.2em] text-muted">
@@ -182,7 +185,7 @@ export default function EventDetail() {
</p>
<h1 className="font-heading text-display30 text-ink">{event.title}</h1>
<p className="text-muted text-sm">
{displayDate} {displayTime}{event.location ? `${event.location}` : ''}
{displayDate} {displayTime} CT{event.location ? `${event.location}` : ''}
</p>
</header>
<div className="space-y-4 text-body leading-relaxed">
@@ -229,7 +232,7 @@ export default function EventDetail() {
<dl className="space-y-4 text-sm">
<div>
<dt className="font-semibold text-ink">When</dt>
<dd className="text-muted">{displayDate} · {displayTime}</dd>
<dd className="text-muted">{displayDate} · {displayTime} CT</dd>
</div>
{event.location && (
<div>

View File

@@ -4,10 +4,10 @@ import { Link } from 'react-router-dom'
import { EventCard } from '../components/Cards'
import { useEvents } from '../hooks/useEvents'
const filters = ['All', 'This Month', 'Next Month', 'Family', 'Outreach']
const filters = ['All', 'This Month', 'Next Month']
function isSameMonth(dateStr, baseDate) {
const date = new Date(dateStr)
const date = new Date(dateStr + 'T00:00:00')
if (Number.isNaN(date)) return false
return (
date.getFullYear() === baseDate.getFullYear() &&
@@ -35,18 +35,19 @@ function filterEvents(events, activeFilter) {
}
function getValidDate(event) {
const date = new Date(event.date)
const date = new Date(event.date + 'T00:00:00')
return Number.isNaN(date.getTime()) ? null : date
}
function formatEventDate(dateStr) {
const date = new Date(dateStr)
const date = new Date(dateStr + 'T00:00:00')
if (Number.isNaN(date.getTime())) return dateStr || 'Date TBA'
return date.toLocaleDateString(undefined, {
return date.toLocaleDateString('en-US', {
weekday: 'long',
month: 'long',
day: 'numeric',
year: 'numeric',
timeZone: 'America/Chicago',
})
}
@@ -63,7 +64,7 @@ export default function Events() {
const today = new Date()
today.setHours(0, 0, 0, 0)
return events.filter(event => {
const date = new Date(event.date)
const date = new Date(event.date + 'T00:00:00')
if (Number.isNaN(date.getTime())) return true
return date >= today
})
@@ -89,8 +90,6 @@ export default function Events() {
)
const totalUpcoming = futureEvents.length
const familyCount = futureEvents.filter(event => (event.category || '').toLowerCase().includes('family')).length
const outreachCount = futureEvents.filter(event => (event.category || '').toLowerCase().includes('outreach')).length
const nextEventDate = nextEvent ? formatEventDate(nextEvent.date) : null
const nextEventTime = nextEvent ? formatEventTime(nextEvent.time) : null
@@ -112,25 +111,13 @@ export default function Events() {
There is always something happening at Annaville SDA Church. Explore ways to worship, volunteer,
and connect with families throughout the Corpus Christi community.
</p>
<div className="grid gap-6 sm:grid-cols-3">
<div className="max-w-xs">
<div>
<p className="font-heading text-display20 text-primary">
{totalUpcoming}
</p>
<p className="text-muted text-sm">Upcoming gatherings</p>
</div>
<div>
<p className="font-heading text-display20 text-primary">
{familyCount}
</p>
<p className="text-muted text-sm">Family-focused events</p>
</div>
<div>
<p className="font-heading text-display20 text-primary">
{outreachCount}
</p>
<p className="text-muted text-sm">Community outreach</p>
</div>
</div>
</div>
@@ -141,7 +128,7 @@ export default function Events() {
<h2 className="font-heading text-h3 text-ink">{nextEvent.title}</h2>
<div className="space-y-1 text-sm text-muted">
<div>{nextEventDate}</div>
<div>{nextEventTime}{nextEvent.location ? ` - ${nextEvent.location}` : ''}</div>
<div>{nextEventTime} CT{nextEvent.location ? ` - ${nextEvent.location}` : ''}</div>
</div>
<p className="text-body text-sm text-muted line-clamp-3">
{nextEvent.description || 'We would love for you to join us. Everyone is welcome!'}

View File

@@ -2,19 +2,18 @@
import { Helmet } from 'react-helmet-async'
import { Link } from 'react-router-dom'
import { TextHero } from '../components/Hero'
import VisitForm from '../components/VisitForm'
import StaticMap from '../components/StaticMap'
import { useEvents } from '../hooks/useEvents'
function formatEventDate(dateStr, timeStr) {
const date = new Date(dateStr)
const date = new Date(dateStr + 'T00:00:00')
if (Number.isNaN(date)) {
return dateStr
}
const formattedDate = date.toLocaleDateString(undefined, {
const formattedDate = date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
timeZone: 'America/Chicago',
})
return timeStr ? `${formattedDate} - ${timeStr}` : formattedDate
}
@@ -27,7 +26,7 @@ export default function Home() {
today.setHours(0, 0, 0, 0)
return events
.filter(event => {
const date = new Date(event.date)
const date = new Date(event.date + 'T00:00:00')
if (Number.isNaN(date)) return true
return date >= today
})
@@ -38,16 +37,16 @@ export default function Home() {
<>
<Helmet>
<title>Annaville Seventh-day Adventist Church - Corpus Christi, TX</title>
<meta name="description" content="Join us for worship services at Annaville SDA Church. Sabbath School 9:30 AM, Divine Worship 11:00 AM. Located at 2710 Violet Rd, Corpus Christi, TX. Welcoming community, family-friendly ministries, and spiritual growth opportunities." />
<meta name="description" content="Join us for worship services at Annaville SDA Church. Sabbath School 9:45 AM CT, Divine Worship 11:00 AM CT. Located at 2710 Violet Rd, Corpus Christi, TX. Welcoming community, family-friendly ministries, and spiritual growth opportunities." />
<meta name="keywords" content="Seventh-day Adventist, church, Corpus Christi, Texas, worship, Sabbath School, Bible study, community" />
<meta property="og:title" content="Annaville Seventh-day Adventist Church" />
<meta property="og:description" content="Join us for worship services at Annaville SDA Church. Sabbath School 9:30 AM, Divine Worship 11:00 AM." />
<meta property="og:description" content="Join us for worship services at Annaville SDA Church. Sabbath School 9:45 AM CT, Divine Worship 11:00 AM CT." />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://annavillesda.org" />
<meta property="og:image" content="https://annavillesda.org/assets/hero_golden_hour.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Annaville Seventh-day Adventist Church" />
<meta name="twitter:description" content="Join us for worship services at Annaville SDA Church. Sabbath School 9:30 AM, Divine Worship 11:00 AM." />
<meta name="twitter:description" content="Join us for worship services at Annaville SDA Church. Sabbath School 9:45 AM CT, Divine Worship 11:00 AM CT." />
<script type="application/ld+json">
{JSON.stringify({
"@context": "https://schema.org",
@@ -117,26 +116,26 @@ export default function Home() {
<section id="service-times" aria-labelledby="stimes" className="section">
<div className="container">
<h2 id="stimes" className="sr-only">Service Times</h2>
{/* Service Times Cards */}
<div className="grid md:grid-cols-2 gap-8 mb-16">
<div className="bg-white p-8 rounded-xl shadow-sm border border-subtle">
<div className="text-center">
<h3 className="font-heading text-h3 text-primary mb-4">Sabbath School</h3>
<div className="text-4xl font-bold text-ink mb-2">9:30 AM</div>
<div className="text-4xl font-bold text-ink mb-2">9:45 AM CT</div>
<p className="text-body text-muted">Bible Study & Discussion</p>
</div>
</div>
<div className="bg-white p-8 rounded-xl shadow-sm border border-subtle">
<div className="text-center">
<h3 className="font-heading text-h3 text-primary mb-4">Divine Worship</h3>
<div className="text-4xl font-bold text-ink mb-2">11:00 AM</div>
<div className="text-4xl font-bold text-ink mb-2">11:00 AM CT</div>
<p className="text-body text-muted">Main Worship Service</p>
</div>
</div>
</div>
{/* Potluck Fellowship Information */}
<div className="max-w-2xl mx-auto p-8 bg-sand rounded-xl text-center">
<h3 className="font-heading text-h3 mb-4 text-primary">Potluck Fellowship Dinner</h3>
@@ -147,7 +146,7 @@ export default function Home() {
</div>
</section>
<section className="section bg-sand">
<section id="upcoming-events" className="section bg-sand">
<div className="container">
<div className="text-center">
<h2 className="font-heading text-h2 mb-8">Upcoming Events</h2>
@@ -186,25 +185,6 @@ export default function Home() {
</div>
</div>
</section>
<section className="section">
<div className="container">
<div className="grid lg:grid-cols-2 gap-24 items-start">
<div>
<h2 className="font-heading text-h2 mb-12">Plan Your Visit</h2>
<p className="text-body mb-12">
We are located at 2710 Violet Road in Corpus Christi, Texas.
Click on the link below to see a map to our church location.
</p>
<VisitForm />
</div>
<div>
<h2 className="font-heading text-h2 mb-12">Location</h2>
<StaticMap />
</div>
</div>
</div>
</section>
</>
)
}

View File

@@ -1,257 +0,0 @@
import React, { useState } from 'react'
import { Helmet } from 'react-helmet-async'
export default function PrayerRequests() {
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
prayerType: '',
request: '',
isConfidential: false,
allowSharing: false
})
const [isSubmitted, setIsSubmitted] = useState(false)
const handleInputChange = (e) => {
const { name, value, type, checked } = e.target
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}))
}
const handleSubmit = (e) => {
e.preventDefault()
// Here you would typically send the data to your backend
console.log('Prayer request submitted:', formData)
setIsSubmitted(true)
setFormData({
name: '',
email: '',
phone: '',
prayerType: '',
request: '',
isConfidential: false,
allowSharing: false
})
}
if (isSubmitted) {
return (
<>
<Helmet>
<title>Prayer Request Submitted - Annaville Seventh-day Adventist Church</title>
</Helmet>
<section className="bg-gradient-to-br from-green-500 to-green-600 text-white py-20">
<div className="container">
<div className="max-w-4xl mx-auto text-center">
<div className="text-6xl mb-6">🙏</div>
<h1 className="font-heading text-h1 mb-6">Prayer Request Received</h1>
<p className="text-xl text-white/90 leading-relaxed mb-8">
Thank you for sharing your prayer request with us. Our prayer team has been notified
and will be lifting you up in prayer.
</p>
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-8 mb-8">
<h3 className="font-heading text-h3 mb-4">What happens next?</h3>
<ul className="text-left space-y-3 text-white/90">
<li className="flex items-center gap-3">
<span className="text-2xl">📝</span>
<span>Your request has been recorded in our prayer journal</span>
</li>
<li className="flex items-center gap-3">
<span className="text-2xl">🙏</span>
<span>Our prayer team will begin praying for you immediately</span>
</li>
<li className="flex items-center gap-3">
<span className="text-2xl">💌</span>
<span>You'll receive a confirmation email with additional resources</span>
</li>
<li className="flex items-center gap-3">
<span className="text-2xl">📞</span>
<span>If you requested follow-up, someone will contact you soon</span>
</li>
</ul>
</div>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<button
onClick={() => setIsSubmitted(false)}
className="btn bg-white text-green-600 hover:bg-gray-100"
>
Submit Another Request
</button>
<a href="/" className="btn-outline border-white text-white hover:bg-white hover:text-green-600">
Return Home
</a>
</div>
</div>
</div>
</section>
</>
)
}
return (
<>
<Helmet>
<title>Prayer Requests - Annaville Seventh-day Adventist Church</title>
<meta name="description" content="Submit your prayer requests to our caring community. We believe in the power of prayer and are here to support you in your time of need." />
<meta name="keywords" content="prayer requests, prayer support, spiritual care, Annaville SDA, prayer ministry" />
</Helmet>
{/* Hero Section */}
<section className="bg-gradient-to-br from-primary to-primaryDeep text-white py-20">
<div className="container">
<div className="max-w-4xl mx-auto text-center">
<div className="text-6xl mb-6">🙏</div>
<h1 className="font-heading text-h1 mb-6">Prayer Requests</h1>
<p className="text-xl text-white/90 leading-relaxed">
We believe in the power of prayer and the strength of community.
Share your prayer requests with us, and know that you are not alone.
</p>
</div>
</div>
</section>
{/* Prayer Request Form Section */}
<section className="section bg-sand">
<div className="container">
<div className="max-w-4xl mx-auto">
<div className="bg-white rounded-xl shadow-lg p-8 md:p-12">
<div className="text-center mb-12">
<h2 className="font-heading text-h2 mb-6">Share Your Prayer Request</h2>
<p className="text-body text-muted">
Your request will be shared with our prayer team. All information is kept confidential
unless you specifically allow us to share it with the congregation.
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-8">
<div className="grid md:grid-cols-2 gap-6">
<div>
<label htmlFor="name" className="block text-sm font-medium text-ink mb-3">
Your Name *
</label>
<input
id="name"
name="name"
type="text"
required
value={formData.name}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-4 py-3 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="Enter your full name"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-ink mb-3">
Email Address *
</label>
<input
id="email"
name="email"
type="email"
required
value={formData.email}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-4 py-3 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="your.email@example.com"
/>
</div>
</div>
<div>
<label htmlFor="phone" className="block text-sm font-medium text-ink mb-3">
Phone Number (Optional)
</label>
<input
id="phone"
name="phone"
type="tel"
value={formData.phone}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-4 py-3 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="(555) 123-4567"
/>
</div>
<div>
<label htmlFor="request" className="block text-sm font-medium text-ink mb-3">
Your Prayer Request *
</label>
<textarea
id="request"
name="request"
rows="6"
required
value={formData.request}
onChange={handleInputChange}
className="w-full border border-subtle rounded-lg px-4 py-3 focus:ring-2 focus:ring-primary focus:border-transparent transition-colors"
placeholder="Please share your prayer request in detail. What would you like us to pray for?"
></textarea>
</div>
<div className="space-y-4">
<div className="flex items-start gap-3">
<input
id="isConfidential"
name="isConfidential"
type="checkbox"
checked={formData.isConfidential}
onChange={handleInputChange}
className="mt-1 h-4 w-4 text-primary focus:ring-primary border-subtle rounded"
/>
<label htmlFor="isConfidential" className="text-sm text-muted">
Keep this request confidential (only shared with prayer team)
</label>
</div>
<div className="flex items-start gap-3">
<input
id="allowSharing"
name="allowSharing"
type="checkbox"
checked={formData.allowSharing}
onChange={handleInputChange}
className="mt-1 h-4 w-4 text-primary focus:ring-primary border-subtle rounded"
/>
<label htmlFor="allowSharing" className="text-sm text-muted">
Allow us to share this request with the congregation (without personal details)
</label>
</div>
</div>
<div className="flex flex-col sm:flex-row gap-4 pt-6">
<button
type="submit"
className="btn flex-1"
>
Submit Prayer Request
</button>
<button
type="reset"
onClick={() => setFormData({
name: '',
email: '',
phone: '',
prayerType: '',
request: '',
isConfidential: false,
allowSharing: false
})}
className="btn-outline flex-1"
>
Reset Form
</button>
</div>
</form>
</div>
</div>
</div>
</section>
</>
)
}

View File

@@ -1,13 +1,126 @@
import React from 'react'
import React, { useEffect } from 'react'
import { Helmet } from 'react-helmet-async'
export default function PrivacyPolicy() {
useEffect(() => {
window.scrollTo(0, 0)
}, [])
return (
<section className="section">
<Helmet><title>Privacy Policy | Annaville SDA Church</title></Helmet>
<div className="container">
<h1 className="font-heading text-display30 mb-4">Privacy Policy</h1>
<p className='text-[16px]'>Privacy policy content placeholder.</p>
<div className="container max-w-4xl">
<h1 className="font-heading text-display30 mb-8">Privacy Policy</h1>
<div className="prose prose-lg max-w-none">
<p className="text-muted mb-6">
<em>Last updated: {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}</em>
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Introduction</h2>
<p className="text-body mb-4">
Annaville Seventh-day Adventist Church ("we," "our," or "us") is committed to protecting your privacy.
This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit
our website or interact with our services.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Information We Collect</h2>
<h3 className="font-heading text-h3 mt-6 mb-3">Personal Information</h3>
<p className="text-body mb-4">
We may collect personal information that you voluntarily provide to us when you:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Fill out contact forms</li>
<li>Register for events or programs</li>
<li>Subscribe to our newsletter</li>
<li>Make online donations</li>
<li>Submit prayer requests</li>
</ul>
<p className="text-body mb-4">
This information may include your name, email address, phone number, mailing address, and any other
information you choose to provide.
</p>
<h3 className="font-heading text-h3 mt-6 mb-3">Automatically Collected Information</h3>
<p className="text-body mb-4">
When you visit our website, we may automatically collect certain information about your device, including
information about your web browser, IP address, time zone, and some of the cookies that are installed on
your device. This information is used to improve our website and user experience.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">How We Use Your Information</h2>
<p className="text-body mb-4">
We use the information we collect to:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Respond to your inquiries and provide customer service</li>
<li>Send you information about church events, programs, and activities</li>
<li>Process your donations and send you receipts</li>
<li>Improve our website and services</li>
<li>Communicate with you about prayer requests and spiritual support</li>
<li>Comply with legal obligations</li>
</ul>
<h2 className="font-heading text-h2 mt-8 mb-4">Information Sharing</h2>
<p className="text-body mb-4">
We do not sell, trade, or rent your personal information to third parties. We may share your information only in the following circumstances:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>With your consent</li>
<li>With service providers who assist us in operating our website or conducting our church activities</li>
<li>To comply with legal obligations or respond to lawful requests</li>
<li>To protect the rights, property, or safety of our church, our members, or others</li>
</ul>
<h2 className="font-heading text-h2 mt-8 mb-4">Data Security</h2>
<p className="text-body mb-4">
We implement appropriate technical and organizational security measures to protect your personal information.
However, please note that no method of transmission over the Internet or electronic storage is 100% secure.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Your Rights</h2>
<p className="text-body mb-4">
You have the right to:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>Access the personal information we hold about you</li>
<li>Request correction of inaccurate information</li>
<li>Request deletion of your personal information</li>
<li>Opt-out of receiving communications from us</li>
<li>Withdraw consent where we rely on consent to process your information</li>
</ul>
<h2 className="font-heading text-h2 mt-8 mb-4">Children's Privacy</h2>
<p className="text-body mb-4">
Our website is not directed to children under the age of 13. We do not knowingly collect personal
information from children under 13. If you are a parent or guardian and believe we have collected
information from your child, please contact us.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Cookies</h2>
<p className="text-body mb-4">
We use cookies and similar tracking technologies to track activity on our website and hold certain information.
You can instruct your browser to refuse all cookies or to indicate when a cookie is being sent.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Changes to This Policy</h2>
<p className="text-body mb-4">
We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new
Privacy Policy on this page and updating the "Last updated" date.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Contact Us</h2>
<p className="text-body mb-4">
If you have questions about this Privacy Policy or our privacy practices, please contact us:
</p>
<div className="bg-sand p-6 rounded-lg mb-8">
<p className="mb-2"><strong>Annaville Seventh-day Adventist Church</strong></p>
<p className="mb-2">2710 Violet Rd, Corpus Christi, TX 78410</p>
<p className="mb-2">Phone: <a href="tel:+13612415501" className="text-primary hover:underline">(361) 241-5501</a></p>
<p>Email: <a href="mailto:info@annavillesda.org" className="text-primary hover:underline">info@annavillesda.org</a></p>
</div>
</div>
</div>
</section>
)

View File

@@ -9,24 +9,12 @@ export default function Resources() {
url: "https://www.adventist.org",
icon: "🌐"
},
{
title: "General Conference",
description: "Official website of the Seventh-day Adventist Church",
url: "https://www.adventist.org",
icon: "⛪"
},
{
title: "SDA Yearbook",
description: "Comprehensive directory of Adventist institutions",
url: "https://www.adventistyearbook.org",
icon: "📚"
},
{
title: "Adventist Education Net",
description: "Educational resources and institutions",
url: "https://education.adventist.org",
icon: "🎓"
},
{
title: "Adventist Plusline",
description: "Professional development and networking",
@@ -39,57 +27,21 @@ export default function Resources() {
url: "https://whiteestate.org",
icon: "✍️"
},
{
title: "TAGnet",
description: "Technology and communication resources",
url: "https://tagnet.org",
icon: "💻"
},
{
title: "Adventist Educational Institutions",
description: "Schools, colleges, and universities worldwide",
url: "https://education.adventist.org",
icon: "🏫"
},
{
title: "Fundamental Beliefs",
description: "28 Fundamental Beliefs of Seventh-day Adventists",
url: "https://www.adventist.org/beliefs/",
icon: "📖"
},
{
title: "SDA Institutions Directory",
description: "Global directory of Adventist organizations",
url: "https://www.adventistdirectory.org",
icon: "🗺️"
},
{
title: "Churches Directory",
description: "Find Adventist churches near you",
url: "https://www.adventistdirectory.org",
icon: "🏛️"
},
{
title: "Open Directory Index",
description: "Comprehensive Adventist resource index",
url: "https://dmoz-odp.org/Society/Religion_and_Spirituality/Christianity/Denominations/Seventh-day_Adventist/",
icon: "📋"
},
{
title: "Yahoo Adventist Index",
description: "Curated Adventist resources and links",
url: "https://dir.yahoo.com/Society_and_Culture/Religion_and_Spirituality/Christianity/Denominations/Seventh_day_Adventist/",
icon: "🔍"
}
]
const bibleStudyResources = [
{
title: "Discover Bible Guides",
description: "Interactive online Bible study courses",
url: "https://www.discoveronline.org",
icon: "🔍"
},
{
title: "Bible Information Online",
description: "Comprehensive Bible study resources",
@@ -119,12 +71,6 @@ export default function Resources() {
description: "Evangelistic resources and materials",
url: "https://www.aims.org",
icon: "📢"
},
{
title: "Daniel Prophecy Series",
description: "In-depth study of biblical prophecy",
url: "https://www.amazingfacts.org/media-library/prophecy",
icon: "🔮"
}
]
@@ -137,12 +83,15 @@ export default function Resources() {
</Helmet>
{/* Hero Section */}
<section className="bg-gradient-to-br from-primary to-primaryDeep text-white py-20">
<section className="bg-gradient-to-br from-primary/15 via-sand/20 to-white py-20">
<div className="container">
<div className="max-w-4xl mx-auto text-center">
<h1 className="font-heading text-h1 mb-6">Adventist Resources</h1>
<p className="text-xl text-white/90 leading-relaxed">
Discover trusted resources, Bible study materials, and educational content
<div className="max-w-4xl mx-auto text-center space-y-6">
<p className="text-xs uppercase tracking-[0.3em] text-primary font-semibold">
Grow in Faith
</p>
<h1 className="font-heading text-h1 text-ink mb-6">Adventist Resources</h1>
<p className="text-xl text-muted leading-relaxed">
Discover trusted resources, Bible study materials, and educational content
from Seventh-day Adventist organizations worldwide.
</p>
</div>

View File

@@ -9,8 +9,8 @@
<h1 className="font-heading text-display30 mb-4">Service Times</h1>
<ul className="text-[16px] list-disc pl-6">
<li><strong>Sabbath School</strong>: 9:30 AM</li>
<li><strong>Worship Service</strong>: 11:00 AM</li>
<li><strong>Sabbath School</strong>: 9:45 AM CT</li>
<li><strong>Worship Service</strong>: 11:00 AM CT</li>
</ul>
</div>

View File

@@ -5,7 +5,7 @@ const services = [
{
title: 'Sabbath School',
image: '/assets/family_entry.png',
time: '9:30 AM',
time: '9:45 AM CT',
day: 'Saturday',
description: 'Interactive Bible study and discussion groups for all ages. Join us for meaningful conversations about Scripture and practical Christian living.',
tagColor: 'bg-blue-100 text-blue-800'
@@ -13,7 +13,7 @@ const services = [
{
title: 'Divine Worship',
image: '/assets/speeking.png',
time: '11:00 AM',
time: '11:00 AM CT',
day: 'Saturday',
description: 'Our main worship service featuring inspiring music, prayer, and biblical messages that speak to everyday life and spiritual growth.',
tagColor: 'bg-primary/15 text-primary'
@@ -29,11 +29,6 @@ const services = [
]
const additionalInfo = [
{
title: 'Church Bus Service',
description: 'Transportation is available for those who need assistance getting to church. Please contact us to arrange pickup.',
icon: '🚌'
},
{
title: 'Family-Friendly Environment',
description: 'Children are welcome in all our services. We also offer age-appropriate activities and care during worship times.',
@@ -46,14 +41,14 @@ const additionalInfo = [
}
]
export default function Services(){
export default function Services() {
return (
<>
<Helmet>
<title>Services - Annaville Seventh-day Adventist Church</title>
<meta
name="description"
content="Join us for Sabbath School at 9:30 AM and Divine Worship at 11:00 AM every Saturday. Experience meaningful fellowship and spiritual growth in a welcoming community."
content="Join us for Sabbath School at 9:45 AM CT and Divine Worship at 11:00 AM CT every Saturday. Experience meaningful fellowship and spiritual growth in a welcoming community."
/>
<meta
name="keywords"
@@ -61,11 +56,14 @@ export default function Services(){
/>
</Helmet>
<section className="bg-gradient-to-br from-primary to-primaryDeep text-white py-20">
<section className="bg-gradient-to-br from-primary/15 via-sand/20 to-white py-20">
<div className="container">
<div className="max-w-4xl mx-auto text-center space-y-6">
<h1 className="font-heading text-h1">Worship Services</h1>
<p className="text-xl text-white/90 leading-relaxed">
<p className="text-xs uppercase tracking-[0.3em] text-primary font-semibold">
Join Us for Worship
</p>
<h1 className="font-heading text-h1 text-ink">Worship Services</h1>
<p className="text-xl text-muted leading-relaxed">
Join us every Saturday for inspiring worship, meaningful Bible study, and warm fellowship. All are welcome to experience the love of Christ in our community.
</p>
</div>
@@ -147,7 +145,7 @@ export default function Services(){
</p>
</div>
<div className="grid md:grid-cols-3 gap-8">
<div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
{additionalInfo.map(item => (
<div key={item.title} className="text-center">
<div className="bg-white p-8 rounded-xl shadow-sm border border-subtle">

View File

@@ -1,13 +1,141 @@
import React from 'react'
import React, { useEffect } from 'react'
import { Helmet } from 'react-helmet-async'
export default function TermsofUse() {
useEffect(() => {
window.scrollTo(0, 0)
}, [])
return (
<section className="section">
<Helmet><title>Terms of Use | Annaville SDA Church</title></Helmet>
<div className="container">
<h1 className="font-heading text-display30 mb-4">Terms of Use</h1>
<p className='text-[16px]'>Terms of use content placeholder.</p>
<div className="container max-w-4xl">
<h1 className="font-heading text-display30 mb-8">Terms of Use</h1>
<div className="prose prose-lg max-w-none">
<p className="text-muted mb-6">
<em>Last updated: {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })}</em>
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Agreement to Terms</h2>
<p className="text-body mb-4">
By accessing and using the Annaville Seventh-day Adventist Church website ("Website"), you accept and agree
to be bound by the terms and provisions of this agreement. If you do not agree to abide by these Terms of Use,
please do not use this Website.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Use of Website</h2>
<p className="text-body mb-4">
This Website is provided for informational and ministry purposes. You may use the Website for lawful purposes
only. You agree not to use the Website:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>In any way that violates any applicable federal, state, local, or international law or regulation</li>
<li>To transmit any material that is unlawful, threatening, abusive, harassing, defamatory, vulgar, obscene, or otherwise objectionable</li>
<li>To impersonate or attempt to impersonate the church, a church employee, another user, or any other person or entity</li>
<li>To engage in any conduct that restricts or inhibits anyone's use or enjoyment of the Website</li>
<li>To introduce any viruses, trojan horses, worms, logic bombs, or other material that is malicious or technologically harmful</li>
</ul>
<h2 className="font-heading text-h2 mt-8 mb-4">Intellectual Property Rights</h2>
<p className="text-body mb-4">
The Website and its entire contents, features, and functionality (including but not limited to all information,
software, text, displays, images, video, and audio, and the design, selection, and arrangement thereof) are
owned by Annaville Seventh-day Adventist Church, its licensors, or other providers of such material and are
protected by United States and international copyright, trademark, patent, trade secret, and other intellectual
property or proprietary rights laws.
</p>
<p className="text-body mb-4">
You may view, download, and print pages from the Website for your personal, non-commercial use, subject to the
restrictions set out below and elsewhere in these Terms of Use:
</p>
<ul className="list-disc pl-6 mb-4 space-y-2">
<li>You must not modify copies of any materials from this Website</li>
<li>You must not use any illustrations, photographs, video or audio sequences, or any graphics separately from the accompanying text</li>
<li>You must not delete or alter any copyright, trademark, or other proprietary rights notices from copies of materials from this Website</li>
</ul>
<h2 className="font-heading text-h2 mt-8 mb-4">User Contributions</h2>
<p className="text-body mb-4">
The Website may contain features that allow users to post, submit, publish, display, or transmit content or
materials (collectively, "User Contributions") on or through the Website. All User Contributions must comply
with these Terms of Use.
</p>
<p className="text-body mb-4">
Any User Contribution you post to the Website will be considered non-confidential and non-proprietary. By
providing any User Contribution on the Website, you grant us the right to use, reproduce, modify, perform,
display, distribute, and otherwise disclose to third parties any such material for any purpose.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Donations</h2>
<p className="text-body mb-4">
Donations made through this Website are processed securely through third-party payment processors. All donations
are final and non-refundable unless otherwise required by law. Annaville Seventh-day Adventist Church is a
501(c)(3) nonprofit organization, and donations may be tax-deductible to the extent allowed by law.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Links to Third-Party Websites</h2>
<p className="text-body mb-4">
This Website may contain links to third-party websites or services that are not owned or controlled by
Annaville Seventh-day Adventist Church. We have no control over, and assume no responsibility for, the content,
privacy policies, or practices of any third-party websites or services. You acknowledge and agree that we shall
not be responsible or liable for any damage or loss caused by or in connection with the use of any such content,
goods, or services available on or through any such websites or services.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Disclaimer of Warranties</h2>
<p className="text-body mb-4">
The Website is provided on an "AS IS" and "AS AVAILABLE" basis. We make no representations or warranties of any
kind, express or implied, as to the operation of the Website or the information, content, materials, or products
included on the Website. You expressly agree that your use of the Website is at your sole risk.
</p>
<p className="text-body mb-4">
To the full extent permissible by applicable law, we disclaim all warranties, express or implied, including,
but not limited to, implied warranties of merchantability and fitness for a particular purpose.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Limitation of Liability</h2>
<p className="text-body mb-4">
In no event will Annaville Seventh-day Adventist Church, its officers, directors, employees, or agents be
liable to you or any third party for any direct, indirect, consequential, exemplary, incidental, special, or
punitive damages, including lost profit, lost revenue, loss of data, or other damages arising from your use of
the Website, even if we have been advised of the possibility of such damages.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Indemnification</h2>
<p className="text-body mb-4">
You agree to defend, indemnify, and hold harmless Annaville Seventh-day Adventist Church, its officers,
directors, employees, and agents from and against any claims, liabilities, damages, judgments, awards, losses,
costs, expenses, or fees (including reasonable attorneys' fees) arising out of or relating to your violation of
these Terms of Use or your use of the Website.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Governing Law</h2>
<p className="text-body mb-4">
These Terms of Use shall be governed by and construed in accordance with the laws of the State of Texas,
without regard to its conflict of law provisions. Any legal action or proceeding arising under these Terms of
Use will be brought exclusively in the federal or state courts located in Nueces County, Texas.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Changes to Terms</h2>
<p className="text-body mb-4">
We reserve the right to modify these Terms of Use at any time. We will notify you of any changes by posting
the new Terms of Use on this page and updating the "Last updated" date. Your continued use of the Website
following the posting of changes constitutes your acceptance of such changes.
</p>
<h2 className="font-heading text-h2 mt-8 mb-4">Contact Information</h2>
<p className="text-body mb-4">
If you have any questions about these Terms of Use, please contact us:
</p>
<div className="bg-sand p-6 rounded-lg mb-8">
<p className="mb-2"><strong>Annaville Seventh-day Adventist Church</strong></p>
<p className="mb-2">2710 Violet Rd, Corpus Christi, TX 78410</p>
<p className="mb-2">Phone: <a href="tel:+13612415501" className="text-primary hover:underline">(361) 241-5501</a></p>
<p>Email: <a href="mailto:info@annavillesda.org" className="text-primary hover:underline">info@annavillesda.org</a></p>
</div>
</div>
</div>
</section>
)

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { createEvent, getAdminToken, getEvent, updateEvent, uploadImage } from '../../utils/api'
const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:4001').replace(/\/$/, '')
const apiBaseUrl = (import.meta.env.VITE_API_BASE_URL || '').replace(/\/$/, '')
function resolveImageUrl(value) {
if (!value) return ''
@@ -15,6 +15,10 @@ function resolveImageUrl(value) {
if (apiBaseUrl) {
return `${apiBaseUrl}${path}`
}
// Fallback to current domain if no API base URL is set
if (typeof window !== 'undefined') {
return `${window.location.origin}${path}`
}
return path
}

View File

@@ -1,4 +1,4 @@
const baseUrl = (import.meta.env.VITE_API_BASE_URL || 'http://localhost:4001').replace(/\/$/, '')
const baseUrl = (import.meta.env.VITE_API_BASE_URL || '').replace(/\/$/, '')
async function request(path, { method = 'GET', body, token, headers = {} } = {}) {
const url = `${baseUrl}${path}`

View File

@@ -8,12 +8,12 @@ export default defineConfig({
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:4001',
target: 'http://localhost:3070',
changeOrigin: true,
secure: false,
},
'/uploads': {
target: 'http://localhost:4001',
target: 'http://localhost:3070',
changeOrigin: true,
secure: false,
},