@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.
- package/backend/netlify.js +8 -1
- package/frontend/public/app.js +122 -10
- package/frontend/public/tw.css +197 -2
- package/frontend/src/index.js +8 -3
- package/frontend/src/mothership.js +8 -0
- package/frontend/src/navbar/navbar.html +2 -2
- package/frontend/src/navbar/navbar.js +4 -1
- package/frontend/src/routes.js +6 -1
- package/frontend/src/team/new-invitation/new-invitation.html +42 -0
- package/frontend/src/team/new-invitation/new-invitation.js +21 -0
- package/frontend/src/team/team.html +86 -0
- package/frontend/src/team/team.js +25 -0
- package/package.json +1 -1
package/backend/netlify.js
CHANGED
|
@@ -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
|
|
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 => {
|
package/frontend/public/app.js
CHANGED
|
@@ -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-[
|
|
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\">×</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
|
-
|
|
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;
|
package/frontend/public/tw.css
CHANGED
|
@@ -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-\[
|
|
618
|
-
top:
|
|
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
|
}
|
package/frontend/src/index.js
CHANGED
|
@@ -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
|
-
|
|
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-[
|
|
39
|
-
|
|
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() {
|
package/frontend/src/routes.js
CHANGED
|
@@ -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">×</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
|
+
});
|