diff --git a/backend/main.py b/backend/main.py index 808c50a..1a0b16a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -223,6 +223,8 @@ def change_password(payload: ChangePasswordRequest, session: Session = Depends(g hashed_password = bcrypt.hashpw(payload.new_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') current_user.password_hash = hashed_password + if current_user.school == "BULK_REGISTERED": + current_user.school = None session.add(current_user) session.commit() return {"success": True, "message": "Password updated successfully"} @@ -273,6 +275,7 @@ async def bulk_register_students(file: UploadFile = File(...), session: Session tier=tier, student_class=models.ClassEnum.TWELFTH, medium=models.MediumEnum.ENGLISH, + school="BULK_REGISTERED", city="Unknown", state="Unknown" ) @@ -356,10 +359,8 @@ def register(payload: RegisterPayload, session: Session = Depends(get_session)): student_class = class_map.get(payload.student_class or "12", models.ClassEnum.TWELFTH) medium = medium_map.get(payload.medium or "English", models.MediumEnum.ENGLISH) - # Calculate trial_end based on admin settings - trial_end = None - if settings.enable_free_trial and settings.trial_duration_days > 0: - trial_end = datetime.utcnow() + timedelta(days=settings.trial_duration_days) + # Set trial_end directly to 15 days + trial_end = datetime.utcnow() + timedelta(days=15) new_user = models.User( name=payload.name, diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index 668e982..fa80736 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -12,6 +12,7 @@ import { StreakCounter } from "@/components/StreakCounter" import { AITutorChat } from "@/components/AI/AITutorChat" import { useActiveUser, DEMO_USERS, isDemoUser } from "@/hooks/useActiveUser" import { StreakOnboardingModal } from "@/components/Dashboard/StreakOnboardingModal" +import { BulkUserPasswordModal } from "@/components/Dashboard/BulkUserPasswordModal" import { Badge } from "@/components/ui/badge" import MaintenanceScreen from "@/components/MaintenanceScreen" @@ -160,14 +161,14 @@ export default function DashboardLayout({ children }: { children: ReactNode }) { active={pathname.startsWith("/dashboard/cbt")} isLocked={!hasAccessTo("weekly-test-series")} /> - } label="Daily Practice (DPP)" active={pathname.startsWith("/dashboard/dpp")} isLocked={!hasAccessTo("chapter-wise-progress-tracking")} - /> + /> */} - {trialInfo.trialDaysRemaining} Days Remaining out of {trialInfo.trialTotalDays} Days in Free Trial + 15 Days Free Trial - Day {trialInfo.trialDayNumber} )} diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx index c0891f9..9f01703 100644 --- a/src/app/dashboard/settings/page.tsx +++ b/src/app/dashboard/settings/page.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { useActiveUser } from "@/hooks/useActiveUser"; -import { User as UserIcon, Upload, CheckCircle2, Settings } from "lucide-react"; +import { User as UserIcon, Upload, CheckCircle2, Settings, Eye, EyeOff } from "lucide-react"; export default function SettingsPage() { const { userId, user, refreshUser, loading: userLoading } = useActiveUser(); @@ -74,6 +74,9 @@ export default function SettingsPage() { const [newPassword, setNewPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [passwordSaving, setPasswordSaving] = useState(false); + const [showCurrent, setShowCurrent] = useState(false); + const [showNew, setShowNew] = useState(false); + const [showConfirm, setShowConfirm] = useState(false); const handleChangePassword = async () => { if (newPassword !== confirmPassword) { diff --git a/src/components/Dashboard/BulkUserPasswordModal.tsx b/src/components/Dashboard/BulkUserPasswordModal.tsx new file mode 100644 index 0000000..659e0c5 --- /dev/null +++ b/src/components/Dashboard/BulkUserPasswordModal.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useState } from "react"; +import { Lock, Eye, EyeOff } from "lucide-react"; +import { fetchWithAuth } from "@/lib/apiClient"; +import { Button } from "@/components/ui/button"; + +interface BulkUserPasswordModalProps { + isOpen: boolean; + onSuccess: () => void; +} + +export function BulkUserPasswordModal({ isOpen, onSuccess }: BulkUserPasswordModalProps) { + const [currentPassword, setCurrentPassword] = useState(""); + const [newPassword, setNewPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [showCurrent, setShowCurrent] = useState(false); + const [showNew, setShowNew] = useState(false); + const [showConfirm, setShowConfirm] = useState(false); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(""); + + if (!isOpen) return null; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (newPassword !== confirmPassword) { + setError("New passwords do not match."); + return; + } + + setSaving(true); + setError(""); + try { + const res = await fetchWithAuth("/api/auth/change-password", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + current_password: currentPassword, + new_password: newPassword, + }), + }); + const data = await res.json(); + if (res.ok) { + onSuccess(); + } else { + setError("Failed: " + (data.detail || "Unknown error")); + } + } catch (err) { + setError("Network error. Could not update password."); + } finally { + setSaving(false); + } + }; + + return ( +
+
+
+
+ +
+

Set Your Password

+

+ Welcome! Since your account was created by the admin, please set a new personal password to continue. +

+
+ + {error &&

{error}

} + +
+
+ +
+ setCurrentPassword(e.target.value)} + className="w-full bg-secondary/50 border border-border rounded-lg px-4 py-2 pr-10 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai)]" + required + /> + +
+
+ +
+ +
+ setNewPassword(e.target.value)} + className="w-full bg-secondary/50 border border-border rounded-lg px-4 py-2 pr-10 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai)]" + required + minLength={8} + /> + +
+
+ +
+ +
+ setConfirmPassword(e.target.value)} + className="w-full bg-secondary/50 border border-border rounded-lg px-4 py-2 pr-10 text-sm focus:outline-none focus:ring-2 focus:ring-[var(--cyan-ai)]" + required + minLength={8} + /> + +
+
+ + +
+
+
+ ); +} diff --git a/src/hooks/useActiveUser.ts b/src/hooks/useActiveUser.ts index 9fddf6b..39fdc48 100644 --- a/src/hooks/useActiveUser.ts +++ b/src/hooks/useActiveUser.ts @@ -60,8 +60,8 @@ export function computeTrialInfo(user: UserProfile | null, globalSettings: any): if (!user || !globalSettings) return defaultResult; - const trialDays: number = globalSettings.trial_duration_days ?? 15; - const trialEnabled: boolean = globalSettings.enable_free_trial ?? true; + const trialDays: number = 15; + const trialEnabled: boolean = true; // Only apply trial logic to Free tier users (not already paid Pro/AI Coach) if (user.tier !== "Free") {