initial commit

This commit is contained in:
Daniel Hjartland
2024-11-26 18:08:49 +01:00
commit 75be79c6cf
26 changed files with 1885 additions and 0 deletions

301
app/app.js Normal file
View File

@@ -0,0 +1,301 @@
document.addEventListener('DOMContentLoaded', async () => {
const token = localStorage.getItem('token');
if (token) {
document.getElementById('auth-section').style.display = 'none';
document.getElementById('video-section').style.display = 'block';
await getVideos();
} else {
document.getElementById('auth-section').style.display = 'block';
document.getElementById('video-section').style.display = 'none';
}
});
document.getElementById('video-draft-form').addEventListener('submit', async (event) => {
event.preventDefault();
await createVideoDraft();
});
document.getElementById('login-form').addEventListener('submit', async (event) => {
event.preventDefault();
await login();
});
async function createVideoDraft() {
const title = document.getElementById('video-title').value;
const description = document.getElementById('video-description').value;
try {
const res = await fetch('/api/videos', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
body: JSON.stringify({ title, description }),
});
const data = await res.json();
if (!res.ok) {
throw new Error(`Failed to create video draft: ${data.error}`);
}
const videoID = data.id;
if (videoID) {
await getVideos();
await videoStateHandler(videoID);
}
} catch (error) {
alert(`Error: ${error.message}`);
}
}
async function login() {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
try {
const res = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
const data = await res.json();
if (!res.ok) {
throw new Error(`Failed to login: ${data.error}`);
}
if (data.token) {
localStorage.setItem('token', data.token);
document.getElementById('auth-section').style.display = 'none';
document.getElementById('video-section').style.display = 'block';
await getVideos();
} else {
alert('Login failed. Please check your credentials.');
}
} catch (error) {
alert(`Error: ${error.message}`);
}
}
async function signup() {
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
try {
const res = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
if (!res.ok) {
const data = await res.json();
throw new Error(`Failed to create user: ${data.error}`);
}
console.log('User created!');
await login();
} catch (error) {
alert(`Error: ${error.message}`);
}
}
function logout() {
localStorage.removeItem('token');
document.getElementById('auth-section').style.display = 'block';
document.getElementById('video-section').style.display = 'none';
}
function setUploadButtonState(uploading, selector) {
const uploadBtn = document.getElementById(selector);
if (uploading) {
uploadBtn.textContent = 'Uploading...';
uploadBtn.disabled = true;
return;
}
uploadBtn.textContent = 'Upload';
uploadBtn.disabled = false;
}
async function uploadThumbnail(videoID) {
const thumbnailFile = document.getElementById('thumbnail').files[0];
if (!thumbnailFile) return;
const formData = new FormData();
formData.append('thumbnail', thumbnailFile);
uploadBtnSelector = 'upload-thumbnail-btn';
setUploadButtonState(true, uploadBtnSelector);
try {
const res = await fetch(`/api/thumbnail_upload/${videoID}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
body: formData,
});
if (!res.ok) {
const data = await res.json();
throw new Error(`Failed to upload thumbnail. Error: ${data.error}`);
}
await res.json();
console.log('Thumbnail uploaded!');
await getVideo(videoID);
} catch (error) {
alert(`Error: ${error.message}`);
}
setUploadButtonState(false, uploadBtnSelector);
}
async function uploadVideoFile(videoID) {
const videoFile = document.getElementById('video-file').files[0];
if (!videoFile) return;
const formData = new FormData();
formData.append('video', videoFile);
uploadBtnSelector = 'upload-video-btn';
setUploadButtonState(true, uploadBtnSelector);
try {
const res = await fetch(`/api/video_upload/${videoID}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
body: formData,
});
if (!res.ok) {
const data = await res.json();
throw new Error(`Failed to upload video file. Error: ${data.error}`);
}
console.log('Video uploaded!');
await getVideo(videoID);
} catch (error) {
alert(`Error: ${error.message}`);
}
setUploadButtonState(false, uploadBtnSelector);
}
const videoStateHandler = createVideoStateHandler();
async function getVideos() {
try {
const res = await fetch('/api/videos', {
method: 'GET',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
if (!res.ok) {
const data = await res.json();
throw new Error(`Failed to get videos. Error: ${data.error}`);
}
const videos = await res.json();
const videoList = document.getElementById('video-list');
videoList.innerHTML = '';
for (const video of videos) {
const listItem = document.createElement('li');
listItem.textContent = video.title;
listItem.onclick = () => videoStateHandler(video.id);
videoList.appendChild(listItem);
}
} catch (error) {
alert(`Error: ${error.message}`);
}
}
function createVideoStateHandler() {
let currentVideoID = null;
return async function handleVideoClick(videoID) {
if (currentVideoID !== videoID) {
currentVideoID = videoID;
// Reset file input values
document.getElementById('thumbnail').value = '';
document.getElementById('video-file').value = '';
await getVideo(videoID);
}
};
}
async function getVideo(videoID) {
try {
const res = await fetch(`/api/videos/${videoID}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
if (!res.ok) {
throw new Error('Failed to get video.');
}
const video = await res.json();
viewVideo(video);
} catch (error) {
alert(`Error: ${error.message}`);
}
}
let currentVideo = null;
function viewVideo(video) {
currentVideo = video;
document.getElementById('video-display').style.display = 'block';
document.getElementById('video-title-display').textContent = video.title;
document.getElementById('video-description-display').textContent = video.description;
const thumbnailImg = document.getElementById('thumbnail-image');
if (!video.thumbnail_url) {
thumbnailImg.style.display = 'none';
} else {
thumbnailImg.style.display = 'block';
thumbnailImg.src = video.thumbnail_url;
}
const videoPlayer = document.getElementById('video-player');
if (videoPlayer) {
if (!video.video_url) {
videoPlayer.style.display = 'none';
} else {
videoPlayer.style.display = 'block';
videoPlayer.src = video.video_url;
videoPlayer.load();
}
}
}
async function deleteVideo() {
if (!currentVideo) {
alert('No video selected for deletion.');
return;
}
try {
const res = await fetch(`/api/videos/${currentVideo.id}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
if (!res.ok) {
throw new Error('Failed to delete video.');
}
alert('Video deleted successfully.');
document.getElementById('video-display').style.display = 'none';
await getVideos();
} catch (error) {
alert(`Error: ${error.message}`);
}
}

105
app/index.html Normal file
View File

@@ -0,0 +1,105 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tubely</title>
<link rel="stylesheet" href="styles.css" />
<script src="app.js" defer></script>
</head>
<body>
<div class="nav-bar">
<h1>
Tubely
<span class="subtitle">The #1 tool for engagement bait</span>
</h1>
<button onclick="logout()">Logout</button>
</div>
<div id="auth-section">
<h2>Login</h2>
<form id="login-form">
<input
class="input-area"
type="email"
id="email"
placeholder="Email"
required
/>
<input
class="input-area"
type="password"
id="password"
placeholder="Password"
required
/>
<div class="button-container">
<button type="submit">Login</button>
<button onclick="signup()" type="button">Signup</button>
</div>
</form>
</div>
<div id="video-section" style="display: none">
<h2>Create Draft</h2>
<form id="video-draft-form">
<input
class="input-area"
type="text"
id="video-title"
placeholder="Video Title"
required
/>
<textarea
class="input-area"
id="video-description"
placeholder="Video Description"
required
></textarea>
<div class="button-container">
<button type="submit">Create Draft</button>
</div>
</form>
<h2>All Videos</h2>
<ul id="video-list"></ul>
<div id="video-display" style="display: none">
<h2>Current Video: <span id="video-title-display"></span></h2>
<p id="video-description-display"></p>
<div class="button-container mb-4">
<button onclick="deleteVideo()">Delete Video</button>
</div>
<div id="video-upload-forms">
<form
id="thumbnail-upload-form"
onsubmit="event.preventDefault(); uploadThumbnail(currentVideo?.id)"
>
<h3>Update Thumbnail</h3>
<input
type="file"
id="thumbnail"
accept="image/*,application/pdf"
required
/>
<button type="submit" id="upload-thumbnail-btn">Upload</button>
<img id="thumbnail-image" style="display: block" />
</form>
<div id="video-container">
<form
id="video-file-upload-form"
onsubmit="event.preventDefault(); uploadVideoFile(currentVideo?.id)"
>
<h3>Update Video File</h3>
<input type="file" id="video-file" accept="video/*" required />
<button type="submit" id="upload-video-btn">Upload</button>
</form>
<video id="video-player" controls style="display: block"></video>
</div>
</div>
</div>
</div>
</body>
</html>

183
app/styles.css Normal file
View File

@@ -0,0 +1,183 @@
:root {
--bg-color: #1e1e1e;
--fg-color: #f5f5f5;
--subtle-color: #888;
--primary-color: #bb86fc;
--input-bg: #2a2a2a;
--input-border: #444;
--button-bg: #7e57c2;
--button-hover: #5c4db1;
}
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: var(--bg-color);
color: var(--fg-color);
line-height: 1.6;
}
.nav-bar {
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--bg-color);
padding: 10px 24px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.nav-bar h1 {
margin: 0;
color: var(--primary-color);
display: flex;
align-items: center;
}
.nav-bar button {
background-color: var(--button-bg);
color: white;
border: none;
border-radius: 5px;
padding: 10px 20px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.nav-bar button:hover {
background-color: var(--button-hover);
}
h1 {
color: var(--primary-color);
margin-bottom: 16px;
margin-left: 24px;
}
.subtitle {
color: var(--subtle-color);
font-size: 0.5em;
margin-left: 10px;
}
h2 {
color: var(--primary-color);
margin-bottom: 16px;
}
.input-area {
width: 100%;
padding: 10px;
border: 1px solid var(--input-border);
border-radius: 5px;
background-color: var(--input-bg);
color: var(--fg-color);
box-sizing: border-box;
}
button {
padding: 10px 20px;
border: none;
border-radius: 5px;
background-color: var(--button-bg);
color: #fff;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: var(--button-hover);
}
.button-container {
display: flex;
justify-content: center;
gap: 10px;
}
#video-display {
border-top: 2px solid #333;
padding-top: 20px;
margin-top: 20px;
}
#video-list {
list-style: none;
padding: 0;
margin-top: 10px;
}
#video-list li {
padding: 10px;
margin-top: 5px;
background-color: #1e1e1e;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
#video-list .active {
background-color: var(--primary-color);
color: #000;
}
#video-list li:hover {
background-color: #333;
}
#thumbnail-image,
#video-player {
max-width: 300px;
margin-left: 10px;
vertical-align: middle;
}
#auth-section {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
#video-section {
max-width: 1080px;
margin: 0 auto;
padding: 20px;
}
form {
border-radius: 5px;
display: flex;
flex-direction: column;
gap: 10px; /* Space between form elements */
}
#video-upload-forms {
display: flex;
gap: 20px; /* Space between the two forms */
}
#thumbnail-upload-form,
#video-container {
background-color: #1a1a1a;
padding: 20px;
border-radius: 5px;
flex: 1;
}
#download-button,
#video-player {
margin-top: 0.5rem;
width: 100%;
}
#video-upload-forms form {
flex: 1;
}
.mb-4 {
margin-bottom: 16px;
}
button[disabled] {
background-color: var(--subtle-color);
cursor: not-allowed;
}