Skip to content
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

add report interface #860

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
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
142 changes: 112 additions & 30 deletions src/app/harbor/battles/battles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ import {
} from '@/app/utils/airtable'
import useLocalStorageState from '../../../../lib/useLocalStorageState'
import { useToast } from '@/hooks/use-toast'
import { HsSession } from '@/app/utils/auth'
// import { HsSession } from '@/app/utils/auth'

import SpeechToText from '@/components/speech-to-text'
import Blessed from './blessed'
import Cursed from './cursed'
import pluralize from '../../../../lib/pluralize'

interface Matchup {
project1: Ships
Expand All @@ -34,6 +33,7 @@ interface ProjectCardProps {
project: Ships
onVote: () => void
onReadmeClick: () => void
onReport: () => void
}

const notFoundImages = [
Expand All @@ -49,6 +49,7 @@ const ProjectCard: React.FC<ProjectCardProps> = ({
project,
onVote,
onReadmeClick,
onReport
}) => {
const notFoundImage = useMemo(() => {
return notFoundImages[Math.floor(Math.random() * notFoundImages.length)]
Expand All @@ -67,6 +68,8 @@ const ProjectCard: React.FC<ProjectCardProps> = ({

return (
<div className="bg-white dark:bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-all duration-300 hover:shadow-xl">
<span onClick={onReport} className='text-5xl absolute z-50 pt-2 drop-shadow-2xl shadow-black opacity-40 hover:opacity-100 cursor-pointer transition-all'>🏴‍☠️</span>
<span onClick={onReport} className='text-5xl absolute z-50 pt-2 drop-shadow-2xl shadow-black opacity-40 hover:opacity-100 cursor-pointer transition-all'>🏴‍☠️</span>
{project.screenshot_url && (
<div className="relative h-48 w-full" style={imageStyle}>
<Image
Expand Down Expand Up @@ -245,7 +248,7 @@ const markdownComponents: Components = {
),
}

export default function Matchups({ session }: { session: HsSession }) {
export default function Matchups(/*{ session }: { session: HsSession }*/) {
const [matchup, setMatchup] = useState<Matchup | null>(null)
const [loading, setLoading] = useState(true)
const [selectedProject, setSelectedProject] = useState<Ships | null>(null)
Expand All @@ -255,6 +258,7 @@ export default function Matchups({ session }: { session: HsSession }) {
const [readmeContent, setReadmeContent] = useState('')
const [isReadmeView, setIsReadmeView] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const [reportedProject, setReportedProject] = useState<Ships | null>(null)
const [cursed, setCursed] = useState(false)
const [blessed, setBlessed] = useState(false)

Expand All @@ -268,12 +272,12 @@ export default function Matchups({ session }: { session: HsSession }) {

const { toast } = useToast()

useEffect(() => {
safePerson().then((sp) => {
setCursed(sp.cursed)
setBlessed(sp.blessed)
})
})
// useEffect(() => {
// safePerson().then((sp) => {
// setCursed(sp.cursed)
// setBlessed(sp.blessed)
// })
// })

useEffect(() => {
setFewerThanTenWords(reason.trim().split(' ').length < 10)
Expand Down Expand Up @@ -305,7 +309,7 @@ export default function Matchups({ session }: { session: HsSession }) {
// }, [selectedProject]);

const fetchVoteBalance = async () => {
setVoteBalance(await getVotesRemainingForNextPendingShip(session.slackId))
// setVoteBalance(await getVotesRemainingForNextPendingShip(session.slackId))
}

const fetchMatchup = async (
Expand All @@ -315,23 +319,19 @@ export default function Matchups({ session }: { session: HsSession }) {
try {
// require at least 1.25 seconds of loading time for full loop of loading animations
const [response, _] = await Promise.all([
fetch(
'/api/battles/matchups' +
(sessionStorage.getItem('tutorial') === 'true'
? '?tutorial=true'
: ''),
),
fetch('/api/battles/matchups'),
new Promise((r) => setTimeout(r, 1250)),
])
if (response.ok) {
const data: Matchup = await response.json()
setMatchup(data)
console.log(data)
} else {
console.error('Failed to fetch matchup')

toast({
title: 'There are no ships to battle right now.',
description: 'Searching again automatically',
title: 'Failed to fetch a new thing to vote on.',
description: 'Retrying automatically...',
})

setTimeout(
Expand Down Expand Up @@ -360,6 +360,7 @@ export default function Matchups({ session }: { session: HsSession }) {

const handleVoteClick = (project: Ships) => {
setSelectedProject(project)
setReportedProject(null)

if (sessionStorage.getItem('tutorial') === 'true') {
setReason(
Expand Down Expand Up @@ -387,10 +388,10 @@ export default function Matchups({ session }: { session: HsSession }) {
return
}

if (selectedProject && matchup && session) {
if (selectedProject && matchup /*&& session*/) {
setIsSubmitting(true)
try {
const slackId = session.slackId
// const slackId = session.slackId
const winner = selectedProject
const loser =
selectedProject.id === matchup.project1.id
Expand All @@ -405,7 +406,7 @@ export default function Matchups({ session }: { session: HsSession }) {
ts: matchup.ts,
project1: matchup.project1,
project2: matchup.project2,
slackId,
// slackId,
explanation: reason,
winner: winner.id,
loser: loser.id,
Expand All @@ -416,11 +417,11 @@ export default function Matchups({ session }: { session: HsSession }) {
})

if (response.ok) {
// const json = await response.json();
// if (json.reload) {
// window.location.reload();
// return;
// }
const json = await response.json();
if (json.reload) {
window.location.reload();
return;
}

setSelectedProject(null)
setReason('')
Expand Down Expand Up @@ -452,6 +453,11 @@ export default function Matchups({ session }: { session: HsSession }) {
}
}

const handleReportClick = (project: Ships)=>{
setReportedProject(project)
setSelectedProject(null)
}

if (isReadmeView) {
return (
<div className="min-h-[75vh] bg-gradient-to-br from-indigo-50 to-purple-50 dark:from-gray-900 dark:to-indigo-900 p-4 sm:p-6 md:p-8">
Expand Down Expand Up @@ -486,13 +492,13 @@ export default function Matchups({ session }: { session: HsSession }) {
to skip!)
</p>

{blessed && <Blessed />}
{cursed && <Cursed />}
{/* {blessed && <Blessed />}
{cursed && <Cursed />} */}

{voteBalance > 0 && (
<div className="flex justify-center items-center space-x-4">
{voteBalance} more {pluralize(voteBalance, 'vote', false)} until
your next ship's payout!
{voteBalance} more vote{voteBalance == 1 ? '' : 's'} until your
next ship's payout!
</div>
)}
</header>
Expand Down Expand Up @@ -521,6 +527,7 @@ export default function Matchups({ session }: { session: HsSession }) {
project={matchup.project1}
onVote={() => handleVoteClick(matchup.project1)}
onReadmeClick={() => handleReadmeClick(matchup.project1)}
onReport={()=> handleReportClick(matchup.project1)}
/>
</div>
<div className="flex items-center justify-center text-6xl font-bold text-indigo-600 dark:text-indigo-300">
Expand All @@ -531,6 +538,7 @@ export default function Matchups({ session }: { session: HsSession }) {
project={matchup.project2}
onVote={() => handleVoteClick(matchup.project2)}
onReadmeClick={() => handleReadmeClick(matchup.project2)}
onReport={()=> handleReportClick(matchup.project2)}
/>
</div>
</div>
Expand Down Expand Up @@ -601,6 +609,80 @@ export default function Matchups({ session }: { session: HsSession }) {
<SpeechToText handleResults={handleAudioTranscription} />
</div>
)}
{/* show container for report like for voting */}
{reportedProject && (
<div
id="report-reason-container-parent"
className="mt-12 bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6"
>
<div id="report-reason-container">
<h3 className="text-2xl text-center font-semibold text-indigo-600 dark:text-indigo-300 mb-4">
🏴‍☠️Why are you reporting {reportedProject.title}?🏴‍☠️
</h3>
<select name="" id="" className='w-full text-center border border-gray-300 dark:border-gray-600 rounded my-4 p-1'>
<option value="type">Select the type of fraud</option>
<option value="repo">Wrong / no repo</option>
<option value="demo">Wrong / no demo</option>
<option value="tutorial">Copying a tutorial / template (link to it below)</option>
<option value="copyCode">Copying someone else's code (link to original below)</option>
<option value="other">Other (specify below)</option>
</select>
<textarea
value={reason}
onChange={(e) => setReason(e.target.value)}
placeholder="Provide your reason here (minimum 10 words)"
className="w-full p-4 border border-gray-300 dark:border-gray-600 rounded-md mb-4 text-gray-900 dark:text-white bg-white dark:bg-gray-700 min-h-[150px]"
rows={6}
/>

{error && (
<p className="text-red-500 text-sm mb-4">{error}</p>
)}
</div>

<button
id="submit-report"
// onClick={handleReportSubmit} submit the report to airtable
disabled={
isSubmitting || reason.trim().split(' ').length < 10
}
className={`bg-indigo-500 hover:bg-indigo-600 text-white font-semibold py-3 px-6 mr-3 rounded-lg transition-colors duration-200 text-lg w-full sm:w-auto disabled:opacity-50 disabled:cursor-not-allowed ${
isSubmitting ? 'opacity-50 cursor-not-allowed' : ''
}`}
>
{reason.trim().split(' ').length < 10 ? (
`${10 - reason.trim().split(' ').length} words left...`
) : isSubmitting ? (
<>
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline-block"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Submitting...
</>
) : (
'Submit Report'
)}
</button>
<SpeechToText handleResults={handleAudioTranscription} />
</div>
)}
</>
)}
</div>
Expand Down