Skip to content
Open
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
36 changes: 33 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,44 @@ Please do not hesitate to ask a question, report a bug or add a suggestion. or s

## Development

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

```bash
$ git clone --branch develop git@github.com:medyo/hackertab.dev.git
VITE_API_URL=https://api.hackertab.dev/
VITE_BUILD_TARGET=web # or extension
VITE_BUILD_PLATFORM=chrome # optional, used for extension builds (chrome or firefox)
VITE_FIREBASE_API_KEY= # optional for local dev, required for auth features
VITE_AMPLITUDE_URL= # optional
VITE_AMPLITUDE_KEY= # optional
VITE_SENTRY_DSN= # optional
```

### Setup

Make sure you are using Node.js version 18.

```bash
$ git clone https://github.com/medyo/hackertab.dev.git
$ cd hackertab.dev
$ yarn
$ yarn start
$ # Then visit http://localhost:3000
```

Then visit [http://localhost:5173](http://localhost:5173) (or the port shown in your terminal).

## 🚀 Build

To build the project for different targets:

```bash
# Web build
$ yarn build:web

# Chrome extension
$ yarn build:chrome

# Firefox extension
$ yarn build:firefox
```

## Maintainers
Expand Down
32 changes: 20 additions & 12 deletions src/components/Layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { BsFillBookmarksFill, BsFillGearFill, BsMoonFill } from 'react-icons/bs'
import { CgTab } from 'react-icons/cg'
import { FaCrown } from 'react-icons/fa'
import { IoMdSunny } from 'react-icons/io'
import { MdDoDisturbOff } from 'react-icons/md'
import { MdDoDisturbOff, MdMonitor } from 'react-icons/md'
import { RiDashboardHorizontalFill } from 'react-icons/ri'
import { TfiLayoutColumn4Alt } from 'react-icons/tfi'
import { Link, useLocation, useNavigate } from 'react-router-dom'
Expand Down Expand Up @@ -39,22 +39,30 @@ export const Header = () => {
const location = useLocation()

useEffect(() => {
document.documentElement.classList.add(theme)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const applyResolvedTheme = (resolved: 'dark' | 'light') => {
document.documentElement.classList.remove('dark', 'light')
document.documentElement.classList.add(resolved)
}

useEffect(() => {
if (theme === 'light') {
document.documentElement.classList.replace('dark', theme)
setThemeIcon(<BsMoonFill />)
} else if (theme === 'dark') {
document.documentElement.classList.replace('light', theme)
setThemeIcon(<IoMdSunny />)
if (theme === 'system') {
const mq = window.matchMedia('(prefers-color-scheme: dark)')
const updateSystemTheme = (matches: boolean) => {
applyResolvedTheme(matches ? 'dark' : 'light')
}
setThemeIcon(<MdMonitor />)
updateSystemTheme(mq.matches)
const handler = (e: MediaQueryListEvent) => updateSystemTheme(e.matches)
mq.addEventListener('change', handler)
return () => mq.removeEventListener('change', handler)
} else {
applyResolvedTheme(theme)
setThemeIcon(theme === 'light' ? <IoMdSunny /> : <BsMoonFill />)
}
}, [theme])

const onThemeChange = useCallback(() => {
const newTheme = theme === 'dark' ? 'light' : 'dark'
const cycle = ['light', 'dark', 'system'] as const
const newTheme = cycle[(cycle.indexOf(theme as (typeof cycle)[number]) + 1) % cycle.length]
setTheme(newTheme)
trackThemeSelect(newTheme)
identifyUserTheme(newTheme)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import React from 'react'
import { BsMoonFill } from 'react-icons/bs'
import { IoMdSunny } from 'react-icons/io'
import { MdMonitor } from 'react-icons/md'
import Toggle from 'react-toggle'
import 'react-toggle/style.css'
import { Footer } from 'src/components/Layout'
Expand All @@ -12,13 +15,20 @@ import {
trackThemeSelect,
} from 'src/lib/analytics'
import { useUserPreferences } from 'src/stores/preferences'
import { Theme } from 'src/types'
import { DeleteAccount } from '../UserSettings/DeleteAccount'
import { UserInfo } from '../UserSettings/UserInfo'
import { CardsNumberSettings } from './CardsNumberSettings'
import { DNDSettings } from './DNDSettings'
import './generalSettings.css'
import { LayoutSettings } from './LayoutSettings'

const themeIcons = {
light: <IoMdSunny />,
dark: <BsMoonFill />,
system: <MdMonitor />,
}

export const GeneralSettings = () => {
const {
openLinksNewTab,
Expand Down Expand Up @@ -47,8 +57,7 @@ export const GeneralSettings = () => {
setListingMode(value)
}

const onDarkModeChange = () => {
const newTheme = theme === 'dark' ? 'light' : 'dark'
const onThemeChange = (newTheme: Theme) => {
setTheme(newTheme)
trackThemeSelect(newTheme)
identifyUserTheme(newTheme)
Expand All @@ -70,9 +79,23 @@ export const GeneralSettings = () => {
<CardsNumberSettings />

<div className="settingRow">
<p className="settingTitle">Dark Mode</p>
<p className="settingTitle">Theme</p>
<div className="settingContent">
<Toggle checked={theme === 'dark'} icons={false} onChange={onDarkModeChange} />
<div className="themeSelector">
{(['light', 'dark', 'system'] as Theme[]).map((option) => (
<label key={option} className={`themeOption${theme === option ? ' active' : ''}`}>
<input
type="radio"
name="theme"
value={option}
checked={theme === option}
onChange={() => onThemeChange(option)}
/>
{themeIcons[option]}
<span>{option.charAt(0).toUpperCase() + option.slice(1)}</span>
</label>
))}
</div>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@
align-self: flex-start;
color: var(--primary-text-color);
}

.settingRow {
padding: 10px 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}

.settingRow .optionIcon {
display: flex;
align-items: center;
column-gap: 8px;
}

.settingRow:not(:last-child) {
border-bottom: 1px solid var(--card-content-divider);
}
Expand All @@ -34,12 +37,15 @@
cursor: pointer;
transition: opacity 0.2s linear;
}

.settingContent button:hover {
opacity: 0.9;
}

.rssButton {
background-color: #ee802f;
color: white;

&:hover {
background-color: #f99147;
color: white;
Expand All @@ -50,44 +56,89 @@
width: 100%;
flex: 1;
}

.settingHint {
font-size: 12px;
margin-top: 12px;
}

.settingHint a {
text-decoration: underline;
cursor: pointer;
font-weight: 500;
color: var(--primary-text-color);
}

/**
Theme selector styles
**/
.themeSelector {
display: flex;
gap: 8px;
flex-wrap: wrap;
}

.themeOption {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 6px 16px;
border-radius: 20px;
border: 1px solid var(--card-border-color);
background-color: transparent;
color: var(--secondary-text-color);
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
user-select: none;
}

.themeOption input[type='radio'] {
display: none;
}

.themeOption.active {
background-color: var(--button-background-color);
border-color: var(--button-background-color);
color: var(--button-text-color);
}

/**
Select styles
**/
.hackertab__control {
background-color: var(--card-background-color) !important;
border-color: var(--tag-border-color) !important;
}

.hackertab__indicator-separator {
background-color: var(--tag-secondary-color) !important;
}

.hackertab__indicator {
color: var(--tag-secondary-color) !important;
}

.hackertab__multi-value {
background-color: var(--tag-background-color) !important;
border-color: var(--tag-background-color) !important;
border-radius: 20px !important;
}

.hackertab__multi-value__label {
color: var(--tag-text-color) !important;
}

.hackertab__menu {
background-color: var(--card-background-color) !important;
}

.hackertab__option {
color: var(--tag-input-background) !important;
background-color: var(--card-background-color) !important;
}

.hackertab__single-value {
color: var(--primary-text-color) !important;
}
Expand All @@ -96,6 +147,7 @@ Select styles
background: var(--tag-background-color) !important;
color: var(--tag-text-color) !important;
}

.hackertab__multi-value__remove {
border-radius: 20px !important;
color: var(--tag-secondary-color) !important;
Expand All @@ -115,26 +167,32 @@ Select styles
border-radius: 100%;
align-self: flex-start;
}

.userDetails {
display: flex;
flex-direction: row;
gap: 8px;
}

.userInfo {
display: flex;
flex-direction: column;
gap: 8px;
}

.userName {
font-weight: 600;
}

.actions {
margin-top: 6px;
}

.description {
font-size: 0.9em;
opacity: 0.9;
}

.sub {
font-size: 0.9em;
opacity: 0.9;
Expand All @@ -156,12 +214,14 @@ Select styles
padding: 0;
font-size: 0.9em;
}

.icon {
width: 1.6em;
vertical-align: bottom;
position: relative;
top: 2px;
}

.highlight {
font-weight: 900;
color: #ff8f1f;
Expand All @@ -178,6 +238,7 @@ Select styles
position: absolute;
margin-top: 9px;
}

.streaks ul.streaksWeek {
position: relative;
margin: 0;
Expand All @@ -197,9 +258,11 @@ Select styles
width: 48px;
position: relative;
}

.streaks .dayWrapper:last-child {
width: auto;
}

.streaks .dayWrapper .day {
border: 1px solid var(--card-content-divider);
background-color: var(--card-content-divider);
Expand All @@ -209,6 +272,7 @@ Select styles
position: relative;
z-index: 5;
}

.streaks .dayWrapper::before {
content: '';
display: block;
Expand All @@ -223,6 +287,7 @@ Select styles
border: 2px solid #18bc2d;
background-color: #18bc2d;
}

.streaks .checked:is(:has(+ .checked))::before {
background-color: #18bc2d;
}
Expand All @@ -231,10 +296,12 @@ Select styles
.settingContent {
margin-top: 6px;
}

.settingRow {
flex-direction: column;
align-items: flex-start;
}

.userContent {
flex-direction: column;
align-items: flex-start;
Expand Down
Loading