diff --git a/arkindex/dataimport/urls.py b/arkindex/dataimport/urls.py
index d3ef6be5cb7c94ef719f1263ad0a3577129b7779..ed4abd173ade2d40a71c24e082199e34f3a5c2f1 100644
--- a/arkindex/dataimport/urls.py
+++ b/arkindex/dataimport/urls.py
@@ -1,18 +1,12 @@
 from django.conf.urls import url
-from arkindex.dataimport.views import (
-    DataImportsList, DataImportStatus, DataImportFailures, DataFileList,
-    RepositoryList, RepositoryCreate,
-)
-from arkindex.users.views import CredentialsList, OAuthCallback
-
+from arkindex.project.views import FrontendView
 
 urlpatterns = [
-    url(r'^$', DataImportsList.as_view(), name='imports'),
-    url(r'^(?P<pk>[\w\-]+)/status/?$', DataImportStatus.as_view(), name='import-status'),
-    url(r'^(?P<pk>[\w\-]+)/failures/?$', DataImportFailures.as_view(), name='import-failures'),
-    url(r'^files/?$', DataFileList.as_view(), name='files'),
-    url(r'^repos/?$', RepositoryList.as_view(), name='repositories'),
-    url(r'^repos/new/?$', RepositoryCreate.as_view(), name='repositories-create'),
-    url(r'^credentials/?$', CredentialsList.as_view(), name='credentials'),
-    url(r'^oauth/(?P<provider>\w+)/callback/?$', OAuthCallback.as_view(), name='oauth-callback'),
+    url(r'^$', FrontendView.as_view(), name='imports'),
+    url(r'^(?P<pk>[\w\-]+)/status/?$', FrontendView.as_view(), name='import-status'),
+    url(r'^(?P<pk>[\w\-]+)/failures/?$', FrontendView.as_view(), name='import-failures'),
+    url(r'^files/?$', FrontendView.as_view(), name='files'),
+    url(r'^repos/?$', FrontendView.as_view(), name='repositories'),
+    url(r'^repos/new/?$', FrontendView.as_view(), name='repositories-create'),
+    url(r'^credentials/?$', FrontendView.as_view(), name='credentials'),
 ]
diff --git a/arkindex/dataimport/views.py b/arkindex/dataimport/views.py
deleted file mode 100644
index 2bbca71a40391a89c3920ca04760f4a2074bc927..0000000000000000000000000000000000000000
--- a/arkindex/dataimport/views.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from django.views.generic import TemplateView, DetailView
-from django.contrib.auth.mixins import LoginRequiredMixin
-from arkindex.documents.models import Corpus
-from arkindex.dataimport.models import DataImport, DataImportState
-
-
-class DataImportsList(LoginRequiredMixin, TemplateView):
-    """
-    List data imports using Vue JS + API
-    """
-    template_name = 'dataimport/list.html'
-
-
-class DataImportStatus(LoginRequiredMixin, DetailView):
-    """
-    View a data import workflow's status
-    """
-    template_name = 'dataimport/status.html'
-    context_object_name = 'dataimport'
-
-    def get_queryset(self):
-        return DataImport.objects.filter(
-            corpus__in=Corpus.objects.readable(self.request.user),
-        ).exclude(
-            state__in=[DataImportState.Created, DataImportState.Configured],
-        )
-
-
-class DataImportFailures(LoginRequiredMixin, DetailView):
-    """
-    View a data import workflow's failures
-    """
-    template_name = 'dataimport/failures.html'
-    context_object_name = 'dataimport'
-
-    def get_queryset(self):
-        return DataImport.objects.filter(
-            corpus__in=Corpus.objects.readable(self.request.user),
-        ).exclude(
-            state__in=[DataImportState.Created, DataImportState.Configured],
-        )
-
-
-class DataFileList(LoginRequiredMixin, TemplateView):
-    """
-    View and manage uploaded files
-    """
-    template_name = 'dataimport/files.html'
-
-
-class RepositoryList(LoginRequiredMixin, TemplateView):
-    """
-    Manage repositories
-    """
-    template_name = 'dataimport/repositories.html'
-
-
-class RepositoryCreate(LoginRequiredMixin, TemplateView):
-    """
-    Create a new repository
-    """
-    template_name = 'dataimport/repository.new.html'
diff --git a/arkindex/project/api_v1.py b/arkindex/project/api_v1.py
index cb3dbc8cdac9b0e0c6bb789c479c7a629127ba56..65afc19b5c8089397d6da2b290da7f1110dd798f 100644
--- a/arkindex/project/api_v1.py
+++ b/arkindex/project/api_v1.py
@@ -18,7 +18,7 @@ from arkindex.dataimport.api import (
     GitRepositoryImportHook, AvailableRepositoriesList, ElementHistory,
 )
 from arkindex.users.api import (
-    ProvidersList, CredentialsList, CredentialsRetrieve, OAuthSignIn, OAuthRetry,
+    ProvidersList, CredentialsList, CredentialsRetrieve, OAuthSignIn, OAuthRetry, OAuthCallback,
     UserRetrieve, UserCreate, UserEmailLogin, UserEmailVerification
 )
 
@@ -105,6 +105,7 @@ api = [
     # Manage OAuth integrations
     url(r'^oauth/providers/$', ProvidersList.as_view(), name='providers-list'),
     url(r'^oauth/providers/(?P<provider>[\w\-]+)/signin/$', OAuthSignIn.as_view(), name='oauth-signin'),
+    url(r'^oauth/providers/(?P<provider>[\w\-]+)/callback/$', OAuthCallback.as_view(), name='oauth-callback'),
     url(r'^oauth/credentials/$', CredentialsList.as_view(), name='credentials-list'),
     url(r'^oauth/credentials/(?P<pk>[\w\-]+)/$', CredentialsRetrieve.as_view(), name='credentials-retrieve'),
     url(r'^oauth/credentials/(?P<pk>[\w\-]+)/retry/$', OAuthRetry.as_view(), name='oauth-retry'),
diff --git a/arkindex/templates/dataimport/credentials.html b/arkindex/templates/dataimport/credentials.html
deleted file mode 100644
index f8060b8b55bfc6e4e8d9413df334d9e8249de917..0000000000000000000000000000000000000000
--- a/arkindex/templates/dataimport/credentials.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends 'base.html' %}
-
-{% block content %}
-<div id="app">
-  <OAuth-List />
-</div>
-{% endblock %}
diff --git a/arkindex/templates/dataimport/failures.html b/arkindex/templates/dataimport/failures.html
deleted file mode 100644
index d14b8d8e9dfcf472367a91a213e1a4262b3f92e3..0000000000000000000000000000000000000000
--- a/arkindex/templates/dataimport/failures.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends 'base.html' %}
-
-{% block content %}
-<div id="app">
-  <Import-Failures id="{{ dataimport.id }}" />
-</div>
-{% endblock %}
diff --git a/arkindex/templates/dataimport/files.html b/arkindex/templates/dataimport/files.html
deleted file mode 100644
index 69fc93a9532fb63176217fe19e54c226fc8c846b..0000000000000000000000000000000000000000
--- a/arkindex/templates/dataimport/files.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends 'base.html' %}
-
-{% block content %}
-<div id="app">
-  <Files-List />
-</div>
-{% endblock %}
diff --git a/arkindex/templates/dataimport/list.html b/arkindex/templates/dataimport/list.html
deleted file mode 100644
index 1af69ce04b5aa990d2a70d539a4918874cd61354..0000000000000000000000000000000000000000
--- a/arkindex/templates/dataimport/list.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends 'base.html' %}
-
-{% block content %}
-<div id="app">
-  <Imports-List />
-</div>
-{% endblock %}
diff --git a/arkindex/templates/dataimport/repositories.html b/arkindex/templates/dataimport/repositories.html
deleted file mode 100644
index d0f5107af8e14f05a5d45f80a6b9b0f777e12e22..0000000000000000000000000000000000000000
--- a/arkindex/templates/dataimport/repositories.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends 'base.html' %}
-
-{% block content %}
-<div id="app">
-  <Repos-List />
-</div>
-{% endblock %}
diff --git a/arkindex/templates/dataimport/repository.new.html b/arkindex/templates/dataimport/repository.new.html
deleted file mode 100644
index d7a054cb0252852bfa56c53d0b05ac996b2463a7..0000000000000000000000000000000000000000
--- a/arkindex/templates/dataimport/repository.new.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends 'base.html' %}
-
-{% block content %}
-<div id="app">
-  <Repos-Create />
-</div>
-{% endblock %}
diff --git a/arkindex/templates/dataimport/status.html b/arkindex/templates/dataimport/status.html
deleted file mode 100644
index 681dfec717bdc98cc69be01a32d20945106e8c8c..0000000000000000000000000000000000000000
--- a/arkindex/templates/dataimport/status.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% extends 'base.html' %}
-
-{% block content %}
-<div id="app">
-  <Import-Status id="{{ dataimport.id }}" />
-</div>
-{% endblock %}
diff --git a/arkindex/users/api.py b/arkindex/users/api.py
index 19b2594f41b6054c1fea9c195e838cf1cf111df6..ec0e6e874188dd6b9221843e3aede3fee62f5b4e 100644
--- a/arkindex/users/api.py
+++ b/arkindex/users/api.py
@@ -2,6 +2,7 @@ from django.urls import reverse
 from django.core.mail import send_mail
 from django.contrib.auth import login, logout
 from django.contrib.auth.tokens import default_token_generator
+from django.contrib.auth.mixins import LoginRequiredMixin
 from django.contrib.auth.models import Group
 from django.views.generic import RedirectView
 from django.template.loader import render_to_string
@@ -198,3 +199,26 @@ class OAuthRetry(RetrieveAPIView):
         return Response({
             'url': creds.provider_class(request=self.request, credentials=creds).get_authorize_uri(),
         })
+
+
+class OAuthCallback(LoginRequiredMixin, RedirectView):
+    """
+    Callback for OAuth responses
+    """
+
+    pattern_name = 'credentials'
+
+    def get(self, request, *args, **kwargs):
+        assert 'provider' in kwargs
+        provider_class = get_provider(kwargs['provider'])
+        if not provider_class:
+            raise ValueError('Unknown provider')
+        provider = provider_class(self.request)
+        try:
+            provider.handle_callback()
+        except Exception:
+            if not provider.credentials:
+                raise
+            provider.credentials.status = OAuthStatus.Error
+            provider.credentials.save()
+        return super().get(request)
diff --git a/arkindex/users/providers.py b/arkindex/users/providers.py
index a629ab0208a0ed8cf0e20dff6c19275445ef35f5..d4d8ed461a20e15b938169aae52422b613c41b9b 100644
--- a/arkindex/users/providers.py
+++ b/arkindex/users/providers.py
@@ -79,7 +79,7 @@ class GitLabOAuthProvider(OAuthProvider):
         if not self.request:
             return
         return self.request.build_absolute_uri(
-            reverse('oauth-callback', kwargs={'provider': self.slug}),
+            reverse('api:oauth-callback', kwargs={'provider': self.slug}),
         )
 
     def get_authorize_uri(self):
diff --git a/arkindex/users/tests/test_gitlab_oauth.py b/arkindex/users/tests/test_gitlab_oauth.py
index c21bccdf040d7a5dbd7a6c662e59ca29c83355b2..975625e86ac8b1843dc640de8f5582de8013a0a9 100644
--- a/arkindex/users/tests/test_gitlab_oauth.py
+++ b/arkindex/users/tests/test_gitlab_oauth.py
@@ -34,7 +34,7 @@ class TestGitLabOAuthProvider(FixtureTestCase):
         GitLabOAuthProvider(request=request_mock).get_callback_uri()
         self.assertEqual(request_mock.build_absolute_uri.call_count, 1)
         args, kwargs = request_mock.build_absolute_uri.call_args
-        self.assertTupleEqual(args, (reverse('oauth-callback', kwargs={'provider': 'gitlab'}), ))
+        self.assertTupleEqual(args, (reverse('api:oauth-callback', kwargs={'provider': 'gitlab'}), ))
         self.assertDictEqual(kwargs, {})
 
     def test_authorize_uri(self):
diff --git a/arkindex/users/tests/test_providers.py b/arkindex/users/tests/test_providers.py
index c6e380689806fbb3906f50973ae4bcf8e68328a2..a395de36245d8a0553c24393e1f08c4e2e70a622 100644
--- a/arkindex/users/tests/test_providers.py
+++ b/arkindex/users/tests/test_providers.py
@@ -1,15 +1,40 @@
+from django.urls import reverse
+from arkindex.project.tests import FixtureTestCase
 from arkindex.users import providers
+from arkindex.users.models import OAuthStatus
 from arkindex.users.providers import get_provider
-from django.test import TestCase
+from unittest.mock import MagicMock
 
 
-class TestProviders(TestCase):
+class TestProviders(FixtureTestCase):
 
-    def test_get_provider(self):
+    @classmethod
+    def setUpTestData(cls):
+        super().setUpTestData()
+        cls.creds = cls.user.credentials.get()
+        cls.provider_mock = MagicMock()
+        cls.provider_mock.slug = 'provider-slug'
+        cls.provider_mock().credentials = cls.creds
+        # Raise anything to test callback error handling
+        cls.provider_mock().handle_callback.side_effect = ValueError
+
+    def setUp(self):
+        self.old_providers = providers.oauth_providers
+        providers.oauth_providers = [self.provider_mock]
 
-        class SomeProvider(object):
-            slug = 'provider-slug'
+    def tearDown(self):
+        providers.oauth_providers = self.old_providers
 
-        providers.oauth_providers = [SomeProvider]
-        self.assertEqual(get_provider('provider-slug'), SomeProvider)
+    def test_get_provider(self):
+        self.assertEqual(get_provider('provider-slug'), self.provider_mock)
         self.assertIsNone(get_provider('doesnotexist'))
+
+    def test_oauth_callback_error(self):
+        self.client.force_login(self.user)
+        self.creds.status = OAuthStatus.Created
+        self.creds.save()
+        response = self.client.get(
+            reverse('api:oauth-callback', kwargs={'provider': 'provider-slug'}),
+        )
+        self.assertRedirects(response, reverse('credentials'))
+        self.assertEqual(self.creds.status, OAuthStatus.Error)
diff --git a/arkindex/users/views.py b/arkindex/users/views.py
deleted file mode 100644
index 83450bceba487af61afb4403a9e89f252ad8cc33..0000000000000000000000000000000000000000
--- a/arkindex/users/views.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from django.views.generic import RedirectView, TemplateView
-from django.contrib.auth.mixins import LoginRequiredMixin
-from arkindex.users.models import OAuthStatus
-from arkindex.users.providers import get_provider
-
-
-class OAuthCallback(LoginRequiredMixin, RedirectView):
-    """
-    Callback for OAuth responses
-    """
-
-    pattern_name = 'credentials'
-
-    def get(self, request, *args, **kwargs):
-        assert 'provider' in kwargs
-        provider_class = get_provider(kwargs['provider'])
-        if not provider_class:
-            raise ValueError('Unknown provider')
-        provider = provider_class(self.request)
-        try:
-            provider.handle_callback()
-        except Exception:
-            if not provider.credentials:
-                raise
-            provider.credentials.status = OAuthStatus.Error
-            provider.credentials.save()
-        return super().get(request)
-
-
-class CredentialsList(LoginRequiredMixin, TemplateView):
-    """
-    View and manage OAuth providers
-    """
-    template_name = 'dataimport/credentials.html'