Skip to content
Closed

Azamjb4 #11129

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
c6ff697
Created /challenges route
1elasmarjad Sep 13, 2025
b690a42
Challenge stuff is workn
1elasmarjad Sep 13, 2025
dcd7b8d
Revert "Challenge stuff is workn"
1elasmarjad Sep 13, 2025
43f6dd2
Revert "Created /challenges route"
1elasmarjad Sep 13, 2025
703ac6d
color
azamjb Sep 13, 2025
9d374d6
Added /challenge
1elasmarjad Sep 13, 2025
c784064
Merge branch 'main' of https://github.com/1elasmarjad/ai-testing
1elasmarjad Sep 13, 2025
bfa7b1e
colorway
1elasmarjad Sep 13, 2025
080d3cd
Removed header
1elasmarjad Sep 13, 2025
4963fbe
Added challenge type
1elasmarjad Sep 13, 2025
a2d13a1
Update ChallengeChat.tsx
1elasmarjad Sep 13, 2025
c3962d7
Added challenge reference to chats + removed sidebar
1elasmarjad Sep 13, 2025
cac0d9d
title shows up as question
1elasmarjad Sep 13, 2025
2c17ca1
navbar and profile page
azamjb Sep 13, 2025
c33d37a
profile changes
azamjb Sep 13, 2025
664f3b0
using newest claude model
1elasmarjad Sep 13, 2025
18034ae
Removed enhance chat button
1elasmarjad Sep 13, 2025
e7f0a3c
Added gemini 2.5 flash
1elasmarjad Sep 13, 2025
7fe4402
solve page
azamjb Sep 13, 2025
8ac06c5
Added pulsing circle
1elasmarjad Sep 13, 2025
410fefe
edits
azamjb Sep 13, 2025
23b7aa3
Merge pull request #1 from 1elasmarjad/azamjb
1elasmarjad Sep 13, 2025
c6cbce7
Update useChatHistory.ts
1elasmarjad Sep 13, 2025
a1a3203
chats show up again if u go bkck
1elasmarjad Sep 13, 2025
f976532
Remove header on chat pages
1elasmarjad Sep 13, 2025
85a13c4
added monitor icon
1elasmarjad Sep 13, 2025
20338e9
Update HeaderActionButtons.client.tsx
1elasmarjad Sep 13, 2025
81c56d3
Added "view target"
1elasmarjad Sep 13, 2025
92efe7f
added image to challenges.json
1elasmarjad Sep 13, 2025
cea9245
Added view target
1elasmarjad Sep 13, 2025
f82cd94
Update Preview.tsx
1elasmarjad Sep 13, 2025
64db496
routing for challenge cards
azamjb Sep 14, 2025
0341082
Update Header.tsx
1elasmarjad Sep 14, 2025
6048b5b
Merge branch 'azamjb' of https://github.com/1elasmarjad/ai-testing
1elasmarjad Sep 14, 2025
3602b53
Delete challenge.counter.tsx
1elasmarjad Sep 14, 2025
a3dfd42
reverted some stuff
1elasmarjad Sep 14, 2025
448302b
Added profile
1elasmarjad Sep 14, 2025
d92f914
css stuff added
1elasmarjad Sep 14, 2025
9a01496
loading challenges from json
1elasmarjad Sep 14, 2025
637d531
updated challenge chat
1elasmarjad Sep 14, 2025
c758149
Update _index.tsx
1elasmarjad Sep 14, 2025
c23a7be
Removed sidebar
1elasmarjad Sep 14, 2025
56ebf2f
Audio for prompts
1elasmarjad Sep 14, 2025
6390f2b
frontend edits
azamjb Sep 14, 2025
be8e97e
Delete app/routes/challenge.counter.tsx
azamjb Sep 14, 2025
dabefb3
Merge pull request #2 from 1elasmarjad/azamjb3
azamjb Sep 14, 2025
bd351ef
Prompt marking
1elasmarjad Sep 14, 2025
a02d76b
Merge branch 'main' of https://github.com/1elasmarjad/ai-testing
1elasmarjad Sep 14, 2025
72558cc
bug fixes and new features
azamjb Sep 14, 2025
5cc1153
Merge pull request #3 from 1elasmarjad/azamjb3
azamjb Sep 14, 2025
360a0db
prompt marking
1elasmarjad Sep 14, 2025
9e3423d
Promptly change
1elasmarjad Sep 14, 2025
446fc93
submit logic, more features
azamjb Sep 14, 2025
6670d02
Update _index.tsx
1elasmarjad Sep 14, 2025
a6ebffc
Merge pull request #4 from 1elasmarjad/azamjb3
azamjb Sep 14, 2025
c9aa713
Update Header.tsx
1elasmarjad Sep 14, 2025
a61471e
Update api.mark-prompt.ts
1elasmarjad Sep 14, 2025
9ce5fa7
more frontend
azamjb Sep 14, 2025
9934f96
Added results page
1elasmarjad Sep 14, 2025
8cfc834
Merge branch 'azamjb3' of https://github.com/1elasmarjad/ai-testing
1elasmarjad Sep 14, 2025
ca534e7
navbar fix
azamjb Sep 14, 2025
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
15 changes: 15 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"permissions": {
"allow": [
"mcp__ide__getDiagnostics",
"WebSearch",
"Bash(pnpm update:*)",
"Bash(pnpm typecheck:*)",
"WebFetch(domain:ai-sdk.dev)",
"Bash(pnpm list:*)",
"WebFetch(domain:www.npmjs.com)"
],
"deny": [],
"ask": []
}
}
Binary file added Amplitude Web 14.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions app/components/challenge/BackToChallengesButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { useNavigate } from '@remix-run/react';

export function BackToChallengesButton() {
const navigate = useNavigate();
return (
<button
onClick={() => navigate('/')}
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-bolt-elements-button-secondary-background hover:bg-bolt-elements-button-secondary-backgroundHover text-bolt-elements-button-secondary-text font-semibold shadow border border-bolt-elements-borderColor transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-bolt-elements-accent/60"
style={{ minWidth: 0 }}
>
<svg width="20" height="20" fill="none" viewBox="0 0 20 20" className="inline-block mr-1">
<path
d="M12.5 15L7.5 10L12.5 5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Back
</button>
);
}
101 changes: 101 additions & 0 deletions app/components/challenge/ChallengeCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useState, useEffect } from 'react';

// utility to clear solved state on app load
if (typeof window !== 'undefined') {
window.addEventListener('load', () => {
Object.keys(localStorage).forEach((key) => {
if (key.startsWith('challenge-solved-')) {
localStorage.removeItem(key);
}
});
});
}

export type ChallengeCardProps = {
id: string;
title: string;
image: string;
difficulty: 'Easy' | 'Medium' | 'Hard';
averageAccuracy?: number; // percentage, optional for backward compatibility
onClick?: () => void;
solved?: boolean;
};

export function ChallengeCard({
id,
title,
image,
difficulty,
averageAccuracy,
onClick,
solved: solvedProp,
}: ChallengeCardProps) {
const [solved, setSolved] = useState(solvedProp || false);

useEffect(() => {
// listen for challenge:submit event and mark as solved if id matches
function handleSubmit(e: CustomEvent) {
if (e.detail && e.detail.id === id) {
setSolved(true);
localStorage.setItem(`challenge-solved-${id}`, '1');
}
}
window.addEventListener('challenge:submit', handleSubmit as EventListener);

// on mount, check localStorage
if (localStorage.getItem(`challenge-solved-${id}`) === '1') {
setSolved(true);
}

return () => {
window.removeEventListener('challenge:submit', handleSubmit as EventListener);
};
}, [id]);

const difficultyColor =
difficulty === 'Easy' ? 'text-green-500' : difficulty === 'Medium' ? 'text-yellow-500' : 'text-red-500';

return (
<div
className={`cursor-pointer bg-bolt-elements-background-depth-2 rounded-lg shadow border border-bolt-elements-borderColor hover:shadow-lg transition flex flex-col overflow-hidden group w-full max-w-xs mx-auto relative ${solved ? 'ring-4 ring-green-400 ring-opacity-60' : ''}`}
onClick={onClick}
tabIndex={0}
role="button"
aria-label={`Open challenge ${title}`}
style={{ minHeight: 220 }}
>
<div className="h-40 w-full bg-gray-100 flex items-center justify-center overflow-hidden relative">
<img
src={image}
alt={title}
className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-200"
/>
{solved && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-green-400/40 text-white font-extrabold text-xl rounded-lg transition-all duration-300">
<svg width="40" height="40" fill="none" viewBox="0 0 24 24" className="mb-2 opacity-80">
<circle cx="12" cy="12" r="10" stroke="white" strokeWidth="2" fill="none" />
<path d="M7 13l3 3 7-7" stroke="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<span className="opacity-90">Solved!</span>
</div>
)}
</div>
<div className="p-3 flex-1 flex flex-col">
<div className="flex items-center justify-between mb-1">
<h2 className="text-base font-bold text-bolt-elements-textPrimary truncate" title={title}>
{title}
</h2>
<div className="flex items-center gap-2">
{typeof averageAccuracy === 'number' && (
<span className="text-xs font-semibold text-blue-500 flex items-center">
{averageAccuracy}%
<span className="mx-2 h-4 border-l border-bolt-elements-borderColor" />
</span>
)}
<span className={`text-xs font-semibold ${difficultyColor}`}>{difficulty}</span>
</div>
</div>
</div>
</div>
);
}
35 changes: 35 additions & 0 deletions app/components/challenge/ChallengeNavbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { BackToChallengesButton } from './BackToChallengesButton';
import { ChallengeTimer } from './ChallengeTimer';

export function ChallengeNavbar({
challenge,
timerProps,
onSubmit,
}: {
challenge: { id: string };
timerProps: {
start: boolean;
duration?: number;
onExpire?: () => void;
};
onSubmit?: () => void;
}) {
return (
<nav className="w-full flex items-center justify-between px-6 py-3 bg-bolt-elements-background-depth-2 border-b border-bolt-elements-borderColor shadow z-50 relative">
<div className="flex items-center h-full">
<BackToChallengesButton />
</div>
<div className="flex items-center gap-2">
<button
className="px-4 py-2 rounded bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-button-primary-text font-semibold shadow border border-bolt-elements-borderColor transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-bolt-elements-accent/60"
onClick={onSubmit}
type="button"
>
Submit
</button>
<ChallengeTimer {...timerProps} challenge={challenge} />
</div>
</nav>
);
}
102 changes: 102 additions & 0 deletions app/components/challenge/ChallengeTimer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, { useEffect, useRef, useState } from 'react';
import { useNavigate } from '@remix-run/react';
import { SubmissionConfirmation } from './SubmissionConfirmation';

export function ChallengeTimer({
start,
duration = 20 * 60,
onExpire,
challenge,
onPreSubmission,
onSubmission,
}: {
start: boolean;
duration?: number;
onExpire?: () => void;
challenge: { id: string };
onPreSubmission?: () => void;
onSubmission?: () => void;
}) {
const [secondsLeft, setSecondsLeft] = useState(duration);
const [showConfirmation, setShowConfirmation] = useState(false);
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const navigate = useNavigate();

useEffect(() => {
if (!start) {
return;
}

if (intervalRef.current) {
return;
}

intervalRef.current = setInterval(() => {
setSecondsLeft((s) => {
if (s <= 1) {
clearInterval(intervalRef.current!);
intervalRef.current = null;

if (onExpire) {
onExpire();
}

return 0;
}

return s - 1;
});
}, 1000);

return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [start, onExpire]);

const handlePreSubmission = () => {
if (onPreSubmission) {
onPreSubmission();
}
};

const handleSubmission = () => {
if (onSubmission) {
onSubmission();
}

console.log(`Challenge: ${JSON.stringify(challenge)}`);

// Always execute the default submission logic
if (challenge?.id) {
localStorage.setItem(`challenge-solved-${challenge.id}`, '1');
window.dispatchEvent(new CustomEvent('challenge:submit', { detail: { id: challenge.id } }));
}

// TODO get the users mark here

// Redirect to landing page
navigate('/result?prompt_score=5&quality_score=100&speed_score=5');
};

const minutes = Math.floor(secondsLeft / 60);
const seconds = secondsLeft % 60;

return (
<div className="bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary px-4 py-2 rounded-lg shadow font-bold text-lg flex items-center gap-2 select-none">
<svg width="20" height="20" fill="none" viewBox="0 0 20 20" className="inline-block mr-1">
<circle cx="10" cy="10" r="8" stroke="currentColor" strokeWidth="2" fill="none" />
<path d="M10 5v5l3 3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
{minutes}:{seconds.toString().padStart(2, '0')}
<SubmissionConfirmation
isOpen={showConfirmation}
onClose={() => setShowConfirmation(false)}
onPreSubmission={handlePreSubmission}
onSubmission={handleSubmission}
challenge={challenge}
/>
</div>
);
}
55 changes: 55 additions & 0 deletions app/components/challenge/SubmissionConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import { Dialog, DialogRoot, DialogTitle, DialogDescription, DialogButton, DialogClose } from '../ui/Dialog';

export interface SubmissionConfirmationProps {
isOpen: boolean;
onClose: () => void;
onPreSubmission: () => void;
onSubmission: () => void;
challenge: { id: string };
}

export function SubmissionConfirmation({
isOpen,
onClose,
onPreSubmission,
onSubmission,
challenge,
}: SubmissionConfirmationProps) {
const handleConfirmSubmission = () => {
onSubmission();
onClose();
};

const handleCancel = () => {
onClose();
};

React.useEffect(() => {
if (isOpen) {
onPreSubmission();
}
}, [isOpen, onPreSubmission]);

return (
<DialogRoot open={isOpen} onOpenChange={onClose}>
<Dialog onClose={handleCancel} onBackdrop={handleCancel}>
<DialogTitle>
Submit Challenge
<DialogClose />
</DialogTitle>
<DialogDescription>
Are you ready to submit your solution for this challenge? This action cannot be undone.
</DialogDescription>
<div className="flex justify-end gap-3 px-5 py-4 border-t border-bolt-elements-borderColor">
<DialogButton type="secondary" onClick={handleCancel}>
Cancel
</DialogButton>
<DialogButton type="primary" onClick={handleConfirmSubmission}>
Submit Solution
</DialogButton>
</div>
</Dialog>
</DialogRoot>
);
}
14 changes: 11 additions & 3 deletions app/components/chat/Artifact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
workbenchStore.showWorkbench.set(!showWorkbench);
}}
>
<div className="px-5 p-3.5 w-full text-left">
<div className="w-full text-bolt-elements-textPrimary font-medium leading-5 text-sm">{artifact?.title}</div>
<div className="w-full w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
<div className="px-5 p-3.5 w-full text-left flex items-center gap-3">
<div className="i-bolt:monitor text-lg text-bolt-elements-textSecondary" />
<div className="flex-1">
<div className="w-full text-bolt-elements-textPrimary font-medium leading-5 text-sm">{artifact?.title}</div>
<div className="w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
</div>
</div>
</button>
<div className="bg-bolt-elements-artifacts-borderColor w-[1px]" />
Expand Down Expand Up @@ -152,6 +155,8 @@ const ActionList = memo(({ actions }: ActionListProps) => {
<div className={classNames('text-lg', getIconColor(action.status))}>
{status === 'running' ? (
<div className="i-svg-spinners:90-ring-with-bg"></div>
) : status === 'dev-running' ? (
<div className="i-ph:circle-fill animate-pulse scale-75"></div>
) : status === 'pending' ? (
<div className="i-ph:circle-duotone"></div>
) : status === 'complete' ? (
Expand Down Expand Up @@ -197,6 +202,9 @@ function getIconColor(status: ActionState['status']) {
case 'running': {
return 'text-bolt-elements-loader-progress';
}
case 'dev-running': {
return 'text-bolt-elements-icon-success';
}
case 'complete': {
return 'text-bolt-elements-icon-success';
}
Expand Down
Loading
Loading