Repository: levante-framework/levante-dashboard
Pull request #348: Ts convert modal and auth
Author: login: digital-pro
Created: 2025-05-22T20:52:38Z | Merged: 2025-05-27T18:12:47Z

## Proposed changes

<!--
Describe your changes here.

If it fixes a bug or resolves a feature request, be sure to link to that issue.

If appropriate, include images of the expected behavior or user experience.
You can drag and drop images into this text box.
-->

## Types of changes

What types of changes does this pull request introduce?

<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Refactor (non-breaking change that does not add functionality but makes code cleaner or more efficient)
- [ ] Tests (new or updated tests)
- [ ] Styles (changes to code styling)
- [ ] CI (continuous integration changes)
- [ ] Other (please describe below)

## Additional Notes
<!-- List any additional information that may be helpful to review or know about this change -->



Diff:
diff --git a/src/components/auth/SignIn.vue b/src/components/auth/SignIn.vue
index 29e9e9d751..ff20356741 100644
--- a/src/components/auth/SignIn.vue
+++ b/src/components/auth/SignIn.vue
@@ -156,32 +156,47 @@
   </RoarModal>
 </template>
 
-<script setup>
-import { reactive, ref, watch } from "vue";
-import { storeToRefs } from "pinia";
-import { required, requiredUnless } from "@vuelidate/validators";
-import { useVuelidate } from "@vuelidate/core";
-import _debounce from "lodash/debounce";
-import PvButton from "primevue/button";
-import PvDivider from "primevue/divider";
-import PvInputText from "primevue/inputtext";
-import PvPassword from "primevue/password";
-import PvSkeleton from "primevue/skeleton";
-import { useAuthStore } from "@/store/auth";
-import RoarModal from "../modals/RoarModal.vue";
+<script setup lang="ts">
+import { reactive, ref, watch } from 'vue';
+import { storeToRefs } from 'pinia';
+import { required, requiredUnless } from '@vuelidate/validators';
+import { useVuelidate } from '@vuelidate/core';
+import _debounce from 'lodash/debounce';
+import PvButton from 'primevue/button';
+import PvDivider from 'primevue/divider';
+import PvInputText from 'primevue/inputtext';
+import PvPassword from 'primevue/password';
+import PvSkeleton from 'primevue/skeleton';
+import { useAuthStore } from '@/store/auth';
+import RoarModal from '../modals/RoarModal.vue';
+
+interface SignInState {
+  email: string;
+  password: string;
+  useLink: boolean;
+  usePassword: boolean;
+}
+
+interface Props {
+  invalid?: boolean;
+}
+
+interface Emits {
+  (e: 'submit', state: SignInState): void;
+  (e: 'update:email', email: string): void;
+}
 
 const authStore = useAuthStore();
 const { roarfirekit } = storeToRefs(authStore);
 
-const emit = defineEmits(["submit", "update:email"]);
-// eslint-disable-next-line no-unused-vars
-const props = defineProps({
-  invalid: { type: Boolean, required: false, default: false },
+const emit = defineEmits<Emits>();
+const props = withDefaults(defineProps<Props>(), {
+  invalid: false,
 });
 
-const state = reactive({
-  email: "",
-  password: "",
+const state = reactive<SignInState>({
+  email: '',
+  password: '',
   useLink: false,
   usePassword: true,
 });
@@ -192,12 +207,12 @@ const rules = {
     requiredIf: requiredUnless(() => state.useLink),
   },
 };
-const submitted = ref(false);
+const submitted = ref<boolean>(false);
 const v$ = useVuelidate(rules, state);
-const capsLockEnabled = ref(false);
-const forgotPasswordModalOpen = ref(false);
+const capsLockEnabled = ref<boolean>(false);
+const forgotPasswordModalOpen = ref<boolean>(false);
 
-const handleFormSubmit = (isFormValid) => {
+const handleFormSubmit = (isFormValid: boolean): void => {
   submitted.value = true;
   if (!isFormValid) {
     return;
@@ -205,18 +220,18 @@ const handleFormSubmit = (isFormValid) => {
   emit("submit", state);
 };
 
-const isValidEmail = (email) => {
+const isValidEmail = (email: string): boolean => {
   var re =
     /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
   return re.test(email);
 };
 
-const evaluatingEmail = ref(false);
-const allowPassword = ref(true);
-const allowLink = ref(true);
+const evaluatingEmail = ref<boolean>(false);
+const allowPassword = ref<boolean>(true);
+const allowLink = ref<boolean>(true);
 
 const validateRoarEmail = _debounce(
-  async (email) => {
+  async (email: string): Promise<void> => {
     // Don't evaluate empty emails or obviously invalid ones
     if (!email || !email.includes("@")) {
       evaluatingEmail.value = false;
@@ -249,7 +264,7 @@ const validateRoarEmail = _debounce(
   { maxWait: 1000 },
 );
 
-function checkForCapsLock(e) {
+function checkForCapsLock(e: Event): void {
   // Make sure the event is a keyboard event.
   // Using password autofill will trigger a regular
   //   event which does not have a getModifierState method.
@@ -258,26 +273,26 @@ function checkForCapsLock(e) {
   }
 }
 
-const forgotEmail = ref("");
-function handleForgotPassword() {
-  console.log("Opening modal for forgot password");
+const forgotEmail = ref<string>('');
+function handleForgotPassword(): void {
+  console.log('Opening modal for forgot password');
   forgotPasswordModalOpen.value = true;
   // e.preventDefault();
 }
-function closeForgotPasswordModal() {
+function closeForgotPasswordModal(): void {
   forgotPasswordModalOpen.value = false;
   forgotEmail.value = "";
 }
-function sendResetEmail() {
-  console.log("Submitting forgot password with email", forgotEmail.value);
+function sendResetEmail(): void {
+  console.log('Submitting forgot password with email', forgotEmail.value);
   roarfirekit.value.sendPasswordResetEmail(forgotEmail.value);
   closeForgotPasswordModal();
 }
 
 watch(
   () => state.email,
-  async (email) => {
-    emit("update:email", email);
+  async (email: string) => {
+    emit('update:email', email);
     if (isValidEmail(email)) {
       evaluatingEmail.value = true;
       validateRoarEmail(email);
diff --git a/src/components/modals/EditUsers.vue b/src/components/modals/EditUsers.vue
index ff0566aaa9..ee89008356 100644
--- a/src/components/modals/EditUsers.vue
+++ b/src/components/modals/EditUsers.vue
@@ -236,47 +236,81 @@
     <!-- </template> -->
   </PvDialog>
 </template>
-<script setup>
-import { watch, ref, onMounted, computed } from "vue";
-import { useToast } from "primevue/usetoast";
-import { storeToRefs } from "pinia";
-import { useAuthStore } from "@/store/auth";
-import PvAutoComplete from "primevue/autocomplete";
-import PvButton from "primevue/button";
-import PvDatePicker from "primevue/datepicker";
-import PvCheckbox from "primevue/checkbox";
-import PvDialog from "primevue/dialog";
-import PvSelect from "primevue/select";
-import PvInputText from "primevue/inputtext";
-import useUserClaimsQuery from "@/composables/queries/useUserClaimsQuery";
-
-const props = defineProps({
-  userData: {
-    type: Object,
-    required: true,
-  },
-  isEnabled: {
-    type: Boolean,
-    required: true,
-    default: false,
-  },
-  userType: {
-    type: String,
-    default: "student",
-  },
+<script setup lang="ts">
+import { watch, ref, onMounted, computed } from 'vue';
+import { useToast } from 'primevue/usetoast';
+import { storeToRefs } from 'pinia';
+import { useAuthStore } from '@/store/auth';
+import PvAutoComplete from 'primevue/autocomplete';
+import PvButton from 'primevue/button';
+import PvDatePicker from 'primevue/datepicker';
+import PvCheckbox from 'primevue/checkbox';
+import PvDialog from 'primevue/dialog';
+import PvSelect from 'primevue/select';
+import PvInputText from 'primevue/inputtext';
+import useUserClaimsQuery from '@/composables/queries/useUserClaimsQuery';
+
+interface StudentData {
+  dob: Date | null;
+  grade: string;
+  gender: string;
+  race: string[];
+  hispanic_ethnicity: boolean;
+  ell_status: boolean;
+  frl_status: boolean;
+  iep_status: boolean;
+}
+
+interface UserName {
+  first: string | null;
+  middle: string | null;
+  last: string | null;
+}
+
+interface UserData {
+  id?: string;
+  name: UserName;
+  studentData: StudentData;
+  testData: boolean;
+  demoData: boolean;
+  userType: string | null;
+}
+
+interface Props {
+  userData: UserData;
+  isEnabled: boolean;
+  userType?: string;
+}
+
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+interface DropdownOption {
+  label: string;
+  value: boolean;
+}
+
+interface AutoCompleteEvent {
+  query: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,
+  userType: 'student',
 });
 
 // Handle modal opening / closing
-const emit = defineEmits(["modalClosed"]);
+const emit = defineEmits<Emits>();
 
 const authStore = useAuthStore();
 const { roarfirekit } = storeToRefs(authStore);
-const initialized = ref(false);
+const initialized = ref<boolean>(false);
 
 watch(
   () => props.isEnabled,
-  (isEnabled) => {
-    console.log("isEnabled from watcher", isEnabled);
+  (isEnabled: boolean) => {
+    console.log('isEnabled from watcher', isEnabled);
     if (isEnabled) {
       localUserData.value = setupUserData();
       console.log("userData", localUserData.value);
@@ -288,17 +322,17 @@ watch(
 const toast = useToast();
 
 // Handle Modal Actions
-const closeModal = () => {
-  errorMessage.value = "";
-  newPassword.value = "";
-  confirmPassword.value = "";
+const closeModal = (): void => {
+  errorMessage.value = '';
+  newPassword.value = '';
+  confirmPassword.value = '';
   showPassword.value = false;
   isOpen.value = false;
   emit("modalClosed");
 };
 
-const onAccept = async () => {
-  errorMessage.value = "";
+const onAccept = async (): Promise<void> => {
+  errorMessage.value = '';
   isSubmitting.value = true;
   await roarfirekit.value
     .updateUserData(props.userData.id, { ...localUserData.value })
@@ -312,14 +346,14 @@ const onAccept = async () => {
         life: 3000,
       });
     })
-    .catch((error) => {
-      console.log("Error occurred during submission:", error);
+    .catch((error: any) => {
+      console.log('Error occurred during submission:', error);
       errorMessage.value = error.message;
       isSubmitting.value = false;
     });
 };
 
-const updatePassword = async () => {
+const updatePassword = async (): Promise<void> => {
   if (newPassword.value.length < 6) {
     errorMessage.value = "Password must be at least 6 characters";
     return;
@@ -342,39 +376,37 @@ const updatePassword = async () => {
         life: 3000,
       });
     })
-    .catch((error) => {
-      console.log("Error occurred during submission:", error);
+    .catch((error: any) => {
+      console.log('Error occurred during submission:', error);
       errorMessage.value = error.message;
       isSubmitting.value = false;
     });
 };
 
-const onReject = () => {
+const onReject = (): void => {
   closeModal();
 };
 
 // Utility functions
-const isOpen = ref(false);
-const localUserData = ref({});
-const newPassword = ref("");
-const confirmPassword = ref("");
-const isSubmitting = ref(false);
-const errorMessage = ref("");
-const showPassword = ref(false);
-
-const setupUserData = () => {
-  let user = {
+const isOpen = ref<boolean>(false);
+const localUserData = ref<UserData>({} as UserData);
+const newPassword = ref<string>('');
+const confirmPassword = ref<string>('');
+const isSubmitting = ref<boolean>(false);
+const errorMessage = ref<string>('');
+const showPassword = ref<boolean>(false);
+
+const setupUserData = (): UserData => {
+  let user: UserData = {
     name: {
       first: props.userData?.name?.first || null,
       middle: props.userData?.name?.middle || null,
       last: props.userData?.name?.last || null,
     },
     studentData: {
-      dob: !isNaN(new Date(props.userData?.studentData?.dob))
-        ? new Date(props.userData?.studentData?.dob)
-        : null,
-      grade: props.userData?.studentData?.grade || "",
-      gender: props.userData?.studentData?.gender || "",
+      dob: !isNaN(new Date(props.userData?.studentData?.dob).getTime()) ? new Date(props.userData?.studentData?.dob) : null,
+      grade: props.userData?.studentData?.grade || '',
+      gender: props.userData?.studentData?.gender || '',
       race: props.userData?.studentData?.race || [],
       hispanic_ethnicity:
         props.userData?.studentData?.hispanic_ethnicity || false,
@@ -389,27 +421,27 @@ const setupUserData = () => {
   return user;
 };
 
-const localUserType = computed(() => {
+const localUserType = computed<string | null>(() => {
   if (props.userData?.userType) return props.userData.userType;
   if (props.userType) return props.userType;
   return null;
 });
 
-const races = [
-  "american Indian or alaska Native",
-  "asian",
-  "black or african American",
-  "native hawaiian or other pacific islander",
-  "white",
+const races: string[] = [
+  'american Indian or alaska Native',
+  'asian',
+  'black or african American',
+  'native hawaiian or other pacific islander',
+  'white',
 ];
 
-const raceOptions = ref([...races]);
-const binaryDropdownOptions = [
-  { label: "Yes", value: true },
-  { label: "No", value: false },
+const raceOptions = ref<string[]>([...races]);
+const binaryDropdownOptions: DropdownOption[] = [
+  { label: 'Yes', value: true },
+  { label: 'No', value: false },
 ];
 
-const searchRaces = (event) => {
+const searchRaces = (event: AutoCompleteEvent): void => {
   const query = event.query.toLowerCase();
 
   let filteredOptions = races.filter((opt) =>
@@ -425,13 +457,13 @@ const searchRaces = (event) => {
   raceOptions.value = filteredOptions;
 };
 
-let unsubscribe;
-const init = () => {
+let unsubscribe: (() => void) | undefined;
+const init = (): void => {
   if (unsubscribe) unsubscribe();
   initialized.value = true;
 };
 
-unsubscribe = authStore.$subscribe(async (mutation, state) => {
+unsubscribe = authStore.$subscribe(async (mutation: any, state: any) => {
   if (state.roarfirekit.restConfig) init();
 });
 
@@ -444,7 +476,7 @@ const { data: userClaims } = useUserClaimsQuery({
   enabled: initialized,
 });
 
-const isSuperAdmin = computed(() => {
+const isSuperAdmin = computed<boolean>(() => {
   if (userClaims.value?.claims?.super_admin) return true;
   return false;
 });
diff --git a/src/components/modals/RoarModal.vue b/src/components/modals/RoarModal.vue
index 0e175125e7..1ecbba6994 100644
--- a/src/components/modals/RoarModal.vue
+++ b/src/components/modals/RoarModal.vue
@@ -32,51 +32,43 @@
     <!-- </template> -->
   </PvDialog>
 </template>
-<script setup>
-import { watch, ref, onMounted } from "vue";
-import { storeToRefs } from "pinia";
-import PvDialog from "primevue/dialog";
-import { useAuthStore } from "@/store/auth";
+<script setup lang="ts">
+import { watch, ref, onMounted } from 'vue';
+import { storeToRefs } from 'pinia';
+import PvDialog from 'primevue/dialog';
+import { useAuthStore } from '@/store/auth';
 
-const props = defineProps({
-  isEnabled: {
-    type: Boolean,
-    required: true,
-    default: false,
-  },
-  title: {
-    type: String,
-    required: true,
-    default: "",
-  },
-  subtitle: {
-    type: String,
-    required: true,
-    default: "",
-  },
-  icon: {
-    type: String,
-    required: false,
-    default: "pi-pencil",
-  },
-  small: {
-    type: Boolean,
-    required: false,
-    default: false,
-  },
+interface Props {
+  isEnabled: boolean;
+  title: string;
+  subtitle: string;
+  icon?: string;
+  small?: boolean;
+}
+
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,
+  title: '',
+  subtitle: '',
+  icon: 'pi-pencil',
+  small: false,
 });
 
 // Handle modal opening / closing
-const emit = defineEmits(["modalClosed"]);
+const emit = defineEmits<Emits>();
 
 const authStore = useAuthStore();
 const { roarfirekit } = storeToRefs(authStore);
-const initialized = ref(false);
+const initialized = ref<boolean>(false);
 
 watch(
   () => props.isEnabled,
-  (isEnabled) => {
-    console.log("isEnabled from watcher", isEnabled);
+  (isEnabled: boolean) => {
+    console.log('isEnabled from watcher', isEnabled);
     if (isEnabled) {
       isOpen.value = true;
     } else if (!isEnabled) {
@@ -85,15 +77,15 @@ watch(
   },
 );
 
-const isOpen = ref(false);
+const isOpen = ref<boolean>(false);
 
-let unsubscribe;
-const init = () => {
+let unsubscribe: (() => void) | undefined;
+const init = (): void => {
   if (unsubscribe) unsubscribe();
   initialized.value = true;
 };
 
-unsubscribe = authStore.$subscribe(async (mutation, state) => {
+unsubscribe = authStore.$subscribe(async (mutation: any, state: any) => {
   if (state.roarfirekit?.restConfig) init();
 });
 


Reviews:
user: login: Zio-4
body: I'm not sure what's up with the AuthEmailLink component, but it's really out of date. Have you pulled in the latest changes to this branch? 
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T18:43:36Z
user: login: digital-pro
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T19:27:04Z
user: login: digital-pro
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T19:31:13Z
user: login: digital-pro
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T19:44:11Z
user: login: digital-pro
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T19:45:43Z
user: login: Zio-4
body: Looks good.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-27T18:08:56Z

Review comments:
diff_hunk: @@ -12,13 +12,18 @@
       />
     </div>
   </div>
+  <transition-group name="p-message" tag="div">
path: src/components/auth/AuthEmailLink.vue
user: login: Zio-4
body: Not sure why this is here, it's old code I removed.
created_at: 2025-05-24T18:38:01Z
author_association: COLLABORATOR
diff_hunk: @@ -27,15 +32,58 @@ import { logger } from "@/logger";
 const router = useRouter();
 const authStore = useAuthStore();
 const { roarfirekit, uid } = storeToRefs(authStore);
+const success = ref(false);
 const isError = ref(false);
+const messages = ref([]);
+
+const addMessages = (errorCode) => {
path: src/components/auth/AuthEmailLink.vue
user: login: Zio-4
body: Old code
created_at: 2025-05-24T18:38:09Z
author_association: COLLABORATOR
diff_hunk: @@ -27,15 +32,58 @@ import { logger } from "@/logger";
 const router = useRouter();
 const authStore = useAuthStore();
 const { roarfirekit, uid } = storeToRefs(authStore);
+const success = ref(false);
 const isError = ref(false);
+const messages = ref([]);
+
+const addMessages = (errorCode) => {
+  messages.value.push({
+    id: Date.now(),
+    severity: 'error',
+    content: `Error: ${errorCode}`
+  });
+};
+
+// Define unsubscribe function first
+const unsubscribe = authStore.$subscribe(async (mutation, state) => {
+  if (
+    state.roarfirekit.isSignInWithEmailLink &&
+    state.roarfirekit.signInWithEmailLink
+  ) {
+    if (!roarfirekit.value.isSignInWithEmailLink(window.location.href)) {
+      router.replace({ name: "Home" });
+    }
+  }
+});
+
+authStore.$subscribe(async () => {
+  if (authStore.roarUid) {
+    const userData = await fetchDocById('users', authStore.roarUid);
+    const userClaims = await fetchDocById('userClaims', authStore.uid);
+    authStore.setUserData(userData);
+    authStore.setUserClaims(userClaims);
+    success.value = true;
+    router.push({ name: 'Home' });
+  }
+});
 
 const loginFromEmailLink = async (email) => {
   unsubscribe();
   const emailLink = window.location.href;
   await authStore
     .signInWithEmailLink({ email, emailLink })
+    .catch((error) => {
path: src/components/auth/AuthEmailLink.vue
user: login: Zio-4
body: old code
created_at: 2025-05-24T18:38:36Z
author_association: COLLABORATOR
diff_hunk: @@ -236,47 +236,81 @@
     <!-- </template> -->
   </PvDialog>
 </template>
-<script setup>
-import { watch, ref, onMounted, computed } from "vue";
-import { useToast } from "primevue/usetoast";
-import { storeToRefs } from "pinia";
-import { useAuthStore } from "@/store/auth";
-import PvAutoComplete from "primevue/autocomplete";
-import PvButton from "primevue/button";
-import PvDatePicker from "primevue/datepicker";
-import PvCheckbox from "primevue/checkbox";
-import PvDialog from "primevue/dialog";
-import PvSelect from "primevue/select";
-import PvInputText from "primevue/inputtext";
-import useUserClaimsQuery from "@/composables/queries/useUserClaimsQuery";
-
-const props = defineProps({
-  userData: {
-    type: Object,
-    required: true,
-  },
-  isEnabled: {
-    type: Boolean,
-    required: true,
-    default: false,
-  },
-  userType: {
-    type: String,
-    default: "student",
-  },
+<script setup lang="ts">
+import { watch, ref, onMounted, computed } from 'vue';
+import { useToast } from 'primevue/usetoast';
+import { storeToRefs } from 'pinia';
+import { useAuthStore } from '@/store/auth';
+import PvAutoComplete from 'primevue/autocomplete';
+import PvButton from 'primevue/button';
+import PvDatePicker from 'primevue/datepicker';
+import PvCheckbox from 'primevue/checkbox';
+import PvDialog from 'primevue/dialog';
+import PvSelect from 'primevue/select';
+import PvInputText from 'primevue/inputtext';
+import useUserClaimsQuery from '@/composables/queries/useUserClaimsQuery';
+
+interface StudentData {
+  dob: Date | null;
+  grade: string;
+  gender: string;
+  race: string[];
+  hispanic_ethnicity: boolean;
+  ell_status: boolean;
+  frl_status: boolean;
+  iep_status: boolean;
+}
+
+interface UserName {
+  first: string | null;
+  middle: string | null;
+  last: string | null;
+}
+
+interface UserData {
+  id?: string;
+  name: UserName;
+  studentData: StudentData;
+  testData: boolean;
+  demoData: boolean;
+  userType: string | null;
+}
+
+interface Props {
+  userData: UserData;
+  isEnabled: boolean;
+  userType?: string;
+}
+
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+interface DropdownOption {
+  label: string;
+  value: boolean;
+}
+
+interface AutoCompleteEvent {
+  query: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,
path: src/components/modals/EditUsers.vue
user: login: Zio-4
body: userData is missing 
created_at: 2025-05-24T18:40:43Z
author_association: COLLABORATOR
diff_hunk: @@ -236,47 +236,81 @@
     <!-- </template> -->
   </PvDialog>
 </template>
-<script setup>
-import { watch, ref, onMounted, computed } from "vue";
-import { useToast } from "primevue/usetoast";
-import { storeToRefs } from "pinia";
-import { useAuthStore } from "@/store/auth";
-import PvAutoComplete from "primevue/autocomplete";
-import PvButton from "primevue/button";
-import PvDatePicker from "primevue/datepicker";
-import PvCheckbox from "primevue/checkbox";
-import PvDialog from "primevue/dialog";
-import PvSelect from "primevue/select";
-import PvInputText from "primevue/inputtext";
-import useUserClaimsQuery from "@/composables/queries/useUserClaimsQuery";
-
-const props = defineProps({
-  userData: {
-    type: Object,
-    required: true,
-  },
-  isEnabled: {
-    type: Boolean,
-    required: true,
-    default: false,
-  },
-  userType: {
-    type: String,
-    default: "student",
-  },
+<script setup lang="ts">
+import { watch, ref, onMounted, computed } from 'vue';
+import { useToast } from 'primevue/usetoast';
+import { storeToRefs } from 'pinia';
+import { useAuthStore } from '@/store/auth';
+import PvAutoComplete from 'primevue/autocomplete';
+import PvButton from 'primevue/button';
+import PvDatePicker from 'primevue/datepicker';
+import PvCheckbox from 'primevue/checkbox';
+import PvDialog from 'primevue/dialog';
+import PvSelect from 'primevue/select';
+import PvInputText from 'primevue/inputtext';
+import useUserClaimsQuery from '@/composables/queries/useUserClaimsQuery';
+
+interface StudentData {
+  dob: Date | null;
+  grade: string;
+  gender: string;
+  race: string[];
+  hispanic_ethnicity: boolean;
+  ell_status: boolean;
+  frl_status: boolean;
+  iep_status: boolean;
+}
+
+interface UserName {
+  first: string | null;
+  middle: string | null;
+  last: string | null;
+}
+
+interface UserData {
+  id?: string;
+  name: UserName;
+  studentData: StudentData;
+  testData: boolean;
+  demoData: boolean;
+  userType: string | null;
+}
+
+interface Props {
+  userData: UserData;
+  isEnabled: boolean;
+  userType?: string;
+}
+
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+interface DropdownOption {
+  label: string;
+  value: boolean;
+}
+
+interface AutoCompleteEvent {
+  query: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,
+  userType: 'student',
 });
 
 // Handle modal opening / closing
-const emit = defineEmits(["modalClosed"]);
+const emit = defineEmits<Emits>();
path: src/components/modals/EditUsers.vue
user: login: Zio-4
body: The things that's emitted, "modalClosed", is missing
created_at: 2025-05-24T18:41:37Z
author_association: COLLABORATOR
diff_hunk: @@ -32,51 +32,43 @@
     <!-- </template> -->
   </PvDialog>
 </template>
-<script setup>
-import { watch, ref, onMounted } from "vue";
-import { storeToRefs } from "pinia";
-import PvDialog from "primevue/dialog";
-import { useAuthStore } from "@/store/auth";
+<script setup lang="ts">
+import { watch, ref, onMounted } from 'vue';
+import { storeToRefs } from 'pinia';
+import PvDialog from 'primevue/dialog';
+import { useAuthStore } from '@/store/auth';
 
-const props = defineProps({
-  isEnabled: {
-    type: Boolean,
-    required: true,
-    default: false,
-  },
-  title: {
-    type: String,
-    required: true,
-    default: "",
-  },
-  subtitle: {
-    type: String,
-    required: true,
-    default: "",
-  },
-  icon: {
-    type: String,
-    required: false,
-    default: "pi-pencil",
-  },
-  small: {
-    type: Boolean,
-    required: false,
-    default: false,
-  },
+interface Props {
+  isEnabled: boolean;
+  title: string;
+  subtitle: string;
+  icon?: string;
+  small?: boolean;
+}
+
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,
+  title: '',
+  subtitle: '',
+  icon: 'pi-pencil',
+  small: false,
 });
 
 // Handle modal opening / closing
-const emit = defineEmits(["modalClosed"]);
+const emit = defineEmits<Emits>();
path: src/components/modals/RoarModal.vue
user: login: Zio-4
body: Missing the emit
created_at: 2025-05-24T18:42:57Z
author_association: COLLABORATOR
diff_hunk: @@ -12,13 +12,18 @@
       />
     </div>
   </div>
+  <transition-group name="p-message" tag="div">
path: src/components/auth/AuthEmailLink.vue
user: login: digital-pro
body: This file should now be removed from the PR. I can work on it again sometime in the future
created_at: 2025-05-24T19:27:04Z
author_association: COLLABORATOR
diff_hunk: @@ -236,47 +236,81 @@
     <!-- </template> -->
   </PvDialog>
 </template>
-<script setup>
-import { watch, ref, onMounted, computed } from "vue";
-import { useToast } from "primevue/usetoast";
-import { storeToRefs } from "pinia";
-import { useAuthStore } from "@/store/auth";
-import PvAutoComplete from "primevue/autocomplete";
-import PvButton from "primevue/button";
-import PvDatePicker from "primevue/datepicker";
-import PvCheckbox from "primevue/checkbox";
-import PvDialog from "primevue/dialog";
-import PvSelect from "primevue/select";
-import PvInputText from "primevue/inputtext";
-import useUserClaimsQuery from "@/composables/queries/useUserClaimsQuery";
-
-const props = defineProps({
-  userData: {
-    type: Object,
-    required: true,
-  },
-  isEnabled: {
-    type: Boolean,
-    required: true,
-    default: false,
-  },
-  userType: {
-    type: String,
-    default: "student",
-  },
+<script setup lang="ts">
+import { watch, ref, onMounted, computed } from 'vue';
+import { useToast } from 'primevue/usetoast';
+import { storeToRefs } from 'pinia';
+import { useAuthStore } from '@/store/auth';
+import PvAutoComplete from 'primevue/autocomplete';
+import PvButton from 'primevue/button';
+import PvDatePicker from 'primevue/datepicker';
+import PvCheckbox from 'primevue/checkbox';
+import PvDialog from 'primevue/dialog';
+import PvSelect from 'primevue/select';
+import PvInputText from 'primevue/inputtext';
+import useUserClaimsQuery from '@/composables/queries/useUserClaimsQuery';
+
+interface StudentData {
+  dob: Date | null;
+  grade: string;
+  gender: string;
+  race: string[];
+  hispanic_ethnicity: boolean;
+  ell_status: boolean;
+  frl_status: boolean;
+  iep_status: boolean;
+}
+
+interface UserName {
+  first: string | null;
+  middle: string | null;
+  last: string | null;
+}
+
+interface UserData {
+  id?: string;
+  name: UserName;
+  studentData: StudentData;
+  testData: boolean;
+  demoData: boolean;
+  userType: string | null;
+}
+
+interface Props {
+  userData: UserData;
+  isEnabled: boolean;
+  userType?: string;
+}
+
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+interface DropdownOption {
+  label: string;
+  value: boolean;
+}
+
+interface AutoCompleteEvent {
+  query: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,
path: src/components/modals/EditUsers.vue
user: login: digital-pro
body: There is a userData interface which rolls up into props. I'm not sure what else is needed?
created_at: 2025-05-24T19:31:13Z
author_association: COLLABORATOR
diff_hunk: @@ -236,47 +236,81 @@
     <!-- </template> -->
   </PvDialog>
 </template>
-<script setup>
-import { watch, ref, onMounted, computed } from "vue";
-import { useToast } from "primevue/usetoast";
-import { storeToRefs } from "pinia";
-import { useAuthStore } from "@/store/auth";
-import PvAutoComplete from "primevue/autocomplete";
-import PvButton from "primevue/button";
-import PvDatePicker from "primevue/datepicker";
-import PvCheckbox from "primevue/checkbox";
-import PvDialog from "primevue/dialog";
-import PvSelect from "primevue/select";
-import PvInputText from "primevue/inputtext";
-import useUserClaimsQuery from "@/composables/queries/useUserClaimsQuery";
-
-const props = defineProps({
-  userData: {
-    type: Object,
-    required: true,
-  },
-  isEnabled: {
-    type: Boolean,
-    required: true,
-    default: false,
-  },
-  userType: {
-    type: String,
-    default: "student",
-  },
+<script setup lang="ts">
+import { watch, ref, onMounted, computed } from 'vue';
+import { useToast } from 'primevue/usetoast';
+import { storeToRefs } from 'pinia';
+import { useAuthStore } from '@/store/auth';
+import PvAutoComplete from 'primevue/autocomplete';
+import PvButton from 'primevue/button';
+import PvDatePicker from 'primevue/datepicker';
+import PvCheckbox from 'primevue/checkbox';
+import PvDialog from 'primevue/dialog';
+import PvSelect from 'primevue/select';
+import PvInputText from 'primevue/inputtext';
+import useUserClaimsQuery from '@/composables/queries/useUserClaimsQuery';
+
+interface StudentData {
+  dob: Date | null;
+  grade: string;
+  gender: string;
+  race: string[];
+  hispanic_ethnicity: boolean;
+  ell_status: boolean;
+  frl_status: boolean;
+  iep_status: boolean;
+}
+
+interface UserName {
+  first: string | null;
+  middle: string | null;
+  last: string | null;
+}
+
+interface UserData {
+  id?: string;
+  name: UserName;
+  studentData: StudentData;
+  testData: boolean;
+  demoData: boolean;
+  userType: string | null;
+}
+
+interface Props {
+  userData: UserData;
+  isEnabled: boolean;
+  userType?: string;
+}
+
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+interface DropdownOption {
+  label: string;
+  value: boolean;
+}
+
+interface AutoCompleteEvent {
+  query: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,
+  userType: 'student',
 });
 
 // Handle modal opening / closing
-const emit = defineEmits(["modalClosed"]);
+const emit = defineEmits<Emits>();
path: src/components/modals/EditUsers.vue
user: login: digital-pro
body: Cursor claims this is okay because the closeModal function calls the proper emit
created_at: 2025-05-24T19:44:10Z
author_association: COLLABORATOR
diff_hunk: @@ -32,51 +32,43 @@
     <!-- </template> -->
   </PvDialog>
 </template>
-<script setup>
-import { watch, ref, onMounted } from "vue";
-import { storeToRefs } from "pinia";
-import PvDialog from "primevue/dialog";
-import { useAuthStore } from "@/store/auth";
+<script setup lang="ts">
+import { watch, ref, onMounted } from 'vue';
+import { storeToRefs } from 'pinia';
+import PvDialog from 'primevue/dialog';
+import { useAuthStore } from '@/store/auth';
 
-const props = defineProps({
-  isEnabled: {
-    type: Boolean,
-    required: true,
-    default: false,
-  },
-  title: {
-    type: String,
-    required: true,
-    default: "",
-  },
-  subtitle: {
-    type: String,
-    required: true,
-    default: "",
-  },
-  icon: {
-    type: String,
-    required: false,
-    default: "pi-pencil",
-  },
-  small: {
-    type: Boolean,
-    required: false,
-    default: false,
-  },
+interface Props {
+  isEnabled: boolean;
+  title: string;
+  subtitle: string;
+  icon?: string;
+  small?: boolean;
+}
+
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,
+  title: '',
+  subtitle: '',
+  icon: 'pi-pencil',
+  small: false,
 });
 
 // Handle modal opening / closing
-const emit = defineEmits(["modalClosed"]);
+const emit = defineEmits<Emits>();
path: src/components/modals/RoarModal.vue
user: login: digital-pro
body: Same thought as above
created_at: 2025-05-24T19:45:42Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-22T20:58:13Z
body: Visit the preview URL for this PR (updated for commit da04823):

[https://hs-levante-admin-dev--pr348-ts-modal-tcp126q8.web.app](https://hs-levante-admin-dev--pr348-ts-modal-tcp126q8.web.app)

<sub>(expires Sat, 31 May 2025 19:31:05 GMT)</sub>

<sub>🔥 via [Firebase Hosting GitHub Action](https://github.com/marketplace/actions/deploy-to-firebase-hosting) 🌎</sub>

<sub>Sign: 7889bff1da3bcc333d7422b9fc863c65b3962be7</sub>
author_association: NONE
user: login: Zio-4
created_at: 2025-05-24T03:16:47Z
body: Lot of conflicts here, and I see some old code. I'll wait until that's fixed to review. 

author_association: COLLABORATOR
user: login: digital-pro
created_at: 2025-05-24T04:51:02Z
body: Hmm, my conflict resolutions don't seem to be sticking. Digging in.
author_association: COLLABORATOR
user: login: digital-pro
created_at: 2025-05-24T04:58:43Z
body: Okay, I merged them by hand. A bunch were the ts code having single quotes but main having changed to double quotes?
author_association: COLLABORATOR
user: login: digital-pro
created_at: 2025-05-24T05:37:28Z
body: Okay, conflicts should be resolved & tests are passing
author_association: COLLABORATOR
user: login: digital-pro
created_at: 2025-05-24T19:17:10Z
body: I actuallly gave up on AuthEmailLink & tried to revert back to whatever is in main (since there were a bunch of new conflicts). Obviously I didn’t do it right

 

From: Philip Ziolkowski ***@***.***> 
Sent: Saturday, May 24, 2025 11:44 AM
To: levante-framework/levante-dashboard ***@***.***>
Cc: David Cardinal ***@***.***>; Assign ***@***.***>
Subject: Re: [levante-framework/levante-dashboard] Ts convert modal and auth (PR #348)

 

@Zio-4 requested changes on this pull request.

I'm not sure what's up with the AuthEmailLink component, but it's really out of date. Have you pulled in the latest changes to this branch?

  _____  

In src/components/auth/AuthEmailLink.vue <https://github.com/levante-framework/levante-dashboard/pull/348#discussion_r2105902414> :

> @@ -12,13 +12,18 @@
       />
     </div>
   </div>
+  <transition-group name="p-message" tag="div">

Not sure why this is here, it's old code I removed.

  _____  

In src/components/auth/AuthEmailLink.vue <https://github.com/levante-framework/levante-dashboard/pull/348#discussion_r2105902430> :

>  const isError = ref(false);
+const messages = ref([]);
+
+const addMessages = (errorCode) => {

Old code

  _____  

In src/components/auth/AuthEmailLink.vue <https://github.com/levante-framework/levante-dashboard/pull/348#discussion_r2105902492> :

>  
 const loginFromEmailLink = async (email) => {
   unsubscribe();
   const emailLink = window.location.href;
   await authStore
     .signInWithEmailLink({ email, emailLink })
+    .catch((error) => {

old code

  _____  

In src/components/modals/EditUsers.vue <https://github.com/levante-framework/levante-dashboard/pull/348#discussion_r2105902982> :

> +
+interface Emits {
+  (e: 'modalClosed'): void;
+}
+
+interface DropdownOption {
+  label: string;
+  value: boolean;
+}
+
+interface AutoCompleteEvent {
+  query: string;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isEnabled: false,

userData is missing

  _____  

In src/components/modals/EditUsers.vue <https://github.com/levante-framework/levante-dashboard/pull/348#discussion_r2105903234> :

>  });
 
 // Handle modal opening / closing
-const emit = defineEmits(["modalClosed"]);
+const emit = defineEmits<Emits>();

The things that's emitted, "modalClosed", is missing

  _____  

In src/components/modals/RoarModal.vue <https://github.com/levante-framework/levante-dashboard/pull/348#discussion_r2105903499> :

>  });
 
 // Handle modal opening / closing
-const emit = defineEmits(["modalClosed"]);
+const emit = defineEmits<Emits>();

Missing the emit

—
Reply to this email directly, view it on GitHub <https://github.com/levante-framework/levante-dashboard/pull/348#pullrequestreview-2866403518> , or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABJ4N3BRIRSIES3PX3OIVI33AC4W3AVCNFSM6AAAAAB5XCOQT2VHI2DSMVQWIX3LMV43YUDVNRWFEZLROVSXG5CSMV3GSZLXHMZDQNRWGQYDGNJRHA> .
You are receiving this because you were assigned.  <https://github.com/notifications/beacon/ABJ4N3B75RN7FKJIHPST62D3AC4W3A5CNFSM6AAAAAB5XCOQT2WGG33NNVSW45C7OR4XAZNRKB2WY3CSMVYXKZLTORJGK5TJMV32UY3PNVWWK3TUL5UWJTVK3HML4.gif> Message ID: ***@***.*** ***@***.***> >


author_association: COLLABORATOR
user: login: Zio-4
created_at: 2025-05-24T19:22:11Z
body: Oh I see. I usually rebase the main branch with the feature branch. 

1. Pull from main
2. Checkout to feature branch
3. Rebase: git rebase origin/main
author_association: COLLABORATOR
user: login: digital-pro
created_at: 2025-05-24T19:34:23Z
body: Thx. Good to know. I’m working through the other comments.

 

From: Philip Ziolkowski ***@***.***> 
Sent: Saturday, May 24, 2025 12:23 PM
To: levante-framework/levante-dashboard ***@***.***>
Cc: David Cardinal ***@***.***>; Assign ***@***.***>
Subject: Re: [levante-framework/levante-dashboard] Ts convert modal and auth (PR #348)

 

  <https://avatars.githubusercontent.com/u/80180474?s=20&v=4> Zio-4 left a comment (levante-framework/levante-dashboard#348) <https://github.com/levante-framework/levante-dashboard/pull/348#issuecomment-2906986249> 

Oh I see. I usually rebase the main branch with the feature branch.

1.	Pull from main
2.	Checkout to feature branch
3.	Rebase: git rebase origin/main

—
Reply to this email directly, view it on GitHub <https://github.com/levante-framework/levante-dashboard/pull/348#issuecomment-2906986249> , or unsubscribe <https://github.com/notifications/unsubscribe-auth/ABJ4N3G7ZQPGY4V53SKO6G33ADBHVAVCNFSM6AAAAAB5XCOQT2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDSMBWHE4DMMRUHE> .
You are receiving this because you were assigned.  <https://github.com/notifications/beacon/ABJ4N3BAY2AA2SZNQGVWYRD3ADBHVA5CNFSM6AAAAAB5XCOQT2WGG33NNVSW45C7OR4XAZNMJFZXG5LFINXW23LFNZ2KUY3PNVWWK3TUL5UWJTVNIULQS.gif> Message ID: ***@***.*** ***@***.***> >


author_association: COLLABORATOR