Compare commits

...

16 Commits

Author SHA1 Message Date
5164255aea
feat(ide): Add Project Default inspection profile
This commit includes a new inspection profile named 'Project Default'. This profile is configured to detect duplicated code in TypeScript with a weak warning level. It is enabled by default.
2024-06-20 15:09:41 +02:00
53be13ef38
feat(components): Add buy-modal component for purchasing crypto
This commit introduces a new component, `buy-modal.tsx`, within the cryptos scope of components. This component allows the option to buy cryptocurrency either from a user or directly from the server. It provides form validation, API integrations, handles states, and displays proper response messages.
2024-06-20 15:09:14 +02:00
49d485c11a
feat(components): Add buy-modal component for purchasing crypto
This commit introduces a new component, `buy-modal.tsx`, within the cryptos scope of components. This component allows the option to buy cryptocurrency either from a user or directly from the server. It provides form validation, API integrations, handles states, and displays proper response messages.
2024-06-20 15:07:15 +02:00
37b7116a69
feat(cryptos): Add ViewModal component
A new ViewModal component was added to the cryptos directory. This React component includes Dialog properties and widgets from ui components such as a Button, Label, and Input. Modifications also include a LineChart from lucide-react library.
2024-06-20 15:05:57 +02:00
2452d8d607
feat: Add new file
This commit introduces a new file to the codebase. No pre-existing files or functionalities have been modified or removed as part of this change.
2024-06-20 15:03:27 +02:00
b188e8573d
feat(component): add DataTable component
This commit introduces a new DataTable component in the UI component library. It includes features like column sorting, pagination, and customizable filtering. This reusable component is designed to handle and display data tables across different parts of the application.
2024-06-20 15:02:44 +02:00
fb4b2bb7c1
feat(components): add CryptosTable and WalletTable data-tables components
Introduce CryptosTable and WalletTable components. CryptosTable is responsible for displaying cryptocurrency information available for purchase, while WalletTable displays the user's current cryptocurrency holdings. Both components utilize react-table for table generation and sorting functionality. Additional "Buy" and "View" modals have been configured for CryptosTable.
2024-06-20 15:00:22 +02:00
f268294b93
feat: Add new dashboard, admin, and wallet pages
Implement new page.tsx files for dashboard, admin, and wallet modules, providing essential UI logic and data-fetching mechanisms. These components handle their respective data retrieval and loading states, and present the information via relevant UI structures such as 'CryptosTable' and 'WalletTable'.
2024-06-20 14:59:28 +02:00
90dd1d0828
feat: add @tanstack/react-table dependency to package
This commit includes the addition of @tanstack/react-table package as a project dependency. Changes were made in both package.json and pnpm-lock.yaml files to reflect this addition. This new package contributes enhanced table functionalities to the project.
2024-06-20 14:58:30 +02:00
2c9fafd802
feat(layout): add Wallet and Dashboard buttons
Imported Link and Button components into layout.tsx, which were used to add Wallet and Dashboard navigational buttons to the header section. The buttons have been set with a 'light' variant and flex features for responsive design.
2024-06-20 14:57:03 +02:00
4e0de3be06
feat(apiRequest): use JSON.parse for accessToken in Authorization header
The Authorization header in the apiRequest now utilizes JSON.parse for the accessToken. This change ensures proper parsing and retrieval of the item from the local storage when the window object is defined.
2024-06-20 14:56:35 +02:00
a2f2996ef9
feat(apiRequest): use JSON.parse for accessToken in Authorization header
The Authorization header in the apiRequest now utilizes JSON.parse for the accessToken. This change ensures proper parsing and retrieval of the item from the local storage when the window object is defined.
2024-06-20 14:55:23 +02:00
0a239f4478
feat(interfaces): Add new interfaces
New interfaces related to referral code and trade request are added to the `api.interface.ts`. Additionally, import statement modified to include `ICryptoInWalletInfo` into the module. This enriches the module with a set of necessary interfaces providing better control over referral codes, trades, and offers operations.
2024-06-20 14:52:07 +02:00
69484cc90f
feat(component): add wallet loading state and user wallet information
The account-info component has been updated to include a loading state for user wallets, a feature that significantly improves the performance of the component by preventing unnecessary re-renders. The IUserWallet type now includes new fields to store wallet data such as cryptocurrencies' details and owned amount. These changes also include display adjustments and improved routing for 'My Wallet' button.
2024-06-20 14:45:12 +02:00
3819a0f338
feat(handler): Update imports and remove excess functions
Updated the import statement within account.handler.ts to feature new, relevant components. Also, this commit includes a significant clean-up of the account.handler service, where a large amount of superfluous function calls were removed to streamline the code.
2024-06-20 14:44:31 +02:00
977b13d46a
feat(button component): Add 'light' style variation to button
This commit introduces a new 'light' style variation for the button component. Now a button can be styled with 'light' variation which changes the button's background and text color on hover state.
2024-06-19 22:26:17 +02:00
20 changed files with 746 additions and 167 deletions

View File

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="86" name="TypeScript" />
</Languages>
</inspection_tool>
</profile>
</component>

View File

@ -39,6 +39,7 @@
"@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-table": "^8.17.3",
"axios": "^1.7.2", "axios": "^1.7.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",

22
pnpm-lock.yaml generated
View File

@ -95,6 +95,9 @@ importers:
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.0.7 specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/react-table':
specifier: ^8.17.3
version: 8.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
axios: axios:
specifier: ^1.7.2 specifier: ^1.7.2
version: 1.7.2 version: 1.7.2
@ -1238,6 +1241,17 @@ packages:
'@swc/helpers@0.5.5': '@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
'@tanstack/react-table@8.17.3':
resolution: {integrity: sha512-5gwg5SvPD3lNAXPuJJz1fOCEZYk9/GeBFH3w/hCgnfyszOIzwkwgp5I7Q4MJtn0WECp84b5STQUDdmvGi8m3nA==}
engines: {node: '>=12'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
'@tanstack/table-core@8.17.3':
resolution: {integrity: sha512-mPBodDGVL+fl6d90wUREepHa/7lhsghg2A3vFpakEhrhtbIlgNAZiMr7ccTgak5qbHqF14Fwy+W1yFWQt+WmYQ==}
engines: {node: '>=12'}
'@types/babel__core@7.20.5': '@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@ -3879,6 +3893,14 @@ snapshots:
'@swc/counter': 0.1.3 '@swc/counter': 0.1.3
tslib: 2.6.3 tslib: 2.6.3
'@tanstack/react-table@8.17.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@tanstack/table-core': 8.17.3
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@tanstack/table-core@8.17.3': {}
'@types/babel__core@7.20.5': '@types/babel__core@7.20.5':
dependencies: dependencies:
'@babel/parser': 7.24.7 '@babel/parser': 7.24.7

View File

@ -0,0 +1,7 @@
export default function AdminPage() {
return (<>
<section>
<h1>Welcome to the Dashboard</h1>
</section>
</>)
}

View File

@ -0,0 +1,44 @@
"use client"
import type {ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {CryptosTable} from "@/components/data-tables/cryptos-table";
import {useEffect, useState} from "react";
import {Skeleton} from "@/components/ui/skeleton";
import ApiRequest from "@/services/apiRequest";
import type {IApiAllOffersRes} from "@/interfaces/api.interface";
export default function DashboardPage() {
const [isLoading, setIsLoading] = useState<boolean>(true)
const [cryptosList, setCryptosList] = useState<ICryptoInWalletInfo[]>([])
//FIX the loop
useEffect(() => {
ApiRequest.authenticated.get.json<ICryptoInWalletInfo[]>(
"crypto/all"
).then((response)=>{
if (response.data.length <= 0) {
setCryptosList([])
setIsLoading(false)
return
}
const resp = response.data
setCryptosList(resp)
setIsLoading(false)
})
}, []);
if (isLoading) {
return (<div className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}>
<h1 className={"text-2xl font-bold"}>Available cryptos</h1>
<Skeleton className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}/>
</div>)
}
return (<>
<section className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}>
<h1 className={"text-2xl font-bold"}>Available cryptos</h1>
<CryptosTable cryptosArray={cryptosList}/>
</section>
</>)
}

View File

@ -8,6 +8,8 @@ import { Providers } from "@/components/providers/providers";
import { ThemeProvider } from "@/components/providers/theme-provider"; import { ThemeProvider } from "@/components/providers/theme-provider";
import { Toaster } from "@/components/ui/toaster"; import { Toaster } from "@/components/ui/toaster";
import type React from "react"; import type React from "react";
import Link from "next/link";
import {Button} from "@/components/ui/button";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "YeloBit", title: "YeloBit",
@ -28,7 +30,10 @@ export default function RootLayout({
<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"}>
<Providers> <Providers>
<Header> <Header>
<PrimaryNavigationMenu /> <div className={"flex flex-row flex-wrap md:flex-nowrap gap-2"}>
<Button asChild variant={'light'}><Link href={'/wallet'}>Wallet</Link></Button>
<Button asChild variant={'light'}><Link href={'/dashboard'}>Dashboard</Link></Button>
</div>
</Header> </Header>
{children} {children}
<Toaster /> <Toaster />

40
src/app/wallet/page.tsx Normal file
View File

@ -0,0 +1,40 @@
"use client"
import type {ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {CryptosTable} from "@/components/data-tables/cryptos-table";
import {useContext, useEffect, useState} from "react";
import {Skeleton} from "@/components/ui/skeleton";
import ApiRequest from "@/services/apiRequest";
import type {IApiAllOffersRes} from "@/interfaces/api.interface";
import {WalletTable} from "@/components/data-tables/wallet-table";
import {UserDataContext} from "@/components/providers/userdata-provider";
import {IUserWallet} from "@/interfaces/userdata.interface";
export default function WalletPage() {
const [isLoading, setIsLoading] = useState<boolean>(true)
const [cryptosList, setCryptosList] = useState<ICryptoInWalletInfo[]>([])
const userContext = useContext(UserDataContext);
//FIX the loop
useEffect(() => {
console.log(userContext?.userData)
if (userContext?.userData) {
setIsLoading(false)
}
}, [userContext]);
if (isLoading || !userContext?.userData) {
return (<div className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}>
<h1 className={"text-2xl font-bold"}>Cryptos in your wallet</h1>
<Skeleton className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}/>
</div>)
}
return (<>
<section className={"container flex flex-col justify-center items-center min-h-64 my-6 gap-4"}>
<h1 className={"text-2xl font-bold"}>Cryptos in your wallet</h1>
<WalletTable walletArray={userContext.userData.wallet as unknown as IUserWallet}/>
</section>
</>)
}

View File

@ -11,13 +11,15 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input"; 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, IUserWallet} from "@/interfaces/userdata.interface";
import { CopyButton } from "@/components/ui/copy-button"; import { CopyButton } from "@/components/ui/copy-button";
import { doDisconnect, getWallet } from "@/services/account.handler"; import { doDisconnect, getWallet } from "@/services/account.handler";
import { Bitcoin, Fingerprint, Key, Landmark, Unplug, User, Wallet } from "lucide-react"; import { Bitcoin, Fingerprint, Key, Landmark, Unplug, User, Wallet } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import type React from "react"; import type React from "react";
import {useEffect, useState} from "react";
import {type ICryptoInUserWalletInfo, ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
export function AccountInfo({ export function AccountInfo({
userData, userData,
@ -28,9 +30,41 @@ export function AccountInfo({
setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>; setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>;
isDisconnected: boolean; isDisconnected: boolean;
}) { }) {
getWallet().then(() => { const [isLoaded,setIsLoaded] = useState(false)
console.log("pong !");
}); useEffect(() => {
if (!isLoaded) {
getWallet().then((res) => {
const wallet: IUserWallet = {
uat: Date.now(),
update_interval: 30_000,
owned_cryptos: res.resolved?.UserHasCrypto?.map((el): ICryptoInUserWalletInfo => {
return {
id: el.Crypto.id,
name: el.Crypto.name,
value: el.Crypto.value,
image: el.Crypto.image,
quantity: el.Crypto.quantity,
owned_amount: el.amount,
created_at: el.Crypto.created_at,
updated_at: el.Crypto.updated_at
}
}) || []
}
delete res.resolved?.UserHasCrypto
//@ts-ignore
setUserData({
...userData,
...res.resolved,
wallet: wallet
})
console.log(userData)
setIsLoaded(true)
});
}
}, [isLoaded]);
if (isDisconnected) { if (isDisconnected) {
return ( return (
<div className={"flex flex-col justify-center items-center h-10 p-2 text-xs mt-2"}> <div className={"flex flex-col justify-center items-center h-10 p-2 text-xs mt-2"}>
@ -64,7 +98,7 @@ export function AccountInfo({
<User /> <User />
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="sm:max-w-[425px] md:max-w-[720px]">
<DialogHeader> <DialogHeader>
<DialogTitle>{`Your account - ${userData.firstName} ${userData.lastName}`}</DialogTitle> <DialogTitle>{`Your account - ${userData.firstName} ${userData.lastName}`}</DialogTitle>
<DialogDescription>{userData.city}</DialogDescription> <DialogDescription>{userData.city}</DialogDescription>
@ -93,7 +127,7 @@ export function AccountInfo({
> >
<Bitcoin /> <Bitcoin />
<p className={"rounded bg-accent text-accent-foreground p-1"}> <p className={"rounded bg-accent text-accent-foreground p-1"}>
You dont have cryptos. {`You currently have ${userData.wallet.owned_cryptos.length} crypto(s)`}
</p> </p>
</div> </div>
</div> </div>
@ -116,13 +150,15 @@ export function AccountInfo({
</div> </div>
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant={"secondary"} className={"gap-2 px-2"}> <Button variant={"secondary"} className={"gap-2 px-2"} asChild>
<Wallet /> <Link href={'/wallet'}>
<p>My wallet</p> <Wallet />
<p>My wallet</p>
</Link>
</Button> </Button>
<Button <Button
variant={"destructive"} variant={"destructive"}
className={"gap-2 px-2"} className={"gap-2 px-2 mb-2"}
onClick={() => doDisconnect()} onClick={() => doDisconnect()}
> >
<Unplug /> <Unplug />

View File

@ -88,7 +88,8 @@ export function AuthForms() {
owned_cryptos: [], owned_cryptos: [],
}, },
}); });
setSub(ReqRes.data.access_token?.toString());
setSub(ReqRes.data.access_token);
} }
console.debug(ReqRes.data.message || "Not additional message from request"); console.debug(ReqRes.data.message || "Not additional message from request");
return { return {

View File

@ -0,0 +1,193 @@
// @flow
import * as React from 'react';
import {
Dialog,
DialogContent,
DialogTrigger,
} from "@/components/ui/dialog";
import {Button} from "@/components/ui/button";
import {Ban, DollarSign, RefreshCw} from "lucide-react";
import * as z from "zod";
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
import {Dispatch, SetStateAction, useEffect, useState} from "react";
import type {ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {toast} from "@/components/ui/use-toast";
import AutoForm, {AutoFormSubmit} from "@/components/auto-form";
import type {IApiAllOffersRes, IApiDoTradeReq} from "@/interfaces/api.interface";
import ApiRequest from "@/services/apiRequest";
import {Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form";
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select";
import Link from "next/link";
import {useForm} from "react-hook-form";
import {zodResolver} from "@hookform/resolvers/zod";
type Props = {
cryptoData: ICryptoInWalletInfo
};
export function BuyModal(props: Props) {
const [isLoading, setIsLoading] = useState<boolean>(false)
const [offersList, setOffersList] = useState<IApiAllOffersRes[]>([])
const [isLoaded,setIsLoaded] = useState(false)
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
if(!isLoaded) {
ApiRequest.authenticated.get.json<IApiAllOffersRes[]>(`offer/crypto/${props.cryptoData.id}`).then((response) => {
setOffersList(response.data);
console.log(`Crypto ${props.cryptoData.name} -> ${response.data.length}`)
setIsLoaded(true);
return;
})
}
}, [isLoaded]);
const buyFromServerSchema = z.object({
amount: z
.number({
required_error: "An amount is needed.",
})
.min(1)
.max(props.cryptoData.quantity, "You cant buy more that what is available on the server.")
.describe("The amount you want to buy."),
});
const buyFromUserSchema = z.object({
offerId: z
.string({
required_error: "You should select an offer.",
})
.uuid(),
})
function onBuyFromServerSubmit(data: z.infer<typeof buyFromServerSchema>) {
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: (<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white text-wrap">{JSON.stringify(res.statusText, null, 2)}</code>
</pre>)
})
return
}
toast({
title: "Transaction accepted.",
description: (
<p>You will be redirected.</p>
)
})
setTimeout(()=>location.reload(), 1_500)
})
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
function onBuyFromUserSubmit(data: z.infer<typeof buyFromUserSchema>) {
ApiRequest.authenticated.post.json("trade/create", {id_offer: data.offerId}).then((res)=>{
if (res.status !== 201) {
toast({
title: "An error occurred !",
description: (<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white text-wrap">{JSON.stringify(res.statusText, null, 2)}</code>
</pre>)
})
return
}
toast({
title: "Transaction accepted.",
description: (
<p>You will be redirected.</p>
)
})
setTimeout(()=>location.reload(), 1_500)
})
toast({
title: "You submitted the following values:",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
}
const buyFromUserForm = useForm<z.infer<typeof buyFromUserSchema>>({
resolver: zodResolver(buyFromUserSchema),
})
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="light"><DollarSign /></Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[800px] flex flex-col justify-start items-center">
<Tabs defaultValue="server" className="w-full p-2 my-4">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="user">Buy from user <p className={" ml-1 px-1 bg-primary text-primary-foreground rounded"}>{offersList.length}</p></TabsTrigger>
<TabsTrigger value="server">Buy from server</TabsTrigger>
</TabsList>
<TabsContent value="user" className={"flex flex-col justify-start items-center"}>
<Form {...buyFromUserForm}>
<form onSubmit={buyFromUserForm.handleSubmit(onBuyFromUserSubmit)} className="w-full space-y-6">
<FormField
control={buyFromUserForm.control}
name="offerId"
render={({ field }) => (
<FormItem>
<FormLabel>Select an offer</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select an offer to purchase." />
</SelectTrigger>
</FormControl>
<SelectContent>
{offersList.map((offer)=>{
return (
<SelectItem value={offer.id} key={offer.id}>{`${offer.amount}x ${offer.Crypto.name} - ${offer.User.pseudo}`}</SelectItem>
)
})}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
</TabsContent>
<TabsContent value="server" className={"flex flex-col justify-start items-center"}>
{!props.cryptoData.quantity && <div
className={"bg-destructive text-destructive-foreground border-destructive rounded p-2 flex justify-start items-center gap-2"}>
<Ban/>
<p>The server dont have stock for the designated cryptos.</p>
</div>}
<div className={"w-full flex justify-center gap-2 items-center p-2"}>
<p>Available quantity on the server :</p>
<p className={"p-1 bg-accent text-accent-foreground rounded m-1"}>{props.cryptoData.quantity}</p>
</div>
{props.cryptoData.quantity && <AutoForm
// Pass the schema to the form
formSchema={buyFromServerSchema}
onSubmit={onBuyFromServerSubmit}
>
<AutoFormSubmit
disabled={!!isLoading}
className={"gap-2 disabled:bg-secondary-foreground"}
>
{/* biome-ignore lint/style/useTemplate: <explanation> */}
<RefreshCw className={`animate-spin ${!isLoading && "hidden"}`}/>
<p>Buy from the server</p>
</AutoFormSubmit>
</AutoForm>}
</TabsContent>
</Tabs>
</DialogContent>
</Dialog>
);
};

View File

@ -0,0 +1,30 @@
// @flow
import * as React from 'react';
import {
Dialog,
DialogContent,
DialogDescription, DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {Button} from "@/components/ui/button";
import {Label} from "@/components/ui/label";
import {Input} from "@/components/ui/input";
import {LineChart} from "lucide-react";
type Props = {
targetedCryptoId: string
};
export function ViewModal(props: Props) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="ghost"><LineChart /></Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px] md:max-w-[800px]">
Test 1,2 !
//From here
</DialogContent>
</Dialog>
);
};

View File

@ -0,0 +1,86 @@
'use client'
import * as React from 'react';
import type {ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"
import {DataTable} from "@/components/ui/data-table";
import {Button} from "@/components/ui/button";
import {ArrowUpDown, MoreHorizontal} from "lucide-react";
import {useRouter} from "next/navigation";
import {useState} from "react";
import {BuyModal} from "@/components/cryptos/buy-modal";
import {ViewModal} from "@/components/cryptos/view-modal";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
type Props = {
cryptosArray: ICryptoInWalletInfo[]
};
export function CryptosTable(props: Props) {
const router = useRouter()
const cryptos= props.cryptosArray
const columns: ColumnDef<ICryptoInWalletInfo, any>[] = [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "value",
header: ({ column }) => {
return (
<Button
variant="light"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Value - USD
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
{
accessorKey: "quantity",
header: ({ column }) => {
return (
<Button
variant="light"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Available from server
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
{
id: "actions",
cell: ({ row }) => {
const payment = row.original
return (
<div className={"flex gap-2"}>
<BuyModal cryptoData={row.original}/>
<ViewModal targetedCryptoId={row.original.id}/>
</div>
)
},
},
];
return (
<>
<DataTable columns={columns} data={cryptos} fieldToFilter={"name"}/>
</>
);
}

View File

@ -0,0 +1,86 @@
'use client'
import * as React from 'react';
import type {ICryptoInUserWalletInfo, ICryptoInWalletInfo} from "@/interfaces/crypto.interface";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"
import {DataTable} from "@/components/ui/data-table";
import {Button} from "@/components/ui/button";
import {ArrowUpDown, MoreHorizontal} from "lucide-react";
import {useRouter} from "next/navigation";
import {useState} from "react";
import {BuyModal} from "@/components/cryptos/buy-modal";
import {ViewModal} from "@/components/cryptos/view-modal";
import {IUserWallet} from "@/interfaces/userdata.interface";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
type Props = {
walletArray: IUserWallet
};
export function WalletTable(props: Props) {
const router = useRouter()
const wallet= props.walletArray.owned_cryptos
const columns: ColumnDef<ICryptoInUserWalletInfo, any>[] = [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "value",
header: ({ column }) => {
return (
<Button
variant="light"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Value - USD
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
{
accessorKey: "owned_amount",
header: ({ column }) => {
return (
<Button
variant="light"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Amount owned
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
)
},
},
{
id: "actions",
cell: ({ row }) => {
const payment = row.original
return (
<div className={"flex gap-2"}>
<p className={"font-light italic text-xs"}>Soon here : Sell, History</p>
</div>
)
},
},
];
return (
<>
<DataTable columns={columns} data={wallet} fieldToFilter={"name"}/>
</>
);
}

View File

@ -15,6 +15,7 @@ const buttonVariants = cva(
"border border-input bg-background hover:bg-accent hover:text-accent-foreground", "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
light: "hover:bg-accent-foreground hover:text-accent",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {

View File

@ -0,0 +1,133 @@
"use client"
import {
type ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
type ColumnFiltersState,
getFilteredRowModel,
getSortedRowModel, type SortingState,
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import {Button} from "@/components/ui/button";
import {useState} from "react";
import {Input} from "@/components/ui/input";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
fieldToFilter: string
}
export function DataTable<TData, TValue>({
columns,
data,
fieldToFilter,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([])
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
[]
)
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
onSortingChange: setSorting,
getSortedRowModel: getSortedRowModel(),
onColumnFiltersChange: setColumnFilters,
getFilteredRowModel: getFilteredRowModel(),
state: {
sorting,
columnFilters,
},
})
return (
<div className={"w-full"}>
<div className="flex items-center py-4">
<Input
placeholder={`Filter ${fieldToFilter}...`}
value={(table.getColumn(fieldToFilter)?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn(fieldToFilter)?.setFilterValue(event.target.value)
}
className="max-w-sm"
/>
</div>
<div className="rounded-md border w-full text-md">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} className={"font-bold text-lg text-center"}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className={"text-center"}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
</div>
</div>
)
}

View File

@ -1,4 +1,4 @@
import { ITrade, type IUserWalletCryptos } from "@/interfaces/crypto.interface"; import {type ICryptoInWalletInfo, ITrade, type IUserWalletCryptos} from "@/interfaces/crypto.interface";
import type { IUserData } from "@/interfaces/userdata.interface"; import type { IUserData } from "@/interfaces/userdata.interface";
// ----- Request ----- // ----- Request -----
@ -27,6 +27,20 @@ export interface IApiOfferCreateReq {
amount: number; amount: number;
} }
export interface IApiCreateReferralCodeReq {
name: string;
value: number;
}
export interface IApiDoTradeReq {
id_offer: string;
}
export interface IApiDoOfferReq {
id_crypto: string;
amount: number;
}
// ----- Response ----- // ----- Response -----
export interface IAbstractApiResponse { export interface IAbstractApiResponse {
@ -58,6 +72,19 @@ export interface IAllRankRes extends IAbstractApiResponse {}
export interface IAllReferralCodeRes extends IAbstractApiResponse {} export interface IAllReferralCodeRes extends IAbstractApiResponse {}
export interface ICreateReferralCodeRes extends IAbstractApiResponse {}
export interface IReferralCodeUpdateRes extends IAbstractApiResponse {} export interface IReferralCodeUpdateRes extends IAbstractApiResponse {}
export interface IReferralCodeDeleteRes 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
}

View File

@ -1,11 +1,18 @@
"use client"; "use client";
import type {IAllReferralCodeRes, IApiAllTradesRes, IApiUserAssetsRes} from "@/interfaces/api.interface"; import type {
import { IUserWalletCryptos } from "@/interfaces/crypto.interface"; 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 { EReturnState, type IStandardisedReturn } from "@/interfaces/general.interface";
import type { IUserData, IUserWallet } from "@/interfaces/userdata.interface"; import type { IUserData, IUserWallet } from "@/interfaces/userdata.interface";
import ApiRequest from "@/services/apiRequest"; import ApiRequest from "@/services/apiRequest";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import {AxiosResponse} from "axios";
//TODO Run disconnect task //TODO Run disconnect task
export function doDisconnect() { export function doDisconnect() {
@ -44,153 +51,3 @@ export async function getWallet(): Promise<IStandardisedReturn<IApiUserAssetsRes
}; };
} }
} }
export async function getUserTrades() {
try {
const ReqRes =
await ApiRequest.authenticated.get.json<IStandardisedReturn<IApiAllTradesRes>>(
"user/my-trades",
);
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,
};
}
}
export async function getAlltrades() {
try {
const ReqRes =
await ApiRequest.authenticated.get.json<IStandardisedReturn<IApiAllTradesRes>>(
"trade/all",
);
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,
};
}
}
export async function createTrade(data: any) {
const ReqRes =
await ApiRequest.authenticated.post.json<IStandardisedReturn<IApiAllTradesRes>>(
"trade/create",
{
}
);
console.log(ReqRes.data);
if (ReqRes.status !== 200) {
return {
state: EReturnState.clientError,
};
}
return {
state: EReturnState.done,
resolved: ReqRes.data,
};
}
export async function getAllTrade() {
const ReqRes =
await ApiRequest.authenticated.get.json<IStandardisedReturn<IApiAllTradesRes>>(
"trade/all",
);
console.log(ReqRes.data);
if (ReqRes.status !== 200) {
return {
state: EReturnState.clientError,
};
}
return {
state: EReturnState.done,
resolved: ReqRes.data,
};
}
export async function getUserTrade() {
const ReqRes =
await ApiRequest.authenticated.get.json<IStandardisedReturn<IApiAllTradesRes>>(
"user/my-trades",
);
console.log(ReqRes.data);
if (ReqRes.status !== 200) {
return {
state: EReturnState.clientError,
};
}
return {
state: EReturnState.done,
resolved: ReqRes.data,
};
}
export async function getAllReferralCode() {
const ReqRes =
await ApiRequest.authenticated.get.json<IStandardisedReturn<IAllReferralCodeRes>>(
"promoCode/all",
);
console.log(ReqRes.data);
if (ReqRes.status !== 200) {
return {
state: EReturnState.clientError,
};
}
return {
state: EReturnState.done,
resolved: ReqRes.data,
};
}
export async function createReferralCode(data: any) {
const ReqRes =
await ApiRequest.authenticated.post.json<IStandardisedReturn<IAllReferralCodeRes>>(
"promoCode/create",
data
);
console.log(ReqRes.data);
if (ReqRes.status !== 200) {
return {
state: EReturnState.clientError,
};
}
return {
state: EReturnState.done,
resolved: ReqRes.data,
};
}
export async function getAllCryptos() {}
export async function getCryptoHistory(cryptoId: string) {}
export async function sellCrypto() {}
export async function buyCrypto() {}

View File

@ -10,7 +10,7 @@ const AxiosConfigs = {
return { return {
headers: { headers: {
"content-type": "application/json", "content-type": "application/json",
Authorization: `Bearer ${typeof window !== "undefined" ? window.localStorage.getItem("sub") : "not-ssr"}`, Authorization: `Bearer ${typeof window !== "undefined" ? JSON.parse(window.localStorage.getItem("sub") || "not-ssr") : "not-ssr"}`,
}, },
validateStatus: (status: number) => { validateStatus: (status: number) => {
return status < 500; // Resolve only if the status code is less than 500 return status < 500; // Resolve only if the status code is less than 500