diff --git a/frontend/app/Markdown.css b/frontend/app/Markdown.css new file mode 100644 index 0000000..5c3b857 --- /dev/null +++ b/frontend/app/Markdown.css @@ -0,0 +1,65 @@ +.markdown-body { + font-family: "JetBrains Mono", sans-serif; +} + +h1 { + font-size: 2.5rem; + font-weight: bold; + padding-bottom: 0.25rem; +} + +h2 { + font-size: 2rem; + font-weight: bold; + padding-bottom: 0.25rem; +} + +h3 { + font-size: 1.75rem; + font-weight: bold; + padding-bottom: 0.25rem; +} + +h4 { + font-size: 1.5rem; + font-weight: bold; + padding-bottom: 0.25rem; +} + +h5 { + font-size: 1.25rem; + font-weight: bold; + padding-bottom: 0.25rem; +} + +h6 { + font-size: 1rem; + font-weight: bold; + padding-bottom: 0.25rem; +} + +ul { + padding-left: 1.5rem; + list-style-type: disc; +} + +ol { + padding-left: 1.5rem; + list-style-type: decimal; +} + +li { + margin-bottom: 0.5rem; +} + +table { + border-collapse: collapse; + width: 100%; +} + +th, +td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} \ No newline at end of file diff --git a/frontend/app/components/MarkdownContent.tsx b/frontend/app/components/MarkdownContent.tsx new file mode 100644 index 0000000..9888d06 --- /dev/null +++ b/frontend/app/components/MarkdownContent.tsx @@ -0,0 +1,27 @@ +"use client" +import React, { createContext, useState, useContext } from 'react'; + +interface MarkdownContextProps { + isMarkdown: boolean; + setIsMarkdown: (value: boolean) => void; +} + +const MarkdownContext = createContext(undefined); + +export const MarkdownProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [isMarkdown, setIsMarkdown] = useState(false); + + return ( + + {children} + + ); +}; + +export const useMarkdown = () => { + const context = useContext(MarkdownContext); + if (!context) { + throw new Error('useMarkdown must be used within a MarkdownProvider'); + } + return context; +}; \ No newline at end of file diff --git a/frontend/app/components/Navbar.tsx b/frontend/app/components/Navbar.tsx index e87e2f0..2405aaa 100644 --- a/frontend/app/components/Navbar.tsx +++ b/frontend/app/components/Navbar.tsx @@ -1,129 +1,221 @@ -'use client'; -import { useState, useEffect } from 'react'; -import Image from 'next/image'; -import axios from 'axios'; +"use client"; +import { useState, useEffect } from "react"; +import Image from "next/image"; +import axios from "axios"; +import { toast, ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import { useMarkdown } from "./MarkdownContent"; -const Navbar = ({ content, titleN }: { content: string, titleN:string }) => { +const Navbar = ({ content, titleN, extensionN }: { content: string; titleN: string; extensionN: string }) => { const [isOpen, setIsOpen] = useState(false); const [title, setTitle] = useState(titleN); + const [extension, setExtension] = useState(extensionN); const [isLoggedIn, setIsLoggedIn] = useState(false); - const [oAuthToken, setOAuthToken] = useState(''); + const [oAuthToken, setOAuthToken] = useState(""); + const { setIsMarkdown } = useMarkdown(); const toggleMenu = () => { setIsOpen(!isOpen); }; useEffect(() => { - const token = localStorage.getItem('token') ?? ''; + const token = localStorage.getItem("token") ?? ""; setOAuthToken(token); - setIsLoggedIn(!!token); + setIsLoggedIn(!!token); // Set isLoggedIn to true if token exists, otherwise false }, []); const handleTitleChange = (event: React.ChangeEvent) => { - setTitle(event.target.value); + const newTitle = event.target.value; + setTitle(newTitle); + const newExtension = newTitle.split(".").pop(); + setExtension(newExtension || ""); + setIsMarkdown(newExtension === "md"); }; const handleSave = async () => { - const [fileName, extension] = title.split('.'); + const [fileName, extension] = title.split("."); const payload = { title: fileName, content, - extension: extension || '.txt', - url: fileName.replace(/\s+/g, '-').toLocaleLowerCase(), + extension: extension || ".txt", + url: fileName.replace(/\s+/g, "-").toLocaleLowerCase(), }; console.log(payload); try { - const response = await axios.post('http://localhost:8080/v1/pastebin/create', payload, { - headers: { - Authorization: `Bearer ${oAuthToken}`, - }, - }); - const generatedUrl = `http://localhost:8080/${fileName.replace(/\s+/g, '-').toLocaleLowerCase()}`; - console.log('Saved successfully:', response.data); - console.log('Generated URL:', generatedUrl); + const response = await axios.post( + "http://localhost:8080/v1/pastebin/create", + payload, + { + headers: { + Authorization: `Bearer ${oAuthToken}`, + }, + } + ); + const generatedUrl = `http://localhost:3000/pastebin/${fileName + .replace(/\s+/g, "-") + .toLocaleLowerCase()}`; + navigator.clipboard + .writeText(generatedUrl) + .then(() => { + toast.success("URL copied to clipboard"); + }) + .catch((err) => { + toast.error( +
+ Failed to copy URL: {generatedUrl} + +
+ ); + }); } catch (error) { - console.error('Error saving content:', error); + console.error("Error saving content:", error); } }; const handleLogout = async () => { try { - await axios.post('http://localhost:8080/v1/auth/logout', {}, { - headers: { - Authorization: `Bearer ${oAuthToken}`, - }, - }); - localStorage.removeItem('token'); + await axios.post( + "http://localhost:8080/v1/auth/logout", + {}, + { + headers: { + Authorization: `Bearer ${oAuthToken}`, + }, + } + ); + localStorage.removeItem("token"); setIsLoggedIn(false); - setOAuthToken(''); - console.log('Logged out successfully'); + setOAuthToken(""); + console.log("Logged out successfully"); } catch (error) { - console.error('Error logging out:', error); + console.error("Error logging out:", error); } }; return ( - + ) : ( +
+ +
+ )} + + )} + + ); }; diff --git a/frontend/app/components/Pastebin.tsx b/frontend/app/components/Pastebin.tsx index f04ef98..9c6f617 100644 --- a/frontend/app/components/Pastebin.tsx +++ b/frontend/app/components/Pastebin.tsx @@ -1,40 +1,58 @@ -'use client'; -import { useState } from 'react'; -import Navbar from './Navbar'; -import Footer from './Footer'; +"use client"; +import { useState } from "react"; +import Navbar from "./Navbar"; +import Footer from "./Footer"; +import ReactMarkdown from "react-markdown"; +import { useMarkdown } from "./MarkdownContent"; +import "../Markdown.css"; interface PastebinProps { initialContent?: string; initialTitle?: string; + extension?: string; } -const Pastebin: React.FC = ({ initialContent = '', initialTitle = 'Untitled file' }) => { +const Pastebin: React.FC = ({ + initialContent = "", + initialTitle = "Untitled file", + extension = ".txt", +}) => { const [content, setContent] = useState(initialContent); - const handleContentChange = (event: React.ChangeEvent) => { + const { isMarkdown } = useMarkdown(); + + const handleContentChange = ( + event: React.ChangeEvent + ) => { setContent(event.target.value); }; return ( <> - +
- + {isMarkdown || extension == "md" ? ( +
+ {content} +
+ ) : ( + + )}