nova_forms/hooks/
use_zip_service.rs

1use leptos::*;
2
3use crate::{Datatype, NonEmptyString, PostalCodeCH};
4
5/// Provides a signal to get the city for a given swiss zip code.
6/// This uses the swiss postal service address checker API (`https://service.post.ch/zopa/app/api/addresschecker/v1/zips`).
7pub fn postal_code_service<S: SignalGet<Value = Option<PostalCodeCH>> + 'static>(postal_code: S) -> Signal<Option<NonEmptyString>> {
8    let zip_service = create_server_action::<PostalCodeServiceServerFn>();
9    create_effect(move |_| {
10        if let Some(postal_code) = postal_code.get() {
11            zip_service.dispatch(PostalCodeServiceServerFn { zip: postal_code.into() });
12        }
13    });
14    let zip_service_value = zip_service.value();
15    let city = create_memo(move |_| {
16        if let Some(Ok(Some(city))) = zip_service_value.get() {
17            NonEmptyString::validate(city).ok()
18        } else {
19            None
20        }
21    });
22    city.into()
23}
24
25#[server]
26async fn postal_code_service_server_fn(zip: String) -> Result<Option<String>, ServerFnError> {
27    use serde::Deserialize;
28
29    #[derive(Deserialize)]
30    struct Response {
31        zips: Vec<ResponseZip>,
32    }
33
34    #[derive(Deserialize)]
35    struct ResponseZip {
36        city18: String,
37    }
38
39    let zips = reqwest::get(format!(
40        "https://service.post.ch/zopa/app/api/addresschecker/v1/zips?limit=2&zip={zip}"
41    ))
42    .await?
43    .json::<Response>()
44    .await?
45    .zips;
46
47    if zips.len() != 1 {
48        return Ok(None);
49    }
50
51    Ok(Some(zips.first().unwrap().city18.to_owned()))
52}
53