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
73 changes: 31 additions & 42 deletions app/(pages)/(hackers)/(hub)/schedule/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';
import { useState, useEffect, useMemo } from 'react';
import CalendarItem from '../../_components/Schedule/CalendarItem';
import Loader from '@components/Loader/Loader';
import Footer from '@components/Footer/Footer';
import Image from 'next/image';
import headerGrass from '@public/hackers/schedule/header_grass.svg';
Expand Down Expand Up @@ -51,7 +50,6 @@ export default function Page() {
const [activeFilters, setActiveFilters] = useState<ScheduleFilter[]>(['ALL']);
const [isMobileFilterOpen, setIsMobileFilterOpen] = useState(false);
const [scheduleData, setScheduleData] = useState<ScheduleData | null>(null);
const [isActionInProgress, setIsActionInProgress] = useState(false);

const changeActiveDay = (day: '9' | '10') => {
setActiveDay(day);
Expand All @@ -70,7 +68,6 @@ export default function Page() {

// Function to handle adding to personal schedule with loading state
const handleAddToSchedule = async (eventId: string) => {
setIsActionInProgress(true);
const success = await addToPersonalSchedule(eventId);

if (success) {
Expand Down Expand Up @@ -98,13 +95,10 @@ export default function Page() {
}
}
}

setIsActionInProgress(false);
};

// Function to handle removing from personal schedule with loading state
const handleRemoveFromSchedule = async (eventId: string) => {
setIsActionInProgress(true);
const success = await removeFromPersonalSchedule(eventId);

if (success) {
Expand Down Expand Up @@ -132,8 +126,6 @@ export default function Page() {
}
}
}

setIsActionInProgress(false);
};

// Force refresh events when user data changes
Expand All @@ -146,7 +138,7 @@ export default function Page() {
// Update the existing useEffect - simplify to just set the schedule data without virtual events
useEffect(() => {
if (!eventsLoading && !personalEventsLoading) {
// Group events by day key - "19" or "20".
// Group events by day key - "09" or "10".
const groupedByDay = eventData.reduce(
(acc: ScheduleData, eventWithCount) => {
const event = eventWithCount.event;
Expand Down Expand Up @@ -303,20 +295,11 @@ export default function Page() {
setActiveFilters([...withoutAll, label]);
};

// Update the loading state to include eventsLoading
const isLoading =
userLoading || personalEventsLoading || eventsLoading || isActionInProgress;
// Loading state only when initially loading data, not when performing add/remove actions (requested by design)
const isInitialLoad = userLoading;

const isError = personalEventsError || eventsError;

// Determine if we're in a loading state
if (isLoading)
return (
<main id="schedule" className="w-full">
<Loader />
</main>
);

if (isError)
return (
<main
Expand Down Expand Up @@ -404,7 +387,7 @@ export default function Page() {
/>
</div>

<div className="shrink-0 flex flex-col gap-2 items-start md:col-start-1 md:row-start-2 md:mt-8">
<div className="shrink-0 flex flex-col gap-2 items-start md:col-start-1 md:row-start-2 md:mt-8 sticky top-20">
<button
onClick={() => changeActiveDay('9')}
type="button"
Expand Down Expand Up @@ -437,7 +420,11 @@ export default function Page() {
</div>

<div className="w-full md:col-start-2 md:row-start-3 mb-[100px] mt-2 md:mt-[24px] lg:mt-[48px]">
{sortedGroupedEntries.length > 0 ? (
{isInitialLoad ? (
<div>
<p>loading...</p>
</div>
) : sortedGroupedEntries.length > 0 ? (
sortedGroupedEntries.map(([timeKey, events]) => (
<div key={timeKey} className="relative mb-[24px]">
<div className="font-dm-mono text-sm md:text-lg font-normal leading-[145%] tracking-[0.36px] text-[#7C7C85] mt-[16px] mb-[6px]">
Expand All @@ -464,27 +451,29 @@ export default function Page() {
</div>
))
) : (
<div className="text-center py-10">
{activeTab === 'personal' ? (
<div>
<p className="mb-4">
No events in your personal schedule yet.
</p>
<Button
onClick={() => setActiveTab('schedule')}
className="w-full sm:w-fit px-8 py-2 border-2 border-black rounded-3xl border-dashed hover:border-solid cursor-pointer relative group"
variant="ghost"
>
<div className="absolute inset-0 rounded-3xl transition-all duration-300 ease-out cursor-pointer bg-black w-0 group-hover:w-full" />
<p className="font-semibold relative z-10 transition-colors duration-300 text-black group-hover:text-white">
Browse the schedule to add events
isInitialLoad && (
<div className="text-center py-10">
{activeTab === 'personal' ? (
<div>
<p className="mb-4">
No events in your personal schedule yet.
</p>
</Button>
</div>
) : (
'No events found for this day and filter(s).'
)}
</div>
<Button
onClick={() => setActiveTab('schedule')}
className="w-full sm:w-fit px-8 py-2 border-2 border-black rounded-3xl border-dashed hover:border-solid cursor-pointer relative group"
variant="ghost"
>
<div className="absolute inset-0 rounded-3xl transition-all duration-300 ease-out cursor-pointer bg-black w-0 group-hover:w-full" />
<p className="font-semibold relative z-10 transition-colors duration-300 text-black group-hover:text-white">
Browse the schedule to add events
</p>
</Button>
</div>
) : (
'No events found for this day and filter(s).'
)}
</div>
)
)}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Image from 'next/image';
import grass_top from 'public/hackers/mvp/beginners/grass_top.svg';
import mobile_grass_top from 'public/hackers/mvp/beginners/mobile_grass_top.svg';
import mascots from 'public/hackers/mvp/beginners/mascots.svg';
import grass_top from 'public/hackers/beginners/grass_top.svg';
import mobile_grass_top from 'public/hackers/beginners/mobile_grass_top.svg';
import mascots from 'public/hackers/beginners/mascots.svg';
import TextCard from './TextCard';

export default function BeginnersSection() {
Expand All @@ -17,13 +17,14 @@ export default function BeginnersSection() {
alt="grass detail lining top of section"
className="md:hidden w-full h-auto"
/>
<div className="flex flex-col md:flex-row items-center justify-between px-[5%] py-[10%] gap-12 md:gap-0">
<div className="flex flex-col md:flex-row items-center justify-between px-[5%] pt-[5%] pb-[10%] gap-12 md:gap-0">
<div className="flex-1 flex justify-center md:justify-start">
<Image
src={mascots}
width={424}
height={611}
alt="four hackdavis mascots looking at a computer"
className="w-auto h-auto max-w-full"
/>
</div>
<div className="flex flex-1 justify-end">
Expand Down
16 changes: 13 additions & 3 deletions app/(pages)/(hackers)/_components/BeginnersSection/TextCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Link from 'next/link';
import Image from 'next/image';
import button_arrow from 'public/hackers/mvp/beginners/button_arrow.svg';
import button_arrow from 'public/hackers/beginners/button_arrow.svg';

interface TextCardProps {
short_text: string;
Expand Down Expand Up @@ -36,10 +36,20 @@ export default function TextCard({
>
<button
style={{ backgroundColor: `#${button_color}` }}
className="flex flex-row items-center justify-center gap-[10px] px-10 py-5 md:px-[50px] md:py-[40px] rounded-[1000px] text-[#003D3D] font-semibold"
className="group flex flex-row items-center justify-center gap-[10px] px-10 py-5 md:px-[50px] md:py-[40px] rounded-[1000px] text-[#003D3D] font-semibold transition-all duration-300 active:brightness-90"
>
{button_text}
<Image src={button_arrow} alt="small arrow" />
<div className="relative flex items-center overflow-hidden w-6 h-6">
<div className="absolute left-0 transition-transform duration-300 ease-in-out -translate-x-2 group-hover:translate-x-0">
<Image
src={button_arrow}
alt="arrow"
className="max-w-none"
width={24}
height={24}
/>
</div>
</div>
</button>
</Link>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,25 @@ export const Card: React.FC<CardProps> = ({

{/* Card Links */}
<div className="flex items-end mt-auto">
<Image
src="/components/MDHelp/arrow.svg"
alt={image}
width={1}
height={1}
className="w-6 h-auto m-0"
/>

<a
href={link}
className={`${dmMono.className} leading-none m-0.5 text-[#3F3F3F] font-medium border-b border-[#3F3F3F]`}
target="_blank"
rel="noopener noreferrer"
className={`${dmMono.className} group inline-flex items-center text-[#3F3F3F] font-medium leading-none`}
>
{linkName}
<span className="w-0 group-hover:w-6 h-6 flex items-center overflow-hidden transition-all duration-300 ease-out shrink-0">
<Image
src="/components/MDHelp/arrow.svg"
alt=""
width={24}
height={24}
className="w-6 h-auto -translate-x-3 group-hover:translate-x-0 transition-transform duration-300 ease-out"
/>
</span>

<span className="transition-transform duration-300 ease-out group-hover:translate-x-1 border-b border-[#3F3F3F] py-0.5 px-0.5">
{linkName}
</span>
</a>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions app/(pages)/(hackers)/_components/Schedule/CalendarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function CalendarItem({
</div>
{displayType !== 'GENERAL' && displayType !== 'MEALS' && (
<div
className={`flex flex-col sm:flex-row justify-between items-center gap-4 sm:gap-6 w-full ${
className={`flex flex-row justify-between items-center gap-2 sm:gap-6 w-full ${
//bottom
displayType !== 'ACTIVITIES' ? '' : 'sm:w-auto'
}`}
Expand Down Expand Up @@ -152,12 +152,12 @@ export function CalendarItem({
</div>
)}

<div className="flex flex-col gap-2 items-end w-full sm:w-auto">
<div className="flex flex-col gap-2 items-end sm:w-auto ml-auto">
<Button
onClick={
inPersonalSchedule ? onRemoveFromSchedule : onAddToSchedule
}
className="w-full sm:w-32 px-8 py-2 rounded-3xl cursor-pointer relative shrink-0"
className="w-auto h-auto px-9 py-4 rounded-3xl cursor-pointer relative shrink-0"
style={{
backgroundColor:
eventStyle.addButtonColor || 'rgba(0, 0, 0, 0)',
Expand Down
101 changes: 61 additions & 40 deletions app/(pages)/(hackers)/_components/Schedule/ScheduleMobileControls.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Image from 'next/image';
import { pageFilters, ScheduleFilter } from '@typeDefs/filters';
import { useEffect, useState } from 'react';

const MOBILE_FILTER_BG_DEFAULT = '#F3F3FC';
const MOBILE_FILTER_TEXT_DEFAULT = '#3F3F3F';
Expand Down Expand Up @@ -28,6 +29,18 @@ export default function ScheduleMobileControls({
const hasSelectedFilters = activeFilters.some((filter) => filter !== 'ALL');
const selectedFilterDots = activeFilters.filter((filter) => filter !== 'ALL');

const [isScrolled, setIsScrolled] = useState(false);

// Allows for filter button to disspear when user scroll down
useEffect(() => {
const handleScroll = () => {
// If scrolled more than 110px, hide filter button
setIsScrolled(window.scrollY > 110);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);

const renderDayButton = (day: '9' | '10', label: string) => (
<button
onClick={() => changeActiveDay(day)}
Expand All @@ -46,49 +59,57 @@ export default function ScheduleMobileControls({
);

return (
<div className="md:hidden">
<div className="md:hidden sticky top-10 z-20">
<div className="flex items-start justify-between gap-4">
<button
onClick={() => setIsMobileFilterOpen((prev) => !prev)}
type="button"
className={`relative inline-flex w-fit min-w-[52px] h-[45px] px-3 py-[13px] justify-center items-center rounded-[22.5px] font-jakarta text-sm font-semibold leading-[100%] tracking-[0.32px] transition-all duration-200 shrink-0 ${
isMobileFilterOpen || hasSelectedFilters
? 'bg-[#3F3F3F]'
: 'bg-[#F3F3FC]'
<div
className={`transition-all duration-300 ${
isScrolled && !isMobileFilterOpen
? 'opacity-0 pointer-events-none -translate-x-4'
: 'opacity-100'
}`}
>
<Image
src={
isMobileFilterOpen
? '/icons/white_x.svg'
: '/icons/hamburger_filter.svg'
}
alt={isMobileFilterOpen ? 'Close filters' : 'Open filters'}
width={16}
height={16}
className={
!isMobileFilterOpen && hasSelectedFilters ? 'invert' : ''
}
/>
{selectedFilterDots.length > 0 && (
<span className="flex items-center gap-1.5 ml-2">
{selectedFilterDots.map((filter) => {
const color = pageFilters.find((f) => f.label === filter)
?.color;
return (
<span
key={`mobile-filter-dot-${filter}`}
className="w-2.5 h-2.5 rounded-full border"
style={{
backgroundColor: color,
borderColor: color,
}}
/>
);
})}
</span>
)}
</button>
<button
onClick={() => setIsMobileFilterOpen((prev) => !prev)}
type="button"
className={`relative inline-flex w-fit min-w-[52px] h-[45px] px-3 py-[13px] justify-center items-center rounded-[22.5px] font-jakarta text-sm font-semibold leading-[100%] tracking-[0.32px] transition-all duration-200 shrink-0 ${
isMobileFilterOpen || hasSelectedFilters
? 'bg-[#3F3F3F]'
: 'bg-[#F3F3FC]'
}`}
>
<Image
src={
isMobileFilterOpen
? '/icons/white_x.svg'
: '/icons/hamburger_filter.svg'
}
alt={isMobileFilterOpen ? 'Close filters' : 'Open filters'}
width={16}
height={16}
className={
!isMobileFilterOpen && hasSelectedFilters ? 'invert' : ''
}
/>
{selectedFilterDots.length > 0 && (
<span className="flex items-center gap-1.5 ml-2">
{selectedFilterDots.map((filter) => {
const color = pageFilters.find((f) => f.label === filter)
?.color;
return (
<span
key={`mobile-filter-dot-${filter}`}
className="w-2.5 h-2.5 rounded-full border"
style={{
backgroundColor: color,
borderColor: color,
}}
/>
);
})}
</span>
)}
</button>
</div>

{!isMobileFilterOpen && (
<div className="shrink-0 h-[45px] flex flex-row gap-8 items-center">
Expand Down
Loading