fix: demo name, trial flow, signup profile fields, docker pgbouncer/frontend
- page.tsx: hero mock shows 'Neha Verma' + 'AI Coach Plan' (was 'Demo Student') - backend/main.py: demo login maps to Neha Verma UUID (33333333-...) - backend/main.py: register endpoint stores exam/class/medium/school + trial_end from admin settings - backend/main.py: register checks allow_new_registrations setting - backend/main.py: admin student list includes trial_end field - useActiveUser.ts: rewrite trial logic using real created_at date - useActiveUser.ts: add computeTrialInfo(), isDemoUser() helpers + expose trialInfo from hook - useActiveUser.ts: hasAccessTo() now uses effectiveTier from trial logic - dashboard/layout.tsx: hide demo switcher for real users (only show for known demo accounts) - dashboard/layout.tsx: trial badge in top header (Day N/15 Trial · Xd left) - dashboard/layout.tsx: trial day shown in sidebar user card and dropdown - signup/page.tsx: wire step 2 selects (exam/class/medium/school) to state + send to backend - signup/page.tsx: success screen shows 'Day 1 of your Free Trial has started!' - userlist.txt: was empty directory causing pgbouncer mount failure; replaced with proper file - pgbouncer.ini: added DB credentials in connection string; fixed admin_users - Dockerfile: added ARG NEXT_PUBLIC_API_URL baked at build time (needed for standalone) - docker-compose.yml: frontend build arg http://backend:8000; pgbouncer healthcheck; backend waits for pgbouncer
This commit is contained in:
@@ -10,7 +10,7 @@ import { ThemeToggle } from "@/components/ThemeToggle"
|
||||
import { TierBadge } from "@/components/TierBadge"
|
||||
import { StreakCounter } from "@/components/StreakCounter"
|
||||
import { AITutorChat } from "@/components/AI/AITutorChat"
|
||||
import { useActiveUser, DEMO_USERS } from "@/hooks/useActiveUser"
|
||||
import { useActiveUser, DEMO_USERS, isDemoUser } from "@/hooks/useActiveUser"
|
||||
import { StreakOnboardingModal } from "@/components/Dashboard/StreakOnboardingModal"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import MaintenanceScreen from "@/components/MaintenanceScreen"
|
||||
@@ -20,7 +20,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { userId, user, globalSettings, switchUser, logout, hasAccessTo, loading } = useActiveUser();
|
||||
const { userId, user, globalSettings, switchUser, logout, hasAccessTo, loading, trialInfo } = useActiveUser();
|
||||
const [maintenance, setMaintenance] = useState(false);
|
||||
const [checkingMaintenance, setCheckingMaintenance] = useState(true);
|
||||
|
||||
@@ -253,17 +253,28 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
<div className="px-4 py-3 border-b border-border bg-secondary/30">
|
||||
<p className="text-sm font-bold text-foreground truncate">{user.name}</p>
|
||||
<p className="text-xs text-muted-foreground truncate">{user.email || user.phone}</p>
|
||||
{/* Trial info in dropdown */}
|
||||
{trialInfo.isTrialActive && (
|
||||
<div className="mt-1.5 flex items-center gap-1.5">
|
||||
<span className="inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded-full bg-primary/10 text-primary border border-primary/20">
|
||||
Day {trialInfo.trialDayNumber} of {trialInfo.trialTotalDays} Trial
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground">{trialInfo.trialDaysRemaining}d left</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-1.5">
|
||||
<Link href="/dashboard/settings" className="flex items-center gap-2 px-3 py-2 text-sm text-foreground hover:bg-secondary rounded-lg transition-colors">
|
||||
<User size={16} /> Edit Profile
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => switchUser(DEMO_USERS.AI_COACH.id)}
|
||||
className="w-full text-left flex items-center gap-2 px-3 py-2 text-sm text-foreground hover:bg-secondary rounded-lg transition-colors"
|
||||
>
|
||||
Reset Demo
|
||||
</button>
|
||||
{isDemoUser(userId) && (
|
||||
<button
|
||||
onClick={() => switchUser(DEMO_USERS.NEHA.id)}
|
||||
className="w-full text-left flex items-center gap-2 px-3 py-2 text-sm text-foreground hover:bg-secondary rounded-lg transition-colors"
|
||||
>
|
||||
Reset Demo
|
||||
</button>
|
||||
)}
|
||||
<div className="h-px bg-border my-1" />
|
||||
<button
|
||||
onClick={logout}
|
||||
@@ -285,7 +296,15 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-bold leading-none truncate">{user ? user.name.split(" ")[0] : "Loading..."}</p>
|
||||
<div className="mt-1"><TierBadge tier={user ? (user.tier === "Al_Coach" ? "AI Coach" : user.tier) : "Free"} /></div>
|
||||
<div className="mt-1">
|
||||
{trialInfo.isTrialActive ? (
|
||||
<span className="inline-flex items-center text-[10px] font-bold text-primary">
|
||||
Day {trialInfo.trialDayNumber}/{trialInfo.trialTotalDays} Trial
|
||||
</span>
|
||||
) : (
|
||||
<TierBadge tier={user ? (user.tier === "Al_Coach" ? "AI Coach" : user.tier) : "Free"} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronUp size={15} className={`text-muted-foreground transition-transform shrink-0 ${dropdownOpen ? "rotate-180" : ""}`} />
|
||||
@@ -307,6 +326,13 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
{targetExamLabel} Target
|
||||
</Badge>
|
||||
)}
|
||||
{/* Trial countdown badge — shown to trial users in top header */}
|
||||
{trialInfo.isTrialActive && (
|
||||
<Badge variant="outline" className="bg-primary/10 text-primary border-primary/20 font-bold px-2.5 py-0.5 hidden md:inline-flex gap-1">
|
||||
<span className="size-1.5 rounded-full bg-primary animate-pulse"></span>
|
||||
Day {trialInfo.trialDayNumber}/{trialInfo.trialTotalDays} Trial · {trialInfo.trialDaysRemaining}d left
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
@@ -317,20 +343,22 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* Profile switcher dropdown for testing */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground font-bold hidden lg:inline">Demo Switcher:</span>
|
||||
<select
|
||||
value={userId}
|
||||
onChange={(e) => switchUser(e.target.value)}
|
||||
className="bg-secondary border border-border text-foreground px-2.5 py-1.5 rounded-xl text-xs font-bold focus:ring-2 focus:ring-[#00F0FF] outline-none cursor-pointer hover:bg-secondary/80 transition-colors"
|
||||
>
|
||||
<option value={DEMO_USERS.NEHA.id}>Neha Verma (AI Coach)</option>
|
||||
<option value={DEMO_USERS.AI_COACH.id}>Rahul (AI Coach)</option>
|
||||
<option value={DEMO_USERS.PRO.id}>Priya (Pro Plan)</option>
|
||||
{/* <option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option> */}
|
||||
</select>
|
||||
</div>
|
||||
{/* Demo Switcher — only visible when logged in as a known demo account */}
|
||||
{isDemoUser(userId) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground font-bold hidden lg:inline">Demo:</span>
|
||||
<select
|
||||
value={userId}
|
||||
onChange={(e) => switchUser(e.target.value)}
|
||||
className="bg-secondary border border-border text-foreground px-2.5 py-1.5 rounded-xl text-xs font-bold focus:ring-2 focus:ring-[#00F0FF] outline-none cursor-pointer hover:bg-secondary/80 transition-colors"
|
||||
>
|
||||
<option value={DEMO_USERS.NEHA.id}>Neha Verma (AI Coach)</option>
|
||||
<option value={DEMO_USERS.AI_COACH.id}>Rahul (AI Coach)</option>
|
||||
<option value={DEMO_USERS.PRO.id}>Priya (Pro Plan)</option>
|
||||
{/* <option value={DEMO_USERS.FREE.id}>Amit (Free Plan)</option> */}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 border-l border-border pl-4">
|
||||
{/* <LanguageToggle /> */}
|
||||
|
||||
Reference in New Issue
Block a user