Skip to content

Commit 1534c33

Browse files
committed
implements sign-in and sign-out
1 parent 03b97e8 commit 1534c33

File tree

12 files changed

+392
-32
lines changed

12 files changed

+392
-32
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
indent_style = space
8+
indent_size = 2
9+
trim_trailing_whitespace = true

database.rules.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"rules": {
33
".read": true,
4-
".write": false,
4+
".write": "auth != null"
55
}
6-
}
6+
}

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
"dependencies": {
1616
"firebase": "^8.6.2",
1717
"react": "^17.0.2",
18-
"react-dom": "^17.0.2"
18+
"react-dom": "^17.0.2",
19+
"styled-components": "^5.3.0"
1920
},
2021
"devDependencies": {
22+
"@firebase/app-types": "^0.6.3",
23+
"@firebase/auth-types": "^0.10.3",
2124
"@firebase/database-types": "^0.7.2",
2225
"@types/node": "^14.17.1",
2326
"@types/react": "^17.0.2",
@@ -27,5 +30,8 @@
2730
"typescript": "^4.3.2",
2831
"vite": "^2.4.4",
2932
"vite-tsconfig-paths": "^3.3.13"
33+
},
34+
"resolutions": {
35+
"styled-components": "^5"
3036
}
3137
}

src/components/App.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
3+
import '@/global.css';
4+
import { Preview } from '@/components/Preview'
5+
import { Index as Admin } from '@/components/admin'
6+
7+
const App = () => {
8+
const params = new URLSearchParams(location.search);
9+
10+
const mode = params.get('mode') || 'preview';
11+
12+
const Component = mode === 'preview' ? Preview :
13+
mode === 'admin' ? Admin : <></>;
14+
15+
return (
16+
<Component />
17+
);
18+
};
19+
20+
export { App };

src/components/Preview.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { VFC } from 'react';
2+
3+
import { TextWidget } from '@/components/TextWidget';
4+
import { TimeWidget } from '@/components/TimeWidget';
5+
6+
const Widgets = {
7+
'text': TextWidget,
8+
'time': TimeWidget,
9+
};
10+
11+
const Preview: VFC = () => {
12+
const text = `オレオレOBSウィジェットの整理`;
13+
14+
const widgets = [
15+
{ name: 'text', props: { text: text } },
16+
{ name: 'time', props: { size: 30 } },
17+
];
18+
19+
return (
20+
<div>
21+
{widgets.map((widget) => {
22+
const Widget = Widgets[widget.name];
23+
return <Widget {...widget.props} />
24+
})}
25+
</div>
26+
);
27+
};
28+
29+
export { Preview };
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import React, { VFC, useEffect, useState } from 'react';
2+
import styled from 'styled-components';
3+
import { auth } from '@/lib/firebase';
4+
5+
const Button = styled.button`
6+
padding: 0.5rem 1rem;
7+
color: #fff;
8+
background-color: #33bbff;
9+
border-radius: 0.25rem;
10+
cursor: pointer;
11+
&:hover {
12+
background-color: #1188dd;
13+
}
14+
`;
15+
16+
const FormGroup = styled.div`
17+
display: flex;
18+
margin-bottom: 1rem;
19+
width: 480px;
20+
& > label {
21+
width: 20%;
22+
}
23+
& > input {
24+
flex-grow: 1;
25+
}
26+
`;
27+
28+
const Input = styled.input`
29+
outline: 1px solid #eee;
30+
padding: 0.25rem 0.5rem;
31+
`;
32+
33+
type SignInFormProps = {
34+
redirectTo: string;
35+
};
36+
37+
const SignInForm: VFC<SignInFormProps> = ({ redirectTo }) => {
38+
const [email, setEmail] = useState("");
39+
const [password, setPassword] = useState("");
40+
41+
const signin = async (e: React.SyntheticEvent) => {
42+
e.preventDefault();
43+
try {
44+
await auth.signInWithEmailAndPassword(email, password);
45+
} catch (err) {
46+
alert(err.message);
47+
}
48+
}
49+
50+
return (
51+
<form onSubmit={signin}>
52+
<FormGroup>
53+
<label>Email</label>
54+
<Input
55+
type="email"
56+
placeholder="example@example.com"
57+
onChange={(e) => setEmail(e.target.value)}
58+
/>
59+
</FormGroup>
60+
<FormGroup>
61+
<label>Password</label>
62+
<Input
63+
type="password"
64+
onChange={(e) => setPassword(e.target.value)}
65+
/>
66+
</FormGroup>
67+
<div>
68+
<Button type="submit">
69+
Sign In
70+
</Button>
71+
</div>
72+
</form>
73+
);
74+
};
75+
76+
export { SignInForm };

src/components/admin/index.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, { VFC, useEffect, useState } from 'react';
2+
import { User } from '@firebase/auth-types';
3+
4+
import { AuthProvider } from '@/lib/AuthProvider';
5+
import { auth } from '@/lib/firebase';
6+
import { Signin } from "@/components/admin/signin";
7+
8+
const Index: VFC = () => {
9+
const [currentUser, setCurrentUser] = useState<User | null>(null);
10+
11+
useEffect(() => {
12+
auth.onAuthStateChanged((user) => {
13+
setCurrentUser(user);
14+
});
15+
});
16+
17+
const signout = async () => {
18+
try {
19+
await auth.signOut();
20+
} catch (err) {
21+
alert(err.message);
22+
}
23+
};
24+
25+
return currentUser !== null ? (
26+
<AuthProvider>
27+
<h1>Admin</h1>
28+
<div>{currentUser?.displayName}</div>
29+
<button onClick={signout}>Sign Out</button>
30+
</AuthProvider>
31+
) : (
32+
<Signin />
33+
)
34+
;
35+
};
36+
37+
export { Index };

src/components/admin/signin.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React, { VFC } from 'react';
2+
import styled from 'styled-components';
3+
4+
import { SignInForm } from "@/components/admin/SignInForm";
5+
6+
const Wrapper = styled.div`
7+
display: flex;
8+
justify-content: center;
9+
align-items: center;
10+
margin: 0;
11+
height: 100vh;
12+
background-color: #444;
13+
`;
14+
15+
const Container = styled.div`
16+
display: flex;
17+
align-items: center;
18+
flex-direction: column;
19+
margin: 4rem auto;
20+
padding: 2rem;
21+
width: 640px;
22+
max-width: 640px;
23+
height: 320px;
24+
background-color: #fff;
25+
border-radius: 0.25rem;
26+
box-shadow: 0px 0px 0.5rem 0.25rem rgb(0 0 0 / 10%);
27+
`;
28+
29+
const H1 = styled.div`
30+
margin-bottom: 2rem;
31+
font-size: 2rem;
32+
font-weight: bold;
33+
line-height: 4rem;
34+
`;
35+
36+
const Signin: VFC = () => {
37+
return (
38+
<Wrapper>
39+
<Container>
40+
<H1>Sign In</H1>
41+
<SignInForm redirectTo="/admin" />
42+
</Container>
43+
</Wrapper>
44+
);
45+
};
46+
47+
export { Signin };

src/lib/AuthProvider.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { User } from '@firebase/auth-types';
2+
import React, { FC, createContext, useEffect, useState } from 'react';
3+
import { auth } from '@/lib/firebase';
4+
5+
type AuthContextProps = {
6+
currentUser: User | null | undefined;
7+
};
8+
9+
const AuthContext = createContext<AuthContextProps>({ currentUser: undefined });
10+
11+
const AuthProvider: FC = ({ children }) => {
12+
const [currentUser, setCurrentUser] = useState<User | null | undefined>(undefined);
13+
14+
useEffect(() => {
15+
auth.onAuthStateChanged((user) => {
16+
setCurrentUser(user);
17+
});
18+
});
19+
20+
return (
21+
<AuthContext.Provider value={{ currentUser }}>
22+
{children}
23+
</AuthContext.Provider>
24+
);
25+
};
26+
27+
export { AuthProvider };

src/lib/firebase.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'firebase/auth';
2+
import firebase from 'firebase/app';
3+
4+
const config = {
5+
apiKey: import.meta.env.VITE_FIREBASE_KEY,
6+
authDomain: import.meta.env.VITE_FIREBASE_DOMAIN,
7+
databaseURL: import.meta.env.VITE_FIREBASE_DATABASE,
8+
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
9+
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
10+
messagingSenderId: import.meta.env.VITE_FIREBASE_SENDER_ID,
11+
appId: import.meta.env.VITE_FIREBASE_APP_ID,
12+
};
13+
14+
let auth;
15+
if (firebase.apps.length === 0) {
16+
firebase.initializeApp(config);
17+
auth = firebase.app().auth();
18+
if (import.meta.env.VITE_FIREBASE_AUTH_EMULATOR_HOST) {
19+
auth.useEmulator(
20+
`http://${import.meta.env.VITE_FIREBASE_AUTH_EMULATOR_HOST}`
21+
);
22+
}
23+
}
24+
25+
// TODO: use database emulator
26+
27+
export { auth };

0 commit comments

Comments
 (0)