@mongoosejs/studio 0.0.128 → 0.0.130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -79,6 +79,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
79
79
  }
80
80
  };
81
81
  exports.Model = {
82
+ addField(params) {
83
+ return client.post('', { action: 'Model.addField', ...params }).then(res => res.data);
84
+ },
82
85
  createChart(params) {
83
86
  return client.post('', { action: 'Model.createChart', ...params }).then(res => res.data);
84
87
  },
@@ -192,6 +195,9 @@ if (window.MONGOOSE_STUDIO_CONFIG.isLambda) {
192
195
  }
193
196
  };
194
197
  exports.Model = {
198
+ addField(params) {
199
+ return client.post('/Model/addField', params).then(res => res.data);
200
+ },
195
201
  createChart: function(params) {
196
202
  return client.post('/Model/createChart', params).then(res => res.data);
197
203
  },
@@ -1527,7 +1533,54 @@ appendCSS(__webpack_require__(/*! ./document-details.css */ "./frontend/src/docu
1527
1533
 
1528
1534
  module.exports = app => app.component('document-details', {
1529
1535
  template,
1530
- props: ['document', 'schemaPaths', 'editting', 'changes', 'invalid'],
1536
+ props: ['document', 'schemaPaths', 'virtualPaths', 'editting', 'changes', 'invalid', 'viewMode'],
1537
+ data() {
1538
+ return {
1539
+ searchQuery: '',
1540
+ selectedType: '',
1541
+ collapsedVirtuals: new Set(),
1542
+ showAddFieldModal: false,
1543
+ fieldData: {
1544
+ name: '',
1545
+ type: '',
1546
+ value: ''
1547
+ },
1548
+ fieldErrors: {},
1549
+ isSubmittingField: false,
1550
+ fieldValueEditor: null
1551
+ };
1552
+ },
1553
+ mounted() {
1554
+ // Focus on search input when component loads
1555
+ this.$nextTick(() => {
1556
+ if (this.$refs.searchInput) {
1557
+ this.$refs.searchInput.focus();
1558
+ }
1559
+
1560
+ if (this.showAddFieldModal) {
1561
+ this.initializeFieldValueEditor();
1562
+ }
1563
+ });
1564
+ },
1565
+ beforeDestroy() {
1566
+ this.destroyFieldValueEditor();
1567
+ },
1568
+ watch: {
1569
+ 'fieldData.type'(newType, oldType) {
1570
+ // When field type changes, we need to handle the transition
1571
+ if (newType !== oldType) {
1572
+ // Destroy existing CodeMirror if it exists
1573
+ this.destroyFieldValueEditor();
1574
+
1575
+ // If switching to a type that needs CodeMirror, initialize it
1576
+ if (this.shouldUseCodeMirror) {
1577
+ this.$nextTick(() => {
1578
+ this.initializeFieldValueEditor();
1579
+ });
1580
+ }
1581
+ }
1582
+ }
1583
+ },
1531
1584
  computed: {
1532
1585
  virtuals() {
1533
1586
  if (this.schemaPaths == null) {
@@ -1541,15 +1594,294 @@ module.exports = app => app.component('document-details', {
1541
1594
  const result = [];
1542
1595
  for (let i = 0; i < docKeys.length; i++) {
1543
1596
  if (!exists.includes(docKeys[i])) {
1544
- result.push({ name: docKeys[i], value: this.document[docKeys[i]] });
1597
+ const isVirtual = this.virtualPaths && this.virtualPaths.includes(docKeys[i]);
1598
+ result.push({
1599
+ name: docKeys[i],
1600
+ value: this.document[docKeys[i]],
1601
+ isVirtual: isVirtual,
1602
+ isUserAdded: !isVirtual
1603
+ });
1545
1604
  }
1546
1605
  }
1547
1606
 
1548
1607
  return result;
1608
+ },
1609
+ availableTypes() {
1610
+ if (!this.schemaPaths) return [];
1611
+ const types = new Set();
1612
+ this.schemaPaths.forEach(path => {
1613
+ if (path.instance) {
1614
+ types.add(path.instance);
1615
+ }
1616
+ });
1617
+
1618
+ // Add virtual field types to the available types
1619
+ this.virtuals.forEach(virtual => {
1620
+ const virtualType = this.getVirtualFieldType(virtual);
1621
+ if (virtualType && virtualType !== 'unknown') {
1622
+ types.add(virtualType);
1623
+ }
1624
+ });
1625
+
1626
+ return Array.from(types).sort();
1627
+ },
1628
+ allFieldTypes() {
1629
+ const schemaTypes = this.availableTypes;
1630
+ const commonTypes = ['String', 'Number', 'Boolean', 'Date', 'Array', 'Object'];
1631
+
1632
+ // Combine schema types with common types, avoiding duplicates
1633
+ const allTypes = new Set([...schemaTypes, ...commonTypes]);
1634
+ return Array.from(allTypes).sort();
1635
+ },
1636
+ shouldUseCodeMirror() {
1637
+ return ['Array', 'Object', 'Embedded'].includes(this.fieldData.type);
1638
+ },
1639
+ shouldUseDatePicker() {
1640
+ return this.fieldData.type === 'Date';
1641
+ },
1642
+ filteredSchemaPaths() {
1643
+ let paths = this.schemaPaths || [];
1644
+
1645
+ // Filter by search query
1646
+ if (this.searchQuery.trim()) {
1647
+ const query = this.searchQuery.toLowerCase();
1648
+ paths = paths.filter(path =>
1649
+ path.path.toLowerCase().includes(query)
1650
+ );
1651
+ }
1652
+
1653
+ // Filter by data type
1654
+ if (this.selectedType) {
1655
+ paths = paths.filter(path =>
1656
+ path.instance === this.selectedType
1657
+ );
1658
+ }
1659
+
1660
+ return paths;
1661
+ },
1662
+ filteredVirtuals() {
1663
+ let virtuals = this.virtuals;
1664
+
1665
+ // Filter by search query
1666
+ if (this.searchQuery.trim()) {
1667
+ const query = this.searchQuery.toLowerCase();
1668
+ virtuals = virtuals.filter(virtual =>
1669
+ virtual.name.toLowerCase().includes(query)
1670
+ );
1671
+ }
1672
+
1673
+ // Filter by data type
1674
+ if (this.selectedType) {
1675
+ virtuals = virtuals.filter(virtual => {
1676
+ const virtualType = this.getVirtualFieldType(virtual);
1677
+ return virtualType === this.selectedType;
1678
+ });
1679
+ }
1680
+
1681
+ return virtuals;
1682
+ },
1683
+ formattedJson() {
1684
+ if (!this.document) {
1685
+ return '{}';
1686
+ }
1687
+ return JSON.stringify(this.document, null, 2);
1688
+ }
1689
+ },
1690
+ methods: {
1691
+ toggleVirtualField(fieldName) {
1692
+ if (this.collapsedVirtuals.has(fieldName)) {
1693
+ this.collapsedVirtuals.delete(fieldName);
1694
+ } else {
1695
+ this.collapsedVirtuals.add(fieldName);
1696
+ }
1697
+ },
1698
+ isVirtualFieldCollapsed(fieldName) {
1699
+ return this.collapsedVirtuals.has(fieldName);
1700
+ },
1701
+ openAddFieldModal() {
1702
+ this.showAddFieldModal = true;
1703
+ this.$nextTick(() => {
1704
+ if (this.shouldUseCodeMirror) {
1705
+ this.initializeFieldValueEditor();
1706
+ }
1707
+ });
1708
+ },
1709
+ closeAddFieldModal() {
1710
+ this.showAddFieldModal = false;
1711
+ this.destroyFieldValueEditor();
1712
+ this.resetFieldForm();
1713
+ },
1714
+ async addNewField(fieldData) {
1715
+ // Emit event to parent component to handle the field addition
1716
+ this.$emit('add-field', fieldData);
1717
+ this.closeAddFieldModal();
1718
+ },
1719
+ validateFieldForm() {
1720
+ this.fieldErrors = {};
1721
+
1722
+ // Validate field name
1723
+ const trimmedName = this.fieldData.name.trim();
1724
+ if (!trimmedName) {
1725
+ this.fieldErrors.name = 'Field name is required';
1726
+ } else {
1727
+ const transformedName = this.getTransformedFieldName();
1728
+ if (!transformedName) {
1729
+ this.fieldErrors.name = 'Field name contains only invalid characters';
1730
+ } else if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(transformedName)) {
1731
+ this.fieldErrors.name = 'Field name must start with a letter, underscore, or $ and contain only letters, numbers, underscores, and $';
1732
+ }
1733
+ }
1734
+
1735
+ // Validate field type
1736
+ if (!this.fieldData.type) {
1737
+ this.fieldErrors.type = 'Field type is required';
1738
+ }
1739
+
1740
+ // Validate field value if provided
1741
+ if (this.fieldData.value && this.fieldData.value.trim()) {
1742
+ if (['Object', 'Array'].includes(this.fieldData.type)) {
1743
+ try {
1744
+ JSON.parse(this.fieldData.value);
1745
+ } catch (e) {
1746
+ this.fieldErrors.value = 'Invalid JSON format for object/array type';
1747
+ }
1748
+ } else if (this.fieldData.type === 'Number') {
1749
+ if (isNaN(Number(this.fieldData.value))) {
1750
+ this.fieldErrors.value = 'Invalid number format';
1751
+ }
1752
+ } else if (this.fieldData.type === 'Boolean') {
1753
+ const lowerValue = this.fieldData.value.toLowerCase();
1754
+ if (!['true', 'false', '1', '0', 'yes', 'no'].includes(lowerValue)) {
1755
+ this.fieldErrors.value = 'Invalid boolean value (use true/false, 1/0, yes/no)';
1756
+ }
1757
+ } else if (this.fieldData.type === 'Date') {
1758
+ // Date picker provides YYYY-MM-DD format, validate it
1759
+ const dateValue = new Date(this.fieldData.value);
1760
+ if (isNaN(dateValue.getTime())) {
1761
+ this.fieldErrors.value = 'Invalid date format';
1762
+ }
1763
+ }
1764
+ }
1765
+
1766
+ return Object.keys(this.fieldErrors).length === 0;
1767
+ },
1768
+ parseFieldValue(value, type) {
1769
+ if (!value || !value.trim()) {
1770
+ return null;
1771
+ }
1772
+
1773
+ switch (type) {
1774
+ case 'Number':
1775
+ return Number(value);
1776
+ case 'Boolean':
1777
+ const lowerValue = value.toLowerCase();
1778
+ return ['true', '1', 'yes'].includes(lowerValue);
1779
+ case 'Date':
1780
+ // For date picker, value is already in YYYY-MM-DD format
1781
+ return new Date(value);
1782
+ case 'Object':
1783
+ case 'Array':
1784
+ return JSON.parse(value);
1785
+ default:
1786
+ return value;
1787
+ }
1788
+ },
1789
+ async handleAddFieldSubmit() {
1790
+ if (!this.validateFieldForm()) {
1791
+ return;
1792
+ }
1793
+
1794
+ this.isSubmittingField = true;
1795
+
1796
+ try {
1797
+ const fieldData = {
1798
+ name: this.getTransformedFieldName(),
1799
+ type: this.fieldData.type,
1800
+ value: this.parseFieldValue(this.fieldData.value, this.fieldData.type)
1801
+ };
1802
+
1803
+ this.$emit('add-field', fieldData);
1804
+ this.closeAddFieldModal();
1805
+ } catch (error) {
1806
+ console.error('Error adding field:', error);
1807
+ this.fieldErrors.value = 'Error processing field value';
1808
+ } finally {
1809
+ this.isSubmittingField = false;
1810
+ }
1811
+ },
1812
+ resetFieldForm() {
1813
+ this.fieldData = {
1814
+ name: '',
1815
+ type: '',
1816
+ value: ''
1817
+ };
1818
+ this.fieldErrors = {};
1819
+ this.isSubmittingField = false;
1820
+ // Reset CodeMirror editor if it exists
1821
+ if (this.fieldValueEditor) {
1822
+ this.fieldValueEditor.setValue('');
1823
+ }
1824
+ },
1825
+ initializeFieldValueEditor() {
1826
+ if (this.$refs.fieldValueEditor && !this.fieldValueEditor && this.shouldUseCodeMirror) {
1827
+ this.$refs.fieldValueEditor.value = this.fieldData.value || '';
1828
+ this.fieldValueEditor = CodeMirror.fromTextArea(this.$refs.fieldValueEditor, {
1829
+ mode: 'javascript',
1830
+ lineNumbers: true,
1831
+ height: 'auto'
1832
+ });
1833
+ this.fieldValueEditor.on('change', () => {
1834
+ this.fieldData.value = this.fieldValueEditor.getValue();
1835
+ });
1836
+ }
1837
+ },
1838
+ destroyFieldValueEditor() {
1839
+ if (this.fieldValueEditor) {
1840
+ this.fieldValueEditor.toTextArea();
1841
+ this.fieldValueEditor = null;
1842
+ }
1843
+ },
1844
+ toSnakeCase(str) {
1845
+ return str
1846
+ .trim()
1847
+ .replace(/\s+/g, '_') // Replace spaces with underscores
1848
+ .replace(/[^a-zA-Z0-9_$]/g, '') // Remove invalid characters
1849
+ .replace(/^[0-9]/, '_$&') // Prefix numbers with underscore
1850
+ .toLowerCase();
1851
+ },
1852
+ getTransformedFieldName() {
1853
+ if (!this.fieldData.name) return '';
1854
+ return this.toSnakeCase(this.fieldData.name.trim());
1855
+ },
1856
+ getVirtualFieldType(virtual) {
1857
+ const value = virtual.value;
1858
+ if (value === null || value === undefined) {
1859
+ return 'null';
1860
+ }
1861
+ if (Array.isArray(value)) {
1862
+ return 'Array';
1863
+ }
1864
+ if (value instanceof Date) {
1865
+ return 'Date';
1866
+ }
1867
+ if (typeof value === 'object') {
1868
+ return 'Object';
1869
+ }
1870
+ if (typeof value === 'number') {
1871
+ return 'Number';
1872
+ }
1873
+ if (typeof value === 'boolean') {
1874
+ return 'Boolean';
1875
+ }
1876
+ if (typeof value === 'string') {
1877
+ return 'String';
1878
+ }
1879
+ return 'unknown';
1549
1880
  }
1550
1881
  }
1551
1882
  });
1552
1883
 
1884
+
1553
1885
  /***/ }),
1554
1886
 
1555
1887
  /***/ "./frontend/src/document-details/document-property/document-property.js":
@@ -1572,10 +1904,41 @@ module.exports = app => app.component('document-property', {
1572
1904
  template,
1573
1905
  data: function() {
1574
1906
  return {
1575
- dateType: 'picker' // picker, iso
1907
+ dateType: 'picker', // picker, iso
1908
+ isCollapsed: false, // Start uncollapsed by default
1909
+ isValueExpanded: false // Track if the value is expanded
1576
1910
  };
1577
1911
  },
1578
1912
  props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid'],
1913
+ computed: {
1914
+ valueAsString() {
1915
+ const value = this.getValueForPath(this.path.path);
1916
+ if (value == null) {
1917
+ return String(value);
1918
+ }
1919
+ if (typeof value === 'object') {
1920
+ return JSON.stringify(value, null, 2);
1921
+ }
1922
+ return String(value);
1923
+ },
1924
+ needsTruncation() {
1925
+ // Truncate if value is longer than 200 characters
1926
+ return this.valueAsString.length > 200;
1927
+ },
1928
+ displayValue() {
1929
+ if (!this.needsTruncation || this.isValueExpanded) {
1930
+ return this.getValueForPath(this.path.path);
1931
+ }
1932
+ // Return truncated value - we'll handle this in the template
1933
+ return this.getValueForPath(this.path.path);
1934
+ },
1935
+ truncatedString() {
1936
+ if (this.needsTruncation && !this.isValueExpanded) {
1937
+ return this.valueAsString.substring(0, 200) + '...';
1938
+ }
1939
+ return this.valueAsString;
1940
+ }
1941
+ },
1579
1942
  methods: {
1580
1943
  getComponentForPath(schemaPath) {
1581
1944
  if (schemaPath.instance === 'Array') {
@@ -1596,6 +1959,9 @@ module.exports = app => app.component('document-property', {
1596
1959
  if (path.instance === 'Embedded') {
1597
1960
  return 'edit-subdocument';
1598
1961
  }
1962
+ if (path.instance === 'Boolean') {
1963
+ return 'edit-boolean';
1964
+ }
1599
1965
  return 'edit-default';
1600
1966
  },
1601
1967
  getValueForPath(path) {
@@ -1611,7 +1977,14 @@ module.exports = app => app.component('document-property', {
1611
1977
  if (!this.document) {
1612
1978
  return;
1613
1979
  }
1614
- return path in this.changes ? this.changes[path] : mpath.get(path, this.document);
1980
+ const documentValue = mpath.get(path, this.document);
1981
+ return documentValue;
1982
+ },
1983
+ toggleCollapse() {
1984
+ this.isCollapsed = !this.isCollapsed;
1985
+ },
1986
+ toggleValueExpansion() {
1987
+ this.isValueExpanded = !this.isValueExpanded;
1615
1988
  }
1616
1989
  }
1617
1990
  });
@@ -1715,13 +2088,16 @@ module.exports = app => app.component('document', {
1715
2088
  invalid: {},
1716
2089
  editting: false,
1717
2090
  virtuals: [],
2091
+ virtualPaths: [],
2092
+ mobileMenuOpen: false,
2093
+ viewMode: 'fields',
1718
2094
  shouldShowConfirmModal: false,
1719
2095
  shouldShowDeleteModal: false,
1720
2096
  shouldShowCloneModal: false
1721
2097
  }),
1722
2098
  async mounted() {
1723
2099
  window.pageState = this;
1724
- const { doc, schemaPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
2100
+ const { doc, schemaPaths, virtualPaths } = await api.Model.getDocument({ model: this.model, documentId: this.documentId });
1725
2101
  window.doc = doc;
1726
2102
  this.document = doc;
1727
2103
  this.schemaPaths = Object.keys(schemaPaths).sort((k1, k2) => {
@@ -1733,6 +2109,7 @@ module.exports = app => app.component('document', {
1733
2109
  }
1734
2110
  return 0;
1735
2111
  }).map(key => schemaPaths[key]);
2112
+ this.virtualPaths = virtualPaths || [];
1736
2113
  this.status = 'loaded';
1737
2114
  },
1738
2115
  computed: {
@@ -1744,6 +2121,9 @@ module.exports = app => app.component('document', {
1744
2121
  return false;
1745
2122
  }
1746
2123
  return !this.roles.includes('readonly');
2124
+ },
2125
+ canEdit() {
2126
+ return this.canManipulate && this.viewMode === 'fields';
1747
2127
  }
1748
2128
  },
1749
2129
  methods: {
@@ -1784,6 +2164,32 @@ module.exports = app => app.component('document', {
1784
2164
  },
1785
2165
  showClonedDocument(doc) {
1786
2166
  this.$router.push({ path: `/model/${this.model}/document/${doc._id}` });
2167
+ },
2168
+ async addField(fieldData) {
2169
+ const { doc } = await api.Model.addField({
2170
+ model: this.model,
2171
+ _id: this.document._id,
2172
+ fieldName: fieldData.name,
2173
+ fieldValue: fieldData.value
2174
+ });
2175
+ this.document = doc;
2176
+
2177
+ // Show success message
2178
+ vanillatoast.create({
2179
+ title: 'Field Added!',
2180
+ text: `Field "${fieldData.name}" has been added to the document`,
2181
+ type: 'success',
2182
+ timeout: 3000,
2183
+ positionClass: 'bottomRight'
2184
+ });
2185
+ },
2186
+ updateViewMode(mode) {
2187
+ this.viewMode = mode;
2188
+ // Exit edit mode when switching to JSON view
2189
+ if (mode === 'json' && this.editting) {
2190
+ this.editting = false;
2191
+ this.changes = {};
2192
+ }
1787
2193
  }
1788
2194
  }
1789
2195
  });
@@ -1853,6 +2259,55 @@ module.exports = app => app.component('edit-array', {
1853
2259
  });
1854
2260
 
1855
2261
 
2262
+ /***/ }),
2263
+
2264
+ /***/ "./frontend/src/edit-boolean/edit-boolean.js":
2265
+ /*!***************************************************!*\
2266
+ !*** ./frontend/src/edit-boolean/edit-boolean.js ***!
2267
+ \***************************************************/
2268
+ /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
2269
+
2270
+ "use strict";
2271
+
2272
+
2273
+ const template = __webpack_require__(/*! ./edit-boolean.html */ "./frontend/src/edit-boolean/edit-boolean.html");
2274
+
2275
+ module.exports = app => app.component('edit-boolean', {
2276
+ template: template,
2277
+ props: ['value'],
2278
+ emits: ['input'],
2279
+ data() {
2280
+ return {
2281
+ selectedValue: null
2282
+ };
2283
+ },
2284
+ mounted() {
2285
+ this.selectedValue = this.value;
2286
+ },
2287
+ watch: {
2288
+ value(newValue) {
2289
+ this.selectedValue = newValue;
2290
+ },
2291
+ selectedValue(newValue) {
2292
+ // Convert null/undefined to strings for proper backend serialization
2293
+ const emitValue = this.convertValueToString(newValue);
2294
+ this.$emit('input', emitValue);
2295
+ }
2296
+ },
2297
+ methods: {
2298
+ selectValue(value) {
2299
+ this.selectedValue = value;
2300
+ },
2301
+ convertValueToString(value) {
2302
+ // Convert null/undefined to strings for proper backend serialization
2303
+ if (value === null) return 'null';
2304
+ if (typeof value === 'undefined') return 'undefined';
2305
+ return value;
2306
+ }
2307
+ }
2308
+ });
2309
+
2310
+
1856
2311
  /***/ }),
1857
2312
 
1858
2313
  /***/ "./frontend/src/edit-date/edit-date.js":
@@ -2710,6 +3165,34 @@ module.exports = app => app.component('models', {
2710
3165
  this.autocompleteTrie.bulkInsert(paths, 10);
2711
3166
  }
2712
3167
  },
3168
+ buildDocumentFetchParams(options = {}) {
3169
+ const params = {
3170
+ model: this.currentModel,
3171
+ limit
3172
+ };
3173
+
3174
+ if (typeof options.skip === 'number') {
3175
+ params.skip = options.skip;
3176
+ }
3177
+
3178
+ const sortKeys = Object.keys(this.sortBy);
3179
+ if (sortKeys.length > 0) {
3180
+ const key = sortKeys[0];
3181
+ if (typeof key === 'string' && key.length > 0) {
3182
+ params.sortKey = key;
3183
+ const direction = this.sortBy[key];
3184
+ if (direction !== undefined && direction !== null) {
3185
+ params.sortDirection = direction;
3186
+ }
3187
+ }
3188
+ }
3189
+
3190
+ if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
3191
+ params.searchText = this.searchText;
3192
+ }
3193
+
3194
+ return params;
3195
+ },
2713
3196
  async initSearchFromUrl() {
2714
3197
  this.status = 'loading';
2715
3198
  this.query = Object.assign({}, this.$route.query); // important that this is here before the if statements
@@ -2753,13 +3236,48 @@ module.exports = app => app.component('models', {
2753
3236
  const before = this.searchText.slice(0, cursorPos);
2754
3237
  const match = before.match(/(?:\{|,)\s*([^:\s]*)$/);
2755
3238
  if (match && match[1]) {
2756
- const term = match[1].replace(/["']/g, '');
3239
+ const token = match[1];
3240
+ const leadingQuoteMatch = token.match(/^["']/);
3241
+ const trailingQuoteMatch = token.length > 1 && /["']$/.test(token)
3242
+ ? token[token.length - 1]
3243
+ : '';
3244
+ const term = token
3245
+ .replace(/^["']/, '')
3246
+ .replace(trailingQuoteMatch ? new RegExp(`[${trailingQuoteMatch}]$`) : '', '')
3247
+ .trim();
2757
3248
  if (!term) {
2758
3249
  this.autocompleteSuggestions = [];
2759
3250
  return;
2760
3251
  }
2761
3252
  if (this.autocompleteTrie) {
2762
- this.autocompleteSuggestions = this.autocompleteTrie.getSuggestions(term, 10);
3253
+ const primarySuggestions = this.autocompleteTrie.getSuggestions(term, 10);
3254
+ const suggestionsSet = new Set(primarySuggestions);
3255
+ if (Array.isArray(this.schemaPaths) && this.schemaPaths.length > 0) {
3256
+ for (const schemaPath of this.schemaPaths) {
3257
+ const path = schemaPath?.path;
3258
+ if (
3259
+ typeof path === 'string' &&
3260
+ path.startsWith(`${term}.`) &&
3261
+ !suggestionsSet.has(path)
3262
+ ) {
3263
+ suggestionsSet.add(path);
3264
+ if (suggestionsSet.size >= 10) {
3265
+ break;
3266
+ }
3267
+ }
3268
+ }
3269
+ }
3270
+ let suggestions = Array.from(suggestionsSet);
3271
+ if (leadingQuoteMatch) {
3272
+ const leadingQuote = leadingQuoteMatch[0];
3273
+ suggestions = suggestions.map(suggestion => `${leadingQuote}${suggestion}`);
3274
+ }
3275
+ if (trailingQuoteMatch) {
3276
+ suggestions = suggestions.map(suggestion =>
3277
+ suggestion.endsWith(trailingQuoteMatch) ? suggestion : `${suggestion}${trailingQuoteMatch}`
3278
+ );
3279
+ }
3280
+ this.autocompleteSuggestions = suggestions;
2763
3281
  this.autocompleteIndex = 0;
2764
3282
  return;
2765
3283
  }
@@ -2796,9 +3314,18 @@ module.exports = app => app.component('models', {
2796
3314
  }
2797
3315
  const token = match[1];
2798
3316
  const start = cursorPos - token.length;
2799
- this.searchText = this.searchText.slice(0, start) + suggestion + after;
3317
+ let replacement = suggestion;
3318
+ const leadingQuote = token.startsWith('"') || token.startsWith('\'') ? token[0] : '';
3319
+ const trailingQuote = token.length > 1 && (token.endsWith('"') || token.endsWith('\'')) ? token[token.length - 1] : '';
3320
+ if (leadingQuote && !replacement.startsWith(leadingQuote)) {
3321
+ replacement = `${leadingQuote}${replacement}`;
3322
+ }
3323
+ if (trailingQuote && !replacement.endsWith(trailingQuote)) {
3324
+ replacement = `${replacement}${trailingQuote}`;
3325
+ }
3326
+ this.searchText = this.searchText.slice(0, start) + replacement + after;
2800
3327
  this.$nextTick(() => {
2801
- const pos = start + suggestion.length;
3328
+ const pos = start + replacement.length;
2802
3329
  input.setSelectionRange(pos, pos);
2803
3330
  });
2804
3331
  this.autocompleteSuggestions = [];
@@ -2847,15 +3374,7 @@ module.exports = app => app.component('models', {
2847
3374
  const container = this.$refs.documentsList;
2848
3375
  if (container.scrollHeight - container.clientHeight - 100 < container.scrollTop) {
2849
3376
  this.status = 'loading';
2850
- const params = {
2851
- model: this.currentModel,
2852
- sort: this.sortBy,
2853
- skip: this.documents.length,
2854
- limit
2855
- };
2856
- if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
2857
- params.searchText = this.searchText;
2858
- }
3377
+ const params = this.buildDocumentFetchParams({ skip: this.documents.length });
2859
3378
  const { docs } = await api.Model.getDocuments(params);
2860
3379
  if (docs.length < limit) {
2861
3380
  this.loadedAllDocs = true;
@@ -2924,14 +3443,7 @@ module.exports = app => app.component('models', {
2924
3443
  let schemaPathsReceived = false;
2925
3444
 
2926
3445
  // Use async generator to stream SSEs
2927
- const params = {
2928
- model: this.currentModel,
2929
- sort: this.sortBy,
2930
- limit
2931
- };
2932
- if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
2933
- params.searchText = this.searchText;
2934
- }
3446
+ const params = this.buildDocumentFetchParams();
2935
3447
  for await (const event of api.Model.getDocumentsStream(params)) {
2936
3448
  if (event.schemaPaths && !schemaPathsReceived) {
2937
3449
  // Sort schemaPaths with _id first
@@ -2975,15 +3487,7 @@ module.exports = app => app.component('models', {
2975
3487
  let numDocsReceived = false;
2976
3488
 
2977
3489
  // Use async generator to stream SSEs
2978
- const params = {
2979
- model: this.currentModel,
2980
- sort: this.sortBy,
2981
- skip: this.documents.length,
2982
- limit
2983
- };
2984
- if (typeof this.searchText === 'string' && this.searchText.trim().length > 0) {
2985
- params.searchText = this.searchText;
2986
- }
3490
+ const params = this.buildDocumentFetchParams({ skip: this.documents.length });
2987
3491
  for await (const event of api.Model.getDocumentsStream(params)) {
2988
3492
  if (event.numDocs !== undefined && !numDocsReceived) {
2989
3493
  this.numDocuments = event.numDocs;
@@ -3354,6 +3858,10 @@ exports.removeFromWorkspace = function removeFromWorkspace(params) {
3354
3858
  return client.post('/removeFromWorkspace', { workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id, ...params }).then(res => res.data);
3355
3859
  };
3356
3860
 
3861
+ exports.updateWorkspaceMember = function updateWorkspaceMember(params) {
3862
+ return client.post('/updateWorkspaceMember', { workspaceId: window.MONGOOSE_STUDIO_CONFIG.workspace._id, ...params }).then(res => res.data);
3863
+ };
3864
+
3357
3865
  exports.hasAPIKey = client.hasAPIKey;
3358
3866
 
3359
3867
 
@@ -3661,6 +4169,7 @@ module.exports = app => app.component('team', {
3661
4169
  invitations: null,
3662
4170
  showNewInvitationModal: false,
3663
4171
  showRemoveModal: null,
4172
+ showEditModal: null,
3664
4173
  status: 'loading'
3665
4174
  }),
3666
4175
  async mounted() {
@@ -3682,15 +4191,60 @@ module.exports = app => app.component('team', {
3682
4191
  getRolesForUser(user) {
3683
4192
  return this.workspace.members.find(member => member.userId === user._id)?.roles ?? [];
3684
4193
  },
4194
+ openEditModal(user) {
4195
+ if (this.getRolesForUser(user).includes('owner')) {
4196
+ return;
4197
+ }
4198
+
4199
+ const roles = this.getRolesForUser(user);
4200
+ const nonOwnerRoles = roles.filter(role => role !== 'owner');
4201
+ const currentRole = nonOwnerRoles[0] ?? null;
4202
+ const editableRole = currentRole ?? (this.workspace?.subscriptionTier ? 'member' : 'dashboards');
4203
+
4204
+ this.showEditModal = {
4205
+ user,
4206
+ role: editableRole,
4207
+ originalRole: currentRole
4208
+ };
4209
+ },
4210
+ closeEditModal() {
4211
+ this.showEditModal = null;
4212
+ },
4213
+ async updateWorkspaceMember() {
4214
+ if (this.showEditModal.role === this.showEditModal.originalRole) {
4215
+ this.closeEditModal();
4216
+ return;
4217
+ }
4218
+
4219
+ const { workspace, users } = await mothership.updateWorkspaceMember({
4220
+ userId: this.showEditModal.user._id,
4221
+ roles: [this.showEditModal.role]
4222
+ });
4223
+
4224
+ this.workspace = workspace;
4225
+ this.users = users;
4226
+ this.closeEditModal();
4227
+ },
3685
4228
  async removeFromWorkspace() {
3686
4229
  const { workspace, users } = await mothership.removeFromWorkspace({ userId: this.showRemoveModal._id });
3687
4230
  this.workspace = workspace;
3688
4231
  this.users = users;
3689
- this.showRemoveModal = false;
4232
+ this.showRemoveModal = null;
3690
4233
  },
3691
4234
  async getWorkspaceCustomerPortalLink() {
3692
4235
  const { url } = await mothership.getWorkspaceCustomerPortalLink();
3693
4236
  window.open(url, '_self');
4237
+ },
4238
+ disableRoleOption(option) {
4239
+ if (this.workspace?.subscriptionTier) {
4240
+ return false;
4241
+ }
4242
+
4243
+ if (this.showEditModal?.originalRole === option) {
4244
+ return false;
4245
+ }
4246
+
4247
+ return option !== 'dashboards';
3694
4248
  }
3695
4249
  }
3696
4250
  });
@@ -3871,6 +4425,9 @@ var map = {
3871
4425
  "./edit-array/edit-array.css": "./frontend/src/edit-array/edit-array.css",
3872
4426
  "./edit-array/edit-array.html": "./frontend/src/edit-array/edit-array.html",
3873
4427
  "./edit-array/edit-array.js": "./frontend/src/edit-array/edit-array.js",
4428
+ "./edit-boolean/edit-boolean": "./frontend/src/edit-boolean/edit-boolean.js",
4429
+ "./edit-boolean/edit-boolean.html": "./frontend/src/edit-boolean/edit-boolean.html",
4430
+ "./edit-boolean/edit-boolean.js": "./frontend/src/edit-boolean/edit-boolean.js",
3874
4431
  "./edit-date/edit-date": "./frontend/src/edit-date/edit-date.js",
3875
4432
  "./edit-date/edit-date.html": "./frontend/src/edit-date/edit-date.html",
3876
4433
  "./edit-date/edit-date.js": "./frontend/src/edit-date/edit-date.js",
@@ -4808,7 +5365,7 @@ module.exports = "<div>\n {{value}}\n</div>";
4808
5365
  /***/ ((module) => {
4809
5366
 
4810
5367
  "use strict";
4811
- module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n}\n\n.document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n}\n\n.document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.document-details .date-position {\n float: right;\n margin-top: -7px;\n}";
5368
+ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n}\n\n.document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n}\n\n.document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n}\n\n.document-details .date-position {\n float: right;\n margin-top: -7px;\n}\n\n/* Add Field Modal Styles */\n.add-field-modal {\n max-width: 500px;\n width: 100%;\n}\n\n.add-field-modal .modal-exit {\n position: absolute;\n top: 15px;\n right: 20px;\n font-size: 24px;\n cursor: pointer;\n color: #6b7280;\n z-index: 10;\n}\n\n.add-field-modal .modal-exit:hover {\n color: #374151;\n}\n\n.add-field-modal form {\n max-height: 70vh;\n overflow-y: auto;\n}\n\n.add-field-modal input[type=\"text\"],\n.add-field-modal input[type=\"email\"],\n.add-field-modal input[type=\"password\"],\n.add-field-modal select,\n.add-field-modal textarea {\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n.add-field-modal input:focus,\n.add-field-modal select:focus,\n.add-field-modal textarea:focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n.add-field-modal .border-red-500 {\n border-color: #ef4444 !important;\n}\n\n.add-field-modal .border-red-500:focus {\n box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;\n}\n\n/* CodeMirror styling in modal */\n.add-field-modal .CodeMirror {\n border: 1px solid #d1d5db;\n border-radius: 0.375rem;\n font-size: 14px;\n height: auto;\n min-height: 100px;\n}\n\n.add-field-modal .CodeMirror:focus-within {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n.add-field-modal .CodeMirror.CodeMirror-focused {\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);\n}\n\n/* JSON View Styles */\n.json-view {\n width: 100%;\n}\n\n.json-view pre {\n margin: 0;\n max-height: 70vh;\n overflow: auto;\n line-height: 1.5;\n}\n\n.json-view pre::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n}\n\n.json-view pre::-webkit-scrollbar-track {\n background: #f1f1f1;\n border-radius: 4px;\n}\n\n.json-view pre::-webkit-scrollbar-thumb {\n background: #888;\n border-radius: 4px;\n}\n\n.json-view pre::-webkit-scrollbar-thumb:hover {\n background: #555;\n}";
4812
5369
 
4813
5370
  /***/ }),
4814
5371
 
@@ -4819,7 +5376,7 @@ module.exports = ".document-details {\n width: 100%;\n}\n\n.document-details .v
4819
5376
  /***/ ((module) => {
4820
5377
 
4821
5378
  "use strict";
4822
- module.exports = "<div class=\"document-details\">\n <div v-for=\"path in schemaPaths\" class=\"value\">\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"></document-property>\n </div>\n <div v-for=\"path in virtuals\" class=\"mb-2\">\n <div class=\"p-1 mb-1 bg-slate-100\">\n {{path.name}}\n <span class=\"path-type\">\n (virtual)\n </span>\n </div>\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n</div>";
5379
+ module.exports = "<div class=\"document-details\">\n <!-- View Toggle and Search/Filter Bar -->\n <div class=\"mb-4 mt-4\">\n\n <!-- Search and Filter Bar (only show in fields view) -->\n <div v-if=\"viewMode === 'fields'\" class=\"flex md:gap-3\">\n <!-- Search Bar -->\n <div class=\"relative flex-1\">\n <input\n ref=\"searchInput\"\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search fields...\"\n class=\"w-full px-4 py-2 pl-10 pr-4 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n />\n <div class=\"absolute inset-y-0 left-0 flex items-center pl-3\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Data Type Filter -->\n <div class=\"relative\">\n <select\n v-model=\"selectedType\"\n class=\"hidden md:block px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none\"\n >\n <option value=\"\">All Types</option>\n <option v-for=\"type in availableTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none\">\n <svg class=\"w-4 h-4 text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n </div>\n </div>\n\n <!-- Add Field Button -->\n <button\n @click=\"openAddFieldModal\"\n class=\"hidden md:block px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 flex items-center gap-2\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\"></path>\n </svg>\n Add Field\n </button>\n </div>\n </div>\n\n <!-- Fields View -->\n <div v-if=\"viewMode === 'fields'\">\n <!-- Schema Paths -->\n <div v-for=\"path in filteredSchemaPaths\" :key=\"path\" class=\"value\">\n <document-property\n :path=\"path\"\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"></document-property>\n </div>\n\n <!-- Virtual Fields -->\n <div v-for=\"path in filteredVirtuals\" class=\"border border-gray-200 rounded-lg mb-2\">\n <!-- Virtual Field Header (Always Visible) -->\n <div\n @click=\"toggleVirtualField(path.name)\"\n class=\"p-3 bg-slate-50 hover:bg-slate-100 cursor-pointer flex items-center justify-between border-b border-gray-200\"\n >\n <div class=\"flex items-center\">\n <svg\n :class=\"isVirtualFieldCollapsed(path.name) ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.name}}</span>\n <span v-if=\"path.isVirtual\" class=\"ml-2 text-sm text-purple-600\">(virtual - {{getVirtualFieldType(path)}})</span>\n <span v-else class=\"ml-2 text-sm text-blue-600\">(user-added - {{getVirtualFieldType(path)}})</span>\n </div>\n </div>\n\n <!-- Virtual Field Content (Collapsible) -->\n <div v-if=\"!isVirtualFieldCollapsed(path.name)\" class=\"p-3\">\n <div v-if=\"path.value == null\" class=\"text-sky-800\">\n {{'' + path.value}}\n </div>\n <div v-else>\n {{path.value}}\n </div>\n </div>\n </div>\n\n <!-- No Results Message -->\n <div v-if=\"searchQuery && filteredSchemaPaths.length === 0 && filteredVirtuals.length === 0\" class=\"text-center py-8 text-gray-500\">\n <svg class=\"w-12 h-12 mx-auto mb-4 text-gray-300\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709\"></path>\n </svg>\n <p>No fields found matching \"{{searchQuery}}\"</p>\n </div>\n </div>\n\n <!-- JSON View -->\n <div v-if=\"viewMode === 'json'\" class=\"json-view\">\n <div class=\"border border-gray-300 rounded-lg bg-gray-50 p-4 overflow-auto\">\n <pre class=\"text-sm font-mono text-gray-800 whitespace-pre\">{{formattedJson}}</pre>\n </div>\n </div>\n\n <!-- Add Field Modal -->\n <modal v-if=\"showAddFieldModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeAddFieldModal\">&times;</div>\n <div class=\"add-field-modal\">\n <div class=\"mb-6\">\n <h2 class=\"text-xl font-semibold text-gray-900 mb-2\">Add New Field</h2>\n <p class=\"text-sm text-gray-600\">Create a new field for this document</p>\n </div>\n\n <form @submit.prevent=\"handleAddFieldSubmit\" class=\"space-y-4\">\n <!-- Field Name -->\n <div>\n <label for=\"fieldName\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Name *\n </label>\n <input\n id=\"fieldName\"\n v-model=\"fieldData.name\"\n type=\"text\"\n required\n placeholder=\"Enter field name...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.name }\"\n />\n <p v-if=\"fieldErrors.name\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.name}}</p>\n <p v-if=\"fieldData.name && getTransformedFieldName() !== fieldData.name.trim()\" class=\"mt-1 text-sm text-blue-600\">\n Field name will be: <code class=\"bg-blue-50 px-1 py-0.5 rounded text-xs\">{{getTransformedFieldName()}}</code>\n </p>\n </div>\n\n <!-- Field Type -->\n <div>\n <label for=\"fieldType\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Field Type *\n </label>\n <select\n id=\"fieldType\"\n v-model=\"fieldData.type\"\n required\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.type }\"\n >\n <option value=\"\">Select field type...</option>\n <option v-for=\"type in allFieldTypes\" :key=\"type\" :value=\"type\">\n {{type}}\n </option>\n </select>\n <p v-if=\"fieldErrors.type\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.type}}</p>\n </div>\n\n <!-- Field Value -->\n <div>\n <label for=\"fieldValue\" class=\"block text-sm font-medium text-gray-700 mb-1\">\n Initial Value\n </label>\n\n <!-- Date picker for Date type -->\n <input\n v-if=\"shouldUseDatePicker\"\n v-model=\"fieldData.value\"\n type=\"date\"\n id=\"fieldValue\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- Simple input for basic types -->\n <input\n v-else-if=\"!shouldUseCodeMirror\"\n v-model=\"fieldData.value\"\n type=\"text\"\n id=\"fieldValue\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n />\n\n <!-- CodeMirror textarea for complex types -->\n <textarea\n v-else\n ref=\"fieldValueEditor\"\n id=\"fieldValue\"\n rows=\"3\"\n placeholder=\"Enter initial value (optional)...\"\n class=\"w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent\"\n :class=\"{ 'border-red-500': fieldErrors.value }\"\n ></textarea>\n\n <p v-if=\"fieldErrors.value\" class=\"mt-1 text-sm text-red-600\">{{fieldErrors.value}}</p>\n <p class=\"mt-1 text-xs text-gray-500\">\n <span v-if=\"shouldUseDatePicker\">Select a date or leave empty for null/undefined values.</span>\n <span v-else-if=\"shouldUseCodeMirror\">Leave empty for null/undefined values. Use valid JSON format.</span>\n <span v-else>Leave empty for null/undefined values.</span>\n </p>\n </div>\n\n\n <!-- Action Buttons -->\n <div class=\"flex justify-end gap-3 pt-4 border-t border-gray-200\">\n <button\n type=\"button\"\n @click=\"closeAddFieldModal\"\n class=\"px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n :disabled=\"isSubmittingField\"\n class=\"px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <span v-if=\"isSubmittingField\">Adding...</span>\n <span v-else>Add Field</span>\n </button>\n </div>\n </form>\n </div>\n </template>\n </modal>\n</div>\n";
4823
5380
 
4824
5381
  /***/ }),
4825
5382
 
@@ -4830,7 +5387,7 @@ module.exports = "<div class=\"document-details\">\n <div v-for=\"path in schem
4830
5387
  /***/ ((module) => {
4831
5388
 
4832
5389
  "use strict";
4833
- module.exports = ".document-details {\n width: 100%;\n }\n \n .document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n }\n \n .document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n }\n \n .document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n }\n \n .document-details .date-position {\n float: right;\n margin-top: -7px;\n }";
5390
+ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-details .value {\n padding-top: 10px;\n padding-bottom: 10px;\n }\n \n .document-details .path-key {\n background-color: #f0f0f0;\n margin-bottom: 0.5em;\n }\n \n .document-details .path-type {\n color: rgba(0,0,0,.36);\n font-size: 0.8em;\n }\n \n .document-details .date-position {\n float: right;\n margin-top: -7px;\n }\n\n .truncated-value-container,\n .expanded-value-container {\n position: relative;\n }\n\n .truncated-value-container button,\n .expanded-value-container button {\n transition: all 0.2s ease;\n }\n\n .truncated-value-container button:hover,\n .expanded-value-container button:hover {\n transform: translateX(2px);\n }";
4834
5391
 
4835
5392
  /***/ }),
4836
5393
 
@@ -4841,7 +5398,7 @@ module.exports = ".document-details {\n width: 100%;\n }\n \n .document-de
4841
5398
  /***/ ((module) => {
4842
5399
 
4843
5400
  "use strict";
4844
- module.exports = "<div>\n <div class=\"relative path-key p-1 flex\">\n <div class=\"grow flex justify-between items-center\">\n <div>\n {{path.path}}\n <span class=\"path-type\">\n ({{(path.instance || 'unknown').toLowerCase()}})\n </span>\n </div>\n <div>\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm mr-1 rounded-md\"\n >View Document\n </router-link>\n </div>\n </div>\n <div v-if=\"editting && path.instance === 'Date'\" class=\"flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"editting && path.path !== '_id'\" class=\"pl-1\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n @input=\"changes[path.path] = $event; delete invalid[path.path];\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else class=\"pl-1\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n</div>\n";
5401
+ module.exports = "<div class=\"border border-gray-200 rounded-lg mb-2\">\n <!-- Collapsible Header -->\n <div \n @click=\"toggleCollapse\"\n class=\"p-3 bg-gray-50 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200\"\n >\n <div class=\"flex items-center\">\n <svg \n :class=\"isCollapsed ? 'rotate-0' : 'rotate-90'\"\n class=\"w-4 h-4 text-gray-500 mr-2 transition-transform duration-200\"\n fill=\"none\" \n stroke=\"currentColor\" \n viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\"></path>\n </svg>\n <span class=\"font-medium text-gray-900\">{{path.path}}</span>\n <span class=\"ml-2 text-sm text-gray-500\">({{(path.instance || 'unknown').toLowerCase()}})</span>\n </div>\n <div class=\"flex items-center gap-2\">\n <router-link\n v-if=\"path.ref && getValueForPath(path.path)\"\n :to=\"`/model/${path.ref}/document/${getValueForPath(path.path)}`\"\n class=\"bg-ultramarine-600 hover:bg-ultramarine-500 text-white px-2 py-1 text-sm rounded-md\"\n @click.stop\n >View Document\n </router-link>\n </div>\n </div>\n \n <!-- Collapsible Content -->\n <div v-if=\"!isCollapsed\" class=\"p-3\">\n <!-- Date Type Selector (when editing dates) -->\n <div v-if=\"editting && path.instance === 'Date'\" class=\"mb-3 flex gap-1.5\">\n <div\n @click=\"dateType = 'picker'\"\n :class=\"dateType === 'picker' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'picker' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n Date Picker\n </div>\n </div>\n <div\n @click=\"dateType = 'iso'\"\n :class=\"dateType === 'iso' ? 'bg-teal-600' : ''\"\n class=\"self-stretch px-2 py-1 rounded-sm justify-center items-center gap-1.5 flex cursor-pointer\">\n <div\n :class=\"dateType === 'iso' ? 'text-white' : ''\"\n class=\"text-xs font-medium font-['Lato'] capitalize leading-tight\">\n ISO String\n </div>\n </div>\n </div>\n \n <!-- Field Content -->\n <div v-if=\"editting && path.path !== '_id'\">\n <component\n :is=\"getEditComponentForPath(path)\"\n :value=\"getEditValueForPath(path)\"\n :format=\"dateType\"\n @input=\"changes[path.path] = $event; delete invalid[path.path];\"\n @error=\"invalid[path.path] = $event;\"\n >\n </component>\n </div>\n <div v-else>\n <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->\n <div v-if=\"needsTruncation && !isValueExpanded\" class=\"truncated-value-container\">\n <div class=\"text-gray-700 whitespace-pre-wrap break-words font-mono text-sm\">{{truncatedString}}</div>\n <button \n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show more ({{valueAsString.length}} characters)\n </button>\n </div>\n <div v-else-if=\"needsTruncation && isValueExpanded\" class=\"expanded-value-container\">\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n <button \n @click=\"toggleValueExpansion\"\n class=\"mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1\"\n >\n <svg class=\"w-4 h-4 rotate-180\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n </svg>\n Show less\n </button>\n </div>\n <div v-else>\n <component :is=\"getComponentForPath(path)\" :value=\"getValueForPath(path.path)\"></component>\n </div>\n </div>\n </div>\n</div>\n";
4845
5402
 
4846
5403
  /***/ }),
4847
5404
 
@@ -4874,7 +5431,7 @@ module.exports = "<div>\n <h2>\n Are you sure you want to delete the f
4874
5431
  /***/ ((module) => {
4875
5432
 
4876
5433
  "use strict";
4877
- module.exports = ".document {\n max-width: 1200px;\n margin-left: auto;\n margin-right: auto;\n padding-top: 25px;\n}\n\n.document .document-menu {\n display: flex;\n}\n\n.document .document-menu .left {\n flex-grow: 1;\n}\n\n.document .document-menu .right {\n flex-grow: 1;\n text-align: right;\n}\n\n.document .document-menu .right button:not(:last-child) {\n margin-right: 0.5em;\n}\n\n.document button img {\n height: 1em;\n}";
5434
+ module.exports = ".document {\n max-width: 1200px;\n margin-left: auto;\n margin-right: auto;\n padding-top: 25px;\n}\n\n.document .document-menu {\n display: flex;\n position: sticky;\n top: 0;\n z-index: 100;\n background-color: white;\n border-radius: 5px;\n padding: 15px 15px;\n margin: -15px 0 15px 0;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.document .document-menu .left {\n flex-grow: 1;\n}\n\n.document .document-menu .right {\n flex-grow: 1;\n text-align: right;\n}\n\n.document .document-menu .right button:not(:last-child) {\n margin-right: 0.5em;\n}\n\n.document button img {\n height: 1em;\n}";
4878
5435
 
4879
5436
  /***/ }),
4880
5437
 
@@ -4885,7 +5442,7 @@ module.exports = ".document {\n max-width: 1200px;\n margin-left: auto;\n mar
4885
5442
  /***/ ((module) => {
4886
5443
 
4887
5444
  "use strict";
4888
- module.exports = "<div class=\"document\">\n <div class=\"document-menu\">\n <div class=\"left\">\n <button\n @click=\"$router.push('/model/' + this.model)\"\n class=\"rounded-md bg-gray-400 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &lsaquo; Back\n </button>\n </div>\n\n <div class=\"right\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n <img src=\"images/delete.svg\" class=\"inline\" /> Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-pink-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n <img src=\"images/duplicate.svg\" class=\"inline\" /> Clone\n </button>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">&times;</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">&times;</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">&times;</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n</div>\n";
5445
+ module.exports = "<div class=\"document px-1 md:px-0\">\n <div class=\"flex justify-between\">\n <div class=\"flex\">\n <button\n @click=\"$router.push('/model/' + this.model)\"\n class=\"mr-2 rounded-md bg-gray-400 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &lsaquo; Back\n </button>\n <button\n @click=\"viewMode = 'fields'\"\n :class=\"viewMode === 'fields'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 border-r-0 rounded-l-lg rounded-r-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 6h16M4 10h16M4 14h16M4 18h16\"></path>\n </svg>\n Fields\n </button>\n <button\n @click=\"viewMode = 'json'\"\n :class=\"viewMode === 'json'\n ? 'bg-blue-600 text-white z-10'\n : 'bg-gray-200 text-gray-700 hover:bg-gray-300'\"\n class=\"px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center gap-2 border border-gray-300 rounded-r-lg rounded-l-none\"\n >\n <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4\"></path>\n </svg>\n JSON\n </button>\n </div>\n\n <div class=\"gap-2 hidden md:flex\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true\"\n :disabled=\"!canEdit\"\n :class=\"{'cursor-not-allowed opacity-50': !canEdit}\"\n type=\"button\"\n class=\"rounded-md bg-ultramarine-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-600\">\n <img src=\"images/edit.svg\" class=\"inline\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false\"\n type=\"button\"\n class=\"rounded-md bg-slate-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-600\">\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n @click=\"shouldShowConfirmModal=true;\"\n type=\"button\"\n class=\"rounded-md bg-forest-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600\">\n <img src=\"images/save.svg\" class=\"inline\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-valencia-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n <img src=\"images/delete.svg\" class=\"inline\" /> Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true;\"\n :disabled=\"!canManipulate\"\n :class=\"{'cursor-not-allowed opacity-50': !canManipulate}\"\n type=\"button\"\n class=\"rounded-md bg-pink-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-valencia-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600\">\n <img src=\"images/duplicate.svg\" class=\"inline\" /> Clone\n </button>\n </div>\n <div class=\"md:hidden flex items-center\">\n <div class=\"relative\">\n <button\n @click=\"mobileMenuOpen = !mobileMenuOpen\"\n type=\"button\"\n class=\"inline-flex items-center justify-center rounded-md bg-gray-200 px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500\"\n aria-expanded=\"mobileMenuOpen\"\n aria-label=\"Open menu\"\n >\n <svg class=\"w-5 h-5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n d=\"M4 6h16M4 12h16M4 18h16\"></path>\n </svg>\n </button>\n <div\n v-show=\"mobileMenuOpen\"\n @click.away=\"mobileMenuOpen = false\"\n class=\"origin-top-right absolute right-0 mt-2 w-52 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50\"\n >\n <div class=\"py-1 flex flex-col\">\n <button\n v-if=\"!editting\"\n @click=\"editting = true; mobileMenuOpen = false\"\n :disabled=\"!canEdit\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canEdit ? 'cursor-not-allowed opacity-50' : 'hover:bg-ultramarine-100']\"\n type=\"button\"\n >\n <img src=\"images/edit.svg\" class=\"inline mr-2\" /> Edit\n </button>\n <button\n v-if=\"editting\"\n @click=\"editting = false; mobileMenuOpen = false\"\n type=\"button\"\n class=\"flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-slate-100\"\n >\n &times; Cancel\n </button>\n <button\n v-if=\"editting\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-green-100']\"\n @click=\"shouldShowConfirmModal=true; mobileMenuOpen = false\"\n type=\"button\"\n >\n <img src=\"images/save.svg\" class=\"inline mr-2\" /> Save\n </button>\n <button\n @click=\"shouldShowDeleteModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-red-100']\"\n type=\"button\"\n >\n <img src=\"images/delete.svg\" class=\"inline mr-2\" /> Delete\n </button>\n <button\n @click=\"shouldShowCloneModal=true; mobileMenuOpen = false\"\n :disabled=\"!canManipulate\"\n :class=\"['flex items-center px-4 py-2 text-sm text-gray-700', !canManipulate ? 'cursor-not-allowed opacity-50' : 'hover:bg-pink-100']\"\n type=\"button\"\n >\n <img src=\"images/duplicate.svg\" class=\"inline mr-2\" /> Clone\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div v-if=\"status === 'loaded'\">\n <document-details\n :document=\"document\"\n :schemaPaths=\"schemaPaths\"\n :virtualPaths=\"virtualPaths\"\n :editting=\"editting\"\n :changes=\"changes\"\n :invalid=\"invalid\"\n :viewMode=\"viewMode\"\n @add-field=\"addField\"\n @view-mode-change=\"updateViewMode\"></document-details>\n <modal v-if=\"shouldShowConfirmModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowConfirmModal = false;\">&times;</div>\n <confirm-changes @close=\"shouldShowConfirmModal = false;\" @save=\"save\" :value=\"changes\"></confirm-changes>\n </template>\n </modal>\n <modal v-if=\"shouldShowDeleteModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowDeleteModal = false;\">&times;</div>\n <confirm-delete @close=\"shouldShowDeleteModal = false;\" @remove=\"remove\" :value=\"document\"></confirm-delete>\n </template>\n </modal>\n <modal v-if=\"shouldShowCloneModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"shouldShowCloneModal = false;\">&times;</div>\n <clone-document :currentModel=\"model\" :doc=\"document\" :schemaPaths=\"schemaPaths\" @close=\"showClonedDocument\"></clone-document>\n </template>\n </modal>\n </div>\n</div>\n";
4889
5446
 
4890
5447
  /***/ }),
4891
5448
 
@@ -4911,6 +5468,17 @@ module.exports = "<div class=\"edit-array\">\n <textarea\n ref=\"arrayEditor
4911
5468
 
4912
5469
  /***/ }),
4913
5470
 
5471
+ /***/ "./frontend/src/edit-boolean/edit-boolean.html":
5472
+ /*!*****************************************************!*\
5473
+ !*** ./frontend/src/edit-boolean/edit-boolean.html ***!
5474
+ \*****************************************************/
5475
+ /***/ ((module) => {
5476
+
5477
+ "use strict";
5478
+ module.exports = "<div class=\"edit-boolean\">\n <div class=\"flex flex-wrap gap-2\">\n <label class=\"flex items-center gap-2 px-3 py-2 border rounded cursor-pointer hover:bg-gray-50\" \n :class=\"selectedValue === true ? 'bg-blue-100 border-blue-300 text-blue-800' : 'border-gray-300 text-gray-700'\">\n <input\n type=\"radio\"\n :checked=\"selectedValue === true\"\n @change=\"selectValue(true)\"\n class=\"w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500\"\n />\n <span class=\"text-sm font-medium\">true</span>\n </label>\n \n <label class=\"flex items-center gap-2 px-3 py-2 border rounded cursor-pointer hover:bg-gray-50\"\n :class=\"selectedValue === false ? 'bg-blue-100 border-blue-300 text-blue-800' : 'border-gray-300 text-gray-700'\">\n <input\n type=\"radio\"\n :checked=\"selectedValue === false\"\n @change=\"selectValue(false)\"\n class=\"w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500\"\n />\n <span class=\"text-sm font-medium\">false</span>\n </label>\n \n <label class=\"flex items-center gap-2 px-3 py-2 border rounded cursor-pointer hover:bg-gray-50\"\n :class=\"selectedValue === null ? 'bg-blue-100 border-blue-300 text-blue-800' : 'border-gray-300 text-gray-700'\">\n <input\n type=\"radio\"\n :checked=\"selectedValue === null\"\n @change=\"selectValue(null)\"\n class=\"w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500\"\n />\n <span class=\"text-sm font-medium\">null</span>\n </label>\n \n <label class=\"flex items-center gap-2 px-3 py-2 border rounded cursor-pointer hover:bg-gray-50\"\n :class=\"selectedValue === undefined ? 'bg-blue-100 border-blue-300 text-blue-800' : 'border-gray-300 text-gray-700'\">\n <input\n type=\"radio\"\n :checked=\"selectedValue === undefined\"\n @change=\"selectValue(undefined)\"\n class=\"w-4 h-4 text-blue-600 border-gray-300 focus:ring-blue-500\"\n />\n <span class=\"text-sm font-medium\">undefined</span>\n </label>\n </div>\n</div>\n";
5479
+
5480
+ /***/ }),
5481
+
4914
5482
  /***/ "./frontend/src/edit-date/edit-date.html":
4915
5483
  /*!***********************************************!*\
4916
5484
  !*** ./frontend/src/edit-date/edit-date.html ***!
@@ -5171,7 +5739,7 @@ module.exports = ".active {\n text-decoration: underline;\n}\n\n.navbar .nav-le
5171
5739
  /***/ ((module) => {
5172
5740
 
5173
5741
  "use strict";
5174
- module.exports = "<div class=\"navbar w-full bg-gray-50 flex justify-between border-b border-gray-200 !h-[55px]\">\n <div class=\"flex items-center gap-4 h-full pl-4\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px] mr-1\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!state.nodeEnv\" title=\"NODE_ENV\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{state.nodeEnv}}\n </div>\n </div>\n <div class=\"h-full pr-4 hidden md:block\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"chatView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Chat</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-10 top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 pr-4 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" class=\"fixed inset-0 bg-white shadow-lg z-50 transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-gray-200\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px]\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Chat</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-ultramarine-600 px-3 py-2 text-base font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-gray-50 rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-gray-900 font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100 cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n";
5742
+ module.exports = "<div class=\"navbar w-full bg-gray-50 flex justify-between border-b border-gray-200 !h-[55px]\">\n <div class=\"flex items-center gap-4 h-full pl-4\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px] mr-1\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!state.nodeEnv\" title=\"NODE_ENV\" class=\"inline-flex items-center rounded-md px-2 py-1 text-sm font-medium text-gray-900\" :class=\"warnEnv ? 'bg-red-300' : 'bg-yellow-300'\">\n {{state.nodeEnv}}\n </div>\n </div>\n <div class=\"h-full pr-4 hidden md:block\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"documentView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <span v-else class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium text-gray-300 cursor-not-allowed\" aria-disabled=\"true\">\n Documents\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"dashboardView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"chatView ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Chat</a>\n <span v-else class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n\n <div class=\"h-full flex items-center\" v-if=\"!user && hasAPIKey\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"rounded bg-ultramarine-600 px-2 py-2 text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"h-full flex items-center relative\" v-clickOutside=\"hideFlyout\">\n <div>\n <button type=\"button\" @click=\"showFlyout = !showFlyout\" class=\"relative flex rounded-full bg-gray-800 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800\" id=\"user-menu-button\" aria-expanded=\"false\" aria-haspopup=\"true\">\n <span class=\"absolute -inset-1.5\"></span>\n <span class=\"sr-only\">Open user menu</span>\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n </button>\n </div>\n\n <div v-if=\"showFlyout\" class=\"absolute right-0 z-[100] top-[90%] w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black/5 focus:outline-none\" role=\"menu\" aria-orientation=\"vertical\" aria-labelledby=\"user-menu-button\" tabindex=\"-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" @click=\"showFlyout = false\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Team</router-link>\n <span v-else class=\"block px-4 py-2 text-sm text-gray-300 cursor-not-allowed\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"cursor-pointer block px-4 py-2 text-sm text-gray-700 hover:bg-ultramarine-200\" role=\"menuitem\" tabindex=\"-1\" id=\"user-menu-item-2\">Sign out</span>\n </div>\n </div>\n\n </div>\n </div>\n <div class=\"md:hidden flex items-center\">\n <!-- Mobile menu toggle, controls the 'mobileMenuOpen' state. -->\n <button type=\"button\" id=\"open-mobile-menu\" class=\"-ml-2 rounded-md p-2 pr-4 text-gray-400\">\n <span class=\"sr-only\">Open menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5\" />\n </svg>\n </button>\n </div>\n\n <!-- Mobile menu mask -->\n <div id=\"mobile-menu-mask\" class=\"fixed inset-0 bg-black bg-opacity-40 z-40 hidden\"></div>\n <!-- Mobile menu drawer -->\n <div id=\"mobile-menu\" class=\"fixed inset-0 bg-white shadow-lg z-50 transform translate-x-full transition-transform duration-200 ease-in-out flex flex-col\">\n <div class=\"flex items-center justify-between px-4 !h-[55px] border-b border-gray-200\">\n <router-link :to=\"{ name: defaultRoute }\">\n <img src=\"images/logo.svg\" class=\"h-[32px]\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <button type=\"button\" id=\"close-mobile-menu\" class=\"text-gray-400 p-2 rounded-md\">\n <span class=\"sr-only\">Close menu</span>\n <svg class=\"h-6 w-6\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <nav class=\"flex-1 px-4 py-4 space-y-2\">\n <a v-if=\"hasAccess(roles, 'root')\"\n href=\"#/\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"documentView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Documents</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Documents\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'dashboards')\"\n href=\"#/dashboards\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"dashboardView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Dashboards</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Dashboards\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <a v-if=\"hasAccess(roles, 'chat')\"\n href=\"#/chat\"\n class=\"block px-3 py-2 rounded-md text-base font-medium\"\n :class=\"chatView ? 'text-ultramarine-700 bg-ultramarine-100' : 'text-gray-700 hover:bg-gray-100'\">Chat</a>\n <span v-else class=\"block px-3 py-2 rounded-md text-base font-medium text-gray-300 cursor-not-allowed\">\n Chat\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <div v-if=\"!user && hasAPIKey\" class=\"mt-4\">\n <button\n type=\"button\"\n @click=\"loginWithGithub\"\n class=\"w-full rounded bg-ultramarine-600 px-3 py-2 text-base font-semibold text-white shadow-sm hover:bg-ultramarine-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n Login\n </button>\n </div>\n <div v-if=\"user && hasAPIKey\" class=\"mt-4\">\n <div class=\"flex items-center gap-3 px-3 py-2 bg-gray-50 rounded-md\">\n <img class=\"size-8 rounded-full\" :src=\"user.picture\" alt=\"\">\n <span class=\"text-gray-900 font-medium\">{{ user.name }}</span>\n </div>\n <div class=\"mt-2 space-y-1\">\n <router-link to=\"/team\" v-if=\"hasAccess(roles, 'team')\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100\">Team</router-link>\n <span v-else class=\"block px-3 py-2 rounded-md text-base text-gray-300 cursor-not-allowed\">\n Team\n <svg class=\"h-4 w-4 ml-1 inline\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <path fill-rule=\"evenodd\" d=\"M10 2a4 4 0 00-4 4v2H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-1V6a4 4 0 00-4-4zm-3 6V6a3 3 0 116 0v2H7z\" clip-rule=\"evenodd\" />\n </svg>\n </span>\n <span @click=\"logout\" class=\"block px-3 py-2 rounded-md text-base text-gray-700 hover:bg-ultramarine-100 cursor-pointer\">Sign out</span>\n </div>\n </div>\n </nav>\n </div>\n</div>\n";
5175
5743
 
5176
5744
  /***/ }),
5177
5745
 
@@ -5204,7 +5772,7 @@ module.exports = "<div class=\"p-1\">\n <form class=\"space-y-4\">\n <div cl
5204
5772
  /***/ ((module) => {
5205
5773
 
5206
5774
  "use strict";
5207
- module.exports = "<div class=\"mx-auto max-w-5xl py-6 px-2 flex flex-col gap-8\">\n <div>\n <div class=\"text-xl font-bold\">\n Subscription Details\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <div v-else-if=\"workspace && workspace.subscriptionTier\" class=\"mt-4 flex justify-between items-center\">\n <div>\n <span class=\"font-bold\">Tier:</span> {{workspace.subscriptionTier ?? 'No subscription'}}\n </div>\n <div>\n <async-button\n type=\"submit\"\n @click=\"getWorkspaceCustomerPortalLink\"\n class=\"inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2\">\n View in Stripe\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4 ml-1\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </async-button>\n </div>\n </div>\n <div v-else-if=\"workspace && !workspace.subscriptionTier\" class=\"mt-4 flex justify-between items-center\">\n <div>\n <span class=\"font-bold\">No active subscription</span>\n <div class=\"text-sm text-gray-700\">\n You won't be able to invite your team until you activate a subscription\n </div>\n </div>\n <div>\n <a\n :href=\"paymentLink\"\n target=\"_blank\"\n class=\"inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-ultramarine-500 focus:ring-offset-2\">\n Subscribe With Stripe\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4 ml-1\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </a>\n </div>\n </div>\n </div>\n <div>\n <div class=\"text-xl font-bold\">\n Current Members\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <ul v-else role=\"list\" class=\"divide-y divide-gray-100\">\n <li class=\"flex justify-between gap-x-6 py-5\" v-for=\"user in users\">\n <div class=\"flex min-w-0 gap-x-4\">\n <img class=\"size-12 flex-none rounded-full bg-gray-50\" :src=\"user.picture ?? 'images/logo.svg'\" alt=\"\">\n <div class=\"min-w-0 flex-auto\">\n <p class=\"text-sm/6 font-semibold text-gray-900\">\n {{user.name || user.githubUsername}}\n <span v-if=\"user.isFreeUser\" class=\"ml-1 inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20\">Free</span>\n </p>\n <p class=\"mt-1 truncate text-xs/5 text-gray-500\">{{user.email ?? 'No Email'}}</p>\n </div>\n </div>\n <div class=\"hidden shrink-0 sm:flex sm:flex-col sm:items-end\">\n <p class=\"text-sm/6 text-gray-900 capitalize\">{{getRolesForUser(user).join(', ')}}</p>\n <div class=\"flex gap-3\">\n <p class=\"mt-1 text-xs/5 text-gray-500 cursor-pointer\">\n Edit\n </p>\n <button\n class=\"mt-1 text-xs/5 text-valencia-500 cursor-pointer disabled:cursor-not-allowed disabled:text-gray-300\"\n :disabled=\"getRolesForUser(user).includes('owner')\"\n @click=\"showRemoveModal = user\">\n Remove\n </button>\n </div>\n </div>\n </li>\n </ul>\n </div>\n <div>\n <div class=\"flex items-center justify-between\">\n <div class=\"text-xl font-bold\">\n Invitations\n </div>\n <div class=\"mt-4 sm:ml-16 sm:mt-0 sm:flex-none\">\n <button\n type=\"button\"\n @click=\"showNewInvitationModal = true\"\n :disabled=\"status === 'loading'\"\n :tier=\"workspace?.subscriptionTier\"\n class=\"block rounded-md bg-ultramarine-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 disabled:bg-gray-500 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n New Invitation\n <svg class=\"inline w-4 h-4 ml-1\" v-if=\"workspace && !workspace.subscriptionTier\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M12 1.5a5.25 5.25 0 00-5.25 5.25v3a3 3 0 00-3 3v6.75a3 3 0 003 3h10.5a3 3 0 003-3v-6.75a3 3 0 00-3-3v-3c0-2.9-2.35-5.25-5.25-5.25zm3.75 8.25v-3a3.75 3.75 0 10-7.5 0v3h7.5z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <div v-else-if=\"invitations?.length > 0\" class=\"mt-8 flow-root\" v-if=\"invitations?.length > 0\">\n <div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\">\n <div class=\"inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8\">\n <table class=\"min-w-full divide-y divide-gray-300\">\n <thead>\n <tr>\n <th scope=\"col\" class=\"py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0\">GitHub Username</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Email</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Status</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Role</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-gray-200 bg-white\">\n <tr v-for=\"invitation in invitations\">\n <td class=\"whitespace-nowrap py-5 pl-4 pr-3 text-sm sm:pl-0\">\n {{invitation.githubUsername}}\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n {{invitation.email}}\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n <span class=\"inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-600/20\">\n Pending\n </span>\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n {{invitation.roles.join(', ')}}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n <div v-else-if=\"invitations?.length === 0\" class=\"mt-4\">\n <div class=\"text-center\">\n <svg class=\"mx-auto size-12 text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path vector-effect=\"non-scaling-stroke\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z\" />\n </svg>\n <h3 class=\"mt-2 text-sm font-semibold text-gray-900\">No invitations</h3>\n <p class=\"mt-1 text-sm text-gray-500\">You have no outstanding invitations</p>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showNewInvitationModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showNewInvitationModal = false\">&times;</div>\n <new-invitation @close=\"showNewInvitationModal = false\" @invitationCreated=\"invitations.push($event.invitation)\"></new-invitation>\n </template>\n </modal>\n\n <modal v-if=\"showRemoveModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showRemoveModal = false\">&times;</div>\n <div>\n Are you sure you want to remove user <span class=\"font-bold\">{{showRemoveModal.githubUsername}}</span> from this workspace?\n </div>\n <div class=\"mt-6 grid grid-cols-2 gap-4\">\n <async-button\n @click=\"removeFromWorkspace(showConfirmDeleteModal)\"\n class=\"border-0 mt-0 flex w-full items-center justify-center gap-3 rounded-md bg-valencia-500 hover:bg-valencia-400 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-400\">\n <span class=\"text-sm font-semibold leading-6\">Yes, Remove</span>\n </async-button>\n\n <span @click=\"showRemoveModal = null\" class=\"cursor-pointer flex w-full items-center justify-center gap-3 rounded-md bg-slate-500 hover:bg-slate-400 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-400\">\n <span class=\"text-sm font-semibold leading-6\">Cancel</span>\n </span>\n </div>\n </template>\n </modal>\n</div>\n";
5775
+ module.exports = "<div class=\"mx-auto max-w-5xl py-6 px-2 flex flex-col gap-8\">\n <div>\n <div class=\"text-xl font-bold\">\n Subscription Details\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <div v-else-if=\"workspace && workspace.subscriptionTier\" class=\"mt-4 flex justify-between items-center\">\n <div>\n <span class=\"font-bold\">Tier:</span> {{workspace.subscriptionTier ?? 'No subscription'}}\n </div>\n <div>\n <async-button\n type=\"submit\"\n @click=\"getWorkspaceCustomerPortalLink\"\n class=\"inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2\">\n View in Stripe\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4 ml-1\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </async-button>\n </div>\n </div>\n <div v-else-if=\"workspace && !workspace.subscriptionTier\" class=\"mt-4 flex justify-between items-center\">\n <div>\n <span class=\"font-bold\">No active subscription</span>\n <div class=\"text-sm text-gray-700\">\n You won't be able to invite your team until you activate a subscription\n </div>\n </div>\n <div>\n <a\n :href=\"paymentLink\"\n target=\"_blank\"\n class=\"inline-flex items-center justify-center rounded-md border border-transparent bg-ultramarine-600 py-1 px-2 text-sm font-medium text-white shadow-sm hover:bg-ultramarine-500 focus:outline-none focus:ring-2 focus:ring-ultramarine-500 focus:ring-offset-2\">\n Subscribe With Stripe\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" class=\"w-4 h-4 ml-1\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </a>\n </div>\n </div>\n </div>\n <div>\n <div class=\"text-xl font-bold\">\n Current Members\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <ul v-else role=\"list\" class=\"divide-y divide-gray-100\">\n <li class=\"flex justify-between gap-x-6 py-5\" v-for=\"user in users\">\n <div class=\"flex min-w-0 gap-x-4\">\n <img class=\"size-12 flex-none rounded-full bg-gray-50\" :src=\"user.picture ?? 'images/logo.svg'\" alt=\"\">\n <div class=\"min-w-0 flex-auto\">\n <p class=\"text-sm/6 font-semibold text-gray-900\">\n {{user.name || user.githubUsername}}\n <span v-if=\"user.isFreeUser\" class=\"ml-1 inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20\">Free</span>\n </p>\n <p class=\"mt-1 truncate text-xs/5 text-gray-500\">{{user.email ?? 'No Email'}}</p>\n </div>\n </div>\n <div class=\"hidden shrink-0 sm:flex sm:flex-col sm:items-end\">\n <p class=\"text-sm/6 text-gray-900 capitalize\">{{getRolesForUser(user).join(', ')}}</p>\n <div class=\"flex gap-3\">\n <button\n type=\"button\"\n class=\"mt-1 text-xs/5 text-gray-500 cursor-pointer disabled:cursor-not-allowed disabled:text-gray-300\"\n :disabled=\"getRolesForUser(user).includes('owner')\"\n @click=\"openEditModal(user)\">\n Edit\n </button>\n <button\n class=\"mt-1 text-xs/5 text-valencia-500 cursor-pointer disabled:cursor-not-allowed disabled:text-gray-300\"\n :disabled=\"getRolesForUser(user).includes('owner')\"\n @click=\"showRemoveModal = user\">\n Remove\n </button>\n </div>\n </div>\n </li>\n </ul>\n </div>\n <div>\n <div class=\"flex items-center justify-between\">\n <div class=\"text-xl font-bold\">\n Invitations\n </div>\n <div class=\"mt-4 sm:ml-16 sm:mt-0 sm:flex-none\">\n <button\n type=\"button\"\n @click=\"showNewInvitationModal = true\"\n :disabled=\"status === 'loading'\"\n :tier=\"workspace?.subscriptionTier\"\n class=\"block rounded-md bg-ultramarine-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-ultramarine-500 disabled:bg-gray-500 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n New Invitation\n <svg class=\"inline w-4 h-4 ml-1\" v-if=\"workspace && !workspace.subscriptionTier\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M12 1.5a5.25 5.25 0 00-5.25 5.25v3a3 3 0 00-3 3v6.75a3 3 0 003 3h10.5a3 3 0 003-3v-6.75a3 3 0 00-3-3v-3c0-2.9-2.35-5.25-5.25-5.25zm3.75 8.25v-3a3.75 3.75 0 10-7.5 0v3h7.5z\" clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n </div>\n <div v-if=\"status === 'loading'\" class=\"mt-4\">\n <img src=\"images/loader.gif\" class=\"inline w-8 h-8\">\n </div>\n <div v-else-if=\"invitations?.length > 0\" class=\"mt-8 flow-root\" v-if=\"invitations?.length > 0\">\n <div class=\"-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\">\n <div class=\"inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8\">\n <table class=\"min-w-full divide-y divide-gray-300\">\n <thead>\n <tr>\n <th scope=\"col\" class=\"py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-0\">GitHub Username</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Email</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Status</th>\n <th scope=\"col\" class=\"px-3 py-3.5 text-left text-sm font-semibold text-gray-900\">Role</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-gray-200 bg-white\">\n <tr v-for=\"invitation in invitations\">\n <td class=\"whitespace-nowrap py-5 pl-4 pr-3 text-sm sm:pl-0\">\n {{invitation.githubUsername}}\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n {{invitation.email}}\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n <span class=\"inline-flex items-center rounded-md bg-gray-50 px-2 py-1 text-xs font-medium text-gray-700 ring-1 ring-inset ring-gray-600/20\">\n Pending\n </span>\n </td>\n <td class=\"whitespace-nowrap px-3 py-5 text-sm text-gray-500\">\n {{invitation.roles.join(', ')}}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n <div v-else-if=\"invitations?.length === 0\" class=\"mt-4\">\n <div class=\"text-center\">\n <svg class=\"mx-auto size-12 text-gray-400\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path vector-effect=\"non-scaling-stroke\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z\" />\n </svg>\n <h3 class=\"mt-2 text-sm font-semibold text-gray-900\">No invitations</h3>\n <p class=\"mt-1 text-sm text-gray-500\">You have no outstanding invitations</p>\n </div>\n </div>\n </div>\n\n <modal v-if=\"showNewInvitationModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showNewInvitationModal = false\">&times;</div>\n <new-invitation @close=\"showNewInvitationModal = false\" @invitationCreated=\"invitations.push($event.invitation)\"></new-invitation>\n </template>\n </modal>\n\n <modal v-if=\"showEditModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"closeEditModal\">&times;</div>\n <div class=\"p-1 space-y-4\">\n <div class=\"text-lg font-bold\">\n Edit Member\n </div>\n\n <div>\n <div class=\"text-sm/6 font-semibold text-gray-900\">\n {{showEditModal.user.name || showEditModal.user.githubUsername}}\n </div>\n <div class=\"text-xs/5 text-gray-500\">\n {{showEditModal.user.email ?? 'No Email'}}\n </div>\n </div>\n\n <div>\n <label for=\"editRole\" class=\"block text-sm/6 font-medium text-gray-900\">Role</label>\n <div class=\"mt-2 grid grid-cols-1\">\n <select\n id=\"editRole\"\n name=\"editRole\"\n v-model=\"showEditModal.role\"\n class=\"col-start-1 row-start-1 w-full appearance-none rounded-md bg-white py-1.5 pl-3 pr-8 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6\">\n <option value=\"admin\" :disabled=\"disableRoleOption('admin')\">Admin</option>\n <option value=\"member\" :disabled=\"disableRoleOption('member')\">Member</option>\n <option value=\"readonly\" :disabled=\"disableRoleOption('readonly')\">Read-only</option>\n <option value=\"dashboards\" :disabled=\"disableRoleOption('dashboards')\">Dashboards Only</option>\n </select>\n <svg class=\"pointer-events-none col-start-1 row-start-1 mr-2 size-5 self-center justify-self-end text-gray-500 sm:size-4\" viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\">\n <path fill-rule=\"evenodd\" d=\"M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z\" clip-rule=\"evenodd\" />\n </svg>\n </div>\n <div v-if=\"!workspace?.subscriptionTier\" class=\"mt-2 text-sm text-gray-700\">\n You can only assign the \"Dashboards Only\" role until you activate a subscription.\n </div>\n </div>\n\n <div class=\"mt-6 grid grid-cols-2 gap-4\">\n <async-button\n @click=\"updateWorkspaceMember\"\n :disabled=\"showEditModal.role === showEditModal.originalRole\"\n class=\"border-0 mt-0 flex w-full items-center justify-center gap-3 rounded-md bg-ultramarine-600 hover:bg-ultramarine-500 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-500\">\n <span class=\"text-sm font-semibold leading-6\">Save</span>\n </async-button>\n\n <span @click=\"closeEditModal\" class=\"cursor-pointer flex w-full items-center justify-center gap-3 rounded-md bg-slate-500 hover:bg-slate-400 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-400\">\n <span class=\"text-sm font-semibold leading-6\">Cancel</span>\n </span>\n </div>\n </div>\n </template>\n </modal>\n\n <modal v-if=\"showRemoveModal\">\n <template v-slot:body>\n <div class=\"modal-exit\" @click=\"showRemoveModal = null\">&times;</div>\n <div>\n Are you sure you want to remove user <span class=\"font-bold\">{{showRemoveModal.githubUsername}}</span> from this workspace?\n </div>\n <div class=\"mt-6 grid grid-cols-2 gap-4\">\n <async-button\n @click=\"removeFromWorkspace\"\n class=\"border-0 mt-0 flex w-full items-center justify-center gap-3 rounded-md bg-valencia-500 hover:bg-valencia-400 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-400\">\n <span class=\"text-sm font-semibold leading-6\">Yes, Remove</span>\n </async-button>\n\n <span @click=\"showRemoveModal = null\" class=\"cursor-pointer flex w-full items-center justify-center gap-3 rounded-md bg-slate-500 hover:bg-slate-400 px-3 py-1.5 text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-400\">\n <span class=\"text-sm font-semibold leading-6\">Cancel</span>\n </span>\n </div>\n </template>\n </modal>\n</div>\n";
5208
5776
 
5209
5777
  /***/ }),
5210
5778
 
@@ -15267,7 +15835,7 @@ var bson = /*#__PURE__*/Object.freeze({
15267
15835
  /***/ ((module) => {
15268
15836
 
15269
15837
  "use strict";
15270
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.126","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
15838
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.0.130","description":"A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.","homepage":"https://studio.mongoosejs.io/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"dependencies":{"archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.1.0","marked":"15.0.12","node-inspect-extracted":"3.x","tailwindcss":"3.4.0","vanillatoasts":"^1.6.0","vue":"3.x","webpack":"5.x"},"peerDependencies":{"bson":"^5.5.1 || 6.x","express":"4.x","mongoose":"7.x || 8.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongoose":"8.x"},"scripts":{"lint":"eslint .","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js"}}');
15271
15839
 
15272
15840
  /***/ })
15273
15841