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
17 changes: 17 additions & 0 deletions .github/workflows/firebase-rules.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Test Firebase rules on PR
'on': pull_request
jobs:
test_rules:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm install -g firebase-tools
- run: npm ci
- run: npm run test:rules
12 changes: 4 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import Login from "./pages/Login";
import Navbar from "./components/Navbar";
import Dashboard from "./pages/Dashboard";

import { devTesting } from "./config";

function App() {
const { user, authIsReady } = useAuthContext();

Expand All @@ -32,12 +30,10 @@ function AppRoutes({ user }: { user: any }) {
{!location.pathname.includes("/dashboard") && <Navbar />}
<Routes>
<Route path="/*" element={<Homepage />} />
{devTesting && (
<>
<Route path="/login" element={user ? <Navigate to="/dashboard/carpool" /> : <Login />} />
<Route path="/dashboard/*" element={user ? <Dashboard /> : <Navigate to="/login" />} />
</>
)}
<>
<Route path="/login" element={user ? <Navigate to="/dashboard/carpool" /> : <Login />} />
<Route path="/dashboard/*" element={user ? <Dashboard /> : <Navigate to="/login" />} />
</>
</Routes>
</>
);
Expand Down
6 changes: 4 additions & 2 deletions src/components/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ export default function Events() {

const filteredEvents = useMemo(() => {
if (!events) return [];
const now = new Date();
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);

return events.filter(event => {
const eventDate = event.date.toDate();
return showUpcoming ? eventDate >= now : eventDate < now;
return showUpcoming ? eventDate >= yesterday : eventDate < yesterday;
});
}, [events, showUpcoming]);

Expand Down
37 changes: 17 additions & 20 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { useAuthContext } from "@/hooks/useAuthContext";
import { devTesting } from "@/config";
import { IconMenu2, IconX } from "@tabler/icons-react";

import wccLogo from "@/assets/WCC-logo-symbol.png";
Expand Down Expand Up @@ -60,25 +59,23 @@ const Navbar: React.FC = () => {
</a>
</li>
))}
{devTesting && (
<li className="w-full md:w-auto">
{user ? (
<Link
to="/dashboard/carpool"
className="block px-6 py-3 rounded-full text-black bg-white font-medium hover:bg-gray-300 hover:shadow-lg transition-all duration-200 text-center"
>
Dashboard
</Link>
) : (
<Link
to="/login"
className="block px-6 py-3 rounded-full text-black bg-white font-medium hover:bg-gray-300 hover:shadow-lg transition-all duration-200 text-center"
>
Login
</Link>
)}
</li>
)}
<li className="w-full md:w-auto">
{user ? (
<Link
to="/dashboard/carpool"
className="block px-6 py-3 rounded-full text-black bg-white font-medium hover:bg-gray-300 hover:shadow-lg transition-all duration-200 text-center"
>
Dashboard
</Link>
) : (
<Link
to="/login"
className="block px-6 py-3 rounded-full text-black bg-white font-medium hover:bg-gray-300 hover:shadow-lg transition-all duration-200 text-center"
>
Login
</Link>
)}
</li>
</ul>
{/* Hamburger button for <md screens */}
<button
Expand Down
17 changes: 15 additions & 2 deletions src/features/carpool/components/Carpool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import CarpoolCard from "./CarpoolCard";
import { useAuthContext } from "@/hooks/useAuthContext";

export default function Carpool() {
const { documents: carpools } = useCollection<CarpoolPost>(collections.carpoolCollection, null, ['targetDate', 'asc'], true);
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const { documents: carpools } = useCollection<CarpoolPost>(collections.carpoolCollection, ['targetDate', '>=', yesterday], ['targetDate', 'asc'], true);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isInboxModalOpen, setIsInboxModalOpen] = useState(false);
const { user: currentUser } = useAuthContext();
Expand All @@ -29,8 +31,19 @@ export default function Carpool() {
const joinedCarpools: CarpoolPost[] = [];
const requestedCarpools: CarpoolPost[] = [];
const availableCarpools: CarpoolPost[] = [];
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);

carpools.forEach((carpool) => {
// Hide carpools that aren't yours and have passed their targetDate
const isOwner = carpool.userId === currentUser.uid;
const targetDate = carpool.targetDate.toDate();
const hasPassed = targetDate <yesterday;

if (!isOwner && hasPassed) {
return; // Skip this carpool
}

if (carpool.userId === currentUser.uid) {
myCarpools.push(carpool);
} else if (carpool.people.includes(currentUser.uid)) {
Expand Down Expand Up @@ -86,7 +99,7 @@ export default function Carpool() {
<div className="w-full">
<div className="flex w-full flex-col gap-4 border border-slate-200/50 bg-white to-indigo-50/30 p-4 sm:p-6 md:p-8 lg:p-10 dark:border-slate-700/50 dark:bg-gray-900 shadow-xl min-h-screen">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
<h1 className="font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 via-purple-600 to-indigo-700 text-3xl sm:text-4xl lg:text-5xl mb-4 sm:mb-0">
<h1 className="font-bold text-transparent bg-clip-text bg-indigo-600 text-3xl sm:text-4xl lg:text-5xl mb-4 sm:mb-0">
Available Carpools
</h1>
<div className="flex items-center gap-4">
Expand Down
50 changes: 48 additions & 2 deletions src/features/carpool/components/CarpoolDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useParams, Link, useNavigate } from "react-router-dom";
import { useCarpoolWithUser } from "@/hooks/useCarpoolWithUser";
import { CarpoolStatusEnum } from "../enums/CarpoolStatusEnum";
import { IconArrowLeft, IconCalendar, IconUser, IconUsers, IconMapPin, IconClock, IconFileText, IconEdit, IconTrash, IconChevronDown, IconPhone, IconMail, IconX, IconAlertTriangle } from "@tabler/icons-react";
import { IconArrowLeft, IconCalendar, IconUser, IconUsers, IconMapPin, IconClock, IconFileText, IconEdit, IconTrash, IconChevronDown, IconPhone, IconMail, IconX, IconAlertTriangle, IconMailForward } from "@tabler/icons-react";
import { convertTimestampToDate } from "@/utils/firebaseDateConvert";
import { useAuthContext } from "@/hooks/useAuthContext";
import { doc, updateDoc, arrayUnion, arrayRemove, deleteDoc } from "firebase/firestore";
Expand Down Expand Up @@ -380,6 +380,44 @@ export default function CarpoolDetails() {
window.location.reload();
};

// Function to handle emailing all carpool members
const handleEmailAll = () => {
if (!carpoolUsers || carpoolUsers.length === 0) {
setMessage({ text: "No participants to email.", type: 'info' });
return;
}

if (!carpool) return;
// Collect all emails from carpool users
const emails = carpoolUsers
.map(user => user.email)
.filter(email => email); // Filter out undefined/null emails

if (emails.length === 0) {
setMessage({ text: "No email addresses available for participants.", type: 'error' });
return;
}

// Create subject and body for the email
const subject = encodeURIComponent(`Carpool Update: ${carpool.location} → ${carpool.destination}`);
const body = encodeURIComponent(
`Hello everyone,\n\n` +
`This is an update about our carpool:\n\n` +
`From: ${carpool.location}\n` +
`To: ${carpool.destination}\n` +
`Date: ${convertTimestampToDate(carpool.targetDate).date}\n` +
`Time: ${convertTimestampToDate(carpool.targetDate).time}\n\n` +
`[Your message here]\n\n` +
`Best regards`
);

// Create mailto link with all emails in TO field
const mailtoLink = `mailto:${emails.join(',')}?subject=${subject}&body=${body}`;

// Open the mailto link
window.location.href = mailtoLink;
};

// Function to render the appropriate action button based on carpool status and user relationship
const renderActionButton = () => {
if (!carpool) return null;
Expand Down Expand Up @@ -587,7 +625,7 @@ export default function CarpoolDetails() {
<IconArrowLeft className="text-slate-600 dark:text-slate-300" size={20} />
</button>
<div>
<h1 className="font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 via-purple-600 to-indigo-700 text-2xl sm:text-3xl lg:text-4xl">
<h1 className="font-bold text-transparent bg-clip-text bg-indigo-600 text-2xl sm:text-3xl lg:text-4xl">
Carpool Details
</h1>
<p className="text-slate-500 dark:text-slate-400 mt-1">
Expand All @@ -600,6 +638,14 @@ export default function CarpoolDetails() {
{/* Owner Actions */}
{currentUser && carpool.userId === currentUser.uid && (
<div className="flex items-center gap-2">
<button
onClick={handleEmailAll}
className="inline-flex items-center gap-2 px-3 py-2 bg-gradient-to-r from-purple-500 to-pink-600 text-white rounded-lg font-medium hover:from-purple-600 hover:to-pink-700 transition-all duration-200 transform hover:scale-[1.02] active:scale-[0.98] text-sm"
title="Send email to all participants"
>
<IconMailForward size={16} />
Email All
</button>
<button
onClick={() => setIsEditModalOpen(true)}
className="inline-flex items-center gap-2 px-3 py-2 bg-gradient-to-r from-blue-500 to-indigo-600 text-white rounded-lg font-medium hover:from-blue-600 hover:to-indigo-700 transition-all duration-200 transform hover:scale-[1.02] active:scale-[0.98] text-sm"
Expand Down
4 changes: 2 additions & 2 deletions src/features/profile/components/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default function Profile() {
if (!currentUser) {
return (
<div className="w-full">
<div className="flex w-full flex-col gap-4 rounded-tl-2xl border border-neutral-200 bg-white p-4 md:p-10 dark:border-neutral-700 dark:bg-neutral-900 min-h-screen">
<div className="flex w-full flex-col gap-4 rounded-tl-2xl border border-neutral-200 bg-white p-4 md:p-10 dark:border-slate-700/50 dark:bg-gray-900 min-h-screen">
<div className="flex flex-col items-center justify-center py-16">
<div className="w-24 h-24 bg-gradient-to-br from-blue-200 to-blue-300 dark:from-blue-700 dark:to-blue-600 rounded-full flex items-center justify-center mb-6">
<IconUser className="w-12 h-12 text-blue-600 dark:text-blue-300" />
Expand All @@ -91,7 +91,7 @@ export default function Profile() {

return (
<div className="w-full">
<div className="flex w-full flex-col gap-6 rounded-tl-2xl border border-neutral-200 bg-white p-4 md:p-10 dark:border-neutral-700 dark:bg-neutral-900 min-h-screen">
<div className="flex w-full flex-col gap-6 rounded-tl-2xl border border-neutral-200 bg-white p-4 md:p-10 dark:border-slate-700/50 dark:bg-gray-900 min-h-screen">

{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
Expand Down