From fe2f9c5ac7f90a4afc79b01e5d6813b787f7cc1d Mon Sep 17 00:00:00 2001 From: JohanDelao Date: Thu, 23 May 2024 12:07:52 -0400 Subject: [PATCH 1/5] protect layouts + new dashboard --- client/package.json | 1 + client/public/images/InterviewifyLogo.png | Bin 0 -> 5551 bytes .../app/(main)/components/numberQuestions.tsx | 24 ++++++++ .../(main)/components/professionDropdown.tsx | 49 +++++++++++++++ .../(main)/components/scheduleInterview.tsx | 32 ++++++++++ client/src/app/(main)/components/sidenav.tsx | 36 +++++++++++ client/src/app/(main)/dashboard/page.tsx | 58 ++++++++++++++++++ client/src/app/(main)/layout.tsx | 50 +++++++++++++++ client/src/app/(welcome)/layout.tsx | 43 +++++++++++++ .../src/app/{welcome => (welcome)}/page.tsx | 0 client/src/app/components/logo.tsx | 14 +++++ client/src/app/globals.css | 11 +++- client/src/app/loading.tsx | 15 ----- client/src/app/page.tsx | 39 ------------ client/src/app/start/layout.tsx | 7 +-- client/tailwind.config.ts | 9 +++ client/yarn.lock | 5 ++ 17 files changed, 333 insertions(+), 60 deletions(-) create mode 100644 client/public/images/InterviewifyLogo.png create mode 100644 client/src/app/(main)/components/numberQuestions.tsx create mode 100644 client/src/app/(main)/components/professionDropdown.tsx create mode 100644 client/src/app/(main)/components/scheduleInterview.tsx create mode 100644 client/src/app/(main)/components/sidenav.tsx create mode 100644 client/src/app/(main)/dashboard/page.tsx create mode 100644 client/src/app/(main)/layout.tsx create mode 100644 client/src/app/(welcome)/layout.tsx rename client/src/app/{welcome => (welcome)}/page.tsx (100%) create mode 100644 client/src/app/components/logo.tsx delete mode 100644 client/src/app/loading.tsx delete mode 100644 client/src/app/page.tsx diff --git a/client/package.json b/client/package.json index 32c4436..d1538dc 100644 --- a/client/package.json +++ b/client/package.json @@ -19,6 +19,7 @@ "antd": "^5.8.5", "autoprefixer": "^10.4.15", "axios": "^1.5.0", + "clsx": "^2.1.1", "eslint": "^8.48.0", "eslint-config-next": "13.4.19", "eslint-config-prettier": "^9.0.0", diff --git a/client/public/images/InterviewifyLogo.png b/client/public/images/InterviewifyLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..ffdf52d2876746f90213ef271366d0c2ec042972 GIT binary patch literal 5551 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 { + const numberProps = [1, 2, 3, 4] + return ( +
+

Number of Questions

+
+ {numberProps.map((number) => { + return ( + + ) + })} +
+
+ ) +}; + +export default NumberQuestions; \ No newline at end of file diff --git a/client/src/app/(main)/components/professionDropdown.tsx b/client/src/app/(main)/components/professionDropdown.tsx new file mode 100644 index 0000000..4773754 --- /dev/null +++ b/client/src/app/(main)/components/professionDropdown.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { ProfessionType } from '@/app/interfaces'; +import { MenuProps } from 'antd'; +import { DownOutlined, UserOutlined } from '@ant-design/icons'; +import { Button, Dropdown, Space } from 'antd'; + +const ScheduleDropdown = ({ profession, setProfession }) => { + const handleMenuClick: MenuProps['onClick'] = (e) => { + setProfession(e.key); + }; + + const items: MenuProps['items'] = [ + { + label: ProfessionType.SOFTWARE_ENGINEER, + key: 'Software Engineer', + icon: , + }, + { + label: ProfessionType.DATA_SCIENTIST, + key: 'Data Scientist', + icon: , + }, + { + label: ProfessionType.PRODUCT_MANAGER, + key: 'Product Manager', + icon: , + }, + ]; + + const menuProps = { + items, + onClick: handleMenuClick, + }; + return ( +
+

Position

+ + + +
+ ); +}; + +export default ScheduleDropdown; diff --git a/client/src/app/(main)/components/scheduleInterview.tsx b/client/src/app/(main)/components/scheduleInterview.tsx new file mode 100644 index 0000000..72daa4c --- /dev/null +++ b/client/src/app/(main)/components/scheduleInterview.tsx @@ -0,0 +1,32 @@ +import { VideoCameraOutlined } from "@ant-design/icons"; +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import ProfessionDropdown from "../components/professionDropdown"; +import NumberQuestions from "../components/numberQuestions"; + +const ScheduleInterview = () => { + const [profession, setProfession] = useState('Software Engineer'); + const [numberQuestions, setNumberQuestions] = useState(1); + const router = useRouter(); + + return ( +
+
+ +

Schedule Interview

+
+
+
+
+ + +
+ +
+
+ ) +}; + +export default ScheduleInterview; \ No newline at end of file diff --git a/client/src/app/(main)/components/sidenav.tsx b/client/src/app/(main)/components/sidenav.tsx new file mode 100644 index 0000000..b7d74ae --- /dev/null +++ b/client/src/app/(main)/components/sidenav.tsx @@ -0,0 +1,36 @@ +"use client" +import React from 'react'; +import Logo from '../../components/logo'; +import Link from 'next/link'; +import clsx from 'clsx'; +import { usePathname } from 'next/navigation'; +import { HomeOutlined, HistoryOutlined } from '@ant-design/icons'; + +const SideNav = () => { + const pathName = usePathname(); + + return ( +
+
+ +
+

DASHBOARD

+ +
+ +

Home

+
+ + +
+ +

History

+
+ +
+
+
+ ) +} + +export default SideNav; \ No newline at end of file diff --git a/client/src/app/(main)/dashboard/page.tsx b/client/src/app/(main)/dashboard/page.tsx new file mode 100644 index 0000000..3478a73 --- /dev/null +++ b/client/src/app/(main)/dashboard/page.tsx @@ -0,0 +1,58 @@ +"use client" +import React from 'react'; +import { useEffect, useState } from 'react'; +import axios from 'axios'; +import { LoadingOutlined } from '@ant-design/icons'; +import { useRouter } from 'next/navigation'; +import { User } from '@/app/interfaces'; +import ScheduleInterview from '../components/scheduleInterview'; + +const Dashboard = () => { + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const router = useRouter(); + + useEffect(() => { + const getUser = async () => { + try { + const res = await axios.get( + 'http://localhost:4000/auth/login/success', + { + withCredentials: true, + }, + ); + if(res.data.user) { + console.log(res.data.user); + setUser(res.data.user) + } else { + // TO DO: error handling for user + router.push('/') + } + } catch (error) { + console.log(error); + } + }; + setIsLoading(true); + getUser().then(() => setTimeout(() => {setIsLoading(false)}, 500)); + }, []); + + if(isLoading === true && user === null){ + return ( +
+ +
+ ) + } + + return ( +
+
+

Hello, {user?.username} 👋

+

Keep pushing forward! Every practice session brings you one step closer to acing your next interview.

+
+ +
+ ) +} + +export default Dashboard; \ No newline at end of file diff --git a/client/src/app/(main)/layout.tsx b/client/src/app/(main)/layout.tsx new file mode 100644 index 0000000..67e20c5 --- /dev/null +++ b/client/src/app/(main)/layout.tsx @@ -0,0 +1,50 @@ +"use client" +import React, { useEffect, useState } from 'react'; +import SideNav from './components/sidenav' +import axios from 'axios'; +import { useRouter } from 'next/navigation'; +import { LoadingOutlined } from '@ant-design/icons'; + +export default function MainLayout({ + children, +}: { + children: React.ReactNode; +}) { + const [isLoading, setIsLoading] = useState(true); + const router = useRouter(); + + useEffect(() => { + const getUser = async () => { + try { + const res = await axios.get( + 'http://localhost:4000/auth/login/success', + { + withCredentials: true, + }, + ); + if(res.data.user) { + setIsLoading(false); + } else { + router.push('/') + } + } catch (error) { + console.log(error); + router.push('/') + } + }; + getUser(); + }, []); + + if(isLoading){ + return
+ } + + return ( +
+ +
+ {children} +
+
+ ); +} \ No newline at end of file diff --git a/client/src/app/(welcome)/layout.tsx b/client/src/app/(welcome)/layout.tsx new file mode 100644 index 0000000..0a90420 --- /dev/null +++ b/client/src/app/(welcome)/layout.tsx @@ -0,0 +1,43 @@ +'use client'; +import axios from 'axios'; +import React from 'react'; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { LoadingOutlined } from "@ant-design/icons" + +export default function Layout({ children }: React.PropsWithChildren<{}>) { + const [isLoading, setIsLoading] = useState(true); + const router = useRouter(); + + useEffect(() => { + const getUser = async () => { + try { + const res = await axios.get( + 'http://localhost:4000/auth/login/success', + { + withCredentials: true, + }, + ); + if(res.data.user) { + router.push('/dashboard'); + } else { + setIsLoading(false); + } + } catch (error) { + console.log(error); + setIsLoading(false); + } + }; + getUser(); + }, []); + + if(isLoading){ + return
+ } + + return ( +
+ {children} +
+ ); +} diff --git a/client/src/app/welcome/page.tsx b/client/src/app/(welcome)/page.tsx similarity index 100% rename from client/src/app/welcome/page.tsx rename to client/src/app/(welcome)/page.tsx diff --git a/client/src/app/components/logo.tsx b/client/src/app/components/logo.tsx new file mode 100644 index 0000000..a4fb64e --- /dev/null +++ b/client/src/app/components/logo.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Image from 'next/image'; + +const LogoSVG = '/images/InterviewifyLogo.png'; + +const Logo = () => { + return ( +
+ Interviewify logo +
+ ) +} + +export default Logo; \ No newline at end of file diff --git a/client/src/app/globals.css b/client/src/app/globals.css index 3235482..1a32ba0 100644 --- a/client/src/app/globals.css +++ b/client/src/app/globals.css @@ -1,12 +1,19 @@ -/* styles/tailwind.css */ +@import url('https://fonts.googleapis.com/css2?family=Red+Hat+Text:ital,wght@0,300..700;1,300..700&family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap'); + @tailwind base; @tailwind components; @tailwind utilities; @import './styles/loading.css'; -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap'); :root { + /* Primary is used for the background color (lighter color) */ + --primary-color: 248 248 255; + /* Secondary is used for components that require attention (buttons, sections, etc) */ + --secondary-color: 55 114 255; + /* Accent is primarily for text */ + --accent-color: 18 18 24; + --scrollbar-width: 0px; --angleNormal: -3deg; diff --git a/client/src/app/loading.tsx b/client/src/app/loading.tsx deleted file mode 100644 index 461d392..0000000 --- a/client/src/app/loading.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Spin } from 'antd'; -import React from 'react'; -import { LoadingOutlined } from '@ant-design/icons'; - -export default function Loading() { - // You can add any UI inside Loading, including a Skeleton. - return ( -
- } - size="large" - /> -
- ); -} diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx deleted file mode 100644 index 92e8123..0000000 --- a/client/src/app/page.tsx +++ /dev/null @@ -1,39 +0,0 @@ -'use client'; -import axios from 'axios'; -import React from 'react'; -import { useEffect, useState } from 'react'; -import { useRouter } from 'next/navigation'; -import Loading from './components/loading'; - -export default function Home() { - const [user, setUser] = useState(false); - const router = useRouter(); - - useEffect(() => { - const getUser = async () => { - try { - const res = await axios.get( - 'http://localhost:4000/auth/login/success', - { - withCredentials: true, - }, - ); - setUser(res.data.user); - if (res.data.user) { - router.push('start/dashboard'); - } - } catch (error) { - console.log(error); - } - }; - - getUser(); - if (user) { - router.push('/dashboard'); - } else { - router.push('/welcome'); - } - }, []); - - return ; -} diff --git a/client/src/app/start/layout.tsx b/client/src/app/start/layout.tsx index 96a7df7..88d27a7 100644 --- a/client/src/app/start/layout.tsx +++ b/client/src/app/start/layout.tsx @@ -71,12 +71,11 @@ export default function RegularLayout({ setIsModalOpen(false); }; - const handleLogout = () => { - axios + const handleLogout = async () => { + await axios .get('http://localhost:4000/auth/logout') - .then((res) => console.log(res)) + .then((res) => router.push('/welcome')) .catch((err) => console.log(err)); - router.push('/welcome'); }; useEffect(() => { diff --git a/client/tailwind.config.ts b/client/tailwind.config.ts index b525a56..6c17309 100644 --- a/client/tailwind.config.ts +++ b/client/tailwind.config.ts @@ -7,7 +7,16 @@ const config: Config = { './src/app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { + fontFamily: { + urbanist: ["Urbanist", "sans"], + redHatText: ["Red Hat Text", "sans"], + }, extend: { + colors: { + primary: "rgb(var(--primary-color) / )", + secondary: "rgb(var(--secondary-color) / )", + accent: "rgb(var(--accent-color) / )" + }, backgroundImage: { 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 'gradient-conic': diff --git a/client/yarn.lock b/client/yarn.lock index 197a085..4c694d3 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -852,6 +852,11 @@ client-only@0.0.1: resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" From 112854819e90638478e1713fd01f205685d344ba Mon Sep 17 00:00:00 2001 From: JohanDelao Date: Thu, 23 May 2024 13:28:46 -0400 Subject: [PATCH 2/5] remove replaced components --- client/src/app/start/dashboard/page.tsx | 124 ------------------------ client/src/app/start/layout.tsx | 17 +--- client/src/app/start/loading.tsx | 15 --- 3 files changed, 3 insertions(+), 153 deletions(-) delete mode 100644 client/src/app/start/dashboard/page.tsx delete mode 100644 client/src/app/start/loading.tsx diff --git a/client/src/app/start/dashboard/page.tsx b/client/src/app/start/dashboard/page.tsx deleted file mode 100644 index 25ff264..0000000 --- a/client/src/app/start/dashboard/page.tsx +++ /dev/null @@ -1,124 +0,0 @@ -'use client'; -import { useState } from 'react'; -import React from 'react'; -import { Button, Dropdown, Space } from 'antd'; -import { DownOutlined, UserOutlined } from '@ant-design/icons'; -import { MenuProps } from 'antd'; -import { useRouter } from 'next/navigation'; -import { ProfessionType } from '@/app/interfaces'; - -export default function Dashboard() { - const [profession, setProfession] = useState('Software Engineer'); - const [numberQuestions, setNumberQuestions] = useState(1); - const router = useRouter(); - - const handleMenuClick: MenuProps['onClick'] = (e) => { - setProfession(e.key); - }; - - const handleNumberClick: MenuProps['onClick'] = (e) => { - setNumberQuestions(parseInt(e.key)); - }; - - const startInterview = () => { - router.push( - `/start/interview?profession=${profession}&numberQs=${numberQuestions}`, - ); - }; - - const items: MenuProps['items'] = [ - { - label: ProfessionType.SOFTWARE_ENGINEER, - key: 'Software Engineer', - icon: , - }, - { - label: ProfessionType.DATA_SCIENTIST, - key: 'Data Scientist', - icon: , - }, - { - label: ProfessionType.PRODUCT_MANAGER, - key: 'Product Manager', - icon: , - }, - ]; - - const menuProps = { - items, - onClick: handleMenuClick, - }; - - const numberProps = { - items: [ - { - label: '1', - key: '1', - }, - { - label: '2', - key: '2', - }, - { - label: '3', - key: '3', - }, - { - label: '4', - key: '4', - }, - ], - onClick: handleNumberClick, - }; - - return ( -
-
-

Profession

-
- - - -
-
-
-
-

- Mock Behavorial Interview -

-
-

- Number of Questions -

- - - -
-
- -
-
-

- Leveraging the power of OpenAIs GPT-4, an advanced language model, you - will be given comprehensive feedback and constructive criticism on - audio responses to question prompts. -

-
-
- ); -} diff --git a/client/src/app/start/layout.tsx b/client/src/app/start/layout.tsx index 88d27a7..cce9b58 100644 --- a/client/src/app/start/layout.tsx +++ b/client/src/app/start/layout.tsx @@ -4,9 +4,6 @@ import '../globals.css'; import React, { useEffect, useState } from 'react'; import { Layout, Menu, MenuProps, Modal } from 'antd'; const { Footer, Sider } = Layout; -import { Suspense } from 'react'; -import Loading from './loading'; -import Image from 'next/image'; import { ClockCircleOutlined, UserOutlined, @@ -15,9 +12,7 @@ import { } from '@ant-design/icons'; import axios, { AxiosError } from 'axios'; import { usePathname, useRouter } from 'next/navigation'; -import CustomFooter from '../components/footer'; import { User } from '../interfaces'; -import Logo from '../../../public/images/LogoV2.png'; type MenuItem = Required['items'][number]; @@ -112,7 +107,7 @@ export default function RegularLayout({ ]; return ( - }> + <>
{/* TO DO: Have Sider animation where when it loads up have it slide up from the side */} @@ -132,12 +127,6 @@ export default function RegularLayout({ } }} > - logo of interviewify

Interviewify

- }>{children} + {children}
@@ -181,6 +170,6 @@ export default function RegularLayout({ >

Your progress will not be saved!

- + ); } diff --git a/client/src/app/start/loading.tsx b/client/src/app/start/loading.tsx deleted file mode 100644 index 461d392..0000000 --- a/client/src/app/start/loading.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Spin } from 'antd'; -import React from 'react'; -import { LoadingOutlined } from '@ant-design/icons'; - -export default function Loading() { - // You can add any UI inside Loading, including a Skeleton. - return ( -
- } - size="large" - /> -
- ); -} From b01aa3853e6b5fffddd3ff07df5eba5611e6d718 Mon Sep 17 00:00:00 2001 From: JohanDelao Date: Fri, 24 May 2024 20:06:05 -0400 Subject: [PATCH 3/5] interview questions + animation view --- client/public/images/DoubleChatBubble.png | Bin 0 -> 424 bytes .../components/InterviewPrompt.tsx | 32 +++++++++++ .../(interview)/components/mainInterview.tsx | 50 ++++++++++++++++++ .../(interview)/components/questionBars.tsx | 28 ++++++++++ .../(main)/(interview)/components/timer.tsx | 15 ++++++ .../app/(main)/(interview)/interview/page.tsx | 40 ++++++++++++++ client/src/app/(main)/(interview)/layout.tsx | 25 +++++++++ .../(main)/components/professionDropdown.tsx | 14 +++-- .../(main)/components/scheduleInterview.tsx | 21 ++++++-- client/src/app/(main)/layout.tsx | 5 +- client/src/app/contexts/interview-context.tsx | 33 ++++++++++++ client/src/app/interfaces.ts | 6 +++ client/src/app/utils/get-questions.ts | 15 +++--- 13 files changed, 268 insertions(+), 16 deletions(-) create mode 100644 client/public/images/DoubleChatBubble.png create mode 100644 client/src/app/(main)/(interview)/components/InterviewPrompt.tsx create mode 100644 client/src/app/(main)/(interview)/components/mainInterview.tsx create mode 100644 client/src/app/(main)/(interview)/components/questionBars.tsx create mode 100644 client/src/app/(main)/(interview)/components/timer.tsx create mode 100644 client/src/app/(main)/(interview)/interview/page.tsx create mode 100644 client/src/app/(main)/(interview)/layout.tsx create mode 100644 client/src/app/contexts/interview-context.tsx diff --git a/client/public/images/DoubleChatBubble.png b/client/public/images/DoubleChatBubble.png new file mode 100644 index 0000000000000000000000000000000000000000..64243d204b6d8f7e64f7868e5f0fff7b0116d4e4 GIT binary patch literal 424 zcmV;Z0ayNsP)5&L_CN`iGP(Ax#Oo zAFrg>-7K^d3s4f7GEpJPSllVo_E1>HHb(aP548v@#5P3Jg9h+K`?xW85tsc*s~K^^ zZ}UufM4TZh-=lFy5xtuicii!>; +} + +const InterviewPrompt = ({ setPrompt } : InterviewPromptProps) => { + const { interview } = useInterviewContext(); + + return ( +
+
+
+ chat bubble icon +

Behavorial Interview Practice

+
+

This interview will be for a {interview?.profession}

+

This interview will consist of {interview?.questionCount} questions, each of which you will have two minutes to answer. You will have one opportunity to redo your response if something unexpected occurs.

+

Please ensure that you grant microphone permission to answer these questions when prompted.

+
+ +
+
+ ) +} + +export default InterviewPrompt; \ No newline at end of file diff --git a/client/src/app/(main)/(interview)/components/mainInterview.tsx b/client/src/app/(main)/(interview)/components/mainInterview.tsx new file mode 100644 index 0000000..432e3d3 --- /dev/null +++ b/client/src/app/(main)/(interview)/components/mainInterview.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { DownOutlined, UpOutlined } from "@ant-design/icons"; +import { Interview } from "@/app/interfaces"; +import clsx from "clsx"; + +type MainInterviewProps = { + interview: Interview | null, + questionIndex: number, + setQuestionIndex: React.Dispatch> +} + +const MainInterview = ({ interview, questionIndex, setQuestionIndex } : MainInterviewProps) => { + + if(!interview){ + return
Error with getting interview
+ } + + return ( +
+
+
+
+ {interview?.questions.map((question) => { + return ( +
+

{question}

+
+ ) + })} +
+
+
+ {/* TO DO: Determine if the up is needed */} + +
+ +
+
+
+
+ ) +}; + +export default MainInterview; \ No newline at end of file diff --git a/client/src/app/(main)/(interview)/components/questionBars.tsx b/client/src/app/(main)/(interview)/components/questionBars.tsx new file mode 100644 index 0000000..6158d80 --- /dev/null +++ b/client/src/app/(main)/(interview)/components/questionBars.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Interview } from '@/app/interfaces'; +import clsx from 'clsx'; + +type QuestionBarsProps = { + interview: Interview | null, + questionIndex: number +} + +const QuestionBars = ( { interview, questionIndex } : QuestionBarsProps ) => { + return ( +
+
+ {interview?.questions.map((question) => { + return ( +
+
+
+ ); + })} +
+
+ ); +}; + +export default QuestionBars; diff --git a/client/src/app/(main)/(interview)/components/timer.tsx b/client/src/app/(main)/(interview)/components/timer.tsx new file mode 100644 index 0000000..24a576a --- /dev/null +++ b/client/src/app/(main)/(interview)/components/timer.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { ClockCircleOutlined } from '@ant-design/icons'; + +const Timer = () => { + return ( +
+ +

+ 2:00 / 2:00 +

+
+ ); +}; + +export default Timer; \ No newline at end of file diff --git a/client/src/app/(main)/(interview)/interview/page.tsx b/client/src/app/(main)/(interview)/interview/page.tsx new file mode 100644 index 0000000..5a2bd85 --- /dev/null +++ b/client/src/app/(main)/(interview)/interview/page.tsx @@ -0,0 +1,40 @@ +"use client" +import React, { useState } from "react"; +import { useInterviewContext } from "../../../contexts/interview-context"; +import InterviewPrompt from "../components/InterviewPrompt"; +import Timer from "../components/timer"; +import QuestionBars from "../components/questionBars"; +import GetQuestions from "@/app/utils/get-questions"; +import MainInterview from "../components/mainInterview"; + +const Interview = () => { + const { interview } = useInterviewContext(); + const [prompt, setPrompt] = useState(true); + + const [questionIndex, setQuestionIndex] = useState(0); + + if(prompt === true){ + return ( +
+ +
+ ) + } + + return ( +
+
+

{interview?.profession} Interview

+
+ +
+ +
+
+ +
+
+ ) +}; + +export default Interview; \ No newline at end of file diff --git a/client/src/app/(main)/(interview)/layout.tsx b/client/src/app/(main)/(interview)/layout.tsx new file mode 100644 index 0000000..80c7e4b --- /dev/null +++ b/client/src/app/(main)/(interview)/layout.tsx @@ -0,0 +1,25 @@ +"use client" +import React, { useEffect } from 'react'; +import { useInterviewContext } from "@/app/contexts/interview-context"; +import { useRouter } from "next/navigation"; + +export default function MainLayout({ + children, +}: { + children: React.ReactNode; +}) { + const { interview } = useInterviewContext(); + const router = useRouter(); + + useEffect(() => { + if (!interview) { + router.push('/dashboard'); + } + }, [interview, router]); + + return ( +
+ {children} +
+ ); +} \ No newline at end of file diff --git a/client/src/app/(main)/components/professionDropdown.tsx b/client/src/app/(main)/components/professionDropdown.tsx index 4773754..71d7ee8 100644 --- a/client/src/app/(main)/components/professionDropdown.tsx +++ b/client/src/app/(main)/components/professionDropdown.tsx @@ -4,25 +4,31 @@ import { MenuProps } from 'antd'; import { DownOutlined, UserOutlined } from '@ant-design/icons'; import { Button, Dropdown, Space } from 'antd'; -const ScheduleDropdown = ({ profession, setProfession }) => { +type ScheduleDropdownProps = { + profession: ProfessionType, + setProfession: React.Dispatch> +} + +const ScheduleDropdown = ({ profession, setProfession } : ScheduleDropdownProps) => { const handleMenuClick: MenuProps['onClick'] = (e) => { + // TO DO: Fix type error here setProfession(e.key); }; const items: MenuProps['items'] = [ { label: ProfessionType.SOFTWARE_ENGINEER, - key: 'Software Engineer', + key: ProfessionType.SOFTWARE_ENGINEER, icon: , }, { label: ProfessionType.DATA_SCIENTIST, - key: 'Data Scientist', + key: ProfessionType.DATA_SCIENTIST, icon: , }, { label: ProfessionType.PRODUCT_MANAGER, - key: 'Product Manager', + key: ProfessionType.PRODUCT_MANAGER, icon: , }, ]; diff --git a/client/src/app/(main)/components/scheduleInterview.tsx b/client/src/app/(main)/components/scheduleInterview.tsx index 72daa4c..d59f0ef 100644 --- a/client/src/app/(main)/components/scheduleInterview.tsx +++ b/client/src/app/(main)/components/scheduleInterview.tsx @@ -1,13 +1,26 @@ -import { VideoCameraOutlined } from "@ant-design/icons"; +import { VideoCameraOutlined, LoadingOutlined } from "@ant-design/icons"; import React, { useState } from "react"; import { useRouter } from "next/navigation"; import ProfessionDropdown from "../components/professionDropdown"; import NumberQuestions from "../components/numberQuestions"; +import { useInterviewContext } from "@/app/contexts/interview-context"; +import { ProfessionType } from "@/app/interfaces"; +import GetQuestions from "@/app/utils/get-questions"; const ScheduleInterview = () => { - const [profession, setProfession] = useState('Software Engineer'); + const [isButtonLoading, setIsButtonLoading] = useState(false); + + const [profession, setProfession] = useState(ProfessionType.SOFTWARE_ENGINEER); const [numberQuestions, setNumberQuestions] = useState(1); const router = useRouter(); + const { setInterview } = useInterviewContext(); + + const handleCreateInterview = () => { + setIsButtonLoading(true); + const questions = GetQuestions(profession, numberQuestions); + setInterview({questionCount: numberQuestions, profession: profession, questions: questions}); + setTimeout(() => router.push('/interview'), 500); + } return (
@@ -21,8 +34,8 @@ const ScheduleInterview = () => {
- diff --git a/client/src/app/(main)/layout.tsx b/client/src/app/(main)/layout.tsx index 67e20c5..02404ba 100644 --- a/client/src/app/(main)/layout.tsx +++ b/client/src/app/(main)/layout.tsx @@ -4,6 +4,7 @@ import SideNav from './components/sidenav' import axios from 'axios'; import { useRouter } from 'next/navigation'; import { LoadingOutlined } from '@ant-design/icons'; +import ThemeContextProvider from '../contexts/interview-context'; export default function MainLayout({ children, @@ -43,7 +44,9 @@ export default function MainLayout({
- {children} + + {children} +
); diff --git a/client/src/app/contexts/interview-context.tsx b/client/src/app/contexts/interview-context.tsx new file mode 100644 index 0000000..940af76 --- /dev/null +++ b/client/src/app/contexts/interview-context.tsx @@ -0,0 +1,33 @@ +"use client" +import React, { createContext, useContext, useState } from "react"; +import { Interview } from "../interfaces"; + +type InterviewContextProviderProps = { + children: React.ReactNode; +}; + +type InterviewContext = { + interview: Interview | null; + setInterview: React.Dispatch>; +} + +export const InterviewContext = createContext(null); + +export default function ThemeContextProvider({ + children +} : InterviewContextProviderProps) { + const [interview, setInterview] = useState(null); + return ( + + {children} + + ) +} + +export function useInterviewContext(){ + const interview = useContext(InterviewContext); + if (interview === null) { + throw new Error("useInterviewContext must be used within a InterviewContextProvider"); + } + return interview; +} \ No newline at end of file diff --git a/client/src/app/interfaces.ts b/client/src/app/interfaces.ts index f5e83be..9fe732f 100644 --- a/client/src/app/interfaces.ts +++ b/client/src/app/interfaces.ts @@ -5,6 +5,12 @@ export interface User { _id: string; } +export interface Interview { + questionCount: number; + profession: ProfessionType; + questions: string[]; +} + export enum ProfessionType { ALL = 'all', SOFTWARE_ENGINEER = 'Software Engineer', diff --git a/client/src/app/utils/get-questions.ts b/client/src/app/utils/get-questions.ts index d0e58e2..3a686f0 100644 --- a/client/src/app/utils/get-questions.ts +++ b/client/src/app/utils/get-questions.ts @@ -7,17 +7,18 @@ export default function GetQuestions( ): string[] { const res: string[] = []; if (profession && numberQs) { - for (let i = 0; i < numberQs; i++) { + while (res.length < numberQs) { let randomIndex = Math.floor(Math.random() * questionBank.length); + let question = questionBank[randomIndex][0]; + let questionProfession = questionBank[randomIndex][1]; + if ( - questionBank[randomIndex][1] !== profession || - questionBank[randomIndex][1] !== ProfessionType.ALL || - res.includes(questionBank[randomIndex][0]) + (questionProfession === profession || questionProfession === ProfessionType.ALL) && + !res.includes(question) ) { - randomIndex = Math.floor(Math.random() * questionBank.length); + res.push(question); } - res.push(questionBank[randomIndex][0]); } } return res; -} +} \ No newline at end of file From 590af6259c7589b0f21dbb312402254b68c7ad04 Mon Sep 17 00:00:00 2001 From: JohanDelao Date: Mon, 27 May 2024 16:23:55 -0400 Subject: [PATCH 4/5] feat: connected timer and react mic w/ new ui --- client/package.json | 1 + .../components/interviewQuestion.tsx | 98 ++++++++++++++++ .../(interview)/components/mainInterview.tsx | 84 ++++++++----- .../(main)/(interview)/components/timer.tsx | 24 +++- .../app/(main)/(interview)/interview/page.tsx | 110 +++++++++++++----- .../(main)/components/scheduleInterview.tsx | 6 +- client/src/app/(main)/dashboard/page.tsx | 3 +- client/yarn.lock | 5 + 8 files changed, 260 insertions(+), 71 deletions(-) create mode 100644 client/src/app/(main)/(interview)/components/interviewQuestion.tsx diff --git a/client/package.json b/client/package.json index d1538dc..669c724 100644 --- a/client/package.json +++ b/client/package.json @@ -25,6 +25,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-react": "^7.33.2", + "lucide-react": "^0.379.0", "next": "13.4.19", "postcss": "^8.4.29", "postcss-nesting": "^12.0.1", diff --git a/client/src/app/(main)/(interview)/components/interviewQuestion.tsx b/client/src/app/(main)/(interview)/components/interviewQuestion.tsx new file mode 100644 index 0000000..02d43c1 --- /dev/null +++ b/client/src/app/(main)/(interview)/components/interviewQuestion.tsx @@ -0,0 +1,98 @@ +import React, { useState } from 'react'; +import { CirclePlay, CircleStop, Mic, MicOff } from 'lucide-react'; +import clsx from 'clsx'; +import { ReactMic } from 'react-mic'; + +type InterviewQuestionProps = { + current: boolean; + question: string; + setBlobs: React.Dispatch>; + started: boolean; + startResponse: () => void; + nextQuestion: () => void; +}; + +type StartButtonProps = { + startResponse: () => void; +} + +const StartButton = ( { startResponse } : StartButtonProps ) => { + return ( + + ); +}; + +type DuringButtonsProps = { + muted: boolean; + setMuted: React.Dispatch>; + nextQuestion: () => void; +} + +// TODO: Parameters: muted, setMuted, submitBlob, RestartButton +const DuringButtons = ( { muted, setMuted, nextQuestion } : DuringButtonsProps ) => { + return
+ {/* Mute Button */} + + {/* Submit Button */} + +
; +}; + +const InterviewQuestion = ({ + current, + question, + setBlobs, + started, + startResponse, + nextQuestion +}: InterviewQuestionProps) => { + function handleAudio(recordedBlob: any) { + setBlobs((prevBlobs) => [...prevBlobs, recordedBlob.blob]); + } + + const [muted, setMuted] = useState(false); + + return ( +
+

+ {question} +

+ +
+ {started ? : } +
+
+ ); +}; + +export default InterviewQuestion; diff --git a/client/src/app/(main)/(interview)/components/mainInterview.tsx b/client/src/app/(main)/(interview)/components/mainInterview.tsx index 432e3d3..cc9054d 100644 --- a/client/src/app/(main)/(interview)/components/mainInterview.tsx +++ b/client/src/app/(main)/(interview)/components/mainInterview.tsx @@ -1,36 +1,56 @@ -import React from "react"; -import { DownOutlined, UpOutlined } from "@ant-design/icons"; -import { Interview } from "@/app/interfaces"; -import clsx from "clsx"; +import React, { useState } from 'react'; +import { DownOutlined, UpOutlined } from '@ant-design/icons'; +import { Interview } from '../../../interfaces'; +import InterviewQuestion from '../components/interviewQuestion'; +import clsx from 'clsx'; type MainInterviewProps = { - interview: Interview | null, - questionIndex: number, - setQuestionIndex: React.Dispatch> -} + started: boolean; + setBlobs: React.Dispatch>; + interview: Interview | null; + questionIndex: number; + startResponse: () => void; + nextQuestion: () => void; +}; -const MainInterview = ({ interview, questionIndex, setQuestionIndex } : MainInterviewProps) => { +const MainInterview = ({ + started, + setBlobs, + interview, + questionIndex, + startResponse, + nextQuestion +}: MainInterviewProps) => { - if(!interview){ - return
Error with getting interview
- } + if (!interview) { + return
Error with getting interview
; + } - return ( -
-
-
-
- {interview?.questions.map((question) => { - return ( -
-

{question}

-
- ) - })} -
-
+ return ( +
+
+
+ {interview?.questions.map((question) => { + // Make a interview component for each interview, give a set blob function through it so you can keep code clean + const current = question === interview.questions[questionIndex]; + return ( + + ); + })} +
+ {/* TO DO: Move to feedback page */} + {/*
- {/* TO DO: Determine if the up is needed */}
-
-
-
- ) +
*/} +
+
+ ); }; -export default MainInterview; \ No newline at end of file +export default MainInterview; diff --git a/client/src/app/(main)/(interview)/components/timer.tsx b/client/src/app/(main)/(interview)/components/timer.tsx index 24a576a..dfb592e 100644 --- a/client/src/app/(main)/(interview)/components/timer.tsx +++ b/client/src/app/(main)/(interview)/components/timer.tsx @@ -1,12 +1,28 @@ import React from 'react'; -import { ClockCircleOutlined } from '@ant-design/icons'; +import { Clock } from 'lucide-react'; + +type TimerProps = { + time: number; +} + +const Timer = ({ time } : TimerProps) => { + + const timeConvert = (time: number) => { + if (time < 0) { + return '00'; + } + if (time < 10) { + return '0' + String(time); + } else { + return String(time); + } + }; -const Timer = () => { return (
- +

- 2:00 / 2:00 + {timeConvert(Math.floor(time / 60))}:{timeConvert(time % 60)} / 2:00

); diff --git a/client/src/app/(main)/(interview)/interview/page.tsx b/client/src/app/(main)/(interview)/interview/page.tsx index 5a2bd85..2befe0f 100644 --- a/client/src/app/(main)/(interview)/interview/page.tsx +++ b/client/src/app/(main)/(interview)/interview/page.tsx @@ -1,40 +1,90 @@ -"use client" -import React, { useState } from "react"; -import { useInterviewContext } from "../../../contexts/interview-context"; -import InterviewPrompt from "../components/InterviewPrompt"; -import Timer from "../components/timer"; -import QuestionBars from "../components/questionBars"; -import GetQuestions from "@/app/utils/get-questions"; -import MainInterview from "../components/mainInterview"; +'use client'; +import React, { useState } from 'react'; +import { useInterviewContext } from '../../../contexts/interview-context'; +import InterviewPrompt from '../components/InterviewPrompt'; +import Timer from '../components/timer'; +import QuestionBars from '../components/questionBars'; +import MainInterview from '../components/mainInterview'; const Interview = () => { - const { interview } = useInterviewContext(); - const [prompt, setPrompt] = useState(true); + const { interview } = useInterviewContext(); - const [questionIndex, setQuestionIndex] = useState(0); + if (!interview) { + return
Error with getting interview
; + } - if(prompt === true){ - return ( -
- -
- ) + const [prompt, setPrompt] = useState(true); + + const [questionIndex, setQuestionIndex] = useState(0); + const [started, setStarted] = useState(false); + const [time, setTime] = useState(30); + const [intervalID, setIntervalID] = useState(null); + const [blobs, setBlobs] = useState([]); + + const startResponse = () => { + setStarted(true); + timer('start'); + }; + + const nextQuestion = () => { + setTime(30); + timer('end'); + setStarted(false); + if (questionIndex < interview?.questionCount - 1) + setQuestionIndex(questionIndex + 1); + }; + + const timer = (action: string) => { + if (action === 'start') { + const intId = setInterval(() => { + if (time > 0) { + setTime((prevTime) => prevTime - 1); + } else { + clearInterval(intId); + } + }, 1000); + setIntervalID(intId); + } else if (action === 'end') { + if (intervalID !== null) { + clearInterval(intervalID); + } } + }; + if (prompt === true) { return ( -
-
-

{interview?.profession} Interview

-
- -
- -
-
- -
+
+ +
+ ); + } + + return ( +
+
+
+

+ {interview?.profession}{' '} + Interview +

- ) +
+ +
+ +
+
+ +
+
+ ); }; -export default Interview; \ No newline at end of file +export default Interview; diff --git a/client/src/app/(main)/components/scheduleInterview.tsx b/client/src/app/(main)/components/scheduleInterview.tsx index d59f0ef..70bd9aa 100644 --- a/client/src/app/(main)/components/scheduleInterview.tsx +++ b/client/src/app/(main)/components/scheduleInterview.tsx @@ -3,9 +3,9 @@ import React, { useState } from "react"; import { useRouter } from "next/navigation"; import ProfessionDropdown from "../components/professionDropdown"; import NumberQuestions from "../components/numberQuestions"; -import { useInterviewContext } from "@/app/contexts/interview-context"; -import { ProfessionType } from "@/app/interfaces"; -import GetQuestions from "@/app/utils/get-questions"; +import { useInterviewContext } from "../../contexts/interview-context"; +import { ProfessionType } from "../../interfaces"; +import GetQuestions from "../../utils/get-questions"; const ScheduleInterview = () => { const [isButtonLoading, setIsButtonLoading] = useState(false); diff --git a/client/src/app/(main)/dashboard/page.tsx b/client/src/app/(main)/dashboard/page.tsx index 3478a73..eb79545 100644 --- a/client/src/app/(main)/dashboard/page.tsx +++ b/client/src/app/(main)/dashboard/page.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import axios from 'axios'; import { LoadingOutlined } from '@ant-design/icons'; import { useRouter } from 'next/navigation'; -import { User } from '@/app/interfaces'; +import { User } from '../../interfaces'; import ScheduleInterview from '../components/scheduleInterview'; const Dashboard = () => { @@ -22,7 +22,6 @@ const Dashboard = () => { }, ); if(res.data.user) { - console.log(res.data.user); setUser(res.data.user) } else { // TO DO: error handling for user diff --git a/client/yarn.lock b/client/yarn.lock index 4c694d3..9db00bb 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2070,6 +2070,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lucide-react@^0.379.0: + version "0.379.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.379.0.tgz#29e34eeffae7fb241b64b09868cbe3ab888ef7cc" + integrity sha512-KcdeVPqmhRldldAAgptb8FjIunM2x2Zy26ZBh1RsEUcdLIvsEmbcw7KpzFYUy5BbpGeWhPu9Z9J5YXfStiXwhg== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" From abc70c8faa6084d4d3daf234babedfbd36e4aa4e Mon Sep 17 00:00:00 2001 From: JohanDelao Date: Mon, 27 May 2024 16:42:06 -0400 Subject: [PATCH 5/5] push to debug --- .../app/(main)/(interview)/interview/page.tsx | 62 ++++++++++++++++++- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/client/src/app/(main)/(interview)/interview/page.tsx b/client/src/app/(main)/(interview)/interview/page.tsx index 2befe0f..0b2321c 100644 --- a/client/src/app/(main)/(interview)/interview/page.tsx +++ b/client/src/app/(main)/(interview)/interview/page.tsx @@ -1,25 +1,81 @@ 'use client'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useInterviewContext } from '../../../contexts/interview-context'; import InterviewPrompt from '../components/InterviewPrompt'; import Timer from '../components/timer'; import QuestionBars from '../components/questionBars'; import MainInterview from '../components/mainInterview'; +import axios from 'axios'; +import { useRouter } from 'next/navigation'; const Interview = () => { const { interview } = useInterviewContext(); + const router = useRouter(); if (!interview) { return
Error with getting interview
; } const [prompt, setPrompt] = useState(true); - + + // States for Question const [questionIndex, setQuestionIndex] = useState(0); const [started, setStarted] = useState(false); - const [time, setTime] = useState(30); + + // States for timer + const [time, setTime] = useState(30); const [intervalID, setIntervalID] = useState(null); + + // States for evaluation and audio const [blobs, setBlobs] = useState([]); + const [evaluations, setEvaluations] = useState([]); + + useEffect(() => { + const saveToMongo = async () => { + const data = { + evaluations: evaluations, + position: interview.profession, + }; + const resp = await axios.post( + 'http://localhost:4000/mongo/save-interview', + data, + { + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, + }, + ); + console.log('Success', evaluations, resp); + // router.push('/start/feedback'); + }; + if (evaluations.length == interview.questionCount) saveToMongo(); + }, [evaluations]); + + // TODO: Need to discuss ways to handle sending request -> redirect to new page -> receiving info + const evaluate = async () => { + const formData = new FormData(); + + let currIdx = blobs.length - 1; + formData.append('question', interview.questions[currIdx] as string); + formData.append('profession', interview.profession as string); + formData.append('audio', blobs[currIdx]); + const resp = await axios.post( + 'http://localhost:4000/gpt/evaluate', + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }, + ); + setEvaluations((prevEvals) => [...prevEvals, resp.data]); + }; + + // when blobs gets updated, evalaute new blob + useEffect(() => { + if (blobs.length > 0) evaluate(); + }, [blobs]); const startResponse = () => { setStarted(true);