- Operating System: Windows 10
- IDE: VS Code
- Browser: Firefox
- My Current Project/Application:
- Model: SSR
- Mode: Production ONLY (Unfortunately)
- Entry Point: frontend/dist/index.html
- Backend: Django:
- Relevant Frameworks/Libraries/Tools Used:
- Django==4.2.7
- Relevant Frameworks/Libraries/Tools Used:
- Frontend:
- Frameworks/Libraries/Tools Used:
- Vite
- React
- JSX
- React Router
- React Beautiful DND
- Styled Components
- React Toastify
- Universal Cookie
- Installed Packages:
- [email protected] P:\django_react_templates\frontend
- ├── @types/[email protected]
- ├── @types/[email protected]
- ├── @vitejs/[email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- ├── [email protected]
- └── [email protected]
- Frameworks/Libraries/Tools Used:
📂 P:\django_react_templates
┣━━ 📂 .git
┣━━ 📂 api
┃ ┣━━ 📂 migrations
┃ ┃ ┣━━ 🐍 0001_initial.py (1.7 kB)
┃ ┃ ┣━━ 🐍 ...
┃ ┃ ┗━━ 🐍 __init__.py (0 bytes)
┃ ┣━━ 🐍 __init__.py (0 bytes)
┃ ┣━━ 🐍 admin.py (2.5 kB)
┃ ┣━━ 🐍 apps.py (138 bytes)
┃ ┣━━ 🐍 models.py (3.3 kB)
┃ ┣━━ 🐍 tests.py (60 bytes)
┃ ┣━━ 🐍 urls.py (647 bytes)
┃ ┗━━ 🐍 views.py (10.9 kB)
┣━━ 📂 backend_ENV
┣━━ 📂 djangocookieauth
┃ ┣━━ 🐍 __init__.py (0 bytes)
┃ ┣━━ 🐍 asgi.py (409 bytes)
┃ ┣━━ 🐍 settings.py (3.8 kB)
┃ ┣━━ 🐍 urls.py (495 bytes)
┃ ┗━━ 🐍 wsgi.py (409 bytes)
┣━━ 📂 frontend
┃ ┣━━ 📂 dist
┃ ┣━━ 📂 node_modules
┃ ┣━━ 📂 public
┃ ┣━━ 📂 src
┃ ┃ ┣━━ 📂 assets
┃ ┃ ┃ ┣━━ 📒 initial-data.js (0 bytes)
┃ ┃ ┃ ┣━━ 📒 initial-data_example.js (1.1 kB)
┃ ┃ ┃ ┗━━ 📜 positions.json (1.1 kB)
┃ ┃ ┣━━ 📂 routes
┃ ┃ ┃ ┣━━ 📄 Admin.jsx (198 bytes)
┃ ┃ ┃ ┣━━ 📄 login.jsx (3.4 kB)
┃ ┃ ┃ ┗━━ 📄 register.jsx (4.8 kB)
┃ ┃ ┣━━ 📄 AuthContext.jsx (6.2 kB)
┃ ┃ ┣━━ 🎨 class_colors.css (154.3 kB)
┃ ┃ ┣━━ 📄 error-page.jsx (526 bytes)
┃ ┃ ┣━━ 📄 main.jsx (1.5 kB)
┃ ┃ ┣━━ 📄 Navbar.jsx (10.7 kB)
┃ ┃ ┣━━ 📄 Root.jsx (1.2 kB)
┃ ┃ ┣━━ 🎨 styles.css (1.7 kB)
┃ ┃ ┣━━ 📒 tabs_api.js (9.5 kB)
┃ ┃ ┗━━ 📒 utils.js (1.1 kB)
┃ ┣━━ 📄 .env (19 bytes)
┃ ┣━━ 📜 .eslintrc.json (1.7 kB)
┃ ┣━━ 📄 .gitignore (253 bytes)
┃ ┣━━ 🕸️ index.html (1.5 kB)
┃ ┣━━ 📜 package-lock.json (191.4 kB)
┃ ┣━━ 📜 package.json (1.2 kB)
┃ ┣━━ Ⓜ️ README.md (451 bytes)
┃ ┣━━ 📝 requirements.txt (512 bytes)
┃ ┗━━ 📄 vite.config.mjs (419 bytes)
┣━━ 📂 Old_Versions
┣━━ 📂 temp
┣━━ 📄 .env (19 bytes)
┣━━ 📄 .gitignore (130 bytes)
┣━━ 🐍 __init__.py (0 bytes)
┣━━ 🥞 db.sqlite3 (249.9 kB)
┣━━ 🔧 django_react_templates.code-workspace (1.8 kB)
┣━━ 📔 DJANGO_SERVER_LOG.log (20.7 kB)
┣━━ 🐍 manage.py (672 bytes)
┣━━ Ⓜ️ README.md (22.4 kB)
┣━━ 📝 requirements.txt (451 bytes)
┣━━ 🕸️ tester.html (0 bytes)
┣━━ 📒 tester.js (611 bytes)
┗━━ 🐍 tester.py (593 bytes)
- This project is based on the first of four options from this testdriven.io tutorial:
- Name: Django Session-based Auth for Single Page Apps
- Option 1 of 4: Django + React SPA served up via Django templates: Cookie based authentication, frontend served with Django templates
- Due to a (seemingly) blatent, inexcusable, and, negligent lack of perspicuous documentation/instructions/examples, of any and all techniques/methods/tools/plugins/frameworks/libraries related to running a Vite + Django/any backend SSR in development mode, this application must ONLY be run in production mode (I tried for way too long to get it to work in development mode)
- A tab manager similar to Workona, but with much less functionality
- The main, or home, page of the application will be a trinary display:
- Workspaces (left-side pane): Menu that contains workspaces
- Contains any workspaces created by the user
- This section dictates what will be contained/displayed in the other two sections
- Open Tabs (middle pane): Tab manager that contains zero or more currently open tabs for that workspace
- If no workspace has been opened, this section will contain all currently open tabs
- If a workspace is opened while there are unassigned tabs open, they will be moved into the opened workspace
- Resources (right-side pane): This section contains any user-created resource sections
- Resource Sections: Vertical displays that contain any saved links for the current workspace
- Contains any number of user created, named, foldable sections containing saved links (urls)
- Urls: Objects that contain the URL (obviously), along with a title, subtitle, and description (only to be used in resource sections)
- Workspaces (left-side pane): Menu that contains workspaces
- This will be displayed in the Root.jsx file using react-beautiful-dnd
- The register, and login pages are already written and fully functional
- the data will be a JOBJECTIONARY (my mocking name for an Object in javascript for calling it an Object) of 4 droppables:
- The left hand pane, or WorkSpace will be droppable, but only to allow dnd to reorder workspaces
- The middle pane, or Tab, will only hold open tabs and will allow the dropping of Url S from any ResourceSection S onto it. It will also allow Url S to be dragged from it to a ResourceSection.
- The right hand pane, or Resource will be droppable, but only to allow dnd to reorder ResourceSection S.
- The resource sections, or ResourceSection S will all allow any Url S to be dropped from the middle pane, other ResourceSection S, or within itself for reordering
I believe this illustrates that there are 3 draggables:
- The WorkSpace S, only within the left pane for reordering
- The ResourceSection S, only within the right pane for reordering
- Url S, to be dnd'd everywhere but the left pane.
So, given this, here is what I think these data might look like:
{
"workspaces": {
"droppableId": "workspaces",
"draggableIds": [
"workspace-1",
"workspace-2",
"workspace-3"
]
},
"tabs": {
"droppableId": "tabs",
"draggableIds": [
"url-1",
"url-2",
"url-3"
]
},
"resources": {
"droppableId": "resources",
"draggableIds": [
"section-1",
"section-2",
"section-3"
]
},
"sections": {
"section-1": {
"droppableId": "section-1",
"draggableIds": [
"url-4",
"url-5"
]
},
"section-2": {
"droppableId": "section-2",
"draggableIds": [
"url-6",
"url-7"
]
},
"section-3": {
"droppableId": "section-3",
"draggableIds": [
"url-8",
"url-9"
]
}
},
"dragables": {
"workspace-1": {
"...": "..."
},
"workspace-2": {
"...": "..."
},
"workspace-3": {
"...": "..."
},
"url-1": {
"...": "..."
},
"url-2": {
"...": "..."
},
"url-3": {
"...": "..."
},
"url-4": {
"...": "..."
},
"url-5": {
"...": "..."
},
"url-6": {
"...": "..."
},
"url-7": {
"...": "..."
},
"url-8": {
"...": "..."
},
"url-9": {
"...": "..."
},
"section-1": {
"...": "..."
},
"section-2": {
"...": "..."
},
"section-3": {
"...": "..."
}
}
}
{
"workspaces": { // UserProfile model
"droppableId (UserProfile.id)": "workspaces", // Constant value
"draggableIds (UserProfile.workspaces_order)": ["workspace-1", "workspace-2", "workspace-3"] // UserProfile.workspaces_order
},
"tabs": { // Tab model
"droppableId (Tab.id)": "tabs", // Constant value
"draggableIds (Tab.urls_order)": ["url-1", "url-2", "url-3"] // Tab.urls_order
},
"resources": { // Resource model
"droppableId (Resource.id)": "resources", // Constant value
"draggableIds (Resource.sections_order)": ["section-1", "section-2", "section-3"] // Resource.sections_order
},
"sections": { // ResourceSection model
"section-1": {
"droppableId (ResourceSection.id)": "section-1", // Constant value
"draggableIds (ResourceSection.urls_order)": ["url-4", "url-5"] // ResourceSection.urls_order
},
// ... more sections ...
},
"dragables": { // WorkSpace, Url, and ResourceSection models
"workspace-1": { // WorkSpace model instance
"id (WorkSpace.id)": "workspace-1",
"name (WorkSpace.name)": "My First Workspace",
"user (WorkSpace.user.id)": "user-1"
},
"workspace-2": {...}, // WorkSpace model instance
"workspace-3": {...}, // WorkSpace model instance
"url-1": { // Url model instance
"id (Url.id)": "url-1",
"url (Url.url)": "https://example.com",
"title (Url.title)": "Example Website",
"subtitle (Url.subtitle)": "An example website",
"description (Url.description)": "This is an example website.",
"date_added (Url.date_added)": "2023-12-01T12:00:00Z",
"tab (Url.tab.id)": "tab-1"
},
"url-2": {...}, // Url model instance
"url-3": {...}, // Url model instance
"url-4": {...}, // Url model instance
"url-5": {...}, // Url model instance
"section-1": {...}, // ResourceSection model instance
"section-2": {...}, // ResourceSection model instance
"section-3": {...}, // ResourceSection model instance
// ... more dragables ...
}
}
- This is what I have so far to store the data, and, I can't stress this enough, I don't really know what I'm doing, so if you happen to see any glaring stupidities, please let me know.
#^ Standard Library Imports
from json import loads
#^ Third Party Imports
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinLengthValidator, MinValueValidator, EmailValidator
from django.db.models import (
BigAutoField,
BooleanField,
CharField,
DateTimeField,
EmailField,
ForeignKey,
IntegerField,
JSONField,
ManyToManyField,
Model,
OneToOneField,
TextField,
URLField,
CASCADE,
)
from django.utils.timezone import make_aware
class Resource(Model):
workspace = OneToOneField("WorkSpace", on_delete=CASCADE, related_name="workspace_resource")
name = CharField(max_length=9 ,default="Resources")
sections_order = JSONField(default=list)
def get_ordered_resource_sections(self):
return self.resource_sections_resource.filter(id__in=self.sections_order).order_by("id")
class ResourceSection(Model):
resource = ForeignKey("Resource", on_delete=CASCADE, related_name="resource_section_resource")
urls = ManyToManyField("Url", related_name="url_resource_section", blank=True)
title = CharField(
max_length=200, default="New Section", validators=[MinLengthValidator(1)]
)
subtitle = CharField(max_length=200, blank=True, null=True)
urls_order = JSONField(default=list)
def get_ordered_urls(self):
return self.urls.filter(id__in=self.urls_order).order_by("id")
class Tab(Model):
urls = ManyToManyField("Url", related_name="url_open_tabs", blank=True)
workspace = ForeignKey("WorkSpace", on_delete=CASCADE, related_name="workspace_open_tabs")
urls_order = JSONField(default=list)
def get_ordered_urls(self):
return self.urls.filter(id__in=self.urls_order).order_by("id")
class Url(Model):
url = CharField(max_length=2_000)
title = CharField(max_length=200, blank=True, null=True)
subtitle = CharField(max_length=200, blank=True, null=True)
description = TextField(max_length=10_000, blank=True, null=True)
date_added = DateTimeField(auto_now_add=True)
tab = ForeignKey("Tab", on_delete=CASCADE, null=True, blank=True, related_name="tab_url")
def __str__(self):
return self.url
class UserProfile(Model):
user = OneToOneField(User, on_delete=CASCADE, related_name="user_profile", blank=True, null=True)
email = EmailField(max_length=200, blank=True, null=True, validators=[EmailValidator()])
bio = TextField(max_length=10_000, blank=True, null=True)
profile_image = CharField(max_length=2_000, blank=True, null=True)
workspaces_order = JSONField(default=list)
def get_ordered_workspaces(self):
return self.user.user_workspace.filter(id__in=self.workspaces_order).order_by("id")
class WorkSpace(Model):
user = ForeignKey(User, on_delete=CASCADE, related_name="user_workspace")
name = CharField(
max_length=64, default="New Workspace", validators=[MinLengthValidator(1)]
)
def save(self, *args, **kwargs):
is_new = self.id is None
super().save(*args, **kwargs)
if is_new:
resource = Resource.objects.create(workspace=self)
ResourceSection.objects.create(resource=resource)
Tab.objects.create(workspace=self)
def __str__(self):
return self.name
- This is set up to be able to easily add and remove test data to the database
# ^ Third Party Imports
from django.contrib.admin import ModelAdmin
from django.contrib.admin import site
from django import forms
from django.forms import ModelMultipleChoiceField
from django.contrib.admin.widgets import FilteredSelectMultiple
# ^ Local Imports
from .models import (
Resource,
ResourceSection,
Tab,
Url,
UserProfile,
User,
WorkSpace,
)
class ResourceAdmin(ModelAdmin):
list_display = ("workspace",)
search_fields = ("workspace",)
class ResourceSectionAdmin(ModelAdmin):
list_display = (
"title",
"subtitle",
"urls_order",
)
search_fields = (
"title",
"subtitle",
"urls_order",
)
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "urls":
kwargs['widget'] = FilteredSelectMultiple(verbose_name="URLs", is_stacked=False)
return ModelMultipleChoiceField(queryset=Url.objects.all(), **kwargs)
return super().formfield_for_manytomany(db_field, request, **kwargs)
class TabAdmin(ModelAdmin):
list_display = (
"urls",
"workspace",
"urls_order",
)
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "urls":
kwargs['widget'] = FilteredSelectMultiple(verbose_name="URLs", is_stacked=False)
return ModelMultipleChoiceField(queryset=Url.objects.all(), required=False, **kwargs)
return super().formfield_for_manytomany(db_field, request, **kwargs)
class UrlAdmin(ModelAdmin):
list_display = (
"__str__",
"id",
"title",
"subtitle",
"description",
"date_added",
"tab",
)
search_fields = (
"id",
"url",
"title",
"subtitle",
"description",
"date_added",
)
class UserProfileAdmin(ModelAdmin):
list_display = (
"user",
"email",
"bio",
"profile_image",
"workspaces_order",
)
search_fields = (
"email",
"bio",
"profile_image",
"workspaces_order",
)
class WorkspaceAdmin(ModelAdmin):
list_display = (
"id",
"user",
"name",
)
search_fields = (
"id",
"name",
)
site.register(Resource, ResourceAdmin)
site.register(ResourceSection, ResourceSectionAdmin)
site.register(Tab, TabAdmin)
site.register(Url, UrlAdmin)
site.register(UserProfile, UserProfileAdmin)
site.register(WorkSpace, WorkspaceAdmin)
//^ Standard Library Imports
//^ Third Party Imports
import "vite/modulepreload-polyfill";
import React, from "react";
import ReactDOM from "react-dom/client";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
//^ Local Imports
import Root from "./Root";
import ErrorPage from "./error-page";
import "./styles.css";
import "./class_colors.css";
import AuthProvider from "./AuthContext";
import Navbar from "./Navbar";
import Login from "./routes/login";
import Admin from "./routes/Admin";
import Register from "./routes/register";
const router = createBrowserRouter([
{
path: "/",
element: <Navbar />,
errorElement: <ErrorPage />,
children: [
{
path: "/",
element: <Root />,
index: true,
},
{
path: "/login",
element: <Login />,
},
{
path: "/register",
element: <Register />,
},
{
path: "/admin",
element: <Admin />,
},
],
},
]);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<AuthProvider>
<RouterProvider router={router}></RouterProvider>
</AuthProvider>
</React.StrictMode>
);
//^ Third Party Imports
import { createContext, useState, useEffect } from "react";
import { PropTypes } from "prop-types";
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
//^ Local Imports
import { getSession, whoami } from "./tabs_api";
export const AuthContext = createContext();
export default function AuthProvider({ children }) {
//^ State Variables
const [error, setError] = useState("");
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [messages, setMessages] = useState([]);
const [messagesShown, setMessagesShown] = useState(false);
const [user, _setUser] = useState({});
//^ Hooks
useEffect(() => {
let sessionData = sessionStorage.getItem("userData");
if (sessionData !== null) {
sessionData = JSON.parse(sessionData);
console.log("LOCATION: AuthContext.AuthProvider.useEffect[]\nMESSAGE: sessionData found in sessionStorage, setting userData to sessionData\nOBJECT: sessionData:\n", sessionData);
} else {
sessionData = getSession();
console.log("LOCATION: AuthContext.AuthProvider.useEffect[]\nMESSAGE: Existing sessionData not found, getting from back end\nOBJECT: sessionData:\n", sessionData);
}
updateUser(sessionData)
.then(isAuth => {
setIsAuthenticated(isAuth);
console.log("LOCATION: AuthContext.AuthProvider.useEffect[]\nMESSAGE: isAuthenticated set\nOBJECT: isAuthenticated:\n", isAuthenticated);
console.log("isAuth: ", isAuth);
})
.catch(err => {
console.error("LOCATION: AuthContext.AuthProvider.useEffect[]\nMESSAGE: getSession failed\nOBJECT: error:\n", err);
});
}, []);
function _userSession(sessionData=null) {
console.log("LOCATION: AuthContext.AuthProvider._userSession\nMESSAGE: _userSession called\nOBJECT: sessionData:\n", sessionData);
let userData;
if (sessionData !== null) {
console.log("LOCATION: AuthContext.AuthProvider._userSession\nMESSAGE: sessionData is not null, so setting userData\nOBJECT: None:\n");
userData = sessionData;
sessionStorage.setItem("userData", JSON.stringify(userData));
} else {
userData = JSON.parse(sessionStorage.getItem("userData"));
console.log("LOCATION: AuthContext.AuthProvider._userSession\nMESSAGE: sessionData is null, so getting userData from sessionStorage\nOBJECT: userData (from sessionStorage):\n", userData);
};
return userData;
}
//^ Functions
async function updateUser(kwargs=null) {
console.log("LOCATION: AuthContext.AuthProvider.updateUser\nMESSAGE: updateUser called\nOBJECT: kwargs:\n", kwargs);
const isAuth = kwargs !== null ? kwargs.isAuthenticated : isAuthenticated;
console.log("LOCATION: AuthContext.AuthProvider.updateUser\nMESSAGE: isAuth:\n", isAuth);
let userData;
if (kwargs) {
console.log("LOCATION: AuthContext.AuthProvider.updateUser\nMESSAGE: kwargs is not null, so setting userData to kwargs\nOBJECT: None:\n");
userData = kwargs;
} else {
userData = await whoami();
console.log("LOCATION: AuthContext.AuthProvider.updateUser\nMESSAGE: kwargs is null, so calling whoami and setting userData to whoami result\nOBJECT: userData (from whoami):\n", userData);
};
const finalData = _userSession(userData);
_setUser(finalData);
console.log("LOCATION: AuthContext.AuthProvider.updateUser\nMESSAGE: _setUser(finalData) called and returning finalData.isAuthenticated:\nOBJECTS: finalData, finalData.isAuthenticated:\n", finalData);
console.log(finalData.isAuthenticated);
return finalData.isAuthenticated;
};
//@ SHOW MESSAGES
useEffect(() => {
if (!messagesShown) {
messages.forEach(message => {
switch (message.type) {
case "success":
toast.success(message.body);
break;
case "error":
toast.error(message.body);
break;
case "warning":
toast.warning(message.body);
break;
case "info":
toast.info(message.body);
break;
default:
toast(message.body);
break;
}
});
setMessages([]);
setMessagesShown(true);
}
}, [messagesShown, messages]);
function showMessage(type, body) {
setMessages([...messages, {"type": type, "body": body}]);
setMessagesShown(false);
};
//^ Return
return (
<AuthContext.Provider value={{
error,
isAuthenticated,
messages,
user,
setError,
setIsAuthenticated,
showMessage,
updateUser,
}}>
{children}
</AuthContext.Provider>
);
}
AuthProvider.propTypes = {
children: PropTypes.node.isRequired,
};
//^ Third Party Imports
import { toast } from "react-toastify";
import { useContext, useEffect } from "react";
import { Link, Outlet } from "react-router-dom";
// eslint-disable-next-line no-unused-vars
import { ToastContainer, Zoom, Flip, Bounce, Slide } from "react-toastify";
//^ Local Imports
import { AuthContext } from "./AuthContext";
import { logoutApi, whoami, get_all_tab_data } from "./tabs_api";
import "react-toastify/dist/ReactToastify.css";
export default function Navbar() {
useEffect(() => {
const handleKeyDown = (event) => {
if (event.key === "Escape") {
toast.dismiss();
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);
const {
isAuthenticated,
setIsAuthenticated,
showMessage,
user,
updateUser,
} = useContext(AuthContext);
async function logout() {
logoutApi()
.then((data) => {
updateUser(data)
.then((isAuth) => {
setIsAuthenticated(isAuth);
console.log(
"LOCATION: Navbar.Navbar.logout.then\nMESSAGE: Logout successful\n updateUser(data) called and set to isAuth\nsetIsAuthenticated(isAuth) called\nOBJECT: isAuth:\n"
);
console.log(isAuth);
console.log("\tOBJECT: isAuthenticated: ");
console.log(isAuthenticated);
})
.catch((err) => {
console.log(
"LOCATION: Navbar.Navbar.logout.then\nMESSAGE: Logout successful\nOBJECT: error: "
);
console.error(err);
});
showMessage(data.message.type, data.message.body);
})
.catch((err) => {
console.log(
"LOCATION: Navbar.Navbar.logout.catch\nMESSAGE: Logout failed\nOBJECT: error: "
);
console.error(err);
showMessage("danger", "Logout failed!");
});
};
// This is just for testing purposes and will be removed
function handleAllTabData() {
get_all_tab_data()
.then((data) => {
console.log(
"LOCATION: GATD.GATD.handleData.then\nMESSAGE: Response is ok\nOBJECT: data: "
);
console.log(data);
showMessage(
"info",
"Everything is fine. Go on about your business"
);
})
.catch((err) => {
console.log(
"LOCATION: GATD.GATD.handleData.catch \nMESSAGE: Response is not ok \nOBJECT: error: "
);
console.error(err);
});
};
return (
<>
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
<div className="container-fluid">
<a className="navbar-brand" href="#"></a>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarNav">
<ul className="navbar-nav">
<li className="nav-item">
<Link
to="/"
className="nav-link active"
aria-current="page"
>
Home
</Link>
</li>
{isAuthenticated ? (
<li className="nav-item">
<Link
className="nav-link"
to="/"
onClick={logout}
>
Logout
</Link>
</li>
) : (
<>
<li className="nav-item">
<Link className="nav-link" to="/login">
Login
</Link>
</li>
<li className="nav-item">
<Link
className="nav-link"
to="/register"
>
Register
</Link>
</li>
</>
)}
// This is just for testing purposes and will be removed
{isAuthenticated ? (
<li className="nav-item">
<Link
className="nav-link"
onClick={handleAllTabData}
>
Run All Tab Data
</Link>
</li>
) : (
""
)}
<li
className="nav-item dropdown"
data-bs-theme="dark"
>
<a
className="nav-link dropdown-toggle"
href="#"
id="navbarDropdown"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Dropdown
</a>
<ul
className="dropdown-menu"
aria-labelledby="navbarDropdown"
>
<li>
<Link
className="dropdown-item"
to="/admin"
exact={true}
>
Admin
</Link>
</li>
<li>
<Link
disabled
className="dropdown-item disabled"
onClick={"World"}
>
Show Me
</Link>
</li>
<li>
<hr className="dropdown-divider" />
</li>
<li>
<a className="dropdown-item" href="#">
Something else here
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
<div className="navbar-nav ms-auto user-badge-container">
<a className="nav-link user-badge d-flex" href="#">
<i
className={
isAuthenticated
? "text-info bi bi-person-circle p-0 m-0 user-icon"
: "bi bi-person-circle p-0 m-0 user-icon"
}
></i>
<small className="username text-info m-0 ms-2 p-0 align-self-center">
{isAuthenticated ? user.display_name : ""}
</small>
</a>
</div>
</nav>
<ToastContainer
position="bottom-right"
autoClose={4000}
limit={6}
hideProgressBar={false}
transition={Bounce}
newestOnTop
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="dark"
/>
<Outlet />
</>
);
}
import Cookies from "universal-cookie";
const cookies = new Cookies();
// This was written before writing most of the backend code, so it's not relevant and has never been used
export async function createTab(tab, setIsAuthenticated) {
fetch("/api/tabs/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": cookies.get("csrftoken"),
},
credentials: "same-origin",
body: JSON.stringify(tab),
})
.then(response => isResponseOk(response))
.then((data) => {
console.log("LOCATION: tabs_api.createTab.fetch.then\nMESSAGE: Response is ok\nOBJECT: data: ");
console.log(data);
setIsAuthenticated(true);
})
.catch((err) => {
console.error("LOCATION: tabs_api.createTab.fetch.catch\nMESSAGE: Error\nOBJECT: error: \n", err);
});
};
// This is just for testing purposes and will be removed
export async function get_all_tab_data() {
return fetch("/api/get_all_tab_data/", {
headers: {
"Content-Type": "application/json",
"X-CSRFToken": cookies.get("csrftoken"),
},
credentials: "same-origin",
})
.then(response => isResponseOk(response))
.then((data) => {
console.log("LOCATION: tabs_api.get_all_tab_data.fetch.then\nMESSAGE: Response is ok\nOBJECT: data:\n", data);
return data;
})
.catch((err) => {
console.error("LOCATION: tabs_api.get_all_tab_data.fetch.catch\nMESSAGE: Error\nOBJECT: error:\n", err);
});
}
export async function getSession() {
console.log("LOCATION: tabs_api.getSession\nMESSAGE: getSession called\nOBJECT: None: ");
fetch("/api/session/", {
headers: {
"Content-Type": "application/json",
"X-CSRFToken": cookies.get("csrftoken"),
},
credentials: "same-origin",
})
.then(response => isResponseOk(response))
.then((data) => {
console.log("LOCATION: tabs_api.getSession.fetch.then\nMESSAGE: Response is ok\nOBJECT: data:\n", data);
return data;
})
.catch((err) => {
console.error("LOCATION: tabs_api.getSession.fetch.catch\nMESSAGE: Error \nOBJECT: error:\n", err);
});
};
// This was written before writing most of the backend code, so it's not relevant and has never been used
export async function getTabs(query) {
fetch("/api/tabs/", {
headers: {
"Content-Type": "application/json",
"query": query,
},
credentials: "same-origin",
})
.then(response => isResponseOk(response))
.then((data) => {
console.log("LOCATION: tabs_api.getTabs.fetch.then\nMESSAGE: Response is ok\nOBJECT: data:\n", data);
return data;
})
.catch((err) => {
console.error("LOCATION: tabs_api.getTabs.fetch.catch\nMESSAGE: Error\nOBJECT: error:\n", err);
});
};
function getStatusType(status) {
let type;
switch (true) {
case (status >= 100 && status < 200):
type = "info";
break;
case (status >= 200 && status < 300):
type = "success";
break;
case (status >= 300 && status < 400):
type = "warning";
break;
case (status >= 400 && status < 600):
type = "error";
break;
default:
type = "";
break;
};
return type;
};
async function isResponseOk(response) {
const type = getStatusType(response.status);
const RJ = await response.json();
const body = RJ.detail;
RJ["message"] = { type: type, body: body };
if (type === "success") {
console.log("LOCATION: tabs_api.isResponeOk.success\nMESSAGE: Ok\nOBJECT: data (RJ):\n", RJ);
return RJ;
} else {
console.error("LOCATION: tabs_api.isResponseOk.else\nMESSAGE: Response is not ok\nOBJECT: error:\n", `${body}:\n ${response.statusText}}`);
throw Error(`${body}:\n ${response.statusText}`);
}
};
export async function loginApi(username, password, setError) {
return fetch("/api/login/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": cookies.get("csrftoken"),
},
credentials: "same-origin",
body: JSON.stringify({ username: username, password: password }),
})
.then(response => isResponseOk(response))
.then(data => {
console.log("LOCATION: tabs_api.loginApi.fetch.then\nMESSAGE: Response is ok\nOBJECT: data:\n", data);
setError("");
return data;
})
.catch((err) => {
console.error("LOCATION: tabs_api.loginApi.fetch.catch\nMESSAGE: Response is not ok\nOBJECT: error:\n", err);
setError("Wrong username or password.");
throw Error("Wrong username or password.");
});
};
export async function logoutApi() {
return fetch("/api/logout/", {
credentials: "same-origin",
})
.then(response => isResponseOk(response))
.then((data) => {
console.log("LOCATION: tabs_api.logoutApi.fetch.then\nMESSAGE: Response is ok\nOBJECT: data:\n", data);
return data;
})
.catch((err) => {
console.error("LOCATION: tabs_api.logoutApi.fetch.catch\nMESSAGE: Response is not ok\nOBJECT: error:\n", err);
throw new Error("tabs_api.logoutApi: Response is not ok", err);
});
};
export async function registerApi(username, password, passwordConfirmation, setIsAuthenticated, setError) {
return fetch("/api/register/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": cookies.get("csrftoken"),
},
credentials: "same-origin",
body: JSON.stringify({ username: username, password: password, passwordConfirmation: passwordConfirmation })
})
.then(response => isResponseOk(response))
.then(data => {
console.log("LOCATION: tabs_api.registerApi.fetch.then\nMESSAGE: Response is ok\nOBJECT: data:\n", data);
setIsAuthenticated(true);
setError("");
return data;
})
.catch(err => {
console.error("LOCATION: tabs_api.registerApi.fetch.catch\nMESSAGE: Response is not ok\nOBJECT: error:\n", err);
setError("Invalid credentials");
throw new Error("Invalid credentials");
});
};
export async function whoami() {
console.log("whoami() called");
return fetch("/api/whoami/", {
headers: {
"Content-Type": "application/json",
},
credentials: "same-origin",
})
.then(response => response.json())
.then((data) => {
console.log("LOCATION: tabs_api.whoami.fetch.then\nMESSAGE: Response is ok\nOBJECT: data:\n", data);
return data;
})
.catch((err) => {
console.error("LOCATION: tabs_api.whoami.fetch.catch\nMESSAGE: Response is not ok\nOBJECT: error:\n", err);
throw new Error("Invalid credentials");
});
};