Add custom Modals
This commit is contained in:
parent
51d2ac96c1
commit
a60796e0ca
|
@ -176,7 +176,7 @@
|
|||
"title": "Slet bruger"
|
||||
},
|
||||
"password_reset": {
|
||||
"description": "Ændre adgangskode for <1></1>",
|
||||
"description": "Ændre adgangskode for <1>{{username}}</1>",
|
||||
"form": {
|
||||
"label": "Ny adgangskode",
|
||||
"placeholder": "adgangskode",
|
||||
|
|
|
@ -176,7 +176,7 @@
|
|||
"title": "Benutzer löschen"
|
||||
},
|
||||
"password_reset": {
|
||||
"description": "Passwort von Benutzer <1></1> aktualisieren",
|
||||
"description": "Passwort von Benutzer <1>{{username}}</1> aktualisieren",
|
||||
"form": {
|
||||
"label": "Neues Passwort",
|
||||
"placeholder": "Passwort",
|
||||
|
|
|
@ -176,7 +176,7 @@
|
|||
"title": "Delete user"
|
||||
},
|
||||
"password_reset": {
|
||||
"description": "Change password for <1></1>",
|
||||
"description": "Change password for <1>{{username}}</1>",
|
||||
"form": {
|
||||
"label": "New password",
|
||||
"placeholder": "password",
|
||||
|
|
|
@ -176,7 +176,7 @@
|
|||
"title": "Eliminar usuario"
|
||||
},
|
||||
"password_reset": {
|
||||
"description": "Cambiar contraseña a <1></1>",
|
||||
"description": "Cambiar contraseña a <1>{{username}}</1>",
|
||||
"form": {
|
||||
"label": "Nueva contraseña",
|
||||
"placeholder": "contraseña",
|
||||
|
|
|
@ -176,7 +176,7 @@
|
|||
"title": "Supprimer l'utilisateur"
|
||||
},
|
||||
"password_reset": {
|
||||
"description": "Changer le mot de passe pour <1></1>",
|
||||
"description": "Changer le mot de passe pour <1>{{username}}</1>",
|
||||
"form": {
|
||||
"label": "Nouveau mot de passe",
|
||||
"placeholder": "mot de passe",
|
||||
|
|
|
@ -176,7 +176,7 @@
|
|||
"title": "Cancella utente"
|
||||
},
|
||||
"password_reset": {
|
||||
"description": "Cambia password per <1></1>",
|
||||
"description": "Cambia password per <1>{{username}}</1>",
|
||||
"form": {
|
||||
"label": "Nuova password",
|
||||
"placeholder": "password",
|
||||
|
|
|
@ -176,7 +176,7 @@
|
|||
"title": "Usuń użytkownika"
|
||||
},
|
||||
"password_reset": {
|
||||
"description": "Zmiana hasła dla <1></1>",
|
||||
"description": "Zmiana hasła dla <1>{{username}}</1>",
|
||||
"form": {
|
||||
"label": "Nowe hasło",
|
||||
"placeholder": "hasło",
|
||||
|
|
|
@ -176,7 +176,7 @@
|
|||
"title": "Radera användare"
|
||||
},
|
||||
"password_reset": {
|
||||
"description": "Ändra lösenord för <1></1>",
|
||||
"description": "Ändra lösenord för <1>{{username}}</1>",
|
||||
"form": {
|
||||
"label": "Nytt lösenord",
|
||||
"placeholder": "lösenord",
|
||||
|
|
|
@ -4,7 +4,7 @@ import Layout from '../../components/layout/Layout'
|
|||
import styled from 'styled-components'
|
||||
import { Link } from 'react-router-dom'
|
||||
import SingleFaceGroup from './SingleFaceGroup/SingleFaceGroup'
|
||||
import { Button, Icon, Input } from 'semantic-ui-react'
|
||||
import { Button, TextField } from '../../primitives/form/Input'
|
||||
import FaceCircleImage from './FaceCircleImage'
|
||||
import useScrollPagination from '../../hooks/useScrollPagination'
|
||||
import PaginateLoader from '../../components/PaginateLoader'
|
||||
|
@ -91,7 +91,7 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
|
|||
const { t } = useTranslation()
|
||||
const [editLabel, setEditLabel] = useState(false)
|
||||
const [inputValue, setInputValue] = useState(group.label ?? '')
|
||||
const inputRef = createRef<Input>()
|
||||
const inputRef = createRef<HTMLInputElement>()
|
||||
|
||||
const [setGroupLabel, { loading }] = useMutation<
|
||||
setGroupLabel,
|
||||
|
@ -117,21 +117,11 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
|
|||
}
|
||||
}, [loading])
|
||||
|
||||
const onKeyUp = (e: React.ChangeEvent<HTMLInputElement> & KeyboardEvent) => {
|
||||
const onKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key == 'Escape') {
|
||||
resetLabel()
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key == 'Enter') {
|
||||
setGroupLabel({
|
||||
variables: {
|
||||
groupID: group.id,
|
||||
label: e.target.value == '' ? null : e.target.value,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let label
|
||||
|
@ -145,20 +135,29 @@ export const FaceDetails = ({ group }: FaceDetailsProps) => {
|
|||
<FaceLabel>
|
||||
{group.label ?? t('people_page.face_group.unlabeled', 'Unlabeled')}
|
||||
</FaceLabel>
|
||||
<EditIcon name="pencil" />
|
||||
{/* <EditIcon name="pencil" /> */}
|
||||
</FaceDetailsButton>
|
||||
)
|
||||
} else {
|
||||
label = (
|
||||
<FaceDetailsButton labeled={!!group.label}>
|
||||
<Input
|
||||
<TextField
|
||||
className="w-[160px]"
|
||||
loading={loading}
|
||||
ref={inputRef}
|
||||
size="mini"
|
||||
// size="mini"
|
||||
placeholder={t('people_page.face_group.label_placeholder', 'Label')}
|
||||
icon="arrow right"
|
||||
// icon="arrow right"
|
||||
value={inputValue}
|
||||
onKeyUp={onKeyUp}
|
||||
action={() =>
|
||||
setGroupLabel({
|
||||
variables: {
|
||||
groupID: group.id,
|
||||
label: inputValue == '' ? null : inputValue,
|
||||
},
|
||||
})
|
||||
}
|
||||
onKeyDown={onKeyUp}
|
||||
onChange={e => setInputValue(e.target.value)}
|
||||
onBlur={() => {
|
||||
resetLabel()
|
||||
|
@ -180,16 +179,16 @@ const FaceImagesCount = styled.span`
|
|||
border-radius: 4px;
|
||||
`
|
||||
|
||||
const EditIcon = styled(Icon)`
|
||||
margin-left: 6px !important;
|
||||
opacity: 0 !important;
|
||||
// const EditIcon = styled(Icon)`
|
||||
// margin-left: 6px !important;
|
||||
// opacity: 0 !important;
|
||||
|
||||
transition: opacity 100ms;
|
||||
// transition: opacity 100ms;
|
||||
|
||||
${FaceDetailsButton}:hover &, ${FaceDetailsButton}:focus-visible & {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
`
|
||||
// ${FaceDetailsButton}:hover &, ${FaceDetailsButton}:focus-visible & {
|
||||
// opacity: 1 !important;
|
||||
// }
|
||||
// `
|
||||
|
||||
type FaceGroupProps = {
|
||||
group: myFaces_myFaceGroups
|
||||
|
@ -251,13 +250,11 @@ const PeopleGallery = () => {
|
|||
return (
|
||||
<Layout title={t('title.people', 'People')}>
|
||||
<Button
|
||||
loading={recognizeUnlabeledLoading}
|
||||
disabled={recognizeUnlabeledLoading}
|
||||
onClick={() => {
|
||||
recognizeUnlabeled()
|
||||
}}
|
||||
>
|
||||
<Icon name="sync" />
|
||||
{t(
|
||||
'people_page.recognize_unlabeled_faces_button',
|
||||
'Recognize unlabeled faces'
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import React, { useState } from 'react'
|
||||
import { gql, useMutation } from '@apollo/client'
|
||||
import { Button, Form, Input, Modal, ModalProps } from 'semantic-ui-react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { settingsUsersQuery_user } from './__generated__/settingsUsersQuery'
|
||||
import Modal from '../../../primitives/Modal'
|
||||
import { TextField } from '../../../primitives/form/Input'
|
||||
|
||||
const changeUserPasswordMutation = gql`
|
||||
mutation changeUserPassword($userId: ID!, $password: String!) {
|
||||
|
@ -12,7 +13,7 @@ const changeUserPasswordMutation = gql`
|
|||
}
|
||||
`
|
||||
|
||||
interface ChangePasswordModalProps extends ModalProps {
|
||||
interface ChangePasswordModalProps {
|
||||
onClose(): void
|
||||
open: boolean
|
||||
user: settingsUsersQuery_user
|
||||
|
@ -22,7 +23,6 @@ const ChangePasswordModal = ({
|
|||
onClose,
|
||||
user,
|
||||
open,
|
||||
...props
|
||||
}: ChangePasswordModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [passwordInput, setPasswordInput] = useState('')
|
||||
|
@ -34,50 +34,50 @@ const ChangePasswordModal = ({
|
|||
})
|
||||
|
||||
return (
|
||||
<Modal open={open} {...props}>
|
||||
<Modal.Header>
|
||||
{t('settings.users.password_reset.title', 'Change password')}
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
<p>
|
||||
<Trans t={t} i18nKey="settings.users.password_reset.description">
|
||||
Change password for <b>{user.username}</b>
|
||||
</Trans>
|
||||
</p>
|
||||
<Form>
|
||||
<Form.Field>
|
||||
<label>
|
||||
{t('settings.users.password_reset.form.label', 'New password')}
|
||||
</label>
|
||||
<Input
|
||||
placeholder={t(
|
||||
'settings.users.password_reset.form.placeholder',
|
||||
'password'
|
||||
)}
|
||||
onChange={e => setPasswordInput(e.target.value)}
|
||||
type="password"
|
||||
/>
|
||||
</Form.Field>
|
||||
</Form>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button onClick={() => onClose && onClose()}>
|
||||
{t('general.action.cancel', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
positive
|
||||
onClick={() => {
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
title={t('settings.users.password_reset.title', 'Change password')}
|
||||
description={
|
||||
<Trans t={t} i18nKey="settings.users.password_reset.description">
|
||||
Change password for <b>{{ username: user.username }}</b>
|
||||
</Trans>
|
||||
}
|
||||
actions={[
|
||||
{
|
||||
key: 'cancel',
|
||||
label: t('general.action.cancel', 'Cancel'),
|
||||
onClick: () => onClose && onClose(),
|
||||
},
|
||||
{
|
||||
key: 'change_password',
|
||||
label: t(
|
||||
'settings.users.password_reset.form.submit',
|
||||
'Change password'
|
||||
),
|
||||
variant: 'positive',
|
||||
onClick: () => {
|
||||
changePassword({
|
||||
variables: {
|
||||
userId: user.id,
|
||||
password: passwordInput,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
{t('settings.users.password_reset.form.submit', 'Change password')}
|
||||
</Button>
|
||||
</Modal.Actions>
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div className="w-[360px]">
|
||||
<TextField
|
||||
label={t('settings.users.password_reset.form.label', 'New password')}
|
||||
placeholder={t(
|
||||
'settings.users.password_reset.form.placeholder',
|
||||
'password'
|
||||
)}
|
||||
onChange={e => setPasswordInput(e.target.value)}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
TableRow,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableScrollWrapper,
|
||||
} from '../../../primitives/Table'
|
||||
import { useQuery, gql } from '@apollo/client'
|
||||
import UserRow from './UserRow'
|
||||
|
@ -53,55 +54,60 @@ const UsersTable = () => {
|
|||
<div>
|
||||
<SectionTitle>{t('settings.users.title', 'Users')}</SectionTitle>
|
||||
<Loader active={loading} />
|
||||
<Table className="w-full max-w-6xl">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell>
|
||||
{t('settings.users.table.column_names.username', 'Username')}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>
|
||||
{t('settings.users.table.column_names.photo_path', 'Photo path')}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>
|
||||
{t(
|
||||
'settings.users.table.column_names.capabilities',
|
||||
'Capabilities'
|
||||
)}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell className="w-0 whitespace-nowrap">
|
||||
{t('settings.users.table.column_names.action', 'Action')}
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableScrollWrapper>
|
||||
<Table className="w-full max-w-6xl">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell>
|
||||
{t('settings.users.table.column_names.username', 'Username')}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>
|
||||
{t(
|
||||
'settings.users.table.column_names.photo_path',
|
||||
'Photo path'
|
||||
)}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell>
|
||||
{t(
|
||||
'settings.users.table.column_names.capabilities',
|
||||
'Capabilities'
|
||||
)}
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell className="w-0 whitespace-nowrap">
|
||||
{t('settings.users.table.column_names.action', 'Action')}
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{userRows}
|
||||
<AddUserRow
|
||||
show={showAddUser}
|
||||
setShow={setShowAddUser}
|
||||
onUserAdded={() => {
|
||||
setShowAddUser(false)
|
||||
refetch()
|
||||
}}
|
||||
/>
|
||||
</TableBody>
|
||||
<TableBody>
|
||||
{userRows}
|
||||
<AddUserRow
|
||||
show={showAddUser}
|
||||
setShow={setShowAddUser}
|
||||
onUserAdded={() => {
|
||||
setShowAddUser(false)
|
||||
refetch()
|
||||
}}
|
||||
/>
|
||||
</TableBody>
|
||||
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableHeaderCell colSpan={4} className="text-right">
|
||||
<Button
|
||||
className="bg-white"
|
||||
variant="positive"
|
||||
disabled={showAddUser}
|
||||
onClick={() => setShowAddUser(true)}
|
||||
>
|
||||
<Icon name="add" />
|
||||
{t('settings.users.table.new_user', 'New user')}
|
||||
</Button>
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableHeaderCell colSpan={4} className="text-right">
|
||||
<Button
|
||||
className="bg-white"
|
||||
variant="positive"
|
||||
disabled={showAddUser}
|
||||
onClick={() => setShowAddUser(true)}
|
||||
>
|
||||
<Icon name="add" />
|
||||
{t('settings.users.table.new_user', 'New user')}
|
||||
</Button>
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
</TableScrollWrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -202,7 +202,7 @@ const MorePopoverSectionPassword = ({
|
|||
disabled={!activated}
|
||||
type={passwordHidden ? 'password' : 'text'}
|
||||
value={passwordInputValue}
|
||||
className="mt-2"
|
||||
className="mt-2 w-full"
|
||||
onKeyDown={event => {
|
||||
if (
|
||||
event.shiftKey ||
|
||||
|
@ -251,7 +251,7 @@ const MorePopover = ({ id, share, query }: MorePopoverProps) => {
|
|||
<MorePopoverSectionPassword id={id} share={share} query={query} />
|
||||
<div className="px-4 py-2 border-t border-gray-200 mt-2 mb-2">
|
||||
<Checkbox label="Expiration date" />
|
||||
<TextField className="mt-2" />
|
||||
<TextField className="mt-2 w-full" />
|
||||
</div>
|
||||
</ArrowPopoverPanel>
|
||||
</Popover.Panel>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import React from 'react'
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { Button } from './form/Input'
|
||||
|
||||
type ModalAction = {
|
||||
key: string
|
||||
label: string
|
||||
variant?: 'negative' | 'positive' | 'default'
|
||||
onClick(event: React.MouseEvent<HTMLButtonElement>): void
|
||||
}
|
||||
|
||||
type ModalProps = {
|
||||
title: string
|
||||
description: React.ReactNode
|
||||
children: React.ReactNode
|
||||
actions: ModalAction[]
|
||||
open: boolean
|
||||
onClose(): void
|
||||
}
|
||||
|
||||
const Modal = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
actions,
|
||||
open,
|
||||
onClose,
|
||||
}: ModalProps) => {
|
||||
const actionElms = actions.map(x => (
|
||||
<Button
|
||||
key={x.key}
|
||||
onClick={e => x.onClick(e)}
|
||||
variant={x.variant}
|
||||
className="bg-white"
|
||||
>
|
||||
{x.label}
|
||||
</Button>
|
||||
))
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
className="fixed z-40 inset-0 overflow-y-auto"
|
||||
>
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Dialog.Overlay className="fixed inset-0 bg-black opacity-30" />
|
||||
|
||||
<div className="fixed bg-white rounded max-w-[calc(100%-16px)] mx-auto rounded shadow-md border">
|
||||
<div className="p-2">
|
||||
<Dialog.Title className="text-xl mb-1">{title}</Dialog.Title>
|
||||
<Dialog.Description className="text-sm mb-4">
|
||||
{description}
|
||||
</Dialog.Description>
|
||||
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<div className="bg-gray-50 p-2 flex gap-2 justify-end mt-4">
|
||||
{actionElms}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal
|
|
@ -39,3 +39,7 @@ export const TableCell = styled.td.attrs({
|
|||
export const TableHeaderCell = styled.th.attrs({
|
||||
className: 'bg-gray-50 py-2 px-2 align-top font-semibold' as string,
|
||||
})``
|
||||
|
||||
export const TableScrollWrapper = styled.div.attrs({
|
||||
className: 'block overflow-x-auto whitespace-nowrap',
|
||||
})``
|
||||
|
|
|
@ -77,7 +77,7 @@ export const TextField = forwardRef(
|
|||
)
|
||||
} else if (action) {
|
||||
input = (
|
||||
<div className="relative">
|
||||
<div className="relative inline-block">
|
||||
{input}
|
||||
<button
|
||||
disabled={disabled}
|
||||
|
|
Loading…
Reference in New Issue