-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathbasic-usage.js
More file actions
116 lines (102 loc) · 3.9 KB
/
basic-usage.js
File metadata and controls
116 lines (102 loc) · 3.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* Basic usage example — shows how to use the toolkit in an Airtable Interface Extension.
*
* This is a simplified version of a campaign detail view. It demonstrates:
* - Safe field access with getField/getFieldString
* - Dynamic select field colors from Airtable schema
* - Inline editing with isWritableTextField gating
* - Linked record pills
* - Attachment previews
*/
import React, { useMemo, useCallback, useState } from 'react';
import { initializeBlock, useBase, useRecords, useCustomProperties, expandRecord } from '@airtable/blocks/interface/ui';
import {
getField,
getFieldString,
getSelectChoices,
isWritableTextField,
EditableSection,
LinkedRecordPills,
LinkedSection,
Badge,
AttachmentPreview,
} from '../frontend';
// Field IDs — replace with your own
const FIELDS = {
NAME: 'fldXXXXXXXXXXXXXXX',
STATUS: 'fldXXXXXXXXXXXXXXX',
DESCRIPTION: 'fldXXXXXXXXXXXXXXX',
BRANDS: 'fldXXXXXXXXXXXXXXX',
IMAGE: 'fldXXXXXXXXXXXXXXX',
};
function getCustomProperties(base) {
return [
{ key: 'campaignsTable', label: 'Campaigns', type: 'table', defaultValue: base.tables[0] },
{ key: 'brandsTable', label: 'Brands', type: 'table', defaultValue: null },
];
}
function App() {
const { customPropertyValueByKey } = useCustomProperties(getCustomProperties);
const campaignsTable = customPropertyValueByKey.campaignsTable;
const brandsTable = customPropertyValueByKey.brandsTable;
const campaigns = useRecords(campaignsTable);
const brands = useRecords(brandsTable);
const [saving, setSaving] = useState(false);
// Read status options dynamically from Airtable field schema
const statusChoices = useMemo(
() => getSelectChoices(campaignsTable, FIELDS.STATUS),
[campaignsTable]
);
// Generic field updater with permission checks
const updateField = useCallback(async (record, fieldId, value) => {
if (!campaignsTable) return;
const check = campaignsTable.checkPermissionsForUpdateRecord(record, { [fieldId]: undefined });
if (!check.hasPermission) {
alert(check.reasonDisplayString);
return;
}
setSaving(true);
try {
await campaignsTable.updateRecordAsync(record, { [fieldId]: value });
} catch (err) {
alert('Failed to save: ' + err.message);
} finally {
setSaving(false);
}
}, [campaignsTable]);
if (!campaigns) return <div>Loading...</div>;
const record = campaigns[0];
if (!record) return <div>No records found.</div>;
const name = getFieldString(record, FIELDS.NAME);
const status = getFieldString(record, FIELDS.STATUS);
const description = getFieldString(record, FIELDS.DESCRIPTION);
const brandLinks = getField(record, FIELDS.BRANDS);
const image = getField(record, FIELDS.IMAGE);
const statusStyle = statusChoices.find(c => c.name === status)?.styles;
return (
<div style={{ padding: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
<h1>{name}</h1>
<Badge text={status} colors={statusStyle} />
</div>
<AttachmentPreview attachments={image} className="w-full max-h-64 mb-4" />
<LinkedSection
label="Brands"
value={brandLinks}
records={brands}
onExpand={expandRecord}
/>
<EditableSection
label="Description"
value={description}
onSave={
isWritableTextField(campaignsTable, FIELDS.DESCRIPTION)
? (val => updateField(record, FIELDS.DESCRIPTION, val || null))
: null
}
disabled={saving}
/>
</div>
);
}
initializeBlock({ interface: () => <App /> });