Added routing for subfolders to other files + simple first frontend
This commit is contained in:
parent
76fb3332bf
commit
84ff8df612
14 changed files with 348 additions and 63 deletions
|
|
@ -2,9 +2,9 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="./src/assets/trx-shortlink-orange.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>trx shortlink</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
56
client/package-lock.json
generated
56
client/package-lock.json
generated
|
|
@ -9,7 +9,8 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0",
|
||||||
|
"react-router-dom": "^7.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.30.1",
|
||||||
|
|
@ -2122,6 +2123,15 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
|
|
@ -3562,6 +3572,44 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "7.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.0.tgz",
|
||||||
|
"integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "^1.0.1",
|
||||||
|
"set-cookie-parser": "^2.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "7.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.0.tgz",
|
||||||
|
"integrity": "sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-router": "7.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18",
|
||||||
|
"react-dom": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
|
@ -3707,6 +3755,12 @@
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/set-cookie-parser": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0"
|
"react-dom": "^19.1.0",
|
||||||
|
"react-router-dom": "^7.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.30.1",
|
"@eslint/js": "^9.30.1",
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
height: 6em;
|
height: 12em;
|
||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
will-change: filter;
|
will-change: filter;
|
||||||
transition: filter 300ms;
|
transition: filter 300ms;
|
||||||
}
|
}
|
||||||
.logo:hover {
|
.logo:hover {
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
filter: drop-shadow(0 0 1em #f29732);
|
||||||
|
|
||||||
}
|
}
|
||||||
.logo.react:hover {
|
.logo.react:hover {
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||||
|
|
@ -28,15 +29,124 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
a:nth-of-type(2) .logo {
|
/*a .logo {
|
||||||
animation: logo-spin infinite 20s linear;
|
animation: logo-spin infinite 20s linear;
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
padding: 2em;
|
padding: 2em;
|
||||||
|
box-shadow: 0 0 10px 5px rgb(0 0 0 / 20%);
|
||||||
|
border-radius: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.read-the-docs {
|
.input {
|
||||||
color: #888;
|
height: 100px;
|
||||||
|
width: 1000px;
|
||||||
|
border-radius: 15px;
|
||||||
|
border-color: #f29732;
|
||||||
|
border-style: double;
|
||||||
|
font-size: 48pt;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.nav {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
width: 100%;
|
||||||
|
height: 65px;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1;
|
||||||
|
background: rgba(0,0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
header nav {
|
||||||
|
margin: 12px 20px 0 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
header nav ul li a {
|
||||||
|
padding: 12px 11px 12px 11px;
|
||||||
|
color: rgb(230, 223, 223);
|
||||||
|
}
|
||||||
|
header nav ul li {
|
||||||
|
margin: -12.5px 0px 0px 15px;
|
||||||
|
height: 35px;
|
||||||
|
float: left;
|
||||||
|
transition: all 300ms; -webkit-transition: all 300ms; -moz-transition: all 300ms; -o-transition: all 300ms;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; /* horizontal zentrieren */
|
||||||
|
align-items: center; /* vertikal zentrieren */
|
||||||
|
list-style: none;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
header nav ul li:hover {
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
header nav ul li a img {
|
||||||
|
width: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#button_short {
|
||||||
|
height: 100px;
|
||||||
|
width: 300px;
|
||||||
|
font-size: x-large;
|
||||||
|
border-radius: 15px;
|
||||||
|
will-change: filter;
|
||||||
|
transition: filter 400ms;
|
||||||
|
}
|
||||||
|
#button_short:hover {
|
||||||
|
filter: drop-shadow(0 0 0.5em #f29732);
|
||||||
|
border-color: transparent;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
#button_short:active {
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logoname {
|
||||||
|
box-shadow: 0 0 10px 5px rgb(0 0 0 / 20%);
|
||||||
|
border-radius: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logoname h1 {
|
||||||
|
will-change: filter;
|
||||||
|
transition: all 400ms;
|
||||||
|
padding: 35px;
|
||||||
|
margin-bottom: 100px;
|
||||||
|
margin-left: 440px;
|
||||||
|
margin-right: 440px;
|
||||||
|
color: rgb(230, 223, 223);
|
||||||
|
}
|
||||||
|
#logoname img {
|
||||||
|
padding: 35px;
|
||||||
|
}
|
||||||
|
#logoname h1:hover {
|
||||||
|
filter: drop-shadow(0 0 0.5em #f29732);
|
||||||
|
}
|
||||||
|
|
||||||
|
#rightlist {
|
||||||
|
right: 20px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
#oriurl {
|
||||||
|
margin: 50px;
|
||||||
|
}
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import { useState } from 'react'
|
|
||||||
import reactLogo from './assets/react.svg'
|
|
||||||
import viteLogo from '/vite.svg'
|
|
||||||
import './App.css'
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const [count, setCount] = useState(0)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<a href="https://vite.dev" target="_blank">
|
|
||||||
<img src={viteLogo} className="logo" alt="Vite logo" />
|
|
||||||
</a>
|
|
||||||
<a href="https://react.dev" target="_blank">
|
|
||||||
<img src={reactLogo} className="logo react" alt="React logo" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<h1>Vite + React</h1>
|
|
||||||
<div className="card">
|
|
||||||
<button onClick={() => setCount((count) => count + 1)}>
|
|
||||||
count is {count}
|
|
||||||
</button>
|
|
||||||
<p>
|
|
||||||
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p className="read-the-docs">
|
|
||||||
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
|
||||||
57
client/src/Home.tsx
Normal file
57
client/src/Home.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import shortlinkLogo from './assets/trx-shortlink-orange.png'
|
||||||
|
import './App.css'
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header>
|
||||||
|
<div className="nav">
|
||||||
|
<nav id="main-nav">
|
||||||
|
<ul id="centerlist">
|
||||||
|
<li id="home"> <a draggable="false" href="#">Home</a> </li>
|
||||||
|
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<ul id="rightlist">
|
||||||
|
<li id="login"> <a draggable="false" href='login'>Login</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div id="logoname">
|
||||||
|
<a href="#" >
|
||||||
|
<img src={shortlinkLogo} draggable="false" className="logo" alt="trx shortlink logo" />
|
||||||
|
</a>
|
||||||
|
<h1>trx shortlink</h1>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<input className="input" id="oriurl"></input>
|
||||||
|
<button id="button_short" onClick={() => shortenLink((document.getElementById("oriurl") as HTMLInputElement).value)
|
||||||
|
}>
|
||||||
|
Shorten link!
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
|
||||||
|
async function shortenLink(url: string) {
|
||||||
|
if ((document.getElementById("oriurl") as HTMLInputElement).value == "") {
|
||||||
|
alert("The url cannot be empty!")
|
||||||
|
} else {
|
||||||
|
const res = await fetch("http://localhost:3000/api/shorten", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ original_url: url }),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
console.log(data.short_url);
|
||||||
|
(document.getElementById("oriurl") as HTMLInputElement).value = ""
|
||||||
|
location.href = "link/" + data.id;
|
||||||
|
return data.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BIN
client/src/assets/trx-shortlink-orange.png
Normal file
BIN
client/src/assets/trx-shortlink-orange.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
BIN
client/src/assets/trx-shortlink.png
Normal file
BIN
client/src/assets/trx-shortlink.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
|
|
@ -1,10 +1,35 @@
|
||||||
import { StrictMode } from 'react'
|
import { createRoot } from 'react-dom/client';
|
||||||
import { createRoot } from 'react-dom/client'
|
import './index.css';
|
||||||
import './index.css'
|
import Home from './Home.tsx';
|
||||||
import App from './App.tsx'
|
import Login from './pages/login.tsx'
|
||||||
|
import Copylink from './pages/copylink.tsx';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BrowserRouter as Router,
|
||||||
|
Routes,
|
||||||
|
Route,
|
||||||
|
Navigate,
|
||||||
|
} from "react-router-dom";
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<Router>
|
||||||
<App />
|
<Routes>
|
||||||
</StrictMode>,
|
<Route
|
||||||
|
path='/home'
|
||||||
|
element={<Home />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='/login'
|
||||||
|
element={<Login />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='/link/:id'
|
||||||
|
element={<Copylink />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path='*'
|
||||||
|
element={<Navigate to={"/home"} />}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</Router>,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
async function shortenLink() {
|
|
||||||
const res = await fetch("http://localhost:3000/api/shorten", {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ original_url: "https://example.com" }),
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
console.log(data.short_url);
|
|
||||||
}
|
|
||||||
39
client/src/pages/copylink.tsx
Normal file
39
client/src/pages/copylink.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import shortlinkLogo from '../assets/trx-shortlink-orange.png'
|
||||||
|
import '../App.css'
|
||||||
|
|
||||||
|
const Copylink = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header>
|
||||||
|
<div className="nav">
|
||||||
|
<nav id="main-nav">
|
||||||
|
<ul id="centerlist">
|
||||||
|
<li id="home"> <a draggable="false" href="/home">Home</a> </li>
|
||||||
|
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<ul id="rightlist">
|
||||||
|
<li id="login"> <a draggable="false" href='login'>Login</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div id="logoname">
|
||||||
|
<a href="#" >
|
||||||
|
<img src={shortlinkLogo} draggable="false" className="logo" alt="trx shortlink logo" />
|
||||||
|
</a>
|
||||||
|
<h1>trx shortlink</h1>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<input className="input" id="oriurl"></input>
|
||||||
|
<button id="button_short" onClick={() => 0}>
|
||||||
|
Copy!
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Copylink;
|
||||||
42
client/src/pages/login.tsx
Normal file
42
client/src/pages/login.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import shortlinkLogo from '../assets/trx-shortlink-orange.png'
|
||||||
|
import '../App.css'
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header>
|
||||||
|
<div className="nav">
|
||||||
|
<nav id="main-nav">
|
||||||
|
<ul id="centerlist">
|
||||||
|
<li id="home"> <a draggable="false" href="home">Home</a> </li>
|
||||||
|
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<ul id="rightlist">
|
||||||
|
<li id="login"> <a draggable="false" href="login">Login</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div id="logoname">
|
||||||
|
<a href="#" >
|
||||||
|
<img src={shortlinkLogo} draggable="false" className="logo" alt="trx shortlink logo" />
|
||||||
|
</a>
|
||||||
|
<h1>trx shortlink</h1>
|
||||||
|
</div>
|
||||||
|
<div className="card">
|
||||||
|
<h2>Login</h2>
|
||||||
|
<input className="login" id="username" defaultValue={"Username..."}></input><br></br>
|
||||||
|
<input className="login" id="password" type='password' defaultValue={"Password..."}></input><br></br>
|
||||||
|
<button id="login" onClick={() => 0+0}>
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default App
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": false,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ app.post("/api/shorten", (req, res) => {
|
||||||
[id, original_url],
|
[id, original_url],
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err) return res.status(500).json({ error: err.message });
|
if (err) return res.status(500).json({ error: err.message });
|
||||||
res.json({ short_url: `http://localhost:5173/${id}` });
|
res.json({ short_url: `http://localhost:3000/${id}` });
|
||||||
|
res.json({ id: `${id}`});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue