Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0877420
feat(itinerary): overhaul page with EventCard, fix drag reorder, pass…
snackman May 2, 2026
906606a
feat: rename /itinerary to /plan with dropdown conference selector
snackman May 3, 2026
3f0ad8f
feat: add table view to /plan and center map on selected conference
snackman May 3, 2026
e98cd73
ui: remove Share Link button from /plan, keep PNG share only
snackman May 3, 2026
c17480f
feat: show distance from event on /plan page
snackman May 3, 2026
1d5b1ad
feat: share card flyer gallery with image proxy
snackman May 3, 2026
692713b
ui: square flyer tiles with title and time above image in share card
snackman May 3, 2026
c300f6b
ui: use explicit pixel sizes for share card gallery tiles
snackman May 3, 2026
67c55aa
ui: double flyer tile size on share card (2-column layout)
snackman May 3, 2026
c203719
ui: improve time readability on share card flyer tiles
snackman May 3, 2026
7885de3
feat: show friend avatars on itinerary event cards
snackman May 3, 2026
c09fc54
ui: show end times on share card flyer tiles
snackman May 3, 2026
fe05b7b
ui: remove schedule conflict indicators from itinerary
snackman May 3, 2026
636e1e9
feat: /plan page inherits view mode from main page
snackman May 3, 2026
f431eea
fix: onboarding wizard tag count uses 'any' match instead of 'all'
snackman May 3, 2026
ffcf299
ui: hide plan header title, back button navigates to active conference
snackman May 3, 2026
9b467d4
feat: add Google Calendar export button to /plan page
snackman May 3, 2026
152a9ae
ui: icon-only calendar button, conference dropdown next to back arrow…
snackman May 3, 2026
321a8b9
fix: initialize /plan view mode from localStorage immediately
snackman May 3, 2026
62cbeb2
fix: properly restore view mode from localStorage on /plan page
snackman May 3, 2026
ea62ecc
ui: larger link/copy button icons to match star button size
snackman May 3, 2026
001237b
ui: larger name and time labels on share card tiles for readability
snackman May 3, 2026
1fcfcff
feat: add Flyers/Text Only toggle to share card modal
snackman May 3, 2026
7b69e4f
fix: persist view mode change on /plan page to localStorage
snackman May 3, 2026
d3c8cd0
ui: match view toggle order on /plan to main view (Map/List/Table)
snackman May 3, 2026
8326b86
fix: pass friendsByEvent to TableView on /plan page
snackman May 3, 2026
fba7e4d
fix: apply theme-aware logo filter on /plan page for dark mode visibi…
snackman May 3, 2026
15028cc
ui: two-row header on /plan — logo top-left, profile top-right, confe…
snackman May 3, 2026
b978105
ui: use shared Header component on /plan with sponsors ticker + filte…
snackman May 3, 2026
7936110
ui: replace plan button with share + calendar buttons in /plan filter…
snackman May 3, 2026
c5751e3
ui: remove My Plan logo+label row above list cards
snackman May 3, 2026
96d51eb
refactor: use EventCard in map popup with compact mode
snackman May 3, 2026
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
60 changes: 60 additions & 0 deletions src/app/api/google-calendar/export/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { parseBody } from '@/lib/api-validation';
import { insertEvents } from '@/lib/google-calendar';

const GoogleCalendarExportSchema = z.object({
accessToken: z.string().min(1, 'Access token is required'),
events: z
.array(
z.object({
id: z.string(),
name: z.string(),
date: z.string(),
dateISO: z.string(),
startTime: z.string(),
endTime: z.string(),
isAllDay: z.boolean(),
organizer: z.string(),
address: z.string(),
cost: z.string(),
isFree: z.boolean(),
vibe: z.string(),
tags: z.array(z.string()),
conference: z.string(),
link: z.string(),
hasFood: z.boolean(),
hasBar: z.boolean(),
note: z.string(),
timeOfDay: z.enum(['morning', 'afternoon', 'evening', 'night', 'all-day']),
lat: z.number().optional(),
lng: z.number().optional(),
matchedAddress: z.string().optional(),
isDuplicate: z.boolean().optional(),
isFeatured: z.boolean().optional(),
})
)
.min(1, 'At least one event is required')
.max(200, 'Too many events (max 200)'),
timezone: z.string().min(1, 'Timezone is required'),
});

export async function POST(request: NextRequest) {
try {
const { data, error } = await parseBody(request, GoogleCalendarExportSchema);
if (error) return error;

const { accessToken, events, timezone } = data;

const result = await insertEvents(accessToken, events, timezone);

return NextResponse.json(result);
} catch (err) {
console.error('Google Calendar export error:', err);
const message = err instanceof Error ? err.message : 'Unknown error';
return NextResponse.json(
{ error: `Failed to export to Google Calendar: ${message}` },
{ status: 500 }
);
}
}
39 changes: 39 additions & 0 deletions src/app/api/image-proxy/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
const url = request.nextUrl.searchParams.get('url');
if (!url) {
return NextResponse.json({ error: 'Missing url parameter' }, { status: 400 });
}

try {
new URL(url);
} catch {
return NextResponse.json({ error: 'Invalid url' }, { status: 400 });
}

try {
const res = await fetch(url, {
signal: AbortSignal.timeout(10000),
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; sheeets-bot/1.0)',
},
});

if (!res.ok) {
return NextResponse.json({ error: 'Upstream fetch failed' }, { status: 502 });
}

const contentType = res.headers.get('content-type') || 'image/jpeg';
const buffer = await res.arrayBuffer();

return new NextResponse(buffer, {
headers: {
'Content-Type': contentType,
'Cache-Control': 'public, max-age=86400, s-maxage=86400',
},
});
} catch {
return NextResponse.json({ error: 'Proxy fetch failed' }, { status: 502 });
}
}
Loading