Skip to content

Commit

Permalink
Added description editing feature with markdown (#513)
Browse files Browse the repository at this point in the history
* Added description editing feature with markdown

* Resolved issue and removed unnecessary console logs
  • Loading branch information
ivntsng authored Oct 28, 2024
1 parent 3c394fe commit a5adfa7
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 33 deletions.
52 changes: 24 additions & 28 deletions frontend/src/components/listing/ListingDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,29 @@ import remarkGfm from "remark-gfm";

interface RenderDescriptionProps {
description: string;
onImageClick?: (src: string, alt: string) => void;
}

export const RenderDescription = ({ description }: RenderDescriptionProps) => {
const [imageModal, setImageModal] = useState<[string, string] | null>(null);

export const RenderDescription = ({
description,
onImageClick,
}: RenderDescriptionProps) => {
return (
<>
<div className="w-full">
<Markdown
remarkPlugins={[remarkGfm]}
className="w-full flex flex-col gap-2"
components={{
p: ({ children }) => <p className="mb-1">{children}</p>,
ul: ({ children }) => <ul className="list-disc ml-4">{children}</ul>,
ul: ({ children }) => (
<ul className="list-disc ml-4 w-full flex flex-col gap-1">
{children}
</ul>
),
ol: ({ children }) => (
<ol className="list-decimal ml-4">{children}</ol>
),
li: ({ children }) => <li className="mb-1">{children}</li>,
li: ({ children }) => <li className="w-full">{children}</li>,
table: ({ children }) => (
<table className="table-auto w-full">{children}</table>
),
Expand All @@ -51,7 +58,9 @@ export const RenderDescription = ({ description }: RenderDescriptionProps) => {
{children}
</a>
),
h1: ({ children }) => <h1 className="text-2xl mb-2">{children}</h1>,
h1: ({ children }) => (
<h1 className="text-2xl mb-2 w-full">{children}</h1>
),
h2: ({ children }) => <h2 className="text-xl mb-2">{children}</h2>,
h3: ({ children }) => (
<h3 className="text-lg mb-2 underline">{children}</h3>
Expand All @@ -62,7 +71,7 @@ export const RenderDescription = ({ description }: RenderDescriptionProps) => {
img: ({ src, alt }) => (
<span
className="flex flex-col justify-center w-full mx-auto gap-2 my-4 md:w-2/3 lg:w-1/2 cursor-pointer"
onClick={() => src && setImageModal([src, alt ?? ""])}
onClick={() => src && onImageClick?.(src, alt ?? "")}
>
<img src={src} alt={alt} className="rounded-lg" />
{alt && <span className="text-sm text-center">{alt}</span>}
Expand All @@ -72,25 +81,7 @@ export const RenderDescription = ({ description }: RenderDescriptionProps) => {
>
{description}
</Markdown>

{imageModal !== null && (
<div
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
onClick={() => setImageModal(null)}
>
<div
className="absolute bg-white rounded-lg p-4 max-w-4xl max-h-4xl m-4"
onClick={(e) => e.stopPropagation()}
>
<img
src={imageModal[0]}
alt={imageModal[1]}
className="max-h-full max-w-full"
/>
</div>
</div>
)}
</>
</div>
);
};

Expand All @@ -115,6 +106,8 @@ const ListingDescription = (props: Props) => {
const [debouncedDescription, setDebouncedDescription] = useState(
initialDescription ?? "",
);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [imageModal, setImageModal] = useState<[string, string] | null>(null);

useEffect(() => {
const handler = setTimeout(() => {
Expand Down Expand Up @@ -177,7 +170,10 @@ const ListingDescription = (props: Props) => {
autoFocus
/>
)}
<RenderDescription description={debouncedDescription} />
<RenderDescription
description={debouncedDescription}
onImageClick={(src, alt) => setImageModal([src, alt])}
/>
{edit && (
<Button
onClick={() => {
Expand Down
125 changes: 122 additions & 3 deletions frontend/src/components/products/ProductPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ListingDeleteButton from "@/components/listing/ListingDeleteButton";
import { RenderDescription } from "@/components/listing/ListingDescription";
import ListingVoteButtons from "@/components/listing/ListingVoteButtons";
import CheckoutButton from "@/components/stripe/CheckoutButton";
import { Input } from "@/components/ui/Input/Input";
import { Input, TextArea } from "@/components/ui/Input/Input";
import Spinner from "@/components/ui/Spinner";
import { Button } from "@/components/ui/button";
import { useAlertQueue } from "@/hooks/useAlertQueue";
Expand Down Expand Up @@ -36,7 +36,7 @@ const ProductPage: React.FC<ProductPageProps> = ({
productId,
checkoutLabel,
title,
description,
description: initialDescription,
features = [],
price,
images = [],
Expand Down Expand Up @@ -212,6 +212,37 @@ const ProductPage: React.FC<ProductPageProps> = ({
setSubmitting(false);
};

const [isEditingDescription, setIsEditingDescription] = useState(false);
const [newDescription, setNewDescription] = useState(initialDescription);
const [descriptionHasChanged, setDescriptionHasChanged] = useState(false);

const handleSaveDescription = async () => {
if (!descriptionHasChanged) {
setIsEditingDescription(false);
return;
}
if (newDescription.length < 6) {
addErrorAlert("Description must be at least 6 characters long.");
return;
}
setSubmitting(true);
const { error } = await auth.client.PUT("/listings/edit/{id}", {
params: {
path: { id: productId },
},
body: {
description: newDescription,
},
});
if (error) {
addErrorAlert(error);
} else {
addAlert("Listing updated successfully", "success");
setIsEditingDescription(false);
}
setSubmitting(false);
};

return (
<Container>
<div ref={contentRef}>
Expand Down Expand Up @@ -420,7 +451,95 @@ const ProductPage: React.FC<ProductPageProps> = ({
<div className="py-8">
<div className="space-y-4">
<div className="text-black leading-6">
<RenderDescription description={description} />
{submitting ? (
<Spinner />
) : (
<>
{isEditingDescription ? (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">
Edit Description (Markdown supported)
</label>
<TextArea
value={newDescription}
onKeyDown={(e) => {
if (e.key === "Enter" && e.ctrlKey) {
handleSaveDescription();
}
}}
onChange={(e) => {
setNewDescription(e.target.value);
setDescriptionHasChanged(true);
}}
className="min-h-[300px] font-mono text-sm p-4"
placeholder="# Heading 1
## Heading 2
**Bold text**
*Italic text*
- Bullet point
- Another point
1. Numbered list
2. Second item
[Link text](https://example.com)
![Image alt text](image-url.jpg)"
autoFocus
/>
</div>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">
Preview
</label>
<div className="border rounded-md p-4 min-h-[200px] bg-gray-50">
<RenderDescription
description={newDescription}
/>
</div>
</div>
</div>
<div className="flex gap-2">
<Button
onClick={handleSaveDescription}
variant="primary"
size="sm"
disabled={!descriptionHasChanged}
>
Save
</Button>
<Button
onClick={() => {
setIsEditingDescription(false);
setNewDescription(initialDescription);
setDescriptionHasChanged(false);
}}
variant="ghost"
size="sm"
>
Cancel
</Button>
</div>
</div>
) : (
<div className="flex items-start gap-2">
<RenderDescription description={newDescription} />
{creatorInfo?.can_edit && (
<Button
onClick={() => setIsEditingDescription(true)}
variant="ghost"
className="-mr-3"
>
<FaPen />
</Button>
)}
</div>
)}
</>
)}
</div>
{features.length > 0 && (
<ul className="list-disc list-inside text-gray-700">
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/hooks/useAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,9 @@ const getStoredAuth = (): string | null => {

export const setStoredAuth = (id: string) => {
localStorage.setItem(AUTH_KEY_ID, id);
console.log("Auth set in localStorage:", id);

const cookieValue = `${AUTH_KEY_ID}=${id}; path=/; max-age=${7 * 24 * 60 * 60}; SameSite=Strict`;
document.cookie = cookieValue;
console.log("Auth cookie set:", cookieValue);
};

export const deleteStoredAuth = () => {
Expand Down

0 comments on commit a5adfa7

Please sign in to comment.