Skip to content

Commit

Permalink
Merge pull request #385 from skalenetwork/add-user-profile
Browse files Browse the repository at this point in the history
Add user profile, add email getter and setter, other minor changes
  • Loading branch information
dmytrotkk authored Oct 8, 2024
2 parents 3f668d7 + b746483 commit 0f10cc0
Show file tree
Hide file tree
Showing 17 changed files with 786 additions and 211 deletions.
7 changes: 6 additions & 1 deletion src/App.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import './variables';
@import './styles/components';
@import './styles/chip';
@import './styles/ProfileModal';

:root {
background: black;
Expand All @@ -24,6 +25,10 @@ body {
width: 75pt;
}

.skMessage {
min-height: 58px;
}

.sk-header {
background-color: rgba(0, 0, 0, 0) !important;
-webkit-backdrop-filter: blur(80px) !important;
Expand Down Expand Up @@ -1229,7 +1234,7 @@ input[type=number] {

.amountInput {
input {
padding: 3px 10px 0 5px !important
padding: 0 !important
}
}

Expand Down
140 changes: 122 additions & 18 deletions src/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* @copyright SKALE Labs 2024-Present
*/

import React, { createContext, useState, useContext, useEffect } from 'react'
import React, { createContext, useState, useContext, useEffect, useCallback, useMemo } from 'react'
import { Logger, type ILogObj } from 'tslog'
import { SiweMessage } from 'siwe'
import { useWagmiAccount } from '@skalenetwork/metaport'
Expand All @@ -30,30 +30,46 @@ const log = new Logger<ILogObj>({ name: 'AuthContext' })

interface AuthContextType {
isSignedIn: boolean
email: string | null
isEmailLoading: boolean
isEmailUpdating: boolean
emailError: string | null
isProfileModalOpen: boolean
handleSignIn: () => Promise<void>
handleSignOut: () => Promise<void>
handleAddressChange: () => Promise<void>
getSignInStatus: () => Promise<boolean>
getEmail: () => Promise<void>
updateEmail: (newEmail: string) => Promise<void>
openProfileModal: () => void
closeProfileModal: () => void
}

const AuthContext = createContext<AuthContextType | undefined>(undefined)

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isSignedIn, setIsSignedIn] = useState(false)
const [email, setEmail] = useState<string | null>(null)
const [isEmailLoading, setIsEmailLoading] = useState(false)
const [isEmailUpdating, setIsEmailUpdating] = useState(false)
const [emailError, setEmailError] = useState<string | null>(null)
const [isProfileModalOpen, setIsProfileModalOpen] = useState(false)
const { address } = useWagmiAccount()

const handleAddressChange = async function () {
const handleAddressChange = useCallback(async () => {
log.info(`Address changed: ${address}`)
if (!address) {
log.warn('No address found, signing out')
setIsSignedIn(false)
setEmail(null)
return
}
try {
const status = await getSignInStatus()
if (status) {
log.info('User is already signed in')
setIsSignedIn(true)
await getEmail()
} else {
log.info('User not signed in, clearing session')
await handleSignOut()
Expand All @@ -62,9 +78,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
log.error('Error checking sign-in status:', error)
setIsSignedIn(false)
}
}
}, [address])

const getSignInStatus = async (): Promise<boolean> => {
const getSignInStatus = useCallback(async (): Promise<boolean> => {
try {
if (!address) return false
log.info(`Checking sign-in status for address: ${address}`)
Expand All @@ -80,9 +96,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
log.error('Error checking sign-in status:', error)
return false
}
}
}, [address])

const handleSignIn = async () => {
const handleSignIn = useCallback(async () => {
if (!address) {
log.warn('Cannot sign in: No address provided')
return
Expand Down Expand Up @@ -116,15 +132,16 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
if (response.ok) {
log.info('Sign in successful')
setIsSignedIn(true)
await getEmail()
} else {
log.error('Sign in failed', response.status, response.statusText)
}
} catch (error) {
log.error('Error signing in:', error)
}
}
}, [address])

const handleSignOut = async () => {
const handleSignOut = useCallback(async () => {
log.info('Initiating sign out')
try {
const response = await fetch(`${API_URL}/auth/signout`, {
Expand All @@ -140,29 +157,116 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
log.error('Error signing out:', error)
} finally {
setIsSignedIn(false)
setEmail(null)
}
}
}, [])

const fetchNonce = async () => {
const fetchNonce = useCallback(async () => {
log.debug('Fetching nonce')
const response = await fetch(`${API_URL}/auth/nonce`)
const data = await response.json()
log.debug(`Nonce received: ${data.nonce}`)
return data.nonce
}
}, [])

const getEmail = useCallback(async () => {
setIsEmailLoading(true)
setEmailError(null)
try {
const response = await fetch(`${API_URL}/auth/email`, {
credentials: 'include'
})
if (response.ok) {
if (response.status === 204) {
setEmail(null)
} else {
const data = await response.json()
setEmail(data.email)
}
} else {
throw new Error('Failed to fetch email')
}
} catch (error) {
console.error('Error fetching email:', error)
setEmailError('Failed to fetch email')
} finally {
setIsEmailLoading(false)
}
}, [])

const updateEmail = useCallback(async (newEmail: string) => {
setIsEmailUpdating(true)
setEmailError(null)
try {
const response = await fetch(`${API_URL}/auth/update_email`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: newEmail }),
credentials: 'include'
})
if (response.ok) {
setEmail(newEmail)
} else {
const errorData = await response.json()
throw new Error(errorData.error || 'Failed to update email')
}
} catch (error: any) {
log.error('Error updating email:', error)
setEmailError(error.message || 'Failed to update email')
} finally {
setIsEmailUpdating(false)
}
}, [])

const openProfileModal = useCallback(() => {
setIsProfileModalOpen(true)
}, [])

const closeProfileModal = useCallback(() => {
setIsProfileModalOpen(false)
}, [])

useEffect(() => {
log.info('Address changed, updating auth status')
handleAddressChange()
}, [address])
}, [address, handleAddressChange])

return (
<AuthContext.Provider
value={{ isSignedIn, handleSignIn, handleSignOut, handleAddressChange, getSignInStatus }}
>
{children}
</AuthContext.Provider>
const contextValue = useMemo(
() => ({
isSignedIn,
email,
isEmailLoading,
isEmailUpdating,
emailError,
isProfileModalOpen,
handleSignIn,
handleSignOut,
handleAddressChange,
getSignInStatus,
getEmail,
updateEmail,
openProfileModal,
closeProfileModal
}),
[
isSignedIn,
email,
isEmailLoading,
isEmailUpdating,
emailError,
isProfileModalOpen,
handleSignIn,
handleSignOut,
handleAddressChange,
getSignInStatus,
getEmail,
updateEmail,
openProfileModal,
closeProfileModal
]
)

return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>
}

export const useAuth = () => {
Expand Down
2 changes: 2 additions & 0 deletions src/Portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Header from './Header'
import SkDrawer from './SkDrawer'
import Router from './Router'
import SkBottomNavigation from './SkBottomNavigation'
import ProfileModal from './components/profile/ProfileModal'

export default function Portal() {
const mpc = useMetaportStore((state) => state.mpc)
Expand All @@ -41,6 +42,7 @@ export default function Portal() {
<SkDrawer />
<div className={cls(cmn.fullWidth)} id="appContentScroll">
<Router />
<ProfileModal />
<div className={cls(cmn.mtop20, cmn.fullWidth)}>
<Debug />
</div>
Expand Down
Loading

0 comments on commit 0f10cc0

Please sign in to comment.