Fix installer, add favicon, improve editor and layout

This commit is contained in:
Jetomit_Bio 2026-01-17 21:07:28 +01:00
parent ea90efc902
commit 75556045c9
4 changed files with 110 additions and 47 deletions

View File

@ -1,11 +1,18 @@
#!/bin/bash #!/bin/bash
set -e set -e
echo "=== Surafino Installer ===" echo "=============================="
echo " Surafino Installer"
echo "=============================="
# -------- INPUT --------
read -p "Domain (example.com): " DOMAIN 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 read -p "DB Port (3306): " DB_PORT
DB_PORT=${DB_PORT:-3306}
read -p "DB User: " DB_USER read -p "DB User: " DB_USER
read -sp "DB Password: " DB_PASSWORD read -sp "DB Password: " DB_PASSWORD
echo echo
@ -13,37 +20,44 @@ read -p "DB Name: " DB_NAME
JWT_SECRET=$(openssl rand -hex 32) JWT_SECRET=$(openssl rand -hex 32)
echo "Installing system packages..." echo "== Installing system packages =="
apt update 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 npm install
echo "== Building app =="
npm run build npm run build
echo "Creating .env..." echo "== Creating .env =="
cat > .env <<EOF cat > .env <<EOF
DB_HOST=$DB_HOST DB_HOST=$DB_HOST
DB_PORT=$DB_PORT DB_PORT=$DB_PORT
DB_USER=$DB_USER DB_USER=$DB_USER
DB_PASSWORD=$DB_PASSWORD DB_PASSWORD=$DB_PASSWORD
DB_NAME=$DB_NAME DB_NAME=$DB_NAME
JWT_SECRET=$JWT_SECRET JWT_SECRET=$JWT_SECRET
APP_URL=https://$DOMAIN APP_URL=https://$DOMAIN
EOF EOF
echo "Setting up database..." echo "== Setting up database =="
sed \ sed \
-e "s/{{DB_NAME}}/$DB_NAME/g" \ -e "s/{{DB_NAME}}/$DB_NAME/g" \
installer/database.sql > /tmp/db.sql installer/database.sql > /tmp/surafino.sql
mysql \ mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASSWORD" < /tmp/surafino.sql
-h $DB_HOST \
-P $DB_PORT \
-u $DB_USER \
-p$DB_PASSWORD < /tmp/db.sql
echo "Configuring nginx..." echo "== Configuring NGINX =="
sed \ sed \
-e "s/{{DOMAIN}}/$DOMAIN/g" \ -e "s/{{DOMAIN}}/$DOMAIN/g" \
installer/nginx.conf.template > /etc/nginx/sites-available/surafino 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 nginx -t
systemctl reload nginx systemctl reload nginx
echo "Installing PM2..." echo "== Installing PM2 =="
npm install -g pm2 npm install -g pm2
pm2 start npm --name surafino -- start pm2 start npm --name surafino -- start
pm2 save pm2 save
pm2 startup systemd -u root --hp /root pm2 startup systemd -u root --hp /root
echo "Requesting SSL certificate..." echo "== Installing SSL (Let's Encrypt) =="
certbot --nginx -d $DOMAIN --non-interactive --agree-tos -m admin@$DOMAIN certbot --nginx -d "$DOMAIN" --non-interactive --agree-tos -m admin@"$DOMAIN" || true
echo "=== INSTALLATION COMPLETE ===" echo "=============================="
echo "Admin login:" echo " INSTALLATION COMPLETE "
echo " username: admin" echo "=============================="
echo " password: admin" echo " Admin login:"
echo "Please change password immediately!" echo " username: admin"
echo " password: admin"
echo " !!! CHANGE PASSWORD IMMEDIATELY !!!"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -1,5 +1,11 @@
import "./globals.css"; import "./globals.css";
import Navbar from "@/components/Navbar"; 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({ export default function RootLayout({
children, children,
@ -8,9 +14,9 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="en"> <html lang="en">
<body> <body className="min-h-screen bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
<Navbar /> <Navbar />
{children} <main>{children}</main>
</body> </body>
</html> </html>
); );

View File

@ -6,30 +6,35 @@ import Image from "@tiptap/extension-image";
import Link from "@tiptap/extension-link"; import Link from "@tiptap/extension-link";
import { Video } from "@/components/extensions/Video"; import { Video } from "@/components/extensions/Video";
export default function Editor({ type Props = {
content,
onChange,
}: {
content: string; content: string;
onChange: (html: string) => void; onChange: (html: string) => void;
}) { };
export default function Editor({ content, onChange }: Props) {
const editor = useEditor({ const editor = useEditor({
extensions: [ extensions: [
StarterKit, StarterKit,
Image, Image,
Link.configure({ openOnClick: false }), Link.configure({
openOnClick: false,
}),
Video, Video,
], ],
content, content,
immediatelyRender: false, // Next.js 16 SSR fix immediatelyRender: false, // SSR fix for Next 16
onUpdate({ editor }) { onUpdate({ editor }) {
onChange(editor.getHTML()); onChange(editor.getHTML());
}, },
}); });
if (!editor) return null; /* =========================
UPLOAD HELPER
async function upload(file: File) { ========================= */
async function upload(file: File): Promise<{
url: string;
type: "image" | "video";
}> {
const data = new FormData(); const data = new FormData();
data.append("file", file); data.append("file", file);
@ -42,18 +47,30 @@ export default function Editor({
throw new Error("Upload failed"); throw new Error("Upload failed");
} }
return res.json() as Promise<{ return res.json();
url: string;
type: "image" | "video";
}>;
} }
/* =========================
ADD IMAGE
========================= */
async function addImage(file: File) { async function addImage(file: File) {
if (!editor) return;
const { url } = await upload(file); 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) { async function addVideo(file: File) {
if (!editor) return;
const { url } = await upload(file); const { url } = await upload(file);
editor editor
@ -67,14 +84,26 @@ export default function Editor({
type: file.type, type: file.type,
}, },
}, },
{ type: "paragraph" }, {
type: "paragraph",
},
]) ])
.run(); .run();
} }
if (!editor) {
return (
<div className="border rounded-lg p-4 text-gray-500">
Loading editor
</div>
);
}
return ( return (
<div className="border rounded-lg bg-white dark:bg-gray-900"> <div className="border rounded-lg bg-white dark:bg-gray-900">
{/* TOOLBAR */} {/* =========================
TOOLBAR
========================= */}
<div <div
className=" className="
flex flex-wrap gap-2 p-2 border-b flex flex-wrap gap-2 p-2 border-b
@ -84,7 +113,9 @@ export default function Editor({
> >
<button <button
type="button" type="button"
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() =>
editor.chain().focus().toggleBold().run()
}
className="editor-btn" className="editor-btn"
> >
Bold Bold
@ -92,7 +123,9 @@ export default function Editor({
<button <button
type="button" type="button"
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() =>
editor.chain().focus().toggleItalic().run()
}
className="editor-btn" className="editor-btn"
> >
Italic Italic
@ -101,7 +134,11 @@ export default function Editor({
<button <button
type="button" type="button"
onClick={() => onClick={() =>
editor.chain().focus().toggleHeading({ level: 2 }).run() editor
.chain()
.focus()
.toggleHeading({ level: 2 })
.run()
} }
className="editor-btn" className="editor-btn"
> >
@ -115,7 +152,8 @@ export default function Editor({
hidden hidden
accept="image/*" accept="image/*"
onChange={e => onChange={e =>
e.target.files && addImage(e.target.files[0]) e.target.files &&
addImage(e.target.files[0])
} }
/> />
</label> </label>
@ -127,13 +165,16 @@ export default function Editor({
hidden hidden
accept="video/mp4,video/webm" accept="video/mp4,video/webm"
onChange={e => onChange={e =>
e.target.files && addVideo(e.target.files[0]) e.target.files &&
addVideo(e.target.files[0])
} }
/> />
</label> </label>
</div> </div>
{/* CONTENT */} {/* =========================
CONTENT
========================= */}
<EditorContent <EditorContent
editor={editor} editor={editor}
className=" className="