@mongoosejs/studio 0.0.57 → 0.0.59

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.
@@ -37,7 +37,14 @@ module.exports = function netlify(options) {
37
37
  .then(res => res.json()));
38
38
  }
39
39
 
40
- const { user, roles } = await fetch(`${mothershipUrl}/me?` + new URLSearchParams({ ...params, workspaceId: workspace._id }))
40
+ const { user, roles } = await fetch(`${mothershipUrl}/me?`, {
41
+ method: 'POST',
42
+ body: JSON.stringify({ workspaceId: workspace._id }),
43
+ headers: {
44
+ 'Authorization': authorization,
45
+ 'Content-Type': 'application/json'
46
+ }
47
+ })
41
48
  .then(response => {
42
49
  if (response.status < 200 || response.status >= 400) {
43
50
  return response.json().then(data => {
@@ -1864,10 +1864,10 @@ module.exports = app => app.component('models', {
1864
1864
 
1865
1865
  const axios = __webpack_require__(/*! axios */ "./node_modules/axios/dist/browser/axios.cjs");
1866
1866
  const client = axios.create({
1867
- baseURL: ''
1867
+ baseURL: 'http://localhost:8888/.netlify/functions'
1868
1868
  });
1869
1869
 
1870
- client.hasAPIKey = !!'';
1870
+ client.hasAPIKey = !!'http://localhost:8888/.netlify/functions';
1871
1871
 
1872
1872
  client.interceptors.request.use(req => {
1873
1873
  const accessToken = window.localStorage.getItem('_mongooseStudioAccessToken') || null;
@@ -1882,12 +1882,20 @@ exports.githubLogin = function githubLogin() {
1882
1882
  return client.post('/githubLogin', { state: window.location.href }).then(res => res.data);
1883
1883
  };
1884
1884
 
1885
+ exports.getWorkspaceTeam = function getWorkspaceTeam() {
1886
+ return client.post('/getWorkspaceTeam', { workspaceId: {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-06T22:24:05.260Z","__v":0,"name":"Zevo DEV"}._id }).then(res => res.data);
1887
+ };
1888
+
1889
+ exports.inviteToWorkspace = function inviteToWorkspace(params) {
1890
+ return client.post('/inviteToWorkspace', { workspaceId: {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-06T22:24:05.260Z","__v":0,"name":"Zevo DEV"}._id, ...params }).then(res => res.data);
1891
+ };
1892
+
1885
1893
  exports.github = function github(code) {
1886
- return client.post('/github', { code, workspaceId: {}._id }).then(res => res.data);
1894
+ return client.post('/github', { code, workspaceId: {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-06T22:24:05.260Z","__v":0,"name":"Zevo DEV"}._id }).then(res => res.data);
1887
1895
  };
1888
1896
 
1889
1897
  exports.me = function me() {
1890
- return client.post('/me', { workspaceId: {}._id }).then(res => res.data);
1898
+ return client.post('/me', { workspaceId: {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-06T22:24:05.260Z","__v":0,"name":"Zevo DEV"}._id }).then(res => res.data);
1891
1899
  };
1892
1900
 
1893
1901
  exports.hasAPIKey = client.hasAPIKey;
@@ -1914,7 +1922,7 @@ appendCSS(__webpack_require__(/*! ./navbar.css */ "./frontend/src/navbar/navbar.
1914
1922
 
1915
1923
  module.exports = app => app.component('navbar', {
1916
1924
  template: template,
1917
- props: ['user'],
1925
+ props: ['user', 'roles'],
1918
1926
  data: () => ({ nodeEnv: null, showFlyout: false }),
1919
1927
  computed: {
1920
1928
  routeName() {
@@ -1925,6 +1933,9 @@ module.exports = app => app.component('navbar', {
1925
1933
  },
1926
1934
  hasAPIKey() {
1927
1935
  return mothership.hasAPIKey;
1936
+ },
1937
+ canViewTeam() {
1938
+ return this.roles?.includes('owner') || this.roles?.includes('admin');
1928
1939
  }
1929
1940
  },
1930
1941
  async mounted() {
@@ -2002,9 +2013,15 @@ module.exports = [
2002
2013
  path: '/dashboard/:dashboardId',
2003
2014
  name: 'dashboard',
2004
2015
  component: 'dashboard'
2016
+ },
2017
+ {
2018
+ path: '/team',
2019
+ name: 'team',
2020
+ component: 'team'
2005
2021
  }
2006
2022
  ];
2007
2023
 
2024
+
2008
2025
  /***/ }),
2009
2026
 
2010
2027
  /***/ "./frontend/src/splash/splash.js":
@@ -2025,7 +2042,7 @@ module.exports = app => app.component('splash', {
2025
2042
  data: () => ({ error: null }),
2026
2043
  computed: {
2027
2044
  workspaceName() {
2028
- return {}.name;
2045
+ return {"_id":"67a5366c745bb0e6735950dc","ownerId":"679ba73e4cc3ddc28f6ef6af","baseUrl":"https://web.zevo.io","members":[{"userId":"679ba73e4cc3ddc28f6ef6af","roles":["owner"]}],"createdAt":"2025-02-06T22:23:40.903Z","updatedAt":"2025-02-06T22:24:05.260Z","__v":0,"name":"Zevo DEV"}.name;
2029
2046
  }
2030
2047
  },
2031
2048
  async mounted() {
@@ -2050,6 +2067,74 @@ module.exports = app => app.component('splash', {
2050
2067
  });
2051
2068
 
2052
2069
 
2070
+ /***/ }),
2071
+
2072
+ /***/ "./frontend/src/team/new-invitation/new-invitation.js":
2073
+ /*!************************************************************!*\
2074
+ !*** ./frontend/src/team/new-invitation/new-invitation.js ***!
2075
+ \************************************************************/
2076
+ /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
2077
+
2078
+ "use strict";
2079
+
2080
+
2081
+ const mothership = __webpack_require__(/*! ../../mothership */ "./frontend/src/mothership.js");
2082
+ const template = __webpack_require__(/*! ./new-invitation.html */ "./frontend/src/team/new-invitation/new-invitation.html");
2083
+
2084
+ module.exports = app => app.component('new-invitation', {
2085
+ template,
2086
+ emits: ['close', 'invitationCreated'],
2087
+ data: () => ({
2088
+ githubUsername: '',
2089
+ email: '',
2090
+ role: null
2091
+ }),
2092
+ methods: {
2093
+ async inviteToWorkspace() {
2094
+ const { invitation } = await mothership.inviteToWorkspace({ githubUsername: this.githubUsername, email: this.email, roles: [this.role] });
2095
+ this.$emit('invitationCreated', { invitation });
2096
+ this.$emit('close');
2097
+ }
2098
+ }
2099
+ });
2100
+
2101
+
2102
+ /***/ }),
2103
+
2104
+ /***/ "./frontend/src/team/team.js":
2105
+ /*!***********************************!*\
2106
+ !*** ./frontend/src/team/team.js ***!
2107
+ \***********************************/
2108
+ /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
2109
+
2110
+ "use strict";
2111
+
2112
+
2113
+ const mothership = __webpack_require__(/*! ../mothership */ "./frontend/src/mothership.js");
2114
+ const template = __webpack_require__(/*! ./team.html */ "./frontend/src/team/team.html");
2115
+
2116
+ module.exports = app => app.component('team', {
2117
+ template,
2118
+ data: () => ({
2119
+ workspace: null,
2120
+ users: null,
2121
+ invitations: null,
2122
+ showNewInvitationModal: false
2123
+ }),
2124
+ async mounted() {
2125
+ const { workspace, users, invitations } = await mothership.getWorkspaceTeam();
2126
+ this.workspace = workspace;
2127
+ this.users = users;
2128
+ this.invitations = invitations;
2129
+ },
2130
+ methods: {
2131
+ getRolesForUser(user) {
2132
+ return this.workspace.members.find(member => member.userId === user._id)?.roles ?? [];
2133
+ }
2134
+ }
2135
+ });
2136
+
2137
+
2053
2138
  /***/ }),
2054
2139
 
2055
2140
  /***/ "./node_modules/mpath/index.js":
@@ -3176,7 +3261,7 @@ module.exports = ".navbar {\n width: 100%;\n background-color: #eee;\n}\n\n.ac
3176
3261
  /***/ ((module) => {
3177
3262
 
3178
3263
  "use strict";
3179
- module.exports = "<div class=\"navbar\">\n <div class=\"nav-left flex items-center gap-4 h-full\">\n <router-link to=\"/\">\n <img src=\"images/logo.svg\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!nodeEnv\" 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 {{nodeEnv}}\n </div>\n </div>\n <div class=\"nav-right h-full\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"routeName === 'root' ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <a\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"routeName === 'dashboards' ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\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-[82%] 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 <!-- Active: \"bg-gray-100 outline-none\", Not Active: \"\" -->\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 style=\"clear: both\"></div>\n</div>\n";
3264
+ module.exports = "<div class=\"navbar\">\n <div class=\"nav-left flex items-center gap-4 h-full\">\n <router-link to=\"/\">\n <img src=\"images/logo.svg\" alt=\"Mongoose Studio Logo\" />\n </router-link>\n <div v-if=\"!!nodeEnv\" 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 {{nodeEnv}}\n </div>\n </div>\n <div class=\"nav-right h-full\">\n <div class=\"sm:ml-6 sm:flex sm:space-x-8 h-full\">\n <a\n href=\"#/\"\n class=\"inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium\"\n :class=\"routeName === 'root' ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Documents</a>\n <a\n href=\"#/dashboards\"\n class=\"inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium\"\n :class=\"routeName === 'dashboards' ? 'text-gray-900 border-ultramarine-500' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700'\">Dashboards</a>\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=\"canViewTeam\" @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 @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 style=\"clear: both\"></div>\n</div>\n";
3180
3265
 
3181
3266
  /***/ }),
3182
3267
 
@@ -3191,6 +3276,28 @@ module.exports = "<div class=\"w-full h-full flex items-center justify-center\">
3191
3276
 
3192
3277
  /***/ }),
3193
3278
 
3279
+ /***/ "./frontend/src/team/new-invitation/new-invitation.html":
3280
+ /*!**************************************************************!*\
3281
+ !*** ./frontend/src/team/new-invitation/new-invitation.html ***!
3282
+ \**************************************************************/
3283
+ /***/ ((module) => {
3284
+
3285
+ "use strict";
3286
+ module.exports = "<div class=\"p-1\">\n <form class=\"space-y-4\">\n <div class=\"text-lg font-bold\">\n New Invitation\n </div>\n\n <div>\n <label for=\"githubUsername\" class=\"block text-sm/6 font-medium text-gray-900\">GitHub Username</label>\n <div class=\"mt-2\">\n <input type=\"githubUsername\" name=\"githubUsername\" id=\"githubUsername\" v-model=\"githubUsername\" class=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6\" placeholder=\"johnsmith12\">\n </div>\n </div>\n\n <div>\n <label for=\"email\" class=\"block text-sm/6 font-medium text-gray-900\">Email (Optional)</label>\n <div class=\"mt-2\">\n <input type=\"email\" name=\"email\" id=\"email\" v-model=\"email\" class=\"block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6\" placeholder=\"you@example.com\">\n </div>\n </div>\n\n <div>\n <label for=\"location\" class=\"block text-sm/6 font-medium text-gray-900\">Role</label>\n <div class=\"mt-2 grid grid-cols-1\">\n <select id=\"role\" name=\"role\" v-model=\"role\" 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-indigo-600 sm:text-sm/6\">\n <option value=\"admin\">Admin</option>\n <option value=\"member\">Member</option>\n <option value=\"readonly\">Read-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>\n\n <async-button\n type=\"submit\"\n @click=\"inviteToWorkspace\"\n class=\"inline-flex justify-center rounded-md border border-transparent bg-forest-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-forest-green-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2\">\n Submit\n </async-button>\n </form>\n</div>\n";
3287
+
3288
+ /***/ }),
3289
+
3290
+ /***/ "./frontend/src/team/team.html":
3291
+ /*!*************************************!*\
3292
+ !*** ./frontend/src/team/team.html ***!
3293
+ \*************************************/
3294
+ /***/ ((module) => {
3295
+
3296
+ "use strict";
3297
+ module.exports = "<div class=\"mx-auto max-w-5xl py-6 px-2\">\n <div class=\"text-xl font-bold\">\n Current Members\n </div>\n <ul 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\">{{user.name}}</p>\n <p class=\"mt-1 truncate text-xs/5 text-gray-500\">{{user.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 <p class=\"mt-1 text-xs/5 text-gray-500\">Last seen <time datetime=\"2023-01-23T13:23Z\">3h ago</time></p>\n </div>\n </li>\n </ul>\n <div class=\"mt-6\">\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 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 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600\">\n New Invitation\n </button>\n </div>\n </div>\n <div 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\n <div v-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</div>\n";
3298
+
3299
+ /***/ }),
3300
+
3194
3301
  /***/ "./node_modules/axios/dist/browser/axios.cjs":
3195
3302
  /*!***************************************************!*\
3196
3303
  !*** ./node_modules/axios/dist/browser/axios.cjs ***!
@@ -11049,6 +11156,8 @@ __webpack_require__(/*! ./modal/modal */ "./frontend/src/modal/modal.js")(app);
11049
11156
  __webpack_require__(/*! ./models/models */ "./frontend/src/models/models.js")(app);
11050
11157
  __webpack_require__(/*! ./navbar/navbar */ "./frontend/src/navbar/navbar.js")(app);
11051
11158
  __webpack_require__(/*! ./splash/splash */ "./frontend/src/splash/splash.js")(app);
11159
+ __webpack_require__(/*! ./team/team */ "./frontend/src/team/team.js")(app);
11160
+ __webpack_require__(/*! ./team/new-invitation/new-invitation */ "./frontend/src/team/new-invitation/new-invitation.js")(app);
11052
11161
 
11053
11162
  app.component('app-component', {
11054
11163
  template: `
@@ -11057,7 +11166,7 @@ app.component('app-component', {
11057
11166
  <splash />
11058
11167
  </div>
11059
11168
  <div v-else-if="!hasAPIKey || user">
11060
- <navbar :user="user" />
11169
+ <navbar :user="user" :roles="roles" />
11061
11170
  <div class="view">
11062
11171
  <router-view :key="$route.fullPath" />
11063
11172
  </div>
@@ -11083,14 +11192,17 @@ app.component('app-component', {
11083
11192
  if (mothership.hasAPIKey) {
11084
11193
  const token = window.localStorage.getItem('_mongooseStudioAccessToken');
11085
11194
  if (token) {
11086
- this.user = await mothership.me().then(res => res.user);
11195
+ const { user, roles } = await mothership.me();
11196
+ this.user = user;
11197
+ this.roles = roles;
11087
11198
  }
11088
11199
  }
11089
11200
  },
11090
11201
  setup() {
11091
11202
  const user = Vue.ref(null);
11203
+ const roles = Vue.ref(null);
11092
11204
 
11093
- const state = Vue.reactive({ user });
11205
+ const state = Vue.reactive({ user, roles });
11094
11206
  Vue.provide('state', state);
11095
11207
 
11096
11208
  return state;
@@ -590,6 +590,10 @@ video {
590
590
  border-width: 0;
591
591
  }
592
592
 
593
+ .pointer-events-none {
594
+ pointer-events: none;
595
+ }
596
+
593
597
  .fixed {
594
598
  position: fixed;
595
599
  }
@@ -614,8 +618,8 @@ video {
614
618
  right: 0px;
615
619
  }
616
620
 
617
- .top-\[82\%\] {
618
- top: 82%;
621
+ .top-\[90\%\] {
622
+ top: 90%;
619
623
  }
620
624
 
621
625
  .isolate {
@@ -626,6 +630,14 @@ video {
626
630
  z-index: 10;
627
631
  }
628
632
 
633
+ .col-start-1 {
634
+ grid-column-start: 1;
635
+ }
636
+
637
+ .row-start-1 {
638
+ grid-row-start: 1;
639
+ }
640
+
629
641
  .m-0 {
630
642
  margin: 0px;
631
643
  }
@@ -686,6 +698,10 @@ video {
686
698
  margin-right: 0.375rem;
687
699
  }
688
700
 
701
+ .mr-2 {
702
+ margin-right: 0.5rem;
703
+ }
704
+
689
705
  .mt-1 {
690
706
  margin-top: 0.25rem;
691
707
  }
@@ -738,6 +754,19 @@ video {
738
754
  display: flow-root;
739
755
  }
740
756
 
757
+ .grid {
758
+ display: grid;
759
+ }
760
+
761
+ .hidden {
762
+ display: none;
763
+ }
764
+
765
+ .size-12 {
766
+ width: 3rem;
767
+ height: 3rem;
768
+ }
769
+
741
770
  .size-5 {
742
771
  width: 1.25rem;
743
772
  height: 1.25rem;
@@ -800,6 +829,10 @@ video {
800
829
  width: 100%;
801
830
  }
802
831
 
832
+ .min-w-0 {
833
+ min-width: 0px;
834
+ }
835
+
803
836
  .min-w-full {
804
837
  min-width: 100%;
805
838
  }
@@ -812,6 +845,14 @@ video {
812
845
  flex: 1 1 0%;
813
846
  }
814
847
 
848
+ .flex-auto {
849
+ flex: 1 1 auto;
850
+ }
851
+
852
+ .flex-none {
853
+ flex: none;
854
+ }
855
+
815
856
  .flex-shrink-0 {
816
857
  flex-shrink: 0;
817
858
  }
@@ -844,6 +885,16 @@ video {
844
885
  list-style-type: disc;
845
886
  }
846
887
 
888
+ .appearance-none {
889
+ -webkit-appearance: none;
890
+ -moz-appearance: none;
891
+ appearance: none;
892
+ }
893
+
894
+ .grid-cols-1 {
895
+ grid-template-columns: repeat(1, minmax(0, 1fr));
896
+ }
897
+
847
898
  .flex-row {
848
899
  flex-direction: row;
849
900
  }
@@ -860,6 +911,10 @@ video {
860
911
  justify-content: center;
861
912
  }
862
913
 
914
+ .justify-between {
915
+ justify-content: space-between;
916
+ }
917
+
863
918
  .gap-1 {
864
919
  gap: 0.25rem;
865
920
  }
@@ -876,6 +931,16 @@ video {
876
931
  gap: 1rem;
877
932
  }
878
933
 
934
+ .gap-x-4 {
935
+ -moz-column-gap: 1rem;
936
+ column-gap: 1rem;
937
+ }
938
+
939
+ .gap-x-6 {
940
+ -moz-column-gap: 1.5rem;
941
+ column-gap: 1.5rem;
942
+ }
943
+
879
944
  .gap-y-5 {
880
945
  row-gap: 1.25rem;
881
946
  }
@@ -896,12 +961,23 @@ video {
896
961
  margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
897
962
  }
898
963
 
964
+ .space-y-4 > :not([hidden]) ~ :not([hidden]) {
965
+ --tw-space-y-reverse: 0;
966
+ margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
967
+ margin-bottom: calc(1rem * var(--tw-space-y-reverse));
968
+ }
969
+
899
970
  .divide-y > :not([hidden]) ~ :not([hidden]) {
900
971
  --tw-divide-y-reverse: 0;
901
972
  border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
902
973
  border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
903
974
  }
904
975
 
976
+ .divide-gray-100 > :not([hidden]) ~ :not([hidden]) {
977
+ --tw-divide-opacity: 1;
978
+ border-color: rgb(243 244 246 / var(--tw-divide-opacity));
979
+ }
980
+
905
981
  .divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
906
982
  --tw-divide-opacity: 1;
907
983
  border-color: rgb(229 231 235 / var(--tw-divide-opacity));
@@ -912,10 +988,18 @@ video {
912
988
  border-color: rgb(209 213 219 / var(--tw-divide-opacity));
913
989
  }
914
990
 
991
+ .self-center {
992
+ align-self: center;
993
+ }
994
+
915
995
  .self-stretch {
916
996
  align-self: stretch;
917
997
  }
918
998
 
999
+ .justify-self-end {
1000
+ justify-self: end;
1001
+ }
1002
+
919
1003
  .overflow-auto {
920
1004
  overflow: auto;
921
1005
  }
@@ -1041,6 +1125,11 @@ video {
1041
1125
  background-color: rgb(156 163 175 / var(--tw-bg-opacity));
1042
1126
  }
1043
1127
 
1128
+ .bg-gray-50 {
1129
+ --tw-bg-opacity: 1;
1130
+ background-color: rgb(249 250 251 / var(--tw-bg-opacity));
1131
+ }
1132
+
1044
1133
  .bg-gray-500 {
1045
1134
  --tw-bg-opacity: 1;
1046
1135
  background-color: rgb(107 114 128 / var(--tw-bg-opacity));
@@ -1051,6 +1140,11 @@ video {
1051
1140
  background-color: rgb(31 41 55 / var(--tw-bg-opacity));
1052
1141
  }
1053
1142
 
1143
+ .bg-green-50 {
1144
+ --tw-bg-opacity: 1;
1145
+ background-color: rgb(240 253 244 / var(--tw-bg-opacity));
1146
+ }
1147
+
1054
1148
  .bg-green-600 {
1055
1149
  --tw-bg-opacity: 1;
1056
1150
  background-color: rgb(22 163 74 / var(--tw-bg-opacity));
@@ -1186,6 +1280,16 @@ video {
1186
1280
  padding-bottom: 1rem;
1187
1281
  }
1188
1282
 
1283
+ .py-5 {
1284
+ padding-top: 1.25rem;
1285
+ padding-bottom: 1.25rem;
1286
+ }
1287
+
1288
+ .py-6 {
1289
+ padding-top: 1.5rem;
1290
+ padding-bottom: 1.5rem;
1291
+ }
1292
+
1189
1293
  .pb-2 {
1190
1294
  padding-bottom: 0.5rem;
1191
1295
  }
@@ -1222,6 +1326,10 @@ video {
1222
1326
  padding-right: 1rem;
1223
1327
  }
1224
1328
 
1329
+ .pr-8 {
1330
+ padding-right: 2rem;
1331
+ }
1332
+
1225
1333
  .pt-1 {
1226
1334
  padding-top: 0.25rem;
1227
1335
  }
@@ -1261,6 +1369,11 @@ video {
1261
1369
  line-height: 1.25rem;
1262
1370
  }
1263
1371
 
1372
+ .text-sm\/6 {
1373
+ font-size: 0.875rem;
1374
+ line-height: 1.5rem;
1375
+ }
1376
+
1264
1377
  .text-xl {
1265
1378
  font-size: 1.25rem;
1266
1379
  line-height: 1.75rem;
@@ -1271,6 +1384,11 @@ video {
1271
1384
  line-height: 1rem;
1272
1385
  }
1273
1386
 
1387
+ .text-xs\/5 {
1388
+ font-size: 0.75rem;
1389
+ line-height: 1.25rem;
1390
+ }
1391
+
1274
1392
  .font-bold {
1275
1393
  font-weight: 700;
1276
1394
  }
@@ -1320,6 +1438,11 @@ video {
1320
1438
  color: rgb(17 24 39 / var(--tw-text-opacity));
1321
1439
  }
1322
1440
 
1441
+ .text-green-700 {
1442
+ --tw-text-opacity: 1;
1443
+ color: rgb(21 128 61 / var(--tw-text-opacity));
1444
+ }
1445
+
1323
1446
  .text-red-400 {
1324
1447
  --tw-text-opacity: 1;
1325
1448
  color: rgb(248 113 113 / var(--tw-text-opacity));
@@ -1367,10 +1490,22 @@ video {
1367
1490
  outline-offset: 2px;
1368
1491
  }
1369
1492
 
1493
+ .outline {
1494
+ outline-style: solid;
1495
+ }
1496
+
1370
1497
  .outline-0 {
1371
1498
  outline-width: 0px;
1372
1499
  }
1373
1500
 
1501
+ .outline-1 {
1502
+ outline-width: 1px;
1503
+ }
1504
+
1505
+ .-outline-offset-1 {
1506
+ outline-offset: -1px;
1507
+ }
1508
+
1374
1509
  .outline-gray-300 {
1375
1510
  outline-color: #d1d5db;
1376
1511
  }
@@ -1398,6 +1533,14 @@ video {
1398
1533
  --tw-ring-color: rgb(17 24 39 / 0.05);
1399
1534
  }
1400
1535
 
1536
+ .ring-green-600\/20 {
1537
+ --tw-ring-color: rgb(22 163 74 / 0.2);
1538
+ }
1539
+
1540
+ .ring-gray-600\/20 {
1541
+ --tw-ring-color: rgb(75 85 99 / 0.2);
1542
+ }
1543
+
1401
1544
  .filter {
1402
1545
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
1403
1546
  }
@@ -1445,6 +1588,11 @@ video {
1445
1588
  background-color: rgb(37 99 235 / var(--tw-bg-opacity));
1446
1589
  }
1447
1590
 
1591
+ .hover\:bg-forest-green-500:hover {
1592
+ --tw-bg-opacity: 1;
1593
+ background-color: rgb(0 242 58 / var(--tw-bg-opacity));
1594
+ }
1595
+
1448
1596
  .hover\:bg-gray-300:hover {
1449
1597
  --tw-bg-opacity: 1;
1450
1598
  background-color: rgb(209 213 219 / var(--tw-bg-opacity));
@@ -1519,6 +1667,26 @@ video {
1519
1667
  outline-offset: 2px;
1520
1668
  }
1521
1669
 
1670
+ .focus\:outline:focus {
1671
+ outline-style: solid;
1672
+ }
1673
+
1674
+ .focus\:outline-2:focus {
1675
+ outline-width: 2px;
1676
+ }
1677
+
1678
+ .focus\:-outline-offset-2:focus {
1679
+ outline-offset: -2px;
1680
+ }
1681
+
1682
+ .focus\:outline-indigo-600:focus {
1683
+ outline-color: #4f46e5;
1684
+ }
1685
+
1686
+ .focus\:outline-ultramarine-600:focus {
1687
+ outline-color: #1823ff;
1688
+ }
1689
+
1522
1690
  .focus\:ring-0:focus {
1523
1691
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
1524
1692
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@@ -1536,6 +1704,11 @@ video {
1536
1704
  --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
1537
1705
  }
1538
1706
 
1707
+ .focus\:ring-forest-green-500:focus {
1708
+ --tw-ring-opacity: 1;
1709
+ --tw-ring-color: rgb(0 242 58 / var(--tw-ring-opacity));
1710
+ }
1711
+
1539
1712
  .focus\:ring-gray-500:focus {
1540
1713
  --tw-ring-opacity: 1;
1541
1714
  --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity));
@@ -1622,6 +1795,11 @@ video {
1622
1795
  display: flex;
1623
1796
  }
1624
1797
 
1798
+ .sm\:size-4 {
1799
+ width: 1rem;
1800
+ height: 1rem;
1801
+ }
1802
+
1625
1803
  .sm\:flex-auto {
1626
1804
  flex: 1 1 auto;
1627
1805
  }
@@ -1630,6 +1808,14 @@ video {
1630
1808
  flex: none;
1631
1809
  }
1632
1810
 
1811
+ .sm\:flex-col {
1812
+ flex-direction: column;
1813
+ }
1814
+
1815
+ .sm\:items-end {
1816
+ align-items: flex-end;
1817
+ }
1818
+
1633
1819
  .sm\:items-center {
1634
1820
  align-items: center;
1635
1821
  }
@@ -1649,6 +1835,10 @@ video {
1649
1835
  padding-right: 1.5rem;
1650
1836
  }
1651
1837
 
1838
+ .sm\:pl-0 {
1839
+ padding-left: 0px;
1840
+ }
1841
+
1652
1842
  .sm\:pl-6 {
1653
1843
  padding-left: 1.5rem;
1654
1844
  }
@@ -1662,6 +1852,11 @@ video {
1662
1852
  line-height: 1.25rem;
1663
1853
  }
1664
1854
 
1855
+ .sm\:text-sm\/6 {
1856
+ font-size: 0.875rem;
1857
+ line-height: 1.5rem;
1858
+ }
1859
+
1665
1860
  .sm\:leading-6 {
1666
1861
  line-height: 1.5rem;
1667
1862
  }
@@ -45,6 +45,8 @@ require('./modal/modal')(app);
45
45
  require('./models/models')(app);
46
46
  require('./navbar/navbar')(app);
47
47
  require('./splash/splash')(app);
48
+ require('./team/team')(app);
49
+ require('./team/new-invitation/new-invitation')(app);
48
50
 
49
51
  app.component('app-component', {
50
52
  template: `
@@ -53,7 +55,7 @@ app.component('app-component', {
53
55
  <splash />
54
56
  </div>
55
57
  <div v-else-if="!hasAPIKey || user">
56
- <navbar :user="user" />
58
+ <navbar :user="user" :roles="roles" />
57
59
  <div class="view">
58
60
  <router-view :key="$route.fullPath" />
59
61
  </div>
@@ -79,14 +81,17 @@ app.component('app-component', {
79
81
  if (mothership.hasAPIKey) {
80
82
  const token = window.localStorage.getItem('_mongooseStudioAccessToken');
81
83
  if (token) {
82
- this.user = await mothership.me().then(res => res.user);
84
+ const { user, roles } = await mothership.me();
85
+ this.user = user;
86
+ this.roles = roles;
83
87
  }
84
88
  }
85
89
  },
86
90
  setup() {
87
91
  const user = Vue.ref(null);
92
+ const roles = Vue.ref(null);
88
93
 
89
- const state = Vue.reactive({ user });
94
+ const state = Vue.reactive({ user, roles });
90
95
  Vue.provide('state', state);
91
96
 
92
97
  return state;
@@ -20,6 +20,14 @@ exports.githubLogin = function githubLogin() {
20
20
  return client.post('/githubLogin', { state: window.location.href }).then(res => res.data);
21
21
  };
22
22
 
23
+ exports.getWorkspaceTeam = function getWorkspaceTeam() {
24
+ return client.post('/getWorkspaceTeam', { workspaceId: config__workspace._id }).then(res => res.data);
25
+ };
26
+
27
+ exports.inviteToWorkspace = function inviteToWorkspace(params) {
28
+ return client.post('/inviteToWorkspace', { workspaceId: config__workspace._id, ...params }).then(res => res.data);
29
+ };
30
+
23
31
  exports.github = function github(code) {
24
32
  return client.post('/github', { code, workspaceId: config__workspace._id }).then(res => res.data);
25
33
  };
@@ -35,8 +35,8 @@
35
35
  </button>
36
36
  </div>
37
37
 
38
- <div v-if="showFlyout" class="absolute right-0 z-10 top-[82%] 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">
39
- <!-- Active: "bg-gray-100 outline-none", Not Active: "" -->
38
+ <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">
39
+ <router-link to="team" v-if="canViewTeam" @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>
40
40
  <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>
41
41
  </div>
42
42
  </div>
@@ -10,7 +10,7 @@ appendCSS(require('./navbar.css'));
10
10
 
11
11
  module.exports = app => app.component('navbar', {
12
12
  template: template,
13
- props: ['user'],
13
+ props: ['user', 'roles'],
14
14
  data: () => ({ nodeEnv: null, showFlyout: false }),
15
15
  computed: {
16
16
  routeName() {
@@ -21,6 +21,9 @@ module.exports = app => app.component('navbar', {
21
21
  },
22
22
  hasAPIKey() {
23
23
  return mothership.hasAPIKey;
24
+ },
25
+ canViewTeam() {
26
+ return this.roles?.includes('owner') || this.roles?.includes('admin');
24
27
  }
25
28
  },
26
29
  async mounted() {
@@ -25,5 +25,10 @@ module.exports = [
25
25
  path: '/dashboard/:dashboardId',
26
26
  name: 'dashboard',
27
27
  component: 'dashboard'
28
+ },
29
+ {
30
+ path: '/team',
31
+ name: 'team',
32
+ component: 'team'
28
33
  }
29
- ];
34
+ ];
@@ -0,0 +1,42 @@
1
+ <div class="p-1">
2
+ <form class="space-y-4">
3
+ <div class="text-lg font-bold">
4
+ New Invitation
5
+ </div>
6
+
7
+ <div>
8
+ <label for="githubUsername" class="block text-sm/6 font-medium text-gray-900">GitHub Username</label>
9
+ <div class="mt-2">
10
+ <input type="githubUsername" name="githubUsername" id="githubUsername" v-model="githubUsername" class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6" placeholder="johnsmith12">
11
+ </div>
12
+ </div>
13
+
14
+ <div>
15
+ <label for="email" class="block text-sm/6 font-medium text-gray-900">Email (Optional)</label>
16
+ <div class="mt-2">
17
+ <input type="email" name="email" id="email" v-model="email" class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-ultramarine-600 sm:text-sm/6" placeholder="you@example.com">
18
+ </div>
19
+ </div>
20
+
21
+ <div>
22
+ <label for="location" class="block text-sm/6 font-medium text-gray-900">Role</label>
23
+ <div class="mt-2 grid grid-cols-1">
24
+ <select id="role" name="role" v-model="role" 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-indigo-600 sm:text-sm/6">
25
+ <option value="admin">Admin</option>
26
+ <option value="member">Member</option>
27
+ <option value="readonly">Read-only</option>
28
+ </select>
29
+ <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">
30
+ <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" />
31
+ </svg>
32
+ </div>
33
+ </div>
34
+
35
+ <async-button
36
+ type="submit"
37
+ @click="inviteToWorkspace"
38
+ class="inline-flex justify-center rounded-md border border-transparent bg-forest-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-forest-green-500 focus:outline-none focus:ring-2 focus:ring-forest-green-500 focus:ring-offset-2">
39
+ Submit
40
+ </async-button>
41
+ </form>
42
+ </div>
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const mothership = require('../../mothership');
4
+ const template = require('./new-invitation.html');
5
+
6
+ module.exports = app => app.component('new-invitation', {
7
+ template,
8
+ emits: ['close', 'invitationCreated'],
9
+ data: () => ({
10
+ githubUsername: '',
11
+ email: '',
12
+ role: null
13
+ }),
14
+ methods: {
15
+ async inviteToWorkspace() {
16
+ const { invitation } = await mothership.inviteToWorkspace({ githubUsername: this.githubUsername, email: this.email, roles: [this.role] });
17
+ this.$emit('invitationCreated', { invitation });
18
+ this.$emit('close');
19
+ }
20
+ }
21
+ });
@@ -0,0 +1,86 @@
1
+ <div class="mx-auto max-w-5xl py-6 px-2">
2
+ <div class="text-xl font-bold">
3
+ Current Members
4
+ </div>
5
+ <ul role="list" class="divide-y divide-gray-100">
6
+ <li class="flex justify-between gap-x-6 py-5" v-for="user in users">
7
+ <div class="flex min-w-0 gap-x-4">
8
+ <img class="size-12 flex-none rounded-full bg-gray-50" :src="user.picture ?? 'images/logo.svg'" alt="">
9
+ <div class="min-w-0 flex-auto">
10
+ <p class="text-sm/6 font-semibold text-gray-900">{{user.name}}</p>
11
+ <p class="mt-1 truncate text-xs/5 text-gray-500">{{user.email}}</p>
12
+ </div>
13
+ </div>
14
+ <div class="hidden shrink-0 sm:flex sm:flex-col sm:items-end">
15
+ <p class="text-sm/6 text-gray-900 capitalize">{{getRolesForUser(user).join(', ')}}</p>
16
+ <p class="mt-1 text-xs/5 text-gray-500">Last seen <time datetime="2023-01-23T13:23Z">3h ago</time></p>
17
+ </div>
18
+ </li>
19
+ </ul>
20
+ <div class="mt-6">
21
+ <div class="flex items-center justify-between">
22
+ <div class="text-xl font-bold">
23
+ Invitations
24
+ </div>
25
+ <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
26
+ <button
27
+ type="button"
28
+ @click="showNewInvitationModal = true"
29
+ 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 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ultramarine-600">
30
+ New Invitation
31
+ </button>
32
+ </div>
33
+ </div>
34
+ <div class="mt-8 flow-root" v-if="invitations?.length > 0">
35
+ <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
36
+ <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
37
+ <table class="min-w-full divide-y divide-gray-300">
38
+ <thead>
39
+ <tr>
40
+ <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>
41
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Email</th>
42
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Status</th>
43
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Role</th>
44
+ </tr>
45
+ </thead>
46
+ <tbody class="divide-y divide-gray-200 bg-white">
47
+ <tr v-for="invitation in invitations">
48
+ <td class="whitespace-nowrap py-5 pl-4 pr-3 text-sm sm:pl-0">
49
+ {{invitation.githubUsername}}
50
+ </td>
51
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
52
+ {{invitation.email}}
53
+ </td>
54
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
55
+ <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">
56
+ Pending
57
+ </span>
58
+ </td>
59
+ <td class="whitespace-nowrap px-3 py-5 text-sm text-gray-500">
60
+ {{invitation.roles.join(', ')}}
61
+ </td>
62
+ </tr>
63
+ </tbody>
64
+ </table>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <div v-if="invitations?.length === 0" class="mt-4">
70
+ <div class="text-center">
71
+ <svg class="mx-auto size-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
72
+ <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" />
73
+ </svg>
74
+ <h3 class="mt-2 text-sm font-semibold text-gray-900">No invitations</h3>
75
+ <p class="mt-1 text-sm text-gray-500">You have no outstanding invitations</p>
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ <modal v-if="showNewInvitationModal">
81
+ <template v-slot:body>
82
+ <div class="modal-exit" @click="showNewInvitationModal = false">&times;</div>
83
+ <new-invitation @close="showNewInvitationModal = false" @invitationCreated="invitations.push($event.invitation)"></new-invitation>
84
+ </template>
85
+ </modal>
86
+ </div>
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ const mothership = require('../mothership');
4
+ const template = require('./team.html');
5
+
6
+ module.exports = app => app.component('team', {
7
+ template,
8
+ data: () => ({
9
+ workspace: null,
10
+ users: null,
11
+ invitations: null,
12
+ showNewInvitationModal: false
13
+ }),
14
+ async mounted() {
15
+ const { workspace, users, invitations } = await mothership.getWorkspaceTeam();
16
+ this.workspace = workspace;
17
+ this.users = users;
18
+ this.invitations = invitations;
19
+ },
20
+ methods: {
21
+ getRolesForUser(user) {
22
+ return this.workspace.members.find(member => member.userId === user._id)?.roles ?? [];
23
+ }
24
+ }
25
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.57",
3
+ "version": "0.0.59",
4
4
  "dependencies": {
5
5
  "archetype": "0.13.1",
6
6
  "csv-stringify": "6.3.0",