Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

VITE_SUPABASE_URL=your-supabase-url
VITE_SUPABASE_ANON_KEY=your-supabase-anon-key
VITE_ANALYTICS_TABLE=analytics_events
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"detect:analytics-table": "node scripts/detect-analytics-table.mjs"
},
"dependencies": {
"@supabase/supabase-js": "^2.84.0",
Expand Down
77 changes: 77 additions & 0 deletions scripts/detect-analytics-table.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Detect which analytics/events table exists in Supabase.
*
* Usage:
* SUPABASE_URL="https://xxxx.supabase.co" SUPABASE_ANON_KEY="ey..." node scripts/detect-analytics-table.mjs
*
* Notes:
* - This does NOT insert any rows.
* - It performs a small SELECT against candidate tables via PostgREST.
*/
const supabaseUrl = process.env.SUPABASE_URL
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY

if (!supabaseUrl || !supabaseAnonKey) {
console.error('Missing SUPABASE_URL and/or SUPABASE_ANON_KEY in environment.')
process.exit(1)
}

const candidates = [
process.env.ANALYTICS_TABLE,
'client_analytics_events',
'analytics_events',
'events',
'event',
'tracking_events',
'client_events'
].filter(Boolean)

const request = async (table) => {
const url = new URL(`/rest/v1/${table}`, supabaseUrl)
url.searchParams.set('select', 'id')
url.searchParams.set('limit', '1')

const res = await fetch(url, {
method: 'GET',
headers: {
apikey: supabaseAnonKey,
Authorization: `Bearer ${supabaseAnonKey}`
}
})

return {
table,
status: res.status,
ok: res.ok
}
}

const results = []
for (const table of candidates) {
try {
results.push(await request(table))
} catch (e) {
results.push({ table, status: 'fetch_error', ok: false, error: e?.message })
}
}

for (const r of results) {
const exists =
r.status === 200 ||
r.status === 206 || // partial content
r.status === 401 ||
r.status === 403
? 'likely_exists'
: r.status === 404
? 'missing'
: 'unknown'

console.log(`${String(r.table).padEnd(24)} -> ${String(r.status).padEnd(10)} ${exists}`)
}

const best = results.find((r) => r.ok) || results.find((r) => r.status === 401 || r.status === 403)
if (best) {
console.log('\nSuggested config:')
console.log(`- VITE_ANALYTICS_TABLE=${best.table}`)
}

6 changes: 4 additions & 2 deletions src/anulunar_cosmic_quiz_flow.html
Original file line number Diff line number Diff line change
Expand Up @@ -609,8 +609,10 @@ <h3>🌙 Your Journey Continues</h3>

function calculateLifePath(dateStr) {
const [year, month, day] = dateStr.split('-').map(Number);
const sum = year + month + day;
return reduceToSingle(sum);
const yearSum = reduceToSingle(year);
const monthSum = reduceToSingle(month);
const daySum = reduceToSingle(day);
return reduceToSingle(yearSum + monthSum + daySum);
}

function updateProgress() {
Expand Down
6 changes: 4 additions & 2 deletions src/anulunar_intake_with_cosmic_logo.html
Original file line number Diff line number Diff line change
Expand Up @@ -644,8 +644,10 @@ <h3>Cycle Position</h3>

function calculateLifePath(dateStr) {
const [year, month, day] = dateStr.split('-').map(Number);
const sum = year + month + day;
return reduceToSingle(sum);
const yearSum = reduceToSingle(year);
const monthSum = reduceToSingle(month);
const daySum = reduceToSingle(day);
return reduceToSingle(yearSum + monthSum + daySum);
}

function getMoonPhase(date) {
Expand Down
35 changes: 34 additions & 1 deletion src/components/BirthDataForm.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { validateBirthData } from '../utils/cosmicBlueprint'

const STORAGE_KEY = 'anulunar.birthDataForm.v1'

const BirthDataForm = ({ onSubmit, loading = false }) => {
const [formData, setFormData] = useState({
firstName: '',
Expand All @@ -10,6 +12,32 @@ const BirthDataForm = ({ onSubmit, loading = false }) => {
birthPlace: ''
})
const [errors, setErrors] = useState({})
const hasHydratedRef = useRef(false)
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name 'hasHydratedRef' could be more descriptive. The term 'hydrated' is commonly used in React contexts for server-side rendering, but here it's simply tracking whether the initial localStorage read has completed. Consider renaming to 'hasLoadedFromStorageRef' or 'isInitializedRef' for clarity.

Copilot uses AI. Check for mistakes.

useEffect(() => {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (raw) {
const parsed = JSON.parse(raw)
if (parsed && typeof parsed === 'object') {
setFormData((prev) => ({ ...prev, ...parsed }))
}
}
} catch {
// ignore corrupt storage
} finally {
hasHydratedRef.current = true
}
}, [])

useEffect(() => {
if (!hasHydratedRef.current) return
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(formData))
} catch {
// ignore quota/blocked storage
}
}, [formData])
Comment on lines +33 to +40
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing personally identifiable information (PII) including firstName, lastName, birthDate, birthTime, and birthPlace in localStorage without encryption poses a privacy risk. This sensitive data persists in the browser and could be accessed by browser extensions or other scripts. Consider either: 1) warning users about the persistence, 2) using sessionStorage for shorter-lived persistence, or 3) implementing encryption before storage.

Copilot uses AI. Check for mistakes.

const handleChange = (e) => {
const { name, value } = e.target
Expand All @@ -30,6 +58,11 @@ const BirthDataForm = ({ onSubmit, loading = false }) => {
}

onSubmit(formData)
try {
localStorage.removeItem(STORAGE_KEY)
} catch {
// ignore
}
}

return (
Expand Down
9 changes: 6 additions & 3 deletions src/components/CosmicQuizFlow-themed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ const CosmicQuizFlow = () => {
G:7,P:7,Y:7,H:8,Q:8,Z:8,I:9,R:9
};
const vowels = ['A','E','I','O','U'];
const masterNumbers = [11, 22, 33];

const reduceToSingle = (num) => {
while (num > 9 && ![11,22,33].includes(num)) {
while (num > 9 && !masterNumbers.includes(num)) {
num = String(num).split('').reduce((a,b)=>a+Number(b),0);
}
return num;
Expand All @@ -33,8 +34,10 @@ const CosmicQuizFlow = () => {
};

const calculateLifePath = (date) => {
const [y,m,d] = date.split('-').map(Number);
return reduceToSingle(y+m+d);
if (!date) return null;
const [y, m, d] = date.split('-').map(Number);
if (![y, m, d].every(Number.isFinite)) return null;
return reduceToSingle(reduceToSingle(y) + reduceToSingle(m) + reduceToSingle(d));
};

const nextStep = () => {
Expand Down
17 changes: 14 additions & 3 deletions src/lib/supabase.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'https://placeholder.supabase.co'
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || 'placeholder-key'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY

export const supabase = createClient(supabaseUrl, supabaseAnonKey)
export const isSupabaseConfigured = Boolean(supabaseUrl && supabaseAnonKey)

if (!isSupabaseConfigured) {
console.warn(
'[supabase] Missing VITE_SUPABASE_URL or VITE_SUPABASE_ANON_KEY. Auth, saving, and analytics will be disabled until configured.'
)
}

export const supabase = createClient(
supabaseUrl || 'https://placeholder.supabase.co',
supabaseAnonKey || 'placeholder-key'
)
74 changes: 53 additions & 21 deletions src/pages/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,32 @@ import { useState, useEffect, useCallback } from 'react'
import { Link } from 'react-router-dom'
import { useAuth } from '../hooks/useAuth'
import { supabase } from '../lib/supabase'
import { getDailyMicroInsight, getWeeklyTheme, getMoonPhase } from '../utils/dailyGuidance'

const Dashboard = () => {
const { user } = useAuth()
const [reports, setReports] = useState([])
const [stats, setStats] = useState({ totalReports: 0, points: 0 })
const [loading, setLoading] = useState(true)
const [copied, setCopied] = useState(false)

const loadDashboardData = useCallback(async () => {
try {
// Load user reports
// Load user reports (larger schema)
const { data: reportsData, error: reportsError } = await supabase
.from('cosmic_reports')
.from('spiritual_reports')
.select('*')
.eq('user_id', user.id)
.eq('profile_id', user.id)
.order('created_at', { ascending: false })

if (reportsError) throw reportsError

setReports(reportsData || [])

// Load user profile/stats
const { data: profileData, error: profileError } = await supabase
.from('user_profiles')
.select('points')
.eq('user_id', user.id)
.single()

if (!profileError && profileData) {
setStats({
totalReports: reportsData?.length || 0,
points: profileData.points || 0
})
} else {
setStats({
totalReports: reportsData?.length || 0,
points: 100 // Default welcome points
})
}
setStats({
totalReports: reportsData?.length || 0,
points: 0
})
} catch (error) {
console.error('Error loading dashboard:', error)
} finally {
Expand All @@ -61,6 +49,29 @@ const Dashboard = () => {
)
}

const latestReport = reports?.[0]?.synthesized_content || reports?.[0]?.report_data
const firstName = user?.user_metadata?.first_name
const celticMoonSign = latestReport?.celticMoonSign?.sign
const sunSign = latestReport?.astrology?.sunSign
const lifePath = latestReport?.numerology?.lifePath?.number

const todayMessage = getDailyMicroInsight(
{ firstName, celticMoonSign, sunSign, lifePath },
new Date()
)
const { phaseName, theme } = getWeeklyTheme({ celticMoonSign }, new Date())
const { lunarDay } = getMoonPhase(new Date())

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(todayMessage)
Comment on lines +58 to +67
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dashboard component attempts to access nested properties from latestReport without proper null checking. If reports[0] exists but synthesized_content and report_data are both null/undefined, the subsequent property accesses (celticMoonSign?.sign, astrology?.sunSign, etc.) will work due to optional chaining, but this creates a scenario where getDailyMicroInsight is called with all undefined values, which may not be the intended behavior. Consider adding a check to ensure latestReport has valid data before using it.

Suggested change
const todayMessage = getDailyMicroInsight(
{ firstName, celticMoonSign, sunSign, lifePath },
new Date()
)
const { phaseName, theme } = getWeeklyTheme({ celticMoonSign }, new Date())
const { lunarDay } = getMoonPhase(new Date())
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(todayMessage)
// Determine if we have any meaningful report data before generating insights
const hasValidReportData =
!!latestReport &&
!!(
latestReport.celticMoonSign ||
latestReport.astrology ||
latestReport.numerology
)
const todayMessage = hasValidReportData
? getDailyMicroInsight(
{ firstName, celticMoonSign, sunSign, lifePath },
new Date()
)
: null
const { phaseName, theme } = hasValidReportData
? getWeeklyTheme({ celticMoonSign }, new Date())
: { phaseName: null, theme: null }
const { lunarDay } = getMoonPhase(new Date())
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(todayMessage || '')

Copilot uses AI. Check for mistakes.
setCopied(true)
window.setTimeout(() => setCopied(false), 1500)
} catch {
// ignore
}
}

return (
<div className="min-h-screen py-8 px-4">
<div className="container mx-auto max-w-6xl">
Expand All @@ -79,6 +90,27 @@ const Dashboard = () => {
</div>
</div>

{/* Daily Guidance */}
<div className="card mb-8">
<div className="flex items-start justify-between gap-4 flex-wrap">
<div className="min-w-[240px]">
<div className="text-sm font-semibold text-gray-300">Today’s guidance</div>
<div className="text-xs text-gray-500 mt-1">
{phaseName} • Lunar Day {lunarDay} • Weekly theme: {theme}
</div>
</div>
<button onClick={handleCopy} className="btn-secondary text-sm py-2 px-4">
{copied ? 'Copied' : 'Copy'}
</button>
</div>
<div className="mt-4 text-lg text-gray-200 leading-relaxed">
{todayMessage}
</div>
<div className="mt-3 text-xs text-gray-500">
Short beats daily. Deep reports live in your blueprints.
</div>
</div>

{/* Stats Grid */}
<div className="grid md:grid-cols-3 gap-6 mb-8">
<StatCard
Expand Down
Loading