Compare commits
2 Commits
old
...
0ead6bd969
| Author | SHA1 | Date | |
|---|---|---|---|
|
0ead6bd969
|
|||
|
747cc1cdb4
|
@@ -4,13 +4,7 @@ import { AccountInfo } from "@/components/account-info";
|
|||||||
import { UserDataContext } from "@/components/providers/userdata-provider";
|
import { UserDataContext } from "@/components/providers/userdata-provider";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import type { IUserData } from "@/interfaces/userdata.interface";
|
import type { IUserData } from "@/interfaces/userdata.interface";
|
||||||
import {
|
import {Dispatch, SetStateAction, useContext, useEffect, useState} from "react";
|
||||||
type Dispatch,
|
|
||||||
type SetStateAction,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
export function AccountDialog() {
|
export function AccountDialog() {
|
||||||
const userContext = useContext(UserDataContext);
|
const userContext = useContext(UserDataContext);
|
||||||
@@ -18,44 +12,18 @@ export function AccountDialog() {
|
|||||||
|
|
||||||
if (!userContext?.userData) {
|
if (!userContext?.userData) {
|
||||||
userContext?.setUserData({
|
userContext?.setUserData({
|
||||||
firstName: "Mathis",
|
age: 0,
|
||||||
lastName: "Herriot",
|
|
||||||
age: 23,
|
|
||||||
city: "Chambéry",
|
city: "Chambéry",
|
||||||
created_at: "jaj",
|
created_at: "jaj",
|
||||||
dollarAvailables: 34,
|
dollarAvailables: 34,
|
||||||
email: "mherriot@tutanota.com",
|
email: "mherriot@tutanota.com",
|
||||||
id: "098807e1-6681-407a-a2b4-e6b7e5ec1919",
|
id: "",
|
||||||
isActive: true,
|
isActive: false,
|
||||||
|
lastName: "Herriot",
|
||||||
pseudo: "Avnyr",
|
pseudo: "Avnyr",
|
||||||
roleId: "092fb1eb-4ce4-4e3e-9df1-d00d467c6a43",
|
roleId: "",
|
||||||
updated_at: "",
|
updated_at: "",
|
||||||
wallet: {
|
firstName: "Mathis",
|
||||||
uat: Date.now(),
|
|
||||||
update_interval: 30_000,
|
|
||||||
owned_cryptos: [
|
|
||||||
{
|
|
||||||
id: "1c237bcc-d2fa-4c4b-9a3a-f0efb4569e1e",
|
|
||||||
name: "Spectral",
|
|
||||||
value: 1.61051,
|
|
||||||
image:
|
|
||||||
"https://images.unsplash.com/photo-1640833906651-6bd1af7aeea3?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8MTh8fGNyeXB0b3xlbnwwfHwwfHx8MA%3D%3D",
|
|
||||||
quantity: 999,
|
|
||||||
created_at: "2024-06-08T22:42:14.113Z",
|
|
||||||
updated_at: "2024-06-11T12:48:07.001Z",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "6c0edc95-a968-4c2f-9169-33acfdc73575",
|
|
||||||
name: "SOL",
|
|
||||||
value: 30.91268053287073,
|
|
||||||
image:
|
|
||||||
"https://images.unsplash.com/photo-1523961131990-5ea7c61b2107?w=500&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Nnx8Y3J5cHRvfGVufDB8fDB8fHww",
|
|
||||||
quantity: 998,
|
|
||||||
created_at: "2024-06-07T09:30:31.282Z",
|
|
||||||
updated_at: "2024-06-08T23:44:54.242Z",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,12 +39,7 @@ export function AccountDialog() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AccountInfo
|
<AccountInfo userData={userContext?.userData as IUserData} setUserData={userContext?.setUserData as Dispatch<SetStateAction<IUserData | undefined>>} />
|
||||||
userData={userContext?.userData as IUserData}
|
|
||||||
setUserData={
|
|
||||||
userContext?.setUserData as Dispatch<SetStateAction<IUserData | undefined>>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,8 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import type { IUserData } from "@/interfaces/userdata.interface";
|
import type { IUserData } from "@/interfaces/userdata.interface";
|
||||||
|
|
||||||
import { CopyButton } from "@/components/ui/copy-button";
|
import { Landmark, Unplug, User, Wallet } from "lucide-react";
|
||||||
import {Bitcoin, Fingerprint, Landmark, RefreshCw, Unplug, User, Wallet} from "lucide-react";
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import {useState} from "react";
|
|
||||||
import {refreshUserData} from "@/services/account.handler";
|
|
||||||
|
|
||||||
export function AccountInfo({
|
export function AccountInfo({
|
||||||
userData,
|
userData,
|
||||||
@@ -26,14 +23,6 @@ export function AccountInfo({
|
|||||||
userData: IUserData;
|
userData: IUserData;
|
||||||
setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>;
|
setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>;
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
const [refreshOngoing, setRefreshOngoing] = useState<boolean>(false)
|
|
||||||
|
|
||||||
function doingRefresh() {
|
|
||||||
setRefreshOngoing(true);
|
|
||||||
refreshUserData().then(()=>setRefreshOngoing(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@@ -44,59 +33,19 @@ export function AccountInfo({
|
|||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{`${userData.firstName} ${userData.lastName}`}</DialogTitle>
|
<DialogTitle>{`Your account - ${userData.firstName} ${userData.lastName}`}</DialogTitle>
|
||||||
<DialogDescription>Your personal account data.</DialogDescription>
|
<DialogDescription>{userData.city}</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className={"flex flex-col items-center justify-center w-full"}>
|
<div className={"flex flex-col items-center justify-center w-full"}>
|
||||||
<div className={"flex flex-col justify-evenly items-center gap-2"}>
|
<div className={"flex flex-row justify-evenly items-center"}>
|
||||||
<div
|
<div className={"flex flex-row gap-1 justify-center items-center mx-auto"}>
|
||||||
className={
|
<Landmark />
|
||||||
"flex flex-col md:flex-row gap-2 justify-center md:justify-evenly items-start md:items-center w-full"
|
<p>{userData.dollarAvailables} $</p>
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"flex gap-1 justify-start md:justify-center items-center mx-auto w-full md:w-fit"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Landmark />
|
|
||||||
<p className={"rounded bg-accent text-accent-foreground p-1"}>
|
|
||||||
{userData.dollarAvailables} $
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"flex gap-1 justify-start md:justify-center items-center mx-auto w-full md:w-fit"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Bitcoin />
|
|
||||||
<p className={"rounded bg-accent text-accent-foreground p-1"}>
|
|
||||||
You dont have cryptos.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={"flex flex-col gap-3 justify-center items-start mx-auto mt-4"}
|
|
||||||
>
|
|
||||||
<div className={"flex flex-row text-nowrap flex-nowrap gap-1 text-primary"}>
|
|
||||||
<Fingerprint />
|
|
||||||
<h2>Your identity</h2>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"font-light text-xs md:text-sm flex flex-row items-center justify-start gap-1 bg-accent p-2 rounded"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<p>{userData.id}</p>
|
|
||||||
<CopyButton value={userData.id} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className={"flex justify-evenly items-center w-full gap-2"}>
|
<DialogFooter>
|
||||||
{!refreshOngoing && <Button variant={"outline"} className={"gap-2 px-2"} onClick={()=>doingRefresh()}>
|
|
||||||
<RefreshCw />
|
|
||||||
</Button>}
|
|
||||||
<Button variant={"secondary"} className={"gap-2 px-2"}>
|
<Button variant={"secondary"} className={"gap-2 px-2"}>
|
||||||
<Wallet />
|
<Wallet />
|
||||||
<p>My wallet</p>
|
<p>My wallet</p>
|
||||||
|
|||||||
112
src/components/ui/copy-button.tsx
Normal file
112
src/components/ui/copy-button.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
"use client";
|
||||||
|
import type { DropdownMenuTriggerProps } from "@radix-ui/react-dropdown-menu";
|
||||||
|
import { CheckIcon, ClipboardIcon } from "lucide-react";
|
||||||
|
|
||||||
|
import { Button, type ButtonProps } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
interface CopyButtonProps extends ButtonProps {
|
||||||
|
value: string;
|
||||||
|
src?: string;
|
||||||
|
event?: Event["NONE"];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Value {
|
||||||
|
data: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function copyToClipboardWithMeta(value: string) {
|
||||||
|
await window?.navigator.clipboard.writeText(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CopyButton({
|
||||||
|
value,
|
||||||
|
className,
|
||||||
|
src,
|
||||||
|
variant = "ghost",
|
||||||
|
event,
|
||||||
|
...props
|
||||||
|
}: CopyButtonProps) {
|
||||||
|
const [hasCopied, setHasCopied] = useState(false);
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setHasCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
}, [hasCopied]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant={variant}
|
||||||
|
className={cn(
|
||||||
|
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:size-3",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
copyToClipboardWithMeta(value).then(() => setHasCopied(true));
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Copy</span>
|
||||||
|
{hasCopied ? <CheckIcon /> : <ClipboardIcon />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CopyMultipleChoiceButtonProps extends DropdownMenuTriggerProps {
|
||||||
|
values: Value[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CopyMultipleChoiceButton({
|
||||||
|
values,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: CopyMultipleChoiceButtonProps) {
|
||||||
|
const [hasCopied, setHasCopied] = useState(false);
|
||||||
|
|
||||||
|
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setHasCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
}, [hasCopied]);
|
||||||
|
|
||||||
|
const copyCommand = useCallback((value: string) => {
|
||||||
|
copyToClipboardWithMeta(value).then(() => setHasCopied(true));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
className={cn(
|
||||||
|
"relative z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{hasCopied ? (
|
||||||
|
<CheckIcon className="h-3 w-3" />
|
||||||
|
) : (
|
||||||
|
<ClipboardIcon className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
<span className="sr-only">Copy</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => copyCommand("npm")}>npm</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export interface IUserWalletCryptos {
|
export interface IUserWalletCryptos {
|
||||||
Crypto?: ICryptoInWalletInfo;
|
Crypto: ICryptoInWalletInfo;
|
||||||
|
amount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICryptoInWalletInfo {
|
export interface ICryptoInWalletInfo {
|
||||||
@@ -12,6 +13,10 @@ export interface ICryptoInWalletInfo {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICryptoInUserWalletInfo extends ICryptoInWalletInfo {
|
||||||
|
owned_amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type IAllTrades = ITrade[];
|
export type IAllTrades = ITrade[];
|
||||||
|
|
||||||
export interface ITrade {
|
export interface ITrade {
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
import {
|
||||||
|
ICryptoInUserWalletInfo,
|
||||||
|
type ICryptoInWalletInfo,
|
||||||
|
IUserWalletCryptos,
|
||||||
|
} from "@/interfaces/crypto.interface";
|
||||||
|
|
||||||
export interface IUserData {
|
export interface IUserData {
|
||||||
id: string;
|
id: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
@@ -11,4 +17,12 @@ export interface IUserData {
|
|||||||
age: number;
|
age: number;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
|
//TODO get on register
|
||||||
|
wallet: IUserWallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUserWallet {
|
||||||
|
uat: number;
|
||||||
|
update_interval: number;
|
||||||
|
owned_cryptos: ICryptoInUserWalletInfo[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,19 +4,18 @@ import type {
|
|||||||
IApiLoginReq,
|
IApiLoginReq,
|
||||||
IApiLoginRes,
|
IApiLoginRes,
|
||||||
IApiRegisterReq,
|
IApiRegisterReq,
|
||||||
IApiRegisterRes, IApiUserAssetsRes,
|
IApiRegisterRes,
|
||||||
} from "@/interfaces/api.interface";
|
} from "@/interfaces/api.interface";
|
||||||
import type {IUserData, IUserWallet} from "@/interfaces/userdata.interface";
|
import type { IUserData } from "@/interfaces/userdata.interface";
|
||||||
import ApiRequest from "@/services/apiRequest";
|
import ApiRequest from "@/services/apiRequest";
|
||||||
import { useEncodedLocalStorage } from "@/services/localStorage";
|
import { useEncodedLocalStorage } from "@/services/localStorage";
|
||||||
import {createContext, type Dispatch, type SetStateAction, useContext, useEffect, useState} from "react";
|
import { createContext, useContext, useState } from "react";
|
||||||
import {type ICryptoInUserWalletInfo, ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
|
|
||||||
|
|
||||||
|
const UserDataContext = createContext<IUserData | null>(null);
|
||||||
const [userData, setUserData] = useEncodedLocalStorage<IUserData | null>(
|
const [userData, setUserData] = useEncodedLocalStorage<IUserData | null>(
|
||||||
"user_data",
|
"user_data",
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const UserDataContext = createContext<readonly [IUserData | null, Dispatch<SetStateAction<IUserData | null>>]>([userData, setUserData]);
|
|
||||||
|
|
||||||
//TODO Run register task
|
//TODO Run register task
|
||||||
export async function doRegister(
|
export async function doRegister(
|
||||||
@@ -32,10 +31,7 @@ export async function doRegister(
|
|||||||
if (ReqRes.data.user) {
|
if (ReqRes.data.user) {
|
||||||
setUserData(ReqRes.data.user);
|
setUserData(ReqRes.data.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
|
||||||
ReqRes.data.message?.forEach((err) => console.warn(err));
|
ReqRes.data.message?.forEach((err) => console.warn(err));
|
||||||
|
|
||||||
return ReqRes.data;
|
return ReqRes.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error during registration:", error);
|
console.error("Error during registration:", error);
|
||||||
@@ -54,8 +50,6 @@ export async function doLogin(loginData: IApiLoginReq) {
|
|||||||
//if (ReqRes.data.user) {
|
//if (ReqRes.data.user) {
|
||||||
// setUserData(ReqRes.data.user)
|
// setUserData(ReqRes.data.user)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// biome-ignore lint/complexity/noForEach: <explanation>
|
|
||||||
ReqRes.data.message?.forEach((err) => console.warn(err));
|
ReqRes.data.message?.forEach((err) => console.warn(err));
|
||||||
return ReqRes.data;
|
return ReqRes.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -77,38 +71,3 @@ export function doDisconnect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO Run update user data
|
//TODO Run update user data
|
||||||
export async function refreshUserData() {
|
|
||||||
if (!userData) return;
|
|
||||||
|
|
||||||
const isTokenAlive =typeof window !== "undefined" ? window.localStorage.getItem("sub") : null;
|
|
||||||
if (!isTokenAlive) return;
|
|
||||||
|
|
||||||
const _reqData = await ApiRequest.authenticated.get.json<IApiUserAssetsRes>("user/my-assets")
|
|
||||||
const old_userData = userData
|
|
||||||
// biome-ignore lint/style/useConst: <explanation>
|
|
||||||
let new_userData: IUserData = old_userData
|
|
||||||
let _owned_cryptos: ICryptoInUserWalletInfo[] = []
|
|
||||||
|
|
||||||
if (_reqData.data.UserHasCrypto) {
|
|
||||||
_owned_cryptos = _reqData.data.UserHasCrypto.map((el): ICryptoInUserWalletInfo =>{
|
|
||||||
return {
|
|
||||||
...el.Crypto,
|
|
||||||
owned_amount: el.amount
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
|
|
||||||
new_userData.wallet = {
|
|
||||||
uat: Date.now(),
|
|
||||||
update_interval: 30_000,
|
|
||||||
owned_cryptos: _owned_cryptos,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Date.now() >= old_userData.wallet.uat + (new_userData.wallet.update_interval / 1_000)) {
|
|
||||||
console.log("New update on userData.")
|
|
||||||
console.trace(new_userData)
|
|
||||||
setUserData(new_userData)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,77 +39,54 @@ export function useLocalStorage<T>(
|
|||||||
}, [key, storedValue]);
|
}, [key, storedValue]);
|
||||||
return [storedValue, setStoredValue];
|
return [storedValue, setStoredValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook that provides an encoded version of a state value and a function to update that value.
|
* Custom hook that provides a way to store and retrieve encoded values in local storage.
|
||||||
*
|
*
|
||||||
* @template T - The type of the state value.
|
* @template T - The type of the value to be stored.
|
||||||
* @param {string} key - The key under which the state value is stored in the local storage.
|
*
|
||||||
* @param {T} initialValue - The initial value of the state.
|
* @param {string} key - The key to be used for storing the encoded value in local storage.
|
||||||
* @return {[T, React.Dispatch<React.SetStateAction<T>>]} - A tuple containing the encoded state value and the function to update it.
|
* @param {T} fallbackValue - The fallback value to be used if no value is found in local storage.
|
||||||
|
*
|
||||||
|
* @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, initialValue: T): readonly [T, React.Dispatch<React.SetStateAction<T>>] {
|
export function useEncodedLocalStorage<T>(
|
||||||
const [encodedState, setEncodedState] = useState<T>(() => getFromLocalStorage(key, initialValue));
|
key: string,
|
||||||
|
fallbackValue: T,
|
||||||
|
): readonly [T, React.Dispatch<React.SetStateAction<T>>] {
|
||||||
|
console.log("Pong !");
|
||||||
|
const [encodedValue, setEncodedValue] = useState<T>(() => {
|
||||||
|
const stored = localStorage?.getItem(key);
|
||||||
|
return stored ? safelyParse(stored, fallbackValue) : fallbackValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
const prevValue = useRef(encodedValue);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
saveToLocalStorage(key, encodedState);
|
console.log({ encodedValue });
|
||||||
}, [encodedState, key]);
|
if (!b64ValEqual(prevValue.current, encodedValue)) {
|
||||||
|
localStorage?.setItem(key, safelyStringify(encodedValue));
|
||||||
return [encodedState, setEncodedState] as const;
|
}
|
||||||
}
|
prevValue.current = encodedValue; // Set ref to current value
|
||||||
|
}, [key, encodedValue]);
|
||||||
/**
|
return [encodedValue, setEncodedValue] as const;
|
||||||
* Retrieves a value from the localStorage with the given key.
|
|
||||||
*
|
function safelyParse(stored: string, fallback: T): T {
|
||||||
* @param {string} key - The key to retrieve the value from.
|
try {
|
||||||
* @param {T} initialValue - The initial value to be returned if the value does not exist in the localStorage.
|
return JSON.parse(atob(stored));
|
||||||
* @returns {T} - The retrieved value from the localStorage or the initial value if it does not exist.
|
} catch {
|
||||||
*/
|
return fallback;
|
||||||
function getFromLocalStorage<T>(key: string, initialValue: T): T {
|
}
|
||||||
try {
|
}
|
||||||
const localStorageValue = localStorage?.getItem(key);
|
|
||||||
if (localStorageValue) {
|
function b64ValEqual<T>(v1: T, v2: T): boolean {
|
||||||
return decodeFromBase64(localStorageValue);
|
return btoa(JSON.stringify(v1)) === btoa(JSON.stringify(v2));
|
||||||
|
}
|
||||||
|
|
||||||
|
function safelyStringify(value: T): string {
|
||||||
|
try {
|
||||||
|
return btoa(JSON.stringify(value));
|
||||||
|
} catch {
|
||||||
|
return btoa(JSON.stringify(fallbackValue));
|
||||||
}
|
}
|
||||||
return initialValue;
|
|
||||||
} catch {
|
|
||||||
return initialValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Save a value to the local storage.
|
|
||||||
*
|
|
||||||
* @param {string} key - The key to store the value.
|
|
||||||
* @param {*} value - The value to be stored.
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
function saveToLocalStorage<T>(key: string, value: T): void {
|
|
||||||
try {
|
|
||||||
const encoded = encodeToBase64(value);
|
|
||||||
localStorage?.setItem(key, encoded);
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a string value from Base64 encoding.
|
|
||||||
*
|
|
||||||
* @param {string} value - The Base64 encoded string to decode.
|
|
||||||
* @template T - The type of the decoded value.
|
|
||||||
* @returns {T} - The decoded value.
|
|
||||||
*/
|
|
||||||
function decodeFromBase64<T>(value: string) {
|
|
||||||
const decoded = atob(value);
|
|
||||||
return JSON.parse(decoded) as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes the given value to Base64.
|
|
||||||
*
|
|
||||||
* @param {T} value - The value to be encoded.
|
|
||||||
* @returns {string} - The Base64 encoded string.
|
|
||||||
*/
|
|
||||||
function encodeToBase64<T>(value: T): string {
|
|
||||||
const serializedState = JSON.stringify(value);
|
|
||||||
return btoa(serializedState);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user