Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds state storage and frontend APIs that wrap backend endpoint calls #16

Merged
merged 3 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,284 changes: 773 additions & 511 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
"react-day-picker": "^8.10.0",
"react-dom": "^18",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"ts-luxon": "^4.5.2",
"zustand": "^4.5.2"
},
"devDependencies": {
"@types/node": "^20",
Expand Down
79 changes: 77 additions & 2 deletions src/app/coaching-sessions/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Metadata } from "next";
// import { Metadata } from "next";

import * as React from "react";

Expand Down Expand Up @@ -34,8 +34,13 @@ import { PresetShare } from "@/components/ui/preset-share";
import { TemperatureSelector } from "@/components/ui/temperature-selector";
import { TopPSelector } from "@/components/ui/top-p-selector";
import { cn } from "@/lib/utils";
// import { fetchOrganizations, fetchOrganization, deleteOrganization, createOrganization, updateOrganization } from "@/lib/api/organizations";
import { useAuthStore } from "@/lib/providers/auth-store-provider";
import { models, types } from "../../data/models";
import { current, future, past } from "../../data/presets";
// import { useEffect, useState } from "react";
// import { Organization } from "@/types/organization";
// import { DateTime } from "ts-luxon";

// export const metadata: Metadata = {
// title: "Coaching Session",
Expand All @@ -44,6 +49,76 @@ import { current, future, past } from "../../data/presets";

export default function CoachingSessionsPage() {
const [isOpen, setIsOpen] = React.useState(false);
const { isLoggedIn, userUUID } = useAuthStore(
(state) => state,
)

// FIXME: these are here as "unit tests" ahead of adding real unit testing in a
// near future commit.

// const [organization, setOrganization] = useState<Organization | null>(null);
// useEffect(() => {
// async function loadOrganization() {
// const fetchedOrganization = await fetchOrganization(1);
// console.debug("Organization(1): " + JSON.stringify(fetchedOrganization[0]));
// setOrganization(fetchedOrganization[0]);
// }
// loadOrganization();
// }, []);

// const [organizations, setOrganizations] = useState<Organization[] | null>(null);
// useEffect(() => {
// async function loadOrganizations() {
// const fetchedOrganizations = await fetchOrganizations();
// console.debug("Organizations: " + JSON.stringify(fetchedOrganizations[0]));
// setOrganizations(fetchedOrganizations[0]);
// }
// loadOrganizations();
// }, []);

// const [createdOrganization, setCreatedOrganization] = useState<Organization | null>(null);
// useEffect(() => {
// async function loadOrganization() {
// var organization: Organization = {
// external_id: "fa594ab3-fad0-4be9-a8fe-9088b2911914",
// name: "New test organization",
// logo: "http://www.fake.org/logo",
// created_at: DateTime.now(),
// updated_at: DateTime.now()
// }
// const createdOrganization = await createOrganization(organization);
// console.debug("createdOrganization: " + JSON.stringify(createdOrganization[0]));
// setCreatedOrganization(createdOrganization[0]);
// }
// loadOrganization();
// }, []);

// const [updatedOrganization, setUpdatedOrganization] = useState<Organization | null>(null);
// useEffect(() => {
// async function loadOrganization() {
// var organization: Organization = {
// external_id: "fa594ab3-fad0-4be9-a8fe-9088b2911914",
// name: "Jim Hodapp Coaching",
// logo: "http://www.fake.org/logo",
// created_at: DateTime.now(),
// updated_at: DateTime.now()
// }
// const updatedOrganization = await updateOrganization(1, organization);
// console.debug("updatedOrganization: " + JSON.stringify(updatedOrganization[0]));
// setUpdatedOrganization(updatedOrganization[0]);
// }
// loadOrganization();
// }, []);

// const [deletedOrganization, setDeletedOrganization] = useState<string | null>(null);
// useEffect(() => {
// async function loadOrganization() {
// const deletedOrganizationId = await deleteOrganization(1);
// console.debug("deletedOrganizationId: " + JSON.stringify(deletedOrganizationId[0]));
// setDeletedOrganization(deletedOrganizationId[0]);
// }
// loadOrganization();
// }, []);

return (
<>
Expand Down Expand Up @@ -147,7 +222,7 @@ export default function CoachingSessionsPage() {
<TabsContent value="notes">
<div className="flex h-full flex-col space-y-4">
<Textarea
placeholder="Notes"
placeholder="Session notes"
className="p-4 min-h-[400px] md:min-h-[630px] lg:min-h-[630px]"
/>
</div>
Expand Down
7 changes: 6 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ThemeProvider } from "@/components/ui/providers"
import { Inter } from "next/font/google";
import "@/styles/globals.css";

import { AuthStoreProvider } from "@/lib/providers/auth-store-provider";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
Expand All @@ -24,7 +26,10 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
{children}
{/* Provides a single AuthStore instance to all child pages/components/functions */}
<AuthStoreProvider>
{children}
</AuthStoreProvider>
</ThemeProvider>
</body>
</html>
Expand Down
5 changes: 1 addition & 4 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,8 @@ export default function AuthenticationPage() {
<div className="relative z-20 mt-auto">
<blockquote className="space-y-2">
<p className="text-lg">
&ldquo;This library has saved me countless hours of work and
helped me deliver stunning designs to my clients faster than
ever before.&rdquo;
A coaching and mentorship platform for engineering leaders and software engineers.
</p>
<footer className="text-sm">Sofia Davis</footer>
</blockquote>
</div>
</div>
Expand Down
25 changes: 24 additions & 1 deletion src/components/ui/site-header.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
"use client";

import { CommandMenu } from "@/components/ui/command-menu"
import { MainNav } from "@/components/ui/main-nav"
import { MobileNav } from "@/components/ui/mobile-nav"
import { ModeToggle } from "@/components/ui/mode-toggle"
import { UserNav } from "@/components/ui/user-nav"

// import { useRouter } from 'next/navigation'
// import { useAuthStore } from "@/lib/providers/auth-store-provider";

export function SiteHeader() {
// This must have "use client" at the top, hooks don't work on server components
// or you'll get a very strange runtime error about useAuthStore() not being a function.
// const { isLoggedIn } = useAuthStore(
// (state) => state,
// )
// const router = useRouter();

return (
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-14 max-w-screen-2xl items-center">
Expand All @@ -16,7 +28,18 @@ export function SiteHeader() {
</div>
<nav className="flex items-center">
<ModeToggle />
<UserNav />
{/* {isLoggedIn ? ( */}
<UserNav />
{/* ) : ( */}
{/* <> */}
{/* {console.warn("User is not logged in, redirecting to login route.")} */}
{/* TODO: For some reason, this is causing a redirect boundary error / trying to update
2 components (Router) + (SiteHeader) at the same time. I think it has to do with the
AuthStoreProvider and the two different layouts.
Help: https://reactjs.org/link/setstate-in-render */}
{/* router.push("/login") */}
{/* </> */}
{/* )} */}
</nav>
</div>
</div>
Expand Down
45 changes: 17 additions & 28 deletions src/components/ui/user-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,27 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

import { AxiosError, AxiosResponse } from "axios";
import { logoutUser } from "@/lib/api/user-session";
import { useAuthStore } from "@/lib/providers/auth-store-provider";
import { useRouter } from "next/navigation";

export function UserNav() {
export function UserNav() {
const router = useRouter();
const axios = require("axios");

async function logout() {
console.log("Logging out");
const { logout } = useAuthStore(
(action) => action,
);

const data = await axios
.get(
"http://localhost:4000/logout",
{
withCredentials: true,
setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend
}
)
.then(function (response: AxiosResponse) {
// handle success
console.log(response);
async function logout_user() {
const err = await logoutUser();
if (err.length > 0) {
console.error("Error while logging out: " + err);
}

router.push("/login");
})
.catch(function (error: AxiosError) {
// handle error
console.log(error.response?.status);
console.log(`Logout failed: ${error.message}`);
})
.finally(function () {
// always executed
});
console.log("Doing auth-store logout");
logout();
// console.log("Redirecting to /login");
router.push("/login");
}

return (
Expand Down Expand Up @@ -91,7 +80,7 @@ import { useRouter } from "next/navigation";
<DropdownMenuItem>New Team</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={logout}>
<DropdownMenuItem onClick={logout_user}>
Log out
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
</DropdownMenuItem>
Expand Down
69 changes: 21 additions & 48 deletions src/components/user-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import * as React from "react";

import { useRouter } from "next/navigation";

import { AxiosError, AxiosResponse } from "axios";

import { cn } from "@/lib/utils";
import { loginUser } from "@/lib/api/user-session";
import { useAuthStore } from "@/lib/providers/auth-store-provider";
import { useRouter } from "next/navigation";
import { Icons } from "@/components/ui/icons";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand All @@ -16,59 +15,32 @@ interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}

export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
const router = useRouter();
const axios = require("axios");
const { userUUID } = useAuthStore(
(state) => state,
);
const { login } = useAuthStore(
(action) => action,
);

const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [email, setEmail] = React.useState<string>("");
const [password, setPassword] = React.useState<string>("");
const [error, setError] = React.useState<string>("");

async function login(event: React.SyntheticEvent) {
async function login_user(event: React.SyntheticEvent) {
event.preventDefault();
setIsLoading(true);

console.log("email: ", email);
console.log("password: ", password);

console.log("Submitting login form with a POST request");

const data = await axios
.post(
"http://localhost:4000/login",
{
email: email,
password: password,
},
{
withCredentials: true,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend
}
)
.then(function (response: AxiosResponse) {
// handle success
console.log(response);
const [userUUID, err] = await loginUser(email, password);
if (userUUID.length > 0 && err.length == 0) {
login(userUUID);
router.push("/coaching-sessions");
} else {
console.error("err: " + err);
setError(err);
}

router.push("/coaching-sessions");
})
.catch(function (error: AxiosError) {
// handle error
console.log(error.response?.status);
if (error.response?.status == 401) {
setError("Login failed: unauthorized");
} else {
console.log(error);
setError(`Login failed: ${error.message.toLocaleLowerCase}`);
}
})
.finally(function () {
// always executed
setTimeout(() => {
setIsLoading(false);
}, 3000);
});
setIsLoading(false);
}

const updateEmail = (value: string) => {
Expand All @@ -83,7 +55,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {

return (
<div className={cn("grid gap-6", className)} {...props}>
<form onSubmit={login}>
<form onSubmit={login_user}>
<div className="grid gap-2">
<div className="grid gap-1">
<Label className="sr-only" htmlFor="email">
Expand Down Expand Up @@ -111,6 +83,7 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
type="password"
name="password"
autoCapitalize="none"
autoComplete="current-password"
autoCorrect="off"
required
disabled={isLoading}
Expand Down
1 change: 1 addition & 0 deletions src/lib/api/coaching-relationships.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Interacts with the coaching_relationship endpoints
Loading
Loading