├── public ├── lastUpdated.json ├── brain.png ├── gssoc.png ├── dashboard.png ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── app ├── favicon.ico ├── (public) │ ├── layout.tsx │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ ├── sign-up │ │ └── [[...sign-up]] │ │ │ └── page.tsx │ ├── contact │ │ └── page.tsx │ ├── page.tsx │ ├── blogs │ │ ├── [slug] │ │ │ └── page.tsx │ │ └── page.tsx │ ├── terms-of-use │ │ └── page.tsx │ └── privacy-policy │ │ └── page.tsx ├── (authenticated_Pages) │ ├── layout.tsx │ └── dashboard │ │ ├── analytics │ │ └── error-boundary.tsx │ │ ├── achievements │ │ └── page.tsx │ │ ├── page.tsx │ │ ├── leaderboard │ │ └── page.tsx │ │ └── interview │ │ └── [[...id]] │ │ └── page.tsx ├── robots.ts ├── api │ ├── leaderboard │ │ └── route.ts │ ├── user │ │ ├── experience │ │ │ └── route.ts │ │ └── login │ │ │ └── route.ts │ ├── achievements │ │ └── route.ts │ ├── interview │ │ ├── history │ │ │ └── route.ts │ │ ├── quiz │ │ │ └── [quizId] │ │ │ │ └── route.ts │ │ ├── submit │ │ │ └── route.ts │ │ └── generate │ │ │ └── route.ts │ ├── topic │ │ └── [topicId] │ │ │ └── quiz-progress │ │ │ └── route.ts │ ├── create-user │ │ └── route.ts │ ├── performance-data │ │ └── route.ts │ ├── reminder-message │ │ └── route.ts │ ├── notes │ │ └── [topicId] │ │ │ └── route.ts │ ├── flashcard-export │ │ └── route.ts │ └── contact │ │ └── route.ts ├── sitemap.ts └── layout.tsx ├── screenshots ├── Home.jpg ├── Dashboard.jpg ├── GettingStarted.jpg ├── StudyReminders.jpg └── TopicWisenotes.jpg ├── postcss.config.mjs ├── types ├── note.ts ├── blog.ts └── index.ts ├── prisma └── migrations │ ├── migration_lock.toml │ ├── 20250804143210_add_contact_us │ └── migration.sql │ ├── 20250805060235_add_streak_and_lastlogin_to_user │ └── migration.sql │ ├── 20250804200842_user_login │ └── migration.sql │ ├── 20250820110304_add_level_system │ └── migration.sql │ ├── 20250414081049_add_cascade_delete │ └── migration.sql │ ├── 20250824075439_flashcard │ └── migration.sql │ ├── 20250909151106_analytics │ └── migration.sql │ └── 20250414072937_init │ └── migration.sql ├── lib ├── utils.ts ├── levelUtils.ts ├── cloudinary.ts ├── prepareMermaidCode.ts ├── twilio.ts ├── prisma.ts ├── contentful.ts ├── rateLimiter.ts ├── bufferToStream.ts ├── blog.ts └── processPrompt.ts ├── utils ├── generateMathCaptcha.ts ├── smooth-scroll.ts ├── generateMetadata.ts └── youtube.ts ├── components ├── ui │ ├── skeleton.tsx │ ├── sonner.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── progress.tsx │ ├── input.tsx │ ├── switch.tsx │ ├── checkbox.tsx │ ├── radio-group.tsx │ ├── badge.tsx │ ├── popover.tsx │ ├── tooltip.tsx │ ├── tabs.tsx │ ├── card.tsx │ ├── button.tsx │ ├── BackToTopButton.tsx │ ├── table.tsx │ └── radialcharttext.tsx ├── accessibility │ ├── index.ts │ ├── AriaLiveRegion.tsx │ ├── SkipLinks.tsx │ └── AccessibleProgressBar.tsx ├── mermaid │ └── mermaid.tsx ├── topic │ ├── TopicHeader.tsx │ ├── SearchBar.tsx │ ├── DeleteDialog.tsx │ ├── ResourceGrid.tsx │ ├── TopicModal.tsx │ ├── ResourceCard.tsx │ └── TopicQuizProgress.tsx ├── magicui │ ├── marquee.tsx │ └── number-ticker.tsx ├── landing │ ├── About.tsx │ ├── AnimatedImage.tsx │ ├── Hero.tsx │ ├── CTA.tsx │ ├── Testimonials.tsx │ ├── HowItWorks.tsx │ └── Features.tsx ├── dashboard │ ├── actionCell.tsx │ └── performanceCard.tsx ├── quiz │ ├── QuizQuestion.tsx │ └── QuizResult.tsx ├── auth │ └── SignInForm.tsx ├── authLeftPanel.tsx ├── blog │ └── BlogCard.tsx └── notes │ └── SimpleNoteEditor.tsx ├── .dockerignore ├── next.config.ts ├── components.json ├── docker-compose.yml ├── tsconfig.json ├── Dockerfile ├── hooks ├── useLocalStorageState.ts └── useTopic.ts ├── .gitignore ├── eslint.config.mjs ├── middleware.ts ├── LICENSE ├── testimonals └── reviews.ts ├── package.json ├── Contributors data.md └── .github └── workflows └── update-contributors.yml /public/lastUpdated.json: -------------------------------------------------------------------------------- 1 | {"lastUpdated":"2025-12-20T06:13:50.132Z"} 2 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/brain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/public/brain.png -------------------------------------------------------------------------------- /public/gssoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/public/gssoc.png -------------------------------------------------------------------------------- /public/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/public/dashboard.png -------------------------------------------------------------------------------- /screenshots/Home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/screenshots/Home.jpg -------------------------------------------------------------------------------- /screenshots/Dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/screenshots/Dashboard.jpg -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /screenshots/GettingStarted.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/screenshots/GettingStarted.jpg -------------------------------------------------------------------------------- /screenshots/StudyReminders.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/screenshots/StudyReminders.jpg -------------------------------------------------------------------------------- /screenshots/TopicWisenotes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vatsal-bhakodia/smriti-ai/HEAD/screenshots/TopicWisenotes.jpg -------------------------------------------------------------------------------- /types/note.ts: -------------------------------------------------------------------------------- 1 | export type Note = { 2 | id: string; 3 | content: string | null; 4 | topicId: string; 5 | userId: string; 6 | }; -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (e.g., Git) 3 | provider = "postgresql" 4 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /utils/generateMathCaptcha.ts: -------------------------------------------------------------------------------- 1 | export function generateMathCaptcha() { 2 | const a = Math.ceil(Math.random() * 10); 3 | const b = Math.ceil(Math.random() * 10); 4 | return { question: `${a} + ${b}`, answer: a + b }; 5 | } 6 | -------------------------------------------------------------------------------- /types/blog.ts: -------------------------------------------------------------------------------- 1 | export interface BlogPost { 2 | title: string; 3 | slug: string; 4 | featureImage: { 5 | url: string; 6 | description?: string; 7 | }; 8 | summary: string; 9 | content: any; // Rich text JSON 10 | author: string; 11 | publishDate: string; 12 | } 13 | -------------------------------------------------------------------------------- /app/(public)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from "@/components/Footer"; 2 | 3 | export default function PublicLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode; 7 | }) { 8 | return ( 9 |
10 | {children} 11 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /lib/levelUtils.ts: -------------------------------------------------------------------------------- 1 | export function getLevelAndTtile(experience: number){ 2 | if(experience < 50) return {level:1, title: "Beginner"}; 3 | if(experience < 150) return {level:2, title: "Intermediate"}; 4 | if(experience < 300) return {level:3, title: "Advanced"}; 5 | return {level:4, title: "Expert"}; 6 | } -------------------------------------------------------------------------------- /app/(authenticated_Pages)/layout.tsx: -------------------------------------------------------------------------------- 1 | import AuthGate from "@/components/AuthGate"; 2 | 3 | export default function RootLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode; 7 | }) { 8 | return ( 9 | 10 |
{children}
11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ) 11 | } 12 | 13 | export { Skeleton } 14 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | .next 4 | dist 5 | npm-debug.log 6 | yarn-debug.log 7 | yarn-error.log 8 | pnpm-debug.log 9 | 10 | 11 | .env.local 12 | .env.development 13 | .env.test 14 | .env.* 15 | 16 | .DS_Store 17 | Thumbs.db 18 | 19 | .git 20 | .gitignore 21 | 22 | Dockerfile* 23 | docker-compose* 24 | 25 | 26 | .vscode 27 | .idea 28 | *.swp 29 | -------------------------------------------------------------------------------- /lib/cloudinary.ts: -------------------------------------------------------------------------------- 1 | import { v2 as cloudinary } from 'cloudinary'; 2 | 3 | if (!cloudinary.config().cloud_name) { 4 | cloudinary.config({ 5 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME!, 6 | api_key: process.env.CLOUDINARY_API_KEY!, 7 | api_secret: process.env.CLOUDINARY_API_SECRET!, 8 | secure: true, 9 | }); 10 | } 11 | 12 | export default cloudinary; 13 | -------------------------------------------------------------------------------- /lib/prepareMermaidCode.ts: -------------------------------------------------------------------------------- 1 | export default function prepareMermaidCode({ code }: { code: string }) { 2 | // If the code includes triple backticks, extract just the mermaid content 3 | if (code.includes("```mermaid")) { 4 | code = code.replace(/```mermaid\n([\s\S]*?)```/g, "$1"); 5 | } 6 | 7 | // Handle any escaped newlines 8 | return code.replace(/\\n/g, "\n"); 9 | } 10 | -------------------------------------------------------------------------------- /lib/twilio.ts: -------------------------------------------------------------------------------- 1 | // lib/twilio.ts 2 | import { Twilio } from "twilio"; 3 | 4 | const accountSid = process.env.TWILIO_ACCOUNT_SID!; 5 | const authToken = process.env.TWILIO_AUTH_TOKEN!; 6 | 7 | if (!accountSid || !authToken) { 8 | throw new Error("Twilio credentials missing in env variables"); 9 | } 10 | 11 | export const twilioClient = new Twilio(accountSid, authToken); 12 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /prisma/migrations/20250804143210_add_contact_us/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "ContactQuery" ( 3 | "id" TEXT NOT NULL, 4 | "name" TEXT NOT NULL, 5 | "email" TEXT NOT NULL, 6 | "subject" TEXT, 7 | "message" TEXT NOT NULL, 8 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | 10 | CONSTRAINT "ContactQuery_pkey" PRIMARY KEY ("id") 11 | ); 12 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | remotePatterns: [ 6 | { protocol: "https", hostname: "images.ctfassets.net" }, 7 | { protocol: "https", hostname: "img.youtube.com" }, 8 | { protocol: "https", hostname: "via.placeholder.com" }, 9 | ], 10 | }, 11 | }; 12 | 13 | export default nextConfig; 14 | -------------------------------------------------------------------------------- /components/accessibility/index.ts: -------------------------------------------------------------------------------- 1 | // Accessibility components exports 2 | export { SkipLinks } from './SkipLinks'; 3 | export { AriaLiveRegion } from './AriaLiveRegion'; 4 | export { AccessibleProgressBar } from './AccessibleProgressBar'; 5 | 6 | // Future accessibility components can be added here 7 | // export { FocusTrap } from './FocusTrap'; 8 | // export { AccessibleModal } from './AccessibleModal'; 9 | // export { ScreenReaderAnnouncer } from './ScreenReaderAnnouncer'; 10 | -------------------------------------------------------------------------------- /components/mermaid/mermaid.tsx: -------------------------------------------------------------------------------- 1 | import mermaid from "mermaid"; 2 | import { useEffect } from "react"; 3 | 4 | mermaid.initialize({}); 5 | 6 | const Mermaid = ({ chart, id }: { chart: string; id: string }) => { 7 | useEffect(() => { 8 | document.getElementById(id)?.removeAttribute("data-processed"); 9 | mermaid.contentLoaded(); 10 | }, [chart, id]); 11 | 12 | return ( 13 |
14 | {chart} 15 |
16 | ); 17 | }; 18 | 19 | export default Mermaid; -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /app/robots.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from "next"; 2 | import { headers } from "next/headers"; 3 | 4 | export default async function robots(): Promise { 5 | const h = await headers(); 6 | const host = h.get("host") || "localhost:3000"; 7 | const protocol = host.startsWith("localhost") ? "http" : "https"; 8 | const BASE = `${protocol}://${host}`; 9 | 10 | return { 11 | rules: { 12 | userAgent: "*", 13 | allow: "/", 14 | }, 15 | sitemap: `${BASE}/sitemap.xml`, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | interface Global { 5 | prisma?: PrismaClient; 6 | } 7 | } 8 | 9 | const globalForPrisma = global as typeof global & { prisma?: PrismaClient }; 10 | const prisma = globalForPrisma.prisma || new PrismaClient(); 11 | if (process.env.NODE_ENV === "development") globalForPrisma.prisma = prisma; 12 | if (process.env.NODE_ENV === "development") 13 | (global as typeof global & { prisma?: PrismaClient }).prisma = prisma; 14 | 15 | export default prisma; 16 | -------------------------------------------------------------------------------- /prisma/migrations/20250805060235_add_streak_and_lastlogin_to_user/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the `UserLogin` table. If the table is not empty, all the data it contains will be lost. 5 | 6 | */ 7 | -- DropForeignKey 8 | ALTER TABLE "public"."UserLogin" DROP CONSTRAINT "UserLogin_userId_fkey"; 9 | 10 | -- AlterTable 11 | ALTER TABLE "public"."User" ADD COLUMN "currentStreak" INTEGER NOT NULL DEFAULT 0, 12 | ADD COLUMN "lastLogin" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; 13 | 14 | -- DropTable 15 | DROP TABLE "public"."UserLogin"; 16 | -------------------------------------------------------------------------------- /app/api/leaderboard/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { PrismaClient } from "@prisma/client"; 3 | 4 | const prisma = new PrismaClient(); 5 | 6 | export async function GET() { 7 | try { 8 | const users = await prisma.user.findMany({ 9 | orderBy: { points: "desc" }, 10 | take: 20, 11 | select: { id: true, username: true, points: true }, 12 | }); 13 | return NextResponse.json(users); 14 | } catch (error) { 15 | console.error("[LEADERBOARD_GET]", error); 16 | return new NextResponse("Internal Server Error", { status: 500 }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /components/topic/TopicHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Pencil } from "lucide-react"; 2 | 3 | interface TopicHeaderProps { 4 | topicName: string; 5 | onClick: () => void; 6 | } 7 | 8 | export default function TopicHeader({ topicName, onClick }: TopicHeaderProps) { 9 | return ( 10 |

14 | {topicName && ( 15 | 16 | )} 17 |

{topicName}

18 |

19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /app/api/user/experience/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { getAuth } from "@clerk/nextjs/server"; 3 | import prisma from "@/lib/prisma"; 4 | 5 | export async function GET(req: NextRequest) { 6 | const { userId } = getAuth(req); 7 | if (!userId) return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); 8 | 9 | const user = await prisma.user.findUnique({ where: { id: userId } }); 10 | if (!user) return NextResponse.json({ message: "User not found" }, { status: 404 }); 11 | 12 | return NextResponse.json({ experience: user.experience, level: user.level }); 13 | } 14 | -------------------------------------------------------------------------------- /types/index.ts: -------------------------------------------------------------------------------- 1 | // /types/index.ts 2 | 3 | export type ResourceType = "VIDEO" | "PDF" | "ARTICLE"; 4 | 5 | export interface ResourceItem { 6 | id: string; 7 | title: string; 8 | type: ResourceType; 9 | url: string; 10 | } 11 | 12 | // Frequency type 13 | export type Frequency = 14 | | { type: 'daily' } 15 | | { type: 'custom', days: number[] }; // 0 for Sunday, 1 for Monday, etc. 16 | 17 | export type StudyTime = { 18 | id: string; 19 | time: string; // e.g., "19:00" 20 | // Frequency type 21 | frequency: Frequency; 22 | // Notify-via methods 23 | method: 'site' | 'mail' | 'whatsapp'; 24 | isEnabled: boolean; 25 | }; -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | postgres: 5 | image: postgres:15 6 | restart: always 7 | environment: 8 | POSTGRES_USER: postgres 9 | POSTGRES_PASSWORD: postgres 10 | POSTGRES_DB: samadhan 11 | ports: 12 | - "5432:5432" 13 | volumes: 14 | - postgres-data:/var/lib/postgresql/data 15 | 16 | nextjs-app: 17 | build: . 18 | depends_on: 19 | - postgres 20 | ports: 21 | - "3000:3000" 22 | environment: 23 | DATABASE_URL: ${DATABASE_URL} 24 | DIRECT_URL: ${DIRECT_URL} 25 | env_file: 26 | - .env 27 | 28 | volumes: 29 | postgres-data: 30 | -------------------------------------------------------------------------------- /prisma/migrations/20250804200842_user_login/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE "public"."UserLogin" ( 3 | "id" TEXT NOT NULL, 4 | "userId" TEXT NOT NULL, 5 | "loginDate" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP, 6 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, 7 | 8 | CONSTRAINT "UserLogin_pkey" PRIMARY KEY ("id") 9 | ); 10 | 11 | -- CreateIndex 12 | CREATE UNIQUE INDEX "UserLogin_userId_loginDate_key" ON "public"."UserLogin"("userId", "loginDate"); 13 | 14 | -- AddForeignKey 15 | ALTER TABLE "public"."UserLogin" ADD CONSTRAINT "UserLogin_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE; 16 | -------------------------------------------------------------------------------- /components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner, ToasterProps } from "sonner" 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme() 8 | 9 | return ( 10 | 22 | ) 23 | } 24 | 25 | export { Toaster } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22-alpine AS deps 2 | WORKDIR /app 3 | COPY package.json package-lock.json* ./ 4 | RUN npm install 5 | 6 | FROM node:22-alpine AS builder 7 | WORKDIR /app 8 | COPY --from=deps /app/node_modules ./node_modules 9 | COPY . . 10 | 11 | COPY prisma ./prisma 12 | RUN npx prisma generate 13 | 14 | RUN npm run build 15 | 16 | 17 | FROM node:22-alpine AS runner 18 | WORKDIR /app 19 | 20 | ENV NODE_ENV=production 21 | 22 | 23 | COPY --from=builder /app/public ./public 24 | COPY --from=builder /app/.next ./.next 25 | COPY --from=builder /app/node_modules ./node_modules 26 | COPY --from=builder /app/package.json ./package.json 27 | COPY --from=builder /app/prisma ./prisma 28 | 29 | 30 | EXPOSE 3000 31 | 32 | CMD ["npm", "start"] 33 | -------------------------------------------------------------------------------- /app/(public)/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { generateMetadataUtil } from "@/utils/generateMetadata"; 2 | import SignInForm from "@/components/auth/SignInForm"; 3 | 4 | export const metadata = generateMetadataUtil({ 5 | title: "Sign In", 6 | description: "Sign in to your Smriti AI account to access personalized learning tools, flashcards, mind maps, and AI-powered study materials.", 7 | keywords: [ 8 | "Smriti AI login", 9 | "sign in", 10 | "user account", 11 | "AI learning platform", 12 | "study tools login", 13 | "flashcards access", 14 | "personalized learning", 15 | "AI study companion" 16 | ], 17 | url: "https://www.smriti.live/sign-in", 18 | }); 19 | 20 | export default function SignInPage() { 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /hooks/useLocalStorageState.ts: -------------------------------------------------------------------------------- 1 | // /hooks/useLocalStorageState.ts 2 | 3 | import { useState, useEffect } from 'react'; 4 | 5 | function getStorageValue(key: string, defaultValue: T): T { 6 | if (typeof window !== 'undefined') { 7 | const saved = localStorage.getItem(key); 8 | if (saved !== null) { 9 | return JSON.parse(saved); 10 | } 11 | } 12 | return defaultValue; 13 | } 14 | 15 | export function useLocalStorageState(key: string, defaultValue: T): [T, React.Dispatch>] { 16 | const [value, setValue] = useState(() => { 17 | return getStorageValue(key, defaultValue); 18 | }); 19 | 20 | useEffect(() => { 21 | localStorage.setItem(key, JSON.stringify(value)); 22 | }, [key, value]); 23 | 24 | return [value, setValue]; 25 | } -------------------------------------------------------------------------------- /lib/contentful.ts: -------------------------------------------------------------------------------- 1 | // Contentful client utility for fetching blog data 2 | import { createClient } from 'contentful'; 3 | 4 | const space = process.env.CONTENTFUL_SPACE_ID; 5 | const accessToken = process.env.CONTENTFUL_ACCESS_TOKEN; 6 | 7 | // Create a mock client for development if credentials are missing 8 | let contentfulClient: any; 9 | 10 | if (!space || !accessToken) { 11 | console.warn('Contentful credentials not found. Using mock client for development.'); 12 | // Mock client for development 13 | contentfulClient = { 14 | getEntries: () => Promise.resolve({ items: [] }), 15 | getEntry: () => Promise.resolve(null), 16 | }; 17 | } else { 18 | contentfulClient = createClient({ 19 | space, 20 | accessToken, 21 | }); 22 | } 23 | 24 | export { contentfulClient }; 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env 35 | .env.local 36 | .env.*.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | next-env.d.ts 44 | 45 | # clerk configuration (can include secrets) 46 | /.clerk/ 47 | 48 | bun.lockb 49 | bun.lock -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | 15 | { 16 | rules: { 17 | "@typescript-eslint/no-explicit-any": "off", 18 | "@typescript-eslint/no-unused-vars": "off", 19 | "react-hooks/exhaustive-deps": "off", 20 | "@typescript-eslint/no-non-null-asserted-optional-chain": "off", 21 | "react/no-unescaped-entities": "off", 22 | }, 23 | }, 24 | ]; 25 | 26 | export default eslintConfig; 27 | -------------------------------------------------------------------------------- /app/(public)/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { generateMetadataUtil } from "@/utils/generateMetadata"; 2 | import SignUpForm from "@/components/auth/SignUpForm"; 3 | 4 | export const metadata = generateMetadataUtil({ 5 | title: "Sign Up", 6 | description: "Create your free Smriti AI account and start learning smarter with AI-powered study tools, personalized learning paths, and intelligent flashcards.", 7 | keywords: [ 8 | "Smriti AI signup", 9 | "create account", 10 | "free registration", 11 | "AI study tools", 12 | "join Smriti AI", 13 | "learning platform signup", 14 | "AI flashcards", 15 | "personalized learning", 16 | "smart studying" 17 | ], 18 | url: "https://www.smriti.live/sign-up", 19 | }); 20 | 21 | export default function SignUpPage() { 22 | return ; 23 | } -------------------------------------------------------------------------------- /lib/rateLimiter.ts: -------------------------------------------------------------------------------- 1 | const submissions = new Map(); 2 | const WINDOW_MS = 60 * 60 * 1000; // 1 hour 3 | const MAX_SUBMISSIONS = 5; 4 | 5 | /** 6 | * Returns true if the IP is under the limit, 7 | * false if the IP has exceeded the rate limit. 8 | */ 9 | export function checkRateLimit(ip: string): boolean { 10 | const now = Date.now(); 11 | const entry = submissions.get(ip); 12 | 13 | if (!entry) { 14 | submissions.set(ip, { count: 1, firstAt: now }); 15 | return true; 16 | } 17 | 18 | if (now - entry.firstAt > WINDOW_MS) { 19 | // Window expired → reset 20 | submissions.set(ip, { count: 1, firstAt: now }); 21 | return true; 22 | } 23 | 24 | if (entry.count >= MAX_SUBMISSIONS) { 25 | return false; 26 | } 27 | 28 | entry.count++; 29 | return true; 30 | } 31 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 |