├── .gitignore
├── README.md
├── app
├── (auth)
│ ├── sign-in
│ │ └── [[...sign-in]]
│ │ │ └── page.jsx
│ └── sign-up
│ │ └── [[...sign-up]]
│ │ └── page.jsx
├── dashboard
│ ├── _components
│ │ ├── AddNewInterview.jsx
│ │ ├── Footer.jsx
│ │ ├── Header.jsx
│ │ ├── InterviewItemCard.jsx
│ │ └── Interviewlist.jsx
│ ├── interview
│ │ └── [interviewId]
│ │ │ ├── feedback
│ │ │ └── page.jsx
│ │ │ ├── page.jsx
│ │ │ └── start
│ │ │ ├── _compnents
│ │ │ ├── QuestionsSections.jsx
│ │ │ └── RecordAnswerSection.jsx
│ │ │ └── page.jsx
│ ├── layout.jsx
│ └── page.jsx
├── favicon.ico
├── globals.css
├── layout.js
└── page.js
├── components.json
├── components
└── ui
│ ├── alert.jsx
│ ├── button.jsx
│ ├── collapsible.jsx
│ ├── dialog.jsx
│ ├── input.jsx
│ ├── sonner.jsx
│ └── textarea.jsx
├── drizzle.config.js
├── jsconfig.json
├── lib
└── utils.js
├── middleware.js
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── Agreement-rafiki.png
├── Recommendation letter-bro.svg
├── Webinar-rafiki.svg
├── logo.svg
├── next.svg
├── vercel.svg
└── webcam.png
├── tailwind.config.js
└── utils
├── GeminiAIModel.js
├── db.js
└── schema.js
/.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.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | .vercel
39 | .env*.local
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🤖 AI Interview Mocker
2 |
3 | Welcome to **AI Interview Mocker**, an innovative platform designed to help you ace your interviews! Powered by the advanced **Gemini API**, we generate realistic and tailored mock interview questions to enhance your preparation. 🌟 Take the next step toward success by practicing with our unique tools today!
4 |
5 | ## 🚀 Key Features:
6 |
7 | - **✨ User-Friendly Interface**: Sign in or sign up effortlessly through our sleek landing page.
8 | - **📊 Dashboard**: Manage and track all your mock interviews in one place.
9 | - **📝 Custom Interview Setup**: Add new interviews with detailed job descriptions.
10 | - **💡 AI-Generated Questions**: Get 5 custom-tailored interview questions based on your inputs.
11 | - **🎙️ Interactive Interview Process**: View questions, choose to read or listen, and begin your mock interview.
12 | - **📹 Record Your Answers**: Seamlessly record and submit your responses.
13 | - **📈 Comprehensive Feedback**: Receive detailed feedback and ratings from **GenerativeAI**.
14 | - **📂 Review Past Interviews**: Easily access and track your previous interview sessions.
15 |
16 | ---
17 |
18 | ## 📸 Screenshots
19 |
20 | Take a look at some of the key areas of our platform:
21 |
22 | ### Landing Page
23 |
24 |
25 | ### Dashboard
26 |
27 |
28 | ### Interview Setup
29 |
30 |
31 | ### Feedback Section
32 |
33 |
34 | ---
35 |
36 | Prepare confidently, improve continuously, and ace your next interview with **AI Interview Mocker**! 🎯
37 |
--------------------------------------------------------------------------------
/app/(auth)/sign-in/[[...sign-in]]/page.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { SignIn,useAuth } from "@clerk/nextjs";
3 | import { useRouter } from "next/navigation";
4 | import { useEffect } from "react";
5 |
6 | export default function Page() {
7 | const {isSignedIn}=useAuth();
8 | const router=useRouter()
9 | useEffect(()=>{
10 | if(isSignedIn){
11 | router.push("/dashboard")
12 | }
13 | },[isSignedIn])
14 | return (
15 |
16 |
17 |
18 |
23 |
24 |
25 |
26 | Home
27 |
38 |
39 |
40 |
41 | Welcome to AI Interview Mocker 🦑
42 |
43 |
44 |
45 | Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eligendi
46 | nam dolorum aliquam, quibusdam aperiam voluptatum.
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
58 | Home
59 |
70 |
71 |
72 |
73 | Welcome to AI Interview Mocker 🦑
74 |
75 |
76 |
77 | Lorem, ipsum dolor sit amet consectetur adipisicing elit.
78 | Eligendi nam dolorum aliquam, quibusdam aperiam voluptatum.
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | );
91 | }
92 |
--------------------------------------------------------------------------------
/app/(auth)/sign-up/[[...sign-up]]/page.jsx:
--------------------------------------------------------------------------------
1 | import { SignUp } from "@clerk/nextjs";
2 |
3 | export default function Page() {
4 | return (
5 |
6 |
7 |
8 |
15 |
16 |
19 |
20 |
21 | Home
22 |
33 |
34 |
35 |
36 | Welcome to AI Interview Mocker 🦑
37 |
38 |
39 |
40 | Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eligendi nam dolorum aliquam,
41 | quibusdam aperiam voluptatum.
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | )
51 | }
--------------------------------------------------------------------------------
/app/dashboard/_components/AddNewInterview.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogDescription,
7 | DialogHeader,
8 | DialogTitle,
9 | DialogTrigger,
10 | } from "@/components/ui/dialog";
11 | import { Button } from "@/components/ui/button";
12 | import { Input } from "@/components/ui/input";
13 | import { Textarea } from "@/components/ui/textarea";
14 | import { chatSession } from "@/utils/GeminiAIModel";
15 | import { LoaderCircle } from "lucide-react";
16 | import { db } from "@/utils/db";
17 | import { MockInterview } from "@/utils/schema";
18 | import { v4 as uuidv4 } from "uuid";
19 | import { useUser } from "@clerk/nextjs";
20 | import moment from "moment/moment";
21 | import { useRouter } from "next/navigation";
22 |
23 | function AddNewInterview() {
24 | const [openDialog, setOpenDialog] = useState(false);
25 | const [jobPosition, setJobPosition] = useState();
26 | const [jobDesc, setJobDesc] = useState();
27 | const [jobExperience, setJobExperience] = useState();
28 | const [loading, setLoading] = useState(false);
29 | const [JsonResponse, setJsonResponse] = useState([]);
30 | const { user } = useUser();
31 | const route=useRouter()
32 | const onSubmit = async (e) => {
33 | setLoading(true);
34 | e.preventDefault();
35 |
36 | const InputPromt = `Generate ${process.env.NEXT_PUBLIC_INTERVIEW_QUESTION} interview questions and answers in JSON format based on the following: Job Position: ${jobPosition}, Job Description: ${jobDesc}, Years of Experience: ${jobExperience}. Only return the JSON, without any additional text.`;
37 | const result = await chatSession.sendMessage(InputPromt);
38 | const MockJsonResp = result.response
39 | .text()
40 | .replace("```json", "")
41 | .replace("```", "");
42 | //console.log(JSON.parse(MockJsonResp))
43 | setJsonResponse(JSON.parse(MockJsonResp));
44 | if(MockJsonResp){
45 | const resp = await db.insert(MockInterview).values({
46 | mockId: uuidv4(),
47 | jsonMockResp: MockJsonResp,
48 | jobPosition: jobPosition,
49 | jobDesc: jobDesc,
50 | jobExperience: jobExperience,
51 | createdBy: user?.primaryEmailAddress?.emailAddress,
52 | createdAt: moment().format("DD-MM-yyyy"),
53 | }).returning({mockId:MockInterview.mockId})
54 | console.log("Insert ID:", resp)
55 | if(resp){
56 | route.push('/dashboard/interview/'+resp[0].mockId)
57 | setOpenDialog(false)
58 | }
59 |
60 |
61 | }else{
62 | console.log("ERROR")
63 | }
64 |
65 | setLoading(false);
66 | console.log(JsonResponse)
67 |
68 |
69 |
70 |
71 | };
72 |
73 | return (
74 |
75 |
setOpenDialog(true)}
78 | >
79 |
+ Add new
80 |
81 |
148 |
149 | );
150 | }
151 |
152 | export default AddNewInterview;
153 |
--------------------------------------------------------------------------------
/app/dashboard/_components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function Footer() {
4 | return (
5 |
125 | );
126 | }
127 |
128 | export default Footer;
129 |
--------------------------------------------------------------------------------
/app/dashboard/_components/Header.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { usePathname, useRouter } from 'next/navigation';
3 | import React, { useEffect } from 'react'
4 | import Image from 'next/image'
5 | import { UserButton } from '@clerk/nextjs'
6 |
7 | function Header() {
8 | const path = usePathname();
9 | const router =useRouter()
10 | useEffect(()=>{
11 | console.log(path)
12 | },[])
13 | function getRoutLink(path){
14 | router.push(path)
15 | }
16 |
17 |
18 | return (
19 |
20 |
21 |
22 |
23 | - getRoutLink('/dashboard')} className={`hover:text-primary hover:font-bold transition-all cursor-pointer ${path=='/dashboard'&&'text-primary font-bold'}`}>Dashboard
24 |
25 | - Questions
26 | - Upgrade
27 | - How it works?
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default Header
--------------------------------------------------------------------------------
/app/dashboard/_components/InterviewItemCard.jsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/ui/button'
2 |
3 | import { useRouter } from 'next/navigation'
4 | import React from 'react'
5 |
6 | function InterviewItemCard({interviewInfo}) {
7 | const router=useRouter()
8 | const onStart=()=>{
9 | console.log("first")
10 | router.push(`/dashboard/interview/${interviewInfo?.mockId}`)
11 | }
12 | const onFeedback=()=>{
13 | router.push(`/dashboard/interview/${interviewInfo.mockId}/feedback`)
14 | }
15 | return (
16 |
17 |
{interviewInfo?.jobPosition}
18 |
{interviewInfo?.jobExperience} Years of Experience
19 |
Created At: {interviewInfo.createdAt}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default InterviewItemCard
--------------------------------------------------------------------------------
/app/dashboard/_components/Interviewlist.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { db } from '@/utils/db';
3 | import { MockInterview } from '@/utils/schema';
4 | import { useUser } from '@clerk/nextjs'
5 | import { desc, eq } from 'drizzle-orm';
6 | import React, { useEffect, useState } from 'react'
7 | import InterviewItemCard from './InterviewItemCard';
8 |
9 | function Interviewlist() {
10 | const {user}=useUser();
11 | const [interviewList,setInterviewList]=useState([])
12 | useEffect(()=>{
13 | user&& GetInterviewList()
14 | },[user])
15 | const GetInterviewList=async()=>{
16 | const result=await db.select().from(MockInterview).where(eq(MockInterview.createdBy,user?.primaryEmailAddress?.emailAddress)).orderBy(desc(MockInterview.id))
17 | setInterviewList(result)
18 | }
19 | return (
20 |
21 |
Previous Mock Interview
22 |
23 | {
24 | interviewList&&interviewList.map((interview,index)=>(
25 | ))
26 | }
27 |
28 |
29 | )
30 | }
31 |
32 | export default Interviewlist
--------------------------------------------------------------------------------
/app/dashboard/interview/[interviewId]/feedback/page.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { db } from "@/utils/db";
3 | import { UserAnswer } from "@/utils/schema";
4 | import { eq } from "drizzle-orm";
5 | import React, { useEffect, useState } from "react";
6 | import {
7 | Collapsible,
8 | CollapsibleContent,
9 | CollapsibleTrigger,
10 | } from "@/components/ui/collapsible"
11 | import { ChevronsUpDownIcon } from "lucide-react";
12 | import { Button } from "@/components/ui/button";
13 | import { useRouter } from "next/navigation";
14 |
15 |
16 | function Feedback({ params }) {
17 | const [feedbackList, setFeedbackList] = useState([]);
18 | const [avgRating,setAvgRating]=useState()
19 | const router=useRouter()
20 | useEffect(() => {
21 | GetFeedBack();
22 | }, []);
23 | const GetFeedBack = async () => {
24 | const result = await db
25 | .select()
26 | .from(UserAnswer)
27 | .where(eq(UserAnswer.mockIdRef, params.interviewId))
28 | .orderBy(UserAnswer.id);
29 |
30 | setFeedbackList(result);
31 | let getTotalOfRating=result.reduce((sum,item)=>sum+Number(item.rating),0)
32 | setAvgRating(Math.round(getTotalOfRating/result?.length))
33 | // setAvgRating(result.reduce((sum, item) => sum + Number(item.rating), 0) / result.length)
34 | // console.log(avgRating)
35 | };
36 | return (
37 |
38 |
39 | {
40 | feedbackList?.length==0?
No Interview Feedback Record Found
:
41 | <>
42 |
Congratulation!
43 |
Here is your interview feedback
44 |
45 | Your overall interview rating {avgRating}/10
46 |
47 |
48 |
49 | Find blow interview question with correct answer, Your answer and
50 | feedback for improvement{" "}
51 |
52 |
53 | {feedbackList&&feedbackList.map((item,index)=>(
54 |
55 | {item.question}
56 |
57 |
58 |
Rating: {item.rating}
59 | Your Answer: {item.userAns}
60 | Correct Answer: {item.correctAns}
61 | Feedback: {item.feedback}
62 |
63 |
64 |
65 |
66 |
67 | ))}
68 | >
69 | }
70 |
71 |
72 |
73 | );
74 | }
75 |
76 | export default Feedback;
77 |
--------------------------------------------------------------------------------
/app/dashboard/interview/[interviewId]/page.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Button } from "@/components/ui/button";
3 | import { db } from "@/utils/db";
4 | import { MockInterview } from "@/utils/schema";
5 | import { eq } from "drizzle-orm";
6 | import { Lightbulb, WebcamIcon } from "lucide-react";
7 | import Link from "next/link";
8 | import React, { useEffect, useState } from "react";
9 | import Webcam from "react-webcam";
10 |
11 | function Interview({ params }) {
12 | const [interviewData, setInterviewData] = useState();
13 | const [webCamEnabled, setWebCamEnabled] = useState(false);
14 | useEffect(() => {
15 | GetInterviewDetail();
16 | }, []);
17 |
18 | /**
19 | * Used to Get Interview Details by MockId/Interview Id
20 | */
21 |
22 | const GetInterviewDetail = async () => {
23 | const result = await db
24 | .select()
25 | .from(MockInterview)
26 | .where(eq(MockInterview.mockId, params.interviewId));
27 | console.log(result);
28 | setInterviewData(result[0]);
29 | };
30 | return (
31 |
32 |
Let's Get Started
33 |
34 |
35 |
36 |
37 | Job Role/Job Position: {interviewData?.jobPosition}{" "}
38 |
39 |
40 | Job Description/Job Description:{" "}
41 | {interviewData?.jobDesc}{" "}
42 |
43 |
44 | Years of Experince: {interviewData?.jobExperience}{" "}
45 |
46 |
47 |
48 |
Information
49 | {process.env.NEXT_PUBLIC_INFORMATION}
50 |
51 |
52 |
53 | {webCamEnabled ? (
54 | setWebCamEnabled(true)}
58 | onUserMediaError={() => setWebCamEnabled(false)}
59 | />
60 | ) : (
61 | <>
62 |
63 |
66 | >
67 | )}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | );
81 | }
82 |
83 | export default Interview;
84 |
--------------------------------------------------------------------------------
/app/dashboard/interview/[interviewId]/start/_compnents/QuestionsSections.jsx:
--------------------------------------------------------------------------------
1 | import { Lightbulb, Volume2 } from 'lucide-react'
2 | import React from 'react'
3 |
4 | function QuestionsSections({activeQuestionIndex,mockInterViewQuestion}) {
5 |
6 | const textToSpeach=(text)=>{
7 | if('speechSynthesis' in window){
8 | const speech= new SpeechSynthesisUtterance(text);
9 | window.speechSynthesis.speak(speech)
10 | }else{
11 | alert('Sorry, Your browser does not sport text to speech (recommended browser Chrome)')
12 | }
13 |
14 | }
15 | return mockInterViewQuestion&&(
16 |
17 |
18 | {mockInterViewQuestion&&mockInterViewQuestion?.map((question,index)=>(
19 |
Question #{index+1}
20 | ))}
21 |
22 |
23 | Q. {mockInterViewQuestion[activeQuestionIndex]?.question}
24 |
25 |
textToSpeach(mockInterViewQuestion[activeQuestionIndex]?.question)} />
26 |
27 |
28 |
29 | Note:
30 |
31 |
32 | {process.env.NEXT_PUBLIC_QUESTION_NOTE}
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default QuestionsSections
--------------------------------------------------------------------------------
/app/dashboard/interview/[interviewId]/start/_compnents/RecordAnswerSection.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useEffect, useState } from "react";
3 | import Image from "next/image";
4 | import Webcam from "react-webcam";
5 | import { Button } from "@/components/ui/button";
6 | import useSpeechToText from "react-hook-speech-to-text";
7 | import { Mic, StopCircle } from "lucide-react";
8 | import { toast } from "sonner";
9 | import { chatSession } from "@/utils/GeminiAIModel";
10 | import { db } from "@/utils/db";
11 | import { UserAnswer } from "@/utils/schema";
12 | import { useUser } from "@clerk/nextjs";
13 | import moment from "moment";
14 |
15 | function RecordAnswerSection({ activeQuestionIndex, mockInterViewQuestion,interviewData }) {
16 | const [userAnswer, setUserAnswer] = useState("");
17 | const [loading,setLoading]=useState(false)
18 | const {user}=useUser()
19 | const {
20 | error,
21 | interimResult,
22 | isRecording,
23 | results,
24 | startSpeechToText,
25 | stopSpeechToText,
26 | setResults
27 | } = useSpeechToText({
28 | continuous: true,
29 | useLegacyResults: false,
30 | });
31 | if (error) {
32 | toast(error);
33 | return;
34 | }
35 |
36 | useEffect(() => {
37 | results.map((result) => {
38 | setUserAnswer((prevAns) => prevAns + result?.transcript);
39 | });
40 | }, [results]);
41 |
42 | const StartStopRecording = async () => {
43 |
44 | if (isRecording) {
45 |
46 |
47 | stopSpeechToText();
48 |
49 |
50 |
51 |
52 | } else {
53 | startSpeechToText();
54 | }
55 | };
56 |
57 | useEffect(()=>{
58 | if(!isRecording&&userAnswer.length>10){
59 | UpdateUserAnswerInDb();
60 | }
61 | // if (userAnswer?.length < 10) {
62 | // setLoading(false)
63 | // toast("Error while saving your answer, Please record again");
64 | // return;
65 | // }
66 |
67 | },[userAnswer])
68 |
69 | const UpdateUserAnswerInDb=async()=>{
70 | console.log(userAnswer)
71 | setLoading(true);
72 | const feedbackPromt = `Question: ${mockInterViewQuestion[activeQuestionIndex]?.question}, User Answer: ${userAnswer}. Based on the question and the user's answer, please provide a rating 1 to 10 for the answer and feedback in the form of areas for improvement, if any. The feedback should in JSON format only nothing else field should be rating and feeback only, in just 3 to 5 lines.`;
73 | const result = await chatSession.sendMessage(feedbackPromt);
74 | const mockJsonResp = result.response
75 | .text()
76 | .replace("```json", "")
77 | .replace("```", "");
78 |
79 | const JsonFeedbackResp=JSON.parse(mockJsonResp)
80 | const resp=await db.insert(UserAnswer).values({
81 | mockIdRef: interviewData?.mockId,
82 | question:mockInterViewQuestion[activeQuestionIndex]?.question,
83 | correctAns:mockInterViewQuestion[activeQuestionIndex]?.answer,
84 | userAns:userAnswer,
85 | feedback:JsonFeedbackResp?.feedback,
86 | rating:JsonFeedbackResp?.rating,
87 | userEmail:user?.primaryEmailAddress?.emailAddress,
88 | createdAt:moment().format('DD-MM-yyyy')
89 |
90 |
91 | })
92 |
93 | if(resp){
94 |
95 | toast('User Answer recorder successfully!')
96 | setUserAnswer('')
97 | setResults([])
98 | }
99 | setResults([])
100 | setLoading(false)
101 | }
102 |
103 | return (
104 |
105 |
106 |
112 |
120 |
121 |
134 |
135 | );
136 | }
137 |
138 | export default RecordAnswerSection;
139 |
--------------------------------------------------------------------------------
/app/dashboard/interview/[interviewId]/start/page.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { db } from "@/utils/db";
3 | import { MockInterview } from "@/utils/schema";
4 | import { eq } from "drizzle-orm";
5 | import React, { useEffect, useState } from "react";
6 | import QuestionsSections from "./_compnents/QuestionsSections";
7 | import RecordAnswerSection from "./_compnents/RecordAnswerSection";
8 | import { Button } from "@/components/ui/button";
9 | import Link from "next/link";
10 |
11 | function StartInterview({ params }) {
12 | const [interviewData, setInterviewData] = useState();
13 | const [mockInterviewQuestion, setMockInterviewQuestion] = useState();
14 | const [activeQuestionIndex, setActiveQuestionIndex] = useState(0);
15 | useEffect(() => {
16 | GetInterviewDetail();
17 | }, []);
18 |
19 | /**
20 | * Used to Get Interview Details by MockId/Interview Id
21 | */
22 |
23 | const GetInterviewDetail = async () => {
24 | const result = await db
25 | .select()
26 | .from(MockInterview)
27 | .where(eq(MockInterview.mockId, params.interviewId));
28 |
29 | const jsonMockResp = JSON.parse(result[0]?.jsonMockResp);
30 |
31 | setMockInterviewQuestion(jsonMockResp);
32 |
33 | setInterviewData(result[0]);
34 | };
35 | return (
36 |
37 |
38 | {/* Questions */}
39 |
43 | {/* Video/ Audio Recording */}
44 |
49 |
50 |
51 |
52 | {activeQuestionIndex > 0 && }
53 |
54 | {activeQuestionIndex !==
55 | mockInterviewQuestion?.length - 1 && }
56 |
57 | {activeQuestionIndex == mockInterviewQuestion?.length - 1 && (
58 |
59 |
60 |
61 |
62 | )}
63 |
64 |
65 | );
66 | }
67 |
68 | export default StartInterview;
69 |
--------------------------------------------------------------------------------
/app/dashboard/layout.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "./_components/Header";
3 | import Footer from "./_components/Footer";
4 |
5 | function DashboardLayout({ children }) {
6 | return (
7 |
8 |
9 |
{children}
10 |
11 |
12 | );
13 | }
14 |
15 | export default DashboardLayout;
16 |
--------------------------------------------------------------------------------
/app/dashboard/page.jsx:
--------------------------------------------------------------------------------
1 | import { UserButton } from '@clerk/nextjs'
2 | import React from 'react'
3 | import AddNewInterview from './_components/AddNewInterview'
4 | import Interviewlist from './_components/Interviewlist'
5 |
6 | function Dashboard() {
7 | return (
8 |
9 |
Dashboard
10 |
Create and Start AI Mockup Interview
11 |
12 |
15 | {/* Previous Interview list */}
16 |
17 |
18 | )
19 | }
20 |
21 | export default Dashboard
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SHARUN266/AI-mock-interview/6a8d5acebe92398ef0f5e48f0b7302223a394b9f/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 0 0% 3.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 0 0% 3.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 0 0% 3.9%;
13 | --primary: 0 0% 9%;
14 | --primary-foreground: 0 0% 98%;
15 | --secondary: 0 0% 96.1%;
16 | --secondary-foreground: 0 0% 9%;
17 | --muted: 0 0% 96.1%;
18 | --muted-foreground: 0 0% 45.1%;
19 | --accent: 0 0% 96.1%;
20 | --accent-foreground: 0 0% 9%;
21 | --destructive: 0 84.2% 60.2%;
22 | --destructive-foreground: 0 0% 98%;
23 | --border: 0 0% 89.8%;
24 | --input: 0 0% 89.8%;
25 | --ring: 0 0% 3.9%;
26 | --radius: 0.5rem;
27 | --chart-1: 12 76% 61%;
28 | --chart-2: 173 58% 39%;
29 | --chart-3: 197 37% 24%;
30 | --chart-4: 43 74% 66%;
31 | --chart-5: 27 87% 67%;
32 | }
33 |
34 | .dark {
35 | --background: 0 0% 3.9%;
36 | --foreground: 0 0% 98%;
37 | --card: 0 0% 3.9%;
38 | --card-foreground: 0 0% 98%;
39 | --popover: 0 0% 3.9%;
40 | --popover-foreground: 0 0% 98%;
41 | --primary: 0 0% 98%;
42 | --primary-foreground: 0 0% 9%;
43 | --secondary: 0 0% 14.9%;
44 | --secondary-foreground: 0 0% 98%;
45 | --muted: 0 0% 14.9%;
46 | --muted-foreground: 0 0% 63.9%;
47 | --accent: 0 0% 14.9%;
48 | --accent-foreground: 0 0% 98%;
49 | --destructive: 0 62.8% 30.6%;
50 | --destructive-foreground: 0 0% 98%;
51 | --border: 0 0% 14.9%;
52 | --input: 0 0% 14.9%;
53 | --ring: 0 0% 83.1%;
54 | --chart-1: 220 70% 50%;
55 | --chart-2: 160 60% 45%;
56 | --chart-3: 30 80% 55%;
57 | --chart-4: 280 65% 60%;
58 | --chart-5: 340 75% 55%;
59 | }
60 | }
61 |
62 | @layer base {
63 | * {
64 | @apply border-border;
65 | }
66 | body {
67 | @apply bg-background text-foreground;
68 | }
69 | }
--------------------------------------------------------------------------------
/app/layout.js:
--------------------------------------------------------------------------------
1 | import { Inter } from "next/font/google";
2 | import "./globals.css";
3 | import { ClerkProvider } from "@clerk/nextjs";
4 | import { Toaster } from "sonner";
5 |
6 | const inter = Inter({ subsets: ["latin"] });
7 |
8 | export const metadata = {
9 | title: "Mock Interview App",
10 | description: "Generated by create next app",
11 | };
12 |
13 | export default function RootLayout({ children }) {
14 | return (
15 |
16 |
17 |
18 |
23 | {/* Google Analytics Script */}
24 |
28 |
37 |
38 |
39 |
40 | {children}
41 |
42 |
43 |
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/app/page.js:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import { Button } from "@/components/ui/button"
3 | import Header from "./dashboard/_components/Header";
4 | import Link from "next/link";
5 |
6 |
7 | export default function Home() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | {/* Start Landing Page */}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Prepare for Your Next Interview
22 |
Practice with mock interviews and receive detailed feedback to understand where you stand.
23 |
24 |
25 |
26 |
27 |
28 |
29 |

30 |
31 |
32 |
33 |
34 |
35 | {/* End Landing Page */}
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": false,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
--------------------------------------------------------------------------------
/components/ui/alert.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva } from "class-variance-authority";
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef(({ className, variant, ...props }, ref) => (
23 |
28 | ))
29 | Alert.displayName = "Alert"
30 |
31 | const AlertTitle = React.forwardRef(({ className, ...props }, ref) => (
32 |
36 | ))
37 | AlertTitle.displayName = "AlertTitle"
38 |
39 | const AlertDescription = React.forwardRef(({ className, ...props }, ref) => (
40 |
44 | ))
45 | AlertDescription.displayName = "AlertDescription"
46 |
47 | export { Alert, AlertTitle, AlertDescription }
48 |
--------------------------------------------------------------------------------
/components/ui/button.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
37 | const Comp = asChild ? Slot : "button"
38 | return (
39 | ()
43 | );
44 | })
45 | Button.displayName = "Button"
46 |
47 | export { Button, buttonVariants }
48 |
--------------------------------------------------------------------------------
/components/ui/collapsible.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/components/ui/dialog.jsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = DialogPrimitive.Portal
14 |
15 | const DialogClose = DialogPrimitive.Close
16 |
17 | const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
18 |
25 | ))
26 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
27 |
28 | const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (
29 |
30 |
31 |
38 | {children}
39 |
41 |
42 | Close
43 |
44 |
45 |
46 | ))
47 | DialogContent.displayName = DialogPrimitive.Content.displayName
48 |
49 | const DialogHeader = ({
50 | className,
51 | ...props
52 | }) => (
53 |
56 | )
57 | DialogHeader.displayName = "DialogHeader"
58 |
59 | const DialogFooter = ({
60 | className,
61 | ...props
62 | }) => (
63 |
66 | )
67 | DialogFooter.displayName = "DialogFooter"
68 |
69 | const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
70 |
74 | ))
75 | DialogTitle.displayName = DialogPrimitive.Title.displayName
76 |
77 | const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
78 |
82 | ))
83 | DialogDescription.displayName = DialogPrimitive.Description.displayName
84 |
85 | export {
86 | Dialog,
87 | DialogPortal,
88 | DialogOverlay,
89 | DialogClose,
90 | DialogTrigger,
91 | DialogContent,
92 | DialogHeader,
93 | DialogFooter,
94 | DialogTitle,
95 | DialogDescription,
96 | }
97 |
--------------------------------------------------------------------------------
/components/ui/input.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Input = React.forwardRef(({ className, type, ...props }, ref) => {
6 | return (
7 | ()
15 | );
16 | })
17 | Input.displayName = "Input"
18 |
19 | export { Input }
20 |
--------------------------------------------------------------------------------
/components/ui/sonner.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useTheme } from "next-themes"
3 | import { Toaster as Sonner } from "sonner"
4 |
5 | const Toaster = ({
6 | ...props
7 | }) => {
8 | const { theme = "system" } = useTheme()
9 |
10 | return (
11 | ()
26 | );
27 | }
28 |
29 | export { Toaster }
30 |
--------------------------------------------------------------------------------
/components/ui/textarea.jsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Textarea = React.forwardRef(({ className, ...props }, ref) => {
6 | return (
7 | ()
14 | );
15 | })
16 | Textarea.displayName = "Textarea"
17 |
18 | export { Textarea }
19 |
--------------------------------------------------------------------------------
/drizzle.config.js:
--------------------------------------------------------------------------------
1 | /** @type { import("drizzle-kit").Config } */
2 | export default {
3 | schema: "./utils/schema.js",
4 | dialect: 'postgresql',
5 | dbCredentials: {
6 | url: 'postgresql://neondb_owner:3tULYM2vKqVb@ep-ancient-lab-a1ip24ct.ap-southeast-1.aws.neon.tech/MockInterviewTool?sslmode=require',
7 | }
8 | };
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./*"]
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | import { clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/middleware.js:
--------------------------------------------------------------------------------
1 | import { clerkMiddleware ,createRouteMatcher} from "@clerk/nextjs/server";
2 |
3 |
4 | const isProtectedRoute = createRouteMatcher([
5 | '/dashboard(.*)',
6 | '/forum(.*)',
7 | ]);
8 |
9 | export default clerkMiddleware((auth, req) => {
10 | if (isProtectedRoute(req)) auth().protect();
11 | });
12 | export const config = {
13 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
14 | };
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ai-interview-mocker",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "db:push": "npx drizzle-kit push",
11 | "db:studio": "npx drizzle-kit studio"
12 | },
13 | "dependencies": {
14 | "@clerk/nextjs": "^5.2.2",
15 | "@google/generative-ai": "^0.14.1",
16 | "@neondatabase/serverless": "^0.9.4",
17 | "@radix-ui/react-collapsible": "^1.1.0",
18 | "@radix-ui/react-dialog": "^1.1.1",
19 | "@radix-ui/react-slot": "^1.1.0",
20 | "add": "^2.0.6",
21 | "class-variance-authority": "^0.7.0",
22 | "clsx": "^2.1.1",
23 | "collapsible": "^1.0.1",
24 | "drizzle-orm": "^0.32.0",
25 | "i": "^0.3.7",
26 | "lucide-react": "^0.407.0",
27 | "moment": "^2.30.1",
28 | "next": "14.2.5",
29 | "next-themes": "^0.3.0",
30 | "npm": "^10.8.2",
31 | "npx": "^10.2.2",
32 | "react": "^18.3.1",
33 | "react-dom": "^18.3.1",
34 | "react-hook-speech-to-text": "^0.8.0",
35 | "react-webcam": "^7.2.0",
36 | "shadcn-ui": "^0.8.0",
37 | "sonner": "^1.5.0",
38 | "tailwind-merge": "^2.4.0",
39 | "tailwindcss-animate": "^1.0.7",
40 | "uuid": "^10.0.0"
41 | },
42 | "devDependencies": {
43 | "drizzle-kit": "^0.23.0",
44 | "postcss": "^8",
45 | "tailwindcss": "^3.4.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/Agreement-rafiki.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SHARUN266/AI-mock-interview/6a8d5acebe92398ef0f5e48f0b7302223a394b9f/public/Agreement-rafiki.png
--------------------------------------------------------------------------------
/public/Recommendation letter-bro.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/Webinar-rafiki.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/public/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/webcam.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SHARUN266/AI-mock-interview/6a8d5acebe92398ef0f5e48f0b7302223a394b9f/public/webcam.png
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{js,jsx}',
6 | './components/**/*.{js,jsx}',
7 | './app/**/*.{js,jsx}',
8 | './src/**/*.{js,jsx}',
9 | ],
10 | prefix: "",
11 | theme: {
12 | container: {
13 | center: true,
14 | padding: "2rem",
15 | screens: {
16 | "2xl": "1400px",
17 | },
18 | },
19 | extend: {
20 | colors: {
21 | border: "hsl(var(--border))",
22 | input: "hsl(var(--input))",
23 | ring: "hsl(var(--ring))",
24 | background: "hsl(var(--background))",
25 | foreground: "hsl(var(--foreground))",
26 | primary: {
27 | DEFAULT: "#4845D2",
28 | foreground: "hsl(var(--primary-foreground))",
29 | },
30 | secondary: {
31 | DEFAULT: "hsl(var(--secondary))",
32 | foreground: "hsl(var(--secondary-foreground))",
33 | },
34 | destructive: {
35 | DEFAULT: "hsl(var(--destructive))",
36 | foreground: "hsl(var(--destructive-foreground))",
37 | },
38 | muted: {
39 | DEFAULT: "hsl(var(--muted))",
40 | foreground: "hsl(var(--muted-foreground))",
41 | },
42 | accent: {
43 | DEFAULT: "hsl(var(--accent))",
44 | foreground: "hsl(var(--accent-foreground))",
45 | },
46 | popover: {
47 | DEFAULT: "hsl(var(--popover))",
48 | foreground: "hsl(var(--popover-foreground))",
49 | },
50 | card: {
51 | DEFAULT: "hsl(var(--card))",
52 | foreground: "hsl(var(--card-foreground))",
53 | },
54 | },
55 | borderRadius: {
56 | lg: "var(--radius)",
57 | md: "calc(var(--radius) - 2px)",
58 | sm: "calc(var(--radius) - 4px)",
59 | },
60 | keyframes: {
61 | "accordion-down": {
62 | from: { height: "0" },
63 | to: { height: "var(--radix-accordion-content-height)" },
64 | },
65 | "accordion-up": {
66 | from: { height: "var(--radix-accordion-content-height)" },
67 | to: { height: "0" },
68 | },
69 | },
70 | animation: {
71 | "accordion-down": "accordion-down 0.2s ease-out",
72 | "accordion-up": "accordion-up 0.2s ease-out",
73 | },
74 | },
75 | },
76 | plugins: [require("tailwindcss-animate")],
77 | }
--------------------------------------------------------------------------------
/utils/GeminiAIModel.js:
--------------------------------------------------------------------------------
1 |
2 | const {
3 | GoogleGenerativeAI,
4 | HarmCategory,
5 | HarmBlockThreshold,
6 | } = require("@google/generative-ai");
7 |
8 | const apiKey = process.env.NEXT_PUBLIC_GEMINI_API_KEY;
9 | const genAI = new GoogleGenerativeAI(apiKey);
10 |
11 | const model = genAI.getGenerativeModel({
12 | model: "gemini-1.5-flash",
13 | });
14 |
15 | const generationConfig = {
16 | temperature: 1,
17 | topP: 0.95,
18 | topK: 64,
19 | maxOutputTokens: 8192,
20 | responseMimeType: "text/plain",
21 | };
22 |
23 |
24 |
25 |
26 |
27 | export const chatSession = model.startChat({
28 | generationConfig,
29 |
30 | // safetySettings: Adjust safety settings
31 | // See https://ai.google.dev/gemini-api/docs/safety-settings
32 |
33 | });
34 |
35 |
36 |
--------------------------------------------------------------------------------
/utils/db.js:
--------------------------------------------------------------------------------
1 | import { neon } from '@neondatabase/serverless';
2 | import { drizzle } from 'drizzle-orm/neon-http';
3 | import * as schema from "./schema"
4 | const sql = neon(process.env.NEXT_PUBLIC_DRIZZLE_DB_URL);
5 | export const db = drizzle(sql,{schema});
--------------------------------------------------------------------------------
/utils/schema.js:
--------------------------------------------------------------------------------
1 | import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
2 |
3 | export const MockInterview = pgTable('mockInterview', {
4 | id: serial("id").primaryKey(),
5 | jsonMockResp: text("jsonMockResp").notNull(),
6 | jobPosition: varchar("jobPosition").notNull(),
7 | jobDesc: varchar("jobDesc").notNull(),
8 | jobExperience: varchar("jobExperience").notNull(),
9 | createdBy: varchar("createdBy").notNull(),
10 | createdAt: varchar("createdAt"),
11 | mockId: varchar("mockId").notNull()
12 | });
13 |
14 |
15 | export const UserAnswer=pgTable('useAnswer',{
16 | id:serial('id').primaryKey(),
17 | mockIdRef:varchar("mockId").notNull(),
18 | question:varchar('question').notNull(),
19 | correctAns:text('correctAns'),
20 | userAns:text('userAns'),
21 | feedback:text('feedback'),
22 | rating:varchar('rating'),
23 | userEmail:varchar('userEmail'),
24 | createdAt:varchar('createdAt')
25 | })
26 |
--------------------------------------------------------------------------------