aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--frontend/.eslintrc.json3
-rw-r--r--frontend/.github/workflows/ci.yml28
-rw-r--r--frontend/.gitignore176
-rw-r--r--frontend/.gitmodules3
-rw-r--r--frontend/.idea/codeStyles/Project.xml73
-rw-r--r--frontend/.idea/codeStyles/codeStyleConfig.xml5
-rw-r--r--frontend/.idea/frontend.iml12
-rw-r--r--frontend/.idea/inspectionProfiles/Project_Default.xml6
-rw-r--r--frontend/.idea/modules.xml8
-rw-r--r--frontend/.idea/vcs.xml7
-rw-r--r--frontend/.prettierrc3
-rw-r--r--frontend/README.md36
-rwxr-xr-xfrontend/bun.lockbbin0 -> 155194 bytes
-rw-r--r--frontend/next.config.mjs4
-rw-r--r--frontend/package.json38
-rw-r--r--frontend/postcss.config.mjs8
-rw-r--r--frontend/public/logo.svg14
-rw-r--r--frontend/public/next.svg1
-rw-r--r--frontend/public/vercel.svg1
m---------frontend/src/api0
-rw-r--r--frontend/src/app/api/auth/[auth0]/route.ts3
-rw-r--r--frontend/src/app/api/new-user/add-ibd-creds/route.ts29
-rw-r--r--frontend/src/app/api/new-user/add-ibd-creds/types.ts10
-rw-r--r--frontend/src/app/api/new-user/check-ibd-creds/route.ts22
-rw-r--r--frontend/src/app/api/new-user/verify-username/route.ts14
-rw-r--r--frontend/src/app/api/new-user/verify-username/types.ts11
-rw-r--r--frontend/src/app/dashboard/page.tsx34
-rw-r--r--frontend/src/app/favicon.icobin0 -> 25931 bytes
-rw-r--r--frontend/src/app/globals.css9
-rw-r--r--frontend/src/app/layout.tsx27
-rw-r--r--frontend/src/app/new-user/page.tsx29
-rw-r--r--frontend/src/app/page.tsx31
-rw-r--r--frontend/src/client/client.ts23
-rw-r--r--frontend/src/components/Layout/Layout.tsx55
-rw-r--r--frontend/src/components/NewUserForm/NewUserForm.tsx171
-rw-r--r--frontend/src/components/NewUserForm/styles.module.css13
-rw-r--r--frontend/tailwind.config.ts20
-rw-r--r--frontend/tsconfig.json26
38 files changed, 953 insertions, 0 deletions
diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json
new file mode 100644
index 0000000..bffb357
--- /dev/null
+++ b/frontend/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/frontend/.github/workflows/ci.yml b/frontend/.github/workflows/ci.yml
new file mode 100644
index 0000000..150da5f
--- /dev/null
+++ b/frontend/.github/workflows/ci.yml
@@ -0,0 +1,28 @@
+name: CI
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+
+jobs:
+ prettier:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: oven-sh/setup-bun@v2
+ - name: Install
+ run: bun install --frozen-lockfile
+ - name: Check formatting
+ run: bun run prettier-check
+
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: oven-sh/setup-bun@v2
+ - name: Install
+ run: bun install --frozen-lockfile
+ - name: Lint
+ run: bun run lint
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000..b184158
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,176 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+# Created by https://www.toptal.com/developers/gitignore/api/intellij
+# Edit at https://www.toptal.com/developers/gitignore?templates=intellij
+
+### Intellij ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# Azure Toolkit for IntelliJ plugin
+# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
+.idea/**/azureSettings.xml
+
+# End of https://www.toptal.com/developers/gitignore/api/intellij
+# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
+# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode
diff --git a/frontend/.gitmodules b/frontend/.gitmodules
new file mode 100644
index 0000000..2d6c487
--- /dev/null
+++ b/frontend/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/api"]
+ path = src/api
+ url = https://github.com/ansg191/ibd-trader-api.git
diff --git a/frontend/.idea/codeStyles/Project.xml b/frontend/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..4ae23e4
--- /dev/null
+++ b/frontend/.idea/codeStyles/Project.xml
@@ -0,0 +1,73 @@
+<component name="ProjectCodeStyleConfiguration">
+ <code_scheme name="Project" version="173">
+ <AndroidXmlCodeStyleSettings>
+ <option name="USE_CUSTOM_SETTINGS" value="true" />
+ </AndroidXmlCodeStyleSettings>
+ <HTMLCodeStyleSettings>
+ <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
+ </HTMLCodeStyleSettings>
+ <JSCodeStyleSettings version="0">
+ <option name="FORCE_SEMICOLON_STYLE" value="true" />
+ <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
+ <option name="FORCE_QUOTE_STYlE" value="true" />
+ <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
+ <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
+ <option name="SPACES_WITHIN_IMPORTS" value="true" />
+ </JSCodeStyleSettings>
+ <JetCodeStyleSettings>
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+ </JetCodeStyleSettings>
+ <TypeScriptCodeStyleSettings version="0">
+ <option name="FORCE_SEMICOLON_STYLE" value="true" />
+ <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
+ <option name="FORCE_QUOTE_STYlE" value="true" />
+ <option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
+ <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
+ <option name="SPACES_WITHIN_IMPORTS" value="true" />
+ </TypeScriptCodeStyleSettings>
+ <VueCodeStyleSettings>
+ <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
+ <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
+ </VueCodeStyleSettings>
+ <codeStyleSettings language="HCL-Terraform">
+ <indentOptions>
+ <option name="TAB_SIZE" value="2" />
+ <option name="USE_TAB_CHARACTER" value="true" />
+ <option name="SMART_TABS" value="true" />
+ </indentOptions>
+ </codeStyleSettings>
+ <codeStyleSettings language="HTML">
+ <option name="SOFT_MARGINS" value="80" />
+ <indentOptions>
+ <option name="INDENT_SIZE" value="2" />
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
+ <option name="TAB_SIZE" value="2" />
+ </indentOptions>
+ </codeStyleSettings>
+ <codeStyleSettings language="JavaScript">
+ <option name="SOFT_MARGINS" value="80" />
+ <indentOptions>
+ <option name="INDENT_SIZE" value="2" />
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
+ <option name="TAB_SIZE" value="2" />
+ </indentOptions>
+ </codeStyleSettings>
+ <codeStyleSettings language="TypeScript">
+ <option name="SOFT_MARGINS" value="80" />
+ <indentOptions>
+ <option name="INDENT_SIZE" value="2" />
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
+ <option name="TAB_SIZE" value="2" />
+ </indentOptions>
+ </codeStyleSettings>
+ <codeStyleSettings language="Vue">
+ <option name="SOFT_MARGINS" value="80" />
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="2" />
+ </indentOptions>
+ </codeStyleSettings>
+ <codeStyleSettings language="kotlin">
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+ </codeStyleSettings>
+ </code_scheme>
+</component> \ No newline at end of file
diff --git a/frontend/.idea/codeStyles/codeStyleConfig.xml b/frontend/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/frontend/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+ </state>
+</component> \ No newline at end of file
diff --git a/frontend/.idea/frontend.iml b/frontend/.idea/frontend.iml
new file mode 100644
index 0000000..24643cc
--- /dev/null
+++ b/frontend/.idea/frontend.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+ <component name="NewModuleRootManager">
+ <content url="file://$MODULE_DIR$">
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module> \ No newline at end of file
diff --git a/frontend/.idea/inspectionProfiles/Project_Default.xml b/frontend/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/frontend/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+ <profile version="1.0">
+ <option name="myName" value="Project Default" />
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+ </profile>
+</component> \ No newline at end of file
diff --git a/frontend/.idea/modules.xml b/frontend/.idea/modules.xml
new file mode 100644
index 0000000..f3d93d7
--- /dev/null
+++ b/frontend/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/frontend.iml" filepath="$PROJECT_DIR$/.idea/frontend.iml" />
+ </modules>
+ </component>
+</project> \ No newline at end of file
diff --git a/frontend/.idea/vcs.xml b/frontend/.idea/vcs.xml
new file mode 100644
index 0000000..dc5671c
--- /dev/null
+++ b/frontend/.idea/vcs.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ <mapping directory="$PROJECT_DIR$/src/api" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
new file mode 100644
index 0000000..757fd64
--- /dev/null
+++ b/frontend/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "trailingComma": "es5"
+}
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000..c403366
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/frontend/bun.lockb b/frontend/bun.lockb
new file mode 100755
index 0000000..8d89cf8
--- /dev/null
+++ b/frontend/bun.lockb
Binary files differ
diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs
new file mode 100644
index 0000000..4678774
--- /dev/null
+++ b/frontend/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..79fa2bb
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "ibd-trader-frontend",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint",
+ "generate": "make -C src/api generate-js",
+ "prettier-check": "prettier --check .",
+ "prettier-write": "prettier --write ."
+ },
+ "dependencies": {
+ "@auth0/nextjs-auth0": "^3.5.0",
+ "@bufbuild/protobuf": "^1.10.0",
+ "@connectrpc/connect-node": "^1.4.0",
+ "@fortawesome/fontawesome-svg-core": "^6.6.0",
+ "@fortawesome/free-solid-svg-icons": "^6.6.0",
+ "@fortawesome/react-fontawesome": "^0.2.2",
+ "next": "14.2.5",
+ "react": "^18",
+ "react-dom": "^18",
+ "server-only": "^0.0.1",
+ "zod": "^3.23.8"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "eslint": "^8",
+ "eslint-config-next": "14.2.5",
+ "postcss": "^8",
+ "prettier": "^3.3.3",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5"
+ }
+}
diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs
new file mode 100644
index 0000000..1a69fd2
--- /dev/null
+++ b/frontend/postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ },
+};
+
+export default config;
diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg
new file mode 100644
index 0000000..cbee702
--- /dev/null
+++ b/frontend/public/logo.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" x="0px" y="0px" viewBox="0 0 895.26 510.7" style="enable-background:new 0 0 895.26 510.7;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#004099;}
+ .st1{fill:#505050;}
+</style>
+<g>
+ <path class="st0" d="M40.17,461.15l36.23-50.95V74.07L40.17,23.13h141.28l-36.25,50.94v336.14l36.25,50.95H40.17z"></path>
+ <path class="st0" d="M358.27,460.42H210.68l34.14-47.79V70.18l-34.14-47.79h150.74c88.76,0,128.67,51.99,128.67,107.66 c0,55.15-27.84,89.29-62.5,99.27c42.54,9.97,79.83,52.51,79.83,105.03C507.43,413.14,459.12,460.42,358.27,460.42z M359.32,72.81 h-47.79v136.03h39.4c45.16,0,72.48-29.41,72.48-75.11C423.4,102.22,402.91,72.81,359.32,72.81z M369.83,261.88h-58.3v145.49h52.52 c49.36,0,76.14-29.94,76.14-70.9C440.2,293.39,412.36,261.88,369.83,261.88z"></path>
+ <path class="st0" d="M661.59,458.13H512.96l33.09-49.36V68.95L512.96,20.1h141.81c127.63,0,187.5,80.88,187.5,210.08 C842.27,378.82,779.77,458.13,661.59,458.13z M650.03,72.09H615.9v332.46h39.92c71.95,0,113.97-49.89,113.97-168.06 C769.79,130.4,732.5,72.09,650.03,72.09z"></path>
+</g>
+<g>
+ <path class="st1" d="M839.29,76.09c-15.49,0-26.13-10.75-26.13-26.83c0-15.95,10.87-26.94,26.24-26.94 c15.38,0,26.13,10.87,26.13,26.94C865.53,65.23,854.55,76.09,839.29,76.09z M839.4,25.45c-12.6,0-22.54,8.56-22.54,23.82 c0,15.15,10.06,23.7,22.43,23.7c12.6,0,22.54-8.55,22.54-23.7C861.83,34.01,851.77,25.45,839.4,25.45z M847.03,63.61l-8.91-12.72 h-2.89v12.25h-4.97V33.43h8.9c6.25,0,10.41,3.12,10.41,8.67c0,4.28-2.31,7.05-6.01,8.21l8.67,12.14L847.03,63.61z M839.17,38.05 h-3.93v8.78h3.7c3.35,0,5.44-1.38,5.44-4.39C844.37,39.56,842.53,38.05,839.17,38.05z"></path>
+</g>
+</svg> \ No newline at end of file
diff --git a/frontend/public/next.svg b/frontend/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/frontend/public/next.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg> \ No newline at end of file
diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg
new file mode 100644
index 0000000..d2f8422
--- /dev/null
+++ b/frontend/public/vercel.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg> \ No newline at end of file
diff --git a/frontend/src/api b/frontend/src/api
new file mode 160000
+Subproject 250159ea00457090d9cc7b3cc270618d14ce9a9
diff --git a/frontend/src/app/api/auth/[auth0]/route.ts b/frontend/src/app/api/auth/[auth0]/route.ts
new file mode 100644
index 0000000..3ce09af
--- /dev/null
+++ b/frontend/src/app/api/auth/[auth0]/route.ts
@@ -0,0 +1,3 @@
+import { handleAuth } from "@auth0/nextjs-auth0";
+
+export const GET = handleAuth();
diff --git a/frontend/src/app/api/new-user/add-ibd-creds/route.ts b/frontend/src/app/api/new-user/add-ibd-creds/route.ts
new file mode 100644
index 0000000..3740b90
--- /dev/null
+++ b/frontend/src/app/api/new-user/add-ibd-creds/route.ts
@@ -0,0 +1,29 @@
+import { NextRequest, NextResponse } from "next/server";
+import { createUserServiceClient } from "@/client/client";
+import { RequestBody, ResponseBody } from "./types";
+import { getSession, withApiAuthRequired } from "@auth0/nextjs-auth0";
+
+export const PUT = withApiAuthRequired(async function(req: NextRequest) {
+ const res = new NextResponse();
+ const session = await getSession(req, res);
+ if (!session || !session.user["sub"]) {
+ return res;
+ }
+
+ const { username, password } = RequestBody.parse(await req.json());
+
+ const client = createUserServiceClient();
+ await client.updateUser({
+ user: {
+ subject: session.user.sub,
+ ibdUsername: username,
+ ibdPassword: password,
+ },
+ updateMask: {
+ paths: ["ibd_username", "ibd_password"],
+ },
+ });
+
+ const ret: ResponseBody = {};
+ return NextResponse.json(ret, { status: 200 });
+});
diff --git a/frontend/src/app/api/new-user/add-ibd-creds/types.ts b/frontend/src/app/api/new-user/add-ibd-creds/types.ts
new file mode 100644
index 0000000..19b25fc
--- /dev/null
+++ b/frontend/src/app/api/new-user/add-ibd-creds/types.ts
@@ -0,0 +1,10 @@
+import { z } from "zod";
+
+export const RequestBody = z.object({
+ username: z.string(),
+ password: z.string(),
+});
+export const ResponseBody = z.object({});
+
+export type RequestBody = z.infer<typeof RequestBody>;
+export type ResponseBody = z.infer<typeof ResponseBody>;
diff --git a/frontend/src/app/api/new-user/check-ibd-creds/route.ts b/frontend/src/app/api/new-user/check-ibd-creds/route.ts
new file mode 100644
index 0000000..837f13a
--- /dev/null
+++ b/frontend/src/app/api/new-user/check-ibd-creds/route.ts
@@ -0,0 +1,22 @@
+import { NextRequest, NextResponse } from "next/server";
+import { createUserServiceClient } from "@/client/client";
+import { getSession, withApiAuthRequired } from "@auth0/nextjs-auth0";
+
+export const PUT = withApiAuthRequired(async function(req: NextRequest) {
+ const res = new NextResponse();
+ const session = await getSession(req, res);
+ if (!session || !session.user["sub"]) {
+ return res;
+ }
+
+ const client = createUserServiceClient();
+ const { authenticated } = await client.authenticateUser({
+ subject: session.user["sub"],
+ });
+
+ if (authenticated) {
+ return new Response(null, { status: 200 });
+ } else {
+ return new Response(null, { status: 401 });
+ }
+});
diff --git a/frontend/src/app/api/new-user/verify-username/route.ts b/frontend/src/app/api/new-user/verify-username/route.ts
new file mode 100644
index 0000000..b60c76e
--- /dev/null
+++ b/frontend/src/app/api/new-user/verify-username/route.ts
@@ -0,0 +1,14 @@
+import { NextRequest, NextResponse } from "next/server";
+import { createUserServiceClient } from "@/client/client";
+import { RequestBody, ResponseBody } from "@/app/api/new-user/verify-username/types";
+import { withApiAuthRequired } from "@auth0/nextjs-auth0";
+
+export const POST = withApiAuthRequired(async function(request: NextRequest) {
+ const { username } = RequestBody.parse(await request.json());
+
+ const client = createUserServiceClient();
+ const { exists } = await client.checkIBDUsername({ ibdUsername: username });
+
+ const ret: ResponseBody = { exists };
+ return NextResponse.json(ret, { status: 200 });
+});
diff --git a/frontend/src/app/api/new-user/verify-username/types.ts b/frontend/src/app/api/new-user/verify-username/types.ts
new file mode 100644
index 0000000..5063da4
--- /dev/null
+++ b/frontend/src/app/api/new-user/verify-username/types.ts
@@ -0,0 +1,11 @@
+import { z } from "zod";
+
+export const RequestBody = z.object({
+ username: z.string(),
+});
+export const ResponseBody = z.object({
+ exists: z.boolean(),
+});
+
+export type RequestBody = z.infer<typeof RequestBody>;
+export type ResponseBody = z.infer<typeof ResponseBody>;
diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx
new file mode 100644
index 0000000..289dcab
--- /dev/null
+++ b/frontend/src/app/dashboard/page.tsx
@@ -0,0 +1,34 @@
+import { Metadata } from "next";
+import { createUserServiceClient } from "@/client/client";
+import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0";
+import Layout from "@/components/Layout/Layout";
+import { redirect, RedirectType } from "next/navigation";
+import { red } from "next/dist/lib/picocolors";
+
+export const metadata: Metadata = {
+ title: "IBD Trader Dashboard",
+};
+
+export default withPageAuthRequired(async function Dashboard() {
+ const client = createUserServiceClient();
+
+ const user = await getSession();
+ if (!user) {
+ redirect("/api/auth/login", RedirectType.replace);
+ }
+
+ const dbUser = await client.createUser({
+ subject: user.user["sub"],
+ });
+
+ // Check if user has IBD credentials
+ if (!dbUser.user || !dbUser.user.ibdUsername) {
+ redirect("/new-user", RedirectType.replace);
+ }
+
+ return (
+ <Layout>
+ <h1>Dashboard</h1>
+ </Layout>
+ );
+}, { returnTo: "/dashboard" });
diff --git a/frontend/src/app/favicon.ico b/frontend/src/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
--- /dev/null
+++ b/frontend/src/app/favicon.ico
Binary files differ
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
new file mode 100644
index 0000000..39a9271
--- /dev/null
+++ b/frontend/src/app/globals.css
@@ -0,0 +1,9 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer utilities {
+ .text-balance {
+ text-wrap: balance;
+ }
+}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
new file mode 100644
index 0000000..bb15d68
--- /dev/null
+++ b/frontend/src/app/layout.tsx
@@ -0,0 +1,27 @@
+import type { Metadata } from "next";
+import { Inter } from "next/font/google";
+import "./globals.css";
+import { UserProvider } from "@auth0/nextjs-auth0/client";
+import { config } from "@fortawesome/fontawesome-svg-core";
+import "@fortawesome/fontawesome-svg-core/styles.css";
+
+config.autoAddCss = false;
+
+const inter = Inter({ subsets: ["latin"] });
+
+export const metadata: Metadata = {
+ title: "Create Next App",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+ }: Readonly<{ children: React.ReactNode }>) {
+ return (
+ <html lang="en">
+ <UserProvider>
+ <body className={inter.className}>{children}</body>
+ </UserProvider>
+ </html>
+ );
+}
diff --git a/frontend/src/app/new-user/page.tsx b/frontend/src/app/new-user/page.tsx
new file mode 100644
index 0000000..57fcf82
--- /dev/null
+++ b/frontend/src/app/new-user/page.tsx
@@ -0,0 +1,29 @@
+import { Metadata } from "next";
+import { getSession, withPageAuthRequired } from "@auth0/nextjs-auth0";
+import { redirect, RedirectType } from "next/navigation";
+import { createUserServiceClient } from "@/client/client";
+import NewUserForm from "@/components/NewUserForm/NewUserForm";
+
+export const metadata: Metadata = {
+ title: "New User",
+};
+
+export default withPageAuthRequired(async function NewUser() {
+ const session = await getSession();
+ if (!session) {
+ redirect("/api/auth/login", RedirectType.replace);
+ }
+
+ const client = createUserServiceClient();
+ const { user } = await client.getUser({ subject: session.user["sub"] });
+ if (!user) {
+ throw new Error("User not found");
+ }
+
+ if (user.ibdUsername) {
+ // User already has IBD credentials
+ redirect("/dashboard", RedirectType.replace);
+ }
+
+ return <NewUserForm />;
+});
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
new file mode 100644
index 0000000..13777a9
--- /dev/null
+++ b/frontend/src/app/page.tsx
@@ -0,0 +1,31 @@
+import Image from "next/image";
+import { Metadata } from "next";
+import Link from "next/link";
+import { getSession } from "@auth0/nextjs-auth0";
+import { redirect } from "next/navigation";
+
+export const metadata: Metadata = {
+ title: "IBD Trader",
+};
+
+export default async function Home() {
+ const user = await getSession();
+
+ if (user) {
+ redirect("/dashboard");
+ }
+
+ return (
+ <div className="min-h-screen flex flex-col items-center justify-center bg-gray-100">
+ <main className="flex flex-col items-center justify-center w-full flex-1 px-20 text-center">
+ <div className="flex justify-center">
+ <Image src="/logo.svg" alt="IBD Trader" width={200} height={200} />
+ </div>
+ <h1 className="text-6xl font-bold mt-4">Trader</h1>
+ <button className="mt-16 px-6 py-3 bg-blue-500 text-white rounded-md hover:bg-blue-600">
+ <Link href="/api/auth/login">Login</Link>
+ </button>
+ </main>
+ </div>
+ );
+}
diff --git a/frontend/src/client/client.ts b/frontend/src/client/client.ts
new file mode 100644
index 0000000..7a92917
--- /dev/null
+++ b/frontend/src/client/client.ts
@@ -0,0 +1,23 @@
+import "server-only";
+import { createGrpcTransport } from "@connectrpc/connect-node";
+import { createPromiseClient, PromiseClient } from "@connectrpc/connect";
+import { UserService } from "@/api/gen/idb/user/v1/user_connect";
+import { StockService } from "@/api/gen/idb/stock/v1/stock_connect";
+
+const baseUrl = process.env.BACKEND_URL || "http://localhost:8000";
+const transport = createGrpcTransport({
+ baseUrl,
+ httpVersion: "2",
+});
+
+export type UserServiceClient = PromiseClient<typeof UserService>;
+
+export function createUserServiceClient(): UserServiceClient {
+ return createPromiseClient(UserService, transport);
+}
+
+export type StockServiceClient = PromiseClient<typeof StockService>
+
+export function createStockServiceClient(): StockServiceClient {
+ return createPromiseClient(StockService, transport);
+}
diff --git a/frontend/src/components/Layout/Layout.tsx b/frontend/src/components/Layout/Layout.tsx
new file mode 100644
index 0000000..6617038
--- /dev/null
+++ b/frontend/src/components/Layout/Layout.tsx
@@ -0,0 +1,55 @@
+"use client";
+
+import React, { useState } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+
+const paths = [
+ { href: "/dashboard", label: "Home" },
+ { href: "/dashboard/positions", label: "Positions" },
+];
+
+export type LayoutProps = Readonly<{
+ children: React.ReactNode;
+}>;
+
+export default function Layout({ children }: LayoutProps) {
+ const [isOpen, setIsOpen] = useState(true);
+ const path = usePathname();
+
+ return (
+ <div className="flex h-screen bg-gray-100">
+ {/* Sidebar */}
+ <div
+ className={`relative text-black space-y-6 py-7 px-2 transition-all duration-500 ease-in-out ${isOpen ? "w-64" : "w-12 overflow-hidden"}`}>
+ {/* Sidebar content */}
+ <nav className={isOpen ? "" : "hidden"}>
+ {paths.map(({ href, label }) => (
+ <Link key={href} href={href}
+ className={`block py-2.5 px-4 mb-2 rounded transition duration-200 ${href == path ? "bg-blue-200" : "hover:bg-blue-200"}`}>
+ {label}
+ </Link>
+ ))}
+ </nav>
+ {/* Toggle button */}
+ <div className="absolute bottom-2 right-0 p-2">
+ <button onClick={() => setIsOpen(!isOpen)}
+ className="text-gray-700 focus:outline-none transition-transform duration-500 transform"
+ style={{ transform: isOpen ? "rotate(180deg)" : "rotate(0)" }}
+ >
+ <FontAwesomeIcon icon={faArrowRight} size="2x" />
+ </button>
+ </div>
+ </div>
+
+ {/* Main content */}
+ <div className="flex-1 flex flex-col overflow-hidden">
+ <main className="flex-1 overflow-y-auto p-4">
+ {children}
+ </main>
+ </div>
+ </div>
+ );
+}
diff --git a/frontend/src/components/NewUserForm/NewUserForm.tsx b/frontend/src/components/NewUserForm/NewUserForm.tsx
new file mode 100644
index 0000000..779a49f
--- /dev/null
+++ b/frontend/src/components/NewUserForm/NewUserForm.tsx
@@ -0,0 +1,171 @@
+"use client";
+
+import { User } from "@/api/gen/idb/user/v1/user_pb";
+import { ChangeEventHandler, FormEventHandler, MouseEventHandler, useEffect, useState } from "react";
+import {
+ RequestBody as VerifyRequestBody,
+ ResponseBody as VerifyResponseBody,
+} from "@/app/api/new-user/verify-username/types";
+import {
+ RequestBody as SubmitRequestBody,
+ ResponseBody as SubmitResponseBody,
+} from "@/app/api/new-user/add-ibd-creds/types";
+
+import styles from "./styles.module.css";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faSpinner } from "@fortawesome/free-solid-svg-icons";
+
+export default function NewUserForm() {
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+ const [isUsernameValid, setIsUsernameValid] = useState(false);
+ const [isSubmittingUsername, setIsSubmittingUsername] = useState(false);
+ const [isSubmittingForm, setIsSubmittingForm] = useState(false);
+ const [isCheckingCreds, setIsCheckingCreds] = useState(false);
+ const [error, setError] = useState<string | null>(null);
+
+ const handleUsernameSubmit: FormEventHandler<HTMLFormElement> = (e) => {
+ e.preventDefault();
+ setIsSubmittingUsername(true);
+ setError(null);
+
+ const body: VerifyRequestBody = { username };
+ fetch("/api/new-user/verify-username", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ }).then((res) => res.json())
+ .then((data) => {
+ const { exists } = VerifyResponseBody.parse(data);
+ if (exists) {
+ setIsUsernameValid(true);
+ } else {
+ setError("Username does not exist");
+ }
+ setIsSubmittingUsername(false);
+ });
+ };
+ const handleFormSubmit: FormEventHandler<HTMLFormElement> = (e) => {
+ e.preventDefault();
+ setIsSubmittingForm(true);
+ setError(null);
+
+ const body: SubmitRequestBody = { username, password };
+ fetch("/api/new-user/add-ibd-creds", {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(body),
+ }).then((res) => res.json())
+ .then((data) => {
+ const {} = SubmitResponseBody.parse(data);
+ setIsSubmittingForm(false);
+ setIsCheckingCreds(true);
+
+ return fetch("/api/new-user/check-ibd-creds", {
+ method: "PUT",
+ });
+ })
+ .then((res) => {
+ if (res.status === 200) {
+ window.location.href = "/dashboard";
+ } else {
+ setError("Invalid credentials");
+ setIsCheckingCreds(false);
+ }
+ });
+ };
+
+ const handleBack: MouseEventHandler<HTMLButtonElement> = () => {
+ setIsUsernameValid(false);
+ setPassword("");
+ setError(null);
+ };
+
+ return (
+ <div className="flex items-center justify-center min-h-screen bg-gray-100">
+ <div className="w-full max-w-md p-8 space-y-4 bg-white rounded-lg shadow-lg">
+ <h1 className="text-2xl font-bold text-center">Set Up Your Account</h1>
+
+ <form onSubmit={isUsernameValid ? handleFormSubmit : handleUsernameSubmit}>
+ <div className="mb-4">
+ <label htmlFor="username" className="block text-sm font-medium text-gray-700">
+ Username
+ </label>
+ <input
+ type="text"
+ id="username"
+ value={username}
+ onChange={(e) => setUsername(e.target.value)}
+ className={`w-full px-3 py-2 mt-1 border ${
+ isUsernameValid ? "border-gray-300 bg-gray-200" : "border-gray-300"
+ } rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500`}
+ required
+ disabled={isUsernameValid}
+ />
+ {error && !isUsernameValid && (
+ <p className="mt-2 text-sm text-red-600">{error}</p>
+ )}
+ </div>
+ {!isUsernameValid && (
+ <div className="flex justify-center">
+ <button
+ type="submit"
+ className="px-4 py-2 text-white bg-indigo-600 rounded-md shadow-sm hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
+ disabled={isSubmittingUsername}
+ >
+ {isSubmittingUsername ? <FontAwesomeIcon icon={faSpinner} spin /> : "Next"}
+ </button>
+ </div>
+ )}
+ </form>
+
+ {isUsernameValid && (
+ <form onSubmit={handleFormSubmit}>
+ <div className={styles.passwordForm}>
+ <div className="mb-4">
+ <label htmlFor="password" className="block text-sm font-medium text-gray-700">
+ Password
+ </label>
+ <input
+ type="password"
+ id="password"
+ value={password}
+ onChange={(e) => setPassword(e.target.value)}
+ className="w-full px-3 py-2 mt-1 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
+ required
+ />
+ </div>
+ {error && isUsernameValid && (
+ <p className="mt-2 text-sm text-red-600">{error}</p>
+ )}
+ <div className="flex justify-between">
+ <button
+ type="button"
+ onClick={handleBack}
+ className="px-4 py-2 text-white bg-gray-600 rounded-md shadow-sm hover:bg-gray-700 focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
+ >
+ Back
+ </button>
+ <button
+ type="submit"
+ className="px-4 py-2 text-white bg-indigo-600 rounded-md shadow-sm hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
+ disabled={isSubmittingForm}
+ >
+ {isSubmittingForm ? (
+ <p><FontAwesomeIcon icon={faSpinner} spin /> Setting Credentials...</p>
+ ) : isCheckingCreds ? (
+ <p><FontAwesomeIcon icon={faSpinner} spin /> Checking Credentials...</p>
+ ) : "Submit"}
+ </button>
+ </div>
+ </div>
+ </form>
+ )}
+ </div>
+ </div>
+ );
+}
diff --git a/frontend/src/components/NewUserForm/styles.module.css b/frontend/src/components/NewUserForm/styles.module.css
new file mode 100644
index 0000000..a2ce597
--- /dev/null
+++ b/frontend/src/components/NewUserForm/styles.module.css
@@ -0,0 +1,13 @@
+@keyframes smooth-appear {
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.passwordForm {
+ margin-top: 1rem;
+ transform: translateY(-5rem);
+ opacity: 0;
+ animation: smooth-appear 1s ease forwards;
+}
diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts
new file mode 100644
index 0000000..e9a0944
--- /dev/null
+++ b/frontend/tailwind.config.ts
@@ -0,0 +1,20 @@
+import type { Config } from "tailwindcss";
+
+const config: Config = {
+ content: [
+ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
+ ],
+ theme: {
+ extend: {
+ backgroundImage: {
+ "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
+ "gradient-conic":
+ "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
+ },
+ },
+ },
+ plugins: [],
+};
+export default config;
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..7b28589
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}