mirror of
https://github.com/MvDevsUnion/WPetition.git
synced 2026-07-18 21:40:07 +00:00
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:
@@ -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!
|
|
||||||
[&_ol]:list-decimal! [&_ol]:my-6!
|
[&_ul]:list-disc! [&_ul]:my-6!
|
||||||
[&_li]:my-2!
|
[&_ol]:list-decimal! [&_ol]:my-6!
|
||||||
[&_li::before]:content-none!
|
[&_li]:my-2!
|
||||||
|
[&_li::before]:content-none!
|
||||||
${isRtl ? "dhivehi dir-rtl text-right [&_ul]:pr-12! [&_ol]:pr-12!" : "text-left [&_ul]:pl-12! [&_ol]:pl-12!"}`}
|
|
||||||
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
${isRtl ? "dhivehi dir-rtl text-right [&_ul]:pr-12! [&_ol]:pr-12!" : "text-left [&_ul]:pl-12! [&_ol]:pl-12!"}
|
||||||
/>
|
${!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}
|
||||||
|
|||||||
Reference in New Issue
Block a user