diff --git a/components/account/AccountActions.tsx b/components/account/AccountActions.tsx
index 79a7053c..e180701a 100644
--- a/components/account/AccountActions.tsx
+++ b/components/account/AccountActions.tsx
@@ -30,13 +30,18 @@ const AccountActions = () => {
return (
<>
- setShowDepositModal(true)}>
+ setShowDepositModal(true)}
+ size="large"
+ >
{t('deposit')}
setShowWithdrawModal(true)}
secondary
+ size="large"
>
{t('withdraw')}
diff --git a/components/forms/Input.tsx b/components/forms/Input.tsx
index 20e7b9ee..dc6573f6 100644
--- a/components/forms/Input.tsx
+++ b/components/forms/Input.tsx
@@ -1,4 +1,4 @@
-import { ChangeEvent, forwardRef, ReactNode } from 'react'
+import { ChangeEvent, forwardRef } from 'react'
interface InputProps {
type: string
@@ -34,14 +34,14 @@ const Input = forwardRef((props, ref) => {
) : null}
((props, ref) => {
})
export default Input
-
-interface LabelProps {
- children: ReactNode
- className?: string
-}
-
-export const Label = ({ children, className }: LabelProps) => (
-
- {children}
-
-)
diff --git a/components/forms/Label.tsx b/components/forms/Label.tsx
new file mode 100644
index 00000000..bd4c9017
--- /dev/null
+++ b/components/forms/Label.tsx
@@ -0,0 +1,5 @@
+const Label = ({ text }: { text: string }) => (
+ {text}
+)
+
+export default Label
diff --git a/components/forms/Select.tsx b/components/forms/Select.tsx
new file mode 100644
index 00000000..8b691925
--- /dev/null
+++ b/components/forms/Select.tsx
@@ -0,0 +1,86 @@
+import { Listbox } from '@headlessui/react'
+import { ChevronDownIcon } from '@heroicons/react/solid'
+import { ReactNode } from 'react'
+
+interface SelectProps {
+ value: string | ReactNode
+ onChange: (x: string) => void
+ children: ReactNode
+ className?: string
+ dropdownPanelClassName?: string
+ placeholder?: string
+ disabled?: boolean
+}
+
+const Select = ({
+ value,
+ onChange,
+ children,
+ className,
+ dropdownPanelClassName,
+ placeholder = 'Select',
+ disabled = false,
+}: SelectProps) => {
+ return (
+
+
+ {({ open }) => (
+ <>
+
+
+ {value ? (
+ value
+ ) : (
+ {placeholder}
+ )}
+
+
+
+ {open ? (
+
+ {children}
+
+ ) : null}
+ >
+ )}
+
+
+ )
+}
+
+interface OptionProps {
+ value: string
+ children: string | ReactNode
+ className?: string
+}
+
+const Option = ({ value, children, className }: OptionProps) => {
+ return (
+
+ {({ selected }) => (
+
+ {children}
+
+ )}
+
+ )
+}
+
+Select.Option = Option
+
+export default Select
diff --git a/components/modals/DepositModal.tsx b/components/modals/DepositModal.tsx
index b6a1e08d..d5589568 100644
--- a/components/modals/DepositModal.tsx
+++ b/components/modals/DepositModal.tsx
@@ -1,17 +1,13 @@
import React, { useState } from 'react'
import mangoStore from '../../store/state'
+import { ModalProps } from '../../types/modal'
import { notify } from '../../utils/notifications'
import Button from '../shared/Button'
import Loading from '../shared/Loading'
import Modal from '../shared/Modal'
-type DepositModalProps = {
- isOpen: boolean
- onClose: () => void
-}
-
-function DepositModal({ isOpen, onClose }: DepositModalProps) {
+function DepositModal({ isOpen, onClose }: ModalProps) {
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState('USDC')
diff --git a/components/modals/IntroModal.tsx b/components/modals/IntroModal.tsx
new file mode 100644
index 00000000..696b0162
--- /dev/null
+++ b/components/modals/IntroModal.tsx
@@ -0,0 +1,12 @@
+import { ModalProps } from '../../types/modal'
+import Modal from '../shared/Modal'
+
+const IntroModal = ({ isOpen, onClose }: ModalProps) => {
+ return (
+
+ Hi
+
+ )
+}
+
+export default IntroModal
diff --git a/components/modals/UserSetupModal.tsx b/components/modals/UserSetupModal.tsx
new file mode 100644
index 00000000..83c74708
--- /dev/null
+++ b/components/modals/UserSetupModal.tsx
@@ -0,0 +1,127 @@
+import { Transition } from '@headlessui/react'
+import { useTranslation } from 'next-i18next'
+import { ChangeEvent, useState } from 'react'
+import { ModalProps } from '../../types/modal'
+import { PROFILE_CATEGORIES } from '../../utils/profile'
+import Input from '../forms/Input'
+import Label from '../forms/Label'
+import Select from '../forms/Select'
+import Button, { LinkButton } from '../shared/Button'
+import InlineNotification from '../shared/InlineNotification'
+import Modal from '../shared/Modal'
+import useLocalStorageState from '../../hooks/useLocalStorageState'
+
+export const SKIP_ACCOUNT_SETUP_KEY = 'skipAccountSetup'
+
+const UserSetupModal = ({ isOpen, onClose }: ModalProps) => {
+ const { t } = useTranslation()
+ const [profileName, setProfileName] = useState('')
+ const [accountName, setAccountName] = useState('')
+ const [profileCategory, setProfileCategory] = useState('')
+ const [showAccountSetup, setShowAccountSetup] = useState(false)
+ const [, setSkipAccountSetup] = useLocalStorageState(SKIP_ACCOUNT_SETUP_KEY)
+
+ const handleSaveProfile = () => {
+ // save profile details to db...
+
+ setShowAccountSetup(true)
+ }
+
+ const handleUserSetup = () => {
+ // create account
+ }
+
+ const handleSkipAccountSetup = () => {
+ setSkipAccountSetup(true)
+ onClose()
+ }
+
+ return (
+
+ <>
+
+
Create Profile
+
+ This is your Mango Identity and is used on our leaderboard and chat
+
+
+
+
+ ) =>
+ setProfileName(e.target.value)
+ }
+ />
+
+
+
+ setProfileCategory(cat)}
+ className="w-full"
+ >
+ {PROFILE_CATEGORIES.map((cat) => (
+
+ {cat}
+
+ ))}
+
+
+
+
+ Save Profile
+
+ setShowAccountSetup(true)}>
+ I'll do this later
+
+
+ >
+
+
+
+
+
Account Setup
+
You need a Mango Account to get started
+
+
+ {/* Not sure if we need to name the first account or if every users first account should have the same name "Main Account" or something similar */}
+
+ ) =>
+ setAccountName(e.target.value)
+ }
+ />
+
+
+
+
+
+ Let's Go
+
+ handleSkipAccountSetup()}>
+ I'll do this later
+
+
+
+
+
+ )
+}
+
+export default UserSetupModal
diff --git a/components/modals/WithdrawModal.tsx b/components/modals/WithdrawModal.tsx
index a401d789..70768df0 100644
--- a/components/modals/WithdrawModal.tsx
+++ b/components/modals/WithdrawModal.tsx
@@ -1,17 +1,13 @@
import { useState } from 'react'
import mangoStore from '../../store/state'
+import { ModalProps } from '../../types/modal'
import { notify } from '../../utils/notifications'
import Button from '../shared/Button'
import Loading from '../shared/Loading'
import Modal from '../shared/Modal'
-type WithdrawModalProps = {
- isOpen: boolean
- onClose: () => void
-}
-
-function WithdrawModal({ isOpen, onClose }: WithdrawModalProps) {
+function WithdrawModal({ isOpen, onClose }: ModalProps) {
const [inputAmount, setInputAmount] = useState('')
const [submitting, setSubmitting] = useState(false)
const [selectedToken, setSelectedToken] = useState('USDC')
diff --git a/components/shared/Button.tsx b/components/shared/Button.tsx
index 2edb0acc..f6eca3f3 100644
--- a/components/shared/Button.tsx
+++ b/components/shared/Button.tsx
@@ -1,6 +1,6 @@
import { FunctionComponent, ReactNode } from 'react'
-interface ButtonProps {
+interface AllButtonProps {
onClick?: (e?: React.MouseEvent) => void
disabled?: boolean
className?: string
@@ -8,21 +8,34 @@ interface ButtonProps {
children?: ReactNode
}
-const Button: FunctionComponent = ({
+interface ButtonProps {
+ size?: 'large' | 'medium' | 'small'
+}
+
+type ButtonCombinedProps = AllButtonProps & ButtonProps
+
+const Button: FunctionComponent = ({
children,
onClick,
disabled = false,
className,
secondary,
+ size = 'medium',
...props
}) => {
return (
@@ -35,7 +48,7 @@ interface IconButtonProps {
hideBg?: boolean
}
-type IconButtonCombinedProps = ButtonProps & IconButtonProps
+type IconButtonCombinedProps = AllButtonProps & IconButtonProps
export const IconButton: FunctionComponent = ({
children,
@@ -60,7 +73,7 @@ export const IconButton: FunctionComponent = ({
)
}
-export const LinkButton: FunctionComponent = ({
+export const LinkButton: FunctionComponent = ({
children,
onClick,
disabled = false,
diff --git a/components/shared/InlineNotification.tsx b/components/shared/InlineNotification.tsx
new file mode 100644
index 00000000..655c0e02
--- /dev/null
+++ b/components/shared/InlineNotification.tsx
@@ -0,0 +1,56 @@
+import { FunctionComponent } from 'react'
+import {
+ CheckCircleIcon,
+ ExclamationCircleIcon,
+ ExclamationIcon,
+ InformationCircleIcon,
+} from '@heroicons/react/solid'
+
+interface InlineNotificationProps {
+ desc?: string
+ title?: string
+ type: 'error' | 'info' | 'success' | 'warning'
+}
+
+const InlineNotification: FunctionComponent = ({
+ desc,
+ title,
+ type,
+}) => (
+
+ {type === 'error' ? (
+
+ ) : null}
+ {type === 'success' ? (
+
+ ) : null}
+ {type === 'warning' ? (
+
+ ) : null}
+ {type === 'info' ? (
+
+ ) : null}
+
+
+)
+
+export default InlineNotification
diff --git a/components/shared/Modal.tsx b/components/shared/Modal.tsx
index ec82f7a3..ec804ed9 100644
--- a/components/shared/Modal.tsx
+++ b/components/shared/Modal.tsx
@@ -1,26 +1,41 @@
-import { useState, useRef } from 'react'
import { Dialog } from '@headlessui/react'
+import { XIcon } from '@heroicons/react/solid'
type ModalProps = {
title?: string
children: React.ReactNode
isOpen: boolean
- onClose: (x: boolean) => void
+ onClose: () => void
+ hideClose?: boolean
}
-function Modal({ title = '', children, isOpen, onClose }: ModalProps) {
+function Modal({
+ title = '',
+ children,
+ isOpen,
+ onClose,
+ hideClose,
+}: ModalProps) {
return (
-
+
-
+
+ {!hideClose ? (
+
+
+
+ ) : null}
{title}
{children}
diff --git a/components/swap/JupiterRoutes.tsx b/components/swap/JupiterRoutes.tsx
index 50650817..c34590d9 100644
--- a/components/swap/JupiterRoutes.tsx
+++ b/components/swap/JupiterRoutes.tsx
@@ -110,10 +110,7 @@ const JupiterRoutes = ({
) : null}
>
-
+
{submitting ? : null}
{t('trade:confirm-trade')}
diff --git a/components/swap/RouteFeeInfo.tsx b/components/swap/RouteFeeInfo.tsx
index df10780f..1335cb00 100644
--- a/components/swap/RouteFeeInfo.tsx
+++ b/components/swap/RouteFeeInfo.tsx
@@ -49,7 +49,7 @@ const RouteFeeInfo = ({
{t('trade:review-trade')}
- */}
{amountOut && amountIn ? (
{t('trade:rate')}
diff --git a/components/swap/Swap.tsx b/components/swap/Swap.tsx
index bc5cc8aa..94862442 100644
--- a/components/swap/Swap.tsx
+++ b/components/swap/Swap.tsx
@@ -343,10 +343,11 @@ const Swap = () => {
) : null}
setShowConfirm(true)}
- className="mt-6 flex w-full justify-center py-3 text-lg"
+ className="mt-6 w-full text-base"
disabled={
!connected || !routes?.length || !selectedRoute || !outputTokenInfo
}
+ size="large"
>
{connected ? (
isLoadingTradeDetails ? (
diff --git a/components/swap/SwapTokenChart.tsx b/components/swap/SwapTokenChart.tsx
index d50fe197..47a62eaa 100644
--- a/components/swap/SwapTokenChart.tsx
+++ b/components/swap/SwapTokenChart.tsx
@@ -229,7 +229,7 @@ const SwapTokenChart: FunctionComponent = ({
setDaysToShow(1)}
@@ -237,7 +237,7 @@ const SwapTokenChart: FunctionComponent = ({
24H
setDaysToShow(7)}
@@ -245,7 +245,7 @@ const SwapTokenChart: FunctionComponent = ({
7D
setDaysToShow(30)}
diff --git a/pages/index.tsx b/pages/index.tsx
index 7a11cfe5..81fb53a3 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,13 +1,19 @@
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
+import { useWallet } from '@solana/wallet-adapter-react'
import type { NextPage } from 'next'
import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
-import { useState } from 'react'
+import { useEffect, useState } from 'react'
import AccountActions from '../components/account/AccountActions'
import DepositModal from '../components/modals/DepositModal'
+import UserSetupModal, {
+ SKIP_ACCOUNT_SETUP_KEY,
+} from '../components/modals/UserSetupModal'
import WithdrawModal from '../components/modals/WithdrawModal'
+import TokenList from '../components/TokenList'
import mangoStore from '../store/state'
import { formatDecimal } from '../utils/numbers'
+import useLocalStorageState from '../hooks/useLocalStorageState'
export async function getStaticProps({ locale }: { locale: string }) {
return {
@@ -19,13 +25,22 @@ export async function getStaticProps({ locale }: { locale: string }) {
const Index: NextPage = () => {
const { t } = useTranslation('common')
+ const { connected } = useWallet()
const mangoAccount = mangoStore((s) => s.mangoAccount)
const [showDepositModal, setShowDepositModal] = useState(false)
const [showWithdrawModal, setShowWithdrawModal] = useState(false)
+ const [showFirstAccountModal, setShowFirstAccountModal] = useState(false)
+ const [skipAccountSetup] = useLocalStorageState(SKIP_ACCOUNT_SETUP_KEY)
+
+ useEffect(() => {
+ if (connected && !mangoAccount && !skipAccountSetup) {
+ setShowFirstAccountModal(true)
+ }
+ }, [connected])
return (
<>
-
+
{t('account-value')}
@@ -40,6 +55,7 @@ const Index: NextPage = () => {
+
{showDepositModal ? (
{
onClose={() => setShowWithdrawModal(false)}
/>
) : null}
+ {showFirstAccountModal ? (
+ setShowFirstAccountModal(false)}
+ />
+ ) : null}
>
)
}
diff --git a/public/locales/en/common.json b/public/locales/en/common.json
index 4bc88778..aa81c95e 100644
--- a/public/locales/en/common.json
+++ b/public/locales/en/common.json
@@ -190,7 +190,7 @@
"initial-deposit": "Initial Deposit",
"insufficient-balance-deposit": "Insufficient balance. Reduce the amount to deposit",
"insufficient-balance-withdraw": "Insufficient balance. Borrow funds to withdraw",
- "insufficient-sol": "The Solana blockchain requires 0.035 SOL to create a Mango Account. This covers rent for your account and will be refunded if you close your account.",
+ "insufficient-sol": "The Solana blockchain requires 0.035 SOL to create a Mango Account. It will be refunded if you close your account.",
"interest": "Interest",
"interest-chart-title": "{{symbol}} Interest – Last 30 days (current bar is delayed)",
"interest-chart-value-title": "{{symbol}} Interest Value – Last 30 days (current bar is delayed)",
diff --git a/styles/globals.css b/styles/globals.css
index eb6e3e42..8e5d0fba 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -160,23 +160,23 @@ svg {
h1,
h2,
h3 {
- @apply font-bold;
+ @apply font-bold text-th-fgd-1;
}
h1 {
- @apply text-2xl;
+ @apply text-3xl;
}
h2 {
- @apply text-xl;
+ @apply text-2xl;
}
h3 {
- @apply text-lg;
+ @apply text-xl;
}
p {
- @apply text-th-fgd-2;
+ @apply text-base text-th-fgd-3;
}
/* Slider */
@@ -253,3 +253,28 @@ table td {
table th {
@apply text-xs font-normal text-th-fgd-3;
}
+
+/* Scrollbars */
+
+.thin-scroll::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+.thin-scroll::-webkit-scrollbar-thumb {
+ @apply rounded bg-th-bkg-4;
+ border: 2px solid transparent;
+ background-clip: padding-box;
+}
+
+.thin-scroll::-webkit-scrollbar-thumb:hover {
+ border: 0;
+}
+
+.thin-scroll::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+.thin-scroll::-webkit-scrollbar-thumb:window-inactive {
+ @apply bg-th-bkg-4;
+}
diff --git a/types/modal.ts b/types/modal.ts
new file mode 100644
index 00000000..c7226170
--- /dev/null
+++ b/types/modal.ts
@@ -0,0 +1,4 @@
+export interface ModalProps {
+ isOpen: boolean
+ onClose: () => void
+}
diff --git a/utils/profile.ts b/utils/profile.ts
new file mode 100644
index 00000000..b13792a9
--- /dev/null
+++ b/utils/profile.ts
@@ -0,0 +1,11 @@
+export const PROFILE_CATEGORIES = [
+ 'borrower',
+ 'day-trader',
+ 'degen',
+ 'discretionary',
+ 'loan-shark',
+ 'market-maker',
+ 'swing-trader',
+ 'trader',
+ 'yolo',
+]