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
67 changes: 67 additions & 0 deletions components/Timezone Selector/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useState } from 'react'
import Select from 'react-timezone-select'
import style from './timezone-selector.module.css'

interface TimezoneSelectorProps {
value: string
onChange: (value: string) => void
label?: string
isEditable?: boolean
}

const TimezoneSelector: React.FC<TimezoneSelectorProps> = ({ value }) => {
const [selectedTimezone, setSelectedTimezone] = useState(value !== '' ? value : '')
const [error, setError] = useState('')
const [success, setSuccess] = useState('')

const handleChange = (selectedOption: any): void => {
const updateTimezone = async (): Promise<void> => {
const oldTimezone = selectedTimezone
try {
const res = await fetch('/api/user/timezone', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ timezone: selectedOption.value })
})
if (res.status === 200) {
setSelectedTimezone(selectedOption.value)
setError('')
setSuccess('Timezone updated successfully.')
} else {
setSuccess('')
setError('Failed to update timezone.')
setSelectedTimezone(oldTimezone)
}
} catch (err: any) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should actually be at the else of the if above. If the api runs into an error, it will return a status code 500 response message, not throw here.

setSuccess('')
setError('Failed to update timezone.')
setSelectedTimezone(oldTimezone)
} finally {
setTimeout(() => {
setSuccess('')
setError('')
}, 3000)
}
}

void updateTimezone()
}

return (
<div className={style.timezone_selector}>
<Select
value={selectedTimezone}
onChange={handleChange}
disabled={false}
className={style.select_timezone}
displayValue="UTC"
/>
{error !== '' && <span className={style.error_message}> {error} </span>}
{success !== '' && <span className={style.success_message}> {success} </span>}
</div>
)
}

export default TimezoneSelector
40 changes: 40 additions & 0 deletions components/Timezone Selector/timezone-selector.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.timezone_selector {
display: flex;
flex-direction: column;
gap: 10px;
margin: 15px 0;
padding: 10px;
border: 1px solid var(--secondary-bg-color);
border-radius: 5px;
background-color: var(--secondary-bg-color);
}

.select_timezone {
background-color: var(--secondary-bg-color) !important;
color: black;
font-family: 'Poppins', sans-serif;
}

.success_message {
color: green;
font-size: 16px;
width: 100%;
height: 100%;
background-color: var(--secondary-bg-color);
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
}

.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;
}
13 changes: 9 additions & 4 deletions components/Transaction/AddressTransactions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,24 @@ import CheckIcon from 'assets/check-icon.png'
import XIcon from 'assets/x-icon.png'
import TableContainerGetter from '../../components/TableContainer/TableContainerGetter'
import { compareNumericString } from 'utils/index'
import moment from 'moment'
import moment from 'moment-timezone'
import { XEC_TX_EXPLORER_URL, BCH_TX_EXPLORER_URL } from 'constants/index'

interface IProps {
addressSyncing: {
[address: string]: boolean
}
tableRefreshCount: number
timezone: string
}

function getGetterForAddress (addressString: string): Function {
return async (page: number, pageSize: number, orderBy: string, orderDesc: boolean) => {
const ok = await fetch(`/api/address/transactions/${addressString}?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&orderDesc=${String(orderDesc)}`)
const ok = await fetch(`/api/address/transactions/${addressString}?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&orderDesc=${String(orderDesc)}`, {
headers: {
Timezone: moment.tz.guess()
}
})
const ok2 = await fetch(`/api/address/transactions/count/${addressString}`)
return {
data: await ok.json(),
Expand All @@ -29,7 +34,7 @@ function getGetterForAddress (addressString: string): Function {
}
}

export default ({ addressSyncing, tableRefreshCount }: IProps): JSX.Element => {
export default ({ addressSyncing, tableRefreshCount, timezone = moment.tz.guess() }: IProps): JSX.Element => {
const columns = useMemo(
() => [
{
Expand All @@ -43,7 +48,7 @@ export default ({ addressSyncing, tableRefreshCount }: IProps): JSX.Element => {
Header: 'Date',
accessor: 'timestamp',
Cell: (cellProps) => {
return <div className='table-date'>{moment(cellProps.cell.value * 1000).format('lll')}</div>
return <div className='table-date'>{moment(cellProps.cell.value * 1000).tz(timezone).format('lll')}</div>
}
},
{
Expand Down
1 change: 1 addition & 0 deletions constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const RESPONSE_MESSAGES = {
UNAUTHORIZED_403: { statusCode: 403, message: 'Unauthorized.' },
COULD_NOT_BROADCAST_TX_TO_WS_SERVER_500: { statusCode: 500, message: 'Could not broadcast upcoming transaction to WS server.' },
INVALID_PASSWORD_FORM_400: { statusCode: 400, message: 'Password form is invalid.' },
INVALID_TIMEZONE_FORM_400: { statusCode: 400, message: 'Timezone form is invalid.' },
WEAK_NEW_PASSWORD_400: { statusCode: 400, message: 'The new password must contain at least 8 characters, including a letter and a number.' },
WRONG_PASSWORD_400: { statusCode: 400, message: 'Wrong password.' },
INVALID_URL_400: { statusCode: 400, message: 'Invalid URL.' },
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"micro": "^9.3.4",
"micro-cors": "^0.1.1",
"moment": "^2.29.4",
"moment-timezone": "^0.5.46",
"mysql2": "^2.3.3",
"next": "^13.2.3",
"node-mocks-http": "^1.11.0",
Expand All @@ -58,7 +59,9 @@
"react-color": "^2.19.3",
"react-dom": "^18.2.0",
"react-hook-form": "^7.32.2",
"react-select": "^5.8.3",
"react-table": "^7.8.0",
"react-timezone-select": "^3.2.8",
"simpledotcss": "^2.1.0",
"socket.io": "^4.4.1",
"socket.io-client": "^4.7.1",
Expand Down Expand Up @@ -101,6 +104,5 @@
},
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} -r tsconfig-paths/register prisma/seed.ts"
},
"license": "MIT"
}
}
14 changes: 14 additions & 0 deletions pages/account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { fetchOrganizationForUser, fetchOrganizationMembers } from 'services/org
import { Organization, UserProfile } from '@prisma/client'
import { removeDateFields, removeUnserializableFields } from 'utils/index'
import TopBar from 'components/TopBar'
import TimezoneSelector from 'components/Timezone Selector'
import moment from 'moment-timezone'

export const getServerSideProps: GetServerSideProps = async (context) => {
supertokensNode.init(SuperTokensConfig.backendConfig())
Expand Down Expand Up @@ -72,6 +74,11 @@ export default function Account ({ user, userPublicKey, organization, orgMembers
const [publicKeyInfo, setPublicKeyInfo] = useState(false)
const [isCopied, setIsCopied] = useState(false)
const [orgMembers, setOrgMembers] = useState(orgMembersProps)
const [timezone, setTimezone] = useState(userProfile?.preferredTimezone !== '' ? userProfile?.preferredTimezone : moment.tz.guess())

const handleTimezoneChange = (selectedOption: any): void => {
setTimezone(selectedOption)
}

const toggleChangePassword = (): void => {
setChangePassword(!changePassword)
Expand Down Expand Up @@ -104,6 +111,13 @@ export default function Account ({ user, userPublicKey, organization, orgMembers
<div className={style.account_card}>
<ChangeFiatCurrency preferredCurrencyId={userProfile.preferredCurrencyId}/>
</div>
<div className={style.label}>Update timezone</div>
<div className={style.account_card}>
<TimezoneSelector
value={timezone}
onChange={handleTimezoneChange}
/>
</div>
{changePassword && (
<>
<div className={style.label}>Update Password</div>
Expand Down
8 changes: 7 additions & 1 deletion pages/api/dashboard/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { CacheGet } from 'redis/index'
import { fetchUserProfileFromId } from 'services/userService'
import { setSession } from 'utils/setSession'

export default async (req: any, res: any): Promise<void> => {
if (req.method === 'GET') {
await setSession(req, res)
const userId = req.session.userId
const resJSON = await CacheGet.dashboardData(userId)
const userReqTimezone = req.headers.timezone as string
const userProfile = await fetchUserProfileFromId(userId)
const userPreferredTimezone = userProfile?.preferredTimezone
const timezone = userPreferredTimezone !== '' ? userPreferredTimezone : userReqTimezone

const resJSON = await CacheGet.dashboardData(userId, timezone)
res.status(200).json(resJSON)
}
}
8 changes: 6 additions & 2 deletions pages/api/payments/count/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { CacheGet } from 'redis/index'
import { fetchUserProfileFromId } from 'services/userService'
import { setSession } from 'utils/setSession'

export default async (req: any, res: any): Promise<void> => {
if (req.method === 'GET') {
await setSession(req, res)
const userId = req.session.userId

const resJSON = await CacheGet.paymentsCount(userId)
const userReqTimezone = req.headers.timezone as string
const userProfile = await fetchUserProfileFromId(userId)
const userPreferredTimezone = userProfile?.preferredTimezone
const timezone = userPreferredTimezone !== '' ? userPreferredTimezone : userReqTimezone
const resJSON = await CacheGet.paymentsCount(userId, timezone)
res.status(200).json(resJSON)
}
}
19 changes: 19 additions & 0 deletions pages/api/user/timezone/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { setSession } from 'utils/setSession'
import * as userService from 'services/userService'
import { parseUpdateUserTimezonePUTRequest } from 'utils/validators'
import { clearDashboardCache } from 'redis/dashboardCache'

export default async (
req: any,
res: any
): Promise<void> => {
await setSession(req, res, true)

if (req.method === 'PUT') {
const session = req.session
const preferredTimezone = parseUpdateUserTimezonePUTRequest(req.body)
const user = await userService.updatePreferredTimezone(session.userId, preferredTimezone.timezone)
await clearDashboardCache(session.userId)
res.status(200).json(user)
}
}
5 changes: 3 additions & 2 deletions pages/button/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import PaybuttonTrigger from 'components/Paybutton/PaybuttonTrigger'
import { UserProfile } from '@prisma/client'
import { fetchUserProfileFromId } from 'services/userService'
import { removeUnserializableFields } from 'utils'
import moment from 'moment-timezone'

export const getServerSideProps: GetServerSideProps = async (context) => {
supertokensNode.init(SuperTokensConfig.backendConfig())
Expand Down Expand Up @@ -56,7 +57,7 @@ export default function Button (props: PaybuttonProps): React.ReactElement {
const [paybuttonNetworks, setPaybuttonNetworks] = useState<number[]>([])
const [selectedCurrency, setSelectedCurrency] = useState<string>('')
const userProfile = props.userProfile

const timezone = userProfile?.preferredTimezone === '' ? moment.tz.guess() : userProfile.preferredTimezone
const router = useRouter()

const updateIsSyncing = (addressStringList: string[]): void => {
Expand Down Expand Up @@ -196,7 +197,7 @@ export default function Button (props: PaybuttonProps): React.ReactElement {
</div>
</div>

<AddressTransactions addressSyncing={isSyncing} tableRefreshCount={tableRefreshCount}/>
<AddressTransactions addressSyncing={isSyncing} tableRefreshCount={tableRefreshCount} timezone={timezone}/>
<PaybuttonTrigger emailCredits={userProfile.emailCredits} paybuttonId={paybutton.id}/>
</>
)
Expand Down
7 changes: 6 additions & 1 deletion pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DashboardData, PeriodData } from 'redis/types'
import { loadStateFromCookie, saveStateToCookie } from 'utils/cookies'
import TopBar from 'components/TopBar'
import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService'
import moment from 'moment-timezone'
const Chart = dynamic(async () => await import('components/Chart'), {
ssr: false
})
Expand Down Expand Up @@ -94,7 +95,11 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen

useEffect(() => {
const fetchData = async (): Promise<void> => {
const res = await fetch('api/dashboard')
const res = await fetch('api/dashboard', {
headers: {
Timezone: moment.tz.guess()
}
})
const json = await res.json()
setDashboardData(json)
}
Expand Down
12 changes: 9 additions & 3 deletions pages/payments/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import BCHIcon from 'assets/bch-logo.png'
import EyeIcon from 'assets/eye-icon.png'
import { formatQuoteValue, compareNumericString, removeUnserializableFields } from 'utils/index'
import { XEC_NETWORK_ID, BCH_TX_EXPLORER_URL, XEC_TX_EXPLORER_URL } from 'constants/index'
import moment from 'moment'
import moment from 'moment-timezone'
import TopBar from 'components/TopBar'
import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService'

Expand Down Expand Up @@ -51,10 +51,16 @@ interface PaybuttonsProps {
}

export default function Payments ({ user, userId }: PaybuttonsProps): React.ReactElement {
const timezone = user?.userProfile.preferredTimezone === '' ? moment.tz.guess() : user?.userProfile?.preferredTimezone

function fetchData (): Function {
return async (page: number, pageSize: number, orderBy: string, orderDesc: boolean) => {
const paymentsResponse = await fetch(`/api/payments?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&orderDesc=${String(orderDesc)}`)
const paymentsCountResponse = await fetch('/api/payments/count')
const paymentsCountResponse = await fetch('/api/payments/count', {
headers: {
Timezone: timezone
}
})
const totalCount = await paymentsCountResponse.json()
const payments = await paymentsResponse.json()
return {
Expand All @@ -70,7 +76,7 @@ export default function Payments ({ user, userId }: PaybuttonsProps): React.Reac
Header: 'Date',
accessor: 'timestamp',
Cell: (cellProps) => {
return <div className='table-date'>{moment(cellProps.cell.value * 1000).format('lll')}</div>
return <div className='table-date'>{moment(cellProps.cell.value * 1000).tz(timezone).format('lll')}</div>
}
},
{
Expand Down
Loading
Loading