diff --git a/backend/src/main/java/fr/inra/urgi/gpds/domain/brapi/v1/data/BrapiTrial.java b/backend/src/main/java/fr/inra/urgi/gpds/domain/brapi/v1/data/BrapiTrial.java
index a3897ff11b80baa543b6c2ad595e71b294fd7150..90c5f8413827d629d14870abd6b1057a77cd9939 100644
--- a/backend/src/main/java/fr/inra/urgi/gpds/domain/brapi/v1/data/BrapiTrial.java
+++ b/backend/src/main/java/fr/inra/urgi/gpds/domain/brapi/v1/data/BrapiTrial.java
@@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonView;
 import fr.inra.urgi.gpds.domain.JSONView;
 
-import java.io.Serializable;
 import java.util.Date;
 import java.util.List;
 
@@ -13,7 +12,7 @@ import java.util.List;
  * @link https://github.com/plantbreeding/API/blob/master/Specification/Trials/GetTrialById.md
  * @link https://github.com/plantbreeding/API/blob/master/Specification/Trials/ListTrialSummaries.md
  */
-public interface BrapiTrial extends Serializable {
+public interface BrapiTrial extends HasBrapiDocumentationURL {
 
     // Trial
     @JsonView(JSONView.BrapiFields.class)
@@ -58,4 +57,6 @@ public interface BrapiTrial extends Serializable {
     @JsonView(JSONView.BrapiFields.class)
     BrapiAdditionalInfo getAdditionalInfo();
 
+    @Override
+    String getDocumentationURL();
 }
diff --git a/backend/src/main/java/fr/inra/urgi/gpds/domain/data/TrialVO.java b/backend/src/main/java/fr/inra/urgi/gpds/domain/data/TrialVO.java
index 6429603f982cf0e6c4264b077ede7690e276e800..b2b39d4cd72590d48231830735ba4aa441dd1ce6 100644
--- a/backend/src/main/java/fr/inra/urgi/gpds/domain/data/TrialVO.java
+++ b/backend/src/main/java/fr/inra/urgi/gpds/domain/data/TrialVO.java
@@ -52,6 +52,15 @@ public class TrialVO implements GnpISInternal, BrapiTrial, HasURI, HasURL, Inclu
     private String url;
     private String sourceUri;
 
+    @Override
+    public String getDocumentationURL() {
+        return url;
+    }
+
+    public void setDocumentationURL(String documentationURL) {
+        this.url = documentationURL;
+    }
+
     @Override
     public String getUri() {
         return uri;
diff --git a/frontend/e2e/src/app.e2e-spec.ts b/frontend/e2e/src/app.e2e-spec.ts
index 43b214801bfc798c535c81f6c039ce2274bad6a2..0d794af9ad135a2f3269e8958561173c83befe6d 100644
--- a/frontend/e2e/src/app.e2e-spec.ts
+++ b/frontend/e2e/src/app.e2e-spec.ts
@@ -14,7 +14,7 @@ describe('workspace-project App', () => {
 
     it('should display site card message', () => {
         page.navigateTo('/sites/FOO');
-        expect(page.getTitleText()).toEqual('site-card works!');
+        expect(page.getTitleText()).toEqual('sites-card works!');
     });
 
     it('should display study card message', () => {
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 1eac5e493dcaa4d8576095c11ace83634271bf8b..2327dddfaf2dc6e019094e2968b3b63bf5e8dd7c 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -5,26 +5,26 @@
   "requires": true,
   "dependencies": {
     "@angular-devkit/architect": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.12.0.tgz",
-      "integrity": "sha512-p7VtPmEq9/9wfIEqXIeWDFt89xmJptBGGLWAn98wgXZNrWc1erahsVmYEEruPRBdwq+KL1xQNaodGFDkUCZxXQ==",
+      "version": "0.12.2",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.12.2.tgz",
+      "integrity": "sha512-32Eim3PM/CJKGcCF1FJQ91ohuF2vBGMd4t1DILaoOMXHWmPLI9N4ILzWHfqFLRvb8QFgLn4VNG7CI9K7GcSBlQ==",
       "dev": true,
       "requires": {
-        "@angular-devkit/core": "7.2.0",
+        "@angular-devkit/core": "7.2.2",
         "rxjs": "6.3.3"
       }
     },
     "@angular-devkit/build-angular": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.12.0.tgz",
-      "integrity": "sha512-Mm0PbkKA/Jt4fBQp7kyqS/jIqE+SA3WOxqZaGN8ponfq55LOY/7Qh0dgGbQP9UtM4ohg+eOAn3mRNbeicfkPMw==",
+      "version": "0.12.2",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.12.2.tgz",
+      "integrity": "sha512-4PDykCNDjjFo6Ximhq2efiufoUP6pj8KvhB8UI03mLbn/Os1W0y1lmiPJn+NjeBLwFWH9DqW9Vxk/pYek7MtEA==",
       "dev": true,
       "requires": {
-        "@angular-devkit/architect": "0.12.0",
-        "@angular-devkit/build-optimizer": "0.12.0",
-        "@angular-devkit/build-webpack": "0.12.0",
-        "@angular-devkit/core": "7.2.0",
-        "@ngtools/webpack": "7.2.0",
+        "@angular-devkit/architect": "0.12.2",
+        "@angular-devkit/build-optimizer": "0.12.2",
+        "@angular-devkit/build-webpack": "0.12.2",
+        "@angular-devkit/core": "7.2.2",
+        "@ngtools/webpack": "7.2.2",
         "ajv": "6.6.2",
         "autoprefixer": "9.4.3",
         "circular-dependency-plugin": "5.0.2",
@@ -45,7 +45,7 @@
         "opn": "5.3.0",
         "parse5": "4.0.0",
         "portfinder": "1.0.17",
-        "postcss": "7.0.5",
+        "postcss": "7.0.11",
         "postcss-import": "12.0.0",
         "postcss-loader": "3.0.0",
         "raw-loader": "0.5.1",
@@ -54,25 +54,25 @@
         "semver": "5.5.1",
         "source-map-loader": "0.2.4",
         "source-map-support": "0.5.9",
-        "speed-measure-webpack-plugin": "1.2.3",
+        "speed-measure-webpack-plugin": "1.2.5",
         "stats-webpack-plugin": "0.7.0",
         "style-loader": "0.23.1",
         "stylus": "0.54.5",
         "stylus-loader": "3.0.2",
-        "terser-webpack-plugin": "1.1.0",
+        "terser-webpack-plugin": "1.2.1",
         "tree-kill": "1.2.0",
-        "webpack": "4.23.1",
+        "webpack": "4.28.4",
         "webpack-dev-middleware": "3.4.0",
-        "webpack-dev-server": "3.1.10",
+        "webpack-dev-server": "3.1.14",
         "webpack-merge": "4.1.4",
         "webpack-sources": "1.3.0",
         "webpack-subresource-integrity": "1.1.0-rc.6"
       }
     },
     "@angular-devkit/build-optimizer": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.12.0.tgz",
-      "integrity": "sha512-EW2Ky0pi37x/3PBDh3buGHdekYrB/EyN041o59KWytvvWWCYDOv0RTjfXZRg6jgRZd4jLtnfcMRJeyBKo762GA==",
+      "version": "0.12.2",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.12.2.tgz",
+      "integrity": "sha512-5SARSE18X5/churU0Qc0gOfDt5EwuwKsJmIA7hHBzi44iotQm5c8ea0q0acua4/U4K+jOsF6A4Faa08Vr2624A==",
       "dev": true,
       "requires": {
         "loader-utils": "1.1.0",
@@ -114,20 +114,20 @@
       }
     },
     "@angular-devkit/build-webpack": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.12.0.tgz",
-      "integrity": "sha512-23U5Rv+ofbbrXcKiwIQ0tW7puBb/ont25sg1qRQoT+bKhmz2wbTnRbXSls+o7dPiRPeLFDw0Tn5zwCkKPEc9PQ==",
+      "version": "0.12.2",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.12.2.tgz",
+      "integrity": "sha512-Uv3f8XJc/5UTj2T7XjxFYDhuybFIIitLGxBpp/hEIc7eXI4MsJKB6CoDJy+2BQch7c/QjKH7W3dmTxzuSJ2j3g==",
       "dev": true,
       "requires": {
-        "@angular-devkit/architect": "0.12.0",
-        "@angular-devkit/core": "7.2.0",
+        "@angular-devkit/architect": "0.12.2",
+        "@angular-devkit/core": "7.2.2",
         "rxjs": "6.3.3"
       }
     },
     "@angular-devkit/core": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.2.0.tgz",
-      "integrity": "sha512-0T9SJQ2f8qxGClonulQd4pMFfOrEv/it+doPgnalsM8UhQ3RRSrziFGEB97kpLTjH8fIPEgC/80YbtwOarfojg==",
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.2.2.tgz",
+      "integrity": "sha512-gDF8iXiPN870WuBMl7bCQ5+Qz5SjGL/qMcvP4hli5hkn+kMAhgG38ligUK1bbhPQUJ+Z/nSOEmyv8gLHO09SZg==",
       "dev": true,
       "requires": {
         "ajv": "6.6.2",
@@ -654,12 +654,12 @@
       }
     },
     "@babel/generator": {
-      "version": "7.2.2",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.2.tgz",
-      "integrity": "sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==",
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.3.0.tgz",
+      "integrity": "sha512-dZTwMvTgWfhmibq4V9X+LMf6Bgl7zAodRn9PvcPdhlzFMbvUutx74dbEv7Atz3ToeEpevYEJtAwfxq/bDCzHWg==",
       "dev": true,
       "requires": {
-        "@babel/types": "^7.2.2",
+        "@babel/types": "^7.3.0",
         "jsesc": "^2.5.1",
         "lodash": "^4.17.10",
         "source-map": "^0.5.0",
@@ -729,9 +729,9 @@
       }
     },
     "@babel/parser": {
-      "version": "7.2.3",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.3.tgz",
-      "integrity": "sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==",
+      "version": "7.3.1",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.3.1.tgz",
+      "integrity": "sha512-ATz6yX/L8LEnC3dtLQnIx4ydcPxhLcoy9Vl6re00zb2w5lG6itY6Vhnr1KFRPq/FHNsgl/gh2mjNN20f9iJTTA==",
       "dev": true
     },
     "@babel/template": {
@@ -786,9 +786,9 @@
       }
     },
     "@babel/types": {
-      "version": "7.2.2",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.2.tgz",
-      "integrity": "sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==",
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.3.0.tgz",
+      "integrity": "sha512-QkFPw68QqWU1/RVPyBe8SO7lXbPfjtqAxRYQKpFpaB8yMq7X2qAqfwK5LKoQufEkSmO5NQ70O6Kc3Afk03RwXw==",
       "dev": true,
       "requires": {
         "esutils": "^2.0.2",
@@ -813,12 +813,12 @@
       }
     },
     "@ngtools/webpack": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.2.0.tgz",
-      "integrity": "sha512-Ll0EF9sryXdEpOK1MTC4rmhh6lyndsZ7FsKZRWt673R6RpwnR1hL4tf4uU8EmhL9aeMTVVcyIFS+x7v+FC2uHQ==",
+      "version": "7.2.2",
+      "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.2.2.tgz",
+      "integrity": "sha512-xjvQ8tlyyReE69q+whAubwX4fayPoy4NHSIDa429qdcUypkvhSScAtou003oVAKG519rznykDrUHAWtvFMVf4Q==",
       "dev": true,
       "requires": {
-        "@angular-devkit/core": "7.2.0",
+        "@angular-devkit/core": "7.2.2",
         "enhanced-resolve": "4.1.0",
         "rxjs": "6.3.3",
         "tree-kill": "1.2.0",
@@ -922,6 +922,11 @@
         }
       }
     },
+    "@types/geojson": {
+      "version": "7946.0.5",
+      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.5.tgz",
+      "integrity": "sha512-rLlMXpd3rdlrp0+xsrda/hFfOpIxgqFcRpk005UKbHtcdFK+QXAjhBAPnvO58qF4O1LdDXrcaiJxMgstCIlcaw=="
+    },
     "@types/jasmine": {
       "version": "2.8.12",
       "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.12.tgz",
@@ -937,6 +942,22 @@
         "@types/jasmine": "*"
       }
     },
+    "@types/leaflet": {
+      "version": "1.2.14",
+      "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.2.14.tgz",
+      "integrity": "sha512-acP2w5DygY0V7bwmjFmaen5I2iBl8RkWx9kon1IJA7k9mNFgBb6702WApjZSrM4AG1ucJVxFcTlS6nr4HvahEw==",
+      "requires": {
+        "@types/geojson": "*"
+      }
+    },
+    "@types/leaflet.markercluster": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@types/leaflet.markercluster/-/leaflet.markercluster-1.0.3.tgz",
+      "integrity": "sha512-rz4xQcsD3Ha9TcX4nMba9wpNe7HPQ03Hvo8Osi3SLpfaDCydHMoTquOG1IsjQ2aFm/LIHz4Uo4hYoeLv7q082w==",
+      "requires": {
+        "@types/leaflet": "*"
+      }
+    },
     "@types/node": {
       "version": "8.9.5",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
@@ -981,174 +1002,174 @@
       }
     },
     "@webassemblyjs/ast": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.10.tgz",
-      "integrity": "sha512-wTUeaByYN2EA6qVqhbgavtGc7fLTOx0glG2IBsFlrFG51uXIGlYBTyIZMf4SPLo3v1bgV/7lBN3l7Z0R6Hswew==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz",
+      "integrity": "sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/helper-module-context": "1.7.10",
-        "@webassemblyjs/helper-wasm-bytecode": "1.7.10",
-        "@webassemblyjs/wast-parser": "1.7.10"
+        "@webassemblyjs/helper-module-context": "1.7.11",
+        "@webassemblyjs/helper-wasm-bytecode": "1.7.11",
+        "@webassemblyjs/wast-parser": "1.7.11"
       }
     },
     "@webassemblyjs/floating-point-hex-parser": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.10.tgz",
-      "integrity": "sha512-gMsGbI6I3p/P1xL2UxqhNh1ga2HCsx5VBB2i5VvJFAaqAjd2PBTRULc3BpTydabUQEGlaZCzEUQhLoLG7TvEYQ==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz",
+      "integrity": "sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==",
       "dev": true
     },
     "@webassemblyjs/helper-api-error": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.10.tgz",
-      "integrity": "sha512-DoYRlPWtuw3yd5BOr9XhtrmB6X1enYF0/54yNvQWGXZEPDF5PJVNI7zQ7gkcKfTESzp8bIBWailaFXEK/jjCsw==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz",
+      "integrity": "sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==",
       "dev": true
     },
     "@webassemblyjs/helper-buffer": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.10.tgz",
-      "integrity": "sha512-+RMU3dt/dPh4EpVX4u5jxsOlw22tp3zjqE0m3ftU2tsYxnPULb4cyHlgaNd2KoWuwasCQqn8Mhr+TTdbtj3LlA==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz",
+      "integrity": "sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==",
       "dev": true
     },
     "@webassemblyjs/helper-code-frame": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.10.tgz",
-      "integrity": "sha512-UiytbpKAULOEab2hUZK2ywXen4gWJVrgxtwY3Kn+eZaaSWaRM8z/7dAXRSoamhKFiBh1uaqxzE/XD9BLlug3gw==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz",
+      "integrity": "sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/wast-printer": "1.7.10"
+        "@webassemblyjs/wast-printer": "1.7.11"
       }
     },
     "@webassemblyjs/helper-fsm": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.10.tgz",
-      "integrity": "sha512-w2vDtUK9xeSRtt5+RnnlRCI7wHEvLjF0XdnxJpgx+LJOvklTZPqWkuy/NhwHSLP19sm9H8dWxKeReMR7sCkGZA==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz",
+      "integrity": "sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==",
       "dev": true
     },
     "@webassemblyjs/helper-module-context": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.10.tgz",
-      "integrity": "sha512-yE5x/LzZ3XdPdREmJijxzfrf+BDRewvO0zl8kvORgSWmxpRrkqY39KZSq6TSgIWBxkK4SrzlS3BsMCv2s1FpsQ==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz",
+      "integrity": "sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==",
       "dev": true
     },
     "@webassemblyjs/helper-wasm-bytecode": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.10.tgz",
-      "integrity": "sha512-u5qy4SJ/OrxKxZqJ9N3qH4ZQgHaAzsopsYwLvoWJY6Q33r8PhT3VPyNMaJ7ZFoqzBnZlCcS/0f4Sp8WBxylXfg==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz",
+      "integrity": "sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==",
       "dev": true
     },
     "@webassemblyjs/helper-wasm-section": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.10.tgz",
-      "integrity": "sha512-Ecvww6sCkcjatcyctUrn22neSJHLN/TTzolMGG/N7S9rpbsTZ8c6Bl98GpSpV77EvzNijiNRHBG0+JO99qKz6g==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz",
+      "integrity": "sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.7.10",
-        "@webassemblyjs/helper-buffer": "1.7.10",
-        "@webassemblyjs/helper-wasm-bytecode": "1.7.10",
-        "@webassemblyjs/wasm-gen": "1.7.10"
+        "@webassemblyjs/ast": "1.7.11",
+        "@webassemblyjs/helper-buffer": "1.7.11",
+        "@webassemblyjs/helper-wasm-bytecode": "1.7.11",
+        "@webassemblyjs/wasm-gen": "1.7.11"
       }
     },
     "@webassemblyjs/ieee754": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.10.tgz",
-      "integrity": "sha512-HRcWcY+YWt4+s/CvQn+vnSPfRaD4KkuzQFt5MNaELXXHSjelHlSEA8ZcqT69q0GTIuLWZ6JaoKar4yWHVpZHsQ==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz",
+      "integrity": "sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==",
       "dev": true,
       "requires": {
         "@xtuc/ieee754": "^1.2.0"
       }
     },
     "@webassemblyjs/leb128": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.10.tgz",
-      "integrity": "sha512-og8MciYlA8hvzCLR71hCuZKPbVBfLQeHv7ImKZ4nlyxrYbG7uJHYtHiHu6OV9SqrGuD03H/HtXC4Bgdjfm9FHw==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.7.11.tgz",
+      "integrity": "sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==",
       "dev": true,
       "requires": {
         "@xtuc/long": "4.2.1"
       }
     },
     "@webassemblyjs/utf8": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.10.tgz",
-      "integrity": "sha512-Ng6Pxv6siyZp635xCSnH3mKmIFgqWPCcGdoo0GBYgyGdxu7cUj4agV7Uu1a8REP66UYUFXJLudeGgd4RvuJAnQ==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.7.11.tgz",
+      "integrity": "sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==",
       "dev": true
     },
     "@webassemblyjs/wasm-edit": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.10.tgz",
-      "integrity": "sha512-e9RZFQlb+ZuYcKRcW9yl+mqX/Ycj9+3/+ppDI8nEE/NCY6FoK8f3dKBcfubYV/HZn44b+ND4hjh+4BYBt+sDnA==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz",
+      "integrity": "sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.7.10",
-        "@webassemblyjs/helper-buffer": "1.7.10",
-        "@webassemblyjs/helper-wasm-bytecode": "1.7.10",
-        "@webassemblyjs/helper-wasm-section": "1.7.10",
-        "@webassemblyjs/wasm-gen": "1.7.10",
-        "@webassemblyjs/wasm-opt": "1.7.10",
-        "@webassemblyjs/wasm-parser": "1.7.10",
-        "@webassemblyjs/wast-printer": "1.7.10"
+        "@webassemblyjs/ast": "1.7.11",
+        "@webassemblyjs/helper-buffer": "1.7.11",
+        "@webassemblyjs/helper-wasm-bytecode": "1.7.11",
+        "@webassemblyjs/helper-wasm-section": "1.7.11",
+        "@webassemblyjs/wasm-gen": "1.7.11",
+        "@webassemblyjs/wasm-opt": "1.7.11",
+        "@webassemblyjs/wasm-parser": "1.7.11",
+        "@webassemblyjs/wast-printer": "1.7.11"
       }
     },
     "@webassemblyjs/wasm-gen": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.10.tgz",
-      "integrity": "sha512-M0lb6cO2Y0PzDye/L39PqwV+jvO+2YxEG5ax+7dgq7EwXdAlpOMx1jxyXJTScQoeTpzOPIb+fLgX/IkLF8h2yw==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz",
+      "integrity": "sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.7.10",
-        "@webassemblyjs/helper-wasm-bytecode": "1.7.10",
-        "@webassemblyjs/ieee754": "1.7.10",
-        "@webassemblyjs/leb128": "1.7.10",
-        "@webassemblyjs/utf8": "1.7.10"
+        "@webassemblyjs/ast": "1.7.11",
+        "@webassemblyjs/helper-wasm-bytecode": "1.7.11",
+        "@webassemblyjs/ieee754": "1.7.11",
+        "@webassemblyjs/leb128": "1.7.11",
+        "@webassemblyjs/utf8": "1.7.11"
       }
     },
     "@webassemblyjs/wasm-opt": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.10.tgz",
-      "integrity": "sha512-R66IHGCdicgF5ZliN10yn5HaC7vwYAqrSVJGjtJJQp5+QNPBye6heWdVH/at40uh0uoaDN/UVUfXK0gvuUqtVg==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz",
+      "integrity": "sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.7.10",
-        "@webassemblyjs/helper-buffer": "1.7.10",
-        "@webassemblyjs/wasm-gen": "1.7.10",
-        "@webassemblyjs/wasm-parser": "1.7.10"
+        "@webassemblyjs/ast": "1.7.11",
+        "@webassemblyjs/helper-buffer": "1.7.11",
+        "@webassemblyjs/wasm-gen": "1.7.11",
+        "@webassemblyjs/wasm-parser": "1.7.11"
       }
     },
     "@webassemblyjs/wasm-parser": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.10.tgz",
-      "integrity": "sha512-AEv8mkXVK63n/iDR3T693EzoGPnNAwKwT3iHmKJNBrrALAhhEjuPzo/lTE4U7LquEwyvg5nneSNdTdgrBaGJcA==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz",
+      "integrity": "sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.7.10",
-        "@webassemblyjs/helper-api-error": "1.7.10",
-        "@webassemblyjs/helper-wasm-bytecode": "1.7.10",
-        "@webassemblyjs/ieee754": "1.7.10",
-        "@webassemblyjs/leb128": "1.7.10",
-        "@webassemblyjs/utf8": "1.7.10"
+        "@webassemblyjs/ast": "1.7.11",
+        "@webassemblyjs/helper-api-error": "1.7.11",
+        "@webassemblyjs/helper-wasm-bytecode": "1.7.11",
+        "@webassemblyjs/ieee754": "1.7.11",
+        "@webassemblyjs/leb128": "1.7.11",
+        "@webassemblyjs/utf8": "1.7.11"
       }
     },
     "@webassemblyjs/wast-parser": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.10.tgz",
-      "integrity": "sha512-YTPEtOBljkCL0VjDp4sHe22dAYSm3ZwdJ9+2NTGdtC7ayNvuip1wAhaAS8Zt9Q6SW9E5Jf5PX7YE3XWlrzR9cw==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz",
+      "integrity": "sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.7.10",
-        "@webassemblyjs/floating-point-hex-parser": "1.7.10",
-        "@webassemblyjs/helper-api-error": "1.7.10",
-        "@webassemblyjs/helper-code-frame": "1.7.10",
-        "@webassemblyjs/helper-fsm": "1.7.10",
+        "@webassemblyjs/ast": "1.7.11",
+        "@webassemblyjs/floating-point-hex-parser": "1.7.11",
+        "@webassemblyjs/helper-api-error": "1.7.11",
+        "@webassemblyjs/helper-code-frame": "1.7.11",
+        "@webassemblyjs/helper-fsm": "1.7.11",
         "@xtuc/long": "4.2.1"
       }
     },
     "@webassemblyjs/wast-printer": {
-      "version": "1.7.10",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.10.tgz",
-      "integrity": "sha512-mJ3QKWtCchL1vhU/kZlJnLPuQZnlDOdZsyP0bbLWPGdYsQDnSBvyTLhzwBA3QAMlzEL9V4JHygEmK6/OTEyytA==",
+      "version": "1.7.11",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz",
+      "integrity": "sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.7.10",
-        "@webassemblyjs/wast-parser": "1.7.10",
+        "@webassemblyjs/ast": "1.7.11",
+        "@webassemblyjs/wast-parser": "1.7.11",
         "@xtuc/long": "4.2.1"
       }
     },
@@ -1235,9 +1256,9 @@
       "dev": true
     },
     "ajv-keywords": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz",
-      "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.3.0.tgz",
+      "integrity": "sha512-CMzN9S62ZOO4sA/mJZIO4S++ZM7KFWzH3PPWkveLhy4OZ9i1/VatgwWMD46w/XbGCBy7Ye0gCk+Za6mmyfKK7g==",
       "dev": true
     },
     "amdefine": {
@@ -1253,9 +1274,9 @@
       "dev": true
     },
     "ansi-escapes": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz",
-      "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+      "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
       "dev": true
     },
     "ansi-html": {
@@ -1513,25 +1534,6 @@
         "num2fraction": "^1.2.2",
         "postcss": "^7.0.6",
         "postcss-value-parser": "^3.3.1"
-      },
-      "dependencies": {
-        "postcss": {
-          "version": "7.0.8",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.8.tgz",
-          "integrity": "sha512-WudsIzuTKRw9IInRTPBgVXJ7DKR26HT09Rxp0g3w0Fqh3TUtYICcUmvC0xURj04o3vdcDtnjCAUCECg/p341iQ==",
-          "dev": true,
-          "requires": {
-            "chalk": "^2.4.2",
-            "source-map": "^0.6.1",
-            "supports-color": "^6.0.0"
-          }
-        },
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
       }
     },
     "aws-sign2": {
@@ -1995,20 +1997,20 @@
       }
     },
     "browserslist": {
-      "version": "4.3.7",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.7.tgz",
-      "integrity": "sha512-pWQv51Ynb0MNk9JGMCZ8VkM785/4MQNXiFYtPqI7EEP0TJO+/d/NqRVn1uiAN0DNbnlUSpL2sh16Kspasv3pUQ==",
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.4.1.tgz",
+      "integrity": "sha512-pEBxEXg7JwaakBXjATYw/D1YZh4QUSCX/Mnd/wnqSRPPSi1U39iDhDoKGoBUcraKdxDlrYqJxSI5nNvD+dWP2A==",
       "dev": true,
       "requires": {
-        "caniuse-lite": "^1.0.30000925",
-        "electron-to-chromium": "^1.3.96",
+        "caniuse-lite": "^1.0.30000929",
+        "electron-to-chromium": "^1.3.103",
         "node-releases": "^1.1.3"
       }
     },
     "browserstack": {
-      "version": "1.5.1",
-      "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.1.tgz",
-      "integrity": "sha512-O8VMT64P9NOLhuIoD4YngyxBURefaSdR4QdhG8l6HZ9VxtU7jc3m6jLufFwKA5gaf7fetfB2TnRJnMxyob+heg==",
+      "version": "1.5.2",
+      "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.2.tgz",
+      "integrity": "sha512-+6AFt9HzhKykcPF79W6yjEUJcdvZOV0lIXdkORXMJftGrDl0OKWqRF4GHqpDNkxiceDT/uB7Fb/aDwktvXX7dg==",
       "dev": true,
       "requires": {
         "https-proxy-agent": "^2.2.1"
@@ -2152,9 +2154,9 @@
       }
     },
     "caniuse-lite": {
-      "version": "1.0.30000927",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000927.tgz",
-      "integrity": "sha512-ogq4NbUWf1uG/j66k0AmiO3GjqJAlQyF8n4w8a954cbCyFKmYGvRtgz6qkq2fWuduTXHibX7GyYL5Pg58Aks2g==",
+      "version": "1.0.30000933",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000933.tgz",
+      "integrity": "sha512-d3QXv7eFTU40DSedSP81dV/ajcGSKpT+GW+uhtWmLvQm9bPk0KK++7i1e2NSW/CXGZhWFt2mFbFtCJ5I5bMuVA==",
       "dev": true
     },
     "canonical-path": {
@@ -3031,9 +3033,9 @@
       }
     },
     "dir-glob": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.0.tgz",
-      "integrity": "sha512-YqrO+bduKFqPgspvpjDAaKk0qhmvY+SY7NjIRljCDAy6CX7Ft65irIduHbrYXhy+BxJnYKjWuREw6X42w9/+DQ==",
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz",
+      "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==",
       "dev": true,
       "requires": {
         "path-type": "^3.0.0"
@@ -3135,9 +3137,9 @@
       "dev": true
     },
     "electron-to-chromium": {
-      "version": "1.3.100",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.100.tgz",
-      "integrity": "sha512-cEUzis2g/RatrVf8x26L8lK5VEls1AGnLHk6msluBUg/NTB4wcXzExTsGscFq+Vs4WBBU2zbLLySvD4C0C3hwg==",
+      "version": "1.3.109",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.109.tgz",
+      "integrity": "sha512-1qhgVZD9KIULMyeBkbjU/dWmm30zpPUfdWZfVO3nPhbtqMHJqHr4Ua5wBcWtAymVFrUCuAJxjMF1OhG+bR21Ow==",
       "dev": true
     },
     "elliptic": {
@@ -3397,9 +3399,9 @@
       "dev": true
     },
     "events": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
-      "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
+      "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==",
       "dev": true
     },
     "eventsource": {
@@ -4017,9 +4019,9 @@
       "dev": true
     },
     "fsevents": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
-      "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz",
+      "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==",
       "dev": true,
       "optional": true,
       "requires": {
@@ -4045,7 +4047,7 @@
           "optional": true
         },
         "are-we-there-yet": {
-          "version": "1.1.4",
+          "version": "1.1.5",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -4069,7 +4071,7 @@
           }
         },
         "chownr": {
-          "version": "1.0.1",
+          "version": "1.1.1",
           "bundled": true,
           "dev": true,
           "optional": true
@@ -4105,7 +4107,7 @@
           }
         },
         "deep-extend": {
-          "version": "0.5.1",
+          "version": "0.6.0",
           "bundled": true,
           "dev": true,
           "optional": true
@@ -4154,7 +4156,7 @@
           }
         },
         "glob": {
-          "version": "7.1.2",
+          "version": "7.1.3",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -4174,12 +4176,12 @@
           "optional": true
         },
         "iconv-lite": {
-          "version": "0.4.21",
+          "version": "0.4.24",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "safer-buffer": "^2.1.0"
+            "safer-buffer": ">= 2.1.2 < 3"
           }
         },
         "ignore-walk": {
@@ -4240,16 +4242,16 @@
           "dev": true
         },
         "minipass": {
-          "version": "2.2.4",
+          "version": "2.3.5",
           "bundled": true,
           "dev": true,
           "requires": {
-            "safe-buffer": "^5.1.1",
+            "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
           }
         },
         "minizlib": {
-          "version": "1.1.0",
+          "version": "1.2.1",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -4272,7 +4274,7 @@
           "optional": true
         },
         "needle": {
-          "version": "2.2.0",
+          "version": "2.2.4",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -4283,18 +4285,18 @@
           }
         },
         "node-pre-gyp": {
-          "version": "0.10.0",
+          "version": "0.10.3",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
             "detect-libc": "^1.0.2",
             "mkdirp": "^0.5.1",
-            "needle": "^2.2.0",
+            "needle": "^2.2.1",
             "nopt": "^4.0.1",
             "npm-packlist": "^1.1.6",
             "npmlog": "^4.0.2",
-            "rc": "^1.1.7",
+            "rc": "^1.2.7",
             "rimraf": "^2.6.1",
             "semver": "^5.3.0",
             "tar": "^4"
@@ -4311,13 +4313,13 @@
           }
         },
         "npm-bundled": {
-          "version": "1.0.3",
+          "version": "1.0.5",
           "bundled": true,
           "dev": true,
           "optional": true
         },
         "npm-packlist": {
-          "version": "1.1.10",
+          "version": "1.2.0",
           "bundled": true,
           "dev": true,
           "optional": true,
@@ -4392,12 +4394,12 @@
           "optional": true
         },
         "rc": {
-          "version": "1.2.7",
+          "version": "1.2.8",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "deep-extend": "^0.5.1",
+            "deep-extend": "^0.6.0",
             "ini": "~1.3.0",
             "minimist": "^1.2.0",
             "strip-json-comments": "~2.0.1"
@@ -4427,16 +4429,16 @@
           }
         },
         "rimraf": {
-          "version": "2.6.2",
+          "version": "2.6.3",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "glob": "^7.0.5"
+            "glob": "^7.1.3"
           }
         },
         "safe-buffer": {
-          "version": "5.1.1",
+          "version": "5.1.2",
           "bundled": true,
           "dev": true
         },
@@ -4453,7 +4455,7 @@
           "optional": true
         },
         "semver": {
-          "version": "5.5.0",
+          "version": "5.6.0",
           "bundled": true,
           "dev": true,
           "optional": true
@@ -4504,17 +4506,17 @@
           "optional": true
         },
         "tar": {
-          "version": "4.4.1",
+          "version": "4.4.8",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "chownr": "^1.0.1",
+            "chownr": "^1.1.1",
             "fs-minipass": "^1.2.5",
-            "minipass": "^2.2.4",
-            "minizlib": "^1.1.0",
+            "minipass": "^2.3.4",
+            "minizlib": "^1.1.1",
             "mkdirp": "^0.5.0",
-            "safe-buffer": "^5.1.1",
+            "safe-buffer": "^5.1.2",
             "yallist": "^3.0.2"
           }
         },
@@ -4525,12 +4527,12 @@
           "optional": true
         },
         "wide-align": {
-          "version": "1.1.2",
+          "version": "1.1.3",
           "bundled": true,
           "dev": true,
           "optional": true,
           "requires": {
-            "string-width": "^1.0.2"
+            "string-width": "^1.0.2 || 2"
           }
         },
         "wrappy": {
@@ -4539,7 +4541,7 @@
           "dev": true
         },
         "yallist": {
-          "version": "3.0.2",
+          "version": "3.0.3",
           "bundled": true,
           "dev": true
         }
@@ -4726,9 +4728,9 @@
       "dev": true
     },
     "handle-thing": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
-      "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz",
+      "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==",
       "dev": true
     },
     "handlebars": {
@@ -5863,9 +5865,9 @@
       "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg=="
     },
     "js-base64": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.0.tgz",
-      "integrity": "sha512-wlEBIZ5LP8usDylWbDNhKPEFVFdI5hCHpnVoT/Ysvoi/PRhJENm/Rlh9TvjYB38HFfKZN7OzEbRjmjvLkFw11g==",
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
+      "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==",
       "dev": true,
       "optional": true
     },
@@ -6141,6 +6143,16 @@
         "invert-kv": "^1.0.0"
       }
     },
+    "leaflet": {
+      "version": "1.3.4",
+      "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.3.4.tgz",
+      "integrity": "sha512-FYL1LGFdj6v+2Ifpw+AcFIuIOqjNggfoLUwuwQv6+3sS21Za7Wvapq+LhbSE4NDXrEj6eYnW3y7LsaBICpyXtw=="
+    },
+    "leaflet.markercluster": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz",
+      "integrity": "sha512-ZSEpE/EFApR0bJ1w/dUGwTSUvWlpalKqIzkaYdYB7jaftQA/Y2Jav+eT4CMtEYFj+ZK4mswP13Q2acnPBnhGOw=="
+    },
     "less": {
       "version": "3.9.0",
       "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz",
@@ -6229,9 +6241,9 @@
       }
     },
     "loader-runner": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz",
-      "integrity": "sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz",
+      "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==",
       "dev": true
     },
     "loader-utils": {
@@ -6414,9 +6426,9 @@
       }
     },
     "math-random": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz",
-      "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=",
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
+      "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==",
       "dev": true
     },
     "md5.js": {
@@ -6437,14 +6449,14 @@
       "dev": true
     },
     "mem": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz",
-      "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/mem/-/mem-4.1.0.tgz",
+      "integrity": "sha512-I5u6Q1x7wxO0kdOpYBB28xueHADYps5uty/zg936CiG8NTe5sJL8EjrCuLneuDW3PlMdZBGDIn8BirEVdovZvg==",
       "dev": true,
       "requires": {
         "map-age-cleaner": "^0.1.1",
         "mimic-fn": "^1.0.0",
-        "p-is-promise": "^1.1.0"
+        "p-is-promise": "^2.0.0"
       }
     },
     "memory-fs": {
@@ -6792,9 +6804,9 @@
       }
     },
     "node-libs-browser": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz",
-      "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==",
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz",
+      "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==",
       "dev": true,
       "requires": {
         "assert": "^1.1.1",
@@ -6804,7 +6816,7 @@
         "constants-browserify": "^1.0.0",
         "crypto-browserify": "^3.11.0",
         "domain-browser": "^1.1.1",
-        "events": "^1.0.0",
+        "events": "^3.0.0",
         "https-browserify": "^1.0.0",
         "os-browserify": "^0.3.0",
         "path-browserify": "0.0.0",
@@ -6818,7 +6830,7 @@
         "timers-browserify": "^2.0.4",
         "tty-browserify": "0.0.0",
         "url": "^0.11.0",
-        "util": "^0.10.3",
+        "util": "^0.11.0",
         "vm-browserify": "0.0.4"
       },
       "dependencies": {
@@ -6831,9 +6843,9 @@
       }
     },
     "node-releases": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.3.tgz",
-      "integrity": "sha512-6VrvH7z6jqqNFY200kdB6HdzkgM96Oaj9v3dqGfgp6mF+cHmU4wyQKZ2/WPDRVoR0Jz9KqbamaBN0ZhdUaysUQ==",
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.6.tgz",
+      "integrity": "sha512-lODUVHEIZutZx+TDdOk47qLik8FJMXzJ+WnyUGci1MTvTOyzZrz5eVPIIpc5Hb3NfHZGeGHeuwrRYVI1PEITWg==",
       "dev": true,
       "requires": {
         "semver": "^5.3.0"
@@ -6907,9 +6919,9 @@
       }
     },
     "normalize-package-data": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
-      "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.2.tgz",
+      "integrity": "sha512-YcMnjqeoUckXTPKZSAsPjUPLxH85XotbpqK3w4RyCwdFQSU5FxxBys8buehkSfg0j9fKvV1hn7O0+8reEgkAiw==",
       "dev": true,
       "requires": {
         "hosted-git-info": "^2.1.4",
@@ -7232,9 +7244,9 @@
       "dev": true
     },
     "p-is-promise": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz",
-      "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=",
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.0.0.tgz",
+      "integrity": "sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg==",
       "dev": true
     },
     "p-limit": {
@@ -7268,9 +7280,9 @@
       "dev": true
     },
     "pako": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz",
-      "integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==",
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.8.tgz",
+      "integrity": "sha512-6i0HVbUfcKaTv+EG8ZTr75az7GFXcLYk9UyLEg7Notv/Ma+z/UG3TCoz6GiNeOrn1E/e63I0X/Hpw18jHOTUnA==",
       "dev": true
     },
     "parallel-transform": {
@@ -7285,16 +7297,17 @@
       }
     },
     "parse-asn1": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
-      "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.3.tgz",
+      "integrity": "sha512-VrPoetlz7B/FqjBLD2f5wBVZvsZVLnRUrxVLfRYhGXCODa/NWE4p3Wp+6+aV3ZPL3KM7/OZmxDIwwijD7yuucg==",
       "dev": true,
       "requires": {
         "asn1.js": "^4.0.0",
         "browserify-aes": "^1.0.0",
         "create-hash": "^1.1.0",
         "evp_bytestokey": "^1.0.0",
-        "pbkdf2": "^3.0.3"
+        "pbkdf2": "^3.0.3",
+        "safe-buffer": "^5.1.1"
       }
     },
     "parse-glob": {
@@ -7495,14 +7508,14 @@
       "dev": true
     },
     "postcss": {
-      "version": "7.0.5",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.5.tgz",
-      "integrity": "sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ==",
+      "version": "7.0.11",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.11.tgz",
+      "integrity": "sha512-9AXb//5UcjeOEof9T+yPw3XTa5SL207ZOIC/lHYP4mbUTEh4M0rDAQekQpVANCZdwQwKhBtFZCk3i3h3h2hdWg==",
       "dev": true,
       "requires": {
-        "chalk": "^2.4.1",
+        "chalk": "^2.4.2",
         "source-map": "^0.6.1",
-        "supports-color": "^5.5.0"
+        "supports-color": "^6.1.0"
       },
       "dependencies": {
         "source-map": {
@@ -7510,15 +7523,6 @@
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
           "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
           "dev": true
-        },
-        "supports-color": {
-          "version": "5.5.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-          "dev": true,
-          "requires": {
-            "has-flag": "^3.0.0"
-          }
         }
       }
     },
@@ -8026,9 +8030,9 @@
       }
     },
     "reflect-metadata": {
-      "version": "0.1.12",
-      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz",
-      "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==",
+      "version": "0.1.13",
+      "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
+      "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==",
       "dev": true
     },
     "regenerate": {
@@ -8974,38 +8978,81 @@
       "dev": true
     },
     "spdy": {
-      "version": "3.4.7",
-      "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz",
-      "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz",
+      "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==",
       "dev": true,
       "requires": {
-        "debug": "^2.6.8",
-        "handle-thing": "^1.2.5",
+        "debug": "^4.1.0",
+        "handle-thing": "^2.0.0",
         "http-deceiver": "^1.2.7",
-        "safe-buffer": "^5.0.1",
         "select-hose": "^2.0.0",
-        "spdy-transport": "^2.0.18"
+        "spdy-transport": "^3.0.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "dev": true
+        }
       }
     },
     "spdy-transport": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.1.tgz",
-      "integrity": "sha512-q7D8c148escoB3Z7ySCASadkegMmUZW8Wb/Q1u0/XBgDKMO880rLQDj8Twiew/tYi7ghemKUi/whSYOwE17f5Q==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
+      "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
       "dev": true,
       "requires": {
-        "debug": "^2.6.8",
-        "detect-node": "^2.0.3",
+        "debug": "^4.1.0",
+        "detect-node": "^2.0.4",
         "hpack.js": "^2.1.6",
-        "obuf": "^1.1.1",
-        "readable-stream": "^2.2.9",
-        "safe-buffer": "^5.0.1",
-        "wbuf": "^1.7.2"
+        "obuf": "^1.1.2",
+        "readable-stream": "^3.0.6",
+        "wbuf": "^1.7.3"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+          "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.1",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+          "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "3.1.1",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz",
+          "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==",
+          "dev": true,
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
       }
     },
     "speed-measure-webpack-plugin": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.2.3.tgz",
-      "integrity": "sha512-p+taQ69VkRUXYMoZOx2nxV/Tz8tt79ahctoZJyJDHWP7fEYvwFNf5Pd73k5kZ6auu0pTsPNLEUwWpM8mCk85Zw==",
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.2.5.tgz",
+      "integrity": "sha512-S/guYjC4Izn5wY2d0+M4zowED/F77Lxh9yjkTZ+XAr244pr9c1MYNcXcRe9lx2hmAj0GPbOrBXgOF2YIp/CZ8A==",
       "dev": true,
       "requires": {
         "chalk": "^2.0.1"
@@ -9027,9 +9074,9 @@
       "dev": true
     },
     "sshpk": {
-      "version": "1.16.0",
-      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz",
-      "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==",
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
       "dev": true,
       "requires": {
         "asn1": "~0.2.3",
@@ -9099,9 +9146,9 @@
       }
     },
     "stream-browserify": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
-      "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+      "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==",
       "dev": true,
       "requires": {
         "inherits": "~2.0.1",
@@ -9281,9 +9328,9 @@
       }
     },
     "supports-color": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
-      "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+      "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
       "dev": true,
       "requires": {
         "has-flag": "^3.0.0"
@@ -9333,9 +9380,9 @@
       }
     },
     "terser-webpack-plugin": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.1.0.tgz",
-      "integrity": "sha512-61lV0DSxMAZ8AyZG7/A4a3UPlrbOBo8NIQ4tJzLPAdGOQ+yoNC7l5ijEow27lBAL2humer01KLS6bGIMYQxKoA==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.1.tgz",
+      "integrity": "sha512-GGSt+gbT0oKcMDmPx4SRSfJPE1XaN3kQRWG4ghxKQw9cn5G9x6aCKSsgYdvyM0na9NJ4Drv0RG6jbBByZ5CMjw==",
       "dev": true,
       "requires": {
         "cacache": "^11.0.2",
@@ -9698,9 +9745,9 @@
       },
       "dependencies": {
         "resolve": {
-          "version": "1.9.0",
-          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz",
-          "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==",
+          "version": "1.10.0",
+          "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz",
+          "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==",
           "dev": true,
           "requires": {
             "path-parse": "^1.0.6"
@@ -9817,56 +9864,6 @@
         }
       }
     },
-    "uglifyjs-webpack-plugin": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz",
-      "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==",
-      "dev": true,
-      "requires": {
-        "cacache": "^10.0.4",
-        "find-cache-dir": "^1.0.0",
-        "schema-utils": "^0.4.5",
-        "serialize-javascript": "^1.4.0",
-        "source-map": "^0.6.1",
-        "uglify-es": "^3.3.4",
-        "webpack-sources": "^1.1.0",
-        "worker-farm": "^1.5.2"
-      },
-      "dependencies": {
-        "commander": {
-          "version": "2.13.0",
-          "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
-          "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
-          "dev": true
-        },
-        "schema-utils": {
-          "version": "0.4.7",
-          "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
-          "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
-          "dev": true,
-          "requires": {
-            "ajv": "^6.1.0",
-            "ajv-keywords": "^3.1.0"
-          }
-        },
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        },
-        "uglify-es": {
-          "version": "3.3.9",
-          "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
-          "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
-          "dev": true,
-          "requires": {
-            "commander": "~2.13.0",
-            "source-map": "~0.6.1"
-          }
-        }
-      }
-    },
     "ultron": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
@@ -10046,9 +10043,9 @@
       }
     },
     "util": {
-      "version": "0.10.4",
-      "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
-      "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+      "version": "0.11.1",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
+      "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
       "dev": true,
       "requires": {
         "inherits": "2.0.3"
@@ -10154,15 +10151,15 @@
       }
     },
     "webpack": {
-      "version": "4.23.1",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.23.1.tgz",
-      "integrity": "sha512-iE5Cu4rGEDk7ONRjisTOjVHv3dDtcFfwitSxT7evtYj/rANJpt1OuC/Kozh1pBa99AUBr1L/LsaNB+D9Xz3CEg==",
+      "version": "4.28.4",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.28.4.tgz",
+      "integrity": "sha512-NxjD61WsK/a3JIdwWjtIpimmvE6UrRi3yG54/74Hk9rwNj5FPkA4DJCf1z4ByDWLkvZhTZE+P3C/eh6UD5lDcw==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.7.10",
-        "@webassemblyjs/helper-module-context": "1.7.10",
-        "@webassemblyjs/wasm-edit": "1.7.10",
-        "@webassemblyjs/wasm-parser": "1.7.10",
+        "@webassemblyjs/ast": "1.7.11",
+        "@webassemblyjs/helper-module-context": "1.7.11",
+        "@webassemblyjs/wasm-edit": "1.7.11",
+        "@webassemblyjs/wasm-parser": "1.7.11",
         "acorn": "^5.6.2",
         "acorn-dynamic-import": "^3.0.0",
         "ajv": "^6.1.0",
@@ -10180,7 +10177,7 @@
         "node-libs-browser": "^2.0.0",
         "schema-utils": "^0.4.4",
         "tapable": "^1.1.0",
-        "uglifyjs-webpack-plugin": "^1.2.4",
+        "terser-webpack-plugin": "^1.1.0",
         "watchpack": "^1.5.0",
         "webpack-sources": "^1.3.0"
       },
@@ -10245,9 +10242,9 @@
       }
     },
     "webpack-dev-server": {
-      "version": "3.1.10",
-      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.10.tgz",
-      "integrity": "sha512-RqOAVjfqZJtQcB0LmrzJ5y4Jp78lv9CK0MZ1YJDTaTmedMZ9PU9FLMQNrMCfVu8hHzaVLVOJKBlGEHMN10z+ww==",
+      "version": "3.1.14",
+      "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz",
+      "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==",
       "dev": true,
       "requires": {
         "ansi-html": "0.0.7",
@@ -10269,12 +10266,14 @@
         "portfinder": "^1.0.9",
         "schema-utils": "^1.0.0",
         "selfsigned": "^1.9.1",
+        "semver": "^5.6.0",
         "serve-index": "^1.7.2",
         "sockjs": "0.3.19",
         "sockjs-client": "1.3.0",
-        "spdy": "^3.4.1",
+        "spdy": "^4.0.0",
         "strip-ansi": "^3.0.0",
         "supports-color": "^5.1.0",
+        "url": "^0.11.0",
         "webpack-dev-middleware": "3.4.0",
         "webpack-log": "^2.0.0",
         "yargs": "12.0.2"
@@ -10460,6 +10459,12 @@
             "once": "^1.3.1"
           }
         },
+        "semver": {
+          "version": "5.6.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
+          "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
+          "dev": true
+        },
         "string-width": {
           "version": "2.1.1",
           "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 8518e439d78d5bc9ec5c72a58c80decc8765e33b..3814d1f71e95bd6f9a0ac10834666b48f5a735bf 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -21,22 +21,20 @@
     "@angular/platform-browser": "7.0.4",
     "@angular/platform-browser-dynamic": "7.0.4",
     "@angular/router": "7.0.4",
-    "@types/leaflet": "^1.2.14",
-    "@types/leaflet.markercluster": "^1.0.3",
+    "@types/leaflet": "1.2.14",
+    "@types/leaflet.markercluster": "1.0.3",
     "@ng-bootstrap/ng-bootstrap": "4.0.0",
     "bootstrap": "4.1.3",
     "core-js": "2.5.7",
     "font-awesome": "4.7.0",
-    "jquery": "^3.3.1",
-    "leaflet": "^1.3.4",
-    "leaflet.markercluster": "^1.4.1",
-    "popper": "^1.0.1",
+    "leaflet": "1.3.4",
+    "leaflet.markercluster": "1.4.1",
     "rxjs": "6.3.3",
     "trait-ontology-widget": "git+https://github.com/gnpis/trait-ontology-widget.git#v2.2.1",
     "zone.js": "0.8.26"
   },
   "devDependencies": {
-    "@angular-devkit/build-angular": "^0.12.2",
+    "@angular-devkit/build-angular": "0.12.2",
     "@angular/cli": "7.0.6",
     "@angular/compiler-cli": "7.0.4",
     "@angular/language-service": "7.0.4",
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 9d785120efcba6a806b32fc668647f506a98c327..f3486f6fcb9ba85c2cabf2a77e9fe73d513e47b0 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -19,7 +19,10 @@ import { ErrorComponent } from './error/error.component';
 import { ErrorInterceptorService } from './error-interceptor.service';
 import { TraitOntologyWidgetComponent } from './form/trait-ontology-widget/trait-ontology-widget.component';
 import { FacetsComponent } from './result-page/facets/facets.component';
-
+import { CardRowComponent } from './card-row/card-row.component';
+import { CardSectionComponent } from './card-section/card-section.component';
+import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component';
+import { CardTableComponent } from './card-table/card-table.component';
 
 @NgModule({
     declarations: [
@@ -35,7 +38,11 @@ import { FacetsComponent } from './result-page/facets/facets.component';
         DocumentComponent,
         ErrorComponent,
         TraitOntologyWidgetComponent,
-        FacetsComponent
+        FacetsComponent,
+        CardRowComponent,
+        CardSectionComponent,
+        LoadingSpinnerComponent,
+        CardTableComponent,
     ],
     imports: [
         BrowserModule,
diff --git a/frontend/src/app/brapi.service.spec.ts b/frontend/src/app/brapi.service.spec.ts
index 36ef86b27e238e09e7b3ad3dc3f5513aa4b0a76b..898d0c90a492a020fb63d66c71df9f865ac6a6ee 100644
--- a/frontend/src/app/brapi.service.spec.ts
+++ b/frontend/src/app/brapi.service.spec.ts
@@ -2,7 +2,17 @@ import { TestBed } from '@angular/core/testing';
 
 import { BrapiService } from './brapi.service';
 import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
-import { SiteModel } from './models/site.model';
+import {
+    BrapiContacts,
+    BrapiGermplasm,
+    BrapiLocation,
+    BrapiObservationVariable,
+    BrapiResult,
+    BrapiResults,
+    BrapiStudy,
+    BrapiTrial
+} from './models/brapi.model';
+import { DataDiscoverySource } from './models/data-discovery.model';
 
 describe('BrapiService', () => {
 
@@ -20,30 +30,185 @@ describe('BrapiService', () => {
 
     afterAll(() => http.verify());
 
-    it('should return an Observable of 1 SiteModel', () => {
-        const hardCodedSite: SiteModel = {
-            result: {
-                locationDbId: 1,
-                latitude: 1,
-                longitude: 1,
-                altitude: 1,
-                institutionName: '',
-                institutionAdress: '',
-                countryName: '',
-                countryCode: '',
-                locationType: '',
-                abbreviation: '',
-                name: 'site1',
-                additionalInfo: {}
-            }
+    const location: BrapiLocation = {
+        locationDbId: '1',
+        locationName: 'loc1',
+        locationType: 'Collecting site',
+        abbreviation: null,
+        countryCode: 'Fr',
+        countryName: 'France',
+        institutionAddress: null,
+        institutionName: 'Insti',
+        altitude: null,
+        latitude: null,
+        longitude: null,
+    };
+
+    const contacts: BrapiContacts = {
+        contactDbId: 'c1',
+        name: 'contact1',
+        email: 'contact1@email.com',
+        type: 'contact',
+        institutionName: 'Inst',
+    };
+
+    const searchStudy: BrapiResult<BrapiStudy> = {
+        metadata: null,
+        result: {
+            studyDbId: 's1',
+            studyType: 'phenotype',
+            studyName: 'study1',
+            studyDescription: null,
+            seasons: ['winter', '2019'],
+            startDate: '2018',
+            endDate: null,
+            active: true,
+            programDbId: 'p1',
+            programName: 'program1',
+            trialDbIds: ['10', '20'],
+            location: location,
+            contacts: [contacts],
+            additionalInfo: null,
+            dataLinks: []
+        }
+    };
+
+    const trial1: BrapiResult<BrapiTrial> = {
+        metadata: null,
+        result: {
+            trialDbId: '10',
+            trialName: 'trial_10',
+            trialType: 'project',
+            active: true,
+            studies: [
+                { studyDbId: 's1', studyName: 'study1' },
+                { studyDbId: 's2', studyName: 'study2' }
+            ]
+
+        }
+    };
+
+    const osbVariable: BrapiResults<BrapiObservationVariable> = {
+        metadata: null,
+        result: {
+            data: [{
+                observationVariableDbId: 'var1',
+                contextOfUse: null,
+                institution: 'Insti',
+                crop: 'WoodyPlant',
+                name: 'varaiable1',
+                ontologyDbId: 'WPO',
+                ontologyName: 'Woody Plant Ontology',
+                synonyms: ['First synonym'],
+                language: 'EN',
+                trait: {
+                    traitDbId: 't1',
+                    name: 'trait1',
+                    description: null,
+                },
+                documentationURL: null,
+            }]
+        }
+    };
+
+    const germplasm: BrapiResults<BrapiGermplasm> = {
+        metadata: null,
+        result: {
+            data: [{
+                germplasmDbId: 'g1',
+                accessionNumber: 'G_10',
+                germplasmName: 'germplam1',
+                genus: 'Populus',
+                species: 'x generosa',
+                subtaxa: ''
+            }, {
+                germplasmDbId: 'g2',
+                accessionNumber: 'G_20',
+                germplasmName: 'germplam2',
+                genus: 'Triticum',
+                species: 'aestivum',
+                subtaxa: 'subsp'
+            }],
+        }
+    };
+
+    const source: DataDiscoverySource = {
+        '@id': 'src1',
+        '@type': ['schema:DataCatalog'],
+        'schema:identifier': 'srcId',
+        'schema:name': 'source1',
+        'schema:url': 'srcUrl',
+        'schema:image': null
+    };
+
+    it('should fetch the study', () => {
+        let fetchedStudy: BrapiResult<BrapiStudy>;
+        const studyDbId: string = searchStudy.result.studyDbId;
+        brapiService.study(searchStudy.result.studyDbId).subscribe(response => {
+            fetchedStudy = response;
+        });
+        http.expectOne(`brapi/v1/studies/${studyDbId}`)
+            .flush(searchStudy);
+
+        expect(fetchedStudy).toEqual(searchStudy);
+
+    });
+
+    it('should fetch the germplasm', () => {
+
+        let fetchedGermplasm: BrapiResults<BrapiGermplasm>;
+        const studyDbId: string = searchStudy.result.studyDbId;
+        brapiService.studyGermplasms(searchStudy.result.studyDbId).subscribe(response => {
+            fetchedGermplasm = response;
+        });
+        http.expectOne(`brapi/v1/studies/${studyDbId}/germplasm`)
+            .flush(germplasm);
+
+        expect(fetchedGermplasm).toEqual(germplasm);
+
+    });
+
+    it('should fetch the variables', () => {
+
+        let fetchedVariables: BrapiResults<BrapiObservationVariable>;
+        const studyDbId: string = searchStudy.result.studyDbId;
+        brapiService.studyObservationVariables(searchStudy.result.studyDbId).subscribe(response => {
+            fetchedVariables = response;
+        });
+        http.expectOne(`brapi/v1/studies/${studyDbId}/observationVariables`)
+            .flush(osbVariable);
+
+        expect(fetchedVariables).toEqual(osbVariable);
+
+    });
+
+    it('should fetch the trials', () => {
+
+        let fetchedTrials: BrapiResult<BrapiTrial>;
+        const trialDbId: string = trial1.result.trialDbId;
+        brapiService.studyTrials(trialDbId).subscribe(response => {
+            fetchedTrials = response;
+        });
+        http.expectOne(`brapi/v1/trials/${trialDbId}`)
+            .flush(trial1);
+
+        expect(fetchedTrials).toEqual(trial1);
+
+    });
+
+    it('should fetch 1 location', () => {
+        const mockResponse: BrapiResult<BrapiLocation> = {
+            metadata: null,
+            result: location
         };
-        let actualSite: SiteModel;
-        const locationId = hardCodedSite.result.locationDbId;
-        brapiService.location(hardCodedSite.result.locationDbId).subscribe(site => actualSite = site);
+        let actualLocation: BrapiLocation;
+        const locationId = mockResponse.result.locationDbId;
+        brapiService.location(mockResponse.result.locationDbId).subscribe(response => actualLocation = response.result);
 
         http.expectOne(`brapi/v1/locations/${locationId}`)
-            .flush(hardCodedSite);
+            .flush(mockResponse);
 
-        expect(actualSite).toEqual(hardCodedSite);
+        expect(actualLocation).toEqual(location);
     });
+
 });
diff --git a/frontend/src/app/brapi.service.ts b/frontend/src/app/brapi.service.ts
index 9200ef99ca740c45503cc32799326bde93298535..be27a43a0e39d9dfe0ea5a302973ee38a6e48a0b 100644
--- a/frontend/src/app/brapi.service.ts
+++ b/frontend/src/app/brapi.service.ts
@@ -1,48 +1,61 @@
 import { Injectable } from '@angular/core';
 import { Observable } from 'rxjs';
 import { HttpClient } from '@angular/common/http';
-import { delay } from 'rxjs/operators';
-import { SiteModel, SiteResultModel } from './models/site.model';
+import {
+    BrapiGermplasm,
+    BrapiLocation,
+    BrapiObservationVariable,
+    BrapiResult,
+    BrapiResults,
+    BrapiStudy,
+    BrapiTrial
+} from './models/brapi.model';
+
+export const BASE_URL = 'brapi/v1';
 
 @Injectable({
     providedIn: 'root'
 })
-
 export class BrapiService {
 
     constructor(private http: HttpClient) {
     }
 
     germplasm(germplasmDbId: string): Observable<object> {
-        return this.http.get<object>(`brapi/v1/germplasm/${germplasmDbId}`);
+        return this.http.get<object>(`${BASE_URL}/germplasm/${germplasmDbId}`);
     }
 
     germplasmPedigree(germplasmDbId: string): Observable<object> {
-        return this.http.get<object>(`brapi/v1/germplasm/${germplasmDbId}/pedigree`);
+        return this.http.get<object>(`${BASE_URL}/germplasm/${germplasmDbId}/pedigree`);
     }
 
     germplasmProgeny(germplasmDbId: string): Observable<object> {
-        return this.http.get<object>(`brapi/v1/germplasm/${germplasmDbId}/progeny`);
+        return this.http.get<object>(`${BASE_URL}/germplasm/${germplasmDbId}/progeny`);
     }
 
     germplasmAttributes(germplasmDbId: string): Observable<object> {
-        return this.http.get<object>(`brapi/v1/germplasm/${germplasmDbId}/attributes`);
+        return this.http.get<object>(`${BASE_URL}/germplasm/${germplasmDbId}/attributes`);
+    }
+
+    study(studyDbId: string): Observable<BrapiResult<BrapiStudy>> {
+        const options = { headers: { 'Accept': 'application/ld+json,application/json' } };
+        return this.http.get<BrapiResult<BrapiStudy>>(`${BASE_URL}/studies/${studyDbId}`, options);
     }
 
-    study(studyDbId: string): Observable<object> {
-        return this.http.get<object>(`brapi/v1/studies/${studyDbId}`);
+    studyGermplasms(studyDbId: string): Observable<BrapiResults<BrapiGermplasm>> {
+        return this.http.get<BrapiResults<BrapiGermplasm>>(`${BASE_URL}/studies/${studyDbId}/germplasm`);
     }
 
-    studyGermplasms(studyDbId: string): Observable<string[]> {
-        return this.http.get<string[]>(`brapi/v1/studies/${studyDbId}/germplasm`);
+    studyObservationVariables(studyDbId: string): Observable<BrapiResults<BrapiObservationVariable>> {
+        return this.http.get<BrapiResults<BrapiObservationVariable>>(`${BASE_URL}/studies/${studyDbId}/observationVariables`);
     }
 
-    studyObservationVariables(studyDbId: string): Observable<string[]> {
-        return this.http.get<string[]>(`brapi/v1/studies/${studyDbId}/observationVariables`);
+    location(locationId: string): Observable<BrapiResult<BrapiLocation>> {
+        return this.http.get<BrapiResult<BrapiLocation>>(`${BASE_URL}/locations/${locationId}`);
     }
 
-    location(locationId: number): Observable<SiteModel> {
-        return this.http.get<SiteModel>(`brapi/v1/locations/${locationId}`);
+    studyTrials(trialsId: string): Observable<BrapiResult<BrapiTrial>> {
+        return this.http.get<BrapiResult<BrapiTrial>>(`${BASE_URL}/trials/${trialsId}`);
     }
 
 }
diff --git a/frontend/src/app/card-row/card-row.component.html b/frontend/src/app/card-row/card-row.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..e3d9f2587f5fe4f6857effbfb70828c6073eb35d
--- /dev/null
+++ b/frontend/src/app/card-row/card-row.component.html
@@ -0,0 +1,14 @@
+<ng-container *ngIf="test && (value === undefined || value)">
+  <div class="row row-sep">
+    <div class="col-4 field my-2">
+      {{ label }}
+    </div>
+    <div class="col my-2">
+      <ng-template #includeTemplate [ngTemplateOutlet]="template"></ng-template>
+      <ng-template #includeStringValue>
+        {{ value }}
+      </ng-template>
+      <ng-container *ngIf="value; then includeStringValue else includeTemplate"></ng-container>
+    </div>
+  </div>
+</ng-container>
diff --git a/frontend/src/app/card-row/card-row.component.scss b/frontend/src/app/card-row/card-row.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..5f6ecd43d609ce9c88484a500fe32cc215501c3c
--- /dev/null
+++ b/frontend/src/app/card-row/card-row.component.scss
@@ -0,0 +1,7 @@
+.row-sep {
+  border-top: 0.01px solid #f0f0f0;
+}
+
+.field {
+  font-weight: bold;
+}
diff --git a/frontend/src/app/card-row/card-row.component.spec.ts b/frontend/src/app/card-row/card-row.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..95ab00d83c82f44f1dd4c222345ef3e7dfe75f0d
--- /dev/null
+++ b/frontend/src/app/card-row/card-row.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CardRowComponent } from './card-row.component';
+
+describe('CardRowComponent', () => {
+    let component: CardRowComponent;
+    let fixture: ComponentFixture<CardRowComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [CardRowComponent]
+        })
+            .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(CardRowComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/frontend/src/app/card-row/card-row.component.ts b/frontend/src/app/card-row/card-row.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9def776858b0589e60ae47c14ce1047fcc2a1c76
--- /dev/null
+++ b/frontend/src/app/card-row/card-row.component.ts
@@ -0,0 +1,16 @@
+import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
+
+@Component({
+    selector: 'gpds-card-row',
+    templateUrl: './card-row.component.html',
+    styleUrls: ['./card-row.component.scss']
+})
+export class CardRowComponent {
+
+    @Input() label: string;
+    @Input() test: any = true;
+    @Input() value: string;
+
+    @ContentChild(TemplateRef) template: TemplateRef<any>;
+
+}
diff --git a/frontend/src/app/card-section/card-section.component.html b/frontend/src/app/card-section/card-section.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..ffaa0ba6b3a5afb787f18d12f65b042585dc4b8a
--- /dev/null
+++ b/frontend/src/app/card-section/card-section.component.html
@@ -0,0 +1,6 @@
+<div class="card mb-3 mt-4" *ngIf="test">
+  <div class="card-header">
+    {{ header }}
+  </div>
+  <ng-container [ngTemplateOutlet]="template"></ng-container>
+</div>
diff --git a/frontend/src/app/card-section/card-section.component.scss b/frontend/src/app/card-section/card-section.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..155466cf1b6c0b21bbf28661089d1da24001ad64
--- /dev/null
+++ b/frontend/src/app/card-section/card-section.component.scss
@@ -0,0 +1,15 @@
+.card {
+  margin-top: 25px;
+  margin-bottom: 10px;
+  border-color: rgba(89, 89, 89, 0.33);
+  border-width: 1.5px;
+}
+
+.card-header {
+  font-size: large;
+  font-weight: bold;
+  color: #0f6191;
+  background-color: #eaeaea;
+  border-color: rgba(29, 29, 29, 0.16);
+  border-width: 2.5px;
+}
diff --git a/frontend/src/app/card-section/card-section.component.spec.ts b/frontend/src/app/card-section/card-section.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..874070e7d85edb2aa22393ae5ab055ea5cb0b5aa
--- /dev/null
+++ b/frontend/src/app/card-section/card-section.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CardSectionComponent } from './card-section.component';
+
+describe('CardSectionComponent', () => {
+    let component: CardSectionComponent;
+    let fixture: ComponentFixture<CardSectionComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [CardSectionComponent]
+        })
+            .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(CardSectionComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/frontend/src/app/card-section/card-section.component.ts b/frontend/src/app/card-section/card-section.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bd014e2e38fe4019d4284108b0428b299ef47c24
--- /dev/null
+++ b/frontend/src/app/card-section/card-section.component.ts
@@ -0,0 +1,15 @@
+import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
+
+@Component({
+    selector: 'gpds-card-section',
+    templateUrl: './card-section.component.html',
+    styleUrls: ['./card-section.component.scss']
+})
+export class CardSectionComponent {
+
+    @Input() header: string;
+    @Input() test: any = true;
+
+    @ContentChild(TemplateRef) template: TemplateRef<any>;
+
+}
diff --git a/frontend/src/app/card-table/card-table.component.html b/frontend/src/app/card-table/card-table.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..beb8c09450a0d12acbc7dfdd9ff074745d034690
--- /dev/null
+++ b/frontend/src/app/card-table/card-table.component.html
@@ -0,0 +1,17 @@
+<div class="table-responsive scroll-table">
+  <table class="table table-sm table-striped">
+    <thead *ngIf="headers">
+      <tr>
+        <th *ngFor="let header of headers" scope="col">
+          {{ header }}
+        </th>
+      </tr>
+    </thead>
+    <tbody>
+      <ng-container *ngFor="let row of rows"
+                    [ngTemplateOutlet]="template"
+                    [ngTemplateOutletContext]="{$implicit: row}">
+      </ng-container>
+    </tbody>
+  </table>
+</div>
diff --git a/frontend/src/app/card-table/card-table.component.scss b/frontend/src/app/card-table/card-table.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..f721891f8501e63874515eb0119c44fcf7ce090a
--- /dev/null
+++ b/frontend/src/app/card-table/card-table.component.scss
@@ -0,0 +1,11 @@
+
+.scroll-table {
+  max-height: 200px;
+  overflow-y: auto;
+
+  thead {
+    position: sticky;
+    top: 0;
+    background-color: white;
+  }
+}
diff --git a/frontend/src/app/card-table/card-table.component.spec.ts b/frontend/src/app/card-table/card-table.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e21f25a54632d5ec9310ad09b42518d824f37dbf
--- /dev/null
+++ b/frontend/src/app/card-table/card-table.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CardTableComponent } from './card-table.component';
+
+describe('CardTableComponent', () => {
+    let component: CardTableComponent;
+    let fixture: ComponentFixture<CardTableComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [CardTableComponent]
+        })
+            .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(CardTableComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/frontend/src/app/card-table/card-table.component.ts b/frontend/src/app/card-table/card-table.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dd871e030080e7ff6679341f87f95ac4ec1254d6
--- /dev/null
+++ b/frontend/src/app/card-table/card-table.component.ts
@@ -0,0 +1,15 @@
+import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
+
+@Component({
+    selector: 'gpds-card-table',
+    templateUrl: './card-table.component.html',
+    styleUrls: ['./card-table.component.scss']
+})
+export class CardTableComponent {
+
+    @Input() headers: string[];
+    @Input() rows: any[];
+
+    @ContentChild(TemplateRef) template: TemplateRef<any>;
+
+}
diff --git a/frontend/src/app/error/error.component.html b/frontend/src/app/error/error.component.html
index 69490868e8b4365c5afaca66941da02a5fad4887..a52b913ffddb93318d8e0dfc91b4e3519d075c03 100644
--- a/frontend/src/app/error/error.component.html
+++ b/frontend/src/app/error/error.component.html
@@ -9,7 +9,9 @@
         <a href="javascript:window.location.reload(true)">
           Refresh the page
         </a>
-        or try later.<br/>
+        or try later.
+        <br/>
+        Please contact <a href="mailto:urgi-support@inra.fr">urgi-support@inra.fr</a> if the problem persists.
       </p>
       <small *ngIf="error.status" id="error-status">
         Status: {{ error.status }}
diff --git a/frontend/src/app/form/form.component.html b/frontend/src/app/form/form.component.html
index f55d6443959040c7e34682c77ffb8466fdf50c81..3df3dbe20dcb972a632008200d563d2b1c63ada5 100644
--- a/frontend/src/app/form/form.component.html
+++ b/frontend/src/app/form/form.component.html
@@ -30,7 +30,7 @@
         inputId="crops"
         criteriaField="crops"
         [criteria$]="criteria$"
-        placeholder="Select or search crops">
+        placeholder="Search crops">
       </gpds-suggestion-field>
     </div>
   </div>
@@ -47,7 +47,7 @@
         inputId="germplasmList"
         criteriaField="germplasmLists"
         [criteria$]="criteria$"
-        placeholder="Select or search germplasm lists">
+        placeholder="Search germplasm lists">
       </gpds-suggestion-field>
     </div>
   </div>
@@ -64,7 +64,7 @@
         inputId="accessions"
         criteriaField="accessions"
         [criteria$]="criteria$"
-        placeholder="Select or search germplasm accession">
+        placeholder="Search germplasm accession">
       </gpds-suggestion-field>
     </div>
   </div>
diff --git a/frontend/src/app/form/form.component.ts b/frontend/src/app/form/form.component.ts
index b19825b4d283f0368d15dcb466c0260d1bf8fbdb..b546c41a84870bf690f8fc60c1751a898ff00838 100644
--- a/frontend/src/app/form/form.component.ts
+++ b/frontend/src/app/form/form.component.ts
@@ -1,5 +1,5 @@
 import { Component, EventEmitter, Input, Output } from '@angular/core';
-import { DataDiscoveryCriteria } from '../model/data-discovery.model';
+import { DataDiscoveryCriteria } from '../models/data-discovery.model';
 import { BehaviorSubject } from 'rxjs';
 
 @Component({
diff --git a/frontend/src/app/form/suggestion-field/suggestion-field.component.spec.ts b/frontend/src/app/form/suggestion-field/suggestion-field.component.spec.ts
index 7b7e7060b49025e8ce71639b7e2ec7617c3a4d95..f20bd4547fa0d6b7c56f1514aee1768fecf7ec16 100644
--- a/frontend/src/app/form/suggestion-field/suggestion-field.component.spec.ts
+++ b/frontend/src/app/form/suggestion-field/suggestion-field.component.spec.ts
@@ -5,7 +5,7 @@ import { ReactiveFormsModule } from '@angular/forms';
 import { NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstrap';
 import { GnpisService } from '../../gnpis.service';
 import { BehaviorSubject, of } from 'rxjs';
-import { DataDiscoveryCriteriaUtils } from '../../model/data-discovery.model';
+import { DataDiscoveryCriteriaUtils } from '../../models/data-discovery.model';
 import { ComponentTester } from 'ngx-speculoos';
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 
diff --git a/frontend/src/app/form/suggestion-field/suggestion-field.component.ts b/frontend/src/app/form/suggestion-field/suggestion-field.component.ts
index 2b06553c2643469bb997902b8a74c322dd987afe..6505693e38a78cecfddab250afd61ecc709bfbb5 100644
--- a/frontend/src/app/form/suggestion-field/suggestion-field.component.ts
+++ b/frontend/src/app/form/suggestion-field/suggestion-field.component.ts
@@ -4,7 +4,7 @@ import { FormControl } from '@angular/forms';
 import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap';
 import { GnpisService } from '../../gnpis.service';
 import { debounceTime, filter, map, switchMap } from 'rxjs/operators';
-import { DataDiscoveryCriteria } from '../../model/data-discovery.model';
+import { DataDiscoveryCriteria } from '../../models/data-discovery.model';
 
 @Component({
     selector: 'gpds-suggestion-field',
diff --git a/frontend/src/app/form/trait-ontology-widget/trait-ontology-widget.component.spec.ts b/frontend/src/app/form/trait-ontology-widget/trait-ontology-widget.component.spec.ts
index def69cf3957b3231b11a81ca90043e2c4dcaadef..0ca592a60211ae1e0dcf6770d991fd1b82daafaa 100644
--- a/frontend/src/app/form/trait-ontology-widget/trait-ontology-widget.component.spec.ts
+++ b/frontend/src/app/form/trait-ontology-widget/trait-ontology-widget.component.spec.ts
@@ -3,7 +3,7 @@ import { TestBed } from '@angular/core/testing';
 import { CropOntologyWidgetFactory, TraitOntologyWidgetComponent } from './trait-ontology-widget.component';
 import { GnpisService } from '../../gnpis.service';
 import { BehaviorSubject, of } from 'rxjs';
-import { DataDiscoveryCriteriaUtils } from '../../model/data-discovery.model';
+import { DataDiscoveryCriteriaUtils } from '../../models/data-discovery.model';
 import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap';
 import { HttpClientTestingModule } from '@angular/common/http/testing';
 
diff --git a/frontend/src/app/form/trait-ontology-widget/trait-ontology-widget.component.ts b/frontend/src/app/form/trait-ontology-widget/trait-ontology-widget.component.ts
index 7750151d926c1fdcb798e3a835bad94210445300..b27eece17dd62178d011f70235a9f32b96c264b0 100644
--- a/frontend/src/app/form/trait-ontology-widget/trait-ontology-widget.component.ts
+++ b/frontend/src/app/form/trait-ontology-widget/trait-ontology-widget.component.ts
@@ -1,6 +1,6 @@
 import { Component, EventEmitter, Injectable, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
 import { CropOntologyWidget } from 'trait-ontology-widget/dist/module/cropOntologyWidget.module';
-import { DataDiscoveryCriteria } from '../../model/data-discovery.model';
+import { DataDiscoveryCriteria } from '../../models/data-discovery.model';
 import { BehaviorSubject } from 'rxjs';
 import { GnpisService } from '../../gnpis.service';
 import { filter } from 'rxjs/operators';
diff --git a/frontend/src/app/gnpis.service.spec.ts b/frontend/src/app/gnpis.service.spec.ts
index b89ac8263987bb31743a1a05a0fb1d18f82e3d94..6ffe4c0cc04c92de9a9582301bf609184bd7bc0d 100644
--- a/frontend/src/app/gnpis.service.spec.ts
+++ b/frontend/src/app/gnpis.service.spec.ts
@@ -1,9 +1,9 @@
 import { TestBed } from '@angular/core/testing';
 
-import { GnpisService } from './gnpis.service';
+import { BASE_URL, GnpisService } from './gnpis.service';
 import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
-import { BrapiMetaData, BrapiResults } from './model/brapi.model';
-import { DataDiscoveryCriteria, DataDiscoveryResults, DataDiscoverySource } from './model/data-discovery.model';
+import { BrapiMetaData, BrapiResults } from './models/brapi.model';
+import { DataDiscoveryCriteria, DataDiscoverySource } from './models/data-discovery.model';
 
 describe('GnpisService', () => {
     let service: GnpisService;
@@ -43,7 +43,7 @@ describe('GnpisService', () => {
         service = TestBed.get(GnpisService);
         const req = httpMock.expectOne({
             method: 'GET',
-            url: `${GnpisService.BASE_URL}/sources`
+            url: `${BASE_URL}/sources`
         });
         req.flush(sources);
 
@@ -62,7 +62,7 @@ describe('GnpisService', () => {
         });
 
         const req = httpMock.expectOne({
-            url: `${GnpisService.BASE_URL}/suggest?field=${field}&text=${text}&fetchSize=${fetchSize}`,
+            url: `${BASE_URL}/suggest?field=${field}&text=${text}&fetchSize=${fetchSize}`,
             method: 'POST'
         });
         req.flush(expectedSuggestions);
@@ -112,7 +112,7 @@ describe('GnpisService', () => {
         });
 
         const req = httpMock.expectOne({
-            url: `${GnpisService.BASE_URL}/search`,
+            url: `${BASE_URL}/search`,
             method: 'POST'
         });
         req.flush(rawResult);
diff --git a/frontend/src/app/gnpis.service.ts b/frontend/src/app/gnpis.service.ts
index 81862244ab6f6165c9f6441716588a6db84024c8..967550c891b5d5ad5ee02d68005f89ce33475bdd 100644
--- a/frontend/src/app/gnpis.service.ts
+++ b/frontend/src/app/gnpis.service.ts
@@ -1,15 +1,16 @@
 import { Injectable } from '@angular/core';
 import { Observable, ReplaySubject, zip } from 'rxjs';
 import { HttpClient } from '@angular/common/http';
-import { DataDiscoveryCriteria, DataDiscoveryFacet, DataDiscoveryResults, DataDiscoverySource } from './model/data-discovery.model';
-import { BrapiResults } from './model/brapi.model';
+import { DataDiscoveryCriteria, DataDiscoveryFacet, DataDiscoveryResults, DataDiscoverySource } from './models/data-discovery.model';
+import { BrapiResults } from './models/brapi.model';
 import { map } from 'rxjs/operators';
 
+export const BASE_URL = 'gnpis/v1/datadiscovery';
+
 @Injectable({
     providedIn: 'root'
 })
 export class GnpisService {
-    static BASE_URL = 'gnpis/v1/datadiscovery';
     sourceByURI$ = new ReplaySubject<{ [key: string]: DataDiscoverySource }>(1);
 
     constructor(private http: HttpClient) {
@@ -17,7 +18,7 @@ export class GnpisService {
     }
 
     private fetchSources(): void {
-        this.http.get(`${GnpisService.BASE_URL}/sources`).subscribe(
+        this.http.get(`${BASE_URL}/sources`).subscribe(
             (response: BrapiResults<DataDiscoverySource>) => {
                 const sourceByURI = {};
                 for (const source of response.result.data) {
@@ -44,7 +45,7 @@ export class GnpisService {
     ): Observable<string[]> {
         const params = { field, text, fetchSize: fetchSize.toString() };
         return this.http.post<string[]>(
-            `${GnpisService.BASE_URL}/suggest`, criteria, { params }
+            `${BASE_URL}/suggest`, criteria, { params }
         );
     }
 
@@ -60,7 +61,7 @@ export class GnpisService {
             // Get source by URI
             this.sourceByURI$,
             // Get documents by criteria
-            this.http.post<any>(`${GnpisService.BASE_URL}/search`, criteria)
+            this.http.post<any>(`${BASE_URL}/search`, criteria)
         ).pipe(map(([sourceByURI, response]) => {
             // Extract BrAPI documents from result
             const documents = response.result.data;
@@ -88,4 +89,11 @@ export class GnpisService {
         }));
     }
 
+    /**
+     * Get data source by URI
+     */
+    getSource(sourceURI: string): Observable<DataDiscoverySource> {
+        return this.sourceByURI$.pipe(map(sourceByURI => sourceByURI[sourceURI]));
+    }
+
 }
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.html b/frontend/src/app/loading-spinner/loading-spinner.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..01060a7dd7c293b6db27feaf2c3928cc9680e766
--- /dev/null
+++ b/frontend/src/app/loading-spinner/loading-spinner.component.html
@@ -0,0 +1,15 @@
+<!-- Loading (see CSS for animation)-->
+<div *ngIf="loading" class="lds-spinner mx-auto">
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+  <div></div>
+</div>
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.scss b/frontend/src/app/loading-spinner/loading-spinner.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..3c571d096f78cb16969f3c8100e0be9a06173ab4
--- /dev/null
+++ b/frontend/src/app/loading-spinner/loading-spinner.component.scss
@@ -0,0 +1,96 @@
+// Loading spinner
+.lds-spinner {
+  $scale: 1.2;
+
+  color: black;
+  display: block;
+  position: relative;
+  width: 64px * $scale;
+  height: 64px * $scale;
+
+  div {
+    transform-origin: 32px * $scale 32px * $scale;
+    animation: lds-spinner 1.2s linear infinite;
+  }
+
+  div:after {
+    content: " ";
+    display: block;
+    position: absolute;
+    top: 3px;
+    left: 29px * $scale;
+    width: 5px * $scale;
+    height: 14px * $scale;
+    border-radius: 20%;
+    background: black;
+  }
+
+  div:nth-child(1) {
+    transform: rotate(0deg);
+    animation-delay: -1.1s;
+  }
+
+  div:nth-child(2) {
+    transform: rotate(30deg);
+    animation-delay: -1s;
+  }
+
+  div:nth-child(3) {
+    transform: rotate(60deg);
+    animation-delay: -0.9s;
+  }
+
+  div:nth-child(4) {
+    transform: rotate(90deg);
+    animation-delay: -0.8s;
+  }
+
+  div:nth-child(5) {
+    transform: rotate(120deg);
+    animation-delay: -0.7s;
+  }
+
+  div:nth-child(6) {
+    transform: rotate(150deg);
+    animation-delay: -0.6s;
+  }
+
+  div:nth-child(7) {
+    transform: rotate(180deg);
+    animation-delay: -0.5s;
+  }
+
+  div:nth-child(8) {
+    transform: rotate(210deg);
+    animation-delay: -0.4s;
+  }
+
+  div:nth-child(9) {
+    transform: rotate(240deg);
+    animation-delay: -0.3s;
+  }
+
+  div:nth-child(10) {
+    transform: rotate(270deg);
+    animation-delay: -0.2s;
+  }
+
+  div:nth-child(11) {
+    transform: rotate(300deg);
+    animation-delay: -0.1s;
+  }
+
+  div:nth-child(12) {
+    transform: rotate(330deg);
+    animation-delay: 0s;
+  }
+}
+
+@keyframes lds-spinner {
+  0% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0;
+  }
+}
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.spec.ts b/frontend/src/app/loading-spinner/loading-spinner.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..88109e14e18227b499a5a3ca4ca007fa86424592
--- /dev/null
+++ b/frontend/src/app/loading-spinner/loading-spinner.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LoadingSpinnerComponent } from './loading-spinner.component';
+
+describe('LoadingSpinnerComponent', () => {
+    let component: LoadingSpinnerComponent;
+    let fixture: ComponentFixture<LoadingSpinnerComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [LoadingSpinnerComponent]
+        })
+            .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(LoadingSpinnerComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/frontend/src/app/loading-spinner/loading-spinner.component.ts b/frontend/src/app/loading-spinner/loading-spinner.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..24cb590256c408c5580e06bc674dcbb31fbaaf62
--- /dev/null
+++ b/frontend/src/app/loading-spinner/loading-spinner.component.ts
@@ -0,0 +1,18 @@
+import { Component, Input, OnInit } from '@angular/core';
+
+@Component({
+    selector: 'gpds-loading-spinner',
+    templateUrl: './loading-spinner.component.html',
+    styleUrls: ['./loading-spinner.component.scss']
+})
+export class LoadingSpinnerComponent implements OnInit {
+
+    @Input() loading: boolean;
+
+    constructor() {
+    }
+
+    ngOnInit() {
+    }
+
+}
diff --git a/frontend/src/app/map/map.component.html b/frontend/src/app/map/map.component.html
index d0fa5e94cbc93811f478ecd047adf32815b1cd9c..d6143c137693b3d868a4eedde59cad686e1a3c64 100644
--- a/frontend/src/app/map/map.component.html
+++ b/frontend/src/app/map/map.component.html
@@ -1,12 +1,12 @@
-<div id="map">
+<div id="map" class="rounded">
 </div>
 <div id="maplegend">
-  <img src="assets/gpds/images/marker-icon-red.png" id="red" />
+  <img src="assets/gpds/images/marker-icon-red.png" id="red"/>
   <label for="red">Origin site</label>
-  <img src="assets/gpds/images/marker-icon-blue.png" id="blue" />
+  <img src="assets/gpds/images/marker-icon-blue.png" id="blue"/>
   <label for="blue">Collecting site</label>
-  <img src="assets/gpds/images/marker-icon-green.png" id="green" />
+  <img src="assets/gpds/images/marker-icon-green.png" id="green"/>
   <label for="green">Evaluation site</label>
-  <img src="assets/gpds/images/marker-icon-purple.png" id="purple" />
+  <img src="assets/gpds/images/marker-icon-purple.png" id="purple"/>
   <label for="purple">Multi-purpose site</label>
 </div>
diff --git a/frontend/src/app/map/map.component.scss b/frontend/src/app/map/map.component.scss
index cb6f34692d608ed2bf0f401844506fa885c7f17d..5f9ab2fdae863ad7361c7e6b0043d310a6c1a131 100644
--- a/frontend/src/app/map/map.component.scss
+++ b/frontend/src/app/map/map.component.scss
@@ -1,5 +1,5 @@
 #map {
-  border: #79c93f solid 1px;
+  border: #0f6e9f solid 1px;
   height: 600px;
   width: 100%
 }
diff --git a/frontend/src/app/map/map.component.spec.ts b/frontend/src/app/map/map.component.spec.ts
index 76a8c02b14627cdd6bc774e546a4f3e269e4b660..275f029147e165dc5351e4a77bb19aadd0bee088 100644
--- a/frontend/src/app/map/map.component.spec.ts
+++ b/frontend/src/app/map/map.component.spec.ts
@@ -1,40 +1,38 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { MapComponent } from './map.component';
-import { SiteModel } from '../models/site.model';
+import { BrapiLocation } from '../models/brapi.model';
 
 describe('MapComponent', () => {
     let component: MapComponent;
     let fixture: ComponentFixture<MapComponent>;
-    const site: SiteModel = {
-        result: {
-            locationDbId: 1,
-            latitude: 1,
-            longitude: 1,
-            altitude: 1,
-            institutionName: '',
-            institutionAdress: '',
-            countryName: '',
-            countryCode: '',
-            locationType: '',
-            abbreviation: '',
-            name: 'site1',
-            additionalInfo: {
-                Topography: '',
-                Slope: '',
-                Comment: '',
-                Exposure: '',
-                'Coordinates precision': '',
-                'Direction from city': '',
-                'Distance to city': '',
-                'Environment type': '',
-                'Geographical location': '',
-                'Site status': ''
-            }
+    const location: BrapiLocation = {
+        locationDbId: '1',
+        latitude: 1,
+        longitude: 1,
+        altitude: 1,
+        institutionName: '',
+        institutionAddress: '',
+        countryName: '',
+        countryCode: '',
+        locationType: '',
+        abbreviation: '',
+        locationName: 'site1',
+        additionalInfo: {
+            Topography: '',
+            Slope: '',
+            Comment: '',
+            Exposure: '',
+            'Coordinates precision': '',
+            'Direction from city': '',
+            'Distance to city': '',
+            'Environment type': '',
+            'Geographical location': '',
+            'Site status': ''
         }
     };
-    const sites: Array<SiteModel> = new Array<SiteModel>();
-    sites.push(site);
+    const locations: BrapiLocation[] = [];
+    locations.push(location);
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
@@ -49,20 +47,20 @@ describe('MapComponent', () => {
     });
 
     it('should create component', () => {
-        component.sites = sites;
+        component.locations = locations;
         fixture.detectChanges();
         expect(component).toBeTruthy();
     });
 
     it('should display map', () => {
-        component.sites = sites;
+        component.locations = locations;
         fixture.detectChanges();
         const element = fixture.nativeElement;
         expect(element.querySelector('#map')).toBeTruthy();
     });
 
     it('should display map legend', () => {
-        component.sites = sites;
+        component.locations = locations;
         fixture.detectChanges();
         const element = fixture.nativeElement;
         expect(element.querySelector('#maplegend')).toBeTruthy();
diff --git a/frontend/src/app/map/map.component.ts b/frontend/src/app/map/map.component.ts
index f80a54253b652f9663a5d3263ef47bbf78e6b8be..23d72dcbf0717295bb101778057db91eccb82c0f 100644
--- a/frontend/src/app/map/map.component.ts
+++ b/frontend/src/app/map/map.component.ts
@@ -1,7 +1,7 @@
 import { Component, Input, OnInit } from '@angular/core';
-import { SiteModel } from '../models/site.model';
 import * as L from 'leaflet';
 import { MarkerClusterGroup } from 'leaflet.markercluster/src';
+import { BrapiLocation } from '../models/brapi.model';
 
 @Component({
     selector: 'gpds-map',
@@ -10,32 +10,32 @@ import { MarkerClusterGroup } from 'leaflet.markercluster/src';
 })
 export class MapComponent implements OnInit {
 
-    @Input() sites: Array<SiteModel>;
+    @Input() locations: BrapiLocation[];
 
     constructor() {
     }
 
     ngOnInit() {
         // initialize map centered on the first site
-        const firstSite: SiteModel = this.sites[0];
+        const firstLocation: BrapiLocation = this.locations[0];
         const container = L.DomUtil.get('map');
         if (container) {
-            const map = L.map('map').setView([firstSite.result.latitude, firstSite.result.longitude], 5);
+            const map = L.map('map').setView([firstLocation.latitude, firstLocation.longitude], 5);
             L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
                 attribution: 'Tiles &copy; Esri &mdash; Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, ' +
                     'Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
             }).addTo(map);
-            // add markers for all sites using markercluster plugin
+            // add markers for all locations using markercluster plugin
             const markers = new MarkerClusterGroup();
-            for (const site of this.sites) {
+            for (const site of this.locations) {
                 const icon = L.icon({
                     iconUrl: this.getMarkerIconUrl(site)
                 });
-                let iconText: string = '<b>' + site.result.name + '</b><br/>';
-                iconText += site.result.locationType + '<br/>';
-                iconText += `<a href="sites/${site.result.locationDbId}">Details</a>`;
+                let iconText: string = '<b>' + site.locationName + '</b><br/>';
+                iconText += site.locationType + '<br/>';
+                iconText += `<a href="sites/${site.locationDbId}">Details</a>`;
                 markers.addLayer(L.marker(
-                    [site.result.latitude, site.result.longitude],
+                    [site.latitude, site.longitude],
                     { icon: icon }
                     ).bindPopup(iconText)
                 );
@@ -48,17 +48,16 @@ export class MapComponent implements OnInit {
         }
     }
 
-    getMarkerIconUrl(site: SiteModel): string {
-        if (site.result.locationType === 'Origin site') {
+    getMarkerIconUrl(site: BrapiLocation): string {
+        if (site.locationType === 'Origin site') {
             return 'assets/gpds/images/marker-icon-red.png';
         }
-        if (site.result.locationType === 'Collecting site') {
+        if (site.locationType === 'Collecting site') {
             return 'assets/gpds/images/marker-icon-blue.png';
         }
-        if (site.result.locationType === 'Evaluation site') {
+        if (site.locationType === 'Evaluation site') {
             return 'assets/gpds/images/marker-icon-green.png';
         }
         return 'assets/gpds/images/marker-icon-purple.png';
     }
-
 }
diff --git a/frontend/src/app/model/brapi.model.ts b/frontend/src/app/model/brapi.model.ts
deleted file mode 100644
index 05c06088347023c948680538f86ec50dda6b1547..0000000000000000000000000000000000000000
--- a/frontend/src/app/model/brapi.model.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-
-interface BrapiData<T> {
-    data: T[];
-}
-
-export interface BrapiResults<T> {
-    metadata: BrapiMetaData;
-    result: BrapiData<T>;
-}
-
-export interface BrapiMetaData {
-
-    pagination: {
-        pageSize: number;
-        currentPage: number;
-        totalCount: number;
-        totalPages: number;
-    };
-
-}
diff --git a/frontend/src/app/models/brapi.model.ts b/frontend/src/app/models/brapi.model.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4360105b11800d1bcf052540a0526e112bd3ec75
--- /dev/null
+++ b/frontend/src/app/models/brapi.model.ts
@@ -0,0 +1,121 @@
+export interface BrapiMetaData {
+    pagination: {
+        pageSize: number;
+        currentPage: number;
+        totalCount: number;
+        totalPages: number;
+    };
+}
+
+
+export interface BrapiResult<T> {
+    metadata: BrapiMetaData;
+    result: T;
+}
+
+
+interface BrapiData<T> {
+    data: T[];
+}
+
+
+export interface BrapiResults<T> extends BrapiResult<BrapiData<T>> {
+}
+
+
+interface BrapiHasDocumentationURL {
+    documentationURL?: string;
+}
+
+
+export interface BrapiStudy extends BrapiHasDocumentationURL {
+    studyDbId: string;
+    studyType: string;
+    studyName: string;
+    studyDescription: string;
+    seasons: string[];
+    startDate: string;
+    endDate: string;
+    active: boolean;
+    programDbId: string;
+    programName: string;
+    trialDbIds: string[];
+    location: BrapiLocation;
+    contacts: BrapiContacts[];
+    additionalInfo: AdditionalInfo;
+    dataLinks: {
+        name: string;
+        type: string;
+        url: string;
+    }[];
+}
+
+
+export interface BrapiLocation extends BrapiHasDocumentationURL  {
+    locationDbId: string;
+    locationName: string;
+    locationType: string;
+    abbreviation: string;
+    countryCode: string;
+    countryName: string;
+    institutionAddress: string;
+    institutionName: string;
+    altitude: number;
+    latitude: number;
+    longitude: number;
+    additionalInfo?: AdditionalInfo;
+}
+
+
+export interface AdditionalInfo {
+    [key: string]: string;
+}
+
+
+export interface BrapiContacts {
+    contactDbId: string;
+    name: string;
+    email: string;
+    type: string;
+    institutionName: string;
+}
+
+
+export interface BrapiObservationVariable extends BrapiHasDocumentationURL  {
+    observationVariableDbId: string;
+    contextOfUse: string[];
+    institution: string;
+    crop: string;
+    name: string;
+    ontologyDbId: string;
+    ontologyName: string;
+    synonyms: string[];
+    language: string;
+    trait: {
+        traitDbId: string;
+        name: string;
+        description: string;
+    };
+}
+
+
+export interface BrapiGermplasm extends BrapiHasDocumentationURL {
+    germplasmDbId: string;
+    accessionNumber: string;
+    germplasmName: string;
+    genus: string;
+    species: string;
+    subtaxa: string;
+}
+
+
+export interface BrapiTrial extends BrapiHasDocumentationURL {
+    trialDbId: string;
+    trialName: string;
+    trialType: string;
+    active: boolean;
+    studies: {
+        studyDbId: string;
+        studyName: string;
+    }[];
+}
diff --git a/frontend/src/app/model/data-discovery.model.ts b/frontend/src/app/models/data-discovery.model.ts
similarity index 100%
rename from frontend/src/app/model/data-discovery.model.ts
rename to frontend/src/app/models/data-discovery.model.ts
diff --git a/frontend/src/app/models/site.model.ts b/frontend/src/app/models/site.model.ts
deleted file mode 100644
index 4d85ae51fe216f9dd969eb6cbbc72b5787ec9bb6..0000000000000000000000000000000000000000
--- a/frontend/src/app/models/site.model.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-export interface SiteModel {
-    result: SiteResultModel;
-}
-
-export interface SiteResultModel {
-    locationDbId: number;
-    name: string;
-    locationType?: string;
-    abbreviation?: string;
-    countryCode?: string;
-    countryName?: string;
-    institutionAdress?: string;
-    institutionName?: string;
-    altitude?: number;
-    latitude?: number;
-    longitude?: number;
-    additionalInfo?: AdditionalInfo;
-}
-
-export interface AdditionalInfo {
-    [key: string]: string;
-}
diff --git a/frontend/src/app/result-page/document/document.component.spec.ts b/frontend/src/app/result-page/document/document.component.spec.ts
index d0c4d19f56a2cbeb734b67a2cd94fddae9b5d3de..05928ff743a8f770521bd591f05cb7f1846c2e61 100644
--- a/frontend/src/app/result-page/document/document.component.spec.ts
+++ b/frontend/src/app/result-page/document/document.component.spec.ts
@@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing';
 
 import { DocumentComponent } from './document.component';
 import { NO_ERRORS_SCHEMA } from '@angular/core';
-import { DataDiscoverySource } from '../../model/data-discovery.model';
+import { DataDiscoverySource } from '../../models/data-discovery.model';
 import { ComponentTester, speculoosMatchers } from 'ngx-speculoos';
 
 describe('DocumentComponent', () => {
diff --git a/frontend/src/app/result-page/document/document.component.ts b/frontend/src/app/result-page/document/document.component.ts
index 22dd1f45b525424ff432899cf3bf70d7b3d56983..749101e45a1d04a31bf9828bfb7ab63bd0a9939a 100644
--- a/frontend/src/app/result-page/document/document.component.ts
+++ b/frontend/src/app/result-page/document/document.component.ts
@@ -1,5 +1,5 @@
 import { Component, Input, OnInit } from '@angular/core';
-import { DataDiscoveryDocument, DataDiscoveryType } from '../../model/data-discovery.model';
+import { DataDiscoveryDocument, DataDiscoveryType } from '../../models/data-discovery.model';
 
 @Component({
     selector: 'gpds-document',
diff --git a/frontend/src/app/result-page/facets/facets.component.spec.ts b/frontend/src/app/result-page/facets/facets.component.spec.ts
index 1408a559b269a376c43cb4657737b3a9821703c9..b00fa981b5676c9a48bb15705c682a9cfeca32c4 100644
--- a/frontend/src/app/result-page/facets/facets.component.spec.ts
+++ b/frontend/src/app/result-page/facets/facets.component.spec.ts
@@ -1,9 +1,9 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { TestBed } from '@angular/core/testing';
 
 import { FacetsComponent } from './facets.component';
-import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { ReactiveFormsModule } from '@angular/forms';
 import { ComponentTester, speculoosMatchers } from 'ngx-speculoos';
-import { DataDiscoveryCriteria, DataDiscoveryCriteriaUtils, DataDiscoveryFacet } from '../../model/data-discovery.model';
+import { DataDiscoveryCriteria, DataDiscoveryCriteriaUtils, DataDiscoveryFacet } from '../../models/data-discovery.model';
 import { BehaviorSubject } from 'rxjs';
 import { take } from 'rxjs/operators';
 
diff --git a/frontend/src/app/result-page/facets/facets.component.ts b/frontend/src/app/result-page/facets/facets.component.ts
index 4388717911f682708d5c91159a3f9a736ad000d0..4aa06655470ddb67e8ad5d3f3fe4cdcc022880dc 100644
--- a/frontend/src/app/result-page/facets/facets.component.ts
+++ b/frontend/src/app/result-page/facets/facets.component.ts
@@ -1,5 +1,5 @@
 import { Component, Input, OnInit } from '@angular/core';
-import { DataDiscoveryCriteria, DataDiscoveryFacet } from '../../model/data-discovery.model';
+import { DataDiscoveryCriteria, DataDiscoveryFacet } from '../../models/data-discovery.model';
 import { FormControl, FormGroup } from '@angular/forms';
 import { BehaviorSubject } from 'rxjs';
 import { filter } from 'rxjs/operators';
diff --git a/frontend/src/app/result-page/result-page.component.spec.ts b/frontend/src/app/result-page/result-page.component.spec.ts
index 0fba21f87f14a7c34e7af87adeb8ee4be7e95ff8..145bb866c1bc67992ce1fee493b1e3a948cacd1a 100644
--- a/frontend/src/app/result-page/result-page.component.spec.ts
+++ b/frontend/src/app/result-page/result-page.component.spec.ts
@@ -12,9 +12,9 @@ import {
     DataDiscoveryCriteriaUtils,
     DataDiscoveryDocument,
     DataDiscoverySource
-} from '../model/data-discovery.model';
+} from '../models/data-discovery.model';
 import { GnpisService } from '../gnpis.service';
-import { BrapiResults } from '../model/brapi.model';
+import { BrapiResults } from '../models/brapi.model';
 
 
 class ResultPageComponentTester extends ComponentTester<ResultPageComponent> {
diff --git a/frontend/src/app/result-page/result-page.component.ts b/frontend/src/app/result-page/result-page.component.ts
index e9062499bb1c905d3880cf7f8da19d5a28c27820..f8422b8fa546e85d850887fe44902fef7a706fdb 100644
--- a/frontend/src/app/result-page/result-page.component.ts
+++ b/frontend/src/app/result-page/result-page.component.ts
@@ -7,7 +7,7 @@ import {
     DataDiscoveryFacet,
     DEFAULT_PAGE_SIZE,
     MAX_RESULTS
-} from '../model/data-discovery.model';
+} from '../models/data-discovery.model';
 import { BehaviorSubject } from 'rxjs';
 import { GnpisService } from '../gnpis.service';
 import { filter } from 'rxjs/operators';
diff --git a/frontend/src/app/site-card/site-card.component.html b/frontend/src/app/site-card/site-card.component.html
index a635a1338a58c0fb33c6a7b8b94f2459050e01d3..f30867c67e9e182427c2daa7f74b6d78dfbfc436 100644
--- a/frontend/src/app/site-card/site-card.component.html
+++ b/frontend/src/app/site-card/site-card.component.html
@@ -1,75 +1,78 @@
-<div id="error" class="alert alert-danger" *ngIf="loadingError">
-  Error: this site cannot be loaded.
-  <br/>
-  Please contact urgi-support@inra.fr.
-</div>
-<div *ngIf="site">
-  <h1>
-    Site: {{ site.result.name }}
-  </h1>
-</div>
-<gpds-map [sites]="sites" *ngIf="site && site.result.latitude && site.result.longitude"></gpds-map>
-<div *ngIf="site">
-  <br/>
-  <h4>Details:</h4>
-  <br/>
-  <div class="container-fluid">
-    <div class="row">
-      <table class="table float-right w-75 p-2 table-sm">
-        <tr *ngIf="site.result.locationType">
-          <td><b>Site type</b></td>
-          <td>{{ site.result.locationType }}</td>
-        </tr>
-        <tr *ngIf="site.result.abbreviation">
-          <td><b>Abbreviation</b></td>
-          <td>{{ site.result.abbreviation }}</td>
-        </tr>
-        <tr *ngIf="site.result.countryCode">
-          <td><b>Country code</b></td>
-          <td>{{ site.result.countryCode }}</td>
-        </tr>
-        <tr *ngIf="site.result.countryName && !site.result.additionalInfo['Geographical location']">
-          <td><b>Country name</b></td>
-          <td>{{ site.result.countryName }}</td>
-        </tr>
-        <tr *ngIf="site.result.institutionAdress">
-          <td><b>Institution address</b></td>
-          <td>{{ site.result.institutionAdress }}</td>
-        </tr>
-        <tr *ngIf="site.result.institutionName">
-          <td><b>Institution name</b></td>
-          <td>{{ site.result.institutionName }}</td>
-        </tr>
-        <tr *ngIf="site.result.altitude">
-          <td><b>Altitude</b></td>
-          <td>{{ site.result.altitude }}</td>
-        </tr>
-        <tr *ngIf="site.result.latitude">
-          <td><b>Latitude</b></td>
-          <td>{{ site.result.latitude }}</td>
-        </tr>
-        <tr *ngIf="site.result.longitude">
-          <td><b>Longitude</b></td>
-          <td>{{ site.result.longitude }}</td>
-        </tr>
-      </table>
-    </div>
-  </div>
-  <div *ngIf="site.result.additionalInfo">
-    <br/>
-    <h4>Additional informations:</h4>
-    <br/>
-    <div class="container-fluid">
-      <div class="row">
-        <table class="table table-sm table-striped">
-          <tbody>
-          <tr *ngFor="let key of additionalInfoKeys">
-            <td><b>{{ key }}</b></td>
-            <td>{{ site.result.additionalInfo[key] }}</td>
-          </tr>
-          </tbody>
-        </table>
+<gpds-loading-spinner [loading]="loading"></gpds-loading-spinner>
+
+<ng-container *ngIf="location">
+  <h3>
+    Site: {{ location.locationName }}
+  </h3>
+
+  <gpds-map [locations]="[location]" *ngIf="location && location.latitude && location.longitude"></gpds-map>
+
+  <gpds-card-section
+    header="Details">
+    <ng-template>
+      <div class="card-body">
+        <gpds-card-row
+          label="Site type"
+          [value]="location.locationType">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Abbreviation"
+          [value]="location.abbreviation">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Country code"
+          [value]="location.countryCode">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Country name"
+          [test]="location.countryName && !location.additionalInfo['Geographical location']"
+          [value]="location.countryName">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Institution address"
+          [value]="location.institutionAddress">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Institution name"
+          [value]="location.institutionAddress">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Altitude"
+          [value]="location.altitude">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Latitude"
+          [value]="location.latitude">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Longitude"
+          [value]="location.longitude">
+        </gpds-card-row>
       </div>
-    </div>
-  </div>
-</div>
+    </ng-template>
+  </gpds-card-section>
+
+  <gpds-card-section
+    header="Additional information"
+    [test]="additionalInfos && additionalInfos.length != 0">
+    <ng-template>
+      <gpds-card-table
+        [rows]="additionalInfos">
+        <ng-template let-row>
+          <tr>
+            <td width="50%">{{ row.key }}</td>
+            <td>{{ row.value }}</td>
+          </tr>
+        </ng-template>
+      </gpds-card-table>
+    </ng-template>
+  </gpds-card-section>
+</ng-container>
diff --git a/frontend/src/app/site-card/site-card.component.scss b/frontend/src/app/site-card/site-card.component.scss
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..55181845b07a912a715c27dd619fd2bb24d19400 100644
--- a/frontend/src/app/site-card/site-card.component.scss
+++ b/frontend/src/app/site-card/site-card.component.scss
@@ -0,0 +1,9 @@
+
+h3 {
+  font-weight: bold;
+  color: #0f6191;
+}
+
+a {
+  text-decoration: underline;
+}
diff --git a/frontend/src/app/site-card/site-card.component.spec.ts b/frontend/src/app/site-card/site-card.component.spec.ts
index 931f97b313267084e98d5d4d04f632b8eeffce26..322fb4b94eb7677e700782b6886e57effa33e007 100644
--- a/frontend/src/app/site-card/site-card.component.spec.ts
+++ b/frontend/src/app/site-card/site-card.component.spec.ts
@@ -3,27 +3,32 @@ import { async, TestBed } from '@angular/core/testing';
 import { SiteCardComponent } from './site-card.component';
 import { MapComponent } from '../map/map.component';
 import { BrapiService } from '../brapi.service';
-import { SiteModel } from '../models/site.model';
 import { ActivatedRoute, convertToParamMap } from '@angular/router';
 import { of } from 'rxjs';
+import { BrapiLocation, BrapiResult } from '../models/brapi.model';
+import { CardRowComponent } from '../card-row/card-row.component';
+import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.component';
+import { CardTableComponent } from '../card-table/card-table.component';
+import { CardSectionComponent } from '../card-section/card-section.component';
 
 describe('SiteCardComponent', () => {
     const brapiService = jasmine.createSpyObj(
         'BrapiService', ['location']
     );
-    const site: SiteModel = {
+    const response: BrapiResult<BrapiLocation> = {
+        metadata: null,
         result: {
-            locationDbId: 1,
+            locationDbId: '1',
             latitude: 1,
             longitude: 1,
             altitude: 1,
             institutionName: '',
-            institutionAdress: '',
+            institutionAddress: '',
             countryName: '',
             countryCode: '',
             locationType: '',
             abbreviation: '',
-            name: 'site1',
+            locationName: 'site1',
             additionalInfo: {
                 Topography: '',
                 Slope: '',
@@ -41,15 +46,16 @@ describe('SiteCardComponent', () => {
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            declarations: [SiteCardComponent, MapComponent],
+            declarations: [
+                SiteCardComponent, MapComponent, LoadingSpinnerComponent,
+                CardRowComponent, CardSectionComponent, CardTableComponent
+            ],
             providers: [
                 { provide: BrapiService, useValue: brapiService },
                 {
                     provide: ActivatedRoute,
                     useValue: {
-                        snapshot: {
-                            paramMap: convertToParamMap( { id: 1 } )
-                        }
+                        paramMap: of(convertToParamMap({ id: 1 }))
                     }
                 }
             ]
@@ -65,19 +71,9 @@ describe('SiteCardComponent', () => {
     it('should display site', () => {
         const fixture = TestBed.createComponent(SiteCardComponent);
         const component = fixture.componentInstance;
-        brapiService.location.and.returnValues(of(site));
+        brapiService.location.and.returnValues(of(response));
         fixture.detectChanges();
         const element = fixture.nativeElement;
-        expect(element.querySelector('h1').textContent).toBe(' Site: site1 ');
-    });
-
-    it('should display error message when site loading is in error', () => {
-        const fixture = TestBed.createComponent(SiteCardComponent);
-        const component = fixture.componentInstance;
-        brapiService.location.and.returnValues(of(site));
-        component.loadingError = true;
-        fixture.detectChanges();
-        const element = fixture.nativeElement;
-        expect(element.querySelector('#error')).toBeTruthy();
+        expect(element.querySelector('h3').textContent).toBe(' Site: site1 ');
     });
 });
diff --git a/frontend/src/app/site-card/site-card.component.ts b/frontend/src/app/site-card/site-card.component.ts
index acb9d79959a29fcf215c4288c4a2944386fa85f3..c92353088b7450ebf51fc75f54ae7b16e870959f 100644
--- a/frontend/src/app/site-card/site-card.component.ts
+++ b/frontend/src/app/site-card/site-card.component.ts
@@ -1,7 +1,8 @@
 import { Component, OnInit } from '@angular/core';
 import { BrapiService } from '../brapi.service';
 import { ActivatedRoute } from '@angular/router';
-import { SiteModel } from '../models/site.model';
+import { BrapiLocation } from '../models/brapi.model';
+import { KeyValueObject } from '../utils';
 
 @Component({
     selector: 'gpds-site-card',
@@ -10,28 +11,30 @@ import { SiteModel } from '../models/site.model';
 })
 export class SiteCardComponent implements OnInit {
 
-    site: SiteModel;
-
-    sites: Array<SiteModel> = new Array<SiteModel>();
-
-    additionalInfoKeys: string[] = [];
-
-    loadingError = false;
+    location: BrapiLocation;
+    additionalInfos: KeyValueObject[];
+    loading = true;
 
     constructor(private brapiService: BrapiService, private route: ActivatedRoute) {
     }
 
     ngOnInit() {
-        // initialize site from location index
-        const locationId = +this.route.snapshot.paramMap.get('id');
-        this.brapiService.location(locationId).subscribe(
-          site => {
-              this.site = site; this.sites.push(site);
-              if (site.result.additionalInfo) {
-                  this.additionalInfoKeys = Object.keys(site.result.additionalInfo);
-              }
-          },
-          () => { console.log('Unable to load site...'); this.loadingError = true; }
-        );
+        this.route.paramMap.subscribe(paramMap => {
+            // initialize site from location ID
+            const locationId = paramMap.get('id');
+
+            this.brapiService.location(locationId).subscribe(
+                response => {
+                    this.location = response.result;
+
+                    this.additionalInfos = [];
+                    if (this.location.additionalInfo) {
+                        this.additionalInfos = KeyValueObject.fromObject(this.location.additionalInfo);
+                    }
+                    this.loading = false;
+                }
+            );
+        });
+
     }
 }
diff --git a/frontend/src/app/study-card/study-card.component.html b/frontend/src/app/study-card/study-card.component.html
index 4012bc02ad58a62835b40d95844664c9a8c44baa..e85da88328d5d9bcf220f0a13c78f7cf27198efc 100644
--- a/frontend/src/app/study-card/study-card.component.html
+++ b/frontend/src/app/study-card/study-card.component.html
@@ -1,59 +1,258 @@
-<h3>
-  Study: {{ study.result.name }}
-</h3>
-
-
-<div class="row">
-  <table class="table table-sm">
-    <!--<tr>
-      <td><b>Data source</b></td>
-      <td>{{ }}</td>
-    </tr>-->
-    <tr>
-      <td><b>Name</b></td>
-      <td>{{ study.result.name }}</td>
-    </tr>
-    <tr>
-      <td><b>Program name</b></td>
-      <td>{{ study.result.programName }}</td>
-    </tr>
-    <tr>
-      <td><b>Description</b></td>
-      <td>{{ study.result.studyDescription }}</td>
-    </tr>
-    <tr>
-      <td><b>Permanent Unique Identifier</b></td>
-      <td></td>
-    </tr>
-    <tr>
-      <td><b>Active</b></td>
-      <td>{{ study.result.active }}</td>
-    </tr>
-    <tr>
-      <td><b>Seasons</b></td>
-      <td *ngFor="let season of study.result.seasons"><div>{{ season }}</div></td>
-    </tr>
-    <tr *ngIf="study.result.startDate != null">
-      <td><b>Date</b></td>
-      <td *ngIf="study.result.endDate != null; else withoutEnd">From {{ study.result.startDate }} to {{ study.result.endDate }}</td>
-      <ng-template><td>{{ study.result.startDate }}</td></ng-template>
-    </tr>
-    <tr>
-      <td><b>Location name</b></td>
-      <td>{{ study.result.locationName }}</td>
-    </tr>
-    <tr>
-      <td><b>Source</b></td>
-      <td>{{ study.result.dataLinks.name }} {{ study.result.dataLinks.documentationURL }}</td>
-    </tr>
-   <!-- <ng-container *ngFor="let info of objectKeys(study.result.additionalInfo)">
-      <tr>
-        <td><b>{{ info }}</b></td><td>{{ study.result.additionalInfo[info] }}</td>
-      </tr>
-    </ng-container>-->
-    <!--<tr>
-      <td>Additional information</td>
-      <td>{{ study.result.additionalInfo }}</td>
-    </tr>-->
-  </table>
-</div>
+<gpds-loading-spinner [loading]="loading"></gpds-loading-spinner>
+
+<ng-container *ngIf="study">
+  <h3>
+    Study {{ study.studyType }}: {{ study.studyName }}
+  </h3>
+
+  <!-- Display the map -->
+  <gpds-map *ngIf="checkLocation(study.location)" [locations]="[study.location]"></gpds-map>
+
+  <!-- Display the study's info -->
+  <gpds-card-section
+    header="Identification">
+    <ng-template>
+      <div class="card-body">
+
+        <gpds-card-row
+          label="Name"
+          [value]="study.studyName">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Identifier"
+          [test]="study.studyDbId">
+          <ng-template>
+            {{ study.studyDbId }}
+            <a target="_blank" *ngIf="studySource && study.documentationURL" [href]="study.documentationURL">
+              ( Link to this study on {{ studySource["schema:identifier"] }} )
+            </a>
+          </ng-template>
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Permanent unique identifier"
+          [test]="study['@id'] && isNotURN(study['@id'])"
+          [value]="study['@id']">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Source"
+          [test]="studySource">
+          <ng-template>
+            <a target="_blank"
+               [href]="studySource['schema:url']">
+              <img [src]="studySource['schema:image']" alt="Study source image"/>
+            </a>
+          </ng-template>
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Data source"
+          [test]="studySource && study.documentationURL">
+          <ng-template>
+            <a target="_blank" *ngIf="study.documentationURL" [href]="study.documentationURL">
+              ( Link to this study on {{ studySource["schema:identifier"] }} )
+            </a>
+          </ng-template>
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Project name"
+          [value]="study.programName">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Description"
+          [value]="study.studyDescription">
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Active"
+          [test]="study.active != null">
+          <ng-template>
+            {{ study.active ? "Yes" : "No" }}
+          </ng-template>
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Seasons"
+          [test]="study.seasons && study.seasons.length != 0">
+          <ng-template>
+            {{ study.seasons.join(', ') }}
+          </ng-template>
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Date"
+          [test]="study.startDate">
+          <ng-template>
+            {{ study.endDate ?
+            'From ' + study.startDate + ' to ' + study.endDate :
+            'Started on ' + study.startDate }}
+          </ng-template>
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Location name"
+          [test]="study.location && study.location.locationDbId">
+          <ng-template>
+            <a [routerLink]="['/sites', study.location.locationDbId]">
+              {{ study.location.locationName }}
+            </a>
+          </ng-template>
+        </gpds-card-row>
+
+        <gpds-card-row
+          label="Data files"
+          [test]="study.dataLinks && study.dataLinks.length != 0">
+          <ng-template>
+            <div *ngFor="let dataLink of study.dataLinks">
+              <a [href]="dataLink.url">
+                {{ dataLink.name }}
+              </a>
+            </div>
+          </ng-template>
+        </gpds-card-row>
+      </div>
+
+    </ng-template>
+  </gpds-card-section>
+
+
+  <gpds-card-section
+    header="Genotype"
+    [test]="studyGermplasms && studyGermplasms.length != 0">
+    <ng-template>
+      <gpds-card-table
+        [headers]="[
+          'Accession number',
+          'Name',
+          'Taxon'
+        ]"
+        [rows]="studyGermplasms">
+        <ng-template let-row>
+          <tr>
+            <td>
+              <a [routerLink]="['/germplasm', row.germplasmDbId]">
+                {{ row.accessionNumber }}
+              </a>
+            </td>
+            <td>{{ row.germplasmName }}</td>
+            <td>{{ row.genus }} {{ row.species }} {{ row.subtaxa }}</td>
+          </tr>
+        </ng-template>
+      </gpds-card-table>
+    </ng-template>
+  </gpds-card-section>
+
+
+  <gpds-card-section
+    header="Variables"
+    [test]="studyObservationVariables && studyObservationVariables.length != 0">
+    <ng-template>
+      <gpds-card-table
+        [headers]="[
+          'Variable id',
+          'Variable short name',
+          'Variable long name',
+          'Ontology name',
+          'Trait description'
+        ]"
+        [rows]="studyObservationVariables">
+        <ng-template let-row>
+          <tr>
+            <td>
+              <ng-template #name>{{ row.observationVariableDbId }}</ng-template>
+              <ng-template #link>
+                <a [href]=row.documentationURL>{{ row.observationVariableDbId }}</a>
+              </ng-template>
+              <ng-container *ngIf="row.documentationURL; then link else name"></ng-container>
+            </td>
+            <td>{{ row.name }}</td>
+            <td>{{ row.synonyms[0] }}</td>
+            <td>{{ row.ontologyName }}</td>
+            <td>{{ row.trait.description }}</td>
+          </tr>
+        </ng-template>
+      </gpds-card-table>
+    </ng-template>
+  </gpds-card-section>
+
+  <gpds-card-section
+    header="Data Set"
+    [test]="studyDataset && studyDataset.length != 0">
+    <ng-template>
+      <gpds-card-table
+        [headers]="[
+          'Name',
+          'Type',
+          'Linked studies identifiers'
+        ]"
+        [rows]="studyDataset">
+        <ng-template let-row>
+          <tr>
+            <td>
+              <ng-template #name>{{ row.trialName }}</ng-template>
+              <ng-template #link>
+                <a [href]=row.documentationURL>{{ row.trialName }}</a>
+              </ng-template>
+              <ng-container *ngIf="row.documentationURL; then link else name"></ng-container>
+            </td>
+            <td>{{ row.trialType }}</td>
+            <td width="60%">
+              <ng-container *ngFor="let trialStudy of row.studies">
+                <a
+                  [routerLink]="['/studies', trialStudy.studyDbId]">
+                  {{ trialStudy.studyName.trim() }}
+                </a>;
+              </ng-container>
+            </td>
+          </tr>
+        </ng-template>
+      </gpds-card-table>
+    </ng-template>
+  </gpds-card-section>
+
+  <gpds-card-section
+    header="Contact"
+    [test]="study.contacts && study.contacts.length != 0">
+    <ng-template>
+      <gpds-card-table
+        [headers]="[
+          'Role',
+           'Name',
+           'Email',
+           'Institution'
+        ]"
+        [rows]="study.contacts">
+        <ng-template let-row>
+          <tr>
+            <td>{{ row.type }}</td>
+            <td>{{ row.name }}</td>
+            <td>{{ row.email }}</td>
+            <td>{{ row.institutionName }}</td>
+          </tr>
+        </ng-template>
+      </gpds-card-table>
+    </ng-template>
+  </gpds-card-section>
+
+  <gpds-card-section
+    header="Additional information"
+    [test]="additionalInfos && additionalInfos.length != 0">
+    <ng-template>
+      <gpds-card-table
+        [rows]="additionalInfos">
+        <ng-template let-row>
+          <tr>
+            <td width="50%">{{ row.key }}</td>
+            <td>{{ row.value }}</td>
+          </tr>
+        </ng-template>
+      </gpds-card-table>
+    </ng-template>
+  </gpds-card-section>
+
+</ng-container>
+
diff --git a/frontend/src/app/study-card/study-card.component.scss b/frontend/src/app/study-card/study-card.component.scss
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..55181845b07a912a715c27dd619fd2bb24d19400 100644
--- a/frontend/src/app/study-card/study-card.component.scss
+++ b/frontend/src/app/study-card/study-card.component.scss
@@ -0,0 +1,9 @@
+
+h3 {
+  font-weight: bold;
+  color: #0f6191;
+}
+
+a {
+  text-decoration: underline;
+}
diff --git a/frontend/src/app/study-card/study-card.component.spec.ts b/frontend/src/app/study-card/study-card.component.spec.ts
index f37f7425304ec09a5ba119bd9c81fd76801d4912..e2407e4792107bf857cc42f5c40fa3c3720c924b 100644
--- a/frontend/src/app/study-card/study-card.component.spec.ts
+++ b/frontend/src/app/study-card/study-card.component.spec.ts
@@ -1,21 +1,264 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, TestBed } from '@angular/core/testing';
 
 import { StudyCardComponent } from './study-card.component';
+import { ComponentTester, fakeRoute, speculoosMatchers } from 'ngx-speculoos';
+import { ActivatedRoute } from '@angular/router';
+import { of } from 'rxjs';
+import {
+    BrapiContacts,
+    BrapiGermplasm,
+    BrapiLocation,
+    BrapiObservationVariable,
+    BrapiResult,
+    BrapiResults,
+    BrapiStudy,
+    BrapiTrial
+} from '../models/brapi.model';
+import { BrapiService } from '../brapi.service';
+import { GnpisService } from '../gnpis.service';
+import { DataDiscoverySource } from '../models/data-discovery.model';
+import { MapComponent } from '../map/map.component';
+import { RouterTestingModule } from '@angular/router/testing';
+import { CardSectionComponent } from '../card-section/card-section.component';
+import { CardRowComponent } from '../card-row/card-row.component';
+import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.component';
+import { CardTableComponent } from '../card-table/card-table.component';
 
 describe('StudyCardComponent', () => {
-    let component: StudyCardComponent;
-    let fixture: ComponentFixture<StudyCardComponent>;
+    beforeEach(() => jasmine.addMatchers(speculoosMatchers));
+
+    class StudyCardComponentTester extends ComponentTester<StudyCardComponent> {
+        constructor() {
+            super(StudyCardComponent);
+        }
+
+        get title() {
+            return this.element('h3');
+        }
+
+        get cardHeader() {
+            return this.elements('div.card-header');
+        }
+
+        get studyInfo() {
+            return this.elements('div.col');
+        }
+
+        get map() {
+            return this.element('gpds-map');
+        }
+    }
+
+    const brapiService = jasmine.createSpyObj(
+        'BrapiService', [
+            'study',
+            'studyTrials',
+            'studyObservationVariables',
+            'studyGermplasms'
+        ]
+    );
+
+    const gnpisService = jasmine.createSpyObj(
+        'GnpisService', ['getSource']
+    );
+
+    const activatedRoute = fakeRoute({
+        params: of({ id: 's1' })
+    });
+
+    const location: BrapiLocation = {
+        locationDbId: '1',
+        locationName: 'loc1',
+        locationType: 'Collecting site',
+        abbreviation: null,
+        countryCode: 'Fr',
+        countryName: 'France',
+        institutionAddress: null,
+        institutionName: 'Insti',
+        altitude: null,
+        latitude: null,
+        longitude: null,
+    };
+
+    const contacts: BrapiContacts = {
+        contactDbId: 'c1',
+        name: 'contact1',
+        email: 'contact1@email.com',
+        type: 'contact',
+        institutionName: 'Inst',
+    };
+
+    const searchStudy: BrapiResult<BrapiStudy> = {
+        metadata: null,
+        result: {
+            studyDbId: 's1',
+            studyType: 'phenotype',
+            studyName: 'study1',
+            studyDescription: null,
+            seasons: ['winter', '2019'],
+            startDate: '2018',
+            endDate: null,
+            active: true,
+            programDbId: 'p1',
+            programName: 'program1',
+            trialDbIds: ['10', '20'],
+            location: location,
+            contacts: [contacts],
+            additionalInfo: null,
+            documentationURL: 'http://example.com/Study/s1',
+            dataLinks: [],
+            'schema:includedInDataCatalog': 'src1'
+        } as BrapiStudy
+    };
+
+    const trial1: BrapiResult<BrapiTrial> = {
+        metadata: null,
+        result: {
+            trialDbId: '10',
+            trialName: 'trial_10',
+            trialType: 'project',
+            active: true,
+            studies: [
+                { studyDbId: 's1', studyName: 'study1' },
+                { studyDbId: 's2', studyName: 'study2' }
+            ]
+
+        }
+    };
+    const trial2: BrapiResult<BrapiTrial> = {
+        metadata: null,
+        result: {
+            trialDbId: '20',
+            trialName: 'trial_20',
+            trialType: 'project',
+            active: true,
+            studies: [
+                { studyDbId: 's3', studyName: 'study3' },
+                { studyDbId: 's4', studyName: 'study4' }
+            ]
+
+        }
+    };
+    const osbVariable: BrapiResults<BrapiObservationVariable> = {
+        metadata: null,
+        result: {
+            data: [{
+                observationVariableDbId: 'var1',
+                contextOfUse: null,
+                institution: 'Insti',
+                crop: 'WoodyPlant',
+                name: 'varaiable1',
+                ontologyDbId: 'WPO',
+                ontologyName: 'Woody Plant Ontology',
+                synonyms: ['First synonym'],
+                language: 'EN',
+                trait: {
+                    traitDbId: 't1',
+                    name: 'trait1',
+                    description: null,
+                },
+                documentationURL: null,
+            }]
+        }
+    };
+
+    const germplasm: BrapiResults<BrapiGermplasm> = {
+        metadata: null,
+        result: {
+            data: [{
+                germplasmDbId: 'g1',
+                accessionNumber: 'G_10',
+                germplasmName: 'germplam1',
+                genus: 'Populus',
+                species: 'x generosa',
+                subtaxa: ''
+            }, {
+                germplasmDbId: 'g2',
+                accessionNumber: 'G_20',
+                germplasmName: 'germplam2',
+                genus: 'Triticum',
+                species: 'aestivum',
+                subtaxa: 'subsp'
+            }],
+        }
+    };
+
+    const source: DataDiscoverySource = {
+        '@id': 'src1',
+        '@type': ['schema:DataCatalog'],
+        'schema:identifier': 'srcId',
+        'schema:name': 'source1',
+        'schema:url': 'srcUrl',
+        'schema:image': null
+    };
+
+    brapiService.study.and.returnValue(of(searchStudy));
+    brapiService.studyTrials.withArgs('10').and.returnValue(of(trial1));
+    brapiService.studyTrials.withArgs('20').and.returnValue(of(trial2));
+    brapiService.studyObservationVariables.and.returnValue(of(osbVariable));
+    brapiService.studyGermplasms.and.returnValue(of(germplasm));
+    gnpisService.getSource.and.returnValue(of(source));
 
     beforeEach(async(() => {
         TestBed.configureTestingModule({
-            declarations: [StudyCardComponent]
-        })
-            .compileComponents();
+            imports: [RouterTestingModule],
+            declarations: [
+                StudyCardComponent, MapComponent, CardSectionComponent,
+                CardRowComponent, LoadingSpinnerComponent, CardTableComponent
+            ],
+            providers: [
+                { provide: ActivatedRoute, useValue: activatedRoute },
+                { provide: BrapiService, useValue: brapiService },
+                { provide: GnpisService, useValue: gnpisService }
+            ]
+        });
     }));
 
-    beforeEach(() => {
-        fixture = TestBed.createComponent(StudyCardComponent);
-        component = fixture.componentInstance;
-        fixture.detectChanges();
-    });
+
+    it('should fetch the study data information but not display map', async(() => {
+        const tester = new StudyCardComponentTester();
+        const component = tester.componentInstance;
+        tester.detectChanges();
+
+        component.loaded.then(() => {
+            expect(component.study).toBeTruthy();
+            component.study.location.longitude = null;
+            component.study.location.latitude = null;
+            tester.detectChanges();
+
+            expect(tester.map).toBeFalsy();
+            expect(tester.title).toContainText('Study phenotype: study1');
+
+            expect(tester.cardHeader[0]).toContainText('Identification');
+
+            expect(tester.studyInfo[1]).toContainText('Link to this study on srcId');
+
+            expect(tester.cardHeader[1]).toContainText('Genotype');
+            expect(component.studyGermplasms.length).toEqual(2);
+
+            expect(tester.cardHeader[2]).toContainText('Variable');
+            expect(component.studyObservationVariables.length).toEqual(1);
+
+            expect(tester.cardHeader[3]).toContainText(' Data Set ');
+            expect(component.trialsIds.length).toEqual(2);
+            expect(component.studyDataset.length).toEqual(2);
+
+            expect(tester.cardHeader[4]).toContainText('Contact');
+        });
+    }));
+
+    it('should display map', async(() => {
+        const tester = new StudyCardComponentTester();
+        const component = tester.componentInstance;
+        tester.detectChanges();
+
+        component.loaded.then(() => {
+            expect(component.study).toBeTruthy();
+            component.study.location.latitude = 48.8534;
+            component.study.location.longitude = 2.3488;
+            tester.detectChanges();
+
+            expect(tester.map).toBeTruthy();
+        });
+    }));
 });
diff --git a/frontend/src/app/study-card/study-card.component.ts b/frontend/src/app/study-card/study-card.component.ts
index 72341f2625f9ab572a36f00b989bfbf0b56c52a5..a8f729b2bef6861b93a19043912a311248e5b14f 100644
--- a/frontend/src/app/study-card/study-card.component.ts
+++ b/frontend/src/app/study-card/study-card.component.ts
@@ -1,6 +1,11 @@
 import { Component, OnInit } from '@angular/core';
 import { BrapiService } from '../brapi.service';
 import { ActivatedRoute } from '@angular/router';
+import { BrapiGermplasm, BrapiLocation, BrapiObservationVariable, BrapiStudy, BrapiTrial } from '../models/brapi.model';
+
+import { GnpisService } from '../gnpis.service';
+import { DataDiscoverySource } from '../models/data-discovery.model';
+import { KeyValueObject } from '../utils';
 
 @Component({
     selector: 'gpds-study-card',
@@ -9,27 +14,89 @@ import { ActivatedRoute } from '@angular/router';
 })
 export class StudyCardComponent implements OnInit {
 
-    study: object = {};
-    studyGermplasms: string[] = [];
-    studyObservationVariables: string[] = [];
-    objectKeys = Object.keys;
+    study: BrapiStudy;
+    studySource: DataDiscoverySource;
+    studyGermplasms: BrapiGermplasm[];
+    studyObservationVariables: BrapiObservationVariable[];
+    additionalInfos: KeyValueObject[];
+    studyDataset: BrapiTrial[];
+    trialsIds: string[] = [];
+    loading: boolean;
+    loaded: Promise<any>;
 
-    constructor(private brapiService: BrapiService, private route: ActivatedRoute) {
+    constructor(private brapiService: BrapiService, private  gnpisService: GnpisService, private route: ActivatedRoute) {
     }
 
     ngOnInit() {
-        const studyDbId = this.route.snapshot.paramMap.get('id');
-        this.brapiService.study(studyDbId)
-            .subscribe(study => {
-                this.study = study;
-            });
-        this.brapiService.studyObservationVariables(studyDbId)
-            .subscribe(studyObsVar => {
-                this.studyObservationVariables.concat(studyObsVar);
-            });
-        this.brapiService.studyGermplasms(studyDbId)
-            .subscribe(studyGermplasm => {
-                this.studyGermplasms.concat(studyGermplasm);
+        this.route.paramMap.subscribe(paramMap => {
+            this.loading = true;
+            const studyDbId = paramMap.get('id');
+
+            const study$ = this.brapiService.study(studyDbId).toPromise();
+            study$
+                .then(response => {
+                    this.study = response.result;
+
+                    this.additionalInfos = [];
+                    if (this.study.additionalInfo) {
+                        this.additionalInfos = KeyValueObject.fromObject(this.study.additionalInfo);
+                    }
+
+                    // Get study trials
+                    this.trialsIds = this.study.trialDbIds;
+                    this.studyDataset = [];
+                    if (this.trialsIds && this.trialsIds !== []) {
+                        for (const trialsId of this.trialsIds) {
+                            this.brapiService.studyTrials(trialsId)
+                                .subscribe(trialResponse => {
+                                    const trial = trialResponse.result;
+
+                                    // Remove current study from trial studies
+                                    trial.studies = trial.studies
+                                        .filter(study => study.studyDbId !== studyDbId);
+                                    this.studyDataset.push(trial);
+                                });
+                        }
+                    }
+
+                    // Get study source
+                    const sourceURI = this.study['schema:includedInDataCatalog'];
+                    if (sourceURI) {
+                        const source$ = this.gnpisService.getSource(sourceURI);
+                        source$
+                            .subscribe(src => {
+                                this.studySource = src;
+                            });
+                    }
+                });
+
+            this.studyObservationVariables = [];
+            const variable$ = this.brapiService.studyObservationVariables(studyDbId).toPromise();
+            variable$
+                .then(response => {
+                    this.studyObservationVariables = response.result.data;
+                });
+
+            this.studyGermplasms = [];
+            const germplasm$ = this.brapiService.studyGermplasms(studyDbId).toPromise();
+            germplasm$
+                .then(studyGermplasm => {
+                    this.studyGermplasms = studyGermplasm.result.data;
+                });
+
+            this.loaded = Promise.all([study$, variable$, germplasm$]);
+            this.loaded.then(() => {
+                this.loading = false;
             });
+        });
+
+    }
+
+    checkLocation(location: BrapiLocation) {
+        return location && location.longitude && location.latitude;
+    }
+
+    isNotURN(pui: string) {
+        return !(pui.substring(0, 3) === 'urn');
     }
 }
diff --git a/frontend/src/app/utils.ts b/frontend/src/app/utils.ts
index d91e5d9590533fc0c763e622564fff7ffa1c76b7..4ff7410ef9771ae2047cdec6ee695a931ca8a2ad 100644
--- a/frontend/src/app/utils.ts
+++ b/frontend/src/app/utils.ts
@@ -7,3 +7,20 @@ export function asArray(obj) {
     }
     return [obj];
 }
+
+export class KeyValueObject {
+    public key: string;
+    public value: string;
+
+    constructor(key: string, value: string) {
+        this.key = key;
+        this.value = value;
+    }
+
+    static fromObject(o: object): KeyValueObject[] {
+        return Object.entries(o)
+            .filter(([key, value]) => !!key && !!value)
+            .map(([key, value]) => new KeyValueObject(key, value));
+    }
+}
+
diff --git a/frontend/src/assets/gpds/theme.scss b/frontend/src/assets/gpds/theme.scss
index a649ce5ab7e37c471dcc90d1a9c0a0c66c7d4007..168116f3953fb0a269af34063b9f1c647c2a2871 100644
--- a/frontend/src/assets/gpds/theme.scss
+++ b/frontend/src/assets/gpds/theme.scss
@@ -16,6 +16,7 @@ $enable-shadows: true;
 @import "~bootstrap/scss/functions";
 @import "~bootstrap/scss/variables";
 @import "~bootstrap/scss/mixins";
+@import "~bootstrap/scss/tables";
 
 // public custom variables used in this theme, and in component styles
 
diff --git a/frontend/src/tslint.json b/frontend/src/tslint.json
index 0c38e753c0333b8b1cb1f74a0aad16f7985e092e..7e545ab7d80894dd3456be1fee601237e43b7bff 100644
--- a/frontend/src/tslint.json
+++ b/frontend/src/tslint.json
@@ -13,6 +13,9 @@
       "gpds",
       "kebab-case"
     ],
-    "template-cyclomatic-complexity": [true, 15]
+    "template-cyclomatic-complexity": [
+      true,
+      10
+    ]
   }
 }