Skip to content

Instantly share code, notes, and snippets.

@GigglePocket
Last active December 7, 2023 07:11
Show Gist options
  • Save GigglePocket/7c11eeb1c473e4e74d44c1e2c6be67bf to your computer and use it in GitHub Desktop.
Save GigglePocket/7c11eeb1c473e4e74d44c1e2c6be67bf to your computer and use it in GitHub Desktop.
Asking for help from my betters about how to structure and handle/structure react-beautiful-dnd data in the db on the backend and IRT on the frontend

Project Info:


Directory Structure 2023/12/06:

📂 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)

Other Relevant Information:

  • 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)

The Purpose of this Application:

  • A tab manager similar to Workona, but with much less functionality

Specifications:

  • 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)
  • This will be displayed in the Root.jsx file using react-beautiful-dnd
  • The register, and login pages are already written and fully functional

Drag and Drop Data Structure:

  • the data will be a JOBJECTIONARY (my mocking name for an Object in javascript for calling it an Object) of 4 droppables:

  1. The left hand pane, or WorkSpace will be droppable, but only to allow dnd to reorder workspaces
  2. 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.
  3. The right hand pane, or Resource will be droppable, but only to allow dnd to reorder ResourceSection S.
  4. 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:

  1. The WorkSpace S, only within the left pane for reordering
  2. The ResourceSection S, only within the right pane for reordering
  3. 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": {
            "...": "..."
        }
    }
}

Data Structure (with annotations):

{
    "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 ...
    }
    }

Code:

Backend:

models.py:

  • 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

admin.py

  • 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)

Frontend:

main.jsx

//^ 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>
);

AuthContext.jsx

//^ 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,
};

Navbar.jsx

//^ 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 />
        </>
    );
}

tabs_api.js

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");
        });
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment