diff --git a/web/components/nav/sidebar.tsx b/web/components/nav/sidebar.tsx index a30df6d..28a6e0f 100644 --- a/web/components/nav/sidebar.tsx +++ b/web/components/nav/sidebar.tsx @@ -1,61 +1,50 @@ -import { - LogoutIcon, - MoonIcon, - SunIcon, - LoginIcon, -} from '@heroicons/react/outline' +import {LoginIcon, LogoutIcon,} from '@heroicons/react/outline' import clsx from 'clsx' -import { buildArray } from 'common/util/array' -import Router, { useRouter } from 'next/router' -import { useUser } from 'web/hooks/use-user' -import { firebaseLogin, firebaseLogout } from 'web/lib/firebase/users' -import { withTracking } from 'web/lib/service/analytics' -import { ProfileSummary } from './profile-summary' -import { Item, SidebarItem } from './sidebar-item' +import {buildArray} from 'common/util/array' +import Router, {useRouter} from 'next/router' +import {useUser} from 'web/hooks/use-user' +import {firebaseLogout} from 'web/lib/firebase/users' +import {withTracking} from 'web/lib/service/analytics' +import {ProfileSummary} from './profile-summary' +import {Item, SidebarItem} from './sidebar-item' import SiteLogo from '../site-logo' -import { Button, ColorType, SizeType } from 'web/components/buttons/button' +import {Button, ColorType, SizeType} from 'web/components/buttons/button' import {signupRedirect} from 'web/lib/util/signup' -import { useProfile } from 'web/hooks/use-profile' -import { useTheme } from 'web/hooks/use-theme' +import {useProfile} from 'web/hooks/use-profile' export default function Sidebar(props: { className?: string isMobile?: boolean navigationOptions: Item[] }) { - const { className, isMobile } = props + const {className, isMobile} = props const router = useRouter() const currentPage = router.pathname const user = useUser() const profile = useProfile() - const { theme, setTheme } = useTheme() - - const toggleTheme = () => { - setTheme(theme === 'auto' ? 'dark' : theme === 'dark' ? 'light' : 'auto') - } const navOptions = props.navigationOptions - const bottomNavOptions = bottomNav(!!user, theme, toggleTheme) + const bottomNavOptions = bottomNav(!!user) return ( @@ -82,39 +71,10 @@ const logout = async () => { const bottomNav = ( loggedIn: boolean, - theme: 'light' | 'dark' | 'auto' | 'loading', - toggleTheme: () => void ) => buildArray( - { - name: theme ?? 'auto', - children: - theme === 'light' ? ( - 'Light' - ) : theme === 'dark' ? ( - 'Dark' - ) : ( - <> - Dark - Light (auto) - - ), - icon: ({ className, ...props }) => ( - <> - - - - ), - onClick: toggleTheme, - }, - !loggedIn && { name: 'Sign in', icon: LoginIcon, href: '/signin' }, - loggedIn && { name: 'Sign out', icon: LogoutIcon, onClick: logout } + !loggedIn && {name: 'Sign in', icon: LoginIcon, href: '/signin'}, + loggedIn && {name: 'Sign out', icon: LogoutIcon, onClick: logout} ) export const SignUpButton = (props: { @@ -123,7 +83,7 @@ export const SignUpButton = (props: { color?: ColorType size?: SizeType }) => { - const { className, text, color, size } = props + const {className, text, color, size} = props return ( - ) -} +// export const SignUpAsMatchmaker = (props: { +// className?: string +// size?: SizeType +// }) => { +// const {className, size} = props +// +// return ( +// +// ) +// } diff --git a/web/components/page-base.tsx b/web/components/page-base.tsx index c8e8588..1a97c4a 100644 --- a/web/components/page-base.tsx +++ b/web/components/page-base.tsx @@ -1,5 +1,6 @@ import {HomeIcon, NewspaperIcon, QuestionMarkCircleIcon} from '@heroicons/react/outline' import { + CogIcon, GlobeAltIcon, HomeIcon as SolidHomeIcon, LinkIcon, @@ -118,6 +119,7 @@ const Organization = {name: 'Organization', href: '/organization', icon: GlobeAl const Vote = {name: 'Vote', href: '/vote', icon: MdThumbUp}; const Contact = {name: 'Contact', href: '/contact', icon: FaEnvelope}; const News = {name: "What's new", href: '/news', icon: NewspaperIcon}; +const Settings = {name: "Settings", href: '/settings', icon: CogIcon}; const base = [ About, @@ -144,7 +146,7 @@ function getBottomNavigation(user: User, profile: Profile | null | undefined) { icon: (props) => ( ), - } + }, ) } @@ -160,6 +162,7 @@ const getDesktopNavigation = (user: User | null | undefined) => { ProfilesHome, Notifs, Messages, + Settings, ...base, ) @@ -170,6 +173,7 @@ const getDesktopNavigation = (user: User | null | undefined) => { const getMobileSidebar = (_toggleModal: () => void) => { return buildArray( + Settings, ...base, ) } diff --git a/web/components/profile/profile-header.tsx b/web/components/profile/profile-header.tsx index 7325212..4bce785 100644 --- a/web/components/profile/profile-header.tsx +++ b/web/components/profile/profile-header.tsx @@ -1,7 +1,6 @@ import {DotsHorizontalIcon, EyeIcon, LockClosedIcon, PencilIcon} from '@heroicons/react/outline' import clsx from 'clsx' import Router from 'next/router' -import router from 'next/router' import Link from 'next/link' import {User, UserActivity} from 'common/user' import {Button} from 'web/components/buttons/button' @@ -20,7 +19,6 @@ import {linkClass} from 'web/components/widgets/site-link' import {updateProfile} from 'web/lib/api' import {useState} from 'react' import {VisibilityConfirmationModal} from './visibility-confirmation-modal' -import {deleteAccount} from "web/lib/util/delete"; import toast from "react-hot-toast"; import {StarButton} from "web/components/widgets/star-button"; import {disableProfile} from "web/lib/util/disable"; @@ -57,7 +55,8 @@ export default function ProfileHeader(props: { - {currentUser && isCurrentUser && disabled &&
You disabled your profile, so no one else can access it.
} + {currentUser && isCurrentUser && disabled && +
You disabled your profile, so no one else can access it.
} {!isCurrentUser && } @@ -110,34 +109,6 @@ export default function ProfileHeader(props: { ), onClick: () => setShowVisibilityModal(true), }, - { - name: 'Delete profile', - icon: null, - onClick: async () => { - const confirmed = confirm( - 'Are you sure you want to delete your profile? This cannot be undone.' - ) - if (confirmed) { - toast - .promise(deleteAccount(user.username), { - loading: 'Deleting account...', - success: () => { - router.push('/') - return 'Your account has been deleted.' - }, - error: () => { - return 'Failed to delete account.' - }, - }) - .then(() => { - // return true - }) - .catch(() => { - // return false - }) - } - }, - }, { name: disabled ? 'Enable profile' : 'Disable profile', icon: null, diff --git a/web/components/theme-icon.tsx b/web/components/theme-icon.tsx new file mode 100644 index 0000000..db2eac3 --- /dev/null +++ b/web/components/theme-icon.tsx @@ -0,0 +1,38 @@ +import {MoonIcon, SunIcon} from "@heroicons/react/outline" +import clsx from "clsx" +import {useTheme} from "web/hooks/use-theme" +import {Row} from "web/components/layout/row"; + +export default function ThemeIcon(props: { + className?: string +}) { + const {className} = props + const {theme, setTheme} = useTheme() + + const toggleTheme = () => { + setTheme(theme === 'auto' ? 'dark' : theme === 'dark' ? 'light' : 'auto') + } + const children = theme === 'light' ? ( + 'Light' + ) : theme === 'dark' ? ( + 'Dark' + ) : ( + <> + Dark + Light (auto) + + ) + const icon = <> + + + + return +} + + + diff --git a/web/pages/notifications.tsx b/web/pages/notifications.tsx index 728f6ce..9a8a965 100644 --- a/web/pages/notifications.tsx +++ b/web/pages/notifications.tsx @@ -13,6 +13,7 @@ import {useGroupedNotifications} from 'web/hooks/use-notifications' import {usePrivateUser, useUser} from 'web/hooks/use-user' import {api} from 'web/lib/api' import {useRedirectIfSignedOut} from "web/hooks/use-redirect-if-signed-out"; +import {NotificationSettings} from "web/components/notifications"; export default function NotificationsPage() { useRedirectIfSignedOut() @@ -23,6 +24,7 @@ export default function NotificationsPage() { }, + {title: 'Settings', content: }, ]} trackingName={'notifications page'} /> diff --git a/web/pages/settings.tsx b/web/pages/settings.tsx index 5af93c7..c3633b5 100644 --- a/web/pages/settings.tsx +++ b/web/pages/settings.tsx @@ -1,33 +1,32 @@ import {PrivateUser} from 'common/src/user' -import { - notification_destination_types, - notification_preference, - notification_preferences, -} from 'common/user-notification-preferences' -import {useCallback} from 'react' import {NoSEO} from 'web/components/NoSEO' import {UncontrolledTabs} from 'web/components/layout/tabs' import {PageBase} from 'web/components/page-base' import {Title} from 'web/components/widgets/title' import {usePrivateUser} from 'web/hooks/use-user' -import {api} from 'web/lib/api' -import {MultiSelectAnswers} from 'web/components/answers/answer-compatibility-question-content' -import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state' -import {debounce} from 'lodash' import {useRedirectIfSignedOut} from "web/hooks/use-redirect-if-signed-out"; +import toast from "react-hot-toast"; +import {deleteAccount} from "web/lib/util/delete"; +import router from "next/router"; +import {Button} from "web/components/buttons/button"; +import {getAuth, sendEmailVerification, sendPasswordResetEmail, User} from 'firebase/auth'; +import {auth} from "web/lib/firebase/users"; +import {NotificationSettings} from "web/components/notifications"; +import ThemeIcon from "web/components/theme-icon"; export default function NotificationsPage() { useRedirectIfSignedOut() const privateUser = usePrivateUser() - if (!privateUser) return null + const user = auth.currentUser + if (!privateUser || !user) return null return ( Settings }, - {title: 'Notifications', content: }, + {title: 'General', content: }, + {title: 'Notifications', content: }, ]} trackingName={'settings page'} /> @@ -35,140 +34,97 @@ export default function NotificationsPage() { ) } -const AccountSettings = (props: { privateUser: PrivateUser }) => { - const {privateUser} = props - return <> -} - -const NotificationSettings = (props: { privateUser: PrivateUser }) => { - const {privateUser} = props - - const [prefs, setPrefs] = - usePersistentInMemoryState( - privateUser.notificationPreferences, - 'notification-preferences' - ) - - const notificationTypes: { - type: notification_preference - question: string - }[] = [ - { - type: 'new_match', - question: - 'Where do you want to be notified when someone ... matches with you?', - }, - { - type: 'new_message', - question: '... sends you a new message?', - }, - { - type: 'new_profile_like', - question: '... likes your profile?', - }, - { - type: 'new_endorsement', - question: '... endorses you?', - }, - { - type: 'new_profile_ship', - question: '... ships you?', - }, - { - type: 'tagged_user', - question: '... mentions you?', - }, - { - type: 'on_new_follow', - question: '... follows you?', - }, - { - type: 'new_search_alerts', - question: 'Alerts from bookmarked searches?', - }, - { - type: 'opt_out_all', - question: - 'Do you want to opt out of all notifications? (You can always change this later)?', - }, - ] - - return ( -
-
- {notificationTypes.map(({type, question}) => ( - { - setPrefs((prevPrefs) => ({...prevPrefs, [type]: selected})) - }} - /> - ))} -
-
- ) -} - -const NotificationOption = (props: { - type: notification_preference - question: string - selected: notification_destination_types[] - onUpdate: (selected: notification_destination_types[]) => void +const GeneralSettings = (props: { + privateUser: PrivateUser, + user: User, }) => { - const {type, question, selected, onUpdate} = props + const {privateUser, user} = props - const getSelectedValues = (destinations: string[]) => { - const values: number[] = [] - if ((destinations ?? []).includes('email')) values.push(0) - if ((destinations ?? []).includes('browser')) values.push(1) - return values - } - - const setValue = async (value: number[]) => { - const newDestinations: notification_destination_types[] = [] - if (value.includes(0)) newDestinations.push('email') - if (value.includes(1)) newDestinations.push('browser') - - onUpdate(newDestinations) - save(selected, newDestinations) - } - - const save = useCallback( - debounce( - ( - oldDestinations: notification_destination_types[], - newDestinations: notification_destination_types[] - ) => { - // for each medium, if it changed, trigger a save - const mediums = ['email', 'browser'] as const - mediums.forEach((medium) => { - const wasEnabled = oldDestinations.includes(medium) - const isEnabled = newDestinations.includes(medium) - if (wasEnabled !== isEnabled) { - api('update-notif-settings', { - type, - medium, - enabled: isEnabled, - }) - } + const handleDeleteAccount = async () => { + const confirmed = confirm( + 'Are you sure you want to delete your profile? This cannot be undone.' + ) + if (confirmed) { + toast + .promise(deleteAccount(), { + loading: 'Deleting account...', + success: () => { + router.push('/') + return 'Your account has been deleted.' + }, + error: () => { + return 'Failed to delete account.' + }, }) - }, - 500 - ), - [] - ) + .catch(() => { + console.log("Failed to delete account") + }) + } + } - return ( -
-
{question}
- + const sendPasswordReset = async () => { + if (!privateUser?.email) { + toast.error('No email found on your account.') + return + } + const auth = getAuth() + toast.promise( + sendPasswordResetEmail(auth, privateUser.email), + { + loading: 'Sending password reset email...', + success: 'Password reset email sent — check your inbox and spam.', + error: 'Failed to send password reset email.', + } + ) + .catch(() => { + console.log("Failed to send password reset email") + }) + } + + const sendVerificationEmail = async () => { + if (!privateUser?.email) { + toast.error('No email found on your account.') + return + } + if (!user) { + toast.error('You must be signed in to send a verification email.') + return + } + toast + .promise(sendEmailVerification(user), { + loading: 'Sending verification email...', + success: 'Verification email sent — check your inbox and spam.', + error: 'Failed to send verification email.', + }) + .catch(() => { + console.log("Failed to send verification email") + }) + } + + const isEmailVerified = user.emailVerified + + return <> +
+

Theme

+ +

Account

+
Credentials
+ + +
Verification
+ + +
Dangerous
+
- ) + } +