mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 19:14:16 +00:00
added tap_share
settings
This commit is contained in:
parent
02db8c6c13
commit
972bd9802b
9
packages/app/constants/settings/tap_share/context.js
Normal file
9
packages/app/constants/settings/tap_share/context.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
const RegisterNewTagStepsDefaultContext = {
|
||||||
|
next: () => { },
|
||||||
|
prev: () => { },
|
||||||
|
submit: () => { },
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.createContext(RegisterNewTagStepsDefaultContext)
|
11
packages/app/constants/settings/tap_share/errors.js
Normal file
11
packages/app/constants/settings/tap_share/errors.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export default {
|
||||||
|
NFC_NOT_SUPPORTED: "Your device doesn't support NFC.",
|
||||||
|
NFC_NOT_ENABLED: "NFC is not enabled.",
|
||||||
|
NFC_NOT_READABLE: "NFC is not readable.",
|
||||||
|
NFC_NOT_WRITABLE: "NFC is not writable.",
|
||||||
|
NFC_READ_ERROR: "Cannot read NFC tag. Please try again.",
|
||||||
|
NFC_WRITE_ERROR: "Cannot write NFC tag. Please try again.",
|
||||||
|
NFC_NOT_OWNER: "This tag is not owned by you.",
|
||||||
|
NFC_NOT_REGISTERED: "This tag is not registered.",
|
||||||
|
NFC_NOT_MATCH: "This tag is not match with the registered tag.",
|
||||||
|
}
|
301
packages/app/constants/settings/tap_share/index.jsx
Normal file
301
packages/app/constants/settings/tap_share/index.jsx
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import { Input } from "antd-mobile"
|
||||||
|
import classnames from "classnames"
|
||||||
|
import NFCModel from "comty.js/models/nfc"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
|
||||||
|
import AnimationPlayer from "components/AnimationPlayer"
|
||||||
|
|
||||||
|
import StepsContext from "./context"
|
||||||
|
import NFC_ERRORS from "./errors"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
import CheckRegister from "./steps/check_register"
|
||||||
|
import DataEditor from "./steps/data_editor"
|
||||||
|
import TagWritter from "./steps/tag_writter"
|
||||||
|
import Success from "./steps/success"
|
||||||
|
|
||||||
|
const RegisterNewTagSteps = [
|
||||||
|
CheckRegister,
|
||||||
|
DataEditor,
|
||||||
|
TagWritter,
|
||||||
|
Success,
|
||||||
|
]
|
||||||
|
|
||||||
|
const RegisterNewTag = (props) => {
|
||||||
|
const [step, setStep] = React.useState(0)
|
||||||
|
const [stepsValues, setStepsValues] = React.useState({
|
||||||
|
...props.tagData ?? {}
|
||||||
|
})
|
||||||
|
|
||||||
|
const nextStep = () => {
|
||||||
|
setStep((step) => step + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevStep = () => {
|
||||||
|
setStep((step) => step - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const finish = () => {
|
||||||
|
if (typeof props.onFinish === "function") {
|
||||||
|
props.onFinish()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof props.close === "function") {
|
||||||
|
props.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a react context for the steps
|
||||||
|
const StepsContextValue = {
|
||||||
|
next: nextStep,
|
||||||
|
prev: prevStep,
|
||||||
|
values: stepsValues,
|
||||||
|
setValue: (key, value) => {
|
||||||
|
setStepsValues((stepsValues) => {
|
||||||
|
return {
|
||||||
|
...stepsValues,
|
||||||
|
[key]: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onFinish: finish,
|
||||||
|
nfcReader: app.cores.nfc.instance(),
|
||||||
|
close: props.close
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.tagData) {
|
||||||
|
return <div className="tap-share-register">
|
||||||
|
<div className="tap-share-register-content">
|
||||||
|
<StepsContext.Provider value={StepsContextValue}>
|
||||||
|
<DataEditor
|
||||||
|
onFinish={finish}
|
||||||
|
/>
|
||||||
|
</StepsContext.Provider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.cores.nfc.incompatible) {
|
||||||
|
return <antd.Result
|
||||||
|
status="error"
|
||||||
|
title="Error"
|
||||||
|
subTitle="Your device doesn't support NFC."
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div
|
||||||
|
className={classnames(
|
||||||
|
"tap-share-register",
|
||||||
|
{
|
||||||
|
["compact"]: step > 0
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="tap-share-register-header">
|
||||||
|
<antd.Button
|
||||||
|
type="link"
|
||||||
|
onClick={prevStep}
|
||||||
|
disabled={step === 0}
|
||||||
|
icon={<Icons.MdChevronLeft />}
|
||||||
|
className={classnames(
|
||||||
|
"tap-share-register-header-back",
|
||||||
|
{
|
||||||
|
["hidden"]: step === 0
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="tap-share-register-header-icon">
|
||||||
|
<Icons.MdNfc />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
Register new tag
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tap-share-register-content">
|
||||||
|
<StepsContext.Provider value={StepsContextValue}>
|
||||||
|
{
|
||||||
|
React.createElement(RegisterNewTagSteps[step])
|
||||||
|
}
|
||||||
|
</StepsContext.Provider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagItem = (props) => {
|
||||||
|
return <div
|
||||||
|
key={props.tag.serialNumber}
|
||||||
|
id={props.tag.serialNumber}
|
||||||
|
className="tap-share-own_tags-item"
|
||||||
|
>
|
||||||
|
<div className="tap-share-own_tags-item-icon">
|
||||||
|
<Icons.MdNfc />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tap-share-own_tags-item-title">
|
||||||
|
<h4>
|
||||||
|
{props.tag.alias}
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{props.tag.serial}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tap-share-own_tags-item-actions">
|
||||||
|
<antd.Button
|
||||||
|
icon={<Icons.MdEdit />}
|
||||||
|
onClick={props.onEdit}
|
||||||
|
/>
|
||||||
|
<antd.Button
|
||||||
|
icon={<Icons.MdDelete />}
|
||||||
|
danger
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
class OwnTags extends React.Component {
|
||||||
|
state = {
|
||||||
|
loading: true,
|
||||||
|
error: null,
|
||||||
|
data: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData = async () => {
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await NFCModel.getOwnTags()
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
this.setState({
|
||||||
|
error: err.message,
|
||||||
|
loading: false,
|
||||||
|
data: null
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
data: result,
|
||||||
|
error: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTagRead = async (error, tag) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownedTag = this.state.data.find((ownedTag) => {
|
||||||
|
return ownedTag.serial === tag.serialNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(ownedTag)
|
||||||
|
|
||||||
|
if (!ownedTag) {
|
||||||
|
app.message.error("This tag is not registered or you don't have permission to edit it.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return OpenTagEditor({
|
||||||
|
tag: ownedTag
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
|
await this.loadData()
|
||||||
|
|
||||||
|
app.cores.nfc.subscribe(this.handleTagRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
app.cores.nfc.unsubscribe(this.handleTagRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.loading) {
|
||||||
|
return <div className="tap-share-own_tags">
|
||||||
|
<antd.Skeleton />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="tap-share-own_tags">
|
||||||
|
{
|
||||||
|
this.state.data.length === 0 && <antd.Empty
|
||||||
|
description="You don't have any tags yet."
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
this.state.data.length > 0 && this.state.data.map((tag) => {
|
||||||
|
return <TagItem
|
||||||
|
key={tag.serialNumber}
|
||||||
|
tag={tag}
|
||||||
|
onEdit={() => {
|
||||||
|
OpenTagEditor({
|
||||||
|
tag
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OpenTagEditor = ({ tag, onFinish = () => app.navigation.softReload() } = {}) => {
|
||||||
|
app.DrawerController.open("tag_register", RegisterNewTag, {
|
||||||
|
componentProps: {
|
||||||
|
tagData: tag,
|
||||||
|
onFinish: onFinish,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const TapShareRender = () => {
|
||||||
|
return <div className="tap-share-render">
|
||||||
|
<div className="tap-share-field">
|
||||||
|
<div className="tap-share-field_header">
|
||||||
|
<h1>
|
||||||
|
<Icons.MdSpoke /> Registered Tags
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<span className="tip">
|
||||||
|
<Icons.MdInfo /> You can quickly edit your tags by tapping them.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<OwnTags />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<antd.Button
|
||||||
|
type="primary"
|
||||||
|
icon={<Icons.Plus />}
|
||||||
|
onClick={() => OpenTagEditor()}
|
||||||
|
>
|
||||||
|
Add new
|
||||||
|
</antd.Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
id: "tap_share",
|
||||||
|
icon: "MdNfc",
|
||||||
|
label: "Tap Share",
|
||||||
|
group: "app",
|
||||||
|
render: TapShareRender
|
||||||
|
}
|
281
packages/app/constants/settings/tap_share/index.less
Normal file
281
packages/app/constants/settings/tap_share/index.less
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
.tap-share-render {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.tap-share-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background-color: var(--background-color-accent);
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-field_header {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-own_tags {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.tap-share-own_tags-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.tap-share-own_tags-item-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-own_tags-item-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
span {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
opacity: 0.7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-own_tags-item-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-register {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
.tap-share-register-header {
|
||||||
|
padding: 10px;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.tap-share-register-header-icon {
|
||||||
|
font-size: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-register-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
background-color: var(--background-color-accent);
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-register-header-back {
|
||||||
|
font-size: 5rem;
|
||||||
|
color: var(--colorPrimary);
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-register-header-icon {
|
||||||
|
font-size: 5rem;
|
||||||
|
color: var(--colorPrimary);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-register-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.tap-share-register_step {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
transition: all 150ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.centered {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.ant-form-item {
|
||||||
|
height: fit-content;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.ant-form_with_selector {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.ant-select {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item-label {
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
label {
|
||||||
|
height: fit-content;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tap-share-register_step_actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animation-player {
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
.phone-loop {
|
||||||
|
color: #17b2ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-loop {
|
||||||
|
color: var(--colorPrimary);
|
||||||
|
|
||||||
|
path {
|
||||||
|
fill: currentColor;
|
||||||
|
stroke: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import NFCModel from "comty.js/models/nfc"
|
||||||
|
|
||||||
|
import AnimationPlayer from "components/AnimationPlayer"
|
||||||
|
|
||||||
|
import NFC_ERRORS from "../../errors"
|
||||||
|
import StepsContext from "../../context"
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
const [error, setError] = React.useState(null)
|
||||||
|
const [loading, setLoading] = React.useState(false)
|
||||||
|
const context = React.useContext(StepsContext)
|
||||||
|
|
||||||
|
const readTagRegister = async (error, data) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
setError(NFC_ERRORS.NFC_READ_ERROR)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(null)
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
|
const registerResult = await NFCModel.getTagBySerial(data.serialNumber).catch((err) => {
|
||||||
|
if (err.response.status === 404) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: err,
|
||||||
|
is_owner: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(registerResult)
|
||||||
|
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
if (!registerResult) {
|
||||||
|
// this means that the tag is not registered, step to the next step
|
||||||
|
context.setValue("serial", data.serialNumber)
|
||||||
|
|
||||||
|
unregisterScan()
|
||||||
|
|
||||||
|
return context.next()
|
||||||
|
} else {
|
||||||
|
if (registerResult.error) {
|
||||||
|
return setError("Cannot check if the tag is registered. Please try again.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registerResult.is_owner) {
|
||||||
|
// this means that the tag is registered but not owned by the user
|
||||||
|
return setError(NFC_ERRORS.NFC_NOT_OWNER)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.setValue("serial", data.serialNumber)
|
||||||
|
context.setValue("alias", registerResult.alias)
|
||||||
|
context.setValue("behavior", registerResult.behavior)
|
||||||
|
|
||||||
|
unregisterScan()
|
||||||
|
|
||||||
|
return context.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unregisterScan = () => {
|
||||||
|
app.cores.nfc.unsubscribe(readTagRegister)
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
app.cores.nfc.subscribe(readTagRegister)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unregisterScan()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div className="tap-share-register_step centered">
|
||||||
|
<AnimationPlayer
|
||||||
|
src="https://storage.ragestudio.net/comty-static-assets/animations/nfc_tap.json"
|
||||||
|
loop={true}
|
||||||
|
className={[
|
||||||
|
{
|
||||||
|
["loading"]: loading,
|
||||||
|
["error"]: error
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
error && <antd.Alert
|
||||||
|
type="error"
|
||||||
|
message={error}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1 style={{
|
||||||
|
opacity: loading ? 0 : 1
|
||||||
|
}}>
|
||||||
|
Tap your tag to your phone
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{
|
||||||
|
loading && <antd.Spin />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import { Input } from "antd-mobile"
|
||||||
|
import NFCModel from "comty.js/models/nfc"
|
||||||
|
import { Icons } from "components/Icons"
|
||||||
|
|
||||||
|
import StepsContext from "../../context"
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
const context = React.useContext(StepsContext)
|
||||||
|
|
||||||
|
if (!context.values.serial) {
|
||||||
|
app.message.error("Serial not available.")
|
||||||
|
|
||||||
|
return <>
|
||||||
|
Serial not available, please try again.
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOnFinish = async (values) => {
|
||||||
|
context.setValue("alias", values.alias)
|
||||||
|
context.setValue("behavior", values.behavior)
|
||||||
|
|
||||||
|
const result = await NFCModel.registerTag(context.values.serial, {
|
||||||
|
alias: values.alias,
|
||||||
|
behavior: values.behavior
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
|
||||||
|
app.message.error("Cannot register your tag. Please try again.")
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.endpoint_url) {
|
||||||
|
app.message.error("Cannot register your tag. Please try again.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.onFinish) {
|
||||||
|
app.message.success("All changes have been saved.")
|
||||||
|
return props.onFinish(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.message.success("Your tag has been registered successfully.")
|
||||||
|
|
||||||
|
context.setValue("endpoint_url", result.endpoint_url)
|
||||||
|
|
||||||
|
return context.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="tap-share-register_step">
|
||||||
|
<h2>
|
||||||
|
Tag Data
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<antd.Form
|
||||||
|
name="register_tag"
|
||||||
|
onFinish={handleOnFinish}
|
||||||
|
initialValues={{
|
||||||
|
serial: context.values.serial,
|
||||||
|
alias: context.values.alias,
|
||||||
|
behavior: context.values.behavior,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<antd.Form.Item
|
||||||
|
name="serial"
|
||||||
|
label={<>
|
||||||
|
<Icons.MdTag />
|
||||||
|
Serial
|
||||||
|
</>}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</antd.Form.Item>
|
||||||
|
<antd.Form.Item
|
||||||
|
name="alias"
|
||||||
|
label={<>
|
||||||
|
<Icons.Tag />
|
||||||
|
Alias
|
||||||
|
</>}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "Please input an alias."
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<antd.Input
|
||||||
|
placeholder="Short name for your tag"
|
||||||
|
/>
|
||||||
|
</antd.Form.Item>
|
||||||
|
|
||||||
|
<antd.Form.Item
|
||||||
|
label={<>
|
||||||
|
<Icons.MdWebhook />
|
||||||
|
Behavior
|
||||||
|
</>}
|
||||||
|
>
|
||||||
|
<span className="description">
|
||||||
|
What will happen when someone taps your tag?
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className="ant-form_with_selector">
|
||||||
|
<antd.Form.Item
|
||||||
|
name={["behavior", "type"]}
|
||||||
|
noStyle
|
||||||
|
size="large"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "Please select your tag behavior."
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<antd.Select
|
||||||
|
placeholder="Options"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<antd.Select.Option
|
||||||
|
value="url"
|
||||||
|
>
|
||||||
|
Custom URL
|
||||||
|
</antd.Select.Option>
|
||||||
|
|
||||||
|
<antd.Select.Option
|
||||||
|
value="profile"
|
||||||
|
>
|
||||||
|
Profile
|
||||||
|
</antd.Select.Option>
|
||||||
|
|
||||||
|
<antd.Select.Option
|
||||||
|
value="post"
|
||||||
|
>
|
||||||
|
Post
|
||||||
|
</antd.Select.Option>
|
||||||
|
</antd.Select>
|
||||||
|
</antd.Form.Item>
|
||||||
|
|
||||||
|
<antd.Form.Item
|
||||||
|
name={["behavior", "value"]}
|
||||||
|
noStyle
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "Please select your behavior value."
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<antd.Input
|
||||||
|
placeholder="value"
|
||||||
|
size="large"
|
||||||
|
autoCapitalize="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
spellCheck="false"
|
||||||
|
/>
|
||||||
|
</antd.Form.Item>
|
||||||
|
</div>
|
||||||
|
</antd.Form.Item>
|
||||||
|
|
||||||
|
<antd.Form.Item
|
||||||
|
colon={false}
|
||||||
|
>
|
||||||
|
<antd.Button
|
||||||
|
block
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
htmlType="submit"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</antd.Button>
|
||||||
|
</antd.Form.Item>
|
||||||
|
</antd.Form>
|
||||||
|
</div>
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
import AnimationPlayer from "components/AnimationPlayer"
|
||||||
|
|
||||||
|
import StepsContext from "../../context"
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
const context = React.useContext(StepsContext)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof context.onFinish === "function") {
|
||||||
|
context.onFinish()
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div className="tap-share-register_step centered">
|
||||||
|
<AnimationPlayer
|
||||||
|
src="https://assets10.lottiefiles.com/packages/lf20_dyy9le6w.json"
|
||||||
|
/>
|
||||||
|
<h1>
|
||||||
|
Your tag is ready to use!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
|
||||||
|
import AnimationPlayer from "components/AnimationPlayer"
|
||||||
|
|
||||||
|
import NFC_ERRORS from "../../errors"
|
||||||
|
import StepsContext from "../../context"
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
const context = React.useContext(StepsContext)
|
||||||
|
|
||||||
|
const [loading, setLoading] = React.useState(false)
|
||||||
|
const [error, setError] = React.useState(null)
|
||||||
|
|
||||||
|
const abortController = React.useRef(new AbortController())
|
||||||
|
|
||||||
|
const handleWritter = async (error, tag) => {
|
||||||
|
if (error) {
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
setError(NFC_ERRORS.NFC_READ_ERROR)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const nfcInstance = app.cores.nfc.instance()
|
||||||
|
|
||||||
|
if (!nfcInstance) {
|
||||||
|
setError(NFC_ERRORS.NFC_NOT_AVAILABLE)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setError(null)
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
if (tag.serialNumber !== context.values.serial) {
|
||||||
|
setError(NFC_ERRORS.NFC_NOT_MATCH)
|
||||||
|
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
nfcInstance.write({
|
||||||
|
records: [{
|
||||||
|
recordType: "url",
|
||||||
|
data: context.values.endpoint_url
|
||||||
|
}]
|
||||||
|
}, {
|
||||||
|
signal: abortController.current.signal
|
||||||
|
}).then(() => {
|
||||||
|
app.message.success("Tag written successfully.")
|
||||||
|
setLoading(false)
|
||||||
|
|
||||||
|
return context.next()
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
|
||||||
|
setError(NFC_ERRORS.NFC_WRITE_ERROR)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
app.cores.nfc.subscribe(handleWritter)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
app.cores.nfc.unsubscribe(handleWritter)
|
||||||
|
abortController.current.abort("finished")
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <div className="tap-share-register_step centered">
|
||||||
|
<h1>
|
||||||
|
Your tag is ready to write!
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Tap your tag to your phone to write the data.
|
||||||
|
<br />
|
||||||
|
This is only necessary the first time you use your tag.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<h2 style={{
|
||||||
|
opacity: loading ? 1 : 0,
|
||||||
|
color: loading ? "red" : "inherit"
|
||||||
|
}}>
|
||||||
|
Please do not pick up the tag
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<AnimationPlayer
|
||||||
|
src="https://storage.ragestudio.net/comty-static-assets/animations/nfc_tap.json"
|
||||||
|
className={[
|
||||||
|
{
|
||||||
|
["loading"]: loading,
|
||||||
|
["error"]: error
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
loop
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
error && <antd.Alert
|
||||||
|
type="error"
|
||||||
|
message={error}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user