7070 <v-card-title class =" text-center" >
7171 Invite Member
7272 </v-card-title >
73+ <v-window v-model =" formWindow" >
74+ <v-window-item value =" form-1" >
75+ <v-card-text >
76+ <p
77+ class =" mb-4"
78+ v-if =" envVariables.isCloud"
79+ >
80+ If this email isn't associated with an existing account, we'll send an email to sign-up.
81+ </p >
7382
74- <v-card-text >
75- <p
76- class =" text-caption text-grey-lighten-4 mb-1"
77- v-if =" envVariables.isCloud"
78- >
79- If this email isn't associated with an existing account, we'll send an email to sign-up.
80- </p >
81- <v-text-field
82- v-model =" email"
83- label =" Email"
84- :error-messages =" emailError"
85- required
86- data-test =" email-text"
87- />
88- </v-card-text >
89-
90- <v-card-text class =" mt-n10" >
91- <v-select
92- v-model =" selectedRole"
93- :items =" items"
94- label =" Role"
95- :error-messages =" selectedRoleError"
96- required
97- data-test =" role-select"
98- />
99- </v-card-text >
83+ <v-text-field
84+ v-model =" email"
85+ label =" Email"
86+ :error-messages =" emailError"
87+ required
88+ data-test =" email-text"
89+ />
90+ </v-card-text >
91+
92+ <v-card-text class =" mt-n10" >
93+ <v-select
94+ v-model =" selectedRole"
95+ :items =" items"
96+ label =" Role"
97+ :error-messages =" selectedRoleError"
98+ required
99+ data-test =" role-select"
100+ />
101+
102+ <v-checkbox
103+ v-model =" getInvitationCheckbox"
104+ label =" Get the invite link instead of sending an e-mail"
105+ hide-details
106+ data-test =" link-request-checkbox"
107+ />
108+ </v-card-text >
109+ </v-window-item >
110+ <v-window-item value =" form-2" >
111+ <v-card-text >
112+ <p class =" mb-4" >
113+ Share this link with the person you want to invite. They can use it to join your namespace.
114+ </p >
115+ <p class =" mb-4" ><strong >Note:</strong > This link is only valid for the email address you entered earlier.
116+ </p >
117+ <v-text-field
118+ v-model =" invitationLink"
119+ @click =" copyText(invitationLink)"
120+ @keypress =" copyText(invitationLink)"
121+ readonly
122+ active
123+ density =" compact"
124+ append-icon =" mdi-content-copy"
125+ label =" Invitation Link"
126+ data-test =" invitation-link"
127+ />
128+ <p class =" text-caption text-grey-darken-1" >
129+ The invitation link remains valid for 7 days, if the link does not work, ensure the invite has not expired.
130+ </p >
131+ </v-card-text >
132+ </v-window-item >
133+
134+ </v-window >
100135
101136 <v-card-actions >
102137 <v-spacer />
108143 <v-btn
109144 color =" primary"
110145 data-test =" invite-btn"
111- @click =" addMember()"
146+ @click =" getInvitationCheckbox ? generateLinkInvite() : sendEmailInvite()"
147+ :disabled =" !email || !selectedRole || !!emailError || formWindow === 'form-2'"
112148 >
113149 Invite
114150 </v-btn >
151+
115152 </v-card-actions >
116153 </v-card >
117154 </v-dialog >
118155 </div >
119156</template >
120157
121158<script setup lang="ts">
122- import { ref } from " vue" ;
159+ import { computed , ref } from " vue" ;
123160import { useField } from " vee-validate" ;
124161import * as yup from " yup" ;
125162import axios , { AxiosError } from " axios" ;
@@ -128,6 +165,7 @@ import hasPermission from "@/utils/permission";
128165import { useStore } from " @/store" ;
129166import { actions , authorizer } from " @/authorizer" ;
130167import {
168+ INotificationsCopy ,
131169 INotificationsError ,
132170 INotificationsSuccess ,
133171} from " @/interfaces/INotifications" ;
@@ -139,6 +177,9 @@ const items = ["administrator", "operator", "observer"];
139177const emit = defineEmits ([" update" ]);
140178const store = useStore ();
141179const dialog = ref (false );
180+ const getInvitationCheckbox = ref (false );
181+ const invitationLink = computed (() => store .getters [" namespaces/getInvitationLink" ]);
182+ const formWindow = ref (" form-1" );
142183
143184const {
144185 value : email,
@@ -152,7 +193,6 @@ const {
152193const {
153194 value : selectedRole,
154195 errorMessage : selectedRoleError,
155- setErrors : setSelectedRoleError,
156196 resetField : resetSelectedRole,
157197} = useField <string >(" selectedRole" , yup .string ().required (), {
158198 initialValue: " " ,
@@ -170,20 +210,6 @@ const hasAuthorization = () => {
170210 return false ;
171211};
172212
173- const hasErrors = () => {
174- if (selectedRole .value === " " ) {
175- setSelectedRoleError (" Select a role" );
176- return true ;
177- }
178-
179- if (email .value === " " ) {
180- setEmailError (" This field is required" );
181- return true ;
182- }
183-
184- return false ;
185- };
186-
187213const getAvatar = (index : number ) => multiavatar (` ${Math .floor (Math .random () * (Number .MAX_SAFE_INTEGER - index + 1 )) + index } ` );
188214
189215const resetFields = () => {
@@ -194,51 +220,82 @@ const resetFields = () => {
194220const close = () => {
195221 resetFields ();
196222 dialog .value = false ;
223+ formWindow .value = " form-1" ;
197224};
198225
199226const update = () => {
200227 emit (" update" );
201228 close ();
202229};
203230
204- const addMember = async () => {
205- if (! hasErrors ()) {
206- try {
207- await store .dispatch (" namespaces/addUser" , {
208- email: email .value ,
209- tenant_id: store .getters [" auth/tenant" ],
210- role: selectedRole .value ,
211- });
212-
213- store .dispatch (
214- " snackbar/showSnackbarSuccessAction" ,
215- INotificationsSuccess .namespaceNewMember ,
216- );
217- update ();
218- resetFields ();
219- } catch (error : unknown ) {
220- store .dispatch (
221- " snackbar/showSnackbarErrorAction" ,
222- INotificationsError .namespaceNewMember ,
223- );
224- if (axios .isAxiosError (error )) {
225- const axiosError = error as AxiosError ;
226- switch (axiosError .response ?.status ) {
227- case 409 :
228- setEmailError (" This user is already a member of this namespace." );
229- break ;
230- case 404 :
231- setEmailError (" This user does not exist." );
232- break ;
233- default :
234- handleError (error );
235- }
236- }
231+ const copyText = (value : string | undefined ) => {
232+ if (value ) {
233+ navigator .clipboard .writeText (value );
234+ store .dispatch (" snackbar/showSnackbarCopy" , INotificationsCopy .invitationLink );
235+ }
236+ };
237+
238+ const handleInviteError = (error : unknown ) => {
239+ store .dispatch (
240+ " snackbar/showSnackbarErrorAction" ,
241+ INotificationsError .namespaceNewMember ,
242+ );
243+
244+ if (axios .isAxiosError (error )) {
245+ const axiosError = error as AxiosError ;
246+ switch (axiosError .response ?.status ) {
247+ case 409 :
248+ setEmailError (" This user is already a member of this namespace." );
249+ break ;
250+ case 404 :
251+ setEmailError (" This user does not exist." );
252+ break ;
253+ default :
254+ handleError (error );
237255 }
238256 }
239257};
240258
241- defineExpose ({ emailError });
259+ const generateLinkInvite = async () => {
260+ try {
261+ await store .dispatch (" namespaces/generateInvitationLink" , {
262+ email: email .value ,
263+ tenant_id: store .getters [" auth/tenant" ],
264+ role: selectedRole .value ,
265+ });
266+
267+ store .dispatch (
268+ " snackbar/showSnackbarSuccessAction" ,
269+ INotificationsSuccess .namespaceNewMember ,
270+ );
271+
272+ formWindow .value = " form-2" ;
273+ } catch (error ) {
274+ handleInviteError (error );
275+ }
276+ };
277+
278+ const sendEmailInvite = async () => {
279+ try {
280+ await store .dispatch (" namespaces/sendEmailInvitation" , {
281+ email: email .value ,
282+ tenant_id: store .getters [" auth/tenant" ],
283+ role: selectedRole .value ,
284+ });
285+
286+ store .dispatch (
287+ " snackbar/showSnackbarSuccessAction" ,
288+ INotificationsSuccess .namespaceNewMember ,
289+ );
290+
291+ update ();
292+ resetFields ();
293+ } catch (error ) {
294+ handleInviteError (error );
295+ }
296+ };
297+
298+ defineExpose ({ emailError , formWindow , invitationLink });
242299 </script >
243300
244301<style lang="scss" scoped>
0 commit comments