Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.685.0",
"@aws-sdk/s3-request-presigner": "^3.685.0",
"@mediapipe/camera_utils": "^0.3.1675466862",
"@mediapipe/face_detection": "^0.4.1646425229",
"axios": "^1.7.7",
"chart.js": "^4.4.6",
"firebase": "^9.10.0",
Expand Down
16 changes: 13 additions & 3 deletions src/app/interview/ongoing/prepare/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getMessaging, isSupported, onMessage } from "firebase/messaging";
import { firebaseApp } from "@/utils/firebaseConfig";
import useInterviewStore from "@/stores/useInterviewStore";
import MicTest from "@/components/mic-test";
import VideoTest from "@/components/video-test-2";

const apiUrl = `${setUrl}`;

Expand All @@ -25,6 +26,7 @@ const InterviewOngoingPreparePage = () => {
let hasFetched = false;

const requestQuestionList = async () => {
console.log("questionRequest: ", questionRequest);
if (!hasFetched) {
hasFetched = true;
await axios.post(`${apiUrl}/question`, questionRequest, {
Expand Down Expand Up @@ -140,13 +142,21 @@ const InterviewOngoingPreparePage = () => {

return (
<div className="flex flex-col items-center space-y-6">
{interview.interviewMethod === "CHAT" ? (
{interview.interviewMethod === "CHAT" && (
<Loading title="질문 생성 쀑" description="μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όμ„Έμš”." />
) : isReady ? (
)}
{interview.interviewMethod === "VOICE" && isReady && (
<Loading title="질문 생성 쀑" description="μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όμ„Έμš”." />
) : (
)}
{interview.interviewMethod === "VOICE" && !isReady && (
<MicTest handleSetReady={handleSetReady} />
)}
{interview.interviewMethod === "VIDEO" && isReady && (
<Loading title="질문 생성 쀑" description="μž μ‹œλ§Œ κΈ°λ‹€λ €μ£Όμ„Έμš”." />
)}
{interview.interviewMethod === "VIDEO" && !isReady && (
<VideoTest handleSetReady={handleSetReady} />
)}
</div>
);
};
Expand Down
153 changes: 116 additions & 37 deletions src/components/RecordingIndicator.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,111 @@
// "use client";

// import { motion } from "framer-motion";
// import { BsMicFill } from "react-icons/bs";

// const RecordingIndicator = ({ isRecording }: { isRecording: boolean }) => {
// const ringVariants = {
// active: {
// scale: [1, 1.3, 1],
// opacity: [0.5, 0.2, 0.5],
// borderColor: ["#60a5fa", "#c084fc"],
// transition: {
// duration: 1.2,
// repeat: Infinity,
// ease: "easeInOut",
// },
// },
// inactive: {
// scale: 1,
// opacity: 0.3,
// borderColor: "#cbd5e1",
// },
// };

// const highlightVariants = {
// active: {
// rotate: [0, 360],
// transition: {
// duration: 2,
// repeat: Infinity,
// ease: "linear",
// },
// },
// inactive: { rotate: 0 },
// };

// return (
// <div className="relative flex flex-col items-center gap-4">
// <motion.div className="relative flex items-center justify-center rounded-full w-32 h-32 bg-white">
// <BsMicFill className="text-blue-600 text-4xl z-10" />

// <motion.div
// className="absolute inset-0 rounded-full border-4"
// variants={ringVariants}
// initial="inactive"
// animate={isRecording ? "active" : "inactive"}
// />

// <motion.div
// className="absolute inset-0 rounded-full border-[6px] border-transparent"
// style={{
// borderTopColor: "rgba(96, 165, 250, 0.5)",
// borderBottomColor: "rgba(192, 132, 252, 0.5)",
// }}
// variants={highlightVariants}
// animate={isRecording ? "active" : "inactive"}
// />
// </motion.div>
// <p className="mt-5 text-lg font-semibold text-gray-500">
// {isRecording ? "λ‹΅λ³€ 쀑" : "닡변을 μ€€λΉ„ν•΄μ£Όμ„Έμš”"}
// </p>
// </div>
// );
// };

// export default RecordingIndicator;

"use client";

import { motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import { BsMicFill } from "react-icons/bs";

const RecordingIndicator = ({ isRecording }: { isRecording: boolean }) => {
const ringVariants = {
const CameraIndicator = ({ isRecording }: { isRecording: boolean }) => {
const videoRef = useRef<HTMLVideoElement | null>(null);
const [isCameraOn, setIsCameraOn] = useState(false);

useEffect(() => {
console.log("isRecording", isRecording);
const enableCamera = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
});
if (videoRef.current) {
videoRef.current.srcObject = stream;
setIsCameraOn(true);
}
} catch (error) {
console.error("Error accessing camera: ", error);
setIsCameraOn(false);
}
};

enableCamera();
console.log("isCameraOn", isCameraOn);
// else {
// if (videoRef.current && videoRef.current.srcObject) {
// const tracks = (videoRef.current.srcObject as MediaStream).getTracks();
// tracks.forEach((track) => track.stop());
// videoRef.current.srcObject = null;
// setIsCameraOn(false);
// }
// }
}, []);

const borderVariants = {
active: {
scale: [1, 1.3, 1],
opacity: [0.5, 0.2, 0.5],
borderColor: ["#60a5fa", "#c084fc"],
transition: {
duration: 1.2,
Expand All @@ -16,51 +114,32 @@ const RecordingIndicator = ({ isRecording }: { isRecording: boolean }) => {
},
},
inactive: {
scale: 1,
opacity: 0.3,
borderColor: "#cbd5e1",
},
};

const highlightVariants = {
active: {
rotate: [0, 360],
transition: {
duration: 2,
repeat: Infinity,
ease: "linear",
},
},
inactive: { rotate: 0 },
};

return (
<div className="relative flex flex-col items-center gap-4">
<motion.div className="relative flex items-center justify-center rounded-full w-32 h-32 bg-white">
<BsMicFill className="text-blue-600 text-4xl z-10" />

<motion.div
className="absolute inset-0 rounded-full border-4"
variants={ringVariants}
initial="inactive"
animate={isRecording ? "active" : "inactive"}
/>

<motion.div
className="absolute inset-0 rounded-full border-[6px] border-transparent"
style={{
borderTopColor: "rgba(96, 165, 250, 0.5)",
borderBottomColor: "rgba(192, 132, 252, 0.5)",
}}
variants={highlightVariants}
animate={isRecording ? "active" : "inactive"}
<motion.div
className="relative w-64 h-64 rounded-full overflow-hidden border-4"
variants={borderVariants}
animate={isRecording ? "active" : "inactive"}
>
<video
ref={videoRef}
autoPlay
muted
playsInline
className="w-full h-full object-cover"
/>
</motion.div>
<BsMicFill className="text-blue-600 text-4xl z-10" />

<p className="mt-5 text-lg font-semibold text-gray-500">
{isRecording ? "λ‹΅λ³€ 쀑" : "닡변을 μ€€λΉ„ν•΄μ£Όμ„Έμš”"}
</p>
</div>
);
};

export default RecordingIndicator;
export default CameraIndicator;
6 changes: 4 additions & 2 deletions src/components/interview/step/check-info-step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ const CheckInfoStep = ({ onPrev, onNext, onSubmit }: StepSubmitProps) => {
{
interviewTitle: getInterviewTitle(interview),
interviewType: interview.interviewType,
interviewMethod: interview.interviewMethod,
interviewMethod:
interview.interviewMethod === "CHAT" ? "CHAT" : "VOICE",
interviewMode: interview.interviewMode,
jobId: interview.jobId,
questionCount: selectedQuestionCount,
Expand Down Expand Up @@ -186,7 +187,8 @@ const CheckInfoStep = ({ onPrev, onNext, onSubmit }: StepSubmitProps) => {
interviewTitle: data.data.interviewTitle,
interviewStatus: data.data.interviewStatus,
interviewType: data.data.interviewType,
interviewMethod: data.data.interviewMethod,
interviewMethod:
data.data.interviewMethod === "CHAT" ? "CHAT" : "VOICE",
interviewMode: data.data.interviewMode,
questionCount: data.data.questionCount,
files:
Expand Down
3 changes: 1 addition & 2 deletions src/components/interview/step/method-step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,9 @@ const MethodStep = ({ onPrev, onNext }: StepProps) => {
/>
<SettingBtn
label="μ˜μƒ"
description="COMING SOON!"
description="웹캠을 ν‚€κ³  μ˜μƒ 면접을 μ§„ν–‰ν•©λ‹ˆλ‹€."
selected={selectedMethod === "VIDEO"}
onClick={() => setSelectedMethod("VIDEO")}
disabled={true}
/>
</div>

Expand Down
Loading