diff --git a/public/favicon.ico b/public/favicon.ico index 60c702a..3ef68e9 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/logo.jpeg b/public/logo.jpeg new file mode 100644 index 0000000..f5d92b7 Binary files /dev/null and b/public/logo.jpeg differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..26cfb61 Binary files /dev/null and b/public/logo.png differ diff --git a/src/app/(protected)/settings/page.tsx b/src/app/(protected)/settings/page.tsx new file mode 100644 index 0000000..d72324e --- /dev/null +++ b/src/app/(protected)/settings/page.tsx @@ -0,0 +1,155 @@ +"use client"; + +import React, { useState } from "react"; +import { User, Lock, Trash2, X, Settings as SettingsIcon } from "lucide-react"; +import AccountDeletion from "~/modules/settings/ui/accountDeletion"; +import PasswordReset from "~/modules/settings/ui/passwordChange"; +import PersonalSettings from "~/modules/settings/ui/personalDetails"; + +type PersonalDetails = { + name: string; + email: string; + bio: string; +}; + +interface SettingsPageProps { + onClose?: () => void; +} + +export default function SettingsPage({ onClose }: SettingsPageProps) { + const [activeTab, setActiveTab] = useState< + "personal" | "password" | "danger" + >("personal"); + + const tabs = [ + { + id: "personal" as const, + label: "Personal Info", + icon: User, + description: "Manage your profile and preferences", + }, + { + id: "password" as const, + label: "Password", + icon: Lock, + description: "Update your password", + }, + { + id: "danger" as const, + label: "Account", + icon: Trash2, + description: "Delete your account", + }, + ]; + + // FIX: Changed 'any' to 'PersonalDetails' + const handleSavePersonalInfo = (data: PersonalDetails) => { + console.log("Saving personal info:", data); + // Add your API call here + }; + + const handlePasswordChange = async (data: { + currentPassword: string; + newPassword: string; + }) => { + console.log("Changing password"); + return true; + }; + + const handleDeleteAccount = async () => { + console.log("Deleting account"); + return true; + }; + + const handleExportData = () => { + console.log("Exporting user data"); + }; + + return ( +
+
+ {/* Header Section */} +
+
+
+
+ +
+

+ Settings +

+
+

+ Manage your account settings and preferences +

+
+ {onClose && ( + + )} +
+ +
+ {/* Sidebar Navigation */} +
+
+ {tabs.map((tab) => { + const Icon = tab.icon; + const isActive = activeTab === tab.id; + const isDanger = tab.id === "danger"; + + return ( + + ); + })} +
+
+ + {/* Main Content Area */} +
+ {activeTab === "personal" && ( + + )} + + {activeTab === "password" && ( + + )} + + {activeTab === "danger" && ( + + )} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 559c02f..6063211 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -11,7 +11,7 @@ import { cn } from "~/lib/utils"; const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); export const metadata: Metadata = { - title: "Create T3 App", + title: "ShikShya", description: "Generated by create-t3-app", icons: [{ rel: "icon", url: "/favicon.ico" }], }; diff --git a/src/components/layout/navbar.tsx b/src/components/layout/navbar.tsx index cc8c7b4..9eca986 100644 --- a/src/components/layout/navbar.tsx +++ b/src/components/layout/navbar.tsx @@ -16,6 +16,7 @@ import { cn } from "~/lib/utils"; import ProfileSidebar from "./sidebar"; import { useSession } from "~/lib/auth-client"; import { UserAvatar } from "../user-avatar"; +import Image from "next/image"; const navItems = [ { name: "Dashboard", path: "/dashboard", icon: LayoutDashboard }, @@ -60,15 +61,22 @@ export default function Navbar() { {/* Logo */} -
- - शि - + {/* Logo Icon */} +
+ Shikshya logo
- - Shikक्षya + + {/* Brand Text */} + + ShikShya diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx index ea8e261..1180b74 100644 --- a/src/components/layout/sidebar.tsx +++ b/src/components/layout/sidebar.tsx @@ -110,23 +110,7 @@ export default function ProfileSidebar({ Account Settings

-
- - Personal Details -
- - -
diff --git a/src/modules/dashboard/ui/components/calendar-widget.tsx b/src/modules/dashboard/ui/components/calendar-widget.tsx index d38d6bf..1474997 100644 --- a/src/modules/dashboard/ui/components/calendar-widget.tsx +++ b/src/modules/dashboard/ui/components/calendar-widget.tsx @@ -120,7 +120,7 @@ export default function CalendarWidget() {
- +
{highestStreak}
diff --git a/src/modules/dashboard/ui/components/mini-todo.tsx b/src/modules/dashboard/ui/components/mini-todo.tsx index e23c18f..d0002bb 100644 --- a/src/modules/dashboard/ui/components/mini-todo.tsx +++ b/src/modules/dashboard/ui/components/mini-todo.tsx @@ -76,7 +76,7 @@ export const MiniTodo = () => { if (!isMounted) return null; return ( -
+

@@ -84,7 +84,7 @@ export const MiniTodo = () => {

-
+
{ onChange={(e) => setInputValue(e.target.value)} onKeyDown={(e) => e.key === "Enter" && addTodo()} placeholder="Plan your next move..." - className="flex-1 rounded-xl border border-[#24283b] bg-[#0a0a0c] px-4 py-3 text-xs text-white placeholder-slate-600 transition-all focus:border-indigo-500/50 focus:outline-none" + className="flex-1 rounded-xl border border-[#24283b] bg-[#0a0a0c] px-4 py-3 text-base text-white placeholder-slate-600 transition-all focus:border-indigo-500/50 focus:outline-none" /> + ) : ( +
+ {/* Confirmation Input */} +
+
+

+ ⚠️ Are you absolutely sure? +

+

+ This action{" "} + + cannot be undone + + . This will permanently delete your + account and remove all your data from + our servers. +

+

+ Please type{" "} + + DELETE + {" "} + to confirm: +

+ { + setConfirmText(e.target.value); + setError(""); + }} + placeholder="Type DELETE" + className="w-full rounded-lg border-2 border-red-500/40 bg-slate-900/80 px-4 py-3 font-mono text-white placeholder-slate-500 transition-all focus:border-red-500/60 focus:ring-2 focus:ring-red-500/30 focus:outline-none" + /> + {error && ( +

+ + {error} +

+ )} +
+ + {/* Final Confirmation */} +
+ + +
+
+ + {/* Action Buttons */} +
+ + +
+
+ )} +
+ + {/* Help Text */} +
+

+ + Need help? + {" "} + If you're experiencing issues or have concerns, please{" "} + + contact our support team + {" "} + before deleting your account. We're here to help! +

+
+
+
+ ); +} diff --git a/src/modules/settings/ui/passwordChange.tsx b/src/modules/settings/ui/passwordChange.tsx new file mode 100644 index 0000000..69fb7c5 --- /dev/null +++ b/src/modules/settings/ui/passwordChange.tsx @@ -0,0 +1,401 @@ +"use client"; + +import React, { useState } from "react"; +import { Lock, Eye, EyeOff, Shield, Check, X, AlertCircle } from "lucide-react"; + +interface PasswordResetProps { + onPasswordChange?: (data: { + currentPassword: string; + newPassword: string; + }) => Promise; +} + +export default function PasswordReset({ + onPasswordChange, +}: PasswordResetProps) { + const [formData, setFormData] = useState({ + currentPassword: "", + newPassword: "", + confirmPassword: "", + }); + + const [showPasswords, setShowPasswords] = useState({ + current: false, + new: false, + confirm: false, + }); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + + // Password validation + const passwordRequirements = [ + { + label: "At least 8 characters", + test: (pwd: string) => pwd.length >= 8, + }, + { + label: "One uppercase letter", + test: (pwd: string) => /[A-Z]/.test(pwd), + }, + { + label: "One lowercase letter", + test: (pwd: string) => /[a-z]/.test(pwd), + }, + { label: "One number", test: (pwd: string) => /[0-9]/.test(pwd) }, + { + label: "One special character", + test: (pwd: string) => /[!@#$%^&*(),.?":{}|<>]/.test(pwd), + }, + ]; + + const handleInputChange = (field: string, value: string) => { + setFormData((prev) => ({ ...prev, [field]: value })); + setError(""); + setSuccess(false); + }; + + const togglePasswordVisibility = (field: "current" | "new" | "confirm") => { + setShowPasswords((prev) => ({ ...prev, [field]: !prev[field] })); + }; + + const validatePassword = () => { + if (!formData.currentPassword) { + setError("Please enter your current password"); + return false; + } + if (!formData.newPassword) { + setError("Please enter a new password"); + return false; + } + if (formData.newPassword === formData.currentPassword) { + setError("New password must be different from current password"); + return false; + } + if ( + !passwordRequirements.every((req) => req.test(formData.newPassword)) + ) { + setError("New password does not meet all requirements"); + return false; + } + if (formData.newPassword !== formData.confirmPassword) { + setError("Passwords do not match"); + return false; + } + return true; + }; + + const handleSubmit = async () => { + if (!validatePassword()) return; + + setIsSubmitting(true); + setError(""); + + try { + const result = await onPasswordChange?.({ + currentPassword: formData.currentPassword, + newPassword: formData.newPassword, + }); + + if (result !== false) { + setSuccess(true); + setFormData({ + currentPassword: "", + newPassword: "", + confirmPassword: "", + }); + setTimeout(() => setSuccess(false), 5000); + } else { + setError("Current password is incorrect"); + } + } catch (err) { + setError("Failed to update password. Please try again."); + } finally { + setIsSubmitting(false); + } + }; + + const allRequirementsMet = passwordRequirements.every((req) => + req.test(formData.newPassword) + ); + const passwordsMatch = + formData.newPassword && + formData.confirmPassword && + formData.newPassword === formData.confirmPassword; + + return ( +
+
+ {/* Header */} +
+

+ Change Password +

+

+ Update your password to keep your account secure +

+
+ + {/* Success Message */} + {success && ( +
+ +
+

+ Password updated successfully! +

+

+ Your password has been changed. Please use your + new password to login. +

+
+
+ )} + + {/* Error Message */} + {error && ( +
+ +
+

{error}

+
+
+ )} + + {/* Form Fields */} +
+ {/* Current Password */} +
+ +
+ + handleInputChange( + "currentPassword", + e.target.value + ) + } + className="w-full rounded-xl border border-slate-700/50 bg-slate-800/50 px-4 py-3 pr-12 text-white placeholder-slate-500 transition-all focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/50 focus:outline-none" + placeholder="Enter your current password" + /> + +
+
+ + {/* New Password */} +
+ +
+ + handleInputChange( + "newPassword", + e.target.value + ) + } + className="w-full rounded-xl border border-slate-700/50 bg-slate-800/50 px-4 py-3 pr-12 text-white placeholder-slate-500 transition-all focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/50 focus:outline-none" + placeholder="Enter your new password" + /> + +
+
+ + {/* Confirm New Password */} +
+ +
+ + handleInputChange( + "confirmPassword", + e.target.value + ) + } + className={`w-full rounded-xl border bg-slate-800/50 px-4 py-3 pr-12 text-white placeholder-slate-500 transition-all focus:ring-2 focus:outline-none ${ + formData.confirmPassword && !passwordsMatch + ? "border-red-500/50 focus:border-red-500/50 focus:ring-red-500/50" + : formData.confirmPassword && + passwordsMatch + ? "border-green-500/50 focus:border-green-500/50 focus:ring-green-500/50" + : "border-slate-700/50 focus:border-indigo-500/50 focus:ring-indigo-500/50" + }`} + placeholder="Confirm your new password" + /> + +
+ {formData.confirmPassword && ( +
+ {passwordsMatch ? ( + <> + +

+ Passwords match +

+ + ) : ( + <> + +

+ Passwords do not match +

+ + )} +
+ )} +
+
+ + {/* Password Requirements */} +
+
+ +
+

+ Password Requirements +

+

+ Your password must meet all the following + criteria: +

+
+
+ +
+ {passwordRequirements.map((req, index) => { + const isMet = req.test(formData.newPassword); + return ( +
+
+ {isMet ? ( + + ) : ( + + )} +
+ + {req.label} + +
+ ); + })} +
+
+ + {/* Security Tip */} +
+
+ +
+

+ Security Tip +

+

+ Use a unique password that you don't use + anywhere else. Consider using a password manager + to generate and store strong passwords. +

+
+
+
+ + {/* Action Buttons */} +
+ + +
+
+
+ ); +} diff --git a/src/modules/settings/ui/personalDetails.tsx b/src/modules/settings/ui/personalDetails.tsx new file mode 100644 index 0000000..4288b98 --- /dev/null +++ b/src/modules/settings/ui/personalDetails.tsx @@ -0,0 +1,151 @@ +"use client"; + +import React, { useState } from "react"; +import { User, Mail, Camera, Save } from "lucide-react"; + +type PersonalDetails = { + name: string; + email: string; + bio: string; +}; + +interface PersonalSettingsProps { + initialData?: PersonalDetails; + onSave?: (data: PersonalDetails) => void; +} +export default function PersonalSettings({ + initialData, + onSave, +}: PersonalSettingsProps) { + const [formData, setFormData] = useState({ + name: initialData?.name ?? "Shikshya", + email: initialData?.email ?? "shikshya@example.com", + bio: initialData?.bio ?? "Learning enthusiast", + }); + + const [isSaving, setIsSaving] = useState(false); + + const handleInputChange = (field: string, value: string) => { + setFormData((prev) => ({ ...prev, [field]: value })); + }; + + const handleSave = () => { + setIsSaving(true); + setTimeout(() => { + setIsSaving(false); + onSave?.(formData); + }, 1000); + }; + + return ( +
+
+ {/* Header */} +
+

+ Personal Information +

+

+ Update your profile details and information +

+
+ + {/* Profile Picture */} +
+
+
+ {formData.name.charAt(0).toUpperCase()} +
+ +
+
+

+ Profile Photo +

+
+
+ + {/* Form Fields */} +
+
+ + + handleInputChange("name", e.target.value) + } + className="w-full rounded-xl border border-slate-700/50 bg-slate-800/50 px-4 py-3 text-white placeholder-slate-500 transition-all focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/50 focus:outline-none" + placeholder="Enter your full name" + /> +
+ +
+ +
+ + handleInputChange("email", e.target.value) + } + className="w-full rounded-xl border border-slate-700/50 bg-slate-800/50 px-4 py-3 text-white placeholder-slate-500 transition-all focus:border-indigo-500/50 focus:ring-2 focus:ring-indigo-500/50 focus:outline-none" + placeholder="your.email@example.com" + /> +
+

+ We'll send verification to this email +

+
+ +
+ +