diff --git a/client/package.json b/client/package.json index ee5fa176..e492d5b8 100755 --- a/client/package.json +++ b/client/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@headlessui/react": "^1.7.4", + "@headlessui/react": "^1.7.5", "firebase": "^9.14.0", "he": "^1.2.0", "luxon": "^3.1.0", diff --git a/client/public/service-worker.js b/client/public/service-worker.js index 62eac214..53ab2dc5 100644 --- a/client/public/service-worker.js +++ b/client/public/service-worker.js @@ -1,7 +1,7 @@ // This dummy service worker is to hopefully migrate all users stuck on the old service worker URL // (`/service-worker.js`) to the new URL (`/sw.js`). The old service worker cannot detect the new service worker // because the URL has changed, so it will stop giving updated app versions and fall behind. This dummy worker -// attempts to remedy that by sending an update to the old service worker telling it to self destruct, which will +// attempts to remedy that by sending an update to the old service worker telling it to self-destruct, which will // hopefully cause the new worker to install itself. // Not sure if this is needed because the old service worker logic should already call `skipWaiting()` for us, but diff --git a/client/src/App.tsx b/client/src/App.tsx index 80cb6eae..4fa51a79 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -32,6 +32,7 @@ import PageNotFound from './pages/404'; import SgyAuthRedirect from './pages/SgyAuthRedirect'; // Components +import ThemeHandler from './components/layout/ThemeHandler'; import FaviconHandler from './components/schedule/FaviconHandler'; import InstallModal from './components/layout/InstallModal'; import SgyInitResults from './components/firebase/SgyInitResults'; @@ -123,6 +124,7 @@ export default function App() { navigator.serviceWorker.getRegistration().then(res => res?.update())}/> + {signInCheckResult?.signedIn && } diff --git a/client/src/components/firebase/SgyInitResults.tsx b/client/src/components/firebase/SgyInitResults.tsx index 74580b2e..ab6a0134 100644 --- a/client/src/components/firebase/SgyInitResults.tsx +++ b/client/src/components/firebase/SgyInitResults.tsx @@ -4,7 +4,7 @@ import {Dialog} from '@headlessui/react'; // Components import CenteredModal from '../layout/CenteredModal'; -import OutlineButton, {DangerOutlineButton, SuccessOutlineButton} from '../layout/OutlineButton'; +import OutlineButton, {ThemeOutlineButton, SuccessOutlineButton} from '../layout/OutlineButton'; import Loading from '../layout/Loading'; // Auth @@ -98,16 +98,16 @@ export default function SgyInitResults() {
{results && (confirmDisable ? (<> - + Yes, Disable Schoology - + setConfirm(false)}> Take Me Back! ) : (<> - setConfirm(true)}> + setConfirm(true)}> Disable Schoology - + Looks Good! diff --git a/client/src/components/layout/Badge.tsx b/client/src/components/layout/Badge.tsx index 2b4e4006..10e60625 100644 --- a/client/src/components/layout/Badge.tsx +++ b/client/src/components/layout/Badge.tsx @@ -4,7 +4,7 @@ import {ReactNode} from 'react'; type BadgeProps = {children: ReactNode}; export default function Badge(props: BadgeProps) { return ( - + {props.children} ) diff --git a/client/src/components/layout/HeaderPage.tsx b/client/src/components/layout/HeaderPage.tsx index 280682bd..ce16dd05 100644 --- a/client/src/components/layout/HeaderPage.tsx +++ b/client/src/components/layout/HeaderPage.tsx @@ -28,7 +28,7 @@ export default function HeaderPage(props: HeaderPageProps) { export function Header(props: {children: ReactNode}) { return ( -
+
{props.children}
) diff --git a/client/src/components/layout/Logo.tsx b/client/src/components/layout/Logo.tsx index 5abbe7ea..a9de755a 100644 --- a/client/src/components/layout/Logo.tsx +++ b/client/src/components/layout/Logo.tsx @@ -1,6 +1,14 @@ +import {useContext} from 'react'; +import UserDataContext from '../../contexts/UserDataContext'; + + export default function Logo(props: {className?: string}) { - const foregroundColor = '#a51618'; - const backgroundColor = '#7f1618'; + const userData = useContext(UserDataContext); + + // TODO: see todo in `Wave.tsx` + const colors = userData.options.theme === 'dark' ? userData.colors.dark : userData.colors.light; + const foregroundColor = userData.colors.id !== 'default' ? colors.theme : '#a51618'; + const backgroundColor = userData.colors.id !== 'default' ? colors.accent : '#7f1618'; return ( + ) diff --git a/client/src/components/layout/Sidebar.tsx b/client/src/components/layout/Sidebar.tsx index 67df7b6d..620212e5 100644 --- a/client/src/components/layout/Sidebar.tsx +++ b/client/src/components/layout/Sidebar.tsx @@ -1,12 +1,12 @@ import {useEffect, useState} from 'react'; import {Link} from 'react-router-dom'; import {useScreenType} from '../../hooks/useScreenType'; -import Logo from './Logo'; // Authentication import {useSigninCheck} from 'reactfire'; // Components +import Logo from './Logo'; import SidebarItem from './SidebarItem'; import GoogleSignInBtn from '../firebase/GoogleSignInBtn'; import GoogleSignOutBtn from '../firebase/GoogleSignOutBtn'; diff --git a/client/src/components/layout/ThemeHandler.tsx b/client/src/components/layout/ThemeHandler.tsx new file mode 100644 index 00000000..b681edaa --- /dev/null +++ b/client/src/components/layout/ThemeHandler.tsx @@ -0,0 +1,69 @@ +import {useContext} from 'react'; +import UserDataContext from '../../contexts/UserDataContext'; +import {hexToRgb} from '../../util/progressBarColor'; + + +type Colors = { theme: string, accent: string, shadow: string }; +export type ColorTheme = { id: 'default' | 'goldenrod' | 'electric-violet', dark: Colors, light: Colors } + +export default function ThemeHandler() { + const {colors} = useContext(UserDataContext); + + return ( + + ) +} + +export const defaultTheme: ColorTheme = { + id: 'default', + dark: { + theme: '#ff594c', + accent: '#eb144c', + shadow: '#b91c1c' + }, + light: { + theme: '#a51618', + accent: '#b91c1c', + shadow: '#b91c1c' + } +} + +export const goldenrod: ColorTheme = { + id: 'goldenrod', + dark: { + theme: '#f59e0b', + accent: '#ea580c', + shadow: '#c2410c' + }, + light: { + theme: '#f59e0b', + accent: '#f97316', + shadow: '#c2410c' + } +} + +export const electricViolet: ColorTheme = { + id: 'electric-violet', + dark: { + theme: '#e879f9', + accent: '#9024f5', + shadow: '#6b21a8' + }, + // TODO: play around with these and make better + light: { + theme: '#913399', + accent: '#5d30a6', + shadow: '#7e22ce' + } +} diff --git a/client/src/components/layout/Wave.tsx b/client/src/components/layout/Wave.tsx index 2cbe7e7d..207f4b5a 100644 --- a/client/src/components/layout/Wave.tsx +++ b/client/src/components/layout/Wave.tsx @@ -1,6 +1,14 @@ +import {useContext} from 'react'; +import UserDataContext from '../../contexts/UserDataContext'; + + export default function Wave() { - const leftColor = 'ff594c'; - const rightColor = 'eb144c'; + const userData = useContext(UserDataContext); + + // TODO: make cleaner, extract logic? + const colors = userData.options.theme === 'dark' ? userData.colors.dark : userData.colors.light; + const leftColor = userData.colors.id !== 'default' ? colors.theme : '#ff594c'; + const rightColor = userData.colors.id !== 'default' ? colors.accent : '#eb144c'; return ( // Constrain the width between 800px and 100vw as a hack for phone aspect ratio. @@ -9,8 +17,8 @@ export default function Wave() { - - + + - - + + - - + + Check In - setIsOpen(false)}> + setIsOpen(false)}> Close - +
) diff --git a/client/src/components/lists/CourseComponent.tsx b/client/src/components/lists/CourseComponent.tsx index c4231875..a30e22b0 100644 --- a/client/src/components/lists/CourseComponent.tsx +++ b/client/src/components/lists/CourseComponent.tsx @@ -4,7 +4,7 @@ import {Course} from '@watt/shared/data/courses'; // Components import CenteredModal from '../layout/CenteredModal'; -import {DangerOutlineButton} from '../layout/OutlineButton'; +import {ThemeOutlineButton} from '../layout/OutlineButton'; export default function CourseComponent(props: Course) { @@ -58,9 +58,9 @@ export default function CourseComponent(props: Course) {
- setModal(false)}> + setModal(false)}> Close - +
diff --git a/client/src/components/lists/StaffComponent.tsx b/client/src/components/lists/StaffComponent.tsx index 8e7feaf5..51880ad4 100644 --- a/client/src/components/lists/StaffComponent.tsx +++ b/client/src/components/lists/StaffComponent.tsx @@ -4,7 +4,7 @@ import {Staff} from '@watt/shared/data/staff'; // Components import CenteredModal from '../layout/CenteredModal'; -import OutlineButton, {DangerOutlineButton} from '../layout/OutlineButton'; +import OutlineButton, {ThemeOutlineButton} from '../layout/OutlineButton'; import PillClubComponent from './PillClubComponent'; // Context @@ -87,9 +87,9 @@ export default function StaffComponent(props: Staff & {id: string}) { Add to my list )} - setModal(false)}> + setModal(false)}> Close - + diff --git a/client/src/contexts/UserDataContext.ts b/client/src/contexts/UserDataContext.ts index b5a89a67..de0b3694 100644 --- a/client/src/contexts/UserDataContext.ts +++ b/client/src/contexts/UserDataContext.ts @@ -1,5 +1,6 @@ import {createContext} from 'react'; import {AllSgyPeriod} from './SgyDataContext'; +import {ColorTheme, defaultTheme} from '../components/layout/ThemeHandler'; // Represents a course on Schoology. @@ -27,7 +28,11 @@ export type UserData = { 5: SgyPeriodData, 6: SgyPeriodData, 7: SgyPeriodData, 8: SgyPeriodData, P: SgyPeriodData, S: SgyPeriodData, H: SgyPeriodData }, - options: {theme: string, time: string, period0: boolean, period8: boolean, clock: boolean, sgy: boolean}, + options: { + theme: string, time: string, period0: boolean, period8: boolean, + clock: boolean, sgy: boolean + }, + colors: ColorTheme id: string, gradYear: number, // The year (eg. 2023) or `0` if unset sgy: { @@ -68,6 +73,7 @@ export const defaultUserData: UserData = { clock: true, sgy: false }, + colors: defaultTheme, id: '00000', gradYear: 0, sgy: { diff --git a/client/src/pages/Testing.tsx b/client/src/pages/Testing.tsx index 17379d08..3ad7cf89 100644 --- a/client/src/pages/Testing.tsx +++ b/client/src/pages/Testing.tsx @@ -2,7 +2,7 @@ import {ReactNode, useContext, useEffect, useState} from 'react'; // Components import CenteredMessage from '../components/layout/CenteredMessage'; -import OutlineButton, {DangerOutlineButton, SuccessOutlineButton} from '../components/layout/OutlineButton'; +import OutlineButton, {ThemeOutlineButton, SuccessOutlineButton} from '../components/layout/OutlineButton'; import Period from '../components/schedule/Period'; import Loading from '../components/layout/Loading'; import WIP from '../components/layout/WIP'; @@ -127,7 +127,7 @@ export default function Testing() { Add to my list - Disable Schoology + Disable Schoology Looks good! diff --git a/client/src/pages/settings/Appearance.tsx b/client/src/pages/settings/Appearance.tsx index 69ebbaf1..5caa6f5a 100644 --- a/client/src/pages/settings/Appearance.tsx +++ b/client/src/pages/settings/Appearance.tsx @@ -9,6 +9,7 @@ import CurrentTimeContext from '../../contexts/CurrentTimeContext'; // Firestore import {useAuth, useFirestore} from 'reactfire'; import { updateUserData } from '../../util/firestore'; +import {ColorTheme, defaultTheme, goldenrod, electricViolet} from '../../components/layout/ThemeHandler'; export default function Appearance() { @@ -23,6 +24,8 @@ export default function Appearance() { const changeTheme = async (theme: string) => await updateUserData('options.theme', theme, auth, firestore); const changeTime = async (time: string) => await updateUserData('options.time', time, auth, firestore); + const changeColors = async (colors: ColorTheme) => + await updateUserData('colors', colors, auth, firestore); return ( @@ -40,6 +43,18 @@ export default function Appearance() { + + + WATT's classic, red look. + + + A golden theme for golden students. + + + High-amp action! + + + 12-hour time ({currTime.toFormat('h:mm:ss a')}). @@ -54,11 +69,11 @@ export default function Appearance() { } // TODO: should these be extracted as layout components? -type RadioCardsProps = { - value: string, onChange: (value: string) => void, +type RadioCardsProps = { + value: T, onChange: (value: T) => void, by?: (keyof T & string) | ((a: T, b: T) => boolean), label: string, children: ReactNode } -function RadioCards(props: RadioCardsProps) { +function RadioCards(props: RadioCardsProps) { const {label, children, ...radioGroupProps} = props; return ( @@ -92,3 +107,29 @@ function RadioCard(props: RadioCardProps) { ) } + +function ColorCard(props: {label: string, value: ColorTheme, children: ReactNode}) { + const {label, value, children} = props; + + const userData = useContext(UserDataContext); + const colors = userData.options.theme === 'dark' ? value.dark : value.light; + + return ( + + {({checked}) => (<> + +
+
+
+
+
+
+ {label} + + {children} + +
+ )} + + ) +} diff --git a/client/tailwind.config.js b/client/tailwind.config.js index 870cd740..a81b7a7e 100644 --- a/client/tailwind.config.js +++ b/client/tailwind.config.js @@ -8,6 +8,8 @@ module.exports = { extend: { colors: { theme: 'rgb(var(--theme) / )', + 'theme-secondary': 'rgb(var(--theme-secondary) / )', + 'theme-tertiary': 'rgb(var(--theme-tertiary) / )', primary: 'rgb(var(--primary))', // NOTE: `primary`, `secondary`, and `tertiary` do *not* work with opacity modifiers. secondary: 'rgb(var(--secondary))', tertiary: 'rgb(var(--tertiary))', diff --git a/package-lock.json b/package-lock.json index ccfb23c7..cadea92c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "name": "@watt/client", "version": "0.1.0", "dependencies": { - "@headlessui/react": "^1.7.4", + "@headlessui/react": "^1.7.5", "firebase": "^9.14.0", "he": "^1.2.0", "luxon": "^3.1.0", @@ -2705,9 +2705,9 @@ } }, "node_modules/@headlessui/react": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.4.tgz", - "integrity": "sha512-D8n5yGCF3WIkPsjEYeM8knn9jQ70bigGGb5aUvN6y4BGxcT3OcOQOKcM3zRGllRCZCFxCZyQvYJF6ZE7bQUOyQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.5.tgz", + "integrity": "sha512-UZSxOfA0CYKO7QDT5OGlFvesvlR1SKkawwSjwQJwt7XQItpzRKdE3ZUQxHcg4LEz3C0Wler2s9psdb872ynwrQ==", "dependencies": { "client-only": "^0.0.1" }, @@ -11084,9 +11084,9 @@ } }, "@headlessui/react": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.4.tgz", - "integrity": "sha512-D8n5yGCF3WIkPsjEYeM8knn9jQ70bigGGb5aUvN6y4BGxcT3OcOQOKcM3zRGllRCZCFxCZyQvYJF6ZE7bQUOyQ==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.5.tgz", + "integrity": "sha512-UZSxOfA0CYKO7QDT5OGlFvesvlR1SKkawwSjwQJwt7XQItpzRKdE3ZUQxHcg4LEz3C0Wler2s9psdb872ynwrQ==", "requires": { "client-only": "^0.0.1" } @@ -11630,7 +11630,7 @@ "@watt/client": { "version": "file:client", "requires": { - "@headlessui/react": "^1.7.4", + "@headlessui/react": "1.7.5", "@types/he": "^1.1.2", "@types/luxon": "^3.1.0", "@types/react": "^18.0.15",