feat(components, services): Add user data management and account handling

This commit introduces user data management with the addition of new interfaces and services. User data interface which defines the structure of user data was created. Account handler service for registering, logging in, updating user data and disconnecting was also added. User data provider component was created for managing user data state. The account info component for displaying and editing the user account was introduced too. Finally, updates were made in the existing components and services for accommodating these new elements.
This commit is contained in:
Mathis H (Avnyr) 2024-06-07 16:57:37 +02:00
parent d624ac6ab2
commit 1dd0384857
Signed by: Mathis
GPG Key ID: DD9E0666A747D126
11 changed files with 172 additions and 13 deletions

View File

@ -23,6 +23,7 @@
"@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-label": "^2.0.2", "@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.4", "@radix-ui/react-menubar": "^1.0.4",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-radio-group": "^1.1.3",
@ -45,6 +46,7 @@
"embla-carousel-react": "^8.1.3", "embla-carousel-react": "^8.1.3",
"framer-motion": "^11.2.10", "framer-motion": "^11.2.10",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"lightweight-charts": "^4.1.4",
"lucide-react": "^0.387.0", "lucide-react": "^0.387.0",
"next": "14.2.3", "next": "14.2.3",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",

View File

@ -5,6 +5,8 @@ import {ThemeProvider} from "@/components/providers/theme-provider";
import type React from "react"; import type React from "react";
import {Footer} from "@/components/footer"; import {Footer} from "@/components/footer";
import {Header} from "@/components/header"; import {Header} from "@/components/header";
import {PrimaryNavigationMenu} from "@/components/primary-nav";
import {Providers} from "@/components/providers/providers";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "YeloBit", title: "YeloBit",
@ -21,16 +23,14 @@ export default function RootLayout({
<head> <head>
<link rel="icon" href="/public/favicon.ico" sizes="any"/> <link rel="icon" href="/public/favicon.ico" sizes="any"/>
</head> </head>
<body className={"w-full min-h-screen flex flex-col items-center justify-between"}> <body className={"w-full min-h-screen flex flex-col items-center justify-between"}>
<ThemeProvider <Providers>
attribute="class" <Header>
defaultTheme="system" <PrimaryNavigationMenu/>
enableSystem </Header>
> {children}
<Header></Header> <Footer/>
{children} </Providers>
<Footer/>
</ThemeProvider>
</body> </body>
</html> </html>
); );

View File

@ -0,0 +1,22 @@
"use client"
import {useContext} from "react";
import {UserDataContext} from "@/components/providers/userdata-provider";
import {AccountInfo} from "@/components/account-info";
export function AccountDialog() {
const userContext = useContext(UserDataContext)
if (!userContext?.userData) {
userContext?.setUserData({name: "Mathis"})
return (<p>Loading...</p>)
}
//TODO No account context
//TODO Loading context
//TODO Account context
return (<AccountInfo userData={userContext.userData}/>)
}

View File

@ -0,0 +1,58 @@
"use client"
import {IUserData} from "@/interfaces/userdata.interface";
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import {User} from "lucide-react";
export function AccountInfo({userData}: {userData: IUserData}) {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" className={"gap-1"}>
<User />
{userData?.name || "?"}
</Button>
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>Edit profile</SheetTitle>
<SheetDescription>
Make changes to your profile here. Click save when you're done.
</SheetDescription>
</SheetHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Name
</Label>
<Input id="name" placeholder={userData.name} className="col-span-3" onChange={(event)=>{console.log(event.target.value)}} />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="username" className="text-right">
Username
</Label>
<Input id="username" value="@peduarte" className="col-span-3" />
</div>
</div>
<SheetFooter>
<SheetClose asChild>
<Button type="submit">Save changes</Button>
</SheetClose>
</SheetFooter>
</SheetContent>
</Sheet>
)
}

View File

@ -1,6 +1,7 @@
import Image from "next/image"; import Image from "next/image";
import React from "react"; import React from "react";
import {ThemeBtnSelector} from "@/components/theme-btn-selector"; import {ThemeBtnSelector} from "@/components/theme-btn-selector";
import {AccountDialog} from "@/components/account-dialog";
export function Header({title, children}: {title?: string, children?: React.ReactNode}) { export function Header({title, children}: {title?: string, children?: React.ReactNode}) {
@ -16,7 +17,8 @@ export function Header({title, children}: {title?: string, children?: React.Reac
<div className={"w-1/3 flex flex-row justify-center items-center"}> <div className={"w-1/3 flex flex-row justify-center items-center"}>
{children} {children}
</div> </div>
<div className={"w-1/3 flex flex-row justify-end items-center"}> <div className={"w-1/3 flex flex-row justify-end gap-2 items-center"}>
<AccountDialog/>
<ThemeBtnSelector/> <ThemeBtnSelector/>
</div> </div>
</header> </header>

View File

@ -0,0 +1,23 @@
"use client"
import React from "react";
import {Header} from "@/components/header";
import {Footer} from "@/components/footer";
import {ThemeProvider} from "@/components/providers/theme-provider";
import {UserDataProvider} from "@/components/providers/userdata-provider";
export function Providers({children}: { children: React.ReactNode }) {
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
>
<UserDataProvider>
{children}
</UserDataProvider>
</ThemeProvider>
)
}

View File

@ -0,0 +1,21 @@
import React from 'react';
import {IUserData} from "@/interfaces/userdata.interface";
import {useEncodedLocalStorage} from "@/services/localStorage";
export interface IUserDataProvider {
userData: IUserData | undefined;
setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>
}
export const UserDataContext = React.createContext<IUserDataProvider | undefined>(undefined);
export const UserDataProvider = ({children}: {children: React.ReactNode}) => {
const [userData, setUserData] = useEncodedLocalStorage<IUserData | undefined>("user_data", undefined);
return (
<UserDataContext.Provider value={{userData, setUserData}}>
{children}
</UserDataContext.Provider>
);
};

View File

@ -0,0 +1,3 @@
export interface IUserData {
name: string
}

View File

@ -0,0 +1,14 @@
"use client"
import { createContext, useContext, useState } from 'react';
import {IUserData} from "@/interfaces/userdata.interface";
const UserDataContext = createContext<IUserData>(false)
//TODO Run register task
//TODO Run login task
//TODO Run disconnect task
//TODO Run update user data

View File

View File

@ -1,6 +1,6 @@
'use client' 'use client'
import React, {useEffect, useState} from "react"; import React, {useEffect, useRef, useState} from "react";
/** /**
* A custom React hook that allows you to store and retrieve data in the browser's localStorage. * A custom React hook that allows you to store and retrieve data in the browser's localStorage.
@ -48,12 +48,20 @@ export function useLocalStorage<T>(key: string, initial: T): [T, React.Dispatch<
* @return {readonly [T, React.Dispatch<React.SetStateAction<T>>]} - An array containing the encoded value and a function to update the encoded value. * @return {readonly [T, React.Dispatch<React.SetStateAction<T>>]} - An array containing the encoded value and a function to update the encoded value.
*/ */
export function useEncodedLocalStorage<T>(key: string, fallbackValue: T): readonly [T, React.Dispatch<React.SetStateAction<T>>] { export function useEncodedLocalStorage<T>(key: string, fallbackValue: T): readonly [T, React.Dispatch<React.SetStateAction<T>>] {
console.log("Pong !")
const [encodedValue, setEncodedValue] = useState<T>(() => { const [encodedValue, setEncodedValue] = useState<T>(() => {
const stored = localStorage.getItem(key); const stored = localStorage.getItem(key);
return stored ? safelyParse(stored, fallbackValue) : fallbackValue; return stored ? safelyParse(stored, fallbackValue) : fallbackValue;
}); });
const prevValue = useRef(encodedValue);
useEffect(() => { useEffect(() => {
localStorage.setItem(key, safelyStringify(encodedValue)); console.log({encodedValue})
if (!b64ValEqual(prevValue.current, encodedValue)) {
localStorage.setItem(key, safelyStringify(encodedValue));
}
prevValue.current = encodedValue; // Set ref to current value
}, [key, encodedValue]); }, [key, encodedValue]);
return [encodedValue, setEncodedValue] as const; return [encodedValue, setEncodedValue] as const;
@ -64,6 +72,12 @@ export function useEncodedLocalStorage<T>(key: string, fallbackValue: T): readon
return fallback; return fallback;
} }
} }
function b64ValEqual<T>(v1: T, v2: T): boolean {
return btoa(JSON.stringify(v1)) === btoa(JSON.stringify(v2));
}
function safelyStringify(value: T): string { function safelyStringify(value: T): string {
try { try {
return btoa(JSON.stringify(value)); return btoa(JSON.stringify(value));