diff --git a/android/app/build.gradle b/android/app/build.gradle
index d827280..b90cbe7 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "com.donetick.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 24
- versionName "1.2.2"
+ versionCode 25
+ versionName "1.2.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
index 1e82591..9f6e577 100644
--- a/android/app/capacitor.build.gradle
+++ b/android/app/capacitor.build.gradle
@@ -18,6 +18,7 @@ dependencies {
implementation project(':capacitor-preferences')
implementation project(':capacitor-push-notifications')
implementation project(':capacitor-status-bar')
+ implementation project(':capgo-capacitor-nfc')
implementation project(':capgo-capacitor-social-login')
implementation project(':revenuecat-purchases-capacitor')
implementation project(':revenuecat-purchases-capacitor-ui')
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 3d5b3c5..76aa59f 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -20,13 +20,20 @@
-
+
+
+
+
+
+
+
+
+
diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle
index 3ad1593..e49f972 100644
--- a/android/capacitor.settings.gradle
+++ b/android/capacitor.settings.gradle
@@ -29,6 +29,9 @@ project(':capacitor-push-notifications').projectDir = new File('../node_modules/
include ':capacitor-status-bar'
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
+include ':capgo-capacitor-nfc'
+project(':capgo-capacitor-nfc').projectDir = new File('../node_modules/@capgo/capacitor-nfc/android')
+
include ':capgo-capacitor-social-login'
project(':capgo-capacitor-social-login').projectDir = new File('../node_modules/@capgo/capacitor-social-login/android')
diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj
index be59e39..02f8c99 100644
--- a/ios/App/App.xcodeproj/project.pbxproj
+++ b/ios/App/App.xcodeproj/project.pbxproj
@@ -359,13 +359,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 24;
+ CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 6UJJ78R3BS;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.donetick.app;
- MARKETING_VERSION = 1.2.2;
+ MARKETING_VERSION = 1.2.3;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -379,12 +379,12 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = App/App.entitlements;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 24;
+ CURRENT_PROJECT_VERSION = 25;
DEVELOPMENT_TEAM = 6UJJ78R3BS;
INFOPLIST_FILE = App/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- MARKETING_VERSION = 1.2.2;
+ MARKETING_VERSION = 1.2.3;
PRODUCT_BUNDLE_IDENTIFIER = com.donetick.app;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
diff --git a/ios/App/App/App.entitlements b/ios/App/App/App.entitlements
index 80b5221..d2bef04 100644
--- a/ios/App/App/App.entitlements
+++ b/ios/App/App/App.entitlements
@@ -2,6 +2,10 @@
+ com.apple.developer.nfc.readersession.formats
+
+ NDEF
+
aps-environment
development
com.apple.developer.applesignin
diff --git a/ios/App/App/Info.plist b/ios/App/App/Info.plist
index 533cd70..5a58712 100644
--- a/ios/App/App/Info.plist
+++ b/ios/App/App/Info.plist
@@ -2,6 +2,8 @@
+ NFCReaderUsageDescription
+ Donetick uses NFC to read and write chore tags for quick task completion.
CFBundleDevelopmentRegion
en
CFBundleDisplayName
diff --git a/ios/App/Podfile b/ios/App/Podfile
index 9602381..e5d54c4 100644
--- a/ios/App/Podfile
+++ b/ios/App/Podfile
@@ -20,6 +20,7 @@ def capacitor_pods
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
pod 'CapacitorPushNotifications', :path => '../../node_modules/@capacitor/push-notifications'
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
+ pod 'CapgoCapacitorNfc', :path => '../../node_modules/@capgo/capacitor-nfc'
pod 'CapgoCapacitorSocialLogin', :path => '../../node_modules/@capgo/capacitor-social-login'
pod 'RevenuecatPurchasesCapacitor', :path => '../../node_modules/@revenuecat/purchases-capacitor'
pod 'RevenuecatPurchasesCapacitorUi', :path => '../../node_modules/@revenuecat/purchases-capacitor-ui'
diff --git a/ios/App/Podfile.lock b/ios/App/Podfile.lock
index c8fb8cb..2fef6fb 100644
--- a/ios/App/Podfile.lock
+++ b/ios/App/Podfile.lock
@@ -35,6 +35,8 @@ PODS:
- Capacitor
- CapacitorStatusBar (7.0.2):
- Capacitor
+ - CapgoCapacitorNfc (7.2.0):
+ - Capacitor
- CapgoCapacitorSocialLogin (7.11.2):
- Alamofire (~> 5.10.2)
- Capacitor
@@ -145,6 +147,7 @@ DEPENDENCIES:
- "CapacitorPreferences (from `../../node_modules/@capacitor/preferences`)"
- "CapacitorPushNotifications (from `../../node_modules/@capacitor/push-notifications`)"
- "CapacitorStatusBar (from `../../node_modules/@capacitor/status-bar`)"
+ - "CapgoCapacitorNfc (from `../../node_modules/@capgo/capacitor-nfc`)"
- "CapgoCapacitorSocialLogin (from `../../node_modules/@capgo/capacitor-social-login`)"
- FirebaseMessaging
- "RevenuecatPurchasesCapacitor (from `../../node_modules/@revenuecat/purchases-capacitor`)"
@@ -202,6 +205,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/@capacitor/push-notifications"
CapacitorStatusBar:
:path: "../../node_modules/@capacitor/status-bar"
+ CapgoCapacitorNfc:
+ :path: "../../node_modules/@capgo/capacitor-nfc"
CapgoCapacitorSocialLogin:
:path: "../../node_modules/@capgo/capacitor-social-login"
RevenuecatPurchasesCapacitor:
@@ -225,6 +230,7 @@ SPEC CHECKSUMS:
CapacitorPreferences: 65107ed7437d96ee72583df5763985e3c0ff2bc2
CapacitorPushNotifications: 7c0659b349b149ee3936a682da31a9d269de9582
CapacitorStatusBar: e04d05e121d5a5979c29eb4249186a4e4a84cacb
+ CapgoCapacitorNfc: 3306447e38359522e6c1ea233aa6e84a54632534
CapgoCapacitorSocialLogin: a320106e1d032da88576cf8b31a336e971f5acad
FBAEMKit: e34530df538b8eb8aeb53c35867715ba6c63ef0c
FBSDKCoreKit: d3f479a69127acebb1c6aad91c1a33907bcf6c2f
@@ -250,6 +256,6 @@ SPEC CHECKSUMS:
SQLCipher: eb79c64049cb002b4e9fcb30edb7979bf4706dfc
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351
-PODFILE CHECKSUM: bb6dcef70c8edc058fe3ba311f08a223fd7dbc66
+PODFILE CHECKSUM: 17f02615fe2b1c9437b2cd884eec64bc1229846c
COCOAPODS: 1.16.2
diff --git a/package-lock.json b/package-lock.json
index cd71ed0..c02d640 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "donetick",
- "version": "1.2.1",
+ "version": "1.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "donetick",
- "version": "1.2.1",
+ "version": "1.2.2",
"dependencies": {
"@capacitor-community/sqlite": "^7.0.0",
"@capacitor/android": "^7.0.0",
@@ -20,6 +20,7 @@
"@capacitor/preferences": "^7.0.0",
"@capacitor/push-notifications": "^7.0.0",
"@capacitor/status-bar": "^7.0.0",
+ "@capgo/capacitor-nfc": "^7.2.0",
"@capgo/capacitor-social-login": "^7.5.3",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
@@ -45,7 +46,6 @@
"esm": "^3.2.25",
"event-source-polyfill": "^1.0.31",
"fuse.js": "^7.0.0",
- "i": "^0.3.7",
"i18next": "^25.8.14",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.2",
@@ -1947,6 +1947,15 @@
"@capacitor/core": ">=7.0.0"
}
},
+ "node_modules/@capgo/capacitor-nfc": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@capgo/capacitor-nfc/-/capacitor-nfc-7.2.0.tgz",
+ "integrity": "sha512-lYMlogAlUG3j1HmieOdFan4SNOnTbTvZ7BMYQmyhnfU9810aip9oPh6CYVXO/2ecZXW03ATTmSKtr4Ci8DpW2Q==",
+ "license": "MPL-2.0",
+ "peerDependencies": {
+ "@capacitor/core": ">=7.0.0"
+ }
+ },
"node_modules/@capgo/capacitor-social-login": {
"version": "7.11.2",
"license": "MIT",
@@ -8748,14 +8757,6 @@
"node": ">=4"
}
},
- "node_modules/i": {
- "version": "0.3.7",
- "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz",
- "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==",
- "engines": {
- "node": ">=0.4"
- }
- },
"node_modules/i18next": {
"version": "25.8.14",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.14.tgz",
diff --git a/package.json b/package.json
index 63a5185..6aed4fc 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "donetick",
"private": true,
- "version": "1.2.2",
+ "version": "1.2.3",
"type": "module",
"engines": {
"node": ">=20.0.0",
@@ -33,7 +33,8 @@
"cap": "npm run build && npx cap sync",
"bump": "node bump-version.js",
"bump:minor": "node bump-version.js minor",
- "bump:major": "node bump-version.js major"
+ "bump:major": "node bump-version.js major",
+ "bump:patch": "node bump-version.js patch"
},
"dependencies": {
"@capacitor-community/sqlite": "^7.0.0",
@@ -48,6 +49,7 @@
"@capacitor/preferences": "^7.0.0",
"@capacitor/push-notifications": "^7.0.0",
"@capacitor/status-bar": "^7.0.0",
+ "@capgo/capacitor-nfc": "^7.2.0",
"@capgo/capacitor-social-login": "^7.5.3",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
diff --git a/src/CapacitorListener.js b/src/CapacitorListener.js
index 32bd343..c8fdf21 100644
--- a/src/CapacitorListener.js
+++ b/src/CapacitorListener.js
@@ -8,6 +8,35 @@ import { PushNotifications } from '@capacitor/push-notifications'
import { focusManager } from '@tanstack/react-query'
import { RegisterDeviceToken } from './utils/Fetcher'
+// NFC chore deep link: donetick://chores/123?auto_complete=true
+const handleNFCChoreDeepLink = url => {
+ try {
+ const urlObj = new URL(url)
+ // donetick://chores/123 → host='chores', pathname='/123'
+ const choreId = urlObj.pathname.slice(1)
+ const autoComplete = urlObj.searchParams.get('auto_complete')
+ const path = `/chores/${choreId}${autoComplete ? '?auto_complete=' + autoComplete : ''}`
+
+ // getLaunchUrl() persists across every WebView reload caused by window.location.href.
+ // If we're already on the target page, skip to avoid an infinite reload loop.
+ if (window.location.pathname + window.location.search === path) return
+
+ console.log('[NFC] navigating to', path)
+ window.location.href = path
+ } catch (error) {
+ console.error('[NFC] Error handling chore deep link:', error)
+ }
+}
+
+const handleUrlOpen = url => {
+ console.log('[NFC] handleUrlOpen:', url)
+ if (url.startsWith('donetick://chores/')) {
+ handleNFCChoreDeepLink(url)
+ } else if (url.startsWith('donetick://auth/')) {
+ handleOAuthDeepLink(url)
+ }
+}
+
// OAuth callback handler for deep links
const handleOAuthDeepLink = async url => {
console.log('OAuth deep link received:', url)
@@ -215,16 +244,20 @@ const registerCapacitorListeners = () => {
return
}
localNotificationListenerRegistration()
-
- // Register deep link handler for OAuth and other deep links
- mobileApp.addListener('appUrlOpen', event => {
- console.log('App URL opened:', event.url)
-
- // Handle OAuth callback
- if (event.url.startsWith('donetick://auth/')) {
- handleOAuthDeepLink(event.url)
+
+ // Cold-start: app was launched by tapping an NFC tag (or other deep link)
+ mobileApp.getLaunchUrl().then(result => {
+ if (result?.url) {
+ console.log('[NFC] getLaunchUrl:', result.url)
+ handleUrlOpen(result.url)
}
})
+
+ // Foreground / singleTask resume: app was already running when the tag was tapped
+ mobileApp.addListener('appUrlOpen', event => {
+ console.log('[NFC] appUrlOpen:', event.url)
+ handleUrlOpen(event.url)
+ })
mobileApp.addListener('appStateChange', ({ isActive }) => {
focusManager.setFocused(isActive)
@@ -233,6 +266,9 @@ const registerCapacitorListeners = () => {
mobileApp.addListener('backButton', ({ canGoBack }) => {
if (canGoBack) {
window.history.back()
+ } else if (window.location.pathname !== '/') {
+ // No history (e.g. app launched directly to a chore via NFC) — go home
+ window.location.href = '/'
} else {
mobileApp.exitApp()
}
diff --git a/src/service/NFCWriter.jsx b/src/service/NFCWriter.jsx
index 91ac978..cc6f37a 100644
--- a/src/service/NFCWriter.jsx
+++ b/src/service/NFCWriter.jsx
@@ -1,11 +1,136 @@
+import { CapacitorNfc } from '@capgo/capacitor-nfc'
+
+// Encodes a URL into an NDEF URI record (TNF=0x01, type='U')
+const buildUriRecord = url => {
+ const encoder = new TextEncoder()
+ let prefixByte = 0x00
+ let uriStr = url
+ if (url.startsWith('https://')) {
+ prefixByte = 0x04
+ uriStr = url.slice(8)
+ } else if (url.startsWith('http://')) {
+ prefixByte = 0x03
+ uriStr = url.slice(7)
+ }
+ return {
+ tnf: 0x01,
+ type: [0x55],
+ id: [],
+ payload: [prefixByte, ...Array.from(encoder.encode(uriStr))],
+ }
+}
+
+// Decodes a URL from an NDEF URI record payload. Returns null if not a URI record.
+export const decodeNdefUrl = record => {
+ if (!record || record.tnf !== 0x01) return null
+ if (record.type.length !== 1 || record.type[0] !== 0x55) return null
+ const payload = record.payload
+ if (!payload || payload.length === 0) return null
+ const prefixes = [
+ '',
+ 'http://www.',
+ 'https://www.',
+ 'http://',
+ 'https://',
+ 'tel:',
+ 'mailto:',
+ ]
+ const prefix = prefixes[payload[0]] ?? ''
+ const uri = new TextDecoder().decode(new Uint8Array(payload.slice(1)))
+ return prefix + uri
+}
+
+// Starts a native NFC write session. Calls onWaiting once scanning is active,
+// then onSuccess or onError when the write completes. Returns a cancel function.
+export const startNativeNFCWrite = async (url, { onWaiting, onSuccess, onError }) => {
+ let listener = null
+ let done = false
+
+ const cleanup = async () => {
+ if (listener) {
+ await listener.remove()
+ listener = null
+ }
+ await CapacitorNfc.stopScanning().catch(() => {})
+ }
+
+ try {
+ listener = await CapacitorNfc.addListener('nfcEvent', async () => {
+ if (done) return
+ done = true
+ try {
+ await CapacitorNfc.write({ records: [buildUriRecord(url)] })
+ await cleanup()
+ onSuccess()
+ } catch (err) {
+ await cleanup()
+ onError(err.message || 'Failed to write to NFC tag')
+ }
+ })
+
+ await CapacitorNfc.startScanning({
+ alertMessage: 'Hold your device near the NFC tag to write',
+ invalidateAfterFirstRead: true,
+ // Without FLAG_READER_SKIP_NDEF_CHECK (0x80), Android enumerates
+ // Ndef/NdefFormatable tech so the plugin can format blank tags on write.
+ androidReaderModeFlags: 0x0f, // NFC_A | NFC_B | NFC_F | NFC_V
+ })
+ onWaiting()
+ return cleanup
+ } catch (err) {
+ await cleanup()
+ onError(err.message || 'Failed to start NFC session')
+ return async () => {}
+ }
+}
+
+// Starts a native NFC scan session for reading. Calls onTag(url) when a URL
+// NDEF record is found, or onError on failure. Returns a cancel function.
+export const startNativeScan = async ({ onTag, onError }) => {
+ let listener = null
+ let done = false
+
+ const cleanup = async () => {
+ if (listener) {
+ await listener.remove()
+ listener = null
+ }
+ await CapacitorNfc.stopScanning().catch(() => {})
+ }
+
+ try {
+ listener = await CapacitorNfc.addListener('nfcEvent', async event => {
+ if (done) return
+ const records = event.tag?.ndefMessage ?? []
+ for (const record of records) {
+ const url = decodeNdefUrl(record)
+ if (url) {
+ done = true
+ await cleanup()
+ onTag(url)
+ return
+ }
+ }
+ })
+
+ await CapacitorNfc.startScanning({
+ alertMessage: 'Hold your device near the NFC tag',
+ invalidateAfterFirstRead: true,
+ })
+ return cleanup
+ } catch (err) {
+ await cleanup()
+ onError(err.message || 'Failed to start NFC session')
+ return async () => {}
+ }
+}
+
+// Legacy default export for web/PWA (NDEFReader API)
const writeToNFC = async url => {
if ('NDEFReader' in window) {
try {
const ndef = new window.NDEFReader()
- await ndef.write({
- records: [{ recordType: 'url', data: url }],
- })
- alert('URL written to NFC tag successfully!')
+ await ndef.write({ records: [{ recordType: 'url', data: url }] })
} catch (error) {
console.error('Error writing to NFC tag:', error)
alert('Error writing to NFC tag. Please try again.')
diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx
index 9548bc8..1123486 100644
--- a/src/views/ChoreEdit/ChoreView.jsx
+++ b/src/views/ChoreEdit/ChoreView.jsx
@@ -120,8 +120,8 @@ const ChoreView = () => {
document.title = 'Donetick: ' + choreData.res.name
setPerformers(circleMembersData.res)
- const auto_complete = searchParams.get('auto_complete')
- if (auto_complete === 'true') {
+ if (searchParams.get('auto_complete') === 'true') {
+ navigate({ search: '' }, { replace: true })
handleTaskCompletion()
}
}, [choreData, circleMembersData])
diff --git a/src/views/Chores/components/ChoreModals.jsx b/src/views/Chores/components/ChoreModals.jsx
index 2a2a6e8..9191088 100644
--- a/src/views/Chores/components/ChoreModals.jsx
+++ b/src/views/Chores/components/ChoreModals.jsx
@@ -1,9 +1,15 @@
+import { Capacitor } from '@capacitor/core'
import DateModal from '../../Modals/Inputs/DateModal'
import NudgeModal from '../../Modals/Inputs/NudgeModal'
import SelectModal from '../../Modals/Inputs/SelectModal'
import TextModal from '../../Modals/Inputs/TextModal'
import WriteNFCModal from '../../Modals/Inputs/WriteNFCModal'
+const getNFCUrl = choreId =>
+ Capacitor.getPlatform() === 'android'
+ ? `donetick://chores/${choreId}`
+ : `${window.location.origin}/chores/${choreId}`
+
const ChoreModals = ({
activeModal,
modalChore,
@@ -65,12 +71,13 @@ const ChoreModals = ({
)}
+
{activeModal === 'nudge' && modalChore && (
{
- if ('NDEFReader' in window) {
- // Assuming permission request is implicit in 'write' or 'scan' methods
- setNfcStatus('idle')
- } else {
- alert('NFC is not supported by this browser.')
- }
+ const getURL = () => {
+ let url = config.url
+ if (isAutoCompleteWhenScan) url += '?auto_complete=true'
+ return url
}
- const writeToNFC = async url => {
- if ('NDEFReader' in window) {
- try {
- const ndef = new window.NDEFReader()
- await ndef.write({
- records: [{ recordType: 'url', data: url }],
- })
- setNfcStatus('success')
- } catch (error) {
- console.error('Error writing to NFC tag:', error)
- setNfcStatus('error')
- setErrorMessage('Error writing to NFC tag. Please try again.')
- }
- } else {
- setNfcStatus('error')
- setErrorMessage(
- 'NFC is not supported by this browser. You can still copy the URL and write it to an NFC tag using a compatible device.',
- )
+ const handleClose = async () => {
+ if (cancelScanRef.current) {
+ await cancelScanRef.current()
+ cancelScanRef.current = null
}
- }
-
- const handleClose = () => {
config.onClose()
setNfcStatus('idle')
setErrorMessage('')
}
- const getURL = () => {
- let url = config.url
- if (isAutoCompleteWhenScan) {
- url = url + '?auto_complete=true'
+
+ const handleCancel = async () => {
+ if (cancelScanRef.current) {
+ await cancelScanRef.current()
+ cancelScanRef.current = null
+ }
+ setNfcStatus('idle')
+ }
+
+ const writeToNFC = async () => {
+ const url = getURL()
+
+ if (isNative) {
+ setNfcStatus('writing')
+ const cancel = await startNativeNFCWrite(url, {
+ onWaiting: () => setNfcStatus('waiting_for_tag'),
+ onSuccess: () => {
+ cancelScanRef.current = null
+ setNfcStatus('success')
+ },
+ onError: msg => {
+ cancelScanRef.current = null
+ setNfcStatus('error')
+ setErrorMessage(msg)
+ },
+ })
+ cancelScanRef.current = cancel
+ } else {
+ if ('NDEFReader' in window) {
+ try {
+ setNfcStatus('writing')
+ const ndef = new window.NDEFReader()
+ await ndef.write({ records: [{ recordType: 'url', data: url }] })
+ setNfcStatus('success')
+ } catch (error) {
+ console.error('Error writing to NFC tag:', error)
+ setNfcStatus('error')
+ setErrorMessage('Error writing to NFC tag. Please try again.')
+ }
+ } else {
+ setNfcStatus('error')
+ setErrorMessage(
+ 'NFC is not supported by this browser. You can still copy the URL and write it to an NFC tag using a compatible device.',
+ )
+ }
+ }
+ }
+
+ const renderBody = () => {
+ if (nfcStatus === 'success') {
+ return (
+
+ URL written to NFC tag successfully!
+
+ )
}
- return url
+ if (nfcStatus === 'waiting_for_tag') {
+ return (
+ <>
+
+
+
+ Hold your device near the NFC tag
+
+
+
+ >
+ )
+ }
+
+ return (
+ <>
+
+ {nfcStatus === 'error'
+ ? errorMessage
+ : 'Press the button below to write to NFC.'}
+
+ {
+ navigator.clipboard.writeText(getURL())
+ alert('URL copied to clipboard!')
+ }}
+ />
+ }
+ />
+
+ setIsAutoCompleteWhenScan(e.target.checked)}
+ label='Auto-complete when scanned'
+ />
+
+
+
+
+ >
+ )
}
+
return (
{nfcStatus === 'success' ? 'Success!' : 'Write to NFC'}
-
- {nfcStatus === 'success' ? (
-
- URL written to NFC tag successfully!
-
- ) : (
- <>
-
- {nfcStatus === 'error'
- ? errorMessage
- : 'Press the button below to write to NFC.'}
-
- {
- navigator.clipboard.writeText(getURL())
- alert('URL copied to clipboard!')
- }}
- />
- }
- />
-
- setIsAutoCompleteWhenScan(e.target.checked)}
- label='Auto-complete when scanned'
- />
-
-
-
-
-
- >
- )}
+ {renderBody()}
)
}