diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 834d9a7..4426b5f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,10 +6,10 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
- node-version: 10.x
+ node-version: 12.x
- run: |
make install
- run: |
diff --git a/components/Button/style.scss b/components/Button/style.scss
index 2aa224f..a8749a8 100644
--- a/components/Button/style.scss
+++ b/components/Button/style.scss
@@ -70,6 +70,9 @@ button.Button {
@media (prefers-color-scheme: dark) {
background-color: $color-text-gray;
}
+ @at-root .theme-dark #{&} {
+ background-color: $color-text-gray;
+ }
&:focus,
&:hover {
diff --git a/components/Layout/Footer.scss b/components/Layout/Footer.scss
index 52cb96a..a466096 100644
--- a/components/Layout/Footer.scss
+++ b/components/Layout/Footer.scss
@@ -10,6 +10,9 @@
@media (prefers-color-scheme: dark) {
background-color: #161616;
}
+ @at-root .theme-dark #{&} {
+ background-color: #161616;
+ }
&__faq {
display: block;
@@ -68,12 +71,18 @@
@media (prefers-color-scheme: dark) {
color: $color-menu-background-hover;
}
+ @at-root .theme-dark #{&} {
+ color: $color-menu-background-hover;
+ }
&:hover,
&:focus {
@media (prefers-color-scheme: dark) {
color: #f3f3f3;
}
+ @at-root .theme-dark #{&} {
+ color: #f3f3f3;
+ }
}
}
}
diff --git a/components/Layout/Footer.tsx b/components/Layout/Footer.tsx
index b9de4d0..4421917 100644
--- a/components/Layout/Footer.tsx
+++ b/components/Layout/Footer.tsx
@@ -28,7 +28,7 @@ const Footer = () => {
Where's the code for this web app?
-
+
It's in GitHub
.
diff --git a/components/Panels/All.tsx b/components/Panels/All.tsx
index faa2e2f..c944cdd 100644
--- a/components/Panels/All.tsx
+++ b/components/Panels/All.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useRef } from 'react';
+import React, { useState, useRef, useEffect } from 'react';
import moment from 'moment';
import styled from 'styled-components';
import { useAsync } from 'react-use';
@@ -42,6 +42,7 @@ const All = () => {
const [monthInView, setMonthInView] = useState(moment().format('YYYY-MM'));
const [currency, setCurrency] = useState('USD');
const [syncToken, setSyncToken] = useState('');
+ const [theme, setTheme] = useState('light');
const [budgets, setBudgets] = useState([]);
const [expenses, setExpenses] = useState([]);
const db = useRef(null);
@@ -113,6 +114,7 @@ const All = () => {
if (typeof window !== 'undefined') {
const userInfo = getUserInfo();
setCurrency(userInfo.currency);
+ setTheme(userInfo.theme || 'light');
setSyncToken(userInfo.syncToken);
const initializedDb = await initializeDb(userInfo.syncToken);
@@ -126,6 +128,13 @@ const All = () => {
}
}, []);
+ useEffect(() => {
+ if (theme === 'dark') {
+ document.getElementsByTagName('html')[0].classList.add('theme-dark');
+ document.getElementsByTagName('body')[0].classList.add('theme-dark');
+ }
+ }, [theme]);
+
return (
@@ -159,6 +168,8 @@ const All = () => {
updateCurrency={setCurrency}
syncToken={syncToken}
db={db.current}
+ currentTheme={theme}
+ updateTheme={setTheme}
/>
diff --git a/components/Panels/Settings.tsx b/components/Panels/Settings.tsx
index e7f0cce..e1b8a7c 100644
--- a/components/Panels/Settings.tsx
+++ b/components/Panels/Settings.tsx
@@ -16,6 +16,8 @@ import appPackage from '../../package.json';
interface SettingsProps {
currentCurrency: T.Currency;
updateCurrency: (currency: T.Currency) => void;
+ currentTheme: T.Theme;
+ updateTheme: (theme: T.Theme) => void;
syncToken: string;
db: RxDatabase;
}
@@ -78,9 +80,14 @@ const HelpButton = styled(Button)`
const currencyLabels = ['$', '€', '£'];
const currencyValues: T.Currency[] = ['USD', 'EUR', 'GBP'];
+const themeLabels = ['Light', 'Dark'];
+const themeValues: T.Theme[] = ['light', 'dark'];
+
const Settings = ({
currentCurrency,
updateCurrency,
+ currentTheme,
+ updateTheme,
syncToken,
db,
}: SettingsProps) => {
@@ -88,11 +95,16 @@ const Settings = ({
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
const [isImportExportModalOpen, setIsImportExportModalOpen] = useState(false);
const [currency, setCurrency] = useState(currentCurrency);
+ const [theme, setTheme] = useState(currentTheme);
useEffect(() => {
setCurrency(currentCurrency);
}, [currentCurrency]);
+ useEffect(() => {
+ setTheme(currentTheme);
+ }, [currentTheme]);
+
const saveCurrency = async (newCurrency: T.Currency) => {
if (isSubmitting) {
// Ignore sequential taps
@@ -101,7 +113,7 @@ const Settings = ({
setIsSubmitting(true);
- const success = await doLogin(syncToken, newCurrency);
+ const success = doLogin(syncToken, newCurrency, currentTheme);
if (success) {
updateCurrency(newCurrency);
@@ -113,10 +125,34 @@ const Settings = ({
}
};
+ const saveTheme = async (newTheme: T.Theme) => {
+ if (isSubmitting) {
+ // Ignore sequential taps
+ return;
+ }
+
+ setIsSubmitting(true);
+
+ const success = doLogin(syncToken, currentCurrency, newTheme);
+
+ if (success) {
+ updateTheme(newTheme);
+ return;
+ }
+
+ if (success) {
+ showNotification('Settings saved successfully.');
+ }
+ };
+
const selectedCurrencyIndex = currencyValues.findIndex(
(_currency) => currency === _currency,
);
+ const selectedThemeIndex = themeValues.findIndex(
+ (_theme) => theme === _theme,
+ );
+
return (
<>
+
+ {
+ setTheme(themeValues[selectedSegmentIndex]);
+ saveTheme(themeValues[selectedSegmentIndex]);
+ }}
+ />
v{appVersion}-{appBuild}
diff --git a/components/TextInput/style.scss b/components/TextInput/style.scss
index 01261bb..0b917b2 100644
--- a/components/TextInput/style.scss
+++ b/components/TextInput/style.scss
@@ -15,6 +15,9 @@ $transition-speed: 140ms;
@media (prefers-color-scheme: dark) {
color: $color-link-hover;
}
+ @at-root .theme-dark #{&} {
+ color: $color-link-hover;
+ }
}
&__input {
diff --git a/lib/constants.ts b/lib/constants.ts
index 7d4de6b..70fb5b0 100644
--- a/lib/constants.ts
+++ b/lib/constants.ts
@@ -1,3 +1,5 @@
+import { getUserInfo } from 'lib/utils';
+
export const defaultTitle = 'Budget Zen — Simple and Easy Budget Management';
export const defaultDescription = 'Simple and easy budget management.';
export const defaultKeywords =
@@ -8,15 +10,28 @@ export const sessionNamespace = 'BudgetZen_appSession';
type Theme = 'dark' | 'light';
export const colors = (theme: Theme = 'light') => {
- if (
- typeof window !== 'undefined' &&
- typeof window.matchMedia === 'function'
- ) {
- if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ const userInfo = getUserInfo();
+
+ if (typeof window !== 'undefined') {
+ if (
+ typeof window.matchMedia === 'function' &&
+ window.matchMedia('(prefers-color-scheme: dark)').matches
+ ) {
theme = 'dark';
}
}
+ if (
+ typeof document !== 'undefined' &&
+ document.getElementsByTagName('body')[0].classList.contains('theme-dark')
+ ) {
+ theme = 'dark';
+ }
+
+ if (userInfo.theme === 'dark') {
+ theme = 'dark';
+ }
+
return {
inputLabel: theme === 'dark' ? '#fff' : '#000',
inputField: theme === 'dark' ? '#666' : '#666',
diff --git a/lib/types.ts b/lib/types.ts
index 716d8eb..b2d13ca 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -6,9 +6,12 @@ export interface PlainObject {
export type Currency = 'USD' | 'EUR' | 'GBP';
+export type Theme = 'dark' | 'light';
+
export interface AuthToken {
syncToken: string;
currency: Currency;
+ theme?: Theme;
}
export interface Expense {
diff --git a/lib/utils.ts b/lib/utils.ts
index 79bac63..a562d2a 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,7 +1,7 @@
import Swal from 'sweetalert2';
import { sessionNamespace } from 'lib/constants';
-import { AuthToken, Currency } from 'lib/types';
+import { AuthToken, Currency, Theme } from 'lib/types';
export const formatNumber = (currency: Currency = 'USD', number: number) =>
new Intl.NumberFormat('en-US', {
@@ -85,10 +85,15 @@ export const showNotification = (
});
};
-export const doLogin = (syncToken: string, currency: Currency = 'USD') => {
+export const doLogin = (
+ syncToken: string,
+ currency: Currency = 'USD',
+ theme: Theme = 'light',
+) => {
const authToken: AuthToken = {
syncToken,
currency,
+ theme,
};
try {
@@ -140,6 +145,7 @@ export const getUserInfo: GetUserInfo = () => {
return {
syncToken: null,
currency: 'USD',
+ theme: 'light',
};
};
diff --git a/serverless.yml b/serverless.yml
index 2ef36e3..9884f55 100644
--- a/serverless.yml
+++ b/serverless.yml
@@ -2,7 +2,7 @@ service:
name: budgetzen-web
myNextAppplication:
- component: "@sls-next/serverless-component@1.18.0"
+ component: "@sls-next/serverless-component@3.6.0"
inputs:
domain: ["app", "budgetzen.net"]
# bucketName: budgetzen-web
diff --git a/styles/__base.scss b/styles/__base.scss
index 51d740c..1fe79fc 100644
--- a/styles/__base.scss
+++ b/styles/__base.scss
@@ -12,6 +12,10 @@ body {
background: #101010;
color: #f3f3f3;
}
+ &.theme-dark {
+ background: #101010;
+ color: #f3f3f3;
+ }
}
a {
@@ -22,6 +26,9 @@ a {
@media (prefers-color-scheme: dark) {
color: $color-link-hover;
}
+ @at-root .theme-dark #{&} {
+ color: $color-link-hover;
+ }
&:hover,
&:focus {
@@ -31,6 +38,9 @@ a {
@media (prefers-color-scheme: dark) {
color: $color-menu-background-hover;
}
+ @at-root .theme-dark #{&} {
+ color: $color-menu-background-hover;
+ }
}
&.style-less {
@@ -52,6 +62,9 @@ pre {
@media (prefers-color-scheme: dark) {
background-color: rgba(0, 0, 0, 0.8);
}
+ @at-root .theme-dark #{&} {
+ background-color: rgba(0, 0, 0, 0.8);
+ }
}
.wrapper {
@@ -116,6 +129,9 @@ button {
@media (prefers-color-scheme: dark) {
background-color: $color-text;
}
+ @at-root .theme-dark #{&} {
+ background-color: $color-text;
+ }
}
// Tweak CSS for switch