Skip to content

Commit 9321f56

Browse files
authored
Merge pull request #41 from Jim-Hodapp-Coaching/dynamic_user_nav_menu
Add a new type called UserSession and use it in the UserNav component
2 parents 1fa724a + 090564a commit 9321f56

File tree

5 files changed

+200
-108
lines changed

5 files changed

+200
-108
lines changed

src/components/ui/user-nav.tsx

+16-6
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ import {
1616
import { logoutUser } from "@/lib/api/user-session";
1717
import { useAppStateStore } from "@/lib/providers/app-state-store-provider";
1818
import { useAuthStore } from "@/lib/providers/auth-store-provider";
19+
import { userFirstLastLettersToString } from "@/types/user-session";
1920
import { useRouter } from "next/navigation";
2021

2122
export function UserNav() {
2223
const router = useRouter();
2324

2425
const { logout } = useAuthStore((action) => action);
2526

27+
const { userSession } = useAuthStore((state) => ({
28+
userSession: state.userSession,
29+
}));
30+
2631
const { reset } = useAppStateStore((action) => action);
2732

2833
async function logout_user() {
@@ -46,16 +51,21 @@ export function UserNav() {
4651
<Button variant="ghost" className="relative mx-2 h-8 w-8 rounded-full">
4752
<Avatar className="h-9 w-9">
4853
<AvatarImage src="/avatars/03.png" alt="@jhodapp" />
49-
<AvatarFallback>JH</AvatarFallback>
54+
<AvatarFallback>
55+
{userFirstLastLettersToString(
56+
userSession.first_name,
57+
userSession.last_name
58+
)}
59+
</AvatarFallback>
5060
</Avatar>
5161
</Button>
5262
</DropdownMenuTrigger>
5363
<DropdownMenuContent className="w-56" align="end" forceMount>
5464
<DropdownMenuLabel className="font-normal">
5565
<div className="flex flex-col space-y-1">
56-
<p className="text-sm font-medium leading-none">Jim Hodapp</p>
66+
<p className="text-sm font-medium leading-none">{`${userSession.first_name} ${userSession.last_name}`}</p>
5767
<p className="text-xs leading-none text-muted-foreground">
58-
68+
{userSession.email}
5969
</p>
6070
</div>
6171
</DropdownMenuLabel>
@@ -65,19 +75,19 @@ export function UserNav() {
6575
Profile
6676
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
6777
</DropdownMenuItem>
68-
<DropdownMenuItem>
78+
{/* <DropdownMenuItem>
6979
Current Organization
7080
<DropdownMenuShortcut>⌘O</DropdownMenuShortcut>
7181
</DropdownMenuItem>
7282
<DropdownMenuItem>
7383
Billing
7484
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
75-
</DropdownMenuItem>
85+
</DropdownMenuItem> */}
7686
<DropdownMenuItem>
7787
Settings
7888
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
7989
</DropdownMenuItem>
80-
<DropdownMenuItem>New Team</DropdownMenuItem>
90+
{/* <DropdownMenuItem>New Team</DropdownMenuItem> */}
8191
</DropdownMenuGroup>
8292
<DropdownMenuSeparator />
8393
<DropdownMenuItem onClick={logout_user}>

src/components/user-auth-form.tsx

+13-11
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import { Icons } from "@/components/ui/icons";
1010
import { Button } from "@/components/ui/button";
1111
import { Input } from "@/components/ui/input";
1212
import { Label } from "@/components/ui/label";
13+
import { userSessionToString } from "@/types/user-session";
1314

1415
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}
1516

1617
export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
1718
const router = useRouter();
18-
const { userId } = useAuthStore((state) => state);
1919
const { login } = useAuthStore((action) => action);
2020

2121
const [isLoading, setIsLoading] = React.useState<boolean>(false);
@@ -27,16 +27,18 @@ export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
2727
event.preventDefault();
2828
setIsLoading(true);
2929

30-
const [userId, err] = await loginUser(email, password);
31-
if (userId.length > 0 && err.length == 0) {
32-
login(userId);
33-
router.push("/dashboard");
34-
} else {
35-
console.error("err: " + err);
36-
setError(err);
37-
}
38-
39-
setIsLoading(false);
30+
await loginUser(email, password)
31+
.then((userSession) => {
32+
console.debug("userSession: " + userSessionToString(userSession));
33+
login(userSession.id, userSession);
34+
setIsLoading(false);
35+
router.push("/dashboard");
36+
})
37+
.catch((err) => {
38+
setIsLoading(false);
39+
console.error("Login failed, err: " + err);
40+
setError(err);
41+
});
4042
}
4143

4244
const updateEmail = (value: string) => {

src/lib/api/user-session.ts

+70-57
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,82 @@
11
// Interacts with the user_session_controller endpoints
2-
import { Id } from "@/types/general";
2+
import {
3+
defaultUserSession,
4+
isUserSession,
5+
parseUserSession,
6+
UserSession,
7+
} from "@/types/user-session";
38
import { AxiosError, AxiosResponse } from "axios";
49

5-
export const loginUser = async (email: string, password: string): Promise<[Id, string]> => {
6-
const axios = require("axios");
10+
export const loginUser = async (
11+
email: string,
12+
password: string
13+
): Promise<UserSession> => {
14+
const axios = require("axios");
715

8-
console.log("email: ", email);
9-
console.log("password: ", password.replace(/./g, "*"));
16+
console.log("email: ", email);
17+
console.log("password: ", password.replace(/./g, "*"));
1018

11-
var userId: Id = "";
12-
var err: string = "";
19+
var userSession: UserSession = defaultUserSession();
20+
var err: string = "";
1321

14-
const data = await axios
15-
.post(
16-
"http://localhost:4000/login",
17-
{
18-
email: email,
19-
password: password,
22+
const data = await axios
23+
.post(
24+
"http://localhost:4000/login",
25+
{
26+
email: email,
27+
password: password,
28+
},
29+
{
30+
withCredentials: true,
31+
headers: {
32+
"Content-Type": "application/x-www-form-urlencoded",
2033
},
21-
{
22-
withCredentials: true,
23-
headers: {
24-
"Content-Type": "application/x-www-form-urlencoded",
25-
},
26-
setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend
27-
}
28-
)
29-
.then(function (response: AxiosResponse) {
30-
// handle success
31-
userId = response.data.data.id;
32-
})
33-
.catch(function (error: AxiosError) {
34-
// handle error
35-
console.error(error.response?.status);
36-
if (error.response?.status == 401) {
37-
err = "Login failed: unauthorized";
38-
} else {
39-
console.error(error);
40-
err = `Login failed: ${error.message}`;
41-
}
42-
})
34+
setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend
35+
}
36+
)
37+
.then(function (response: AxiosResponse) {
38+
// handle success
39+
const userSessionData = response.data.data;
40+
if (isUserSession(userSessionData)) {
41+
userSession = parseUserSession(userSessionData);
42+
}
43+
})
44+
.catch(function (error: AxiosError) {
45+
// handle error
46+
console.error(error.response?.status);
47+
if (error.response?.status == 401) {
48+
err = "Login failed: unauthorized";
49+
} else {
50+
err = `Login failed: ${error.message}`;
51+
}
52+
});
4353

44-
return [userId, err];
45-
}
54+
if (err) {
55+
console.error(err);
56+
throw err;
57+
}
58+
59+
return userSession;
60+
};
4661

4762
export const logoutUser = async (): Promise<string> => {
48-
const axios = require("axios");
63+
const axios = require("axios");
4964

50-
const data = await axios
51-
.get(
52-
"http://localhost:4000/logout",
53-
{
54-
withCredentials: true,
55-
setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend
56-
}
57-
)
58-
.then(function (response: AxiosResponse) {
59-
// handle success
60-
console.debug(response);
61-
})
62-
.catch(function (error: AxiosError) {
63-
// handle error
64-
console.error(`Logout failed: ${error.message}`);
65-
return(`Logout failed: ${error.message}`);
66-
})
65+
const data = await axios
66+
.get("http://localhost:4000/logout", {
67+
withCredentials: true,
68+
setTimeout: 5000, // 5 seconds before timing out trying to log in with the backend
69+
})
70+
.then(function (response: AxiosResponse) {
71+
// handle success
72+
console.debug(response);
73+
})
74+
.catch(function (error: AxiosError) {
75+
// handle error
76+
const err = `Logout failed: ${error.message}`;
77+
console.error(err);
78+
return err;
79+
});
6780

68-
return "";
69-
}
81+
return "";
82+
};

src/lib/stores/auth-store.ts

+35-34
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,48 @@
1-
import { Id } from '@/types/general';
2-
import { create, useStore } from 'zustand';
3-
import { createJSONStorage, devtools, persist } from 'zustand/middleware';
1+
import { Id } from "@/types/general";
2+
import { defaultUserSession, UserSession } from "@/types/user-session";
3+
import { create, useStore } from "zustand";
4+
import { createJSONStorage, devtools, persist } from "zustand/middleware";
45

56
interface AuthState {
6-
// Holds user id UUID from the backend DB schema for a User
7-
userId: Id;
8-
isLoggedIn: boolean;
7+
// Holds user id UUID from the backend DB schema for a User
8+
userId: Id;
9+
userSession: UserSession;
10+
isLoggedIn: boolean;
911
}
1012

1113
interface AuthActions {
12-
login: (userId: Id) => void;
13-
logout: () => void;
14+
login: (userId: Id, userSession: UserSession) => void;
15+
logout: () => void;
1416
}
1517

1618
export type AuthStore = AuthState & AuthActions;
1719

1820
export const defaultInitState: AuthState = {
19-
userId: "",
20-
isLoggedIn: false,
21-
}
21+
userId: "",
22+
userSession: defaultUserSession(),
23+
isLoggedIn: false,
24+
};
2225

23-
export const createAuthStore = (
24-
initState: AuthState = defaultInitState,
25-
) => {
26-
const authStore = create<AuthStore>()(
27-
devtools(
28-
persist(
29-
(set) => ({
30-
... initState,
26+
export const createAuthStore = (initState: AuthState = defaultInitState) => {
27+
const authStore = create<AuthStore>()(
28+
devtools(
29+
persist(
30+
(set) => ({
31+
...initState,
3132

32-
login: (userId) => {
33-
set({ isLoggedIn: true, userId });
34-
},
35-
logout: () => {
36-
set({ isLoggedIn: false, userId: undefined });
37-
},
38-
}),
39-
{
40-
name: 'auth-store',
41-
storage: createJSONStorage(() => sessionStorage),
42-
}
43-
)
44-
)
33+
login: (userId, userSession) => {
34+
set({ isLoggedIn: true, userId, userSession });
35+
},
36+
logout: () => {
37+
set(defaultInitState);
38+
},
39+
}),
40+
{
41+
name: "auth-store",
42+
storage: createJSONStorage(() => sessionStorage),
43+
}
44+
)
4545
)
46-
return authStore;
47-
}
46+
);
47+
return authStore;
48+
};

src/types/user-session.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Id } from "@/types/general";
2+
3+
// This must always reflect the Rust struct on the backend
4+
// controller::user_session_controller::login()'s return value
5+
export interface UserSession {
6+
id: Id;
7+
email: string;
8+
first_name: string;
9+
last_name: string;
10+
display_name: string;
11+
}
12+
13+
export function parseUserSession(data: any): UserSession {
14+
if (!isUserSession(data)) {
15+
throw new Error("Invalid UserSession object data");
16+
}
17+
return {
18+
id: data.id,
19+
email: data.email,
20+
first_name: data.first_name,
21+
last_name: data.last_name,
22+
display_name: data.display_name,
23+
};
24+
}
25+
26+
export function isUserSession(value: unknown): value is UserSession {
27+
if (!value || typeof value !== "object") {
28+
return false;
29+
}
30+
const object = value as Record<string, unknown>;
31+
32+
return (
33+
typeof object.id === "string" &&
34+
typeof object.email === "string" &&
35+
typeof object.first_name === "string" &&
36+
typeof object.last_name === "string" &&
37+
typeof object.display_name === "string"
38+
);
39+
}
40+
41+
export function defaultUserSession(): UserSession {
42+
return {
43+
id: "",
44+
email: "",
45+
first_name: "",
46+
last_name: "",
47+
display_name: "",
48+
};
49+
}
50+
51+
// Given first and last name strings, return the first letters of each as a new string
52+
// e.g. "John" "Smith" => "JS"
53+
export function userFirstLastLettersToString(
54+
firstName: string,
55+
lastName: string
56+
): string {
57+
const firstLetter = firstName.charAt(0);
58+
const lastLetter = lastName.charAt(0);
59+
return firstLetter + lastLetter;
60+
}
61+
62+
export function userSessionToString(
63+
userSession: UserSession | undefined
64+
): string {
65+
return JSON.stringify(userSession);
66+
}

0 commit comments

Comments
 (0)