Re-login modal (#717)

* Implement quick login/register flow with global event handling

- Add QuickRegister component with improved modal management
- Integrate quick login/register with app store state
- Implement custom event handling for login/registration flow
- Update OAuth callback to support quick login in popup windows
- Refactor authentication-related components to use global events

* Refactor authentication flow with centralized useAuth composable

- Create new useAuth composable to centralize login, registration, and social login logic
- Simplify authentication methods in LoginForm and RegisterForm
- Add event-based login/registration flow with quick login support
- Remove redundant API calls and consolidate authentication processes
- Improve error handling and analytics tracking for authentication events

* Enhance QuickRegister and RegisterForm components with unauthorized error handling

- Add closeable functionality to modals based on unauthorized error state
- Implement logout button in QuickRegister for unauthorized users
- Reset unauthorized error state on component unmount
- Update styling for "OR" text in RegisterForm for consistency
- Set unauthorized error flag in app store upon 401 response in API calls

* Refactor Authentication Flow and Remove Unused Callback Views

- Deleted unused callback views for Notion and OAuth to streamline the codebase.
- Updated QuickRegister and LoginForm components to remove the after-login event emission, replacing it with a window message system for better communication between components.
- Enhanced the RegisterForm and other components to utilize the new window message system for handling login completion, improving reliability and maintainability.
- Added a verifyAuthentication method in the useAuth composable to ensure user data is loaded correctly after social logins, including retry logic for fetching user data.

These changes aim to simplify the authentication process and improve the overall user experience by ensuring a more robust handling of login events.

* Add eslint-disable comment to useWindowMessage composable for linting control

* Refactor QuickRegister.vue for improved template structure and clarity

- Adjusted the rendering of horizontal dividers and the "or" text for better semantic HTML.
- Added a compact-header prop to the modal for enhanced layout control.

These changes aim to enhance the readability and maintainability of the QuickRegister component.

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Chirag Chhatrala
2025-03-25 15:11:11 +05:30
committed by GitHub
parent a711b961d4
commit 2c746437c9
15 changed files with 602 additions and 224 deletions

View File

@@ -11,7 +11,7 @@
class="w-full flex flex-grow"
:error="error"
:is-guest="isGuest"
@open-register="registerModal = true"
@open-register="appStore.quickRegisterModal = true"
/>
<div
v-else
@@ -19,25 +19,18 @@
>
<Loader class="h-6 w-6 text-nt-blue mx-auto" />
</div>
<quick-register
:show-register-modal="registerModal"
@close="registerModal = false"
@reopen="registerModal = true"
@after-login="afterLogin"
/>
</div>
</template>
<script setup>
import FormEditor from "~/components/open/forms/components/FormEditor.vue"
import QuickRegister from "~/components/pages/auth/components/QuickRegister.vue"
import CreateFormBaseModal from "../../../components/pages/forms/create/CreateFormBaseModal.vue"
import { initForm } from "~/composables/forms/initForm.js"
import { fetchTemplate } from "~/stores/templates.js"
import { fetchAllWorkspaces } from "~/stores/workspaces.js"
import { WindowMessageTypes } from "~/composables/useWindowMessage"
const appStore = useAppStore()
const templatesStore = useTemplatesStore()
const workingFormStore = useWorkingFormStore()
const workspacesStore = useWorkspacesStore()
@@ -63,7 +56,6 @@ definePageMeta({
// Data
const stateReady = ref(false)
const error = ref("")
const registerModal = ref(false)
const isGuest = ref(true)
const showInitialFormModal = ref(false)
@@ -92,10 +84,15 @@ onMounted(() => {
showInitialFormModal.value = true
}
stateReady.value = true
// Set up window message listener for after-login
const afterLoginMessage = useWindowMessage(WindowMessageTypes.AFTER_LOGIN)
afterLoginMessage.listen(() => {
afterLogin()
}, { useMessageChannel: false })
})
const afterLogin = () => {
registerModal.value = false
isGuest.value = false
fetchAllWorkspaces()
setTimeout(() => {

View File

@@ -15,7 +15,7 @@
class="m-6 flex flex-col items-center space-y-4"
>
<p class="text-center">
Unable to sign it at the moment.
Unable to sign in at the moment.
</p>
<v-button
:to="{ name: 'login' }"
@@ -29,74 +29,77 @@
<script setup>
import { useNuxtApp } from "nuxt/app"
import { WindowMessageTypes } from "~/composables/useWindowMessage"
const { $utm } = useNuxtApp()
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
const workspacesStore = useWorkspacesStore()
const formsStore = useFormsStore()
const logEvent = useAmplitude().logEvent
const loading = ref(true)
const form = useForm({
code: '',
utm_data: null,
})
definePageMeta({
alias: '/oauth/:provider/callback'
})
function handleCallback() {
const handleCallback = async () => {
const auth = useAuth()
const provider = route.params.provider
try {
const { isNewUser } = await auth.handleSocialCallback(
provider,
route.query.code,
$utm.value
)
const provider = route.params.provider
form.code = route.query.code
form.utm_data = $utm.value
form.post(`/oauth/${provider}/callback`).then(async (data) => {
authStore.setToken(data.token, data.expires_in)
const [userDataResponse, workspacesResponse] = await Promise.all([
opnFetch("user"),
fetchAllWorkspaces(),
])
authStore.setUser(userDataResponse)
workspacesStore.set(workspacesResponse.data.value)
// Load forms
formsStore.loadAll(workspacesStore.currentId)
if (!data.new_user) {
logEvent("login", { source: provider })
try {
useGtm().trackEvent({
event: 'login',
source: provider
})
} catch (error) {
console.error(error)
}
router.push({ name: "home" })
return
} else {
logEvent("register", { source: provider })
router.push({ name: "forms-create" })
useAlert().success("Success! You're now registered with your Google account! Welcome to OpnForm.")
try {
useGtm().trackEvent({
event: 'register',
source: provider
})
} catch (error) {
console.error(error)
useAlert().error(error)
}
if (!isNewUser) {
// Handle existing user login
if (window.opener) {
try {
// Use the WindowMessage composable for more reliable communication
const windowMessage = useWindowMessage(WindowMessageTypes.LOGIN_COMPLETE)
// Send the login-complete message and wait for acknowledgment
await windowMessage.send(window.opener, {
waitForAcknowledgment: true,
timeout: 500
})
// Now we can safely close the window
window.close()
// If window doesn't close (some browsers prevent it), show a message
loading.value = false
} catch (err) {
console.error("Error in social callback:", err)
loading.value = false
}
}).catch(error => {
useAlert().error(error.response._data.message)
loading.value = false
})
} else {
// No opener, redirect to home
router.push({ name: "home" })
}
} else {
// Handle new user registration
router.push({ name: "forms-create" })
useAlert().success("Success! You're now registered with your Google account! Welcome to OpnForm.")
}
} catch (error) {
console.error("Social login error:", error)
useAlert().error(error.response?._data?.message || "Authentication failed")
loading.value = false
}
}
onMounted(() => {
handleCallback()
})
onMounted(() => {
// Set a timeout to ensure we don't get stuck in loading state
const timeoutId = setTimeout(() => {
if (loading.value) {
loading.value = false
console.error("Social login timed out")
}
}, 10000) // 10 second timeout
handleCallback().finally(() => {
clearTimeout(timeoutId)
})
})
</script>