diff --git a/Dockerfile b/Dockerfile
index 99c74b8..3a2866a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,6 +12,9 @@ COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/lib ./lib
+COPY --from=builder /app/config ./config
+
+RUN mkdir -p /app/data
EXPOSE 3000
CMD ["node", "server.js"]
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
index 6bf2a80..c31ae9d 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -2,7 +2,6 @@ import MissionControl from '@/components/MissionControl';
import db from '@/lib/db';
export default function Home() {
- // SQLite handles the 'now' comparison perfectly
const issRow = db.prepare(`
SELECT pass_time, end_time
FROM iss_passes
@@ -10,26 +9,41 @@ export default function Home() {
ORDER BY datetime(pass_time) ASC LIMIT 1
`).get() as { pass_time: string, end_time: string } | undefined;
- // 2. Fetch Moon
const moonRow = db.prepare(`
SELECT title, event_time
FROM global_events
WHERE id = 'moon_phase'
`).get() as { title: string, event_time: string } | undefined;
- // Prepare Dates
+ const cosmicRow = db.prepare(`
+ SELECT title, event_time
+ FROM global_events
+ WHERE id = 'next_cosmic_event'
+ `).get() as { title: string, event_time: string } | undefined;
+
+ const launchRow = db.prepare(
+ "SELECT title, event_time FROM global_events WHERE id = 'next_launch'"
+ ).get() as { title: string, event_time: string } | undefined;
+
+ const launchStart = launchRow ? new Date(launchRow.event_time) : null;
+ const launchEnd = launchStart ? new Date(launchStart.getTime() + 7200000) : null;
+
const issStart = issRow ? new Date(issRow.pass_time) : null;
const issEnd = issRow ? new Date(issRow.end_time) : null;
const moonStart = moonRow ? new Date(moonRow.event_time) : null;
- // Moon "Event" lasts 24 hours
const moonEnd = moonStart ? new Date(moonStart.getTime() + 86400000) : null;
+ const cosmicStart = cosmicRow ? new Date(cosmicRow.event_time) : null;
+ const cosmicEnd = cosmicStart ? new Date(cosmicStart.getTime() + 14400000) : null;
+
return (
);
diff --git a/components/EventCard.tsx b/components/EventCard.tsx
index bec5998..7070051 100644
--- a/components/EventCard.tsx
+++ b/components/EventCard.tsx
@@ -112,20 +112,17 @@ export default function EventCard({ id, title, targetDate, endDate, icon }: Even
{parseInt(timeLeft.d) >= 2 ? (
- /* Case 1: More than 2 days - Keep it super simple */
<>
{timeLeft.d}
Days
>
) : parseInt(timeLeft.d) === 1 ? (
- /* Case 2: Between 24 and 48 hours - Show Day + Hours */
<>
01d
:
{timeLeft.h}h
>
) : (
- /* Case 3: Under 24 hours - The full high-precision clock */
<>
{timeLeft.h}
:
@@ -135,9 +132,18 @@ export default function EventCard({ id, title, targetDate, endDate, icon }: Even
>
)}
-
- {parseInt(timeLeft.d) > 0 ? "Until event" : "T-Minus to Horizon"}
+ {isLive ? (
+ "Mission in Progress"
+ ) : id === 'iss' ? (
+ "T-Minus to Horizon"
+ ) : id === 'launch' ? (
+ "T-Minus to Ignition"
+ ) : id === 'moon' ? (
+ "Until Lunar Phase"
+ ) : (
+ "Days to Peak Phase"
+ )}
>
)}
diff --git a/components/MissionControl.tsx b/components/MissionControl.tsx
index a27d312..2655646 100644
--- a/components/MissionControl.tsx
+++ b/components/MissionControl.tsx
@@ -12,10 +12,7 @@ const Starfield = dynamic(() => import('@/components/Starfield'), {
});
-export default function MissionControl({ iss, moon }: MissionControlProps) {
-
- const BASE_TIME = 1769610273000;
-
+export default function MissionControl({ iss, moon, cosmic, launch }: MissionControlProps) {
const events = useMemo(() => [
{
id: 'iss',
@@ -32,20 +29,20 @@ export default function MissionControl({ iss, moon }: MissionControlProps) {
icon: ,
},
{
- id: 'starlink',
- title: "Starlink Train",
- date: new Date(BASE_TIME + 1000 * 60 * 45),
- endDate: new Date(BASE_TIME + 1000 * 60 * 55),
- icon: ,
+ id: 'cosmic',
+ title: cosmic.title,
+ date: cosmic.start,
+ endDate: cosmic.end,
+ icon: ,
},
{
- id: 'meteor',
- title: "Meteor Shower",
- date: new Date(BASE_TIME + 1000 * 60 * 60 * 24 * 10),
- endDate: new Date(BASE_TIME + 1000 * 60 * 60 * 24 * 10.1),
- icon: ,
+ id: 'launch',
+ title: launch.title,
+ date: launch.start,
+ endDate: launch.end,
+ icon: ,
}
- ], [iss, moon]);
+ ], [iss, moon, cosmic, launch]);
return (
@@ -63,7 +60,7 @@ export default function MissionControl({ iss, moon }: MissionControlProps) {
{events.map((event) => (
0) {
- targetDate = new Date(now.getTime() + daysToFullMoon * 86400000);
- title = "Next Full Moon";
- } else {
- targetDate = new Date(now.getTime() + daysToNewMoon * 86400000);
- title = "Next New Moon";
+ const moon = daysToFull > 0
+ ? { title: "Next Full Moon", date: new Date(now.getTime() + daysToFull * 86400000) }
+ : { title: "Next New Moon", date: new Date(now.getTime() + daysToNew * 86400000) };
+
+ db.prepare(`
+ INSERT INTO global_events (id, title, event_time) VALUES ('moon_phase', ?, ?)
+ ON CONFLICT(id) DO UPDATE SET title=excluded.title, event_time=excluded.event_time
+ `).run(moon.title, moon.date.toISOString());
+
+ console.log(`🌙 Moon: ${moon.title}`);
+}
+
+function updateCosmicEvents() {
+ const configPath = path.resolve(process.cwd(), 'config/events.json');
+ if (!fs.existsSync(configPath)) return;
+
+ const events: CosmicEvent[] = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
+ const now = new Date();
+
+ const next = events
+ .filter((e) => new Date(e.event_time) > now)
+ .sort((a, b) => new Date(a.event_time).getTime() - new Date(b.event_time).getTime())[0];
+
+ if (next) {
+ db.prepare(`
+ INSERT INTO global_events (id, title, event_time) VALUES ('next_cosmic_event', ?, ?)
+ ON CONFLICT(id) DO UPDATE SET title=excluded.title, event_time=excluded.event_time
+ `).run(next.title, next.event_time);
+ console.log(`✨ Cosmic: ${next.title}`);
}
+}
- return { title, targetDate };
+async function updateRocketLaunches() {
+ try {
+ const nowISO = new Date().toISOString();
+ const url = `https://lldev.thespacedevs.com/2.2.0/launch/upcoming/?limit=1&mode=list&net__gt=${nowISO}`;
+
+ const response = await fetch(url);
+ const data = await response.json();
+
+ if (data.results && data.results.length > 0) {
+ const launch = data.results[0];
+
+ db.prepare(`
+ INSERT INTO global_events (id, title, event_time)
+ VALUES ('next_launch', ?, ?)
+ ON CONFLICT(id) DO UPDATE SET title=excluded.title, event_time=excluded.event_time
+ `).run(
+ `Launch: ${launch.name}`,
+ launch.net
+ );
+
+ console.log(`🚀 Rocket Sync: ${launch.name}`);
+ }
+ } catch (error) {
+ console.error("❌ Rocket Fetch Error:", error);
+ }
}
async function updateAllData() {
- await updateISSData();
+ console.log("📡 Starting Ground Station sync...");
- const moon = getNextMoonPhase();
- const upsertMoon = db.prepare(`
- INSERT INTO global_events (id, title, event_time)
- VALUES ('moon_phase', ?, ?)
- ON CONFLICT(id) DO UPDATE SET title=excluded.title, event_time=excluded.event_time
- `);
- upsertMoon.run(moon.title, moon.targetDate.toISOString());
-
- console.log(`🌙 Updated Moon Phase: ${moon.title}`);
+ try {
+ await updateISSData();
+ updateMoonPhase();
+ updateCosmicEvents();
+ await updateRocketLaunches();
+
+ console.log("✅ All systems synchronized.");
+ } catch (error) {
+ console.error("❌ Sync failed:", error);
+ }
}
updateAllData().catch(console.error);
diff --git a/types/space.ts b/types/space.ts
index 38b2d20..994b88d 100644
--- a/types/space.ts
+++ b/types/space.ts
@@ -32,4 +32,12 @@ export interface EventCardProps {
export interface MissionControlProps {
iss: { start: Date | null; end: Date | null };
moon: { title: string; start: Date | null; end: Date | null };
-}
\ No newline at end of file
+ cosmic: { title: string; start: Date | null; end: Date | null };
+ launch: { title: string; start: Date | null; end: Date | null };
+}
+
+export interface CosmicEvent {
+ id: string;
+ title: string;
+ event_time: string;
+}