feat(dashboard) frontend & working

main
itsmrval 2024-08-02 11:16:54 +02:00
parent 77a6414fee
commit 6af894f319
5 changed files with 162 additions and 73 deletions

2
.gitignore vendored
View File

@ -7,7 +7,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
.DS_Store
database.db database.db
.env .env

View File

@ -87,7 +87,7 @@ func (app *App) SetupRoutes() {
app.Router.GET("/login", app.loginGithub) app.Router.GET("/login", app.loginGithub)
app.Router.GET("/callback", app.githubCallback) app.Router.GET("/callback", app.githubCallback)
app.Router.GET("/logout", app.logout) app.Router.GET("/logout", app.logout)
app.Router.GET("/dashboard", app.myLinks) app.Router.GET("/dashboard", app.dashboard)
app.Router.DELETE("/dashboard", app.deleteLink) app.Router.DELETE("/dashboard", app.deleteLink)
app.Router.PUT("/dashboard", app.updateLink) app.Router.PUT("/dashboard", app.updateLink)
app.Router.GET("/:shortURL", app.redirectToOriginal) app.Router.GET("/:shortURL", app.redirectToOriginal)
@ -100,9 +100,9 @@ func (app *App) Run() error {
func (app *App) createTable() error { func (app *App) createTable() error {
_, err := app.DB.Exec(`CREATE TABLE IF NOT EXISTS links ( _, err := app.DB.Exec(`CREATE TABLE IF NOT EXISTS links (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY NOT NULL UNIQUE,
original_url TEXT NOT NULL, original_url TEXT NOT NULL,
short_url TEXT NOT NULL, short_url TEXT NOT NULL UNIQUE,
user_id TEXT user_id TEXT
)`) )`)
return err return err
@ -136,6 +136,10 @@ func (app *App) shortenURL(c *gin.Context) {
id, originalURL, shortURL, userID) id, originalURL, shortURL, userID)
if err != nil { if err != nil {
log.Printf("Error inserting link: %v", err) log.Printf("Error inserting link: %v", err)
if err.Error() == "UNIQUE constraint failed: links.short_url" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Short URL already exists"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
@ -183,7 +187,7 @@ func (app *App) githubCallback(c *gin.Context) {
c.Redirect(http.StatusFound, "/") c.Redirect(http.StatusFound, "/")
} }
func (app *App) myLinks(c *gin.Context) { func (app *App) dashboard(c *gin.Context) {
userID := getUserID(c) userID := getUserID(c)
if userID == "" { if userID == "" {
c.Redirect(http.StatusFound, "/") c.Redirect(http.StatusFound, "/")
@ -208,7 +212,7 @@ func (app *App) myLinks(c *gin.Context) {
links = append(links, link) links = append(links, link)
} }
c.HTML(http.StatusOK, "dashboard.html", gin.H{"links": links}) c.HTML(http.StatusOK, "dashboard.html", gin.H{"links": links, "userId": userID})
} }
func (app *App) deleteLink(c *gin.Context) { func (app *App) deleteLink(c *gin.Context) {
@ -247,6 +251,10 @@ func (app *App) updateLink(c *gin.Context) {
_, err := app.DB.Exec("UPDATE links SET short_url = ? WHERE id = ? AND user_id = ?", payload.NewName, payload.ID, userID) _, err := app.DB.Exec("UPDATE links SET short_url = ? WHERE id = ? AND user_id = ?", payload.NewName, payload.ID, userID)
if err != nil { if err != nil {
if err.Error() == "UNIQUE constraint failed: links.short_url" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Short URL already exists"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return return
} }

10
static/img/logo.svg Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -64 640 640" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#9333EA"/>
<stop offset="80%" style="stop-color:#DB2777"/>
<stop offset="100%" style="stop-color:#2563EB"/>
</linearGradient>
</defs>
<path fill="url(#logoGradient)" d="M312 8C175 8 64 119 64 256s111 248 248 248 248-111 248-248c0-25.38-3.82-49.86-10.9-72.91l63.92 52.97L640 173.49l-98.2-10.89c-.65-1.6-1.31-3.19-2-4.77l94.89-40.43L595 61.64l-60.41 84.91C494.17 64.46 409.7 8 312 8zM191.99 256c0-9.3 4.1-17.5 10.5-23.4l-31-9.3c-8.5-2.5-13.3-11.5-10.7-19.9 2.5-8.5 11.4-13.2 19.9-10.7l80 24c8.5 2.5 13.3 11.5 10.7 19.9-2.1 6.9-8.4 11.4-15.3 11.4-.5 0-1.1-.2-1.7-.2.7 2.7 1.7 5.3 1.7 8.2 0 17.7-14.3 32-32 32s-32.1-14.3-32.1-32zm252.61-32.7l-31 9.3c6.3 5.8 10.5 14.1 10.5 23.4 0 17.7-14.3 32-32 32s-32-14.3-32-32c0-2.9.9-5.6 1.7-8.2-.6.1-1.1.2-1.7.2-6.9 0-13.2-4.5-15.3-11.4-2.5-8.5 2.3-17.4 10.7-19.9l80-24c8.4-2.5 17.4 2.3 19.9 10.7 2.5 8.5-2.3 17.4-10.8 19.9zm-265.19-52.28h262.56c42.35 0 54.97 49.74 53.8 83.99-1.18 34.83-41.79 72.53-72.23 72.53-61.58 0-73.62-40.25-112.85-40.28-39.23.03-51.27 40.28-112.85 40.28-30.44 0-71.05-37.7-72.23-72.53-1.17-34.25 11.45-83.99 53.8-83.99z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,31 +1,83 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="fr">
<head> <head>
<title>dashboard</title> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lksNinja - Dashboard</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<script>
tailwind.config = {
theme: {
extend: {
boxShadow: {
'rainbow': '0 0 10px rgba(255, 0, 0, 0.2), 0 0 20px rgba(255, 165, 0, 0.2), 0 0 30px rgba(255, 255, 0, 0.2), 0 0 40px rgba(0, 255, 0, 0.2), 0 0 50px rgba(0, 0, 255, 0.2), 0 0 60px rgba(75, 0, 130, 0.2), 0 0 70px rgba(238, 130, 238, 0.2)',
'color': '0 0 15px rgba(99, 102, 241, 0.4)'
}
}
}
}
</script>
</head> </head>
<body>
<h1>My Links</h1> <body class="min-h-screen flex flex-col items-center justify-center p-4 bg-[url('/static/img/hero.svg')] bg-cover">
<table> <img src="/static/img/logo.svg" width="128" height="128" class="mb-4" alt="Links Ninja">
<tr>
<th>Original URL</th> <div class="bg-white/0 ring-1 ring-black/5 rounded-3xl p-8 w-full max-w-4xl backdrop-blur-md">
<th>Short URL</th> <h2 class="text-3xl font-bold mb-6 text-center text-gray-800">👋 Welcome on your dashboard</h2>
<th>Actions</th> <div id="error" class="hidden p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
<span class="font-medium">⚠️ An error occured!</span>
<span id="error-content"></span>
</div>
<div class="bg-white rounded-2xl p-6 shadow mb-6 overflow-x-auto">
<table class="w-full">
<thead>
<tr class="bg-gray-100">
<th class="px-4 py-2 text-left">Original URL</th>
<th class="px-4 py-2 text-left">Short URL</th>
<th class="px-4 py-2 text-left">Actions</th>
</tr> </tr>
</thead>
<tbody>
{{range .links}} {{range .links}}
<tr> <tr class="border-b">
<td>{{.OriginalURL}}</td> <td class="px-4 py-2">{{.OriginalURL}}</td>
<td>{{.ShortURL}}</td> <td class="px-4 py-2">
<td> <div class="flex items-center space-x-2">
<button onclick="deleteLink('{{.ID}}')">Delete</button> <input type="text" id="new-name-{{.ID}}" value="{{.ShortURL}}" placeholder="Short URL" class="px-2 py-1 rounded-full border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-300">
<input type="text" id="new-name-{{.ID}}" placeholder="New name"> <button onclick="updateLink('{{.ID}}')" class="px-3 py-1 bg-gradient-to-r from-green-400 to-blue-400 text-white rounded-full hover:from-green-500 hover:to-blue-500 focus:outline-none focus:ring-2 focus:ring-green-300 transition duration-300 ease-in-out transform hover:scale-105">
<button onclick="updateLink('{{.ID}}')">Update</button> <i class="fa fa-pen"></i>
</button>
</div>
</td>
<td class="px py-2">
<button onclick="deleteLink('{{.ID}}')" class="px-3 py-1 bg-gradient-to-r from-red-400 to-pink-400 text-white rounded-full hover:from-red-500 hover:to-pink-500 focus:outline-none focus:ring-2 focus:ring-red-300 transition duration-300 ease-in-out transform hover:scale-105">
<i class="fa fa-trash"></i> Delete
</button>
</td> </td>
</tr> </tr>
{{end}} {{end}}
</tbody>
</table> </table>
<a href="/">Back to Home</a> </div>
</body>
<script> <div class="flex justify-center mb-4">
<small>Account ID: {{.userId }}</small>
</div>
<div class="flex justify-center">
<a href="/" class="px-6 py-2 bg-gradient-to-r from-purple-400 to-indigo-400 text-white rounded-full hover:from-purple-500 hover:to-indigo-500 focus:outline-none focus:ring-2 focus:ring-purple-300 transition duration-300 ease-in-out transform hover:scale-105">
Return to home
</a>
</div>
</div>
<script>
const errorBox = document.getElementById('error');
const errorContent = document.getElementById('error-content');
const resultBox = document.getElementById('result');
const resultLink = document.getElementById('result-link');
function deleteLink(id) { function deleteLink(id) {
fetch('/dashboard', { fetch('/dashboard', {
method: 'DELETE', method: 'DELETE',
@ -38,7 +90,9 @@
if (response.ok) { if (response.ok) {
location.reload(); location.reload();
} else { } else {
alert('Failed to delete the link.'); errorContent.textContent = 'Failed to delete the link.';
errorBox.classList.remove('hidden');
} }
}); });
} }
@ -56,9 +110,11 @@
if (response.ok) { if (response.ok) {
location.reload(); location.reload();
} else { } else {
alert('Failed to update the link.'); errorContent.textContent = 'Failed to update the link.';
errorBox.classList.remove('hidden');
} }
}); });
} }
</script> </script>
</body>
</html> </html>

View File

@ -21,7 +21,8 @@
</head> </head>
<body class="min-h-screen flex flex-col items-center justify-center p-4 bg-[url('/static/img/hero.svg')] bg-cover"> <body class="min-h-screen flex flex-col items-center justify-center p-4 bg-[url('/static/img/hero.svg')] bg-cover">
<h1 class="text-5xl p-4 text-center font-bold from-purple-600 via-pink-600 to-blue-600 bg-gradient-to-r bg-clip-text text-transparent">LKS <small>ninja</small></h1> <img src="/static/img/logo.svg" width="128" height="128" class="mb-4" alt="Links Ninja">
<div class="bg-white/0 ring-1 ring-black/5 rounded-3xl p-8 w-full max-w-2xl backdrop-blur-md"> <div class="bg-white/0 ring-1 ring-black/5 rounded-3xl p-8 w-full max-w-2xl backdrop-blur-md">
<div class="bg-white rounded-2xl p-6 shadow mb-6"> <div class="bg-white rounded-2xl p-6 shadow mb-6">
@ -45,19 +46,23 @@
<span id="result-link"></span> <span id="result-link"></span>
<button id="copy-btn" class="ml-2 px-2 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-300">Copy</button> <button id="copy-btn" class="ml-2 px-2 py-1 bg-blue-500 text-white rounded hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-300">Copy</button>
</div> </div>
<div id="error" class="hidden p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
<span class="font-medium">⚠️ An error occured!</span>
<span id="error-content"></span>
</div>
<div class="mt-6 flex justify-between items-center"> <div class="mt-6 flex justify-between items-center">
{{if .loggedIn}} {{if .loggedIn}}
<a href="/dashboard" <a href="/dashboard"
class="px-6 py-2 bg-gradient-to-r from-green-400 to-blue-400 text-white rounded-full hover:from-green-500 hover:to-blue-500 focus:outline-none focus:ring-2 focus:ring-green-300 transition duration-300 ease-in-out transform hover:scale-105"> class="px-6 py-2 bg-blue-400 text-white rounded-full focus:outline-none focus:ring-2 focus:ring-green-300 transition duration-300 ease-in-out transform hover:scale-105">
Dashboard Dashboard
</a> </a>
<a href="/logout" class="px-6 py-2 bg-gradient-to-r from-purple-400 to-indigo-400 text-white rounded-full hover:from-purple-500 hover:to-indigo-500 focus:outline-none focus:ring-2 focus:ring-purple-300 transition duration-300 ease-in-out transform hover:scale-105"> <a href="/logout" class="px-6 py-2 bg-purple-400 text-white rounded-full focus:outline-none focus:ring-2 focus:ring-purple-300 transition duration-300 ease-in-out transform hover:scale-105">
Logout Logout
</a> </a>
{{else}} {{else}}
<a href="/login" <a href="/login"
class="px-6 py-2 bg-gradient-to-r from-blue-400 to-cyan-400 text-white rounded-full hover:from-blue-500 hover:to-cyan-500 focus:outline-none focus:ring-2 focus:ring-blue-300 transition duration-300 ease-in-out transform hover:scale-105"> class="px-6 py-2 bg-blue-400 text-white rounded-full focus:outline-none focus:ring-2 focus:ring-blue-300 transition duration-300 ease-in-out transform hover:scale-105">
<i class="fab fa-github"></i> Login using GitHub <i class="fab fa-github"></i> Login using GitHub
</a> </a>
{{end}} {{end}}
@ -65,6 +70,10 @@
</div> </div>
<script> <script>
const errorBox = document.getElementById('error');
const errorContent = document.getElementById('error-content');
const resultBox = document.getElementById('result');
const resultLink = document.getElementById('result-link');
document.getElementById('shorten-form').addEventListener('submit', function(e) { document.getElementById('shorten-form').addEventListener('submit', function(e) {
e.preventDefault(); e.preventDefault();
fetch('/', { fetch('/', {
@ -74,8 +83,14 @@
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
this.reset(); this.reset();
const resultBox = document.getElementById('result'); errorBox.classList.add('hidden');
const resultLink = document.getElementById('result-link'); resultBox.classList.add('hidden');
if (data.error) {
errorContent.textContent = data.error;
errorBox.classList.remove('hidden');
return;
}
const shortURL = window.location.origin + '/' + data.shortURL; const shortURL = window.location.origin + '/' + data.shortURL;
resultLink.textContent = shortURL; resultLink.textContent = shortURL;
resultLink.setAttribute('data-url', shortURL); resultLink.setAttribute('data-url', shortURL);