Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ssl-cert.pem
ssl-key.pem
static/
!ami/*/static/
ami/templates/front/

# The private key file for Firebase Cloud Messaging
*-adminsdk-*.json
177 changes: 177 additions & 0 deletions ami/agent_admin/static/css/agent-admin-styles.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ami/authentication/tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ def fake_nonce():
monkeypatch.setattr("ami.authentication.views.generate_nonce", fake_nonce)
response = django_app.get("/login-france-connect")
redirected_url = response.headers["location"]
assert redirected_url == "https://localhost:5173/#/technical-error"
assert redirected_url == "https://localhost:5173/technical-error"
2 changes: 1 addition & 1 deletion ami/authentication/tests/test_login_callback_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,4 +269,4 @@ def fake_retry(*args):
monkeypatch.setattr("ami.authentication.views.retry_fc_later", fake_retry)
response = django_app.get("/login-callback?state=some-state")
redirected_url = response.headers["location"]
assert redirected_url == "https://localhost:5173/#/technical-error"
assert redirected_url == "https://localhost:5173/technical-error"
4 changes: 2 additions & 2 deletions ami/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def login_france_connect(request):
return redirect(login_url)
except Exception as e:
logging.exception(e)
return redirect(f"{settings.PUBLIC_APP_URL}/#/technical-error")
return redirect(f"{settings.PUBLIC_APP_URL}/technical-error")


@require_GET
Expand Down Expand Up @@ -146,4 +146,4 @@ async def login_callback(request):
return retry_fc_later({"error_code": e.code})
except Exception as e:
logging.exception(e)
return redirect(f"{settings.PUBLIC_APP_URL}/#/technical-error")
return redirect(f"{settings.PUBLIC_APP_URL}/technical-error")
24 changes: 24 additions & 0 deletions ami/notification/migrations/0007_internal_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.db import migrations


def forward(apps, schema_editor):
ScheduledNotification = apps.get_model("notification", "ScheduledNotification")
Notification = apps.get_model("notification", "Notification")

for notification in Notification.objects.filter(internal_url="/#/procedure"):
notification.internal_url = "/procedure"
notification.save()

for scheduled_notification in ScheduledNotification.objects.filter(internal_url="/#/procedure"):
scheduled_notification.internal_url = "/procedure"
scheduled_notification.save()


class Migration(migrations.Migration):
dependencies = [
("notification", "0006_remove_sender"),
]

operations = [
migrations.RunPython(forward, reverse_code=migrations.RunPython.noop),
]
14 changes: 11 additions & 3 deletions ami/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@
from ami.api import urls as api_urls
from ami.authentication import urls as authentication_urls


class MobileAppView(TemplateView):
def get_template_names(self):
subpath = self.kwargs.get("subpath", "")
return [f"{subpath}/index.html"] if subpath else ["index.html"]


urlpatterns = [
path("", include(authentication_urls)),
path("", include(api_urls)),
path("", TemplateView.as_view(template_name="index.html")),
path("ami-admin/", admin.site.urls),
path("agent-admin/", include(agent_admin_urls, namespace="agent-admin")),
path("", include(authentication_urls)),
path("", include(api_urls)),
path("<path:subpath>/", MobileAppView.as_view()),
path("", MobileAppView.as_view()),
]
9 changes: 9 additions & 0 deletions ami/utils/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from whitenoise.storage import CompressedManifestStaticFilesStorage


class AMIStorage(CompressedManifestStaticFilesStorage):
def hashed_name(self, name, content=None, filename=None):
# Ne pas re-hasher les fichiers déjà hashés par Svelte
if "_app/immutable/" in name:
return name
return super().hashed_name(name, content, filename)
15 changes: 7 additions & 8 deletions public/mobile-app/src/lib/ConnectedHomepage.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { PUBLIC_FEATUREFLAG_REQUESTS_ENABLED } from '$env/static/public';
import type { Agenda } from '$lib/agenda';
import { buildAgenda } from '$lib/agenda';
Expand Down Expand Up @@ -69,15 +68,15 @@
};

const goToProfile = async () => {
goto('/#/profile');
window.location.href = '/profile/';
};

const goToSettings = () => {
goto('/#/settings');
window.location.href = '/settings/';
};

const goToContact = () => {
goto('/#/contact');
window.location.href = '/contact/';
};
</script>

Expand All @@ -89,7 +88,7 @@

<div class="header-right">
<div class="notification-svg-icon" id="notification-icon">
<a href="/#/notifications">
<a href="/notifications/">
<img src="/remixicons/notification-3.svg" alt="Icône de notifications">
<div class="count-number-wrapper" data-content="{unreadNotificationsCount}">
{unreadNotificationsCount}
Expand Down Expand Up @@ -164,7 +163,7 @@
<div class="fr-tile__content">
<img class="address-icon" src="/remixicons/house.svg" alt="Icône adresse">
<h3 class="fr-tile__title">
<a href="/#/edit-address"
<a href="/edit-address/"
><b
>Renseignez votre adresse sur l'application pour faciliter vos
échanges&nbsp;!</b
Expand Down Expand Up @@ -198,7 +197,7 @@
{:else}
<div class="header-container">
<span class="title">Mon agenda</span>
<a class="see-all" title="Voir tous mes évènements" href="/#/agenda">
<a class="see-all" title="Voir tous mes évènements" href="/agenda/">
<span>Voir tout</span>
<img
class="arrow-line"
Expand Down Expand Up @@ -235,7 +234,7 @@
{:else}
<div class="header-container">
<span class="title">Mes démarches</span>
<a class="see-all" title="Voir toutes mes démarches" href="/#/requests">
<a class="see-all" title="Voir toutes mes démarches" href="/requests/">
<span>Voir tout</span>
<img
class="arrow-line"
Expand Down
18 changes: 3 additions & 15 deletions public/mobile-app/src/lib/ConnectedHomepage.svelte.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import '@testing-library/jest-dom/vitest';
import { fireEvent, render, screen, waitFor } from '@testing-library/svelte';
import type { WS as WSType } from 'vitest-websocket-mock';
import WS from 'vitest-websocket-mock';
import * as navigationMethods from '$app/navigation';
import * as envModule from '$env/static/public';
import * as agendaMethods from '$lib/agenda';
import { Agenda, Item } from '$lib/agenda';
Expand Down Expand Up @@ -113,9 +112,6 @@ describe('/ConnectedHomepage.svelte', () => {

test('should navigate to User profile page when user clicks on Mon profil button', async () => {
// Given
const spy = vi
.spyOn(navigationMethods, 'goto')
.mockImplementation(() => Promise.resolve());
render(ConnectedHomepage);

// When
Expand All @@ -124,16 +120,12 @@ describe('/ConnectedHomepage.svelte', () => {

// Then
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenNthCalledWith(1, '/#/profile');
expect(window.location.href).toEqual('/profile/');
});
});

test('should navigate to Settings page when user clicks on Paramétrer button', async () => {
// Given
const spy = vi
.spyOn(navigationMethods, 'goto')
.mockImplementation(() => Promise.resolve());
render(ConnectedHomepage);

// When
Expand All @@ -142,16 +134,12 @@ describe('/ConnectedHomepage.svelte', () => {

// Then
await waitFor(() => {
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenNthCalledWith(1, '/#/settings');
expect(window.location.href).toEqual('/settings/');
});
});

test('should navigate to Contact page when user clicks on Nous contacter button', async () => {
// Given
const spy = vi
.spyOn(navigationMethods, 'goto')
.mockImplementation(() => Promise.resolve());
render(ConnectedHomepage);

// When
Expand All @@ -160,7 +148,7 @@ describe('/ConnectedHomepage.svelte', () => {

// Then
await waitFor(() => {
expect(spy).toHaveBeenCalledWith('/#/contact');
expect(window.location.href).toEqual('/contact/');
});
});

Expand Down
2 changes: 1 addition & 1 deletion public/mobile-app/src/lib/agenda.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ describe('/agenda.ts', () => {
// Then
expect(link1).equal('');
expect(link2).equal('');
expect(link3).equal('/#/procedure');
expect(link3).equal('/procedure');
expect(link4).equal('');
});
});
Expand Down
2 changes: 1 addition & 1 deletion public/mobile-app/src/lib/agenda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export class Item {
otv: {
label: 'Logement',
icon: 'fr-icon-home-4-fill',
link: '/#/procedure',
link: '/procedure',
},
election: {
label: 'Élections',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ describe('/AgendaItem.svelte', () => {
const link = screen.getByTestId('agenda-item-link');

// Then
expect(link.getAttribute('href')).toBe('/#/procedure?date=2025-12-05');
expect(link.getAttribute('href')).toBe('/procedure?date=2025-12-05');
});
});
5 changes: 2 additions & 3 deletions public/mobile-app/src/lib/components/BackButton.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { goto } from '$app/navigation';

interface Props {
backUrl: string;
}
let { backUrl }: Props = $props();

const navigateToPreviousPage = async () => {
goto(backUrl);
const navigateToPreviousPage = () => {
window.location.href = backUrl;
};
</script>

Expand Down
12 changes: 2 additions & 10 deletions public/mobile-app/src/lib/components/BackButton.svelte.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import { fireEvent, render, screen } from '@testing-library/svelte';
import { describe, expect, test, vi } from 'vitest';
import * as navigationMethods from '$app/navigation';
import { describe, expect, test } from 'vitest';
import Page from './BackButton.svelte';

describe('/BackButton.svelte', () => {
test('should navigate to previous page when user clicks on Back button', async () => {
// Given
const backSpy = vi
.spyOn(navigationMethods, 'goto')
.mockImplementation(() => Promise.resolve());

// When
render(Page, { backUrl: '/foobar' });
const backButton = screen.getByTestId('back-button');
await fireEvent.click(backButton);

// Then
expect(backSpy).toHaveBeenCalledTimes(1);
expect(backSpy).toHaveBeenCalledWith('/foobar');
backSpy.mockRestore();
expect(window.location.href).toEqual('/foobar');
});
});
5 changes: 0 additions & 5 deletions public/mobile-app/src/lib/components/NavWithBackButton.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { goto } from '$app/navigation';
import BackButton from '$lib/components/BackButton.svelte';

interface Props {
Expand All @@ -9,10 +8,6 @@
children?: Snippet;
}
let { backUrl, children, title }: Props = $props();

const navigateToPreviousPage = async () => {
goto(backUrl);
};
</script>

<nav>
Expand Down
4 changes: 2 additions & 2 deletions public/mobile-app/src/lib/components/Navigation.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<li class="menu__item">
<a
class="menu__link {current.agenda ? 'highlight': ''}"
href="/#/agenda"
href="/agenda/"
aria-current="{current.agenda ? 'true': null}"
>
<img src="/remixicons/calendar-event-line.svg" alt="Icône de calendrier">
Expand All @@ -36,7 +36,7 @@
<li class="menu__item">
<a
class="menu__link {current.requests ? 'highlight': ''}"
href="/#/requests"
href="/requests/"
aria-current="{current.requests ? 'true': null}"
>
<img src="/remixicons/vector.svg" alt="Icône de suivi">
Expand Down
7 changes: 4 additions & 3 deletions public/mobile-app/src/lib/matomo.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { browser } from '$app/environment';
import {
PUBLIC_MATOMO_CDN_URL,
PUBLIC_MATOMO_ENABLED,
Expand All @@ -9,7 +10,7 @@ import { userStore } from '$lib/state/User.svelte';
const MATOMO_ENABLED = PUBLIC_MATOMO_ENABLED === 'true';

export function initMatomo() {
if (!MATOMO_ENABLED || typeof window === 'undefined') {
if (!MATOMO_ENABLED || !browser) {
return;
}

Expand All @@ -25,12 +26,12 @@ export function initMatomo() {
}

export function trackPageView(title?: string) {
if (typeof window === 'undefined') {
if (!browser) {
return;
}

window._paq = window._paq || [];
let path = window.location.hash ? window.location.hash.substr(1) : '/';
let path = window.location.pathname;
if (!userStore.connected && path === '/') {
path = '/login';
}
Expand Down
2 changes: 1 addition & 1 deletion public/mobile-app/src/lib/nativeEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const emit = (eventName: string, data?: any) => {
};

export const isNative = (): boolean => {
return !!window.NativeBridge;
return typeof window !== 'undefined' && !!window.NativeBridge;
};

export const runOrNativeEvent = (func: () => any, eventName: string, data?: any) => {
Expand Down
10 changes: 6 additions & 4 deletions public/mobile-app/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import '@gouvfr/dsfr/dist/utility/utility.min.css';
import '../app.css';
import { onMount } from 'svelte';
import { afterNavigate, goto } from '$app/navigation';
import { afterNavigate } from '$app/navigation';
import { env } from '$env/dynamic/public';
import Toasts from '$lib/components/Toasts.svelte';
import { initDsfr } from '$lib/dsfr';
Expand All @@ -14,7 +14,7 @@
onMount(async () => {
if (env.PUBLIC_WEBSITE_PUBLIC === undefined && window.NativeBridge === undefined) {
// The web app isn't opened to the public yet, and it's not being served in a native application.
goto('/#/forbidden');
window.location.href = '/forbidden/';
}
await initDsfr();

Expand All @@ -26,5 +26,7 @@
});
</script>

<Toasts />
{@render children()}
<div data-sveltekit-reload style="display: contents">
<Toasts />
{@render children()}
</div>
Loading
Loading