Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions components/Account/ChangeFiatCurrency.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { ReactElement, useState } from 'react'
import style from './account.module.css'
import { SUPPORTED_QUOTES, SUPPORTED_QUOTES_FROM_ID, SupportedQuotesType, QUOTE_IDS } from 'constants/index'

interface IProps {
preferredCurrencyId: number
}

export default function ChangeFiatCurrency ({ preferredCurrencyId }: IProps): ReactElement {
const preferredCurrency = SUPPORTED_QUOTES_FROM_ID[preferredCurrencyId]
const [currency, setCurrency] = useState(preferredCurrency)
const [error, setError] = useState('')
const [success, setSuccess] = useState('')

const onChangeCurrency = async (thisCurrency: string): Promise<void> => {
const oldCurrency = currency
const currencyId = QUOTE_IDS[thisCurrency.toUpperCase()]
setCurrency(SUPPORTED_QUOTES_FROM_ID[currencyId])
try {
const res = await fetch('/api/user', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ currencyId })
})
if (res.status === 200) {
setError('')
setSuccess('Updated currency successfully.')
}
} catch (err: any) {
setSuccess('')
setError(err.response.data.message)
setCurrency(oldCurrency)
} finally {
setTimeout(() => {
setSuccess('')
setError('')
}, 3000)
}
}

return (<>
<div className={style.changeCurrency_ctn}>
<select
id='currency'
required
value={currency}
onChange={(e) => { void onChangeCurrency(e.target.value) }}
>
{SUPPORTED_QUOTES.map((currency: SupportedQuotesType) => (
<option key={currency} value={currency}>
{currency.toUpperCase()}
</option>
))}
</select>
</div>
{error !== '' && <span className={style.error_message}> {error} </span>}
{success !== '' && <span className={style.success_message}> {success} </span>}
</>)
}
17 changes: 11 additions & 6 deletions components/Account/account.module.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
.success_message {
color: green;
font-size: 16px;
position: absolute;
width: 100%;
height: 100%;
background-color: var(--secondary-bg-color);
Expand All @@ -13,11 +12,6 @@
border-radius: 10px;
}

.error_message {
color: red;
font-size: 14px;
}

.changepw_ctn {
width: 100%;
background-color: var(--secondary-bg-color);
Expand Down Expand Up @@ -48,3 +42,14 @@ body[data-theme='dark'] .changepw_ctn input {
max-width: unset;
}
}

.error_message {
color: red;
font-size: 14px;
top: -20px;
left: 0;
background-color: rgba(255, 0, 0, 0.1);
padding: 1px 10px;
border-radius: 5px;
}

22 changes: 16 additions & 6 deletions pages/account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import Session from 'supertokens-node/recipe/session'
import { GetServerSideProps } from 'next'
import Page from 'components/Page'
import ChangePassword from 'components/Account/ChangePassword'
import ChangeFiatCurrency from 'components/Account/ChangeFiatCurrency'
import style from './account.module.css'
import { fetchUserWithSupertokens, getUserPublicKeyHex, UserWithSupertokens } from 'services/userService'
import { fetchUserProfileFromId, fetchUserWithSupertokens, getUserPublicKeyHex, UserWithSupertokens } from 'services/userService'
import CopyIcon from '../../assets/copy-black.png'
import { ViewOrganization } from 'components/Organization'
import { fetchOrganizationForUser, fetchOrganizationMembers } from 'services/organizationService'
Expand All @@ -16,7 +17,6 @@ import { removeDateFields, removeUnserializableFields } from 'utils/index'
import TopBar from 'components/TopBar'

export const getServerSideProps: GetServerSideProps = async (context) => {
// this runs on the backend, so we must call init on supertokens-node SDK
supertokensNode.init(SuperTokensConfig.backendConfig())
let session
try {
Expand All @@ -35,6 +35,8 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
const user = await fetchUserWithSupertokens(userId)
removeUnserializableFields(user.userProfile)
const organization = await fetchOrganizationForUser(userId)
const userProfile = await fetchUserProfileFromId(userId)

let serializableOrg = null
let members: UserProfile[] = []

Expand All @@ -45,12 +47,14 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
} else {
members = []
}
removeUnserializableFields(userProfile)
return {
props: {
organization: serializableOrg,
orgMembersProps: members,
user,
userPublicKey: await getUserPublicKeyHex(userId)
userPublicKey: await getUserPublicKeyHex(userId),
userProfile
}
}
}
Expand All @@ -60,16 +64,19 @@ interface IProps {
userPublicKey: string
organization: Organization
orgMembersProps: UserProfile[]
userProfile: UserProfile
}

export default function Account ({ user, userPublicKey, organization, orgMembersProps }: IProps): React.ReactElement {
export default function Account ({ user, userPublicKey, organization, orgMembersProps, userProfile }: IProps): React.ReactElement {
const [changePassword, setChangePassword] = useState(false)
const [publicKeyInfo, setPublicKeyInfo] = useState(false)
const [isCopied, setIsCopied] = useState(false)
const [orgMembers, setOrgMembers] = useState(orgMembersProps)

const toggleChangePassword = (): void => {
setChangePassword(!changePassword)
}

const togglePublicKeyInfo = (): void => {
setPublicKeyInfo(!publicKeyInfo)
}
Expand All @@ -93,12 +100,17 @@ export default function Account ({ user, userPublicKey, organization, orgMembers
<TopBar title="Account" user={user.stUser?.email} />
<div className={style.label}>Email</div>
<div className={style.account_card}>{user.stUser?.email}</div>
<div className={style.label}>Currency</div>
<div className={style.account_card}>
<ChangeFiatCurrency preferredCurrencyId={userProfile.preferredCurrencyId}/>
</div>
{changePassword && (
<>
<div className={style.label}>Update Password</div>
<ChangePassword toggleChangePassword={toggleChangePassword} />
</>
)}

<div
onClick={() => setChangePassword(!changePassword)}
className={`${style.updatebtn} button_outline`}
Expand All @@ -120,15 +132,13 @@ export default function Account ({ user, userPublicKey, organization, orgMembers
<br/>
<br/>
To verify, check variable &lt;signature&gt; variable. It should contain two keys:

<br/>
- payload: The transaction data variables present in the POST request concatenated using the plus (+) symbol as a separator.
<br/>
- signature: The signature of the payload.
<br/>
<br/>
Check if the payload's signature came from the private key paired to this public key using your preferred method.

</div>
)}
{publicKeyInfo &&
Expand Down
9 changes: 8 additions & 1 deletion pages/api/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { setSession } from 'utils/setSession'
import * as userService from 'services/userService'
import { parseUpdatePUTRequest } from 'utils/validators'

export default async (
req: any,
res: any
): Promise<void> => {
await setSession(req, res, true)
const session = req.session
if (req.method === 'GET') {
const session = req.session
const user = await userService.fetchUserProfileFromId(session.userId)
res.status(200).json(user)
}

if (req.method === 'PUT') {
const preferredCurrencyIdObject = parseUpdatePUTRequest(req.body)
const user = await userService.updatePreferredCurrency(session.userId, preferredCurrencyIdObject.currencyId)
res.status(200).json(user)
}
}
9 changes: 9 additions & 0 deletions services/userService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,12 @@ export async function isUserAdmin (id: string): Promise<boolean> {
export const exportedForTesting = {
getUserSeedHash
}

export async function updatePreferredCurrency (id: string, preferredCurrencyId: number): Promise<void> {
await prisma.userProfile.update({
where: { id },
data: {
preferredCurrencyId
}
})
}
20 changes: 20 additions & 0 deletions utils/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ export interface ChangePasswordInput {
newPassword: string
}

export interface UpdatePreferredCurrencyInput {
currencyId: number
}

export const parseChangePasswordPOSTRequest = function (params: ChangePasswordPOSTParameters): ChangePasswordInput {
if (
params.newPassword !== params.newPasswordConfirmed ||
Expand Down Expand Up @@ -484,6 +488,10 @@ export interface JoinOrganizationInput {
token: string
}

export interface UpdatePreferredCurrencyPUTParameters {
currencyId?: string | number
}

export const parseJoinOrganizationPOSTRequest = function (params: JoinOrganizationPOSTParameters): JoinOrganizationInput {
if (params.userId === '' || params.userId === undefined) throw new Error(RESPONSE_MESSAGES.USER_ID_NOT_PROVIDED_400.message)
if (params.token === '' || params.token === undefined) throw new Error(RESPONSE_MESSAGES.INVITATION_TOKEN_NOT_PROVIDED_400.message)
Expand All @@ -510,3 +518,15 @@ export const parseCreateOrganizationPOSTRequest = function (params: CreateOrgani
name: params.name
}
}

export const parseUpdatePUTRequest = function (params: UpdatePreferredCurrencyPUTParameters): UpdatePreferredCurrencyInput {
if (params.currencyId === '' ||
params.currencyId === undefined
) {
throw new Error(RESPONSE_MESSAGES.INVALID_PASSWORD_FORM_400.message)
}

return {
currencyId: Number(params.currencyId)
}
}