Compare commits

..

5 Commits
old-1 ... dev

Author SHA1 Message Date
7a27b3a5ae
Update IDE config and seed file with additional data
Adjusted .idea/workspace.xml for correct recent project path and tasks. Updated seed.ts to include new cryptocurrency records and modified existing promo code details.
2024-11-27 10:55:28 +01:00
ccbb1d749a
Some ide config files and libs changes... 2024-11-26 19:59:12 +01:00
b6f50dc944
Add defaultCryptoId prop to SellForm and enhance usability
Introduced a new defaultCryptoId prop for the SellForm component to pre-select a cryptocurrency on mount. Adjusted responsive layout and usability for various components including headers, modals, and navigation links. Ensured better user feedback and message clarity for login and registration processes.
2024-11-24 23:50:12 +01:00
e3aa219389
Remove IDE configuration files and add new selling feature
Removed unnecessary IntelliJ IDEA (`.idea`) configuration files to clean up the repository. Added new sell-related functionality including the SellCryptoPage, SellModal, SellForm, and updated relevant types and UI components.
2024-11-22 11:06:05 +01:00
ea7ab60e5c
Add QueryClientProvider for react-query support
Integrate react-query by adding `QueryClientProvider` to the application. This involves updating dependencies, wrapping existing providers with `QueryProvider`, and refactoring the `account-info` module to utilize new hooks and APIs.
2024-11-14 16:04:14 +01:00
32 changed files with 777 additions and 247 deletions

8
.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

2
.idea/discord.xml generated
View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DiscordProjectSettings"> <component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" /> <option name="show" value="PROJECT" />
<option name="description" value="" /> <option name="description" value="" />
</component> </component>
</project> </project>

6
.idea/git_toolbox_blame.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxBlameSettings">
<option name="version" value="2" />
</component>
</project>

15
.idea/git_toolbox_prj.xml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

View File

@ -1,10 +0,0 @@
<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>

3
.idea/modules.xml generated
View File

@ -2,7 +2,8 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/brief-07-front.iml" filepath="$PROJECT_DIR$/.idea/brief-07-front.iml" /> <module fileurl="file://$PROJECT_DIR$/../neptune-back/.idea/neptune-backend.iml" filepath="$PROJECT_DIR$/../neptune-back/.idea/neptune-backend.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/neptune-front.iml" filepath="$PROJECT_DIR$/.idea/neptune-front.iml" />
</modules> </modules>
</component> </component>
</project> </project>

View File

@ -8,5 +8,6 @@
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="neptune-backend" />
</component> </component>
</module> </module>

6
.idea/nx-console.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NxConsoleProjectSettingsProvider">
<option name="workspacePath" value="" />
</component>
</project>

1
.idea/vcs.xml generated
View File

@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/../neptune-back" vcs="Git" />
</component> </component>
</project> </project>

21
.idea/webResources.xml generated Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebResourcesPaths">
<contentEntries>
<entry url="file://$PROJECT_DIR$/../neptune-back">
<entryData>
<resourceRoots>
<path value="file://$PROJECT_DIR$/../neptune-back" />
</resourceRoots>
</entryData>
</entry>
<entry url="file://$PROJECT_DIR$">
<entryData>
<resourceRoots>
<path value="file://$PROJECT_DIR$" />
</resourceRoots>
</entryData>
</entry>
</contentEntries>
</component>
</project>

155
.idea/workspace.xml generated Normal file
View File

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="fe5a67da-16c2-44c1-97cf-5ff76c380f48" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/../neptune-back/prisma/seed.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../neptune-back/prisma/seed.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/../neptune-back" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 0
}</component>
<component name="ProjectId" id="2oC9EyMrMXnN5JRY3d8EUK3Fg2I" />
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/avnyr/monoliths/neptune-back&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;pnpm&quot;,
&quot;npm.dev.executor&quot;: &quot;Run&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.lookFeel&quot;,
&quot;ts.external.directory.path&quot;: &quot;/home/avnyr/monoliths/neptune-front/node_modules/typescript/lib&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/public" />
</key>
</component>
<component name="RunManager">
<configuration name="dev" type="js.build_tools.npm" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="dev" />
</scripts>
<node-interpreter value="project" />
<package-manager value="$USER_HOME$/.local/share/pnpm/pnpm" />
<envs />
<method v="2" />
</configuration>
</component>
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-d6986cc7102b-e768b9ed790e-JavaScript-WS-243.21565.180" />
</set>
</attachedChunks>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="fe5a67da-16c2-44c1-97cf-5ff76c380f48" name="Changes" comment="" />
<created>1730362584431</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1730362584431</updated>
<workItem from="1730362585545" duration="2622000" />
<workItem from="1730365668445" duration="599000" />
<workItem from="1730370820744" duration="1688000" />
<workItem from="1730715257858" duration="38000" />
<workItem from="1730971311881" duration="4339000" />
<workItem from="1732021386744" duration="27000" />
<workItem from="1732021424352" duration="4000" />
<workItem from="1732028162494" duration="139000" />
<workItem from="1732110655402" duration="2406000" />
<workItem from="1732115490707" duration="1304000" />
<workItem from="1732177377647" duration="212000" />
<workItem from="1732177642993" duration="25000" />
<workItem from="1732177670017" duration="26000" />
<workItem from="1732177712103" duration="20000" />
<workItem from="1732177736286" duration="34000" />
<workItem from="1732202348717" duration="1808000" />
<workItem from="1732268531361" duration="8833000" />
<workItem from="1732453974909" duration="11758000" />
<workItem from="1732526630314" duration="3449000" />
<workItem from="1732646974158" duration="1446000" />
<workItem from="1732696522710" duration="884000" />
</task>
<task id="LOCAL-00001" summary="Remove IDE configuration files and add new selling feature&#10;&#10;Removed unnecessary IntelliJ IDEA (`.idea`) configuration files to clean up the repository. Added new sell-related functionality including the SellCryptoPage, SellModal, SellForm, and updated relevant types and UI components.">
<option name="closed" value="true" />
<created>1732269965774</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1732269965774</updated>
</task>
<task id="LOCAL-00002" summary="Enable user crypto amount selection and update deployment docs&#10;&#10;Added the ability to select user crypto amounts in the user service. Removed commented-out code in the offer controller for clarity. Updated the README with deployment and production instructions.">
<option name="closed" value="true" />
<created>1732270010458</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1732270010458</updated>
</task>
<task id="LOCAL-00003" summary="Fix offer retrieval method and add GDPR acknowledgment&#10; &#10;Return the result from `getOffersById` method to fix route handling. Add `id` field in offers selection query and introduce `gdpr_acknowledgement` to the user model and mock data for GDPR compliance.">
<option name="closed" value="true" />
<created>1732488599143</created>
<option name="number" value="00003" />
<option name="presentableId" value="LOCAL-00003" />
<option name="project" value="LOCAL" />
<updated>1732488599143</updated>
</task>
<task id="LOCAL-00004" summary="Add defaultCryptoId prop to SellForm and enhance usability&#10;&#10;Introduced a new defaultCryptoId prop for the SellForm component to pre-select a cryptocurrency on mount. Adjusted responsive layout and usability for various components including headers, modals, and navigation links. Ensured better user feedback and message clarity for login and registration processes.">
<option name="closed" value="true" />
<created>1732488612776</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1732488612776</updated>
</task>
<task id="LOCAL-00005" summary="Some ide config files and libs changes...">
<option name="closed" value="true" />
<created>1732647552711</created>
<option name="number" value="00005" />
<option name="presentableId" value="LOCAL-00005" />
<option name="project" value="LOCAL" />
<updated>1732647552711</updated>
</task>
<option name="localTasksCounter" value="6" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="VcsManagerConfiguration">
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" />
<MESSAGE value="Remove IDE configuration files and add new selling feature&#10;&#10;Removed unnecessary IntelliJ IDEA (`.idea`) configuration files to clean up the repository. Added new sell-related functionality including the SellCryptoPage, SellModal, SellForm, and updated relevant types and UI components." />
<MESSAGE value="Enable user crypto amount selection and update deployment docs&#10;&#10;Added the ability to select user crypto amounts in the user service. Removed commented-out code in the offer controller for clarity. Updated the README with deployment and production instructions." />
<MESSAGE value="Fix offer retrieval method and add GDPR acknowledgment&#10; &#10;Return the result from `getOffersById` method to fix route handling. Add `id` field in offers selection query and introduce `gdpr_acknowledgement` to the user model and mock data for GDPR compliance." />
<MESSAGE value="Add defaultCryptoId prop to SellForm and enhance usability&#10;&#10;Introduced a new defaultCryptoId prop for the SellForm component to pre-select a cryptocurrency on mount. Adjusted responsive layout and usability for various components including headers, modals, and navigation links. Ensured better user feedback and message clarity for login and registration processes." />
<MESSAGE value="Some ide config files and libs changes..." />
<option name="LAST_COMMIT_MESSAGE" value="Some ide config files and libs changes..." />
</component>
</project>

View File

@ -1,3 +1,16 @@
# neptune-front # neptune-front
The project to validate my DWWM diploma on the front end. The project to validate my DWWM diploma on the front end.
## Déploiement
- Configurer les variables d'environnements
### En développement
```shell
pnpm dev
```
### En production
```shell
pnpm build
pnpm start
```

View File

@ -5,7 +5,7 @@
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev --turbo",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
@ -41,6 +41,7 @@
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.4",
"@tanstack/react-query": "^5.60.2",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"axios": "^1.7.7", "axios": "^1.7.7",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
@ -52,7 +53,7 @@
"input-otp": "^1.4.1", "input-otp": "^1.4.1",
"lightweight-charts": "^4.2.1", "lightweight-charts": "^4.2.1",
"lucide-react": "^0.395.0", "lucide-react": "^0.395.0",
"next": "14.2.10", "next": "14.2.18",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",

108
pnpm-lock.yaml generated
View File

@ -95,6 +95,9 @@ importers:
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: ^1.1.4 specifier: ^1.1.4
version: 1.1.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.1.4(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/react-query':
specifier: ^5.60.2
version: 5.60.2(react@18.3.1)
'@tanstack/react-table': '@tanstack/react-table':
specifier: ^8.20.5 specifier: ^8.20.5
version: 8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -129,8 +132,8 @@ importers:
specifier: ^0.395.0 specifier: ^0.395.0
version: 0.395.0(react@18.3.1) version: 0.395.0(react@18.3.1)
next: next:
specifier: 14.2.10 specifier: 14.2.18
version: 14.2.10(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 14.2.18(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes: next-themes:
specifier: ^0.3.0 specifier: ^0.3.0
version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 0.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@ -542,59 +545,59 @@ packages:
'@jridgewell/trace-mapping@0.3.25': '@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@next/env@14.2.10': '@next/env@14.2.18':
resolution: {integrity: sha512-dZIu93Bf5LUtluBXIv4woQw2cZVZ2DJTjax5/5DOs3lzEOeKLy7GxRSr4caK9/SCPdaW6bCgpye6+n4Dh9oJPw==} resolution: {integrity: sha512-2vWLOUwIPgoqMJKG6dt35fVXVhgM09tw4tK3/Q34GFXDrfiHlG7iS33VA4ggnjWxjiz9KV5xzfsQzJX6vGAekA==}
'@next/swc-darwin-arm64@14.2.10': '@next/swc-darwin-arm64@14.2.18':
resolution: {integrity: sha512-V3z10NV+cvMAfxQUMhKgfQnPbjw+Ew3cnr64b0lr8MDiBJs3eLnM6RpGC46nhfMZsiXgQngCJKWGTC/yDcgrDQ==} resolution: {integrity: sha512-tOBlDHCjGdyLf0ube/rDUs6VtwNOajaWV+5FV/ajPgrvHeisllEdymY/oDgv2cx561+gJksfMUtqf8crug7sbA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@next/swc-darwin-x64@14.2.10': '@next/swc-darwin-x64@14.2.18':
resolution: {integrity: sha512-Y0TC+FXbFUQ2MQgimJ/7Ina2mXIKhE7F+GUe1SgnzRmwFY3hX2z8nyVCxE82I2RicspdkZnSWMn4oTjIKz4uzA==} resolution: {integrity: sha512-uJCEjutt5VeJ30jjrHV1VIHCsbMYnEqytQgvREx+DjURd/fmKy15NaVK4aR/u98S1LGTnjq35lRTnRyygglxoA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@next/swc-linux-arm64-gnu@14.2.10': '@next/swc-linux-arm64-gnu@14.2.18':
resolution: {integrity: sha512-ZfQ7yOy5zyskSj9rFpa0Yd7gkrBnJTkYVSya95hX3zeBG9E55Z6OTNPn1j2BTFWvOVVj65C3T+qsjOyVI9DQpA==} resolution: {integrity: sha512-IL6rU8vnBB+BAm6YSWZewc+qvdL1EaA+VhLQ6tlUc0xp+kkdxQrVqAnh8Zek1ccKHlTDFRyAft0e60gteYmQ4A==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@next/swc-linux-arm64-musl@14.2.10': '@next/swc-linux-arm64-musl@14.2.18':
resolution: {integrity: sha512-n2i5o3y2jpBfXFRxDREr342BGIQCJbdAUi/K4q6Env3aSx8erM9VuKXHw5KNROK9ejFSPf0LhoSkU/ZiNdacpQ==} resolution: {integrity: sha512-RCaENbIZqKKqTlL8KNd+AZV/yAdCsovblOpYFp0OJ7ZxgLNbV5w23CUU1G5On+0fgafrsGcW+GdMKdFjaRwyYA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@next/swc-linux-x64-gnu@14.2.10': '@next/swc-linux-x64-gnu@14.2.18':
resolution: {integrity: sha512-GXvajAWh2woTT0GKEDlkVhFNxhJS/XdDmrVHrPOA83pLzlGPQnixqxD8u3bBB9oATBKB//5e4vpACnx5Vaxdqg==} resolution: {integrity: sha512-3kmv8DlyhPRCEBM1Vavn8NjyXtMeQ49ID0Olr/Sut7pgzaQTo4h01S7Z8YNE0VtbowyuAL26ibcz0ka6xCTH5g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@next/swc-linux-x64-musl@14.2.10': '@next/swc-linux-x64-musl@14.2.18':
resolution: {integrity: sha512-opFFN5B0SnO+HTz4Wq4HaylXGFV+iHrVxd3YvREUX9K+xfc4ePbRrxqOuPOFjtSuiVouwe6uLeDtabjEIbkmDA==} resolution: {integrity: sha512-mliTfa8seVSpTbVEcKEXGjC18+TDII8ykW4a36au97spm9XMPqQTpdGPNBJ9RySSFw9/hLuaCMByluQIAnkzlw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@next/swc-win32-arm64-msvc@14.2.10': '@next/swc-win32-arm64-msvc@14.2.18':
resolution: {integrity: sha512-9NUzZuR8WiXTvv+EiU/MXdcQ1XUvFixbLIMNQiVHuzs7ZIFrJDLJDaOF1KaqttoTujpcxljM/RNAOmw1GhPPQQ==} resolution: {integrity: sha512-J5g0UFPbAjKYmqS3Cy7l2fetFmWMY9Oao32eUsBPYohts26BdrMUyfCJnZFQkX9npYaHNDOWqZ6uV9hSDPw9NA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@next/swc-win32-ia32-msvc@14.2.10': '@next/swc-win32-ia32-msvc@14.2.18':
resolution: {integrity: sha512-fr3aEbSd1GeW3YUMBkWAu4hcdjZ6g4NBl1uku4gAn661tcxd1bHs1THWYzdsbTRLcCKLjrDZlNp6j2HTfrw+Bg==} resolution: {integrity: sha512-Ynxuk4ZgIpdcN7d16ivJdjsDG1+3hTvK24Pp8DiDmIa2+A4CfhJSEHHVndCHok6rnLUzAZD+/UOKESQgTsAZGg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@next/swc-win32-x64-msvc@14.2.10': '@next/swc-win32-x64-msvc@14.2.18':
resolution: {integrity: sha512-UjeVoRGKNL2zfbcQ6fscmgjBAS/inHBh63mjIlfPg/NG8Yn2ztqylXt5qilYb6hoHIwaU2ogHknHWWmahJjgZQ==} resolution: {integrity: sha512-dtRGMhiU9TN5nyhwzce+7c/4CCeykYS+ipY/4mIrGzJ71+7zNo55ZxCB7cAVuNqdwtYniFNR2c9OFQ6UdFIMcg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -1246,6 +1249,14 @@ 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/query-core@5.59.20':
resolution: {integrity: sha512-e8vw0lf7KwfGe1if4uPFhvZRWULqHjFcz3K8AebtieXvnMOz5FSzlZe3mTLlPuUBcydCnBRqYs2YJ5ys68wwLg==}
'@tanstack/react-query@5.60.2':
resolution: {integrity: sha512-JhpJNxIAPuE0YCpP1Py4zAsgx+zY0V531McRMtQbwVlJF8+mlZwcOPrzGmPV248K8IP+mPbsfxXToVNMNwjUcw==}
peerDependencies:
react: ^18 || ^19
'@tanstack/react-table@8.20.5': '@tanstack/react-table@8.20.5':
resolution: {integrity: sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==} resolution: {integrity: sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -2111,8 +2122,8 @@ packages:
react: ^16.8 || ^17 || ^18 react: ^16.8 || ^17 || ^18
react-dom: ^16.8 || ^17 || ^18 react-dom: ^16.8 || ^17 || ^18
next@14.2.10: next@14.2.18:
resolution: {integrity: sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==} resolution: {integrity: sha512-H9qbjDuGivUDEnK6wa+p2XKO+iMzgVgyr9Zp/4Iv29lKa+DYaxJGjOeEA+5VOvJh/M7HLiskehInSa0cWxVXUw==}
engines: {node: '>=18.17.0'} engines: {node: '>=18.17.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -3126,33 +3137,33 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
'@next/env@14.2.10': {} '@next/env@14.2.18': {}
'@next/swc-darwin-arm64@14.2.10': '@next/swc-darwin-arm64@14.2.18':
optional: true optional: true
'@next/swc-darwin-x64@14.2.10': '@next/swc-darwin-x64@14.2.18':
optional: true optional: true
'@next/swc-linux-arm64-gnu@14.2.10': '@next/swc-linux-arm64-gnu@14.2.18':
optional: true optional: true
'@next/swc-linux-arm64-musl@14.2.10': '@next/swc-linux-arm64-musl@14.2.18':
optional: true optional: true
'@next/swc-linux-x64-gnu@14.2.10': '@next/swc-linux-x64-gnu@14.2.18':
optional: true optional: true
'@next/swc-linux-x64-musl@14.2.10': '@next/swc-linux-x64-musl@14.2.18':
optional: true optional: true
'@next/swc-win32-arm64-msvc@14.2.10': '@next/swc-win32-arm64-msvc@14.2.18':
optional: true optional: true
'@next/swc-win32-ia32-msvc@14.2.10': '@next/swc-win32-ia32-msvc@14.2.18':
optional: true optional: true
'@next/swc-win32-x64-msvc@14.2.10': '@next/swc-win32-x64-msvc@14.2.18':
optional: true optional: true
'@nodelib/fs.scandir@2.1.5': '@nodelib/fs.scandir@2.1.5':
@ -3850,6 +3861,13 @@ snapshots:
'@swc/counter': 0.1.3 '@swc/counter': 0.1.3
tslib: 2.6.3 tslib: 2.6.3
'@tanstack/query-core@5.59.20': {}
'@tanstack/react-query@5.60.2(react@18.3.1)':
dependencies:
'@tanstack/query-core': 5.59.20
react: 18.3.1
'@tanstack/react-table@8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': '@tanstack/react-table@8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies: dependencies:
'@tanstack/table-core': 8.20.5 '@tanstack/table-core': 8.20.5
@ -4860,9 +4878,9 @@ snapshots:
react: 18.3.1 react: 18.3.1
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
next@14.2.10(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): next@14.2.18(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
'@next/env': 14.2.10 '@next/env': 14.2.18
'@swc/helpers': 0.5.5 '@swc/helpers': 0.5.5
busboy: 1.6.0 busboy: 1.6.0
caniuse-lite: 1.0.30001636 caniuse-lite: 1.0.30001636
@ -4872,15 +4890,15 @@ snapshots:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
styled-jsx: 5.1.1(@babel/core@7.24.7)(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.24.7)(react@18.3.1)
optionalDependencies: optionalDependencies:
'@next/swc-darwin-arm64': 14.2.10 '@next/swc-darwin-arm64': 14.2.18
'@next/swc-darwin-x64': 14.2.10 '@next/swc-darwin-x64': 14.2.18
'@next/swc-linux-arm64-gnu': 14.2.10 '@next/swc-linux-arm64-gnu': 14.2.18
'@next/swc-linux-arm64-musl': 14.2.10 '@next/swc-linux-arm64-musl': 14.2.18
'@next/swc-linux-x64-gnu': 14.2.10 '@next/swc-linux-x64-gnu': 14.2.18
'@next/swc-linux-x64-musl': 14.2.10 '@next/swc-linux-x64-musl': 14.2.18
'@next/swc-win32-arm64-msvc': 14.2.10 '@next/swc-win32-arm64-msvc': 14.2.18
'@next/swc-win32-ia32-msvc': 14.2.10 '@next/swc-win32-ia32-msvc': 14.2.18
'@next/swc-win32-x64-msvc': 14.2.10 '@next/swc-win32-x64-msvc': 14.2.18
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros

View File

@ -10,7 +10,6 @@ import { useEffect, useState } from "react";
export default function DashboardPage() { export default function DashboardPage() {
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const [cryptosList, setCryptosList] = useState<ICryptoInWalletInfo[]>([]); const [cryptosList, setCryptosList] = useState<ICryptoInWalletInfo[]>([]);
//FIX the loop
useEffect(() => { useEffect(() => {
ApiRequest.authenticated.get ApiRequest.authenticated.get

View File

@ -14,7 +14,7 @@ import type React from "react";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Neptune Crypto", title: "Neptune Crypto",
description: "A fictive app", description: "A fictive app",
icons: "neptune.svg", icons: "/neptune.svg",
}; };
export default function RootLayout({ export default function RootLayout({
@ -27,18 +27,18 @@ 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>
<div className={"flex flex-row flex-wrap md:flex-nowrap gap-2"}> <nav className="flex flex-row flex-wrap md:flex-nowrap gap-2">
<Button asChild variant={"light"}> <Button asChild variant="light">
<Link href={"/wallet"}><p className={"lg:text-lg"}>Wallet</p></Link> <Link href="/wallet" title={"Go to your wallet"}><p className="lg:text-lg">Wallet</p></Link>
</Button> </Button>
<Button asChild variant={"light"}> <Button asChild variant="light">
<Link href={"/dashboard"}><p className={"lg:text-lg"}>Explore cryptos</p></Link> <Link href="/dashboard" title={"Go to the crypto explorer"}><p className="lg:text-lg">Explore cryptos</p></Link>
</Button> </Button>
</div> </nav>
</Header> </Header>
{children} {children}
<Toaster /> <Toaster/>
<Footer /> <Footer/>
</Providers> </Providers>
</body> </body>
</html> </html>

View File

@ -1,18 +1,18 @@
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input";
import { Bitcoin, DollarSign, LineChart, Lock, Zap } from "lucide-react" import { Bitcoin, DollarSign, LineChart, Lock, Zap } from "lucide-react";
import Link from "next/link" import Link from "next/link";
export default function HomePage() { export default function HomePage() {
return ( return (
<div className="flex flex-col min-h-screen"> <div className="flex flex-col min-h-screen">
<main className="flex-1"> <main className="flex-1">
<section className="w-full py-12 md:py-16 lg:py-24xl:py-32"> <section className="w-full py-12 md:py-16 lg:py-24 xl:py-32">
<div className="container px-4 md:px-6"> <div className="container px-4 md:px-6">
<div className="flex flex-col items-center space-y-4 text-center"> <div className="flex flex-col items-center space-y-4 text-center">
<div className="space-y-2"> <div className="space-y-2">
<h1 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl lg:text-6xl/none"> <h1 className="text-3xl font-bold tracking-tighter sm:text-4xl md:text-5xl lg:text-6xl">
Welcome to Neptune Crypto Welcome to Neptune Crypto
</h1> </h1>
<p className="mx-auto max-w-[700px] text-gray-500 md:text-xl dark:text-gray-400"> <p className="mx-auto max-w-[700px] text-gray-500 md:text-xl dark:text-gray-400">
@ -23,30 +23,34 @@ export default function HomePage() {
<Button>Get Started</Button> <Button>Get Started</Button>
<Button variant="outline">Learn More</Button> <Button variant="outline">Learn More</Button>
</div> </div>
<p className="mx-auto max-w-[700px] text-destructive/50 md:text-lg">
This website is fictional and for demonstration purposes only.
</p>
</div> </div>
</div> </div>
</section> </section>
<section className="w-full py-12 md:py-16 lg:py-20 bg-card rounded"> <section className="w-full py-12 md:py-16 lg:py-20 bg-card rounded">
<div className="container px-4 md:px-6 rounded"> <div className="container px-4 md:px-6 rounded">
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl text-center mb-12">Our Features</h2> <h2 className="text-3xl font-bold tracking-tighter sm:text-5xl text-center mb-12">Our Features</h2>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
<Card> <Card>
<CardHeader> <CardHeader>
<Bitcoin className="h-10 w-10 mb-2" /> <Bitcoin className="h-10 w-10 mb-2"/>
<CardTitle>Multiple Cryptocurrencies</CardTitle> <CardTitle>Multiple Cryptocurrencies</CardTitle>
<CardDescription>Trade a wide variety of popular cryptocurrencies.</CardDescription> <CardDescription>Trade a wide variety of popular cryptocurrencies.</CardDescription>
</CardHeader> </CardHeader>
</Card> </Card>
<Card> <Card>
<CardHeader> <CardHeader>
<Lock className="h-10 w-10 mb-2" /> <Lock className="h-10 w-10 mb-2"/>
<CardTitle>Secure Storage</CardTitle> <CardTitle>Secure Storage</CardTitle>
<CardDescription>Your assets are protected with state-of-the-art security measures.</CardDescription> <CardDescription>Your assets are protected with state-of-the-art security measures.</CardDescription>
</CardHeader> </CardHeader>
</Card> </Card>
<Card> <Card>
<CardHeader> <CardHeader>
<LineChart className="h-10 w-10 mb-2" /> <LineChart className="h-10 w-10 mb-2"/>
<CardTitle>Advanced Trading Tools</CardTitle> <CardTitle>Advanced Trading Tools</CardTitle>
<CardDescription>Access powerful analytics and trading features.</CardDescription> <CardDescription>Access powerful analytics and trading features.</CardDescription>
</CardHeader> </CardHeader>
@ -54,24 +58,25 @@ export default function HomePage() {
</div> </div>
</div> </div>
</section> </section>
<section className="w-full py-12 md:py-24 lg:py-32"> <section className="w-full py-12 md:py-24 lg:py-32">
<div className="container px-4 md:px-6"> <div className="container px-4 md:px-6">
<div className="flex flex-col items-center justify-center space-y-4 text-center"> <div className="flex flex-col items-center justify-center space-y-4 text-center">
<div className="space-y-2"> <div className="space-y-2">
<h2 className="text-3xl font-bold tracking-tighter sm:text-5xl">Start Trading Today</h2> <h2 className="text-3xl font-bold tracking-tighter sm:text-5xl">Start Trading Today</h2>
<p className="mx-auto max-w-[600px] text-gray-500 md:text-xl/relaxed lg:text-base/relaxed xl:text-xl/relaxed dark:text-gray-400"> <p className="mx-auto max-w-[600px] text-gray-500 md:text-xl lg:text-base xl:text-xl dark:text-gray-400">
Join thousands of traders and investors on our platform. Get started with as little as $10. Join thousands of traders and investors on our platform. Get started with as little as $10.
</p> </p>
</div> </div>
<div className="w-full max-w-sm space-y-2"> <div className="w-full max-w-sm space-y-2">
<Button> <Button>
Sign Up Sign Up
<DollarSign className="ml-2 h-4 w-4" /> <DollarSign className="ml-2 h-4 w-4"/>
</Button> </Button>
<p className="text-xs text-gray-500 dark:text-gray-400"> <p className="text-xs text-gray-500 dark:text-gray-400">
By signing up, you agree to our{" "} By signing up, you agree to our{" "}
<Link className="underline underline-offset-2" href="#"> <Link title="Go to legal notice" className="underline underline-offset-2" href="/legal">
Terms & Conditions Legal Notice
</Link> </Link>
</p> </p>
</div> </div>
@ -80,5 +85,5 @@ export default function HomePage() {
</section> </section>
</main> </main>
</div> </div>
) );
} }

View File

@ -0,0 +1,24 @@
"use client"
import {OfferList} from "@/components/sub/OfferList";
import {SellForm} from "@/components/sell-form";
interface SellCryptoPageProps {
params: IParams;
}
interface IParams {
cryptoId: string;
}
export default function SellCryptoPage({ params }: SellCryptoPageProps) {
console.log(params);
return (
<main className="flex flex-col sm:flex-row items-center justify-center w-full h-full gap-4 p-2 sm:p-4">
<section className={"w-full lg:w-1/3 h-full p-0.5 sm:p-2 flex flex-col items-center justify-center gap-4"}>
<SellForm defaultCryptoId={params.cryptoId}/>
</section>
</main>
);
}

View File

@ -14,7 +14,6 @@ export default function WalletPage() {
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const [cryptosList, setCryptosList] = useState<ICryptoInWalletInfo[]>([]); const [cryptosList, setCryptosList] = useState<ICryptoInWalletInfo[]>([]);
const userContext = useContext(UserDataContext); const userContext = useContext(UserDataContext);
//FIX the loop
useEffect(() => { useEffect(() => {
console.log(userContext?.userData); console.log(userContext?.userData);

View File

@ -1,4 +1,5 @@
"use client"; "use client";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,
@ -9,30 +10,55 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type { IUserData, IUserWallet } 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 { import type {
type ICryptoInUserWalletInfo, ICryptoInUserWalletInfo,
ICryptoInWalletInfo,
} from "@/interfaces/crypto.interface"; } from "@/interfaces/crypto.interface";
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 { useEffect, useState, useCallback, useMemo } from "react";
import { EReturnState, type IStandardisedReturn } from "@/interfaces/general.interface";
import type { IApiUserAssetsRes } from "@/interfaces/api.interface";
import ApiRequest from "@/services/apiRequest";
export function AccountInfo({ export function doDisconnect() {
userData, if (typeof window !== "undefined") {
setUserData, window.localStorage.removeItem("sub");
isDisconnected, //Redirect to homepage
}: { window.location.href = "/";
userData: IUserData; return true;
setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>; }
isDisconnected: boolean; console.log("Whut ? Why trying to remove an item from the localStorage when running in SSR ?");
}) { return false;
}
async function getWallet(): Promise<IStandardisedReturn<IApiUserAssetsRes>> {
try {
const ReqRes =
await ApiRequest.authenticated.get.json<IStandardisedReturn<IApiUserAssetsRes>>(
"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,
};
}
}
function useWallet(userData: IUserData, setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>) {
const [isLoaded, setIsLoaded] = useState(false); const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => { useEffect(() => {
@ -40,52 +66,80 @@ export function AccountInfo({
getWallet().then((res) => { getWallet().then((res) => {
const wallet: IUserWallet = { const wallet: IUserWallet = {
uat: Date.now(), uat: Date.now(),
update_interval: 30_000, update_interval: 30000,
owned_cryptos: owned_cryptos: res.resolved?.UserHasCrypto?.map((el): ICryptoInUserWalletInfo => ({
res.resolved?.UserHasCrypto?.map((el): ICryptoInUserWalletInfo => { id: el.Crypto.id,
return { name: el.Crypto.name,
id: el.Crypto.id, value: el.Crypto.value,
name: el.Crypto.name, image: el.Crypto.image,
value: el.Crypto.value, quantity: el.Crypto.quantity,
image: el.Crypto.image, owned_amount: el.amount,
quantity: el.Crypto.quantity, created_at: el.Crypto.created_at,
owned_amount: el.amount, updated_at: el.Crypto.updated_at,
created_at: el.Crypto.created_at, })) || [],
updated_at: el.Crypto.updated_at,
};
}) || [],
}; };
delete res.resolved?.UserHasCrypto; delete res.resolved?.UserHasCrypto;
//@ts-ignore setUserData((prev) => ({
setUserData({ ...prev,
...userData,
...res.resolved, ...res.resolved,
wallet: wallet, wallet,
}); }) as unknown as IUserData);
console.log(userData); console.log(userData);
setIsLoaded(true); setIsLoaded(true);
}); });
} }
}, [isLoaded, userData, setUserData]); }, [isLoaded, userData, setUserData]);
return isLoaded;
}
export function AccountInfo({
userData,
setUserData,
isDisconnected,
}: {
userData: IUserData;
setUserData: React.Dispatch<React.SetStateAction<IUserData | undefined>>;
isDisconnected: boolean;
}) {
const isLoaded = useWallet(userData, setUserData);
const walletInfo = useMemo(() => (
<div className={"flex flex-col md:flex-row gap-2 justify-center md:justify-evenly items-start md:items-center w-full"}>
<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 currently have ${userData.wallet.owned_cryptos.length} crypto(s)`}</p>
</div>
</div>
), [userData.dollarAvailables, userData.wallet.owned_cryptos.length]);
const userInfo = useMemo(() => (
<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>
), [userData.id]);
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"}>
<div <div className={"flex flex-row justify-center items-center gap-1 text-destructive to-red-900 animate-pulse"}>
className={
"flex flex-row justify-center items-center gap-1 text-destructive to-red-900 animate-pulse"
}
>
<Unplug className={"w-4"} /> <Unplug className={"w-4"} />
<p>Disconnected</p> <p>Disconnected</p>
</div> </div>
<div> <div>
<Link <Link href={"/auth"} className={"hover:text-primary flex justify-evenly items-center gap-1 p-1 text-nowrap"}>
href={"/auth"}
className={
"hover:text-primary flex justify-evenly items-center gap-1 p-1 text-nowrap"
}
>
<Key className={"w-3"} /> Link account <Key className={"w-3"} /> Link account
</Link> </Link>
</div> </div>
@ -107,50 +161,8 @@ export function AccountInfo({
<DialogDescription>{userData.pseudo}</DialogDescription> <DialogDescription>{userData.pseudo}</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"}> {walletInfo}
<div {userInfo}
className={
"flex flex-col md:flex-row gap-2 justify-center md:justify-evenly items-start md:items-center w-full"
}
>
<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 currently have ${userData.wallet.owned_cryptos.length} crypto(s)`}
</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>
<DialogFooter> <DialogFooter>
<Button variant={"secondary"} className={"gap-2 px-2"} asChild> <Button variant={"secondary"} className={"gap-2 px-2"} asChild>
@ -159,11 +171,7 @@ export function AccountInfo({
<p>My wallet</p> <p>My wallet</p>
</Link> </Link>
</Button> </Button>
<Button <Button variant={"destructive"} className={"gap-2 px-2 mb-2"} onClick={() => doDisconnect()}>
variant={"destructive"}
className={"gap-2 px-2 mb-2"}
onClick={() => doDisconnect()}
>
<Unplug /> <Unplug />
<p>Disconnect</p> <p>Disconnect</p>
</Button> </Button>

View File

@ -64,6 +64,9 @@ const registerSchema = z.object({
.regex(/[0-9]/, "Password must contain at least one number.") .regex(/[0-9]/, "Password must contain at least one number.")
.regex(/[^a-zA-Z0-9]/, "Password must contain at least one special character.") .regex(/[^a-zA-Z0-9]/, "Password must contain at least one special character.")
.describe("Your account password."), .describe("Your account password."),
promoCode: z
.string().max(255, "Your promotional code is too long.").optional(),
}); });
export function AuthForms() { export function AuthForms() {
@ -176,7 +179,7 @@ export function AuthForms() {
} }
//toast.custom(<ToastBox message={"Login successful ! \n You will be redirected."} type={toastType.success}/>) //toast.custom(<ToastBox message={"Login successful ! \n You will be redirected."} type={toastType.success}/>)
toast({ toast({
description: "Login successful ! \n You will be redirected.", description: "Login successful ! \n You will be redirected to the home page.",
}); });
setTimeout(() => { setTimeout(() => {
setIsLoading(false); setIsLoading(false);
@ -245,9 +248,9 @@ export function AuthForms() {
<p>Register</p> <p>Register</p>
</AutoFormSubmit> </AutoFormSubmit>
<p className="text-gray-500 text-sm"> <p className="text-gray-500 text-sm">
By submitting this form, you agree to our{" "} By submitting this form, you agree to the{" "}
<Link href="#" className="text-primary underline"> <Link title={"Go to legal page"} href="/legal" className="text-primary underline">
terms and conditions GDPR Compliance Policy & Usage Policy
</Link> </Link>
. .
</p> </p>

View File

@ -3,7 +3,6 @@ import type { DefaultValues } from "react-hook-form";
import type { z } from "zod"; import type { z } from "zod";
import type { FieldConfig } from "./types"; import type { FieldConfig } from "./types";
// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.
export type ZodObjectOrWrapped = export type ZodObjectOrWrapped =
| z.ZodObject<any, any> | z.ZodObject<any, any>
| z.ZodEffects<z.ZodObject<any, any>>; | z.ZodEffects<z.ZodObject<any, any>>;

View File

@ -4,29 +4,27 @@ import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { import {
Form, Form,
FormControl, FormControl,
FormDescription,
FormField, FormField,
FormItem, FormItem,
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { import {
Select, Select,
SelectContent, SelectTrigger,
SelectItem, SelectValue,
SelectTrigger, SelectContent,
SelectValue, SelectItem,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { toast } from "@/components/ui/use-toast"; import { toast } from "@/components/ui/use-toast";
import type { IApiAllOffersRes, IApiDoTradeReq } from "@/interfaces/api.interface"; import type { IApiAllOffersRes } from "@/interfaces/api.interface";
import type { ICryptoInWalletInfo } from "@/interfaces/crypto.interface"; import type { ICryptoInWalletInfo } from "@/interfaces/crypto.interface";
import ApiRequest from "@/services/apiRequest"; import ApiRequest from "@/services/apiRequest";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Ban, DollarSign, RefreshCw } from "lucide-react"; import { Ban, DollarSign, RefreshCw } from "lucide-react";
import Link from "next/link";
import * as React from "react"; import * as React from "react";
import { Dispatch, SetStateAction, useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import * as z from "zod"; import * as z from "zod";
type Props = { type Props = {
@ -43,6 +41,7 @@ export function BuyModal(props: Props) {
ApiRequest.authenticated.get ApiRequest.authenticated.get
.json<IApiAllOffersRes[]>(`offer/crypto/${props.cryptoData.id}`) .json<IApiAllOffersRes[]>(`offer/crypto/${props.cryptoData.id}`)
.then((response) => { .then((response) => {
console.debug(response)
if (response.data) {setOffersList(response.data)} if (response.data) {setOffersList(response.data)}
console.log(`Crypto ${props.cryptoData.name} -> ${response.data.length}`); console.log(`Crypto ${props.cryptoData.name} -> ${response.data.length}`);
setIsLoaded(true); setIsLoaded(true);
@ -157,7 +156,10 @@ export function BuyModal(props: Props) {
{offersList.length} {offersList.length}
</p> </p>
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="server">Buy from server</TabsTrigger> <TabsTrigger value="server">Buy from server{" "}<p
className={" ml-1 px-1 bg-primary text-primary-foreground rounded"}>
{props.cryptoData.quantity}
</p></TabsTrigger>
</TabsList> </TabsList>
<TabsContent <TabsContent
value="user" value="user"
@ -171,30 +173,28 @@ export function BuyModal(props: Props) {
<FormField <FormField
control={buyFromUserForm.control} control={buyFromUserForm.control}
name="offerId" name="offerId"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>Select an offer</FormLabel> <FormLabel>Select an offer</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}> <Select defaultValue={offersList[0]?.id} onValueChange={(val) => {
<FormControl> console.log(val);
<SelectTrigger> field.onChange(val);
<SelectValue placeholder="Select an offer to purchase." /> }}>
</SelectTrigger> <SelectTrigger>
</FormControl> <SelectValue placeholder="Select an offer to purchase."/>
<SelectContent> </SelectTrigger>
{offersList.map((offer) => { <SelectContent>
if (!offer) return; {offersList.map((offer) => (
return ( <SelectItem
<SelectItem value={offer.id}
value={offer.id} key={offer.id}
key={offer.id} >{`${offer.amount}x ${offer.Crypto.name}`}</SelectItem>
>{`${offer.amount}x ${offer.Crypto.name} - ${offer.User.pseudo}`}</SelectItem> ))}
); </SelectContent>
})} </Select>
</SelectContent> <FormMessage />
</Select> </FormItem>
<FormMessage /> )}
</FormItem>
)}
/> />
<Button type="submit">Submit</Button> <Button type="submit">Submit</Button>
</form> </form>

View File

@ -0,0 +1,10 @@
import type {Row} from "@tanstack/react-table";
import type {ICryptoInUserWalletInfo} from "@/interfaces/crypto.interface";
interface SellModalProps {
row: Row<ICryptoInUserWalletInfo>
}
export function SellModal({ row }: SellModalProps) {
}

View File

@ -10,15 +10,12 @@ import { ViewModal } from "@/components/cryptos/view-modal";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { DataTable } from "@/components/ui/data-table"; import { DataTable } from "@/components/ui/data-table";
import type { IUserWallet } from "@/interfaces/userdata.interface"; import type { IUserWallet } from "@/interfaces/userdata.interface";
import { import type {
type ColumnDef, ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { ArrowUpDown, MoreHorizontal } from "lucide-react"; import { ArrowUpDown, MoreHorizontal, Receipt } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import Link from "next/link";
interface DataTableProps<TData, TValue> { interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]; columns: ColumnDef<TData, TValue>[];
@ -72,8 +69,8 @@ export function WalletTable(props: Props) {
const payment = row.original; const payment = row.original;
return ( return (
<div className={"flex gap-2"}> <div>
<p className={"font-light italic text-xs"}>Soon here : Sell, History</p> <Link href={`/sell/${row.original.id}`} className={"flex w-fit gap-2 p-2 justify-center items-center rounded hover:bg-card"}><Receipt /> <p className={"hidden sm:block"}>Sell for USD</p></Link>
</div> </div>
); );
}, },

View File

@ -11,11 +11,11 @@ export function Header({
return ( return (
<header <header
className={ className={
"flex flex-col md:flex-row justify-between items-center w-full p-1 md:px-3 md:py-2 pb-2 border-b-2" "flex flex-col md:flex-row justify-between items-center w-full p-1 md:px-3 gap-2 md:py-2 pb-2 border-b-2"
} }
> >
<Link href={"/"} className={"flex flex-row justify-center md:justify-start items-center w-fit gap-2"}> <Link title={"Return to home page"} href={"/"} className={"flex flex-row justify-center md:justify-start items-center w-fit gap-2"}>
<Image src={"neptune.svg"} alt={"Logo of Neptune"} width={42} height={42} /> <Image src={"/neptune.svg"} alt={"Logo of Neptune"} width={42} height={42} />
<h1 className={"font-bold text-xl align-middle text-center text-wrap"}> <h1 className={"font-bold text-xl align-middle text-center text-wrap"}>
{title || "Neptune"} {title || "Neptune"}
</h1> </h1>

View File

@ -4,11 +4,16 @@ import { Header } from "@/components/header";
import { ThemeProvider } from "@/components/providers/theme-provider"; import { ThemeProvider } from "@/components/providers/theme-provider";
import { UserDataProvider } from "@/components/providers/userdata-provider"; import { UserDataProvider } from "@/components/providers/userdata-provider";
import type React from "react"; import type React from "react";
import {QueryProvider} from "@/components/providers/query-provider";
export function Providers({ children }: { children: React.ReactNode }) { export function Providers({ children }: { children: React.ReactNode }) {
return ( return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem> <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<UserDataProvider>{children}</UserDataProvider> <QueryProvider>
<UserDataProvider>
{children}
</UserDataProvider>
</QueryProvider>
</ThemeProvider> </ThemeProvider>
); );
} }

View File

@ -0,0 +1,13 @@
"use client"
import type {ReactNode} from "react";
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
interface QueryProviderProps {
children: ReactNode;
}
export function QueryProvider({ children }: QueryProviderProps) {
return (<QueryClientProvider client={new QueryClient()}>
{ children }
</QueryClientProvider>)
}

View File

@ -0,0 +1,199 @@
"use client";
import * as z from "zod";
import ApiRequest from "@/services/apiRequest";
import { toast } from "@/components/ui/use-toast";
import * as React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { useEffect, useState } from "react";
import type { IUserWalletCryptos } from "@/interfaces/crypto.interface";
import type { IApiUserAssetsRes } from "@/interfaces/api.interface";
import { Slider } from "@/components/ui/slider";
import { DollarSign, Equal, X } from "lucide-react";
import { Card } from "@/components/ui/card";
interface SellFormProps {
defaultCryptoId?: string;
}
export function SellForm({ defaultCryptoId }: SellFormProps) {
const [currentWallet, setCurrentWallet] = useState<IUserWalletCryptos[]>();
const [sliderValue, setSliderValue] = useState(0);
const [quantity, setQuantity] = useState(0);
const [selectedCrypto, setSelectedCrypto] = useState<string>();
useEffect(() => {
async function fetchAssets() {
const res = await ApiRequest.authenticated.get.json<IApiUserAssetsRes>("user/my-assets");
setCurrentWallet(res.data.UserHasCrypto);
}
fetchAssets();
}, []);
useEffect(() => {
if (defaultCryptoId) {
const maxShares = getMaxSharesForCrypto(defaultCryptoId);
setSliderValue(maxShares);
setSelectedCrypto(defaultCryptoId);
setQuantity(1);
}
}, []);
function getMaxSharesForCrypto(cryptoId: string) {
return currentWallet?.find((crypto) => crypto.Crypto.id === cryptoId)?.amount || 0;
}
function getCryptoValue(cryptoId: string) {
return currentWallet?.find((crypto) => crypto.Crypto.id === cryptoId)?.Crypto.value || 0;
}
function getCryptoName(cryptoId: string) {
return currentWallet?.find((crypto) => crypto.Crypto.id === cryptoId)?.Crypto.name || "";
}
const sellToUserSchema = z.object({
cryptoId: z.string({ required_error: "You should select a crypto from your wallet." }).uuid(),
amount: z.number({
required_error: "You should select an amount of the assets that you want to sell.",
}).max(sliderValue),
});
async function onSellToUserSubmit(data: z.infer<typeof sellToUserSchema>) {
const res = await ApiRequest.authenticated.post.json("offer/create", {
id_crypto: data.cryptoId,
amount: data.amount,
});
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;
}
console.log(res);
toast({
title: "Transaction accepted.",
description: <p>The page is going to reload.</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 sellToUserForm = useForm<z.infer<typeof sellToUserSchema>>({
resolver: zodResolver(sellToUserSchema),
});
if (!currentWallet) {
return (
<Card>
<div className="flex items-center justify-center p-2">
<p>Loading...</p>
</div>
</Card>
);
}
return (
<Form {...sellToUserForm}>
<form onSubmit={sellToUserForm.handleSubmit(onSellToUserSubmit)} className="w-full space-y-6">
<FormField
control={sellToUserForm.control}
name="cryptoId"
render={({ field }) => (
<FormItem
onChange={(event) => {
// @ts-ignore
const maxShares = getMaxSharesForCrypto(event.target.value as string);
setSliderValue(maxShares);
setQuantity(maxShares);
// @ts-ignore
setSelectedCrypto(event.target.value as string);
}}
>
<FormLabel>Sell shares of your wallet</FormLabel>
<Select
defaultValue={defaultCryptoId}
onValueChange={(val) => {
const maxShares = getMaxSharesForCrypto(val);
setSliderValue(maxShares);
setQuantity(1);
setSelectedCrypto(val);
field.onChange(val);
}}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a crypto from your wallet." />
</SelectTrigger>
</FormControl>
<SelectContent>
{currentWallet?.map((crypto) => (<SelectItem value={crypto.Crypto.id} key={crypto.Crypto.id}>
{`${crypto.amount}x ${crypto.Crypto.name} - ${Math.round(crypto.Crypto.value)}$/u`}
</SelectItem>))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={sellToUserForm.control}
name="amount"
render={({ field }) => (
<FormItem hidden={!selectedCrypto}>
<FormLabel>Quantity</FormLabel>
<div className="flex items-center justify-between gap-2 p-2">
<p className="bg-accent text-accent-foreground p-1 rounded">{field.value}</p>
<Slider
form="amount"
onValueChange={(val) => {
const singleValue = val[0];
setQuantity(singleValue as number);
field.onChange(singleValue);
}}
value={[quantity]}
min={1}
max={sliderValue}
step={1}
/>
</div>
<FormMessage />
</FormItem>
)}
/>
{selectedCrypto && (
<div className="flex items-center justify-center p-2 gap-2">
<p>{getCryptoName(selectedCrypto)}</p>
<X />
<p>{quantity}</p>
<Equal />
<em className="bg-secondary/35 p-1 rounded">
{(quantity * getCryptoValue(selectedCrypto)).toLocaleString("en-US")}
</em>
<DollarSign />
</div>
)}
<Button type="submit" disabled={!quantity}>
Place sell order
</Button>
</form>
</Form>
);
}

View File

View File

@ -0,0 +1,50 @@
import {Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table";
import {ScrollArea} from "@/components/ui/scroll-area";
import type {IAllTrades} from "@/interfaces/crypto.interface";
import {cn} from "@/lib/utils";
interface OfferListProps {
className?: string;
trades: IAllTrades;
}
interface RenderRowsProps {
data?: IAllTrades;
}
function RenderRows({ data }: RenderRowsProps) {
if (!data || data?.length === 0) return (<TableRow>
<TableCell className="font-medium">{" "}</TableCell>
<TableCell>{" "}</TableCell>
<TableCell>{"No history..."}</TableCell>
<TableCell className="text-right">{" "}</TableCell>
</TableRow>);
return data.map((item, index) => (
<TableRow key={item.Crypto.created_at.toString()}>
<TableCell className="font-medium">{item.Giver.pseudo}</TableCell>
<TableCell>{item.Receiver.pseudo}</TableCell>
<TableCell>{item.Crypto.quantity}</TableCell>
<TableCell className="text-right">${item.Crypto.value}</TableCell>
</TableRow>
));
}
export function OfferList({ className, trades}: OfferListProps) {
return (<div className={cn("bg-card text-card-foreground rounded p-2", className)}>
<Table>
<TableCaption>A list of recent sell offers.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Seller</TableHead>
<TableHead>Buyer</TableHead>
<TableHead>Crypto share amount</TableHead>
<TableHead className="text-right">$</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<RenderRows data={trades}/>
</TableBody>
</Table>
</div>)
}

View File

@ -15,7 +15,6 @@ export interface IUserData {
dollarAvailables: number; dollarAvailables: number;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
//TODO get on register
wallet: IUserWallet; wallet: IUserWallet;
} }