Allow users to generate an API key in their profile (#182)
* Add /private-users/apiKey to DB * Add field to edit API key on profile * Move API key to bottom of profile page Austin thinks this is better since most people don't care about it.
This commit is contained in:
parent
19da0c6c82
commit
72b21925e5
|
@ -35,4 +35,5 @@ export type PrivateUser = {
|
||||||
unsubscribedFromGenericEmails?: boolean
|
unsubscribedFromGenericEmails?: boolean
|
||||||
initialDeviceToken?: string
|
initialDeviceToken?: string
|
||||||
initialIpAddress?: string
|
initialIpAddress?: string
|
||||||
|
apiKey?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,9 @@ service cloud.firestore {
|
||||||
|
|
||||||
match /private-users/{userId} {
|
match /private-users/{userId} {
|
||||||
allow read: if resource.data.id == request.auth.uid || isAdmin();
|
allow read: if resource.data.id == request.auth.uid || isAdmin();
|
||||||
|
allow update: if (resource.data.id == request.auth.uid || isAdmin())
|
||||||
|
&& request.resource.data.diff(resource.data).affectedKeys()
|
||||||
|
.hasOnly(['apiKey']);
|
||||||
}
|
}
|
||||||
|
|
||||||
match /private-users/{userId}/views/{viewId} {
|
match /private-users/{userId}/views/{viewId} {
|
||||||
|
|
|
@ -55,6 +55,13 @@ export async function updateUser(userId: string, update: Partial<User>) {
|
||||||
await updateDoc(doc(db, 'users', userId), { ...update })
|
await updateDoc(doc(db, 'users', userId), { ...update })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updatePrivateUser(
|
||||||
|
userId: string,
|
||||||
|
update: Partial<PrivateUser>
|
||||||
|
) {
|
||||||
|
await updateDoc(doc(db, 'private-users', userId), { ...update })
|
||||||
|
}
|
||||||
|
|
||||||
export function listenForUser(
|
export function listenForUser(
|
||||||
userId: string,
|
userId: string,
|
||||||
setUser: (user: User | null) => void
|
setUser: (user: User | null) => void
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { RefreshIcon } from '@heroicons/react/outline'
|
||||||
import Router from 'next/router'
|
import Router from 'next/router'
|
||||||
|
|
||||||
import { AddFundsButton } from 'web/components/add-funds-button'
|
import { AddFundsButton } from 'web/components/add-funds-button'
|
||||||
|
@ -13,7 +14,7 @@ import { uploadImage } from 'web/lib/firebase/storage'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { updateUser } from 'web/lib/firebase/users'
|
import { updateUser, updatePrivateUser } from 'web/lib/firebase/users'
|
||||||
import { defaultBannerUrl } from 'web/components/user-page'
|
import { defaultBannerUrl } from 'web/components/user-page'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
import { SiteLink } from 'web/components/site-link'
|
||||||
import Textarea from 'react-expanding-textarea'
|
import Textarea from 'react-expanding-textarea'
|
||||||
|
@ -63,6 +64,7 @@ export default function ProfilePage() {
|
||||||
const [avatarLoading, setAvatarLoading] = useState(false)
|
const [avatarLoading, setAvatarLoading] = useState(false)
|
||||||
const [name, setName] = useState(user?.name || '')
|
const [name, setName] = useState(user?.name || '')
|
||||||
const [username, setUsername] = useState(user?.username || '')
|
const [username, setUsername] = useState(user?.username || '')
|
||||||
|
const [apiKey, setApiKey] = useState(privateUser?.apiKey || '')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -72,6 +74,12 @@ export default function ProfilePage() {
|
||||||
}
|
}
|
||||||
}, [user])
|
}, [user])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (privateUser) {
|
||||||
|
setApiKey(privateUser.apiKey || '')
|
||||||
|
}
|
||||||
|
}, [privateUser])
|
||||||
|
|
||||||
const updateDisplayName = async () => {
|
const updateDisplayName = async () => {
|
||||||
const newName = cleanDisplayName(name)
|
const newName = cleanDisplayName(name)
|
||||||
|
|
||||||
|
@ -103,6 +111,17 @@ export default function ProfilePage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateApiKey = async (e: React.MouseEvent) => {
|
||||||
|
const newApiKey = crypto.randomUUID()
|
||||||
|
if (user?.id != null) {
|
||||||
|
setApiKey(newApiKey)
|
||||||
|
await updatePrivateUser(user.id, { apiKey: newApiKey }).catch(() => {
|
||||||
|
setApiKey(privateUser?.apiKey || '')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
const fileHandler = async (event: any) => {
|
const fileHandler = async (event: any) => {
|
||||||
const file = event.target.files[0]
|
const file = event.target.files[0]
|
||||||
|
|
||||||
|
@ -155,7 +174,6 @@ export default function ProfilePage() {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="label">Display name</label>
|
<label className="label">Display name</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Display name"
|
placeholder="Display name"
|
||||||
|
@ -168,7 +186,6 @@ export default function ProfilePage() {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="label">Username</label>
|
<label className="label">Username</label>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Username"
|
placeholder="Username"
|
||||||
|
@ -233,6 +250,25 @@ export default function ProfilePage() {
|
||||||
<AddFundsButton />
|
<AddFundsButton />
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="label">API key</label>
|
||||||
|
<div className="input-group w-full">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Click refresh to generate key"
|
||||||
|
className="input input-bordered w-full"
|
||||||
|
value={apiKey}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary btn-square p-2"
|
||||||
|
onClick={updateApiKey}
|
||||||
|
>
|
||||||
|
<RefreshIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Col>
|
</Col>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user