Skip to content

Commit 2b41dd2

Browse files
authored
Merge pull request #294 from medyo/develop
New version
2 parents b86ae8f + f56e79c commit 2b41dd2

11 files changed

Lines changed: 178 additions & 60 deletions

File tree

README.md

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,44 @@ Please do not hesitate to ask a question, report a bug or add a suggestion. or s
5858

5959
## Development
6060

61-
Please use the develop branch. Create an .env file with the necessary env variables
61+
Please use the develop branch. Create an `.env` file in the root directory with the following variables:
6262

6363
```bash
64-
$ git clone --branch develop git@github.com:medyo/hackertab.dev.git
64+
VITE_API_URL=https://api.hackertab.dev/
65+
VITE_BUILD_TARGET=web # or extension
66+
VITE_BUILD_PLATFORM=chrome # optional, used for extension builds (chrome or firefox)
67+
VITE_FIREBASE_API_KEY= # optional for local dev, required for auth features
68+
VITE_AMPLITUDE_URL= # optional
69+
VITE_AMPLITUDE_KEY= # optional
70+
VITE_SENTRY_DSN= # optional
71+
```
72+
73+
### Setup
74+
75+
Make sure you are using Node.js version 18.
76+
77+
```bash
78+
$ git clone https://github.com/medyo/hackertab.dev.git
6579
$ cd hackertab.dev
6680
$ yarn
6781
$ yarn start
68-
$ # Then visit http://localhost:3000
82+
```
83+
84+
Then visit [http://localhost:5173](http://localhost:5173) (or the port shown in your terminal).
85+
86+
## 🚀 Build
87+
88+
To build the project for different targets:
89+
90+
```bash
91+
# Web build
92+
$ yarn build:web
93+
94+
# Chrome extension
95+
$ yarn build:chrome
96+
97+
# Firefox extension
98+
$ yarn build:firefox
6999
```
70100

71101
## Maintainers

src/components/Elements/Button/Button.css

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,4 @@
8585
color: var(--button-hover-text-color);
8686
}
8787
}
88-
89-
&.dark-focus {
90-
background-color: var(--dark-mode-background-color);
91-
color: var(--dark-mode-text-color);
92-
93-
&:hover {
94-
opacity: 0.9;
95-
}
96-
}
9788
}

src/components/Elements/Button/CircleButton.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const sizes = {
99

1010
const variants = {
1111
primary: 'primary',
12-
darkfocus: 'dark-focus',
1312
}
1413

1514
type CircleButtonProps = {

src/components/Layout/Header.tsx

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { clsx } from 'clsx'
22
import { useCallback, useEffect, useState } from 'react'
3-
import { BsFillBookmarksFill, BsFillGearFill, BsMoonFill } from 'react-icons/bs'
3+
import { BsFillBookmarksFill, BsFillGearFill } from 'react-icons/bs'
44
import { CgTab } from 'react-icons/cg'
55
import { FaCrown } from 'react-icons/fa'
6-
import { IoMdSunny } from 'react-icons/io'
76
import { MdDoDisturbOff } from 'react-icons/md'
87
import { RiDashboardHorizontalFill } from 'react-icons/ri'
98
import { TfiLayoutColumn4Alt } from 'react-icons/tfi'
@@ -15,12 +14,7 @@ import { UserTags } from 'src/components/Elements/UserTags'
1514
import { useAuth } from 'src/features/auth'
1615
import { Changelog } from 'src/features/changelog'
1716
import { useRemoteConfigStore } from 'src/features/remoteConfig'
18-
import {
19-
identifyUserTheme,
20-
trackDNDDisable,
21-
trackDisplayTypeChange,
22-
trackThemeSelect,
23-
} from 'src/lib/analytics'
17+
import { trackDNDDisable, trackDisplayTypeChange } from 'src/lib/analytics'
2418
import { useUserPreferences } from 'src/stores/preferences'
2519
import { lazyImport } from 'src/utils/lazyImport'
2620
import { Button, CircleButton } from '../Elements'
@@ -30,36 +24,32 @@ const { DonateModal } = lazyImport(() => import('src/features/donate'), 'DonateM
3024
export const Header = () => {
3125
const { openAuthModal, user, isConnected, isConnecting } = useAuth()
3226

33-
const [themeIcon, setThemeIcon] = useState(<BsMoonFill />)
3427
const [openDonateModal, setOpenDonateModal] = useState(false)
3528
const { paywall } = useRemoteConfigStore()
36-
const { theme, setTheme, setDNDDuration, isDNDModeActive, layout, setLayout } =
37-
useUserPreferences()
29+
const { theme, setDNDDuration, isDNDModeActive, layout, setLayout } = useUserPreferences()
3830
const navigate = useNavigate()
3931
const location = useLocation()
4032

4133
useEffect(() => {
42-
document.documentElement.classList.add(theme)
43-
// eslint-disable-next-line react-hooks/exhaustive-deps
44-
}, [])
34+
const applyResolvedTheme = (resolved: 'dark' | 'light') => {
35+
document.documentElement.classList.remove('dark', 'light')
36+
document.documentElement.classList.add(resolved)
37+
}
4538

46-
useEffect(() => {
47-
if (theme === 'light') {
48-
document.documentElement.classList.replace('dark', theme)
49-
setThemeIcon(<BsMoonFill />)
50-
} else if (theme === 'dark') {
51-
document.documentElement.classList.replace('light', theme)
52-
setThemeIcon(<IoMdSunny />)
39+
if (theme === 'system') {
40+
const mq = window.matchMedia('(prefers-color-scheme: dark)')
41+
const updateSystemTheme = (matches: boolean) => {
42+
applyResolvedTheme(matches ? 'dark' : 'light')
43+
}
44+
updateSystemTheme(mq.matches)
45+
const handler = (e: MediaQueryListEvent) => updateSystemTheme(e.matches)
46+
mq.addEventListener('change', handler)
47+
return () => mq.removeEventListener('change', handler)
48+
} else {
49+
applyResolvedTheme(theme)
5350
}
5451
}, [theme])
5552

56-
const onThemeChange = useCallback(() => {
57-
const newTheme = theme === 'dark' ? 'light' : 'dark'
58-
setTheme(newTheme)
59-
trackThemeSelect(newTheme)
60-
identifyUserTheme(newTheme)
61-
}, [theme, setTheme])
62-
6353
const onLayoutChange = useCallback(() => {
6454
const newLayout = layout === 'cards' ? 'grid' : 'cards'
6555
trackDisplayTypeChange(newLayout)
@@ -111,9 +101,6 @@ export const Header = () => {
111101
<CircleButton onClick={onLayoutChange}>
112102
{layout === 'cards' ? <RiDashboardHorizontalFill /> : <TfiLayoutColumn4Alt />}
113103
</CircleButton>
114-
<CircleButton onClick={onThemeChange} variant="darkfocus">
115-
{themeIcon}
116-
</CircleButton>
117104
<CircleButton onClick={() => navigate('/settings/bookmarks')}>
118105
<BsFillBookmarksFill />
119106
</CircleButton>

src/features/adv/components/AdvBanner.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ export const AdvBanner = () => {
5454
</button>
5555
</div>
5656
{ad.sponsored_by && (
57-
<a href={ad.link} className="sponsoredBadge" target="_blank" title="Sponsored">
57+
<a
58+
href={`https://hackertab.dev/advertise`}
59+
className="sponsoredBadge"
60+
target="_blank"
61+
title="Sponsored">
5862
{ad.sponsored_by}
5963
</a>
6064
)}

src/features/adv/components/StickyBanner.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@ export const StickyBanner = () => {
3838
cards: cards.map((card) => card.name),
3939
firstSeenDate,
4040
isConnected,
41-
isSupported: user?.isSupporter || false,
41+
isSupporter: user?.isSupporter || false,
4242
usageInDays: diffBetweenTwoDatesInDays(firstSeenDate, Date.now()),
4343
}
4444
}, [userSelectedTags, firstSeenDate, cards, user])
4545

4646
const shouldShowAd =
47-
ad && (!ad.condition || jsonPath.query([userAttributes], ad.condition)?.length > 0)
47+
ad &&
48+
!user?.isSupporter &&
49+
(!ad.condition || jsonPath.query([userAttributes], ad.condition)?.length > 0)
4850

4951
useEffect(() => {
5052
if (shouldShowAd) {
@@ -78,7 +80,11 @@ export const StickyBanner = () => {
7880
</button>
7981

8082
{ad.sponsored_by && (
81-
<a href={ad.link} className="sponsoredBadge" target="_blank" title="Sponsored">
83+
<a
84+
href={`https://hackertab.dev/advertise`}
85+
className="sponsoredBadge"
86+
target="_blank"
87+
title="Sponsored">
8288
{ad.sponsored_by}
8389
</a>
8490
)}

src/features/settings/components/GeneralSettings/GeneralSettings.tsx

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import React from 'react'
2+
import { BsMoonFill } from 'react-icons/bs'
3+
import { IoMdSunny } from 'react-icons/io'
4+
import { MdMonitor } from 'react-icons/md'
25
import Toggle from 'react-toggle'
36
import 'react-toggle/style.css'
47
import { Footer } from 'src/components/Layout'
@@ -12,13 +15,20 @@ import {
1215
trackThemeSelect,
1316
} from 'src/lib/analytics'
1417
import { useUserPreferences } from 'src/stores/preferences'
18+
import { Theme } from 'src/types'
1519
import { DeleteAccount } from '../UserSettings/DeleteAccount'
1620
import { UserInfo } from '../UserSettings/UserInfo'
1721
import { CardsNumberSettings } from './CardsNumberSettings'
1822
import { DNDSettings } from './DNDSettings'
1923
import './generalSettings.css'
2024
import { LayoutSettings } from './LayoutSettings'
2125

26+
const themeIcons = {
27+
light: <IoMdSunny />,
28+
dark: <BsMoonFill />,
29+
system: <MdMonitor />,
30+
}
31+
2232
export const GeneralSettings = () => {
2333
const {
2434
openLinksNewTab,
@@ -47,8 +57,7 @@ export const GeneralSettings = () => {
4757
setListingMode(value)
4858
}
4959

50-
const onDarkModeChange = () => {
51-
const newTheme = theme === 'dark' ? 'light' : 'dark'
60+
const onThemeChange = (newTheme: Theme) => {
5261
setTheme(newTheme)
5362
trackThemeSelect(newTheme)
5463
identifyUserTheme(newTheme)
@@ -70,9 +79,23 @@ export const GeneralSettings = () => {
7079
<CardsNumberSettings />
7180

7281
<div className="settingRow">
73-
<p className="settingTitle">Dark Mode</p>
82+
<p className="settingTitle">Theme</p>
7483
<div className="settingContent">
75-
<Toggle checked={theme === 'dark'} icons={false} onChange={onDarkModeChange} />
84+
<div className="themeSelector">
85+
{(['light', 'dark', 'system'] as Theme[]).map((option) => (
86+
<label key={option} className={`themeOption${theme === option ? ' active' : ''}`}>
87+
<input
88+
type="radio"
89+
name="theme"
90+
value={option}
91+
checked={theme === option}
92+
onChange={() => onThemeChange(option)}
93+
/>
94+
{themeIcons[option]}
95+
<span>{option.charAt(0).toUpperCase() + option.slice(1)}</span>
96+
</label>
97+
))}
98+
</div>
7699
</div>
77100
</div>
78101

0 commit comments

Comments
 (0)