diff --git a/installer/install.sh b/installer/install.sh index c2137a7..17e28d6 100644 --- a/installer/install.sh +++ b/installer/install.sh @@ -1,11 +1,18 @@ #!/bin/bash set -e -echo "=== Surafino Installer ===" +echo "==============================" +echo " Surafino Installer" +echo "==============================" +# -------- INPUT -------- read -p "Domain (example.com): " DOMAIN -read -p "DB Host: " DB_HOST +read -p "DB Host (127.0.0.1): " DB_HOST +DB_HOST=${DB_HOST:-127.0.0.1} + read -p "DB Port (3306): " DB_PORT +DB_PORT=${DB_PORT:-3306} + read -p "DB User: " DB_USER read -sp "DB Password: " DB_PASSWORD echo @@ -13,37 +20,44 @@ read -p "DB Name: " DB_NAME JWT_SECRET=$(openssl rand -hex 32) -echo "Installing system packages..." +echo "== Installing system packages ==" apt update -apt install -y nginx certbot python3-certbot-nginx mariadb-client nodejs npm +apt install -y nginx certbot python3-certbot-nginx mariadb-client curl ca-certificates -echo "Installing node dependencies..." +echo "== Installing Node.js 20 LTS ==" +curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +apt install -y nodejs + +echo "== Node version ==" +node -v +npm -v + +echo "== Installing dependencies ==" npm install + +echo "== Building app ==" npm run build -echo "Creating .env..." +echo "== Creating .env ==" cat > .env < /tmp/db.sql + installer/database.sql > /tmp/surafino.sql -mysql \ - -h $DB_HOST \ - -P $DB_PORT \ - -u $DB_USER \ - -p$DB_PASSWORD < /tmp/db.sql +mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" < /tmp/surafino.sql -echo "Configuring nginx..." +echo "== Configuring NGINX ==" sed \ -e "s/{{DOMAIN}}/$DOMAIN/g" \ installer/nginx.conf.template > /etc/nginx/sites-available/surafino @@ -52,17 +66,19 @@ ln -sf /etc/nginx/sites-available/surafino /etc/nginx/sites-enabled/surafino nginx -t systemctl reload nginx -echo "Installing PM2..." +echo "== Installing PM2 ==" npm install -g pm2 pm2 start npm --name surafino -- start pm2 save pm2 startup systemd -u root --hp /root -echo "Requesting SSL certificate..." -certbot --nginx -d $DOMAIN --non-interactive --agree-tos -m admin@$DOMAIN +echo "== Installing SSL (Let's Encrypt) ==" +certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos -m admin@"$DOMAIN" || true -echo "=== INSTALLATION COMPLETE ===" -echo "Admin login:" -echo " username: admin" -echo " password: admin" -echo "Please change password immediately!" +echo "==============================" +echo " INSTALLATION COMPLETE " +echo "==============================" +echo " Admin login:" +echo " username: admin" +echo " password: admin" +echo " !!! CHANGE PASSWORD IMMEDIATELY !!!" diff --git a/src/app/favicon.ico b/src/app/favicon.ico index 718d6fe..d68c874 100644 Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index bc0b1f1..313dc46 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,11 @@ import "./globals.css"; import Navbar from "@/components/Navbar"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Surafino", + description: "Surafino blog & news platform", +}; export default function RootLayout({ children, @@ -8,9 +14,9 @@ export default function RootLayout({ }) { return ( - + - {children} +
{children}
); diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index 3a6a662..66f8f69 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -6,30 +6,35 @@ import Image from "@tiptap/extension-image"; import Link from "@tiptap/extension-link"; import { Video } from "@/components/extensions/Video"; -export default function Editor({ - content, - onChange, -}: { +type Props = { content: string; onChange: (html: string) => void; -}) { +}; + +export default function Editor({ content, onChange }: Props) { const editor = useEditor({ extensions: [ StarterKit, Image, - Link.configure({ openOnClick: false }), + Link.configure({ + openOnClick: false, + }), Video, ], content, - immediatelyRender: false, // Next.js 16 SSR fix + immediatelyRender: false, // SSR fix for Next 16 onUpdate({ editor }) { onChange(editor.getHTML()); }, }); - if (!editor) return null; - - async function upload(file: File) { + /* ========================= + UPLOAD HELPER + ========================= */ + async function upload(file: File): Promise<{ + url: string; + type: "image" | "video"; + }> { const data = new FormData(); data.append("file", file); @@ -42,18 +47,30 @@ export default function Editor({ throw new Error("Upload failed"); } - return res.json() as Promise<{ - url: string; - type: "image" | "video"; - }>; + return res.json(); } + /* ========================= + ADD IMAGE + ========================= */ async function addImage(file: File) { + if (!editor) return; + const { url } = await upload(file); - editor.chain().focus().setImage({ src: url }).run(); + + editor + .chain() + .focus() + .setImage({ src: url }) + .run(); } + /* ========================= + ADD VIDEO + ========================= */ async function addVideo(file: File) { + if (!editor) return; + const { url } = await upload(file); editor @@ -67,14 +84,26 @@ export default function Editor({ type: file.type, }, }, - { type: "paragraph" }, + { + type: "paragraph", + }, ]) .run(); } + if (!editor) { + return ( +
+ Loading editor… +
+ ); + } + return (
- {/* TOOLBAR */} + {/* ========================= + TOOLBAR + ========================= */}
editor.chain().focus().toggleBold().run()} + onClick={() => + editor.chain().focus().toggleBold().run() + } className="editor-btn" > Bold @@ -92,7 +123,9 @@ export default function Editor({
- {/* CONTENT */} + {/* ========================= + CONTENT + ========================= */}