Can create new blogs

This commit is contained in:
cdricms
2025-02-21 19:46:36 +01:00
parent 7a97961fef
commit 4b005945b2
9 changed files with 362 additions and 99 deletions

View File

@@ -0,0 +1,100 @@
import { Loader2 } from "lucide-react";
import { Button } from "./ui/button";
import React from "react";
import { cn } from "@/lib/utils";
import { ButtonProps } from "./ui/button";
interface ActionButtonProps extends ButtonProps {
isLoading?: boolean;
isSuccess?: boolean;
error?: any;
className?: string;
children?: React.ReactNode; // Allow custom subcomponents as children
}
const ActionButtonDefault = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, children, ...props }, ref) => (
<span ref={ref} className={cn("flex items-center", className)} {...props}>
{children ?? "Submit"}
</span>
));
const ActionButtonLoading = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, children, ...props }, ref) => (
<ActionButtonDefault ref={ref} className={className} {...props}>
{children ?? (
<>
<Loader2 className="animate-spin mr-2" /> Loading...
</>
)}
</ActionButtonDefault>
));
const ActionButtonSuccess = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, children, ...props }, ref) => (
<ActionButtonDefault ref={ref} className={className} {...props}>
{children ?? <>Success!</>}
</ActionButtonDefault>
));
const ActionButtonError = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, children, ...props }, ref) => (
<ActionButtonDefault ref={ref} className={className} {...props}>
{children ?? <>Error occurred.</>}
</ActionButtonDefault>
));
const ActionButton = React.forwardRef<HTMLButtonElement, ActionButtonProps>(
(
{ variant, isLoading, isSuccess, error, className, children, ...props },
ref,
) => {
let buttonContent = null;
React.Children.forEach(children, (child) => {
if (React.isValidElement(child)) {
if (child.type === ActionButtonLoading && isLoading) {
buttonContent = child;
} else if (child.type === ActionButtonSuccess && isSuccess) {
buttonContent = child;
} else if (child.type === ActionButtonError && error) {
buttonContent = child;
} else if (
child.type === ActionButtonDefault &&
!isLoading &&
!isSuccess &&
!error
) {
buttonContent = child;
}
}
});
return (
<Button
ref={ref}
disabled={isLoading || isSuccess || error !== undefined}
type="submit"
className={`w-full transition-all ease-in-out ${isSuccess ? "bg-green-800" : error ? "bg-red-800" : ""} ${className}`}
{...props}
>
{buttonContent}
</Button>
);
},
);
export {
ActionButton,
ActionButtonLoading,
ActionButtonError,
ActionButtonSuccess,
ActionButtonDefault,
};

View File

@@ -1,12 +1,18 @@
"use client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import useApiMutation from "@/hooks/use-api";
import { useToast } from "@/hooks/use-toast";
import { Loader2 } from "lucide-react";
import { CircleCheckIcon, CircleXIcon, Loader2, SendIcon } from "lucide-react";
import { useState } from "react";
import {
ActionButton,
ActionButtonDefault,
ActionButtonError,
ActionButtonLoading,
ActionButtonSuccess,
} from "./action-button";
interface FormData {
firstname: string;
@@ -159,26 +165,27 @@ const Contact = () => {
required
/>
</div>
<Button
disabled={
isLoading || isSuccess || error !== undefined
}
type="submit"
className={`w-full transition-all ease-in-out ${isSuccess ? "bg-green-800" : error ? "bg-red-800" : ""}`}
<ActionButton
isLoading={isLoading}
isSuccess={isSuccess}
error={error}
>
{isSuccess ? (
<>Message envoyé</>
) : error ? (
<>Échec de l'envoie du mail.</>
) : (
<>
{isLoading && (
<Loader2 className="animate-spin" />
)}
Envoyer
</>
)}
</Button>
<ActionButtonSuccess className="gap-2">
<CircleCheckIcon className="animate-pulse" />
Message envoyé
</ActionButtonSuccess>
<ActionButtonLoading className="gap-2">
<Loader2 className="animate-spin" />{" "}
Chargement...
</ActionButtonLoading>
<ActionButtonError className="gap-2">
<CircleXIcon className="animate-pulse" />
Une erreur est survenue
</ActionButtonError>
<ActionButtonDefault className="gap-2">
<SendIcon /> Envoyer
</ActionButtonDefault>
</ActionButton>
</form>
</div>
</div>

View File

@@ -28,14 +28,32 @@ export function LocalEditor({
setTitle,
}: EditorProps) {
const getTitle = (editor: Editor) => {
const h1s: string[] = [];
editor.state.doc.descendants((node, pos) => {
if (node.type.name === "heading" && node.attrs.level === 1) {
h1s.push(node.textContent);
}
});
const firstNode = editor.state.doc.firstChild;
return h1s.length > 0 ? h1s[0] : null;
if (!firstNode) {
editor.commands.setNode("heading", {
level: 1,
content: [{ type: "text", text: "Titre" }],
});
}
if (
firstNode &&
!(firstNode.type.name === "heading" && firstNode.attrs.level === 1)
) {
setFirstLineAsH1(editor);
}
return firstNode?.textContent;
};
const setFirstLineAsH1 = (editor: Editor) => {
const firstNode = editor.state.doc.firstChild;
// Check if the first node is a paragraph and make it h1
if (firstNode && firstNode.type.name === "paragraph") {
editor.commands.setNode("heading", { level: 1 });
}
};
const editor = useEditor({
@@ -70,7 +88,6 @@ export function LocalEditor({
},
onUpdate: ({ editor }) => {
localStorage.setItem("blog_draft", editor.getHTML());
// Set the first H1 if it exists
const title = getTitle(editor);
setTitle?.(title ?? "");
},