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
4 changes: 3 additions & 1 deletion apps/web/app/challenges/[id]/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ async function DashboardContent({
initialSummary={initialSummary}
>
<div className="mx-auto max-w-2xl px-4 py-6 space-y-4">
<OnboardingCard challengeId={challenge.id} userId={user._id} />
{dateOnlyToUtcMs(challenge.startDate) > Date.now() && (
<OnboardingCard challengeId={challenge.id} userId={user._id} challengeStartDate={challenge.startDate} />
)}
<ActivityFeed
challengeId={challenge.id}
initialItems={initialFeed.page}
Expand Down
9 changes: 8 additions & 1 deletion apps/web/components/dashboard/challenge-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import { useChallengeRealtime } from './challenge-realtime-context';
import { UserAvatar } from '@/components/user-avatar';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ActiveMiniGames } from '@/components/mini-games';
import { OnboardingCard } from './onboarding-card';
import { dateOnlyToUtcMs } from '@/lib/date-only';
import { cn } from '@/lib/utils';

interface ChallengeSidebarProps {
challengeId: string;
currentUserId: string;
challengeStartDate: string;
}

export function ChallengeSidebar({ challengeId, currentUserId }: ChallengeSidebarProps) {
export function ChallengeSidebar({ challengeId, currentUserId, challengeStartDate }: ChallengeSidebarProps) {
const { summary } = useChallengeRealtime();
const { stats, leaderboard } = summary;

Expand Down Expand Up @@ -112,6 +115,10 @@ export function ChallengeSidebar({ challengeId, currentUserId }: ChallengeSideba
})}
</CardContent>
</Card>

{dateOnlyToUtcMs(challengeStartDate) <= Date.now() && (
<OnboardingCard challengeId={challengeId} userId={currentUserId} challengeStartDate={challengeStartDate} />
)}
</div>
);
}
17 changes: 11 additions & 6 deletions apps/web/components/dashboard/dashboard-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { ReactNode } from "react";
import { ReactNode, useCallback, useRef, WheelEvent } from "react";
import { Plus } from "lucide-react";
import type { Doc } from "@repo/backend/_generated/dataModel";
import { formatDateShortFromDateOnly } from "@/lib/date-only";
Expand Down Expand Up @@ -34,10 +34,15 @@ export function DashboardLayout({
children,
hideRightSidebar = false,
}: DashboardLayoutProps) {
const mainRef = useRef<HTMLElement>(null);
const forwardScroll = useCallback((e: WheelEvent) => {
mainRef.current?.scrollBy({ top: e.deltaY, left: e.deltaX });
}, []);

return (
<div className="flex h-screen bg-black text-white">
{/* Left Sidebar - Collapsed (lg) */}
<aside className="hidden w-[72px] flex-shrink-0 flex-col border-r border-zinc-800 lg:flex xl:hidden">
<aside onWheel={forwardScroll} className="hidden w-[72px] flex-shrink-0 flex-col border-r border-zinc-800 lg:flex xl:hidden">
<div className="flex h-full flex-col items-center py-4">
{/* Logo/Icon */}
<div className="mb-4 h-10 w-10 rounded-full bg-gradient-to-r from-indigo-500 to-fuchsia-500" />
Expand Down Expand Up @@ -80,7 +85,7 @@ export function DashboardLayout({
</aside>

{/* Left Sidebar - Full (xl) */}
<aside className="hidden w-72 flex-shrink-0 flex-col border-r border-zinc-800 xl:flex">
<aside onWheel={forwardScroll} className="hidden w-72 flex-shrink-0 flex-col border-r border-zinc-800 xl:flex">
<div className="flex h-full flex-col">
{/* Challenge Header */}
<div className="p-4">
Expand Down Expand Up @@ -125,20 +130,20 @@ export function DashboardLayout({
</aside>

{/* Main Content - Scrollable */}
<main className="flex-1 overflow-y-auto scrollbar-hide pb-20 lg:pb-0">
<main ref={mainRef} className="flex-1 overflow-y-auto overscroll-contain scrollbar-hide pb-20 lg:pb-0">
<PaymentRequiredBanner challengeId={challenge.id} />
<AnnouncementBanner challengeId={challenge.id} />
{children}
</main>

{/* Right Sidebar - Fixed (xl only) */}
{!hideRightSidebar && (
<aside className="hidden w-96 flex-shrink-0 flex-col border-l border-zinc-800 xl:flex">
<aside onWheel={forwardScroll} className="hidden w-96 flex-shrink-0 flex-col border-l border-zinc-800 lg:flex">
<div className="p-4">
<UserSearch challengeId={challenge.id} />
</div>
<div className="flex-1 overflow-y-auto scrollbar-hide p-4">
<ChallengeSidebar challengeId={challenge.id} currentUserId={currentUserId} />
<ChallengeSidebar challengeId={challenge.id} currentUserId={currentUserId} challengeStartDate={challenge.startDate} />
</div>
</aside>
)}
Expand Down
26 changes: 13 additions & 13 deletions apps/web/components/dashboard/onboarding-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { Input } from "@/components/ui/input";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
Expand All @@ -21,6 +20,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { StravaConnectButton } from "@/components/integrations/strava-connect-button";
import { parseDateOnlyToUtcMs } from "@/lib/date-only";
import {
CheckCircle2,
UserPlus,
Expand All @@ -35,12 +35,22 @@ import {
ChevronUp,
} from "lucide-react";

function getOnboardingTitle(startDate: string): string {
const startMs = parseDateOnlyToUtcMs(startDate);
const now = Date.now();
const daysUntilStart = Math.ceil((startMs - now) / (1000 * 60 * 60 * 24));
if (daysUntilStart > 1) return `${daysUntilStart} days until the challenge`;
if (daysUntilStart === 1) return "Challenge starts tomorrow";
return "Getting started";
}

interface OnboardingCardProps {
challengeId: string;
userId: string;
challengeStartDate: string;
}

export function OnboardingCard({ challengeId, userId }: OnboardingCardProps) {
export function OnboardingCard({ challengeId, userId, challengeStartDate }: OnboardingCardProps) {
const [dismissed, setDismissed] = useState(false);
const [expandedStep, setExpandedStep] = useState<number | null>(null);

Expand Down Expand Up @@ -127,13 +137,6 @@ export function OnboardingCard({ challengeId, userId }: OnboardingCardProps) {
},
];

const completedCount = steps.filter((s) => s.complete).length;
// Don't count the invite step in total since it's never "complete"
const completableCount = steps.filter((s) => s.key !== "invite").length;
const completedCompletable = steps.filter(
(s) => s.key !== "invite" && s.complete
).length;

const toggleStep = (index: number) => {
setExpandedStep(expandedStep === index ? null : index);
};
Expand All @@ -143,10 +146,7 @@ export function OnboardingCard({ challengeId, userId }: OnboardingCardProps) {
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div>
<CardTitle className="text-base">Get ready for the challenge</CardTitle>
<CardDescription>
{completedCompletable}/{completableCount} steps completed
</CardDescription>
<CardTitle className="text-base">{getOnboardingTitle(challengeStartDate)}</CardTitle>
</div>
{allCompletableStepsDone && (
<Button
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import type * as mutations_payments from "../mutations/payments.js";
import type * as mutations_stravaWebhook from "../mutations/stravaWebhook.js";
import type * as mutations_templates from "../mutations/templates.js";
import type * as mutations_users from "../mutations/users.js";
import type * as mutations_webhookPayloads from "../mutations/webhookPayloads.js";
import type * as queries_achievements from "../queries/achievements.js";
import type * as queries_activities from "../queries/activities.js";
import type * as queries_activityTypes from "../queries/activityTypes.js";
Expand Down Expand Up @@ -141,6 +142,7 @@ declare const fullApi: ApiFromModules<{
"mutations/stravaWebhook": typeof mutations_stravaWebhook;
"mutations/templates": typeof mutations_templates;
"mutations/users": typeof mutations_users;
"mutations/webhookPayloads": typeof mutations_webhookPayloads;
"queries/achievements": typeof queries_achievements;
"queries/activities": typeof queries_activities;
"queries/activityTypes": typeof queries_activityTypes;
Expand Down