-
Notifications
You must be signed in to change notification settings - Fork 0
AnuLunar user experience refinement #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
1601ce9
42b90c8
0fe3137
992bb5e
2d7e0a8
a7c942e
49850c2
a33eeaa
339e4e0
17b75ff
2839903
e0725c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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}`) | ||
| } | ||
|
|
| 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: '', | ||
|
|
@@ -10,6 +12,32 @@ const BirthDataForm = ({ onSubmit, loading = false }) => { | |
| birthPlace: '' | ||
| }) | ||
| const [errors, setErrors] = useState({}) | ||
| const hasHydratedRef = useRef(false) | ||
|
|
||
| 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
|
||
|
|
||
| const handleChange = (e) => { | ||
| const { name, value } = e.target | ||
|
|
@@ -30,6 +58,11 @@ const BirthDataForm = ({ onSubmit, loading = false }) => { | |
| } | ||
|
|
||
| onSubmit(formData) | ||
| try { | ||
| localStorage.removeItem(STORAGE_KEY) | ||
| } catch { | ||
| // ignore | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
|
|
||
| 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' | ||
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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 { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 || '') |
There was a problem hiding this comment.
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.