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

#514 Implement voting on Community note #516

Merged
merged 13 commits into from
Dec 14, 2024
136 changes: 136 additions & 0 deletions checkers-app/src/components/myvotes/CommunityNoteCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, {useState} from "react";
import {Card, CardBody, Typography, Button, Collapse} from "@material-tailwind/react";
import { UserIcon, LinkIcon } from "@heroicons/react/24/solid";

interface PropType {
en: string;
cn: string;
links: string[];
downvoted: boolean;
}

// Helper function to detect URLs and split the text
const splitTextByUrls = (text: string) => {
// This regex will match URLs
const urlRegex = /(https?:\/\/[^\s]+)/g;
let match;
let lastIndex = 0;
const parts = [];

// Find al matches and their indices
while ((match = urlRegex.exec(text)) !== null) {
const url = match[0];
const index = match.index;

// Push text before URL
if (index > lastIndex) {
parts.push({ text: text.substring(lastIndex, index), isUrl: false});
}

// Push URL
parts.push({ text: url, isUrl: true});

// Update lastIndex to end of current URL
lastIndex = index + url.length;
}

// Push remaining text after last URL
if (lastIndex < text.length) {
parts.push({ text: text.substring(lastIndex), isUrl: false});
}

return parts;
}

export default function CommunityNoteCard(prop: PropType){
const [open, setOpen] = useState(false);
const toggleOpen = () => setOpen((cur) => !cur);

const [isExpanded, setIsExpanded] = useState(false);
const lengthBeforeTruncation = 300;
const { en, cn, links, downvoted } = prop;

const toggleExpansion = () => {
setIsExpanded(!isExpanded);
};

let displayText = en ?? "";

const shouldTruncate = displayText.length > lengthBeforeTruncation;
const textToShow =
isExpanded || !shouldTruncate
? displayText
: displayText.slice(0, lengthBeforeTruncation) + "...";

// Split text by URLs
const textParts = splitTextByUrls(textToShow)

return (
<Card className="bg-blue-100 overflow-y-auto overflow-x-hidden max-w-md w-full h-full max-h-full p-3 mb-2 mt-3">
<CardBody className="-m-3">
<Typography className="flex items-center mb-2">
<UserIcon className="h-6 w-6 text-[#ff8932] mr-2 flex-shrink-0"/>
<p className="font-semibold leading-none">Community Note:</p>
</Typography>

<Typography className="w-full">
{textParts.map((part, index) => {
// Split the text part by new lines
const lines = part.text.split("\n");
return (
<React.Fragment key={index}>
{lines.map((line, lineIndex) => (
<React.Fragment key={lineIndex}>
{part.isUrl ? (
<a
href={line}
target="_blank"
rel="noopener noreferrer"
className="text-blue-500 hover:underline"
>
{line}
</a>
) : (
<span>{line}</span>
)}
{lineIndex < lines.length - 1 && <br />}
</React.Fragment>
))}
</React.Fragment>
);
})}
</Typography>
{shouldTruncate && (
<Button
onClick={toggleExpansion}
variant="text"
className="p-0 text-primary-color3"
size="sm"
>
{isExpanded ? "Show Less" : "Read More"}
</Button>
)}

{links.length > 0 ? (
<>
<Typography className="pt-4">
<p className="font-semibold leading-none">Reference Links:</p>
</Typography>
<ul className="list-disc pt-1">
{links.map((link) => {
return (
<li className = "flex gap-x-2">
<LinkIcon aria-hidden = "true" className='h-6 w-5 flex-none'/>
<a href={link} rel="noopener noreferrer"
className="text-blue-600 hover:underline">{link}</a>
</li>
)
})}
</ul>
</>
) : null}

</CardBody>
</Card>
)
}
92 changes: 92 additions & 0 deletions checkers-app/src/components/vote/CommunityNoteCategories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useState } from "react";
import { TrophyIcon } from "@heroicons/react/20/solid";
import { CheckBadgeIcon } from "@heroicons/react/24/solid";
import { XCircleIcon } from "@heroicons/react/24/outline";
import { Button } from "@material-tailwind/react"
import { ForwardIcon } from "@heroicons/react/24/solid";

interface PropType {
messageId: string | null;
voteRequestId: string | null;
currentCommunityCategory: string | null;
onNextStep: (value: number) => void;
onSelectCommunityCategory: (communityCategory: string | null) => void;
}

const CATEGORIES = [
{
name: "great",
icon: <TrophyIcon className = "h-7 w-7" />,
display: "Great",
description: "Good response, can't do any better",
},
{
name: "acceptable",
icon: <CheckBadgeIcon className = "h-7 w-7" />,
display: "Acceptable",
description: "Acceptable response, but can be improved"
},
{
name: "unacceptable",
icon: <XCircleIcon className="h-7 w-7" />,
display: "Unacceptable",
description: "Unacceptable response"
}
]


export default function CommunityNoteCategories(Prop: PropType) {
const currentCategory = Prop.currentCommunityCategory
const messageId = Prop.messageId;
const voteRequestId = Prop.voteRequestId;
const [selectedCategory, setSelectedCategory] = useState<string | null>(currentCategory);
const [communityCategory, setCommunityCategory] = useState<string | null>(currentCategory);

const handleCommunityCategoryChange = (category: string) => {
switch(category) {
default:
setCommunityCategory(category);
Prop.onSelectCommunityCategory(category);
break;
}
}

const handleSelection = (categoryName: string) => {
setSelectedCategory(categoryName);
handleCommunityCategoryChange(categoryName);
}

const handleNextStep = (value: number) => {
Prop.onNextStep(value)
}

return (
<div className="grid grid-flow-row gap-y-4 items-center">
{CATEGORIES.map((cat, index) => (
<>
<Button
className={`flex flex-row items-center justify-start gap-2 max-w-md space-x-3 text-sm
${
selectedCategory === cat.name
? "bg-primary-color3"
: "bg-primary-color"}`}
key={index}
onClick={() => handleSelection(cat.name)}
>
{cat.icon}
{cat.display}
</Button>
</>
))}

{communityCategory ? (
<Button fullWidth className="flex items-center justify-center gap-3 bg-green-400" size="sm"
onClick = {() => handleNextStep(2)}
>
Move to next step
<ForwardIcon className="h-5 w-5"/>
</Button>
) : null}
</div>
)
}
Loading
Loading