Skip to content

adding plan for the user #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
97 changes: 76 additions & 21 deletions packages/frontend/src/pages/PLTestPage.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even need this page? Please confirm on me if we can just delete it or we still need it.

@saraisa12 @ThatIsHero

Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { Option, Question } from './PlacementTest';
import { sections as questions } from './Questions';
import { Link } from 'react-router-dom';
import FButton from '../components/FButton';
import TButton from '../components/TButton';
import { post } from 'aws-amplify/api';
import { toJSON } from '../utilities';

/**
* What increments to use for each level
*
* First key specifies whether the student answered correctly or not. Second key specifies the question level.
*
*
*/
const knowledgeBase = {
correct: {
A1: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1],
Expand All @@ -36,9 +31,9 @@ type CFRLevel = (typeof CFRLevels)[number];

export const PLTestPage = () => {
const [showDev, setShowDev] = useState(false);

const [cf, setCf] = useState([0, 0, 0, 0, 0, 0]);
const [questionCount, setQuestionCount] = useState(0);
const [resultHandled, setResultHandled] = useState(false);
const selectedLevel = selectLevel(cf);
const [level, question] = getRandomQuestion(selectedLevel);

Expand All @@ -47,17 +42,59 @@ export const PLTestPage = () => {
const handleClick = (option: Option) => {
console.log(`correct: ${option.isCorrect}`);
const CFRL = CFRLevels[selectedLevel] as CFRLevel;
const t1 = option.isCorrect ? knowledgeBase.correct : knowledgeBase.incorrect;
const t1 = option.isCorrect
? knowledgeBase.correct
: knowledgeBase.incorrect;
const increments = t1[CFRL];

const cfCopy = increments.map((value, index) => combineCf(value, cf[index]));
const cfCopy = increments.map((value, index) =>
combineCf(value, cf[index]),
);

setCf(cfCopy);
setQuestionCount(prevCount => prevCount + 1);
};

const highestScoringLevel = getHighestScoringLevel(cf);

const handleResult = async () => {
console.log('handleResult function started');

const highestScoringLevel = getHighestScoringLevel(cf); // Get the highest scoring level
console.log('Highest scoring level:', highestScoringLevel);

try {
const response = await toJSON(
post({
apiName: 'myAPI',
path: '/addPlan',
options: {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: {
planType: 'vocab',
level: highestScoringLevel, // Include the level in the request body
},
},
}),
);

console.log('Response from Lambda:', response);
} catch (error) {
console.error('There was a problem with the API operation:', error);
}

setResultHandled(true); // Mark result as handled
};

useEffect(() => {
if (!resultHandled && questionCount >= 20) {
handleResult();
}
}, [resultHandled, questionCount]);

return (
<>
{questionCount < 20 ? (
Expand All @@ -68,27 +105,39 @@ export const PLTestPage = () => {
<DevPanel cf={cf} question={question} level={level} />
</div>
<div className="w-full flex justify-center pb-36">
<h1 className="text-5xl font-bold text-[#363534]">Placement Test</h1>
<h1 className="text-5xl font-bold text-[#363534]">
Placement Test
</h1>
</div>
<RenderQuestion question={question} handleClick={handleClick} />
</main>
</>
) : (
<main className="w-full h-full flex items-center flex-col">
<div className="w-1/2 flex flex-col items-center">
<h3 className="font-bold text-4xl pb-12 max-sm:text-2xl">You are all set!</h3>
<h3 className="font-bold text-4xl pb-12 max-sm:text-2xl">
You are all set!
</h3>
</div>
<div className="w-1/2 flex flex-col items-center">
<div className='w-full'><h4 className='text-2xl font-semibold max-sm:text-lg'>Your current level is</h4></div>
<div className="w-full">
<h4 className="text-2xl font-semibold max-sm:text-lg">
Your current level is
</h4>
</div>
<img
src={`assets/Levels/${highestScoringLevel}.png`}
alt={highestScoringLevel}
className='w-60 pt-8'
className="w-60 pt-8"
/>
</div>
<div className='flex flex-row w-full justify-center items-center pt-36 gap-x-14'>
<Link to="/home"><FButton label="View Plan" tag="3B828E"/></Link>
<Link to="/home"><TButton label='Return Home'/></Link>
<div className="flex flex-row w-full justify-center items-center pt-36 gap-x-14">
<Link to="/home">
<FButton label="View Plan" tag="3B828E" />
</Link>
<Link to="/home">
<TButton label="Return Home" />
</Link>
</div>
</main>
)}
Expand All @@ -107,8 +156,12 @@ const RenderQuestion = ({
}) => (
<div className="w-3/4 flex flex-col items-center max-sm:w-full">
<div className="w-full">
<h3 className="font-bold text-4xl pb-12 mx-10 max-sm:text-2xl">{question.text}</h3>
<h5 className="font-bold text-2xl pb-12 mx-10 max-sm:text-lg">{question.sub}</h5>
<h3 className="font-bold text-4xl pb-12 mx-10 max-sm:text-2xl">
{question.text}
</h3>
<h5 className="font-bold text-2xl pb-12 mx-10 max-sm:text-lg">
{question.sub}
</h5>
</div>
<div className="flex flex-row w-full flex-wrap justify-between">
{question.options.map(option => {
Expand Down Expand Up @@ -149,7 +202,9 @@ const DevPanel = ({
question: Question;
level: number;
}) => {
const correctAnswer = question.options.findIndex(({ isCorrect }) => isCorrect);
const correctAnswer = question.options.findIndex(
({ isCorrect }) => isCorrect,
);

return (
<>
Expand Down
45 changes: 41 additions & 4 deletions packages/frontend/src/pages/PlacementTest.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your changes to this page conflict with the UI changes to the page #287, and I'm not comfortable with the current use of useEffect, but I'll look into that later.

For now, I think you can either try to resolve the conflicts, and if you need help in that, get in contact with the rest of the team.

Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Nav } from '../components/Nav';
import Button from '../components/FButton';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { sections } from './Questions';
import { post } from 'aws-amplify/api';

export interface Question {
text: string;
Expand Down Expand Up @@ -34,6 +35,7 @@ const PlacementTest = () => {
const [sectionScore, setSectionScore] = useState(0);
const [level, setLevel] = useState('');
const [sectionSummary, setSectionSummary] = useState<Selected[]>([]);
const [resultHandled, setResultHandled] = useState(false); // New state

const optionClicked = (text: string) => {
const currentSectionQuestions = sections[currentSection - 1];
Expand Down Expand Up @@ -74,32 +76,67 @@ const PlacementTest = () => {
setCurrentQuestion(0); // Start from the first question
setSectionSummary([]); // Clear the summary array
setShowFeedback(false); // Hide the feedback section
} else {
handleResult(); // Call handleResult before showing the result
}
};
const handleResult = () => {

const handleResult = async () => {
console.log('handleResult function started');
let updatedScore = sectionScore;

if (score >= 5) {
updatedScore += 1;
}

console.log('Calculated updatedScore:', updatedScore);

if (updatedScore < 2) {
setLevel('A1');
console.log('Level set to A1');
} else if (updatedScore === 2) {
setLevel('A2');
console.log('Level set to A2');
} else if (updatedScore === 3) {
setLevel('B1');
console.log('Level set to B1');
} else if (updatedScore === 4) {
setLevel('B2');
console.log('Level set to B2');
} else if (updatedScore === 5) {
setLevel('C1');
console.log('Level set to C1');
} else if (updatedScore === 6) {
setLevel('C2');
console.log('Level set to C2');
}
console.log('Invoking Lambda function via API.post');

try {
console.log('Invoking Lambda function via API.post');
const response = await post({
apiName: 'myAPI',
path: '/addPlan',
body: {
planType: 'vocab',
},
});

console.log('Response from Lambda:', response);
} catch (error) {
console.error('There was a problem with the API operation:', error);
}

setShowResult(true);
setResultHandled(true); // Mark result as handled
setShowResult(true); // Show the result after handling it
};

useEffect(() => {
if (showResult && !resultHandled) {
handleResult();
}
}, [showResult, resultHandled]);

return (
<main className="bg-[#FBF9F1] h-full min-h-screen">
<Nav />
Expand Down Expand Up @@ -140,7 +177,7 @@ const PlacementTest = () => {
<Button label="Next Section" tag="3B828E" />
</div>
) : (
<div className="w-1/2" onClick={handleResult}>
<div className="w-1/2" onClick={() => setShowResult(true)}>
<Button label="Show Result" tag="3B828E" />
</div>
)}
Expand Down
115 changes: 115 additions & 0 deletions packages/functions/src/updateplan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
DynamoDBDocumentClient,
GetCommand,
UpdateCommand,
} from '@aws-sdk/lib-dynamodb';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { Table } from 'sst/node/table';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

const client = new DynamoDBClient({});
const dynamoDb = DynamoDBDocumentClient.from(client);

export const main = async (
event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => {
console.log('Lambda function invoked');
console.log('Event body:', event.body);

let data;
try {
if (!event.body) {
throw new Error('Request body is missing');
}
const body = event.isBase64Encoded
? Buffer.from(event.body, 'base64').toString('utf-8')
: event.body;
data = JSON.parse(body);
} catch (err) {
console.error('Error parsing request body:', err.message);
return {
statusCode: 400,
body: JSON.stringify({ error: 'Invalid JSON in request body' }),
};
}

const planType = data.planType;
console.log('Received plan type:', planType);
const level = data.level;
console.log('Received level:', level);

const userId = event.requestContext.authorizer?.jwt.claims.sub;
console.log('user id: ', userId);

if (!userId) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'User ID not found in the token' }),
};
}

const tableName = Table.Records.tableName;

if (!tableName) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Table name not set' }),
};
}

const key = {
PK: userId,
SK: 'plan', // Fixed SK value
};

try {
console.log('Fetching existing item...');
const getParams = { TableName: tableName, Key: key };
const getCommand = new GetCommand(getParams);
const result = await dynamoDb.send(getCommand);
const existingItem = result.Item;

if (!existingItem) {
console.log('Item not found');
return {
statusCode: 404,
body: JSON.stringify({ error: 'Item not found' }),
};
}

console.log('Item found, updating...');
const updateParams = {
TableName: tableName,
Key: key,
UpdateExpression: `set #planType = :value`,
ExpressionAttributeNames: {
'#planType': planType,
},
Comment on lines +120 to +122
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this add a check to limit planType to only allowed values. Security 101: Don't trust user inputs!

ExpressionAttributeValues: {
':value': 'test', // Update with the value you want
},
ReturnValues: 'ALL_NEW' as const,
};

const updateCommand = new UpdateCommand(updateParams);
const updateResult = await dynamoDb.send(updateCommand);
console.log('Item updated successfully');

return {
statusCode: 200,
body: JSON.stringify({
status: 'success',
updatedAttributes: updateResult.Attributes,
}),
};
} catch (error) {
console.error('Error updating item:', error.message);
return {
statusCode: 500,
body: JSON.stringify({
error: 'Could not update item',
details: error.message,
}),
};
}
};
Loading