) {
+ ApiRequest.authenticated.post
+ .json("crypto/buy", { id_crypto: props.cryptoData.id, amount: data.amount })
+ .then((res) => {
+ if (res.status !== 201) {
+ toast({
+ title: "An error occurred !",
+ description: (
+
+
+ {JSON.stringify(res.statusText, null, 2)}
+
+
+ ),
+ });
+ return;
+ }
+ toast({
+ title: "Transaction accepted.",
+ description: You will be redirected.
,
+ });
+ setTimeout(() => location.reload(), 1_500);
+ });
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ });
+ }
+ function onBuyFromUserSubmit(data: z.infer) {
+ ApiRequest.authenticated.post
+ .json("trade/create", { id_offer: data.offerId })
+ .then((res) => {
+ if (res.status !== 201) {
+ toast({
+ title: "An error occurred !",
+ description: (
+
+
+ {JSON.stringify(res.statusText, null, 2)}
+
+
+ ),
+ });
+ return;
+ }
+ toast({
+ title: "Transaction accepted.",
+ description: You will be redirected.
,
+ });
+ setTimeout(() => location.reload(), 1_500);
+ });
+ toast({
+ title: "You submitted the following values:",
+ description: (
+
+ {JSON.stringify(data, null, 2)}
+
+ ),
+ });
+ }
+
+ const buyFromUserForm = useForm>({
+ resolver: zodResolver(buyFromUserSchema),
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ Buy from user{" "}
+
+ {offersList.length}
+
+
+ Buy from server
+
+
+
+
+
+
+ {!props.cryptoData.quantity && (
+
+
+
The server dont have stock for the designated cryptos.
+
+ )}
+
+
Available quantity on the server :
+
+ {props.cryptoData.quantity}
+
+
+ {props.cryptoData.quantity && (
+
+
+
+ Buy from the server
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/src/components/cryptos/view-modal.tsx b/src/components/cryptos/view-modal.tsx
new file mode 100644
index 0000000..69e2979
--- /dev/null
+++ b/src/components/cryptos/view-modal.tsx
@@ -0,0 +1,32 @@
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { LineChart } from "lucide-react";
+// @flow
+import * as React from "react";
+type Props = {
+ targetedCryptoId: string;
+};
+export function ViewModal(props: Props) {
+ return (
+
+
+
+
+
+
+
+ Test 1,2 ! //From here
+
+
+ );
+}
diff --git a/src/components/data-tables/cryptos-table.tsx b/src/components/data-tables/cryptos-table.tsx
new file mode 100644
index 0000000..84438b5
--- /dev/null
+++ b/src/components/data-tables/cryptos-table.tsx
@@ -0,0 +1,85 @@
+"use client";
+import type { ICryptoInWalletInfo } from "@/interfaces/crypto.interface";
+import * as React from "react";
+
+import { BuyModal } from "@/components/cryptos/buy-modal";
+import { ViewModal } from "@/components/cryptos/view-modal";
+import { Button } from "@/components/ui/button";
+import { DataTable } from "@/components/ui/data-table";
+import {
+ type ColumnDef,
+ flexRender,
+ getCoreRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+import { ArrowUpDown, MoreHorizontal } from "lucide-react";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+
+interface DataTableProps {
+ columns: ColumnDef[];
+ data: TData[];
+}
+
+type Props = {
+ cryptosArray: ICryptoInWalletInfo[];
+};
+
+export function CryptosTable(props: Props) {
+ const router = useRouter();
+ const cryptos = props.cryptosArray;
+
+ const columns: ColumnDef[] = [
+ {
+ accessorKey: "name",
+ header: "Name",
+ },
+ {
+ accessorKey: "value",
+ header: ({ column }) => {
+ return (
+ column.toggleSorting(column.getIsSorted() === "asc")}
+ >
+ Value - USD
+
+
+ );
+ },
+ },
+ {
+ accessorKey: "quantity",
+ header: ({ column }) => {
+ return (
+ column.toggleSorting(column.getIsSorted() === "asc")}
+ >
+ Available from server
+
+
+ );
+ },
+ },
+ {
+ id: "actions",
+ cell: ({ row }) => {
+ const payment = row.original;
+
+ return (
+
+
+
+
+ );
+ },
+ },
+ ];
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/src/components/data-tables/offers-table.tsx b/src/components/data-tables/offers-table.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/data-tables/trades-table.tsx b/src/components/data-tables/trades-table.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/data-tables/wallet-table.tsx b/src/components/data-tables/wallet-table.tsx
new file mode 100644
index 0000000..931b200
--- /dev/null
+++ b/src/components/data-tables/wallet-table.tsx
@@ -0,0 +1,88 @@
+"use client";
+import type {
+ ICryptoInUserWalletInfo,
+ ICryptoInWalletInfo,
+} from "@/interfaces/crypto.interface";
+import * as React from "react";
+
+import { BuyModal } from "@/components/cryptos/buy-modal";
+import { ViewModal } from "@/components/cryptos/view-modal";
+import { Button } from "@/components/ui/button";
+import { DataTable } from "@/components/ui/data-table";
+import type { IUserWallet } from "@/interfaces/userdata.interface";
+import {
+ type ColumnDef,
+ flexRender,
+ getCoreRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+import { ArrowUpDown, MoreHorizontal } from "lucide-react";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+
+interface DataTableProps {
+ columns: ColumnDef[];
+ data: TData[];
+}
+
+type Props = {
+ walletArray: IUserWallet;
+};
+
+export function WalletTable(props: Props) {
+ const router = useRouter();
+ const wallet = props.walletArray.owned_cryptos;
+
+ const columns: ColumnDef[] = [
+ {
+ accessorKey: "name",
+ header: "Name",
+ },
+ {
+ accessorKey: "value",
+ header: ({ column }) => {
+ return (
+ column.toggleSorting(column.getIsSorted() === "asc")}
+ >
+ Value - USD
+
+
+ );
+ },
+ },
+ {
+ accessorKey: "owned_amount",
+ header: ({ column }) => {
+ return (
+ column.toggleSorting(column.getIsSorted() === "asc")}
+ >
+ Amount owned
+
+
+ );
+ },
+ },
+ {
+ id: "actions",
+ cell: ({ row }) => {
+ const payment = row.original;
+
+ return (
+
+
Soon here : Sell, History
+
+ );
+ },
+ },
+ ];
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/src/components/footer.tsx b/src/components/footer.tsx
new file mode 100644
index 0000000..3b7847a
--- /dev/null
+++ b/src/components/footer.tsx
@@ -0,0 +1,55 @@
+import { Copyright } from "lucide-react";
+import Link from "next/link";
+
+export function Footer() {
+ return (
+
+ );
+}
diff --git a/src/components/header.tsx b/src/components/header.tsx
new file mode 100644
index 0000000..4da4885
--- /dev/null
+++ b/src/components/header.tsx
@@ -0,0 +1,38 @@
+import { AccountDialog } from "@/components/account-dialog";
+import { ThemeBtnSelector } from "@/components/theme-btn-selector";
+import Image from "next/image";
+import type React from "react";
+import Link from "next/link";
+
+export function Header({
+ title,
+ children,
+}: { title?: string; children?: React.ReactNode }) {
+ return (
+
+
+
+
+ {title || "Neptune"}
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/src/components/primary-nav.tsx b/src/components/primary-nav.tsx
new file mode 100644
index 0000000..f195d1f
--- /dev/null
+++ b/src/components/primary-nav.tsx
@@ -0,0 +1,154 @@
+"use client";
+
+import Link from "next/link";
+import * as React from "react";
+
+import {
+ NavigationMenu,
+ NavigationMenuContent,
+ NavigationMenuItem,
+ NavigationMenuLink,
+ NavigationMenuList,
+ NavigationMenuTrigger,
+ navigationMenuTriggerStyle,
+} from "@/components/ui/navigation-menu";
+import { cn } from "@/lib/utils";
+import { Boxes, Info } from "lucide-react";
+import Image from "next/image";
+
+const components: { title: string; href: string; description: string }[] = [
+ {
+ title: "Alert Dialog",
+ href: "/docs/primitives/alert-dialog",
+ description:
+ "A modal dialog that interrupts the user with important content and expects a response.",
+ },
+ {
+ title: "Hover Card",
+ href: "/docs/primitives/hover-card",
+ description: "For sighted users to preview content available behind a link.",
+ },
+ {
+ title: "Progress",
+ href: "/docs/primitives/progress",
+ description:
+ "Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
+ },
+ {
+ title: "Scroll-area",
+ href: "/docs/primitives/scroll-area",
+ description: "Visually or semantically separates content.",
+ },
+ {
+ title: "Tabs",
+ href: "/docs/primitives/tabs",
+ description:
+ "A set of layered sections of content—known as tab panels—that are displayed one at a time.",
+ },
+ {
+ title: "Tooltip",
+ href: "/docs/primitives/tooltip",
+ description:
+ "A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
+ },
+];
+
+export function PrimaryNavigationMenu() {
+ return (
+
+
+
+
+
+ Getting started
+
+
+
+
+
+
+
+
+ Features
+
+
+
+ {components.map((component) => (
+
+ {component.description}
+
+ ))}
+
+
+
+
+
+
+ Documentation
+
+
+
+
+
+ );
+}
+
+const ListItem = React.forwardRef<
+ React.ElementRef<"a">,
+ React.ComponentPropsWithoutRef<"a">
+>(({ className, title, children, ...props }, ref) => {
+ return (
+
+
+
+ {title}
+
+ {children}
+
+
+
+
+ );
+});
+ListItem.displayName = "ListItem";
diff --git a/src/components/providers/providers.tsx b/src/components/providers/providers.tsx
new file mode 100644
index 0000000..593b054
--- /dev/null
+++ b/src/components/providers/providers.tsx
@@ -0,0 +1,14 @@
+"use client";
+import { Footer } from "@/components/footer";
+import { Header } from "@/components/header";
+import { ThemeProvider } from "@/components/providers/theme-provider";
+import { UserDataProvider } from "@/components/providers/userdata-provider";
+import type React from "react";
+
+export function Providers({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/components/providers/theme-provider.tsx b/src/components/providers/theme-provider.tsx
new file mode 100644
index 0000000..2ab4309
--- /dev/null
+++ b/src/components/providers/theme-provider.tsx
@@ -0,0 +1,9 @@
+"use client";
+
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+import type { ThemeProviderProps } from "next-themes/dist/types";
+import * as React from "react";
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children} ;
+}
diff --git a/src/components/providers/userdata-provider.tsx b/src/components/providers/userdata-provider.tsx
new file mode 100644
index 0000000..0a1a84c
--- /dev/null
+++ b/src/components/providers/userdata-provider.tsx
@@ -0,0 +1,25 @@
+import type { IUserData } from "@/interfaces/userdata.interface";
+import { useEncodedLocalStorage } from "@/services/localStorage";
+import React from "react";
+
+export interface IUserDataProvider {
+ userData: IUserData | undefined;
+ setUserData: React.Dispatch>;
+}
+
+export const UserDataContext = React.createContext(
+ undefined,
+);
+
+export const UserDataProvider = ({ children }: { children: React.ReactNode }) => {
+ const [userData, setUserData] = useEncodedLocalStorage(
+ "user_data",
+ undefined,
+ );
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/components/theme-btn-selector.tsx b/src/components/theme-btn-selector.tsx
new file mode 100644
index 0000000..f976ee6
--- /dev/null
+++ b/src/components/theme-btn-selector.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import { MoonStar, Sun } from "lucide-react";
+import { useTheme } from "next-themes";
+import * as React from "react";
+
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+export function ThemeBtnSelector() {
+ const { setTheme } = useTheme();
+
+ return (
+
+
+
+
+
+ Toggle theme
+
+
+
+ setTheme("light")}>Light
+ setTheme("dark")}>Dark
+ setTheme("system")}>System
+
+
+ );
+}
diff --git a/src/interfaces/api.interface.ts b/src/interfaces/api.interface.ts
new file mode 100644
index 0000000..20f4fe6
--- /dev/null
+++ b/src/interfaces/api.interface.ts
@@ -0,0 +1,92 @@
+import {
+ type ICryptoInWalletInfo,
+ ITrade,
+ type IUserWalletCryptos,
+} from "@/interfaces/crypto.interface";
+import type { IUserData } from "@/interfaces/userdata.interface";
+
+// ----- Request -----
+
+export interface IApiRegisterReq {
+ firstName: string;
+ lastName: string;
+ pseudo: string;
+ email: string;
+ password: string;
+}
+
+export interface IApiLoginReq {
+ email: string;
+ password: string;
+}
+
+export interface IApiTradeCreateRq {
+ id_offer: string;
+}
+
+export interface IApiOfferCreateReq {
+ id_crypto: string;
+ amount: number;
+}
+
+export interface IApiCreateReferralCodeReq {
+ name: string;
+ value: number;
+}
+
+export interface IApiDoTradeReq {
+ id_offer: string;
+}
+
+export interface IApiDoOfferReq {
+ id_crypto: string;
+ amount: number;
+}
+
+// ----- Response -----
+
+export interface IAbstractApiResponse {
+ message?: Array | string;
+ error?: string;
+ statusCode?: number;
+}
+
+export interface IApiRegisterRes extends IAbstractApiResponse {
+ access_token?: string;
+ user?: IUserData;
+}
+
+export interface IApiLoginRes extends IAbstractApiResponse {
+ access_token?: string;
+}
+
+export interface IApiUserAssetsRes extends IAbstractApiResponse {
+ firstName?: string;
+ lastName?: string;
+ dollarAvailables?: number;
+ pseudo?: string;
+ UserHasCrypto?: IUserWalletCryptos[];
+}
+
+export interface IApiAllTradesRes extends IAbstractApiResponse {}
+
+export interface IAllRankRes extends IAbstractApiResponse {}
+
+export interface IAllReferralCodeRes extends IAbstractApiResponse {}
+
+export interface ICreateReferralCodeRes extends IAbstractApiResponse {}
+
+export interface IReferralCodeUpdateRes extends IAbstractApiResponse {}
+
+export interface IReferralCodeDeleteRes extends IAbstractApiResponse {}
+
+export interface IApiAllOffersRes extends IAbstractApiResponse {
+ id: string;
+ User: {
+ pseudo: string;
+ };
+ amount: number;
+ created_at: string;
+ id_user: string;
+ Crypto: ICryptoInWalletInfo;
+}
diff --git a/src/interfaces/crypto.interface.ts b/src/interfaces/crypto.interface.ts
new file mode 100644
index 0000000..1d658f3
--- /dev/null
+++ b/src/interfaces/crypto.interface.ts
@@ -0,0 +1,50 @@
+export interface IUserWalletCryptos {
+ Crypto: ICryptoInWalletInfo;
+ amount: number;
+}
+
+export interface ICryptoInWalletInfo {
+ id: string;
+ name: string;
+ value: number;
+ image: string;
+ quantity: number;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface ICryptoInUserWalletInfo extends ICryptoInWalletInfo {
+ owned_amount: number;
+}
+
+export type IAllTrades = ITrade[];
+
+export interface ITrade {
+ Giver: ISellerIdentity;
+ Receiver: IBuyerIdentity;
+ Crypto: ITradedCrypto;
+}
+
+export interface ISellerIdentity {
+ firstName: string;
+ lastName: string;
+ pseudo: string;
+ dollarAvailables: number;
+}
+
+export interface IBuyerIdentity {
+ firstName: string;
+ lastName: string;
+ pseudo: string;
+ dollarAvailables: number;
+}
+
+export interface ITradedCrypto {
+ id: string;
+ name: string;
+ value: number;
+ image: string;
+ quantity: number;
+ created_at: string;
+ updated_at: string;
+}
diff --git a/src/interfaces/general.interface.ts b/src/interfaces/general.interface.ts
new file mode 100644
index 0000000..461a4e1
--- /dev/null
+++ b/src/interfaces/general.interface.ts
@@ -0,0 +1,13 @@
+export interface IStandardisedReturn {
+ state: EReturnState;
+ message?: string;
+ resolved?: T;
+}
+
+export enum EReturnState {
+ unauthorized = 0,
+ clientError = 1,
+ serverError = 2,
+ done = 3,
+ queued = 4,
+}
diff --git a/src/interfaces/userdata.interface.ts b/src/interfaces/userdata.interface.ts
new file mode 100644
index 0000000..1575221
--- /dev/null
+++ b/src/interfaces/userdata.interface.ts
@@ -0,0 +1,26 @@
+import {
+ type ICryptoInUserWalletInfo,
+ type ICryptoInWalletInfo,
+ IUserWalletCryptos,
+} from "@/interfaces/crypto.interface";
+
+export interface IUserData {
+ id: string;
+ firstName: string;
+ lastName: string;
+ pseudo: string;
+ email: string;
+ roleId: string;
+ isActive: boolean;
+ dollarAvailables: number;
+ created_at: string;
+ updated_at: string;
+ //TODO get on register
+ wallet: IUserWallet;
+}
+
+export interface IUserWallet {
+ uat: number;
+ update_interval: number;
+ owned_cryptos: ICryptoInUserWalletInfo[];
+}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..ac680b3
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/src/services/account.handler.ts b/src/services/account.handler.ts
new file mode 100644
index 0000000..d1f7337
--- /dev/null
+++ b/src/services/account.handler.ts
@@ -0,0 +1,54 @@
+"use client";
+
+import type {
+ IAbstractApiResponse,
+ IAllReferralCodeRes,
+ IApiAllTradesRes,
+ IApiDoTradeReq,
+ IApiUserAssetsRes,
+ ICreateReferralCodeRes,
+} from "@/interfaces/api.interface";
+import { ICryptoInWalletInfo, IUserWalletCryptos } from "@/interfaces/crypto.interface";
+import { EReturnState, type IStandardisedReturn } from "@/interfaces/general.interface";
+import type { IUserData, IUserWallet } from "@/interfaces/userdata.interface";
+import ApiRequest from "@/services/apiRequest";
+import { AxiosResponse } from "axios";
+import type { Dispatch, SetStateAction } from "react";
+
+//TODO Run disconnect task
+export function doDisconnect() {
+ if (typeof window !== "undefined") {
+ window.localStorage.removeItem("sub");
+ //Redirect to homepage
+ window.location.href = "/";
+ return true;
+ }
+ console.log(
+ "Whut ? Why trying to remove an item from the localStorage when running in SSR ?",
+ );
+ return false;
+}
+
+export async function getWallet(): Promise> {
+ try {
+ const ReqRes =
+ await ApiRequest.authenticated.get.json>(
+ "user/my-assets",
+ );
+ console.log(ReqRes.data);
+
+ if (ReqRes.status !== 200) {
+ return {
+ state: EReturnState.clientError,
+ };
+ }
+ return {
+ state: EReturnState.done,
+ resolved: ReqRes.data,
+ };
+ } catch (err) {
+ return {
+ state: EReturnState.serverError,
+ };
+ }
+}
diff --git a/src/services/apiRequest.ts b/src/services/apiRequest.ts
new file mode 100644
index 0000000..c4a4699
--- /dev/null
+++ b/src/services/apiRequest.ts
@@ -0,0 +1,83 @@
+"use client";
+
+import axios, { type AxiosResponse } from "axios";
+
+const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3333/";
+
+const AxiosConfigs = {
+ authenticated: {
+ json: () => {
+ return {
+ headers: {
+ "content-type": "application/json",
+ Authorization: `Bearer ${typeof window !== "undefined" ? JSON.parse(window.localStorage.getItem("sub") || "not-ssr") : "not-ssr"}`,
+ },
+ validateStatus: (status: number) => {
+ return status < 500; // Resolve only if the status code is less than 500
+ },
+ };
+ },
+ },
+ standard: {
+ json: () => {
+ return {
+ headers: {
+ "content-type": "application/json",
+ },
+ validateStatus: (status: number) => {
+ return status < 500; // Resolve only if the status code is less than 500
+ },
+ };
+ },
+ },
+};
+
+async function doAuthenticatedJsonPostReq(
+ route: string,
+ body: ReqT,
+): Promise> {
+ return await axios.post(baseUrl + route, body, AxiosConfigs.authenticated.json());
+}
+
+async function doAuthenticatedGetReq(route: string): Promise> {
+ return await axios.get(baseUrl + route, AxiosConfigs.authenticated.json());
+}
+
+async function doAuthenticatedPatchReq(
+ route: string,
+ body: ReqT,
+): Promise> {
+ return await axios.patch(baseUrl + route, body, AxiosConfigs.authenticated.json());
+}
+
+async function doAuthenticatedDelReq(route: string): Promise> {
+ return await axios.delete(baseUrl + route, AxiosConfigs.authenticated.json());
+}
+
+//TODO form/multipart req
+
+async function doJsonPostReq(
+ route: string,
+ body: ReqT,
+): Promise> {
+ return await axios.post(baseUrl + route, body, AxiosConfigs.standard.json());
+}
+
+async function doJsonGetReq(route: string): Promise> {
+ return await axios.get(baseUrl + route, AxiosConfigs.standard.json());
+}
+
+const ApiRequest = {
+ authenticated: {
+ post: { json: doAuthenticatedJsonPostReq },
+ patch: { json: doAuthenticatedPatchReq },
+ delete: { json: doAuthenticatedDelReq },
+ get: { json: doAuthenticatedGetReq },
+ },
+ standard: {
+ post: { json: doJsonPostReq },
+ get: { json: doJsonGetReq },
+ },
+};
+
+export default ApiRequest;
diff --git a/src/services/exchange.handler.ts b/src/services/exchange.handler.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/services/localStorage.ts b/src/services/localStorage.ts
new file mode 100644
index 0000000..0b6658b
--- /dev/null
+++ b/src/services/localStorage.ts
@@ -0,0 +1,92 @@
+"use client";
+
+import type React from "react";
+import { useEffect, useRef, useState } from "react";
+
+const localStorage = typeof window !== "undefined" ? window.localStorage : null;
+
+/**
+ * A custom React hook that allows you to store and retrieve data in the browser's localStorage.
+ *
+ * @param {string} key - The key under which the data should be stored in localStorage.
+ * @param {T} initial - The initial value for the stored data.
+ * @returns {[T, React.Dispatch>]} - An array containing the stored value and a function to update the stored value.
+ */
+export function useLocalStorage(
+ key: string,
+ initial: T,
+): [T, React.Dispatch>] {
+ const readValue = () => {
+ const item = localStorage?.getItem(key);
+ if (item) {
+ try {
+ return JSON.parse(item);
+ } catch (error) {
+ console.warn(`Error reading localStorage key “${key}”:`, error);
+ }
+ }
+ return initial;
+ };
+
+ const [storedValue, setStoredValue] = useState(readValue);
+
+ useEffect(() => {
+ try {
+ localStorage?.setItem(key, JSON.stringify(storedValue));
+ } catch (error) {
+ console.warn(`Error setting localStorage key “${key}”:`, error);
+ }
+ }, [key, storedValue]);
+ return [storedValue, setStoredValue];
+}
+/**
+ * Custom hook that provides a way to store and retrieve encoded values in local storage.
+ *
+ * @template T - The type of the value to be stored.
+ *
+ * @param {string} key - The key to be used for storing the encoded value in local storage.
+ * @param {T} fallbackValue - The fallback value to be used if no value is found in local storage.
+ *
+ * @return {readonly [T, React.Dispatch>]} - An array containing the encoded value and a function to update the encoded value.
+ */
+export function useEncodedLocalStorage(
+ key: string,
+ fallbackValue: T,
+): readonly [T, React.Dispatch>] {
+ console.log("Pong !");
+ const [encodedValue, setEncodedValue] = useState(() => {
+ const stored = localStorage?.getItem(key);
+ return stored ? safelyParse(stored, fallbackValue) : fallbackValue;
+ });
+
+ const prevValue = useRef(encodedValue);
+
+ useEffect(() => {
+ console.log({ encodedValue });
+ if (!b64ValEqual(prevValue.current, encodedValue)) {
+ localStorage?.setItem(key, safelyStringify(encodedValue));
+ }
+ prevValue.current = encodedValue; // Set ref to current value
+ }, [key, encodedValue]);
+ return [encodedValue, setEncodedValue] as const;
+
+ function safelyParse(stored: string, fallback: T): T {
+ try {
+ return JSON.parse(atob(stored));
+ } catch {
+ return fallback;
+ }
+ }
+
+ function b64ValEqual(v1: T, v2: T): boolean {
+ 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));
+ }
+ }
+}
diff --git a/tailwind.config.ts b/tailwind.config.ts
new file mode 100644
index 0000000..5bd0d14
--- /dev/null
+++ b/tailwind.config.ts
@@ -0,0 +1,95 @@
+import type { Config } from "tailwindcss"
+
+// @ts-ignore
+import {default as flattenColorPalette} from "tailwindcss/lib/util/flattenColorPalette";
+
+const config = {
+ darkMode: ["class"],
+ content: [
+ './pages/**/*.{ts,tsx}',
+ './components/**/*.{ts,tsx}',
+ './app/**/*.{ts,tsx}',
+ './src/**/*.{ts,tsx}',
+ ],
+ prefix: "",
+ theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
+ extend: {
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ keyframes: {
+ "accordion-down": {
+ from: { height: "0" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: "0" },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
+ },
+ },
+ },
+ plugins: [require("tailwindcss-animate"), addVariablesForColors],
+} satisfies Config
+
+// This plugin adds each Tailwind color as a global CSS variable, e.g. var(--gray-200).
+function addVariablesForColors({ addBase, theme }: any) {
+ let allColors = flattenColorPalette(theme("colors"));
+ let newVars = Object.fromEntries(
+ Object.entries(allColors).map(([key, val]) => [`--${key}`, val])
+ );
+
+ addBase({
+ ":root": newVars,
+ });
+}
+
+export default config
\ No newline at end of file
diff --git a/temp.ts b/temp.ts
new file mode 100644
index 0000000..38531f2
--- /dev/null
+++ b/temp.ts
@@ -0,0 +1,400 @@
+////////apiTypes.ts
+
+
+
+export interface ResponseSuccess {
+
+ data: any
+
+ status: number
+
+ statusText: string
+
+}
+
+
+
+export interface ResponseFailed {
+
+ code: string
+
+ message: string
+
+ name: string
+
+ response: {
+
+ data: {
+
+ error: string
+
+ message: string
+
+ statusCode: number
+
+ }
+
+ status: number
+
+ statusText: string
+
+ }
+
+}
+
+
+
+
+
+///////////////cryptoTypes.ts
+
+
+
+export enum RoleName {
+
+ user = 'user',
+
+ admin = 'admin',
+
+}
+
+
+
+export type Role = {
+
+ id: string
+
+ name: RoleName
+
+ created_at?: string
+
+ updated_at?: string
+
+}
+
+
+
+export type PromoCode = {
+
+ id: string
+
+ name: string
+
+ value: number
+
+}
+
+
+
+export type CryptoHistory = {
+
+ id: string
+
+ id_crypto: string
+
+ value: number
+
+ created_at: string
+
+ updated_at: string
+
+}
+
+
+
+export interface Offer {
+
+ id: string
+
+ User: {
+
+ pseudo: string
+
+ }
+
+ amount: number
+
+ created_at: string
+
+ id_user: string
+
+ Crypto: CryptoData
+
+}
+
+
+
+export interface UserAssets {
+
+ firstName: string
+
+ lastName: string
+
+ dollarAvailables: number
+
+ pseudo: string
+
+ age: number
+
+ UserHasCrypto: CryptoData[]
+
+}
+
+
+
+export interface Signin {
+
+ access_token: string
+
+ user: UserExtended
+
+ Role: Role
+
+}
+
+
+
+export interface CryptoData {
+
+ id: string
+
+ name: string
+
+ value: number
+
+ image: string
+
+ quantity: number
+
+ created_at: string
+
+ updated_at: string
+
+}
+
+
+
+export interface MyCryptoData {
+
+ Crypto: CryptoData
+
+ amount: number
+
+ id: string
+
+}
+
+
+
+export interface Trade {
+
+ Giver: User
+
+ Receiver: User
+
+ Crypto: CryptoData
+
+ id: string
+
+}
+
+
+
+export interface User {
+
+ firstName: string
+
+ lastName: string
+
+ pseudo: string
+
+ dollarAvailables: number
+
+}
+
+export interface UserHasCrypto {
+
+ id: string
+
+ id_user: string
+
+ id_crypto: string
+
+ amount: number
+
+ createdAt: string
+
+ updated_at: string
+
+ Crypto: CryptoData
+
+}
+
+export interface UserExtended extends User {
+
+ id: string
+
+ hash: string
+
+ email: string
+
+ roleId: string
+
+ isActive: boolean
+
+ city: string
+
+ age: number
+
+ created_at: string
+
+ updated_at: string
+
+ UserHasCrypto?: UserHasCrypto
+
+}
+
+
+
+export interface MyTrade {
+
+ id: string
+
+ id_giver: string
+
+ id_receiver: string
+
+ id_crypto: string
+
+ amount_traded: number
+
+ created_at: string
+
+ updated_at: string
+
+ Crypto: CryptoData
+
+ Giver: UserExtended
+
+ Receiver: UserExtended
+
+}
+
+
+
+export interface AuthData {
+
+ access_token: string
+
+ user: {
+
+ id: string
+
+ firstName: string
+
+ lastName: string
+
+ pseudo: string
+
+ hash: null | any
+
+ email: string
+
+ roleId: string
+
+ isActive: boolean
+
+ city: string
+
+ dollarAvailables: number
+
+ age: number
+
+ created_at: string
+
+ updated_at: string
+
+ UserHasCrypto: UserHasCrypto[]
+
+ Role: Role
+
+ }
+
+}
+
+
+
+
+
+////////////formTypes.ts
+
+
+
+export type RegisterInput = {
+
+ firstName: string
+
+ lastName: string
+
+ pseudo: string
+
+ city: string
+
+ email: string
+
+ password: string
+
+ confirmPassword: string
+
+ promoCode: string
+
+ age: number
+
+}
+
+
+
+export type LoginInput = {
+
+ email: string
+
+ password: string
+
+}
+
+
+
+export type RoleInput = {
+
+ name: string
+
+}
+
+
+
+export type PromoCodeInput = {
+
+ name: string
+
+ value: number
+
+}
+
+
+
+export type TradeInput = {
+
+ id_offer: string
+
+}
+
+
+
+export type OfferInput = {
+
+ id_crypto: string
+
+ amount: number
+
+}
+
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..7b28589
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}