Add Dexie for persistence; user management with dexie; this is the way
This commit is contained in:
		
							parent
							
								
									8036aa2942
								
							
						
					
					
						commit
						23d275acec
					
				
					 16 changed files with 285 additions and 494 deletions
				
			
		| 
						 | 
				
			
			@ -37,7 +37,6 @@ const ActionBar = (props) => {
 | 
			
		|||
                </Typography>
 | 
			
		||||
                {props.selectedSubscription !== null && <IconSubscribeSettings
 | 
			
		||||
                    subscription={props.selectedSubscription}
 | 
			
		||||
                    users={props.users}
 | 
			
		||||
                    onClearAll={props.onClearAll}
 | 
			
		||||
                    onUnsubscribe={props.onUnsubscribe}
 | 
			
		||||
                />}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,19 +12,20 @@ import connectionManager from "../app/ConnectionManager";
 | 
			
		|||
import Subscriptions from "../app/Subscriptions";
 | 
			
		||||
import Navigation from "./Navigation";
 | 
			
		||||
import ActionBar from "./ActionBar";
 | 
			
		||||
import Users from "../app/Users";
 | 
			
		||||
import notificationManager from "../app/NotificationManager";
 | 
			
		||||
import NoTopics from "./NoTopics";
 | 
			
		||||
import Preferences from "./Preferences";
 | 
			
		||||
import db from "../app/db";
 | 
			
		||||
import {useLiveQuery} from "dexie-react-hooks";
 | 
			
		||||
 | 
			
		||||
// TODO subscribe dialog:
 | 
			
		||||
//  - check/use existing user
 | 
			
		||||
//  - add baseUrl
 | 
			
		||||
// TODO user management
 | 
			
		||||
// TODO embed into ntfy server
 | 
			
		||||
// TODO make default server functional
 | 
			
		||||
// TODO indexeddb for notifications + subscriptions
 | 
			
		||||
// TODO business logic with callbacks
 | 
			
		||||
// TODO connection indicator in subscription list
 | 
			
		||||
 | 
			
		||||
const App = () => {
 | 
			
		||||
    console.log(`[App] Rendering main view`);
 | 
			
		||||
| 
						 | 
				
			
			@ -32,21 +33,18 @@ const App = () => {
 | 
			
		|||
    const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
 | 
			
		||||
    const [prefsOpen, setPrefsOpen] = useState(false);
 | 
			
		||||
    const [subscriptions, setSubscriptions] = useState(new Subscriptions());
 | 
			
		||||
    const [users, setUsers] = useState(new Users());
 | 
			
		||||
    const [selectedSubscription, setSelectedSubscription] = useState(null);
 | 
			
		||||
    const [notificationsGranted, setNotificationsGranted] = useState(notificationManager.granted());
 | 
			
		||||
    const users = useLiveQuery(() => db.users.toArray());
 | 
			
		||||
    const handleSubscriptionClick = (subscriptionId) => {
 | 
			
		||||
        setSelectedSubscription(subscriptions.get(subscriptionId));
 | 
			
		||||
        setPrefsOpen(false);
 | 
			
		||||
    }
 | 
			
		||||
    const handleSubscribeSubmit = (subscription, user) => {
 | 
			
		||||
    const handleSubscribeSubmit = (subscription) => {
 | 
			
		||||
        console.log(`[App] New subscription: ${subscription.id}`);
 | 
			
		||||
        if (user !== null) {
 | 
			
		||||
            setUsers(prev => prev.add(user).clone());
 | 
			
		||||
        }
 | 
			
		||||
        setSubscriptions(prev => prev.add(subscription).clone());
 | 
			
		||||
        setSelectedSubscription(subscription);
 | 
			
		||||
        poll(subscription, user);
 | 
			
		||||
        poll(subscription);
 | 
			
		||||
        handleRequestPermission();
 | 
			
		||||
    };
 | 
			
		||||
    const handleDeleteNotification = (subscriptionId, notificationId) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -80,9 +78,9 @@ const App = () => {
 | 
			
		|||
        setPrefsOpen(true);
 | 
			
		||||
        setSelectedSubscription(null);
 | 
			
		||||
    };
 | 
			
		||||
    const poll = (subscription, user) => {
 | 
			
		||||
    const poll = (subscription) => {
 | 
			
		||||
        const since = subscription.last;
 | 
			
		||||
        api.poll(subscription.baseUrl, subscription.topic, since, user)
 | 
			
		||||
        api.poll(subscription.baseUrl, subscription.topic, since)
 | 
			
		||||
            .then(notifications => {
 | 
			
		||||
                setSubscriptions(prev => {
 | 
			
		||||
                    subscription.addNotifications(notifications);
 | 
			
		||||
| 
						 | 
				
			
			@ -94,12 +92,10 @@ const App = () => {
 | 
			
		|||
    // Define hooks: Note that the order of the hooks is important. The "loading" hooks
 | 
			
		||||
    // must be before the "saving" hooks.
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        // Load subscriptions and users
 | 
			
		||||
        // Load subscriptions
 | 
			
		||||
        const subscriptions = repository.loadSubscriptions();
 | 
			
		||||
        const selectedSubscriptionId = repository.loadSelectedSubscriptionId();
 | 
			
		||||
        const users = repository.loadUsers();
 | 
			
		||||
        setSubscriptions(subscriptions);
 | 
			
		||||
        setUsers(users);
 | 
			
		||||
 | 
			
		||||
        // Set selected subscription
 | 
			
		||||
        const maybeSelectedSubscription = subscriptions.get(selectedSubscriptionId);
 | 
			
		||||
| 
						 | 
				
			
			@ -109,8 +105,7 @@ const App = () => {
 | 
			
		|||
 | 
			
		||||
        // Poll all subscriptions
 | 
			
		||||
        subscriptions.forEach((subscriptionId, subscription) => {
 | 
			
		||||
            const user = users.get(subscription.baseUrl); // May be null
 | 
			
		||||
            poll(subscription, user);
 | 
			
		||||
            poll(subscription);
 | 
			
		||||
        });
 | 
			
		||||
    }, [/* initial render */]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
| 
						 | 
				
			
			@ -127,7 +122,6 @@ const App = () => {
 | 
			
		|||
        connectionManager.refresh(subscriptions, users, handleNotification);
 | 
			
		||||
    }, [subscriptions, users]);
 | 
			
		||||
    useEffect(() => repository.saveSubscriptions(subscriptions), [subscriptions]);
 | 
			
		||||
    useEffect(() => repository.saveUsers(users), [users]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const subscriptionId = (selectedSubscription) ? selectedSubscription.id : "";
 | 
			
		||||
        repository.saveSelectedSubscriptionId(subscriptionId)
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +134,6 @@ const App = () => {
 | 
			
		|||
                <CssBaseline/>
 | 
			
		||||
                <ActionBar
 | 
			
		||||
                    selectedSubscription={selectedSubscription}
 | 
			
		||||
                    users={users}
 | 
			
		||||
                    onClearAll={handleDeleteAllNotifications}
 | 
			
		||||
                    onUnsubscribe={handleUnsubscribe}
 | 
			
		||||
                    onMobileDrawerToggle={() => setMobileDrawerOpen(!mobileDrawerOpen)}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,6 @@ import api from "../app/Api";
 | 
			
		|||
const IconSubscribeSettings = (props) => {
 | 
			
		||||
    const [open, setOpen] = useState(false);
 | 
			
		||||
    const anchorRef = useRef(null);
 | 
			
		||||
    const users = props.users;
 | 
			
		||||
 | 
			
		||||
    const handleToggle = () => {
 | 
			
		||||
        setOpen((prevOpen) => !prevOpen);
 | 
			
		||||
| 
						 | 
				
			
			@ -40,8 +39,7 @@ const IconSubscribeSettings = (props) => {
 | 
			
		|||
    const handleSendTestMessage = () => {
 | 
			
		||||
        const baseUrl = props.subscription.baseUrl;
 | 
			
		||||
        const topic = props.subscription.topic;
 | 
			
		||||
        const user = users.get(baseUrl); // May be null
 | 
			
		||||
        api.publish(baseUrl, topic, user,
 | 
			
		||||
        api.publish(baseUrl, topic,
 | 
			
		||||
            `This is a test notification sent by the ntfy Web UI at ${new Date().toString()}.`); // FIXME result ignored
 | 
			
		||||
        setOpen(false);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,9 +57,9 @@ const NavList = (props) => {
 | 
			
		|||
        setSubscribeDialogOpen(false);
 | 
			
		||||
        setSubscribeDialogKey(prev => prev+1);
 | 
			
		||||
    }
 | 
			
		||||
    const handleSubscribeSubmit = (subscription, user) => {
 | 
			
		||||
    const handleSubscribeSubmit = (subscription) => {
 | 
			
		||||
        handleSubscribeReset();
 | 
			
		||||
        props.onSubscribeSubmit(subscription, user);
 | 
			
		||||
        props.onSubscribeSubmit(subscription);
 | 
			
		||||
    }
 | 
			
		||||
    const showSubscriptionsList = props.subscriptions.size() > 0;
 | 
			
		||||
    const showGrantPermissionsBox = props.subscriptions.size() > 0 && !props.notificationsGranted;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,18 @@
 | 
			
		|||
import * as React from 'react';
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
import {FormControl, Select, Stack, Table, TableBody, TableCell, TableHead, TableRow} from "@mui/material";
 | 
			
		||||
import {useEffect, useState} from 'react';
 | 
			
		||||
import {
 | 
			
		||||
    CardActions,
 | 
			
		||||
    CardContent,
 | 
			
		||||
    FormControl,
 | 
			
		||||
    Select,
 | 
			
		||||
    Stack,
 | 
			
		||||
    Table,
 | 
			
		||||
    TableBody,
 | 
			
		||||
    TableCell,
 | 
			
		||||
    TableHead,
 | 
			
		||||
    TableRow,
 | 
			
		||||
    useMediaQuery
 | 
			
		||||
} from "@mui/material";
 | 
			
		||||
import Typography from "@mui/material/Typography";
 | 
			
		||||
import Paper from "@mui/material/Paper";
 | 
			
		||||
import repository from "../app/Repository";
 | 
			
		||||
| 
						 | 
				
			
			@ -11,6 +23,15 @@ import IconButton from "@mui/material/IconButton";
 | 
			
		|||
import Container from "@mui/material/Container";
 | 
			
		||||
import TextField from "@mui/material/TextField";
 | 
			
		||||
import MenuItem from "@mui/material/MenuItem";
 | 
			
		||||
import Card from "@mui/material/Card";
 | 
			
		||||
import Button from "@mui/material/Button";
 | 
			
		||||
import db from "../app/db";
 | 
			
		||||
import {useLiveQuery} from "dexie-react-hooks";
 | 
			
		||||
import theme from "./theme";
 | 
			
		||||
import Dialog from "@mui/material/Dialog";
 | 
			
		||||
import DialogTitle from "@mui/material/DialogTitle";
 | 
			
		||||
import DialogContent from "@mui/material/DialogContent";
 | 
			
		||||
import DialogActions from "@mui/material/DialogActions";
 | 
			
		||||
 | 
			
		||||
const Preferences = (props) => {
 | 
			
		||||
    return (
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +47,7 @@ const Preferences = (props) => {
 | 
			
		|||
 | 
			
		||||
const Notifications = (props) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <Paper sx={{p: 3}}>
 | 
			
		||||
        <Card sx={{p: 3}}>
 | 
			
		||||
            <Typography variant="h5">
 | 
			
		||||
                Notifications
 | 
			
		||||
            </Typography>
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +55,7 @@ const Notifications = (props) => {
 | 
			
		|||
                <MinPriority/>
 | 
			
		||||
                <DeleteAfter/>
 | 
			
		||||
            </PrefGroup>
 | 
			
		||||
        </Paper>
 | 
			
		||||
        </Card>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +87,7 @@ const DeleteAfter = () => {
 | 
			
		|||
        repository.setDeleteAfter(ev.target.value);
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <Pref title="Minimum priority">
 | 
			
		||||
        <Pref title="Delete notifications">
 | 
			
		||||
            <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
 | 
			
		||||
                <Select value={deleteAfter} onChange={handleChange}>
 | 
			
		||||
                    <MenuItem value={0}>Never</MenuItem>
 | 
			
		||||
| 
						 | 
				
			
			@ -139,53 +160,191 @@ const DefaultServer = (props) => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
const Users = (props) => {
 | 
			
		||||
    const [dialogKey, setDialogKey] = useState(0);
 | 
			
		||||
    const [dialogOpen, setDialogOpen] = useState(false);
 | 
			
		||||
    const users = useLiveQuery(() => db.users.toArray());
 | 
			
		||||
    const handleAddClick = () => {
 | 
			
		||||
        setDialogKey(prev => prev+1);
 | 
			
		||||
        setDialogOpen(true);
 | 
			
		||||
    };
 | 
			
		||||
    const handleDialogCancel = () => {
 | 
			
		||||
        setDialogOpen(false);
 | 
			
		||||
    };
 | 
			
		||||
    const handleDialogSubmit = async (user) => {
 | 
			
		||||
        setDialogOpen(false);
 | 
			
		||||
        try {
 | 
			
		||||
            await db.users.add(user);
 | 
			
		||||
            console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} added`);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.log(`[Preferences] Error adding user.`, e);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
        <Paper sx={{p: 3}}>
 | 
			
		||||
            <Typography variant="h5">
 | 
			
		||||
                Manage users
 | 
			
		||||
            </Typography>
 | 
			
		||||
            <Paragraph>
 | 
			
		||||
                You may manage users for your protected topics here. Please note that since this is a client
 | 
			
		||||
                application only, username and password are stored in the browser's local storage.
 | 
			
		||||
            </Paragraph>
 | 
			
		||||
            <UserTable/>
 | 
			
		||||
        </Paper>
 | 
			
		||||
        <Card sx={{p: 3}}>
 | 
			
		||||
            <CardContent>
 | 
			
		||||
                <Typography variant="h5">
 | 
			
		||||
                    Manage users
 | 
			
		||||
                </Typography>
 | 
			
		||||
                <Paragraph>
 | 
			
		||||
                    Add/remove users for your protected topics here. Please note that username and password are
 | 
			
		||||
                    stored in the browser's local storage.
 | 
			
		||||
                </Paragraph>
 | 
			
		||||
                {users?.length > 0 && <UserTable users={users}/>}
 | 
			
		||||
            </CardContent>
 | 
			
		||||
            <CardActions>
 | 
			
		||||
                <Button onClick={handleAddClick}>Add user</Button>
 | 
			
		||||
                <UserDialog
 | 
			
		||||
                    key={`userAddDialog${dialogKey}`}
 | 
			
		||||
                    open={dialogOpen}
 | 
			
		||||
                    user={null}
 | 
			
		||||
                    users={users}
 | 
			
		||||
                    onCancel={handleDialogCancel}
 | 
			
		||||
                    onSubmit={handleDialogSubmit}
 | 
			
		||||
                />
 | 
			
		||||
            </CardActions>
 | 
			
		||||
        </Card>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const UserTable = () => {
 | 
			
		||||
    const users = repository.loadUsers();
 | 
			
		||||
const UserTable = (props) => {
 | 
			
		||||
    const [dialogKey, setDialogKey] = useState(0);
 | 
			
		||||
    const [dialogOpen, setDialogOpen] = useState(false);
 | 
			
		||||
    const [dialogUser, setDialogUser] = useState(null);
 | 
			
		||||
    const handleEditClick = (user) => {
 | 
			
		||||
        setDialogKey(prev => prev+1);
 | 
			
		||||
        setDialogUser(user);
 | 
			
		||||
        setDialogOpen(true);
 | 
			
		||||
    };
 | 
			
		||||
    const handleDialogCancel = () => {
 | 
			
		||||
        setDialogOpen(false);
 | 
			
		||||
    };
 | 
			
		||||
    const handleDialogSubmit = async (user) => {
 | 
			
		||||
        setDialogOpen(false);
 | 
			
		||||
        try {
 | 
			
		||||
            await db.users.put(user); // put() is an upsert
 | 
			
		||||
            console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} updated`);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.log(`[Preferences] Error updating user.`, e);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    const handleDeleteClick = async (user) => {
 | 
			
		||||
        try {
 | 
			
		||||
            await db.users.delete(user.baseUrl);
 | 
			
		||||
            console.debug(`[Preferences] User ${user.username} for ${user.baseUrl} deleted`);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            console.error(`[Preferences] Error deleting user for ${user.baseUrl}`, e);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
            <Table size="small">
 | 
			
		||||
                <TableHead>
 | 
			
		||||
                    <TableRow>
 | 
			
		||||
                        <TableCell>User</TableCell>
 | 
			
		||||
                        <TableCell>Service URL</TableCell>
 | 
			
		||||
                        <TableCell/>
 | 
			
		||||
        <Table size="small">
 | 
			
		||||
            <TableHead>
 | 
			
		||||
                <TableRow>
 | 
			
		||||
                    <TableCell>User</TableCell>
 | 
			
		||||
                    <TableCell>Service URL</TableCell>
 | 
			
		||||
                    <TableCell/>
 | 
			
		||||
                </TableRow>
 | 
			
		||||
            </TableHead>
 | 
			
		||||
            <TableBody>
 | 
			
		||||
                {props.users?.map(user => (
 | 
			
		||||
                    <TableRow
 | 
			
		||||
                        key={user.baseUrl}
 | 
			
		||||
                        sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
 | 
			
		||||
                    >
 | 
			
		||||
                        <TableCell component="th" scope="row">{user.username}</TableCell>
 | 
			
		||||
                        <TableCell>{user.baseUrl}</TableCell>
 | 
			
		||||
                        <TableCell align="right">
 | 
			
		||||
                            <IconButton onClick={() => handleEditClick(user)}>
 | 
			
		||||
                                <EditIcon/>
 | 
			
		||||
                            </IconButton>
 | 
			
		||||
                            <IconButton onClick={() => handleDeleteClick(user)}>
 | 
			
		||||
                                <CloseIcon />
 | 
			
		||||
                            </IconButton>
 | 
			
		||||
                        </TableCell>
 | 
			
		||||
                    </TableRow>
 | 
			
		||||
                </TableHead>
 | 
			
		||||
                <TableBody>
 | 
			
		||||
                    {users.map((user, i) => (
 | 
			
		||||
                        <TableRow
 | 
			
		||||
                            key={i}
 | 
			
		||||
                            sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
 | 
			
		||||
                        >
 | 
			
		||||
                            <TableCell component="th" scope="row">{user.username}</TableCell>
 | 
			
		||||
                            <TableCell>{user.baseUrl}</TableCell>
 | 
			
		||||
                            <TableCell align="right">
 | 
			
		||||
                                <IconButton>
 | 
			
		||||
                                    <EditIcon/>
 | 
			
		||||
                                </IconButton>
 | 
			
		||||
                                <IconButton>
 | 
			
		||||
                                    <CloseIcon />
 | 
			
		||||
                                </IconButton>
 | 
			
		||||
                            </TableCell>
 | 
			
		||||
                        </TableRow>
 | 
			
		||||
                    ))}
 | 
			
		||||
                </TableBody>
 | 
			
		||||
            </Table>
 | 
			
		||||
 | 
			
		||||
                ))}
 | 
			
		||||
            </TableBody>
 | 
			
		||||
            <UserDialog
 | 
			
		||||
                key={`userEditDialog${dialogKey}`}
 | 
			
		||||
                open={dialogOpen}
 | 
			
		||||
                user={dialogUser}
 | 
			
		||||
                users={props.users}
 | 
			
		||||
                onCancel={handleDialogCancel}
 | 
			
		||||
                onSubmit={handleDialogSubmit}
 | 
			
		||||
            />
 | 
			
		||||
        </Table>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const UserDialog = (props) => {
 | 
			
		||||
    const [baseUrl, setBaseUrl] = useState("");
 | 
			
		||||
    const [username, setUsername] = useState("");
 | 
			
		||||
    const [password, setPassword] = useState("");
 | 
			
		||||
    const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
 | 
			
		||||
    const editMode = props.user !== null;
 | 
			
		||||
    const addButtonEnabled = (() => {
 | 
			
		||||
        if (editMode) {
 | 
			
		||||
            return username.length > 0 && password.length > 0;
 | 
			
		||||
        }
 | 
			
		||||
        const baseUrlExists = props.users?.map(user => user.baseUrl).includes(baseUrl);
 | 
			
		||||
        return !baseUrlExists && username.length > 0 && password.length > 0;
 | 
			
		||||
    })();
 | 
			
		||||
    const handleSubmit = async () => {
 | 
			
		||||
        props.onSubmit({
 | 
			
		||||
            baseUrl: baseUrl,
 | 
			
		||||
            username: username,
 | 
			
		||||
            password: password
 | 
			
		||||
        })
 | 
			
		||||
    };
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (editMode) {
 | 
			
		||||
            setBaseUrl(props.user.baseUrl);
 | 
			
		||||
            setUsername(props.user.username);
 | 
			
		||||
            setPassword(props.user.password);
 | 
			
		||||
        }
 | 
			
		||||
    }, []);
 | 
			
		||||
    return (
 | 
			
		||||
        <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
 | 
			
		||||
            <DialogTitle>{editMode ? "Edit user" : "Add user"}</DialogTitle>
 | 
			
		||||
            <DialogContent>
 | 
			
		||||
                {!editMode && <TextField
 | 
			
		||||
                    autoFocus
 | 
			
		||||
                    margin="dense"
 | 
			
		||||
                    id="baseUrl"
 | 
			
		||||
                    label="Service URL, e.g. https://ntfy.sh"
 | 
			
		||||
                    value={baseUrl}
 | 
			
		||||
                    onChange={ev => setBaseUrl(ev.target.value)}
 | 
			
		||||
                    type="url"
 | 
			
		||||
                    fullWidth
 | 
			
		||||
                    variant="standard"
 | 
			
		||||
                />}
 | 
			
		||||
                <TextField
 | 
			
		||||
                    autoFocus={editMode}
 | 
			
		||||
                    margin="dense"
 | 
			
		||||
                    id="username"
 | 
			
		||||
                    label="Username, e.g. phil"
 | 
			
		||||
                    value={username}
 | 
			
		||||
                    onChange={ev => setUsername(ev.target.value)}
 | 
			
		||||
                    type="text"
 | 
			
		||||
                    fullWidth
 | 
			
		||||
                    variant="standard"
 | 
			
		||||
                />
 | 
			
		||||
                <TextField
 | 
			
		||||
                    margin="dense"
 | 
			
		||||
                    id="password"
 | 
			
		||||
                    label="Password"
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    value={password}
 | 
			
		||||
                    onChange={ev => setPassword(ev.target.value)}
 | 
			
		||||
                    fullWidth
 | 
			
		||||
                    variant="standard"
 | 
			
		||||
                />
 | 
			
		||||
            </DialogContent>
 | 
			
		||||
            <DialogActions>
 | 
			
		||||
                <Button onClick={props.onCancel}>Cancel</Button>
 | 
			
		||||
                <Button onClick={handleSubmit} disabled={!addButtonEnabled}>{editMode ? "Save" : "Add"}</Button>
 | 
			
		||||
            </DialogActions>
 | 
			
		||||
        </Dialog>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Preferences;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,8 +12,8 @@ import {Autocomplete, Checkbox, FormControlLabel, useMediaQuery} from "@mui/mate
 | 
			
		|||
import theme from "./theme";
 | 
			
		||||
import api from "../app/Api";
 | 
			
		||||
import {topicUrl, validTopic, validUrl} from "../app/utils";
 | 
			
		||||
import User from "../app/User";
 | 
			
		||||
import Box from "@mui/material/Box";
 | 
			
		||||
import db from "../app/db";
 | 
			
		||||
 | 
			
		||||
const defaultBaseUrl = "http://127.0.0.1"
 | 
			
		||||
//const defaultBaseUrl = "https://ntfy.sh"
 | 
			
		||||
| 
						 | 
				
			
			@ -23,10 +23,10 @@ const SubscribeDialog = (props) => {
 | 
			
		|||
    const [topic, setTopic] = useState("");
 | 
			
		||||
    const [showLoginPage, setShowLoginPage] = useState(false);
 | 
			
		||||
    const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
 | 
			
		||||
    const handleSuccess = (user) => {
 | 
			
		||||
    const handleSuccess = () => {
 | 
			
		||||
        const actualBaseUrl = (baseUrl) ? baseUrl : defaultBaseUrl; // FIXME
 | 
			
		||||
        const subscription = new Subscription(actualBaseUrl, topic);
 | 
			
		||||
        props.onSuccess(subscription, user);
 | 
			
		||||
        props.onSuccess(subscription);
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
 | 
			
		||||
| 
						 | 
				
			
			@ -65,7 +65,7 @@ const SubscribePage = (props) => {
 | 
			
		|||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for anonymous user`);
 | 
			
		||||
        props.onSuccess(null);
 | 
			
		||||
        props.onSuccess();
 | 
			
		||||
    };
 | 
			
		||||
    const handleUseAnotherChanged = (e) => {
 | 
			
		||||
        props.setBaseUrl("");
 | 
			
		||||
| 
						 | 
				
			
			@ -129,7 +129,7 @@ const LoginPage = (props) => {
 | 
			
		|||
    const baseUrl = (props.baseUrl) ? props.baseUrl : defaultBaseUrl;
 | 
			
		||||
    const topic = props.topic;
 | 
			
		||||
    const handleLogin = async () => {
 | 
			
		||||
        const user = new User(baseUrl, username, password);
 | 
			
		||||
        const user = {baseUrl, username, password};
 | 
			
		||||
        const success = await api.auth(baseUrl, topic, user);
 | 
			
		||||
        if (!success) {
 | 
			
		||||
            console.log(`[SubscribeDialog] Login to ${topicUrl(baseUrl, topic)} failed for user ${username}`);
 | 
			
		||||
| 
						 | 
				
			
			@ -137,7 +137,8 @@ const LoginPage = (props) => {
 | 
			
		|||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        console.log(`[SubscribeDialog] Successful login to ${topicUrl(baseUrl, topic)} for user ${username}`);
 | 
			
		||||
        props.onSuccess(user);
 | 
			
		||||
        db.users.put(user);
 | 
			
		||||
        props.onSuccess();
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import {styled} from "@mui/styles";
 | 
			
		||||
import Typography from "@mui/material/Typography";
 | 
			
		||||
import theme from "./theme";
 | 
			
		||||
import Container from "@mui/material/Container";
 | 
			
		||||
import {styled} from "@mui/material";
 | 
			
		||||
 | 
			
		||||
export const Paragraph = styled(Typography)({
 | 
			
		||||
  paddingTop: 8,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue