mirror of https://github.com/itsmrval/ltsNinja
feat(dashboard) frontend & working
parent
77a6414fee
commit
6af894f319
|
|
@ -7,7 +7,7 @@
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
database.db
|
database.db
|
||||||
.env
|
.env
|
||||||
|
|
|
||||||
18
index.go
18
index.go
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
|
@ -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>
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue