Skip to content

Commit 254c75d

Browse files
authored
refactor: admin components (#114)
* refactor: split add Profile/Wdiget Dialog components * refactor: split Navbar component
1 parent fa1a4c6 commit 254c75d

File tree

6 files changed

+343
-435
lines changed

6 files changed

+343
-435
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useState } from 'react';
2+
import {
3+
Dialog,
4+
DialogTitle,
5+
DialogContent,
6+
DialogActions,
7+
FormControl,
8+
TextField,
9+
Button,
10+
} from '@mui/material';
11+
import { ref, set } from '@firebase/database';
12+
import { db } from '@/lib/firebase';
13+
14+
type AddProfileDialogProps = {
15+
open: boolean;
16+
onClose: () => void;
17+
};
18+
19+
const AddProfileDialog = ({ open, onClose }: AddProfileDialogProps) => {
20+
const [profileId, setProfileId] = useState("");
21+
22+
return (
23+
<Dialog open={open} onClose={onClose}>
24+
<DialogTitle>Add Profile</DialogTitle>
25+
<DialogContent>
26+
<FormControl variant="standard">
27+
<TextField required autoFocus fullWidth label="ID" value={profileId} variant="standard" onChange={(e) => { setProfileId(e.target.value); }} />
28+
</FormControl>
29+
</DialogContent>
30+
<DialogActions>
31+
<Button color="primary" variant="contained" onClick={() =>{
32+
if (profileId.length > 0) {
33+
set(ref(db, `/profiles/${profileId}/name`), profileId);
34+
setProfileId("");
35+
onClose();
36+
}
37+
}}>Add</Button>
38+
</DialogActions>
39+
</Dialog>
40+
);
41+
};
42+
43+
export { AddProfileDialog };
44+
export type { AddProfileDialogProps };
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { useState } from 'react';
2+
import styled from 'styled-components';
3+
import {
4+
Dialog,
5+
DialogTitle,
6+
DialogContent,
7+
DialogActions,
8+
FormControl,
9+
InputLabel,
10+
Select,
11+
Button,
12+
TextField,
13+
MenuItem,
14+
} from '@mui/material';
15+
import { ref, set, onValue } from '@firebase/database';
16+
import { db } from '@/lib/firebase';
17+
import { TextWidgetEditor } from '@/components/TextWidget';
18+
import { TimeWidgetEditor } from '@/components/TimeWidget';
19+
import { IFrameWidgetEditor } from '@/components/IFrameWidget';
20+
21+
const Editors = {
22+
text: TextWidgetEditor,
23+
time: TimeWidgetEditor,
24+
iframe: IFrameWidgetEditor,
25+
};
26+
27+
type AddWidgetDialogProps = {
28+
profile: string;
29+
open: boolean;
30+
onClose: () => void;
31+
};
32+
33+
const AddWidgetDialog = ({ profile, open, onClose }: AddWidgetDialogProps) => {
34+
const [widgetId, setWidgetId] = useState("");
35+
const [widgetType, setWidgetType] = useState("text");
36+
37+
const FormGroup = styled.div`
38+
display: flex;
39+
margin-bottom: 1rem;
40+
& > div {
41+
fleprofilex-grow: 1;
42+
margin-left: 0.25rem;
43+
}
44+
`;
45+
46+
const style = {
47+
display: 'flex',
48+
flexDirection: 'column',
49+
position: 'absolute' as 'absolute',
50+
top: '50%',
51+
left: '50%',
52+
transform: 'translate(-50%, -50%)',
53+
width: 640,
54+
bgcolor: 'background.paper',
55+
border: '1px solid #ddd',
56+
borderRadius: '4px',
57+
boxShadow: 24,
58+
pt: 4,
59+
px: 4,
60+
pb: 3,
61+
};
62+
63+
return (
64+
<Dialog
65+
open={open}
66+
onClose={onClose}
67+
>
68+
<DialogTitle>Add Widget</DialogTitle>
69+
<DialogContent>
70+
<FormGroup>
71+
<FormControl variant="standard">
72+
<TextField autoFocus fullWidth label="ID" value={widgetId} variant="standard" onChange={(e) => { setWidgetId(e.target.value); }}/>
73+
</FormControl>
74+
</FormGroup>
75+
76+
<FormGroup>
77+
<FormControl variant="standard">
78+
<InputLabel id="widget-type-label">Widget</InputLabel>
79+
<Select
80+
labelId="widget-type-label"
81+
id="widget-type"
82+
value={widgetType}
83+
label="Widget"
84+
onChange={(e) => { setWidgetType(e.target.value); }}
85+
>
86+
<MenuItem value={"text"}>Text</MenuItem>
87+
<MenuItem value={"time"}>Time</MenuItem>
88+
<MenuItem value={"iframe"}>IFrame</MenuItem>
89+
</Select>
90+
</FormControl>
91+
</FormGroup>
92+
</DialogContent>
93+
<DialogActions>
94+
<Button color="primary" variant="contained" onClick={() => {
95+
set(ref(db, `/profiles/${profile}/widgets/${widgetId}`), {
96+
name: widgetType,
97+
props: Editors[widgetType].defaultProps
98+
});
99+
100+
setWidgetId("");
101+
setWidgetType("text");
102+
onClose();
103+
}}>Add</Button>
104+
</DialogActions>
105+
</Dialog>
106+
);
107+
};
108+
109+
export { AddWidgetDialog };
110+
export type { AddWidgetDialogProps };

src/components/admin/Dialog/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export { AddProfileDialog } from './AddProfileDialog';
2+
export type { AddProfileDialogProps } from './AddProfileDialog';
3+
export { AddWidgetDialog } from './AddWidgetDialog';
4+
export type { AddWidgetDialogProps } from './AddWidgetDialog';

src/components/admin/Navbar/index.tsx

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { useState, useEffect, MouseEvent } from 'react';
2+
import { useRouter } from 'next/router'
3+
import {
4+
Box,
5+
AppBar,
6+
Toolbar,
7+
Menu,
8+
MenuItem,
9+
Divider,
10+
Typography,
11+
IconButton,
12+
} from '@mui/material';
13+
import { makeStyles } from '@mui/styles'
14+
import AddIcon from '@mui/icons-material/Add';
15+
import WidgetsIcon from '@mui/icons-material/Widgets';
16+
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
17+
import { ref, onValue, DataSnapshot } from '@firebase/database';
18+
19+
import { auth, db } from '@/lib/firebase';
20+
import { AddProfileDialog, AddWidgetDialog } from '@/components/admin/Dialog';
21+
22+
const useStyles = makeStyles((_) => ({
23+
title: {
24+
flexGrow: 1,
25+
},
26+
}));
27+
28+
type NavbarProps = {
29+
profile?: string;
30+
}
31+
32+
const Navbar = ({ profile }: NavbarProps) => {
33+
const classes = useStyles();
34+
const router = useRouter();
35+
36+
const [userAnchorEl, setUserAnchorEl] = useState<HTMLElement | null>(null);
37+
const [profileAnchorEl, setProfileAnchorEl] = useState<HTMLElement | null>(null);
38+
const [addProfileDialogOpened, setAddProfileDialogOpened] = useState(false);
39+
const [addWidgetDialogOpened, setAddWidgetDialogOpened] = useState(false);
40+
const [profiles, setProfiles] = useState<string[]>([]);
41+
42+
const userMenuId = 'user-menu';
43+
const handleUserMenuOpen = (event: MouseEvent<HTMLElement>) => {
44+
setUserAnchorEl(event.currentTarget);
45+
};
46+
const handleUserMenuClose = () => {
47+
setUserAnchorEl(null);
48+
};
49+
50+
const profileMenuId = 'profile-menu';
51+
const handleProfileMenuOpen = (event: MouseEvent<HTMLElement>) => {
52+
setProfileAnchorEl(event.currentTarget);
53+
};
54+
const handleProfileMenuClose = () => {
55+
setProfileAnchorEl(null);
56+
};
57+
58+
const isUserMenuOpen = Boolean(userAnchorEl);
59+
const isProfileMenuOpen = Boolean(profileAnchorEl);
60+
61+
const signout = async () => {
62+
try {
63+
await auth.signOut();
64+
} catch (err) {
65+
alert(err.message);
66+
}
67+
};
68+
69+
useEffect(() => {
70+
const profilesRef = ref(db, `/profiles`);
71+
onValue(profilesRef, (snap: DataSnapshot) => {
72+
if (snap?.val()) {
73+
setProfiles(Object.keys(snap.val()));
74+
}
75+
});
76+
}, []);
77+
78+
return (
79+
<>
80+
<AppBar position="static">
81+
<Toolbar>
82+
<Typography variant="h6" className={classes.title}>
83+
Admin
84+
</Typography>
85+
{profile && (<Typography variant="h6" className={classes.title}>
86+
Profile:{' '}
87+
{profile}
88+
</Typography>)}
89+
<Box sx={{ flexGrow: 1 }} />
90+
<Box sx={{ display: { xs: 'none', md: 'flex' } }}>
91+
<IconButton
92+
size="large"
93+
color="inherit"
94+
edge="end"
95+
aria-controls={profileMenuId}
96+
aria-haspopup="true"
97+
aria-expanded={isProfileMenuOpen ? 'true' : undefined}
98+
onClick={handleProfileMenuOpen}
99+
>
100+
<WidgetsIcon />
101+
</IconButton>
102+
<IconButton
103+
size="large"
104+
color="inherit"
105+
onClick={()=>{setAddWidgetDialogOpened(true);}}
106+
>
107+
<AddIcon />
108+
</IconButton>
109+
<IconButton
110+
size="large"
111+
color="inherit"
112+
edge="end"
113+
aria-controls={userMenuId}
114+
aria-haspopup="true"
115+
aria-expanded={isUserMenuOpen ? 'true' : undefined}
116+
onClick={handleUserMenuOpen}
117+
>
118+
<AccountCircleIcon />
119+
</IconButton>
120+
</Box>
121+
</Toolbar>
122+
</AppBar>
123+
<Menu
124+
id={profileMenuId}
125+
anchorEl={profileAnchorEl}
126+
open={isProfileMenuOpen}
127+
onClose={handleProfileMenuClose}
128+
>
129+
{profiles.map((profile) => (
130+
<MenuItem key={profile} color="inherit" onClick={() => { router.push(`/admin/${profile}`); }}>{profile}</MenuItem>
131+
))}
132+
<Divider />
133+
<MenuItem color="inherit" onClick={() => { setAddProfileDialogOpened(true);}}>Add</MenuItem>
134+
</Menu>
135+
<Menu
136+
id={userMenuId}
137+
anchorEl={userAnchorEl}
138+
open={isUserMenuOpen}
139+
onClose={handleUserMenuClose}
140+
>
141+
<MenuItem color="inherit" onClick={signout}>Logout</MenuItem>
142+
</Menu>
143+
<AddProfileDialog
144+
open={addProfileDialogOpened}
145+
onClose={() => {
146+
setAddProfileDialogOpened(false);
147+
}}
148+
/>
149+
{profile && (<AddWidgetDialog
150+
profile={profile}
151+
open={addWidgetDialogOpened}
152+
onClose={() => {
153+
setAddWidgetDialogOpened(false);
154+
}}
155+
/>)}
156+
</>
157+
);
158+
};
159+
160+
export { Navbar };
161+
export type { NavbarProps };

0 commit comments

Comments
 (0)