Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/constants/RouteConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export const communityCoffeeChatsPath = `${communityBase}/coffeechats`;
export const communityRegionalHubsPath = `${communityBase}/regionalhubs`;
export const communityAmbassadorPath = `${communityBase}/ambassador`;
export const communityStartChapterPath = `${communityBase}/start-a-chapter`;
export const communityCreateEventPath = `${communityBase}/admin/create-event`;

// ── CUSTOMERS BASE ───────────────────────────────────────────────
export const customersBase = "/customers";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FaEnvelope, FaLinkedin } from "react-icons/fa";
import SEO from "../../util/SEO";

const SHEET_ID = "1Tr9-EvVLRroabQN7r5Xdu1W9gGKve4_WF4Lp8mZcEU4";
const API_KEY = "AIzaSyAgE4vhIZ-PR4XuGVRd8PZpyRFMfXIjNFM";
const API_KEY = "AIzaSyCjxXVDGAolugKgrTXpJ0HmAjL0lLxLN1E";
const SHEET_NAME = "Anote Alumni For Coffee Chats";

export default function ArmorCoffeeChats() {
Expand Down
131 changes: 116 additions & 15 deletions frontend/src/landing_page/landing_page_armor/ArmorEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ import {
BrowserRouter,
} from "react-router-dom";

// ─── Google Calendar live feed ───────────────────────────────────────────────
// 1. Create a Google Calendar for Anote events and make it public:
// calendar.google.com β†’ three-dot menu β†’ Settings β†’ Share β†’ "Make available to public"
// 2. Copy the Calendar ID:
// Settings β†’ Integrate calendar β†’ Calendar ID
// 3. Ensure the Google Calendar API is enabled for this project's API key in Google Cloud Console.
const GCAL_ID = "e88890e803003d95935e56c48dc68aedfd5311e3204360f781830c06287f4f24@group.calendar.google.com";
const GCAL_API_KEY = "AIzaSyCjxXVDGAolugKgrTXpJ0HmAjL0lLxLN1E";

// For calendar events that also have a dedicated community page, map event title
// β†’ internal route + image. Events not listed here link to the Google Calendar page.
const EVENT_ROUTE_MAP = {
"Anote World Cup Finals Watch Party": {
path: worldCupPartyPath,
image: "/events_images/worldcup.png",
},
};

const eventsData = [
// {
// path: feb2026Path,
Expand Down Expand Up @@ -268,8 +286,61 @@ const eventsData = [
}
];

function formatDisplayDate(isoString) {
const d = new Date(isoString);
return d.toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" });
}

function parseCalendarEvent(gcalEvent) {
const startISO = gcalEvent.start?.dateTime || gcalEvent.start?.date;
const endISO = gcalEvent.end?.dateTime || gcalEvent.end?.date;
const title = gcalEvent.summary || "Untitled Event";
const override = EVENT_ROUTE_MAP[title] || {};

// Support an optional "image: /path.png" first line in the calendar description.
let rawDesc = gcalEvent.description || "";
let image = override.image || "/events_images/aiday.png";
const imageMatch = rawDesc.match(/^image:\s*(\S+)\s*\n?/i);
if (imageMatch) {
image = imageMatch[1];
rawDesc = rawDesc.replace(imageMatch[0], "").trim();
}

return {
title,
date: formatDisplayDate(startISO),
description: rawDesc || title,
startISO,
endISO,
location: gcalEvent.location || "",
image,
path: override.path || gcalEvent.htmlLink,
external: !override.path,
fromCalendar: true,
};
}

const ArmorEvents = () => {
const navigate = useNavigate();
const [calendarEvents, setCalendarEvents] = useState([]);

useEffect(() => {
if (GCAL_ID === "REPLACE_WITH_YOUR_CALENDAR_ID") return;
const now = new Date().toISOString();
const url =
`https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(GCAL_ID)}/events` +
`?key=${GCAL_API_KEY}&singleEvents=true&orderBy=startTime&timeMin=${now}&maxResults=20`;

fetch(url)
.then((r) => r.json())
.then((data) => {
if (data.items) {
setCalendarEvents(data.items.map(parseCalendarEvent));
}
})
.catch(() => {}); // silently fall back to hardcoded list
}, []);

const getToday = () => {
const now = new Date();
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
Expand All @@ -296,7 +367,14 @@ const ArmorEvents = () => {
// .map(event => ({ ...event, dateObject: parseDate(event.date) }))
// .sort((a, b) => a.dateObject - b.dateObject);

const sortedEvents = eventsData
// Merge live calendar events with hardcoded list.
// Calendar events take precedence; deduplicate by title so the World Cup
// Party (which is hardcoded AND may appear in the calendar) shows only once.
const calendarTitles = new Set(calendarEvents.map((e) => e.title));
const dedupedStatic = eventsData.filter((e) => !calendarTitles.has(e.title));
const mergedEvents = [...calendarEvents, ...dedupedStatic];

const sortedEvents = mergedEvents
.map(event => ({ ...event, dateObject: parseDate(event.date) }))
.sort((a, b) => b.dateObject - a.dateObject);

Expand All @@ -305,6 +383,7 @@ const ArmorEvents = () => {

const [query, setQuery] = useState("");
const [filterType, setFilterType] = useState("all");
const [viewMode, setViewMode] = useState("cards"); // "cards" | "calendar"


const applyFilter = (ev) => {
Expand All @@ -330,34 +409,54 @@ const ArmorEvents = () => {
path="/community/events"
/>
<div className="mx-auto px-4 sm:px-6 lg:px-8">
{/* Tabs */}
<div className="mb-6 space-x-4">
{[
{/* Toolbar: filter tabs + view toggle */}
<div className="mb-6 flex flex-wrap items-center justify-between gap-4">
<div className="flex gap-2">
{[
{ label: "All", value: "all" },
{ label: "Upcoming", value: "upcoming" },
{ label: "Past", value: "past" },
].map(({ label, value }) => (
<button
<button
key={label}
onClick={() => setFilterType(value)}
onClick={() => setViewMode("cards") || setFilterType(value)}
className={`px-4 py-2 rounded-full border text-sm transition ${
filterType === value
filterType === value && viewMode === "cards"
? "bg-blue-600 text-white border-blue-600"
: "bg-gray-800 text-gray-300 border-gray-600 hover:bg-gray-700"
}`}
>
{label}
</button>
))}
</div>
<button
onClick={() => setViewMode(viewMode === "calendar" ? "cards" : "calendar")}
className={`px-4 py-2 rounded-full border text-sm transition ${
viewMode === "calendar"
? "bg-blue-600 text-white border-blue-600"
: "bg-gray-800 text-gray-300 border-gray-600 hover:bg-gray-700"
}`}
>
πŸ“… Calendar View
</button>
</div>
{/* <input
type="text"
placeholder="Search events…"
value={query}
onChange={(e) => setQuery(e.target.value)}
className="w-full sm:w-64 px-4 py-2 rounded-md bg-gray-800 border border-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
/> */}
</div>

{/* Google Calendar embed */}
{viewMode === "calendar" && (
<div className="mb-10 rounded-xl overflow-hidden border border-gray-700 shadow-lg">
<iframe
src={`https://calendar.google.com/calendar/embed?src=e88890e803003d95935e56c48dc68aedfd5311e3204360f781830c06287f4f24%40group.calendar.google.com&ctz=America%2FNew_York&bgcolor=%23111827&showTitle=0&showNav=1&showDate=1&showPrint=0&showTabs=0&showCalendars=0&mode=AGENDA`}
style={{ border: 0 }}
width="100%"
height="600"
title="Anote Community Events Calendar"
/>
</div>
)}

{/* Event cards grid */}
{viewMode === "cards" && (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-12">
{filteredEvents.map((event, index) => (
<div
Expand Down Expand Up @@ -389,7 +488,9 @@ const ArmorEvents = () => {
</div>
))}
</div>
)}
</div>
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import classNames from "classnames";

const SHEET_ID = "1Yb3zE-xvDUHsfiRaR75ggmMmOmkkgiDhgJ3WaVtRp10";
const API_KEY = "AIzaSyAgE4vhIZ-PR4XuGVRd8PZpyRFMfXIjNFM";
const API_KEY = "AIzaSyCjxXVDGAolugKgrTXpJ0HmAjL0lLxLN1E";
const SHEET_NAME = "active"; // your sheet tab name

function ArmorGrantsPartnerships() {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/landing_page/landing_page_armor/ArmorHubs.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const ArmorHubs = () => {

// Google Sheets API Configuration
const SHEET_ID = "1Io-rzcwhTP9YJ4I_5964XVptrvPqtvadFlgqpvkZ6WI"; // Your Google Sheet ID
const API_KEY = "AIzaSyAgE4vhIZ-PR4XuGVRd8PZpyRFMfXIjNFM"; // Your Google API Key
const API_KEY = "AIzaSyCjxXVDGAolugKgrTXpJ0HmAjL0lLxLN1E"; // Your Google API Key

// Function to construct sheet URLs based on city and data type
const constructSheetURL = (citySheetName, dataType) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import SEO from "../../util/SEO";
import ShareButton from "./ShareButton";

const SHEET_ID = "1IREd2vCxo7rDGOoDUYtzoywVGLqqUFCsu3qGsA0HsMc";
const API_KEY = "AIzaSyAgE4vhIZ-PR4XuGVRd8PZpyRFMfXIjNFM";
const API_KEY = "AIzaSyCjxXVDGAolugKgrTXpJ0HmAjL0lLxLN1E";
const SHEET_NAME = "Sheet-1";

function ArmorMembers() {
Expand Down
117 changes: 117 additions & 0 deletions frontend/src/landing_page/landing_page_armor/CreateEventAdmin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { useState } from "react";

// After deploying create-event.gs as a web app, paste the URL here.
const SCRIPT_URL = "https://script.google.com/macros/s/AKfycbypiWD1M4ne-qXKQR8tNraVVHTdknu_ixBUPS8EHJLqTMUMfNtfUAE0EWGfQ6jS2ltL/exec";

const FIELD_DEFS = [
{ name: "title", label: "Event Title", type: "text", required: true },
{ name: "description", label: "Description", type: "textarea", required: false },
{ name: "startDateTime", label: "Start Date & Time", type: "datetime-local", required: true },
{ name: "endDateTime", label: "End Date & Time", type: "datetime-local", required: true },
{ name: "location", label: "Location", type: "text", required: false },
{ name: "imagePath", label: "Image Path (optional)", type: "text", required: false,
placeholder: "/events_images/worldcup.png" },
];

const EMPTY = Object.fromEntries(FIELD_DEFS.map((f) => [f.name, ""]));

export default function CreateEventAdmin() {
const [form, setForm] = useState(EMPTY);
const [status, setStatus] = useState("idle"); // idle | loading | success | error
const [message, setMessage] = useState("");

const handleChange = (e) => {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
};

const handleSubmit = async (e) => {
e.preventDefault();
if (SCRIPT_URL === "REPLACE_WITH_APPS_SCRIPT_WEB_APP_URL") {
setStatus("error");
setMessage("Deploy create-event.gs first and paste the web app URL into CreateEventAdmin.js.");
return;
}
setStatus("loading");
try {
await fetch(SCRIPT_URL, {
method: "POST",
mode: "no-cors",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
// no-cors means we can't read the response body β€” assume success if no throw
setStatus("success");
setMessage(`"${form.title}" was added to the Anote community calendar.`);
setForm(EMPTY);
} catch (err) {
setStatus("error");
setMessage("Failed to create event. Check the script URL and try again.");
}
};

const inputClass =
"w-full px-4 py-2 rounded-md bg-gray-700 text-white border border-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-400";

return (
<div className="min-h-screen bg-gray-900 text-white flex items-center justify-center px-4 py-12">
<div className="w-full max-w-lg bg-gray-800 rounded-xl shadow-lg p-8">
<h1 className="text-2xl font-bold mb-2">Add Event to Calendar</h1>
<p className="text-sm text-gray-400 mb-6">
Creates an event in the Anote community Google Calendar. It will appear
on the events page automatically.
</p>

{status === "success" && (
<div className="mb-6 rounded-lg bg-green-900/50 border border-green-500 px-4 py-3 text-green-300">
βœ… {message}
</div>
)}
{status === "error" && (
<div className="mb-6 rounded-lg bg-red-900/50 border border-red-500 px-4 py-3 text-red-300">
❌ {message}
</div>
)}

<form onSubmit={handleSubmit} className="space-y-4">
{FIELD_DEFS.map((field) => (
<div key={field.name}>
<label className="block text-sm font-medium mb-1">
{field.label}
{field.required && <span className="text-red-400 ml-1">*</span>}
</label>
{field.type === "textarea" ? (
<textarea
name={field.name}
value={form[field.name]}
onChange={handleChange}
rows={3}
className={inputClass}
placeholder={field.placeholder || ""}
/>
) : (
<input
type={field.type}
name={field.name}
value={form[field.name]}
onChange={handleChange}
required={field.required}
className={inputClass}
placeholder={field.placeholder || ""}
/>
)}
</div>
))}

<button
type="submit"
disabled={status === "loading"}
className="w-full bg-blue-600 hover:bg-blue-500 disabled:opacity-50 text-white font-bold py-2 px-4 rounded-md transition"
>
{status === "loading" ? "Creating…" : "Create Event"}
</button>
</form>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/src/landing_page/landing_page_armor/LocalPOCs.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const LocalPOCs = () => {
const [showGuide, setShowGuide] = useState(false); // State for showing/hiding guide

const SHEET_ID = "1f5XnPzK-h1nn7d9iNCscTc5jycSZHOMhU0fljB3zC6I";
const API_KEY = "AIzaSyAgE4vhIZ-PR4XuGVRd8PZpyRFMfXIjNFM";
const API_KEY = "AIzaSyCjxXVDGAolugKgrTXpJ0HmAjL0lLxLN1E";
const SHEET_URL = `https://sheets.googleapis.com/v4/spreadsheets/${SHEET_ID}/values/Overview?key=${API_KEY}`;

useEffect(() => {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/landing_page/landing_page_armor/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import ArmorAIAcademy from "./ArmorAIAcademy";
import ArmorAIAcademyOverview from "./ArmorAIAcademyOverview";
import AmbassadorProgram from "./AmbassadorProgram";
import StartAChapter from "./StartAChapter";
import CreateEventAdmin from "./CreateEventAdmin";

// ─── Nav config ──────────────────────────────────────────────────
const navigation = [
Expand Down Expand Up @@ -301,6 +302,7 @@ export default function Main() {
<Route path="/regionalhubs" element={<ArmorHubs />} />
<Route path="/ambassador" element={<AmbassadorProgram />} />
<Route path="/start-a-chapter" element={<StartAChapter />} />
<Route path="/admin/create-event" element={<CreateEventAdmin />} />
{/* Fallback */}
<Route path="/academy" element={<ArmorAIAcademy />} />
</Routes>
Expand Down
Loading
Loading