big updates

added read more
added a prompt to skip reading and auto scroll to sign
moved the erase button to the sig pad so easier to access

updated icon on submit button
This commit is contained in:
fISHIE
2026-01-21 13:17:58 +05:00
parent 1f3d3f13e1
commit 37e6b642ae
4 changed files with 80 additions and 39 deletions
+18 -2
View File
@@ -1,4 +1,4 @@
import { useState } from "react"; import { useState, useRef } from "react";
import { usePetition } from "@/hooks/usePetition"; import { usePetition } from "@/hooks/usePetition";
import { useLanguage } from "@/hooks/useLanguage"; import { useLanguage } from "@/hooks/useLanguage";
import { submitSignature } from "@/lib/api"; import { submitSignature } from "@/lib/api";
@@ -10,6 +10,7 @@ import { AuthorCard } from "@/components/petition/AuthorCard";
import { PetitionBody } from "@/components/petition/PetitionBody"; import { PetitionBody } from "@/components/petition/PetitionBody";
import { SignatureForm } from "@/components/signature/SignatureForm"; import { SignatureForm } from "@/components/signature/SignatureForm";
import { TweetModal } from "@/components/TweetModal"; import { TweetModal } from "@/components/TweetModal";
import { PenLine } from "lucide-react";
function getPetitionIdFromUrl(): string | null { function getPetitionIdFromUrl(): string | null {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
@@ -23,6 +24,11 @@ function App() {
const { petition, loading, error, refetch } = usePetition(petitionId); const { petition, loading, error, refetch } = usePetition(petitionId);
const { language, setLanguage } = useLanguage(); const { language, setLanguage } = useLanguage();
const [showTweetModal, setShowTweetModal] = useState(false); const [showTweetModal, setShowTweetModal] = useState(false);
const signatureFormRef = useRef<HTMLDivElement>(null);
const scrollToSignForm = () => {
signatureFormRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
};
const handleSubmit = async (data: { const handleSubmit = async (data: {
name: string; name: string;
@@ -77,6 +83,14 @@ function App() {
<PetitionHeader petition={petition} language={language} /> <PetitionHeader petition={petition} language={language} />
<button
onClick={scrollToSignForm}
className={`w-full flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition-colors ${language === "dv" ? "flex-row-reverse dhivehi" : ""}`}
>
<PenLine className="w-5 h-5" />
{language === "dv" ? "މިހާރު ސޮއި ކުރައްވާ" : "Sign Now"}
</button>
<AuthorCard <AuthorCard
author={petition.authorDetails} author={petition.authorDetails}
language={language} language={language}
@@ -88,7 +102,9 @@ function App() {
language={language} language={language}
/> />
<SignatureForm language={language} onSubmit={handleSubmit} /> <div ref={signatureFormRef}>
<SignatureForm language={language} onSubmit={handleSubmit} />
</div>
<TweetModal <TweetModal
open={showTweetModal} open={showTweetModal}
@@ -1,6 +1,7 @@
import { useMemo } from "react"; import { useMemo, useState } from "react";
import { marked } from "marked"; import { marked } from "marked";
import DOMPurify from "dompurify"; import DOMPurify from "dompurify";
import { ChevronDown, ChevronUp } from "lucide-react";
import type { Language } from "@/types/petition"; import type { Language } from "@/types/petition";
interface PetitionBodyProps { interface PetitionBodyProps {
@@ -9,11 +10,14 @@ interface PetitionBodyProps {
language: Language; language: Language;
} }
const COLLAPSED_HEIGHT = 300;
export function PetitionBody({ export function PetitionBody({
bodyEng, bodyEng,
bodyDhiv, bodyDhiv,
language, language,
}: PetitionBodyProps) { }: PetitionBodyProps) {
const [isExpanded, setIsExpanded] = useState(false);
const content = language === "dv" ? bodyDhiv : bodyEng; const content = language === "dv" ? bodyDhiv : bodyEng;
const isRtl = language === "dv"; const isRtl = language === "dv";
@@ -24,21 +28,46 @@ export function PetitionBody({
return ( return (
<div className="mt-8"> <div className="mt-8">
<div <div className="relative">
className={`leading-loose prose prose-slate max-w-none text-slate-800 md:prose-lg <div
prose-p:mb-6 prose-p:mt-0 className={`leading-loose prose prose-slate max-w-none text-slate-800 md:prose-lg
prose-headings:text-slate-900 prose-headings:font-bold prose-headings:mb-6 prose-headings:mt-12 prose-p:mb-6 prose-p:mt-0
prose-headings:text-slate-900 prose-headings:font-bold prose-headings:mb-6 prose-headings:mt-12
[&_hr]:my-8! [&_hr]:border-slate-300! [&_hr]:my-8! [&_hr]:border-slate-300!
[&_ul]:list-disc! [&_ul]:my-6! [&_ul]:list-disc! [&_ul]:my-6!
[&_ol]:list-decimal! [&_ol]:my-6! [&_ol]:list-decimal! [&_ol]:my-6!
[&_li]:my-2! [&_li]:my-2!
[&_li::before]:content-none! [&_li::before]:content-none!
${isRtl ? "dhivehi dir-rtl text-right [&_ul]:pr-12! [&_ol]:pr-12!" : "text-left [&_ul]:pl-12! [&_ol]:pl-12!"}`} ${isRtl ? "dhivehi dir-rtl text-right [&_ul]:pr-12! [&_ol]:pr-12!" : "text-left [&_ul]:pl-12! [&_ol]:pl-12!"}
dangerouslySetInnerHTML={{ __html: htmlContent }} ${!isExpanded ? "overflow-hidden" : ""}`}
/> style={!isExpanded ? { maxHeight: `${COLLAPSED_HEIGHT}px` } : undefined}
dangerouslySetInnerHTML={{ __html: htmlContent }}
/>
{!isExpanded && (
<div className="absolute bottom-0 left-0 right-0 h-24 bg-gradient-to-t from-white to-transparent pointer-events-none" />
)}
</div>
<button
onClick={() => setIsExpanded(!isExpanded)}
className={`mt-4 flex items-center gap-2 text-blue-600 hover:text-blue-700 font-medium transition-colors ${isRtl ? "flex-row-reverse" : ""}`}
>
{isExpanded ? (
<>
<ChevronUp className="w-5 h-5" />
{language === "dv" ? "ކުޑަކޮށް ދައްކާ" : "Show Less"}
</>
) : (
<>
<ChevronDown className="w-5 h-5" />
{language === "dv" ? "އިތުރަށް ކިޔާ" : "Read More"}
</>
)}
</button>
</div> </div>
); );
} }
@@ -6,7 +6,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { SignaturePad, type SignaturePadRef } from "./SignaturePad"; import { SignaturePad, type SignaturePadRef } from "./SignaturePad";
import { Turnstile } from "@marsidev/react-turnstile"; import { Turnstile } from "@marsidev/react-turnstile";
import { Eraser, Send, CheckCircle, AlertCircle } from "lucide-react"; import { Eraser, Check, CheckCircle, AlertCircle } from "lucide-react";
import type { Language } from "@/types/petition"; import type { Language } from "@/types/petition";
interface SignatureFormProps { interface SignatureFormProps {
@@ -200,8 +200,18 @@ export function SignatureForm({ language, onSubmit }: SignatureFormProps) {
<Label className={`text-slate-700 ${isRtl ? "dhivehi" : ""}`}> <Label className={`text-slate-700 ${isRtl ? "dhivehi" : ""}`}>
{language === "en" ? "Signature" : "ސޮއި"} {language === "en" ? "Signature" : "ސޮއި"}
</Label> </Label>
<div className="border border-input rounded-lg overflow-hidden shadow-sm hover:border-ring/50 transition-colors"> <div className="relative border border-input rounded-lg overflow-hidden shadow-sm hover:border-ring/50 transition-colors">
<SignaturePad ref={signaturePadRef} /> <SignaturePad ref={signaturePadRef} />
<Button
type="button"
variant="ghost"
onClick={handleClear}
size="icon"
className="absolute top-2 right-2 h-8 w-8 text-red-500 hover:text-red-700 hover:bg-red-50/80 bg-white/70 backdrop-blur-sm"
title={language === "en" ? "Clear Signature" : "ފޮހެލާ"}
>
<Eraser className="h-4 w-4" />
</Button>
</div> </div>
<p className={`text-xs text-slate-500 ${isRtl ? "dhivehi" : ""}`}> <p className={`text-xs text-slate-500 ${isRtl ? "dhivehi" : ""}`}>
{language === "en" {language === "en"
@@ -243,38 +253,25 @@ export function SignatureForm({ language, onSubmit }: SignatureFormProps) {
</Alert> </Alert>
)} )}
{/* Turnstile */} {/* Turnstile and Submit */}
<div className="flex justify-center sm:justify-start"> <div className="flex flex-col items-center sm:items-start gap-4">
<Turnstile <Turnstile
siteKey="0x4AAAAAACHH4QC3wIhkCuhd" siteKey="0x4AAAAAACHH4QC3wIhkCuhd"
onSuccess={setTurnstileToken} onSuccess={setTurnstileToken}
onError={() => setTurnstileToken(null)} onError={() => setTurnstileToken(null)}
onExpire={() => setTurnstileToken(null)} onExpire={() => setTurnstileToken(null)}
/> />
</div>
{/* Form Buttons */}
<div className={`flex gap-4 pt-4 ${isRtl ? "flex-row-reverse" : ""}`}>
<Button
type="button"
variant="ghost"
onClick={handleClear}
className={`text-slate-500 hover:text-slate-900 ${isRtl ? "dhivehi" : ""}`}
>
<Eraser className="h-4 w-4 mr-2" />
{language === "en" ? "Clear Signature" : "ފޮހެލާ"}
</Button>
<Button <Button
type="submit" type="submit"
disabled={isSubmitting} disabled={isSubmitting}
size="lg" size="lg"
className={`min-w-[140px] shadow-md hover:shadow-lg transition-all ${isRtl ? "dhivehi" : ""}`} className={`w-[300px] bg-green-600 hover:bg-green-700 shadow-md hover:shadow-lg transition-all ${isRtl ? "dhivehi" : ""}`}
> >
{isSubmitting ? ( {isSubmitting ? (
<span className="animate-pulse">Submitting...</span> <span className="animate-pulse">Submitting...</span>
) : ( ) : (
<> <>
<Send className="h-4 w-4 mr-2" /> <Check className="h-5 w-5 mr-2" />
{language === "en" ? "Submit Petition" : "ހުށަހެޅުއް"} {language === "en" ? "Submit Petition" : "ހުށަހެޅުއް"}
</> </>
)} )}
@@ -60,8 +60,7 @@ export const SignaturePad = forwardRef<SignaturePadRef, SignaturePadProps>(
ref={sigCanvasRef} ref={sigCanvasRef}
penColor="black" penColor="black"
canvasProps={{ canvasProps={{
className: "w-full", className: "w-full aspect-[2/1] md:aspect-[3/1]",
style: { aspectRatio: "3", height: "auto" },
}} }}
onBegin={onBegin} onBegin={onBegin}
onEnd={onEnd} onEnd={onEnd}