512 lines
15 KiB
TypeScript
512 lines
15 KiB
TypeScript
/**
|
|
* @file Setting page
|
|
*/
|
|
|
|
import {
|
|
IonButtons,
|
|
IonContent,
|
|
IonHeader,
|
|
IonIcon,
|
|
IonItem,
|
|
IonItemDivider,
|
|
IonItemGroup,
|
|
IonLabel,
|
|
IonList,
|
|
IonMenuButton,
|
|
IonModal,
|
|
IonNote,
|
|
IonPage,
|
|
IonSelect,
|
|
IonSelectOption,
|
|
IonTitle,
|
|
IonToggle,
|
|
IonToolbar,
|
|
isPlatform,
|
|
useIonActionSheet,
|
|
} from "@ionic/react";
|
|
import {
|
|
downloadOutline,
|
|
downloadSharp,
|
|
ellipsisVertical,
|
|
logOutOutline,
|
|
logOutSharp,
|
|
menuSharp,
|
|
refreshOutline,
|
|
refreshSharp,
|
|
warningOutline,
|
|
warningSharp,
|
|
} from "ionicons/icons";
|
|
import {FC, useEffect, useRef, useState} from "react";
|
|
import {useHistory} from "react-router-dom";
|
|
|
|
import AddToHomeScreen from "~/assets/icons/md-add-to-home-screen.svg?react";
|
|
import PlusApp from "~/assets/icons/sf-symbols-plus.app.svg?react";
|
|
import SquareAndArrowUp from "~/assets/icons/sf-symbols-square.and.arrow.up.svg?react";
|
|
import {useEphemeralStore} from "~/lib/stores/ephemeral";
|
|
import {usePersistentStore} from "~/lib/stores/persistent";
|
|
import {client} from "~/lib/supabase";
|
|
import {GlobalMessageMetadata, MeasurementSystem, Theme} from "~/lib/types";
|
|
import {GIT_BRANCH, GIT_COMMIT, VERSION} from "~/lib/vars";
|
|
import styles from "~/pages/settings.module.css";
|
|
|
|
/**
|
|
* Account deleted message metadata
|
|
*/
|
|
const ACCOUNT_DELETED_MESSAGE_METADATA: GlobalMessageMetadata = {
|
|
symbol: Symbol("settings.account-deleted"),
|
|
name: "Account Deleted",
|
|
description: "Your account has been successfully deleted",
|
|
};
|
|
|
|
/**
|
|
* Settings page
|
|
* @returns JSX
|
|
*/
|
|
export const Settings: FC = () => {
|
|
// Hooks
|
|
const [beforeInstallPromptEvent, setBeforeInstallPromptEvent] =
|
|
useState<BeforeInstallPromptEvent>();
|
|
|
|
const [appInstalled, setAppInstalled] = useState(
|
|
window.matchMedia("(display-mode: standalone)").matches,
|
|
);
|
|
|
|
const appInstallInstructionsModal = useRef<HTMLIonModalElement>(null);
|
|
|
|
const setMessage = useEphemeralStore(state => state.setMessage);
|
|
const theme = usePersistentStore(state => state.theme);
|
|
const setTheme = usePersistentStore(state => state.setTheme);
|
|
const showFABs = usePersistentStore(state => state.showFABs);
|
|
const setShowFABs = usePersistentStore(state => state.setShowFABs);
|
|
const slidingActions = usePersistentStore(state => state.useSlidingActions);
|
|
|
|
const setSlidingActions = usePersistentStore(
|
|
state => state.setUseSlidingActions,
|
|
);
|
|
|
|
const showAmbientEffect = usePersistentStore(
|
|
state => state.showAmbientEffect,
|
|
);
|
|
|
|
const setShowAmbientEffect = usePersistentStore(
|
|
state => state.setShowAmbientEffect,
|
|
);
|
|
|
|
const measurementSystem = usePersistentStore(
|
|
state => state.measurementSystem,
|
|
);
|
|
|
|
const setMeasurementSystem = usePersistentStore(
|
|
state => state.setMeasurementSystem,
|
|
);
|
|
|
|
const reset = usePersistentStore(state => state.reset);
|
|
|
|
const history = useHistory();
|
|
const [present] = useIonActionSheet();
|
|
|
|
// Methods
|
|
/**
|
|
* Begin the app installation process
|
|
*/
|
|
const installApp = async () => {
|
|
await (beforeInstallPromptEvent === undefined
|
|
? appInstallInstructionsModal.current?.present()
|
|
: beforeInstallPromptEvent.prompt());
|
|
};
|
|
|
|
/**
|
|
* Reset all settings
|
|
* @returns Promise
|
|
*/
|
|
const resetSettings = () =>
|
|
present({
|
|
header: "Reset all settings",
|
|
subHeader:
|
|
"Are you sure you want to reset all settings? You will be signed out.",
|
|
buttons: [
|
|
{
|
|
text: "Cancel",
|
|
role: "cancel",
|
|
},
|
|
{
|
|
text: "Reset",
|
|
role: "destructive",
|
|
handler: reset,
|
|
},
|
|
],
|
|
});
|
|
|
|
/**
|
|
* Sign out
|
|
* @returns Promise
|
|
*/
|
|
const signOut = () =>
|
|
present({
|
|
header: "Sign out",
|
|
subHeader: "Are you sure you want to sign out?",
|
|
buttons: [
|
|
{
|
|
text: "Cancel",
|
|
role: "cancel",
|
|
},
|
|
{
|
|
text: "Sign out",
|
|
role: "destructive",
|
|
/**
|
|
* Sign out handler
|
|
*/
|
|
handler: async () => {
|
|
// Sign out
|
|
await client.auth.signOut();
|
|
|
|
// Redirect to the home
|
|
history.push("/");
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
/**
|
|
* Delete account
|
|
* @returns Promise
|
|
*/
|
|
const deleteAccount = () =>
|
|
present({
|
|
header: "Delete Your Account",
|
|
subHeader:
|
|
"Are you sure you want to delete your account? This action cannot be undone.",
|
|
buttons: [
|
|
{
|
|
text: "Cancel",
|
|
role: "cancel",
|
|
},
|
|
{
|
|
text: "Delete My Account",
|
|
role: "destructive",
|
|
/**
|
|
* Delete account handler
|
|
*/
|
|
handler: async () => {
|
|
// Delete the account
|
|
const {error} = await client.rpc("delete_account");
|
|
|
|
// Handle error
|
|
if (error) {
|
|
return;
|
|
}
|
|
|
|
// Sign out
|
|
await client.auth.signOut();
|
|
|
|
// Redirect to the home
|
|
history.push("/");
|
|
|
|
// Display the message
|
|
setMessage(ACCOUNT_DELETED_MESSAGE_METADATA);
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
// Effects
|
|
useEffect(() => {
|
|
// Capture the installed event
|
|
window.addEventListener("appinstalled", () => setAppInstalled(true));
|
|
|
|
// Capture the installable event
|
|
window.addEventListener(
|
|
"beforeinstallprompt",
|
|
event => {
|
|
event.preventDefault();
|
|
setBeforeInstallPromptEvent(event as BeforeInstallPromptEvent);
|
|
},
|
|
{
|
|
once: true,
|
|
},
|
|
);
|
|
}, []);
|
|
|
|
return (
|
|
<IonPage>
|
|
<IonHeader className="ion-no-border">
|
|
<IonToolbar>
|
|
<IonButtons slot="start">
|
|
<IonMenuButton />
|
|
</IonButtons>
|
|
|
|
<IonTitle>Settings</IonTitle>
|
|
</IonToolbar>
|
|
</IonHeader>
|
|
|
|
<IonModal
|
|
className={styles.modal}
|
|
ref={appInstallInstructionsModal}
|
|
initialBreakpoint={1}
|
|
breakpoints={[0, 1]}
|
|
>
|
|
<div className="flex flex-col items-center justify-center p-4 w-full">
|
|
<h2 className="font-bold mb-2 text-center text-lg">
|
|
App Install Instructions
|
|
</h2>
|
|
|
|
<p className="mb-2">
|
|
This website is a Progresive Web App (PWA), meaning it has app
|
|
functionality. You can install it on your device by following the
|
|
below instructions:
|
|
</p>
|
|
|
|
<ol className="list-decimal ml-4">
|
|
{(() => {
|
|
if (isPlatform("ios")) {
|
|
return (
|
|
<>
|
|
<li>
|
|
Press the share button on the menu bar below.
|
|
<div className="my-4 w-full">
|
|
<SquareAndArrowUp className="dark:fill-[#4693ff] dark:stroke-[#4693ff] fill-[#007aff] h-16 mx-auto stroke-[#007aff] w-16" />
|
|
</div>
|
|
</li>
|
|
<li>
|
|
Select <q>Add to Home Screen</q>.
|
|
<div className="my-4 w-full">
|
|
<PlusApp className="dark:fill-white dark:stroke-white fill-black h-14 mx-auto stroke-black w-14" />
|
|
</div>
|
|
</li>
|
|
</>
|
|
);
|
|
} else if (isPlatform("android")) {
|
|
return (
|
|
<>
|
|
<li>
|
|
Press the three dots on the menu bar above.
|
|
<div className="my-4 w-full">
|
|
<IonIcon
|
|
className="block dark:fill-white dark:stroke-white fill-black h-14 mx-auto stroke-black w-14"
|
|
icon={ellipsisVertical}
|
|
/>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
Select <q>Add to Home screen</q>.
|
|
<div className="my-4 w-full">
|
|
<AddToHomeScreen className="dark:fill-white dark:stroke-white fill-black h-16 mx-auto stroke-black w-16" />
|
|
</div>
|
|
</li>
|
|
</>
|
|
);
|
|
} else {
|
|
return (
|
|
<>
|
|
<li>
|
|
Open your browser's menu.
|
|
<div className="my-4 w-full">
|
|
<IonIcon
|
|
className="block dark:fill-white dark:stroke-white fill-black h-16 mx-auto stroke-black w-16"
|
|
icon={menuSharp}
|
|
/>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
Select <q>Add to Home Screen</q>, <q>Install Beacon</q>,
|
|
or similar option.
|
|
<div className="my-4 w-full">
|
|
<AddToHomeScreen className="dark:fill-white dark:stroke-white fill-black h-16 mx-auto stroke-black w-16" />
|
|
</div>
|
|
</li>
|
|
</>
|
|
);
|
|
}
|
|
})()}
|
|
</ol>
|
|
</div>
|
|
</IonModal>
|
|
|
|
<IonContent color="light">
|
|
<IonList className="py-0" inset={true}>
|
|
<IonItemGroup>
|
|
<IonItemDivider>
|
|
<IonLabel>Look and Feel</IonLabel>
|
|
</IonItemDivider>
|
|
<IonItem>
|
|
<IonSelect
|
|
interface="action-sheet"
|
|
interfaceOptions={{
|
|
header: "Theme",
|
|
subHeader: "Select your preferred theme",
|
|
}}
|
|
label="Theme"
|
|
labelPlacement="floating"
|
|
onIonChange={event => setTheme(event.detail.value)}
|
|
value={theme}
|
|
>
|
|
<IonSelectOption value={Theme.LIGHT}>Light</IonSelectOption>
|
|
<IonSelectOption value={Theme.DARK}>Dark</IonSelectOption>
|
|
</IonSelect>
|
|
</IonItem>
|
|
|
|
<IonItem>
|
|
<IonToggle
|
|
checked={showFABs}
|
|
onIonChange={event => setShowFABs(event.detail.checked)}
|
|
>
|
|
Show floating action buttons
|
|
</IonToggle>
|
|
</IonItem>
|
|
|
|
<IonItem>
|
|
<IonToggle
|
|
checked={slidingActions}
|
|
onIonChange={event => setSlidingActions(event.detail.checked)}
|
|
>
|
|
Use sliding actions on posts
|
|
</IonToggle>
|
|
</IonItem>
|
|
|
|
<IonItem>
|
|
<IonToggle
|
|
checked={showAmbientEffect}
|
|
onIonChange={event =>
|
|
setShowAmbientEffect(event.detail.checked)
|
|
}
|
|
>
|
|
Show ambient effect below posts
|
|
</IonToggle>
|
|
</IonItem>
|
|
|
|
<IonItem>
|
|
<IonSelect
|
|
interface="action-sheet"
|
|
interfaceOptions={{
|
|
header: "Measurement system",
|
|
subHeader: "Select your preferred measurement system",
|
|
}}
|
|
label="Measurement system"
|
|
labelPlacement="floating"
|
|
onIonChange={event => setMeasurementSystem(event.detail.value)}
|
|
value={measurementSystem}
|
|
>
|
|
<IonSelectOption value={MeasurementSystem.METRIC}>
|
|
Metric
|
|
</IonSelectOption>
|
|
<IonSelectOption value={MeasurementSystem.IMPERIAL}>
|
|
Imperial
|
|
</IonSelectOption>
|
|
</IonSelect>
|
|
</IonItem>
|
|
</IonItemGroup>
|
|
|
|
<IonItemGroup>
|
|
<IonItemDivider>
|
|
<IonLabel>Miscellaneous</IonLabel>
|
|
</IonItemDivider>
|
|
|
|
<IonItem button={true} disabled={appInstalled} onClick={installApp}>
|
|
<IonLabel>
|
|
Install app {appInstalled ? "(Already Installed)" : ""}
|
|
</IonLabel>
|
|
<IonIcon
|
|
color="success"
|
|
slot="end"
|
|
ios={downloadOutline}
|
|
md={downloadSharp}
|
|
/>
|
|
</IonItem>
|
|
|
|
<IonItem button={true} onClick={resetSettings}>
|
|
<IonLabel>Reset all settings</IonLabel>
|
|
<IonIcon
|
|
color="danger"
|
|
slot="end"
|
|
ios={refreshOutline}
|
|
md={refreshSharp}
|
|
/>
|
|
</IonItem>
|
|
</IonItemGroup>
|
|
|
|
<IonItemGroup>
|
|
<IonItemDivider>
|
|
<IonLabel>Account</IonLabel>
|
|
</IonItemDivider>
|
|
|
|
<IonItem button={true} onClick={signOut}>
|
|
<IonLabel>Sign out</IonLabel>
|
|
<IonIcon
|
|
color="danger"
|
|
slot="end"
|
|
ios={logOutOutline}
|
|
md={logOutSharp}
|
|
/>
|
|
</IonItem>
|
|
|
|
<IonItem button={true} onClick={deleteAccount}>
|
|
<IonLabel>Delete Account</IonLabel>
|
|
<IonIcon
|
|
color="danger"
|
|
slot="end"
|
|
ios={warningOutline}
|
|
md={warningSharp}
|
|
/>
|
|
</IonItem>
|
|
</IonItemGroup>
|
|
|
|
<IonItemGroup>
|
|
<IonItemDivider>
|
|
<IonLabel>About</IonLabel>
|
|
</IonItemDivider>
|
|
|
|
<IonItem>
|
|
<IonLabel>Version</IonLabel>
|
|
<IonNote slot="end">{VERSION}</IonNote>
|
|
</IonItem>
|
|
|
|
<IonItem>
|
|
<IonLabel>Branch</IonLabel>
|
|
<IonNote slot="end">{GIT_BRANCH}</IonNote>
|
|
</IonItem>
|
|
|
|
<IonItem>
|
|
<IonLabel>Commit</IonLabel>
|
|
<IonNote slot="end">{GIT_COMMIT}</IonNote>
|
|
</IonItem>
|
|
</IonItemGroup>
|
|
|
|
<IonItemGroup>
|
|
<IonItemDivider>
|
|
<IonLabel>Links</IonLabel>
|
|
</IonItemDivider>
|
|
|
|
<IonItem routerLink="/faq">
|
|
<IonLabel>Frequently Asked Questions</IonLabel>
|
|
</IonItem>
|
|
|
|
<IonItem routerLink="/terms-and-conditions">
|
|
<IonLabel>Terms and Conditions</IonLabel>
|
|
</IonItem>
|
|
|
|
<IonItem routerLink="/privacy-policy">
|
|
<IonLabel>Privacy Policy</IonLabel>
|
|
</IonItem>
|
|
|
|
<IonItem
|
|
rel="noreferrer"
|
|
target="_blank"
|
|
href="https://github.com/ColoradoSchoolOfMines/Beacon"
|
|
>
|
|
<IonLabel>Source code</IonLabel>
|
|
</IonItem>
|
|
|
|
<IonItem
|
|
rel="noreferrer"
|
|
target="_blank"
|
|
href="https://github.com/ColoradoSchoolOfMines/beacon/issues/new/choose"
|
|
>
|
|
<IonLabel>Bug report/feature request</IonLabel>
|
|
</IonItem>
|
|
</IonItemGroup>
|
|
</IonList>
|
|
</IonContent>
|
|
</IonPage>
|
|
);
|
|
};
|