Skip to content

Commit 7a7a22e

Browse files
committed
feat: Add profile button to header and create profile page.
1 parent 885dc3d commit 7a7a22e

11 files changed

Lines changed: 213 additions & 76 deletions

File tree

src/App.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { DNDLayout } from 'src/components/Layout'
33
import { setupAnalytics, setupIdentification, trackPageView } from 'src/lib/analytics'
44
import { useUserPreferences } from 'src/stores/preferences'
55
import { AppContentLayout } from './components/Layout'
6-
import { AuthModal } from './features/auth'
76
import { isWebOrExtensionVersion } from './utils/Environment'
87
import { lazyImport } from './utils/lazyImport'
98
const { OnboardingModal } = lazyImport(() => import('src/features/onboarding'), 'OnboardingModal')
@@ -20,7 +19,6 @@ const intersectionCallback = (entries: IntersectionObserverEntry[]) => {
2019

2120
export const App = () => {
2221
const [showOnboarding, setShowOnboarding] = useState(true)
23-
const [showAuth, setshowAuth] = useState(true)
2422
const { onboardingCompleted, maxVisibleCards, isDNDModeActive, DNDDuration, setDNDDuration } =
2523
useUserPreferences()
2624

@@ -64,8 +62,6 @@ export const App = () => {
6462
<OnboardingModal showOnboarding={showOnboarding} setShowOnboarding={setShowOnboarding} />
6563
)}
6664

67-
{<AuthModal showAuth={showAuth} setShowAuth={setshowAuth} />}
68-
6965
<div className="layoutLayers hideScrollBar">
7066
{isDNDModeActive() && <DNDLayout />}
7167
<AppContentLayout />

src/assets/App.css

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ a {
122122
}
123123

124124
.extras {
125-
display: none;
125+
display: flex;
126126
flex-direction: row;
127127
align-content: center;
128128
order: 2;
@@ -180,6 +180,12 @@ a {
180180
gap: 6px;
181181
}
182182

183+
.profileImage {
184+
height: 40px;
185+
width: 40px;
186+
border-radius: 20px;
187+
}
188+
183189
.badgeCount {
184190
width: 20px;
185191
height: 20px;
@@ -1216,10 +1222,6 @@ Producthunt item
12161222
position: relative;
12171223
}
12181224

1219-
.extras {
1220-
display: block;
1221-
}
1222-
12231225
.scrollButton {
12241226
align-items: center;
12251227
display: flex;

src/components/Layout/Header.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
import { useEffect, useState } from 'react'
22
import { BsFillBookmarksFill, BsFillGearFill, BsMoonFill } from 'react-icons/bs'
33
import { CgTab } from 'react-icons/cg'
4+
import { FaUserLarge } from 'react-icons/fa6'
45
import { IoMdSunny } from 'react-icons/io'
56
import { MdDoDisturbOff } from 'react-icons/md'
67
import { RxArrowLeft } from 'react-icons/rx'
78
import { Link, useLocation, useNavigate } from 'react-router-dom'
89
import { ReactComponent as HackertabLogo } from 'src/assets/logo.svg'
910
import { SearchBar } from 'src/components/Elements/SearchBar'
1011
import { UserTags } from 'src/components/Elements/UserTags'
12+
import { AuthModal } from 'src/features/auth'
1113
import { Changelog } from 'src/features/changelog'
1214
import { identifyUserTheme, trackDNDDisable, trackThemeSelect } from 'src/lib/analytics'
1315
import { useBookmarks } from 'src/stores/bookmarks'
1416
import { useUserPreferences } from 'src/stores/preferences'
17+
import { useAuth } from 'src/stores/user'
1518

1619
export const Header = () => {
20+
const { user } = useAuth()
21+
const [showAuth, setshowAuth] = useState(false)
22+
useEffect(() => {
23+
if (user != null) {
24+
setshowAuth(false)
25+
}
26+
})
27+
1728
const [themeIcon, setThemeIcon] = useState(<BsMoonFill />)
1829
const { theme, setTheme, setDNDDuration, isDNDModeActive } = useUserPreferences()
1930
const { userBookmarks } = useBookmarks()
@@ -63,6 +74,8 @@ export const Header = () => {
6374

6475
return (
6576
<>
77+
{<AuthModal showAuth={showAuth} setShowAuth={setshowAuth} />}
78+
6679
<header className="AppHeader">
6780
<span className="AppName">
6881
<i className="logo">
@@ -96,6 +109,20 @@ export const Header = () => {
96109
<BookmarksBadgeCount />
97110
</>
98111
</Link>
112+
{user != null ? (
113+
<Link to="/settings/profile" className="extraBtn" aria-label="Open profile">
114+
<img className="profileImage" src={user.imageURL} />
115+
</Link>
116+
) : (
117+
<button
118+
aria-label="open login"
119+
className="extraBtn"
120+
onClick={() => {
121+
setshowAuth(true)
122+
}}>
123+
<FaUserLarge />
124+
</button>
125+
)}
99126
</div>
100127
{location.pathname === '/' ? (
101128
<UserTags />

src/components/Layout/SettingsLayout/SettingsLayout.tsx

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,14 @@
11
import React from 'react'
22
import 'react-contexify/dist/ReactContexify.css'
33
import { NavLink, Outlet } from 'react-router-dom'
4-
import { User } from 'src/features/auth/types'
5-
import { useAuth } from 'src/stores/user'
64
import './settings.css'
75

8-
interface UserInfoProps {
9-
user: User
10-
}
11-
12-
const UserInfo = ({ user }: UserInfoProps) => {
13-
const { logout } = useAuth()
14-
15-
return (
16-
<div className="userCard">
17-
{user?.imageURL && <img src={user.imageURL} className="userImage"></img>}
18-
<div className="userInfos">
19-
<div className="userName">{user.name}</div>
20-
<div className="userEmail">{user?.email}</div>
21-
</div>
22-
<button className="logoutBtn" onClick={logout}>
23-
Logout
24-
</button>
25-
</div>
26-
)
27-
}
28-
296
export const SettingsLayout = () => {
30-
const { user } = useAuth()
317
const navigation = [
8+
{
9+
name: 'Profile',
10+
path: '/settings/profile',
11+
},
3212
{
3313
name: 'Topics',
3414
path: '/settings/topics',
@@ -52,7 +32,6 @@ export const SettingsLayout = () => {
5232
]
5333
return (
5434
<div className="settings">
55-
{user != null && <UserInfo user={user} />}
5635
<div className="horizontalTabsLayout">
5736
<nav className="navigation">
5837
{navigation.map((item) => {

src/components/Layout/SettingsLayout/settings.css

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -40,44 +40,6 @@
4040
border-right: 4px solid var(--horizontal-tabs-layout-active-color);
4141
font-weight: bold;
4242
}
43-
.userCard {
44-
width: auto;
45-
background-color: var(--placeholder-background-color);
46-
padding: 10px;
47-
margin: 10px;
48-
border-radius: 20px;
49-
display: flex;
50-
align-items: center;
51-
}
52-
.userImage {
53-
width: 60px;
54-
height: 60px;
55-
border-radius: 20%;
56-
}
57-
.userInfos {
58-
width: auto;
59-
margin-left: 10px;
60-
}
61-
.userName {
62-
font-size: larger;
63-
font-weight: 600;
64-
}
65-
.userEmail {
66-
font-size: medium;
67-
margin-top: 6px;
68-
}
69-
.logoutBtn {
70-
font-size: medium;
71-
padding: 8px 20px;
72-
background-color: #e57373;
73-
color: var(--button-text-color);
74-
border: 0;
75-
border-radius: 20px;
76-
font-weight: bold;
77-
font-size: 16px;
78-
text-align: center;
79-
margin-left: auto;
80-
}
8143

8244
@media only screen and (max-width: 768px) {
8345
.horizontalTabsLayout {

src/features/settings/components/BookmarkSettings/BookmarkSettings.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ type BookmarkItemProps = {
1515
item: BookmarkedPost
1616
appendRef?: boolean
1717
}
18-
const BookmarkItem = ({ item, appendRef = false }: BookmarkItemProps) => {
18+
19+
export const BookmarkItem = ({ item, appendRef = false }: BookmarkItemProps) => {
1920
const { unbookmarkPost } = useBookmarks()
2021
const { userCustomCards } = useUserPreferences()
2122

src/features/settings/components/BookmarkSettings/bookmarkSettings.css

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,52 @@
8181
.btn:hover {
8282
filter: brightness(85%);
8383
}
84+
85+
.container {
86+
width: auto;
87+
display: flex;
88+
flex-direction: column;
89+
gap: 10px;
90+
}
91+
92+
.userCard {
93+
width: auto;
94+
background-color: var(--placeholder-background-color);
95+
padding: 10px;
96+
margin: 10px;
97+
border-radius: 20px;
98+
display: flex;
99+
align-items: center;
100+
}
101+
.userImage {
102+
width: 60px;
103+
height: 60px;
104+
border-radius: 20%;
105+
}
106+
.userInfos {
107+
width: auto;
108+
margin-left: 10px;
109+
}
110+
.userName {
111+
font-size: larger;
112+
font-weight: 600;
113+
}
114+
.userEmail {
115+
font-size: medium;
116+
margin-top: 6px;
117+
}
118+
.actions {
119+
margin-left: auto;
120+
}
121+
.logoutBtn {
122+
font-size: medium;
123+
padding: 8px 20px;
124+
background-color: #e57373;
125+
color: var(--button-text-color);
126+
border: 0;
127+
border-radius: 20px;
128+
font-weight: bold;
129+
font-size: 16px;
130+
text-align: center;
131+
margin-left: 8px;
132+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { useRef } from 'react'
2+
import { RiFileDownloadFill, RiFileUploadFill } from 'react-icons/ri'
3+
import toast from 'react-simple-toasts'
4+
import { SettingsContentLayout } from 'src/components/Layout/SettingsContentLayout'
5+
import { User } from 'src/features/auth'
6+
import { BookmarkedPost } from 'src/features/bookmarks'
7+
import { useBookmarks } from 'src/stores/bookmarks'
8+
import { useAuth } from 'src/stores/user'
9+
import { BookmarkItem } from './BookmarkSettings'
10+
import './bookmarkSettings/bookmarkSettings.css'
11+
12+
interface UserInfoProps {
13+
user: User
14+
}
15+
16+
const UserInfo = ({ user }: UserInfoProps) => {
17+
const { logout } = useAuth()
18+
19+
return (
20+
<div className="userCard">
21+
{user?.imageURL && <img src={user.imageURL} className="userImage"></img>}
22+
<div className="userInfos">
23+
<div className="userName">{user.name}</div>
24+
<div className="userEmail">{user?.email}</div>
25+
</div>
26+
<div className="actions">
27+
<button className="logoutBtn" onClick={logout}>
28+
Logout
29+
</button>
30+
<button className="logoutBtn" onClick={() => {}}>
31+
Delete account
32+
</button>
33+
</div>
34+
</div>
35+
)
36+
}
37+
38+
export const ProfileSettings = () => {
39+
const { user } = useAuth()
40+
41+
const inputFile = useRef<HTMLInputElement | null>(null)
42+
const { initState, userBookmarks } = useBookmarks()
43+
44+
const importBookmarks = () => {
45+
inputFile.current?.click()
46+
}
47+
48+
const exportBookmarks = () => {
49+
const blob = new Blob([JSON.stringify(userBookmarks, null, 2)], {
50+
type: 'application/json',
51+
})
52+
const downloadURL = URL.createObjectURL(blob)
53+
const link = document.createElement('a')
54+
link.href = downloadURL
55+
link.download = 'hackertabBookmarks'
56+
link.click()
57+
}
58+
59+
const handleFileChange = (event: any) => {
60+
const file = event.target.files?.[0]
61+
if (file) {
62+
const reader = new FileReader()
63+
reader.onload = () => {
64+
const importData: BookmarkedPost[] = JSON.parse(reader.result as string)
65+
const validatedData = importData
66+
.filter(
67+
(data: BookmarkedPost) =>
68+
data.title &&
69+
data.url &&
70+
!userBookmarks.some((bm) => bm.title === data.title && bm.url === data.url)
71+
)
72+
.map((data) => ({
73+
title: data.title,
74+
url: data.url,
75+
source: data.source || '',
76+
sourceType: data.sourceType || '',
77+
}))
78+
initState({
79+
userBookmarks: [...userBookmarks, ...validatedData],
80+
})
81+
toast('Your bookmarks have been successfully imported', { theme: 'defaultToast' })
82+
}
83+
reader.readAsText(file)
84+
}
85+
}
86+
87+
return (
88+
<div className="container">
89+
{user != null && <UserInfo user={user} />}
90+
<SettingsContentLayout
91+
title="Bookmarks"
92+
description="Find all your bookmarks here. You can remove a bookmark by clicking on the remove icon."
93+
actions={
94+
<>
95+
<input
96+
type="file"
97+
id="file"
98+
ref={inputFile}
99+
accept="application/json"
100+
className="hidden"
101+
onChange={handleFileChange}
102+
/>
103+
<button className="extraBtn extraTextBtn" onClick={() => importBookmarks()}>
104+
<RiFileUploadFill />
105+
&nbsp;Import
106+
</button>
107+
<button className="extraBtn" onClick={() => exportBookmarks()}>
108+
<RiFileDownloadFill />
109+
</button>
110+
</>
111+
}>
112+
<div className="bookmarks">
113+
{userBookmarks.map((bm) => (
114+
<BookmarkItem item={bm} key={bm.url} />
115+
))}
116+
</div>
117+
</SettingsContentLayout>
118+
</div>
119+
)
120+
}

0 commit comments

Comments
 (0)