From 043f49c784bc007ca0fa756ca4ed2d3b08843ad9 Mon Sep 17 00:00:00 2001
From: Yassine Doghri <yassine@doghri.fr>
Date: Thu, 27 Aug 2020 10:05:44 +0000
Subject: [PATCH] feat: add platforms form in podcast settings

- set and remove platform links for a podcast
- remove unnecessary fields from platforms and platform_links tables
- add platforms svg icons to show in form
- update platform and auth seeders
- update svgo config for images
---
 .svgo.yml                                     |   5 +-
 app/Config/Routes.php                         |  24 ++
 app/Controllers/Admin/PodcastSettings.php     |  98 ++++++
 .../2020-06-05-190000_add_platforms.php       |  36 +--
 .../2020-06-08-160000_add_platform_links.php  |  13 +-
 app/Database/Seeds/AuthSeeder.php             |   5 +
 app/Database/Seeds/PlatformSeeder.php         | 304 ++++--------------
 app/Entities/Platform.php                     |  24 ++
 app/Helpers/svg_helper.php                    |  27 ++
 app/Language/en/AdminNavigation.php           |   1 +
 app/Language/en/Breadcrumb.php                |   2 +
 app/Language/en/Platforms.php                 |  22 ++
 app/Models/PlatformLinkModel.php              |  33 --
 app/Models/PlatformModel.php                  |  51 +++
 app/Views/_assets/icons/delete-bin.svg        |   6 +
 app/Views/_assets/icons/upload.svg            |   6 +
 .../_assets/images/platforms/_default.svg     |   7 +
 .../images/platforms/apple-podcasts.svg       |  14 +
 .../_assets/images/platforms/blubrry.svg      |   6 +
 .../_assets/images/platforms/castbox.svg      |  17 +
 app/Views/_assets/images/platforms/castro.svg |  17 +
 app/Views/_assets/images/platforms/deezer.svg |  54 ++++
 .../images/platforms/google-podcasts.svg      |  14 +
 app/Views/_assets/images/platforms/ivoox.svg  |   7 +
 .../_assets/images/platforms/listennotes.svg  |  10 +
 .../_assets/images/platforms/overcast.svg     |   6 +
 .../_assets/images/platforms/playerfm.svg     |   9 +
 .../_assets/images/platforms/pocketcasts.svg  |   6 +
 .../_assets/images/platforms/podbean.svg      |   9 +
 .../images/platforms/podcast-addict.svg       |  13 +
 .../_assets/images/platforms/podchaser.svg    |   8 +
 .../_assets/images/platforms/podtail.svg      |  13 +
 .../_assets/images/platforms/radiopublic.svg  |   8 +
 .../_assets/images/platforms/spotify.svg      |   6 +
 .../_assets/images/platforms/spreaker.svg     |   6 +
 .../_assets/images/platforms/stitcher.svg     |  10 +
 app/Views/_assets/images/platforms/tunein.svg |   8 +
 app/Views/admin/_header.php                   |   2 +-
 app/Views/admin/_sidenav.php                  |   2 +-
 .../admin/podcast/settings/dashboard.php      |   9 +
 .../admin/podcast/settings/platforms.php      |  99 ++++++
 app/Views/admin/podcast/view.php              |   4 +
 app/Views/auth/_layout.php                    |   5 +-
 package.json                                  |   2 +-
 44 files changed, 699 insertions(+), 329 deletions(-)
 create mode 100644 app/Controllers/Admin/PodcastSettings.php
 create mode 100644 app/Entities/Platform.php
 create mode 100644 app/Language/en/Platforms.php
 delete mode 100644 app/Models/PlatformLinkModel.php
 create mode 100644 app/Views/_assets/icons/delete-bin.svg
 create mode 100644 app/Views/_assets/icons/upload.svg
 create mode 100644 app/Views/_assets/images/platforms/_default.svg
 create mode 100644 app/Views/_assets/images/platforms/apple-podcasts.svg
 create mode 100644 app/Views/_assets/images/platforms/blubrry.svg
 create mode 100644 app/Views/_assets/images/platforms/castbox.svg
 create mode 100644 app/Views/_assets/images/platforms/castro.svg
 create mode 100644 app/Views/_assets/images/platforms/deezer.svg
 create mode 100644 app/Views/_assets/images/platforms/google-podcasts.svg
 create mode 100644 app/Views/_assets/images/platforms/ivoox.svg
 create mode 100644 app/Views/_assets/images/platforms/listennotes.svg
 create mode 100644 app/Views/_assets/images/platforms/overcast.svg
 create mode 100644 app/Views/_assets/images/platforms/playerfm.svg
 create mode 100644 app/Views/_assets/images/platforms/pocketcasts.svg
 create mode 100644 app/Views/_assets/images/platforms/podbean.svg
 create mode 100644 app/Views/_assets/images/platforms/podcast-addict.svg
 create mode 100644 app/Views/_assets/images/platforms/podchaser.svg
 create mode 100644 app/Views/_assets/images/platforms/podtail.svg
 create mode 100644 app/Views/_assets/images/platforms/radiopublic.svg
 create mode 100644 app/Views/_assets/images/platforms/spotify.svg
 create mode 100644 app/Views/_assets/images/platforms/spreaker.svg
 create mode 100644 app/Views/_assets/images/platforms/stitcher.svg
 create mode 100644 app/Views/_assets/images/platforms/tunein.svg
 create mode 100644 app/Views/admin/podcast/settings/dashboard.php
 create mode 100644 app/Views/admin/podcast/settings/platforms.php

diff --git a/.svgo.yml b/.svgo.yml
index 562727d81e..b4177b4f84 100644
--- a/.svgo.yml
+++ b/.svgo.yml
@@ -1,8 +1,5 @@
 plugins:
   - removeXMLNS: true
   - removeDimensions: true
-  - addAttributesToSVGElement:
-      attributes:
-        - width: "1em"
-        - height: "1em"
   - sortAttrs: true
+  - prefixIds: true
diff --git a/app/Config/Routes.php b/app/Config/Routes.php
index be223070b6..2472310c8b 100644
--- a/app/Config/Routes.php
+++ b/app/Config/Routes.php
@@ -196,6 +196,30 @@ $routes->group(
                         ]);
                     });
                 });
+
+                $routes->group('settings', function ($routes) {
+                    $routes->get('/', 'PodcastSettings/$1', [
+                        'as' => 'podcast-settings',
+                    ]);
+                    $routes->get('platforms', 'PodcastSettings::platforms/$1', [
+                        'as' => 'platforms',
+                        'filter' => 'permission:podcast-manage_platforms',
+                    ]);
+                    $routes->post(
+                        'platforms',
+                        'PodcastSettings::attemptPlatformsUpdate/$1',
+                        ['filter' => 'permission:podcast-manage_platforms']
+                    );
+
+                    $routes->get(
+                        'platforms/(:num)/remove-link',
+                        'PodcastSettings::removePlatformLink/$1/$2',
+                        [
+                            'as' => 'platforms-remove',
+                            'filter' => 'permission:podcast-manage_platforms',
+                        ]
+                    );
+                });
             });
         });
 
diff --git a/app/Controllers/Admin/PodcastSettings.php b/app/Controllers/Admin/PodcastSettings.php
new file mode 100644
index 0000000000..012128b0ef
--- /dev/null
+++ b/app/Controllers/Admin/PodcastSettings.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+namespace App\Controllers\Admin;
+
+use App\Models\PlatformModel;
+use App\Models\PodcastModel;
+use Config\Services;
+
+class PodcastSettings extends BaseController
+{
+    /**
+     * @var \App\Entities\Podcast|null
+     */
+    protected $podcast;
+
+    public function _remap($method, ...$params)
+    {
+        if (!($this->podcast = (new PodcastModel())->find($params[0]))) {
+            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
+        }
+        unset($params[0]);
+
+        return $this->$method(...$params);
+    }
+
+    public function index()
+    {
+        return view('admin/podcast/settings/dashboard');
+    }
+
+    public function platforms()
+    {
+        helper('form');
+
+        $data = [
+            'podcast' => $this->podcast,
+            'platforms' => (new PlatformModel())->getPlatformsWithLinks(),
+        ];
+
+        replace_breadcrumb_params([0 => $this->podcast->title]);
+        return view('admin/podcast/settings/platforms', $data);
+    }
+
+    public function attemptPlatformsUpdate()
+    {
+        $platformModel = new PlatformModel();
+        $validation = Services::validation();
+
+        $platformLinksData = [];
+        foreach (
+            $this->request->getPost('platforms')
+            as $platformName => $platformLink
+        ) {
+            $platformLinkUrl = $platformLink['url'];
+            if (
+                !empty($platformLinkUrl) &&
+                $validation->check($platformLinkUrl, 'valid_url')
+            ) {
+                $platformId = $platformModel->getPlatformId($platformName);
+                array_push($platformLinksData, [
+                    'platform_id' => $platformId,
+                    'podcast_id' => $this->podcast->id,
+                    'link_url' => $platformLinkUrl,
+                    'visible' => array_key_exists('visible', $platformLink)
+                        ? $platformLink['visible'] == 'yes'
+                        : false,
+                ]);
+            }
+        }
+
+        $platformModel->savePlatformLinks(
+            $this->podcast->id,
+            $platformLinksData
+        );
+
+        return redirect()
+            ->back()
+            ->with('message', lang('Platforms.messages.updateSuccess'));
+    }
+
+    public function removePlatformLink($platformId)
+    {
+        (new PlatformModel())->removePlatformLink(
+            $this->podcast->id,
+            $platformId
+        );
+
+        return redirect()
+            ->back()
+            ->with('message', lang('Platforms.messages.removeLinkSuccess'));
+    }
+}
diff --git a/app/Database/Migrations/2020-06-05-190000_add_platforms.php b/app/Database/Migrations/2020-06-05-190000_add_platforms.php
index c012efd6b1..d24750f4b5 100644
--- a/app/Database/Migrations/2020-06-05-190000_add_platforms.php
+++ b/app/Database/Migrations/2020-06-05-190000_add_platforms.php
@@ -29,47 +29,21 @@ class AddPlatforms extends Migration
                 'constraint' => 191,
                 'unique' => true,
             ],
-            'home_url' => [
+            'label' => [
                 'type' => 'VARCHAR',
                 'constraint' => 191,
             ],
-            'submit_url' => [
-                'type' => 'VARCHAR',
-                'constraint' => 191,
-                'null' => true,
-            ],
-            'iosapp_url' => [
+            'home_url' => [
                 'type' => 'VARCHAR',
                 'constraint' => 191,
-                'null' => true,
             ],
-            'androidapp_url' => [
+            'submit_url' => [
                 'type' => 'VARCHAR',
                 'constraint' => 191,
                 'null' => true,
+                'default' => null,
             ],
-            'comment' => [
-                'type' => 'TEXT',
-                'null' => true,
-            ],
-            'display_by_default' => [
-                'type' => 'TINYINT',
-                'constraint' => 1,
-                'default' => 0,
-            ],
-            'ios_deeplink' => [
-                'type' => 'TINYINT',
-                'constraint' => 1,
-                'default' => 0,
-            ],
-            'android_deeplink' => [
-                'type' => 'TINYINT',
-                'constraint' => 1,
-                'default' => 0,
-                'comment' =>
-                    'Android deeplinking for this platform: 0=No, 1=Manual, 2=Automatic.',
-            ],
-            'logo_file_name' => [
+            'icon_filename' => [
                 'type' => 'VARCHAR',
                 'constraint' => 1024,
             ],
diff --git a/app/Database/Migrations/2020-06-08-160000_add_platform_links.php b/app/Database/Migrations/2020-06-08-160000_add_platform_links.php
index bc44741f62..cf0a3be0dd 100644
--- a/app/Database/Migrations/2020-06-08-160000_add_platform_links.php
+++ b/app/Database/Migrations/2020-06-08-160000_add_platform_links.php
@@ -18,12 +18,6 @@ class AddPlatformLinks extends Migration
     public function up()
     {
         $this->forge->addField([
-            'id' => [
-                'type' => 'BIGINT',
-                'constraint' => 20,
-                'unsigned' => true,
-                'auto_increment' => true,
-            ],
             'podcast_id' => [
                 'type' => 'BIGINT',
                 'constraint' => 20,
@@ -38,11 +32,6 @@ class AddPlatformLinks extends Migration
                 'type' => 'VARCHAR',
                 'constraint' => 191,
             ],
-            'comment' => [
-                'type' => 'TEXT',
-                'comment' => 'Comment.',
-                'null' => true,
-            ],
             'visible' => [
                 'type' => 'TINYINT',
                 'constraint' => 1,
@@ -55,7 +44,7 @@ class AddPlatformLinks extends Migration
                 'type' => 'TIMESTAMP',
             ],
         ]);
-        $this->forge->addKey('id', true);
+        $this->forge->addPrimaryKey(['podcast_id', 'platform_id']);
         $this->forge->addForeignKey('podcast_id', 'podcasts', 'id');
         $this->forge->addForeignKey('platform_id', 'platforms', 'id');
         $this->forge->createTable('platform_links');
diff --git a/app/Database/Seeds/AuthSeeder.php b/app/Database/Seeds/AuthSeeder.php
index 06286125e8..ddeac94bc9 100644
--- a/app/Database/Seeds/AuthSeeder.php
+++ b/app/Database/Seeds/AuthSeeder.php
@@ -203,6 +203,11 @@ class AuthSeeder extends Seeder
                 'description' => 'Publish / unpublish a podcast',
                 'has_permission' => ['podcast_admin'],
             ],
+            [
+                'name' => 'manage_platforms',
+                'description' => 'Set / remove platform links of a podcast',
+                'has_permission' => ['podcast_admin'],
+            ],
         ],
         'podcast_episodes' => [
             [
diff --git a/app/Database/Seeds/PlatformSeeder.php b/app/Database/Seeds/PlatformSeeder.php
index c46a0e62f4..11e6b96b1d 100644
--- a/app/Database/Seeds/PlatformSeeder.php
+++ b/app/Database/Seeds/PlatformSeeder.php
@@ -19,328 +19,148 @@ class PlatformSeeder extends Seeder
     {
         $data = [
             [
-                'name' => 'Apple Podcasts',
+                'name' => 'apple-podcasts',
+                'label' => 'Apple Podcasts',
                 'home_url' => 'https://www.apple.com/itunes/podcasts/',
                 'submit_url' =>
                     'https://podcastsconnect.apple.com/my-podcasts/new-feed',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/apple-podcasts/id525463029',
-                'androidapp_url' => '',
-                'comment' => '',
-                'display_by_default' => 1,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'ApplePodcasts.png',
+                'icon_filename' => 'apple-podcasts.svg',
             ],
             [
-                'name' => 'Blubrry',
+                'name' => 'blubrry',
+                'label' => 'Blubrry',
                 'home_url' => 'https://www.blubrry.com/',
                 'submit_url' => 'https://www.blubrry.com/addpodcast.php',
-                'iosapp_url' => '',
-                'androidapp_url' => '',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'blubrry.png',
+                'icon_filename' => 'blubrry.svg',
             ],
             [
-                'name' => 'Castbox',
+                'name' => 'castbox',
+                'label' => 'Castbox',
                 'home_url' => 'https://castbox.fm/',
                 'submit_url' =>
                     'https://helpcenter.castbox.fm/portal/kb/articles/submit-my-podcast',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/castbox-the-podcast-app/id1243410543',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=fm.castbox.audiobook.radio.podcast&hl=fr',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 2,
-                'logo_file_name' => 'Castbox.png',
+                'icon_filename' => 'castbox.svg',
             ],
             [
-                'name' => 'Castro',
+                'name' => 'castro',
+                'label' => 'Castro',
                 'home_url' => 'http://castro.fm/',
-                'submit_url' => '',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/apple-store/id1080840241?ign-mpt=uo%3D4',
-                'androidapp_url' => '',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'Castro.png',
+                'submit_url' => null,
+                'icon_filename' => 'castro.svg',
             ],
             [
-                'name' => 'Chartable',
-                'home_url' => 'https://chartable.com/',
-                'submit_url' => 'https://chartable.com/podcasts/submit',
-                'iosapp_url' => '',
-                'androidapp_url' => '',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'Chartable.png',
-            ],
-            [
-                'name' => 'Deezer',
+                'name' => 'deezer',
+                'label' => 'Deezer',
                 'home_url' => 'https://www.deezer.com/',
                 'submit_url' => 'https://podcasters.deezer.com/submission',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/deezer-music-podcast-player/id292738169',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=deezer.android.app',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 2,
-                'logo_file_name' => 'Deezer.png',
+                'icon_filename' => 'deezer.svg',
             ],
             [
-                'name' => 'Google Podcasts',
+                'name' => 'google-podcasts',
+                'label' => 'Google Podcasts',
                 'home_url' => 'https://podcasts.google.com/about',
                 'submit_url' =>
                     'https://search.google.com/search-console/about',
-                'iosapp_url' => '',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.google.android.apps.podcasts',
-                'comment' => '',
-                'display_by_default' => 1,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 1,
-                'logo_file_name' => 'GooglePodcasts.png',
+                'icon_filename' => 'google-podcasts.svg',
             ],
             [
-                'name' => 'Ivoox',
+                'name' => 'ivoox',
+                'label' => 'Ivoox',
                 'home_url' => 'https://www.ivoox.com/',
-                'submit_url' => '',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/apple-store/id542673545',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.ivoox.app',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'ivoox.png',
+                'submit_url' => null,
+                'icon_filename' => 'ivoox.svg',
             ],
             [
-                'name' => 'ListenNotes',
+                'name' => 'listennotes',
+                'label' => 'ListenNotes',
                 'home_url' => 'https://www.listennotes.com/',
                 'submit_url' => 'https://www.listennotes.com/submit/',
-                'iosapp_url' => '',
-                'androidapp_url' => '',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'ListenNotes.png',
+                'icon_filename' => 'listennotes.svg',
             ],
-            //array('name' => 'Majelan', 'home_url' => 'https://www.majelan.com/', 'submit_url' => 'https://support.majelan.com/article/64-how-to-add-my-podcast-on-majelan', 'iosapp_url' => 'https://apps.apple.com/app/majelan-best-audio-stories/id1443711081', 'androidapp_url' => 'https://play.google.com/store/apps/details?id=com.majelanapp', 'comment' => 'Uses public podcasts indexes. Send a DM if you are not listed.', 'display_by_default' => 0, 'ios_deeplink' => 0, 'android_deeplink' => 2, 'logo_file_name' => 'Majelan.png'), // https://aide.majelan.com/article/130-pourquoi-nouvelle-application-7-juillet
             [
-                'name' => 'Mytuner',
-                'home_url' => 'https://mytuner-radio.com/',
-                'submit_url' => 'https://mytuner-radio.com/broadcasters/',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/apple-store/id520502858',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.appgeneration.itunerfree',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 1,
-                'logo_file_name' => 'myTuner.png',
-            ],
-            [
-                'name' => 'Overcast',
+                'name' => 'overcast',
+                'label' => 'Overcast',
                 'home_url' => 'https://overcast.fm/',
                 'submit_url' => 'https://overcast.fm/podcasterinfo',
-                'iosapp_url' =>
-                    'https://apps.apple.com/us/app/overcast-podcast-player/id888422857',
-                'androidapp_url' => '',
-                'comment' =>
-                    'Overcast uses Apple Podcasts index, no podcast submission needed.',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 1,
-                'logo_file_name' => 'Overcast.png',
+                'icon_filename' => 'overcast.svg',
             ],
             [
-                'name' => 'Player.Fm',
+                'name' => 'playerfm',
+                'label' => 'Player.Fm',
                 'home_url' => 'https://player.fm/',
                 'submit_url' => 'https://player.fm/importer/feed',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/podcast-app-by-player-fm/id940568467',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=fm.player',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 1,
-                'logo_file_name' => 'PlayerFM.png',
+                'icon_filename' => 'playerfm.svg',
             ],
             [
-                'name' => 'Pocketcasts',
+                'name' => 'pocketcasts',
+                'label' => 'Pocketcasts',
                 'home_url' => 'https://www.pocketcasts.com/',
                 'submit_url' => 'https://www.pocketcasts.com/submit/',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/pocket-casts/id414834813',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=au.com.shiftyjelly.pocketcasts',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'PocketCasts.png',
+                'icon_filename' => 'pocketcasts.svg',
             ],
             [
-                'name' => 'Podbean',
+                'name' => 'podbean',
+                'label' => 'Podbean',
                 'home_url' => 'https://www.podbean.com/',
                 'submit_url' => 'https://www.podbean.com/site/submitPodcast',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/apple-store/id973361050',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.podbean.app.podcast',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 2,
-                'logo_file_name' => 'Podbean.png',
+                'icon_filename' => 'podbean.svg',
             ],
             [
-                'name' => 'Podcast Addict',
+                'name' => 'podcast-addict',
+                'label' => 'Podcast Addict',
                 'home_url' => 'https://podcastaddict.com/',
                 'submit_url' => 'https://podcastaddict.com/submit',
-                'iosapp_url' => '',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.bambuna.podcastaddict',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 2,
-                'logo_file_name' => 'podcastaddict.svg',
-            ],
-            [
-                'name' => 'Podcastland',
-                'home_url' => 'https://podcastland.com/',
-                'submit_url' => '',
-                'iosapp_url' => '',
-                'androidapp_url' => '',
-                'comment' => 'Uses Apple Podcasts index.',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'PodcastLand.png',
-            ],
-            [
-                'name' => 'Podcastrepublic',
-                'home_url' => 'https://www.podcastrepublic.net/',
-                'submit_url' =>
-                    'https://www.podcastrepublic.net/for-podcast-publisher',
-                'iosapp_url' => '',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.itunestoppodcastplayer.app',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 1,
-                'logo_file_name' => 'PodcastRepublic.png',
+                'icon_filename' => 'podcast-addict.svg',
             ],
             [
-                'name' => 'Podchaser',
+                'name' => 'podchaser',
+                'label' => 'Podchaser',
                 'home_url' => 'https://www.podchaser.com/',
                 'submit_url' => 'https://www.podchaser.com/creators/edit',
-                'iosapp_url' => '',
-                'androidapp_url' => '',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'Podchaser.png',
+                'icon_filename' => 'podchaser.svg',
             ],
             [
-                'name' => 'Podtail',
+                'name' => 'podtail',
+                'label' => 'Podtail',
                 'home_url' => 'https://podtail.com/',
                 'submit_url' => 'https://podtail.com/about/faq/',
-                'iosapp_url' => '',
-                'androidapp_url' => '',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 0,
-                'logo_file_name' => 'Podtail.png',
+                'icon_filename' => 'podtail.svg',
             ],
             [
-                'name' => 'Radiopublic',
+                'name' => 'radiopublic',
+                'label' => 'Radiopublic',
                 'home_url' => 'https://radiopublic.com/',
                 'submit_url' => 'https://podcasters.radiopublic.com/signup',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/radiopublic-free-podcasts/id1113752736',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.radiopublic.android',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 1,
-                'logo_file_name' => 'RadioPublic.png',
+                'icon_filename' => 'radiopublic.svg',
             ],
             [
-                'name' => 'Spotify',
+                'name' => 'spotify',
+                'label' => 'Spotify',
                 'home_url' => 'https://www.spotify.com/',
                 'submit_url' => 'https://podcasters.spotify.com/submit',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/spotify-music/id324684580',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.spotify.music',
-                'comment' => '',
-                'display_by_default' => 1,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 2,
-                'logo_file_name' => 'Spotify.png',
+                'icon_filename' => 'spotify.svg',
             ],
             [
-                'name' => 'Spreaker',
+                'name' => 'spreaker',
+                'label' => 'Spreaker',
                 'home_url' => 'https://www.spreaker.com/',
                 'submit_url' => 'https://www.spreaker.com/cms/shows/rss-import',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/spreaker-podcast-radio/id388449677',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.spreaker.android',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 1,
-                'logo_file_name' => 'Spreaker.png',
+                'icon_filename' => 'spreaker.svg',
             ],
             [
-                'name' => 'Stitcher',
+                'name' => 'stitcher',
+                'label' => 'Stitcher',
                 'home_url' => 'https://www.stitcher.com/',
                 'submit_url' => 'https://www.stitcher.com/content-providers',
-                'iosapp_url' => 'https://apps.apple.com/app/id288087905',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=com.stitcher.app',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 1,
-                'logo_file_name' => 'Stitcher.png',
+                'icon_filename' => 'stitcher.svg',
             ],
             [
-                'name' => 'TuneIn',
+                'name' => 'tunein',
+                'label' => 'TuneIn',
                 'home_url' => 'https://tunein.com/',
                 'submit_url' =>
                     'https://help.tunein.com/contact/add-podcast-S19TR3Sdf',
-                'iosapp_url' =>
-                    'https://apps.apple.com/app/tunein-radio/id418987775',
-                'androidapp_url' =>
-                    'https://play.google.com/store/apps/details?id=tunein.player',
-                'comment' => '',
-                'display_by_default' => 0,
-                'ios_deeplink' => 0,
-                'android_deeplink' => 2,
-                'logo_file_name' => 'TuneIn.png',
+                'icon_filename' => 'tunein.svg',
             ],
         ];
         $this->db->table('platforms')->insertBatch($data);
diff --git a/app/Entities/Platform.php b/app/Entities/Platform.php
new file mode 100644
index 0000000000..cd51209e23
--- /dev/null
+++ b/app/Entities/Platform.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+namespace App\Entities;
+
+use CodeIgniter\Entity;
+
+class Platform extends Entity
+{
+    protected $casts = [
+        'id' => 'integer',
+        'name' => 'string',
+        'label' => 'string',
+        'home_url' => 'string',
+        'submit_url' => '?string',
+        'link_url' => '?string',
+        'visible' => '?boolean',
+    ];
+}
diff --git a/app/Helpers/svg_helper.php b/app/Helpers/svg_helper.php
index 87d9f787b4..a3b353d7ab 100644
--- a/app/Helpers/svg_helper.php
+++ b/app/Helpers/svg_helper.php
@@ -45,3 +45,30 @@ function svg($name, $class = null)
     }
     return $svg_contents;
 }
+
+/**
+ * Returns the inline svg platform icon. Returns the default icon if not found.
+ *
+ * @param  string $name name of the image file without the .svg extension
+ * @param  string $class to be added to the svg string
+ * @return string svg contents
+ */
+function platform_icon($name, $class = null)
+{
+    try {
+        $svg_contents = file_get_contents('assets/images/platforms/' . $name);
+    } catch (\Exception $e) {
+        $svg_contents = file_get_contents(
+            'assets/images/platforms/_default.svg'
+        );
+    }
+
+    if ($class) {
+        $svg_contents = str_replace(
+            '<svg',
+            '<svg class="' . $class . '"',
+            $svg_contents
+        );
+    }
+    return $svg_contents;
+}
diff --git a/app/Language/en/AdminNavigation.php b/app/Language/en/AdminNavigation.php
index 4f03423a36..9b398f577d 100644
--- a/app/Language/en/AdminNavigation.php
+++ b/app/Language/en/AdminNavigation.php
@@ -14,6 +14,7 @@ return [
     'admin' => 'Home',
     'podcast-list' => 'All podcasts',
     'podcast-create' => 'New podcast',
+    'podcast-import' => 'Import a podcast',
     'user-list' => 'All users',
     'user-create' => 'New user',
     'page-list' => 'All pages',
diff --git a/app/Language/en/Breadcrumb.php b/app/Language/en/Breadcrumb.php
index d434287bcc..70e5beb9ce 100644
--- a/app/Language/en/Breadcrumb.php
+++ b/app/Language/en/Breadcrumb.php
@@ -20,4 +20,6 @@ return [
     'my-account' => 'my account',
     'change-password' => 'change password',
     'import' => 'feed import',
+    'settings' => 'settings',
+    'platforms' => 'platforms',
 ];
diff --git a/app/Language/en/Platforms.php b/app/Language/en/Platforms.php
new file mode 100644
index 0000000000..d03675249a
--- /dev/null
+++ b/app/Language/en/Platforms.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @copyright  2020 Podlibre
+ * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
+ * @link       https://castopod.org/
+ */
+
+return [
+    'title' => 'Platforms',
+    'home_url' => 'Go to {platformName} website',
+    'submit_url' => 'Submit your podcast on {platformName}',
+    'visible' => 'Display in podcast homepage?',
+    'remove' => 'Remove {platformName}',
+    'submit' => 'Save',
+    'messages' => [
+        'updateSuccess' => 'Platform links have been successfully updated!',
+        'removeLinkSuccess' => 'The platform link has been removed.',
+        'removeLinkError' =>
+            'The platform link could not be removed. Try again.',
+    ],
+];
diff --git a/app/Models/PlatformLinkModel.php b/app/Models/PlatformLinkModel.php
deleted file mode 100644
index fbddcc465d..0000000000
--- a/app/Models/PlatformLinkModel.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/**
- * Class PlatformLinkModel
- * Model for platform links table in database
- *
- * @copyright  2020 Podlibre
- * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
- * @link       https://castopod.org/
- */
-
-namespace App\Models;
-
-use CodeIgniter\Model;
-
-class PlatformLinkModel extends Model
-{
-    protected $table = 'platform_links';
-    protected $primaryKey = 'id';
-
-    protected $allowedFields = [
-        'podcast_id',
-        'platform_id',
-        'link_url',
-        'comment',
-        'visible',
-    ];
-
-    protected $returnType = App\Entities\PlatformLink::class;
-    protected $useSoftDeletes = false;
-
-    protected $useTimestamps = true;
-}
diff --git a/app/Models/PlatformModel.php b/app/Models/PlatformModel.php
index 977e7fed14..d09e5ce175 100644
--- a/app/Models/PlatformModel.php
+++ b/app/Models/PlatformModel.php
@@ -35,4 +35,55 @@ class PlatformModel extends Model
     protected $useSoftDeletes = false;
 
     protected $useTimestamps = true;
+
+    public function getPlatformsWithLinks()
+    {
+        return $this->select(
+            'platforms.*, platform_links.link_url, platform_links.visible'
+        )
+            ->join(
+                'platform_links',
+                'platform_links.platform_id = platforms.id',
+                'left'
+            )
+            ->findAll();
+    }
+
+    public function savePlatformLinks($podcastId, $platformLinksData)
+    {
+        // Remove already previously set platforms to overwrite them
+        $this->db
+            ->table('platform_links')
+            ->delete(['podcast_id' => $podcastId]);
+
+        // Set platformLinks
+        return $this->db
+            ->table('platform_links')
+            ->insertBatch($platformLinksData);
+    }
+
+    public function getPlatformId($platform)
+    {
+        if (is_numeric($platform)) {
+            return (int) $platform;
+        }
+
+        $p = $this->where('name', $platform)->first();
+
+        if (!$p) {
+            $this->error = lang('Platform.platformNotFound', [$platform]);
+
+            return false;
+        }
+
+        return (int) $p->id;
+    }
+
+    public function removePlatformLink($podcastId, $platformId)
+    {
+        return $this->db->table('platform_links')->delete([
+            'podcast_id' => $podcastId,
+            'platform_id' => $platformId,
+        ]);
+    }
 }
diff --git a/app/Views/_assets/icons/delete-bin.svg b/app/Views/_assets/icons/delete-bin.svg
new file mode 100644
index 0000000000..91a963ddcf
--- /dev/null
+++ b/app/Views/_assets/icons/delete-bin.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <g>
+        <path fill="none" d="M0 0h24v24H0z"/>
+        <path d="M17 6h5v2h-2v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8H2V6h5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v3zm1 2H6v12h12V8zm-9 3h2v6H9v-6zm4 0h2v6h-2v-6zM9 4v2h6V4H9z"/>
+    </g>
+</svg>
diff --git a/app/Views/_assets/icons/upload.svg b/app/Views/_assets/icons/upload.svg
new file mode 100644
index 0000000000..ad478afce1
--- /dev/null
+++ b/app/Views/_assets/icons/upload.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <g>
+        <path fill="none" d="M0 0h24v24H0z"/>
+        <path d="M4 19h16v-7h2v8a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-8h2v7zm9-10v7h-2V9H6l6-6 6 6h-5z"/>
+    </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/_default.svg b/app/Views/_assets/images/platforms/_default.svg
new file mode 100644
index 0000000000..b7c7127fa0
--- /dev/null
+++ b/app/Views/_assets/images/platforms/_default.svg
@@ -0,0 +1,7 @@
+<svg id="default" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <rect width="300" height="300" rx="67" fill="#ebfaeb"/>
+  <path id="phylactery" d="M195,112.36H105a40,40,0,0,0,0,80h36.65a6.67,6.67,0,0,0,0-13.33H105a26.67,26.67,0,0,1,0-53.34h90A26.67,26.67,0,0,1,195,179h-6.37A34,34,0,0,0,155.31,213a6.67,6.67,0,1,0,13.33,0,20.64,20.64,0,0,1,20.61-20.61H195a40,40,0,0,0,0-80" fill="#37c837"/>
+  <circle id="leftdot" cx="123.62" cy="152.5" r="6.81" fill="#37c837"/>
+  <circle id="middledot" cx="150.01" cy="152.5" r="6.81" fill="#37c837"/>
+  <circle id="rightdot" cx="176.39" cy="152.5" r="6.81" fill="#37c837"/>
+</svg>
diff --git a/app/Views/_assets/images/platforms/apple-podcasts.svg b/app/Views/_assets/images/platforms/apple-podcasts.svg
new file mode 100644
index 0000000000..db1d3e8660
--- /dev/null
+++ b/app/Views/_assets/images/platforms/apple-podcasts.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 300 300">
+  <defs>
+    <linearGradient id="apple-podcasts" x1="150" y1="300" x2="150" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#822cbe"/>
+      <stop offset="1" stop-color="#d772fb"/>
+    </linearGradient>
+  </defs>
+  <g id="apple-podcasts-2" data-name="apple-podcasts">
+    <rect width="300" height="300" rx="67" fill="url(#apple-podcasts)"/>
+    <path d="M144.55,233.2c-6.53-2.34-7.94-5.51-10.61-24.11-3.12-21.62-3.8-35-2-39.23,2.39-5.62,8.88-8.81,18-8.85s15.56,3.18,18,8.85c1.81,4.23,1.14,17.61-2,39.23-2.12,15.1-3.28,18.92-6.2,21.49a15,15,0,0,1-15.1,2.63ZM116.65,211c-22.54-11.09-37-29.86-42.24-54.82-1.31-6.44-1.53-21.82-.29-27.73,3.28-15.86,9.56-28.27,20-39.25C109.14,73.38,128.47,65,150,65a74.93,74.93,0,0,1,55.22,23.59c11.17,11.61,17.44,23.88,20.65,40.06,1.1,5.38,1.1,20.06.08,26.12a77.61,77.61,0,0,1-29.55,48.3c-5.55,4.18-19.12,11.49-21.31,11.49-.8,0-.87-.83-.51-4.19.66-5.4,1.32-6.52,4.38-7.81,4.89-2,13.21-8,18.31-13.09a69.77,69.77,0,0,0,18.32-32.24c1.89-7.52,1.67-24.23-.44-32-6.64-24.59-26.71-43.7-51.29-48.81-7.15-1.46-20.14-1.46-27.36,0-24.88,5.11-45.45,25.17-51.73,50.42-1.68,6.85-1.68,23.56,0,30.42,4.16,16.71,15,32,29.11,41.15a62.4,62.4,0,0,0,7.52,4.3c3.06,1.32,3.72,2.41,4.3,7.81.37,3.28.29,4.23-.51,4.23a59.41,59.41,0,0,1-8.17-3.5Zm.3-29.68c-7.59-6.06-14.3-16.8-17.08-27.33-1.67-6.36-1.67-18.46.08-24.81,4.59-17.13,17.21-30.41,34.72-36.64,6-2.12,19.27-2.59,26.63-1,25.39,5.66,43.41,31.08,39.91,56.31a51.41,51.41,0,0,1-11.09,26.26c-3.06,3.92-10.5,10.49-11.82,10.49-.22,0-.43-2.48-.43-5.5v-5.52l3.79-4.52c14.3-17.13,13.28-41.06-2.34-56.76-6-6.11-13-9.7-22.1-11.36-5.84-1.08-7.08-1.08-13.21-.07a40,40,0,0,0-22.91,11.45c-15.68,15.54-16.7,39.6-2.4,56.74l3.76,4.52v5.55c0,3.06-.24,5.54-.54,5.54a26.16,26.16,0,0,1-4.67-3.28Zm25.31-29.79c-6.49-3-10-8.72-10.07-16.15,0-6.67,3.72-12.49,10.14-15.9,4.09-2.15,11.31-2.15,15.4,0A19.81,19.81,0,0,1,167.21,131c4.24,14.39-11,27-24.8,20.55Z" fill="#fff"/>
+    <circle cx="151.15" cy="136.19" r="18.2" fill="#fff"/>
+    <path d="M152.89,161.43a36.45,36.45,0,0,1,6.09,1.24,21.89,21.89,0,0,1,6.1,3.05,10.74,10.74,0,0,1,3.51,3.85,16.33,16.33,0,0,1,1.31,6.09,102.7,102.7,0,0,1-.51,14.3c-.66,7.42-2,18.3-3,25.27s-1.75,10.07-2.7,12.33a11.08,11.08,0,0,1-8.1,7.08,17.7,17.7,0,0,1-4.23.36,18.24,18.24,0,0,1-4.23-.36,13.42,13.42,0,0,1-5.48-2.34,11.86,11.86,0,0,1-3.64-5.69c-.88-2.48-1.54-5.47-2.41-11.45s-2-14.89-2.77-21.67-1-11.46-1.08-14.59a22.66,22.66,0,0,1,.43-6.21,12.6,12.6,0,0,1,1.75-4,13.39,13.39,0,0,1,2.92-3,12,12,0,0,1,3.4-1.84,25.47,25.47,0,0,1,4.67-1.31c1.9-.37,4.09-.73,5.21-.88a5.61,5.61,0,0,1,2.38,0Z" fill="#fff"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/blubrry.svg b/app/Views/_assets/images/platforms/blubrry.svg
new file mode 100644
index 0000000000..56becf31c8
--- /dev/null
+++ b/app/Views/_assets/images/platforms/blubrry.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="blubrry">
+    <rect width="300" height="300" rx="67" fill="#b347a3"/>
+    <path d="M182.68,88.87s7.79-17.79,7.39-18.13-17.85,10.63-17.85,10.63S150.91,65,150.06,65c-1.14,0-19.27,15-19.27,15S111.46,66,111,66.48,117.26,89,117.26,89c-28.14,11.88-47.74,37.74-47.74,69.11,0,42.45,36,76.9,80.48,76.9,45.92,0,80.48-34.45,80.48-76.9C230.48,126.73,210.82,100.87,182.68,88.87Zm7.85,69a37.88,37.88,0,0,0-21.77-33.42l3-7.45a45.85,45.85,0,0,1,27.12,40.87Zm18.24,0a55,55,0,0,0-33.53-49.67l4.26-10.75c24.39,10.52,41.32,33.6,41.32,60.43Z" fill="#fff"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/castbox.svg b/app/Views/_assets/images/platforms/castbox.svg
new file mode 100644
index 0000000000..48543373b3
--- /dev/null
+++ b/app/Views/_assets/images/platforms/castbox.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 300 300">
+  <defs>
+    <linearGradient id="castbox" x1="19.62" y1="19.62" x2="280.38" y2="280.38" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#ffb768"/>
+      <stop offset="0.19" stop-color="#ff9953"/>
+      <stop offset="0.57" stop-color="#ff652e"/>
+      <stop offset="0.85" stop-color="#ff4518"/>
+      <stop offset="1" stop-color="#ff390f"/>
+    </linearGradient>
+  </defs>
+  <g id="castbox-2" data-name="castbox">
+    <rect width="300" height="300" rx="67" fill="url(#castbox)"/>
+    <path d="M165.11,103c-5.19,0-9.44,3.78-9.44,9v21.72c0,2.83-2.36,4.72-5.67,4.72s-5.67-2.36-5.67-4.72V104.43c0-4.72-4.25-9-9.44-9s-9.45,3.78-9.45,9V128c0,2.84-2.36,4.72-5.66,4.72s-5.67-2.36-5.67-4.72v-8.5c0-4.72-4.25-9-9.44-9s-9.45,3.78-9.45,9v20.31c0,2.83-2.36,4.72-5.66,4.72s-5.67-2.36-5.67-4.72v-2.36a9.45,9.45,0,1,0-18.89,0v28.33a9.45,9.45,0,0,0,18.89,0v-2.36c0-2.83,2.36-4.72,5.67-4.72s5.66,2.36,5.66,4.72v32.11c0,4.72,4.25,9,9.45,9s9.44-3.78,9.44-9V151.65c0-2.83,2.36-4.72,5.67-4.72s5.66,2.36,5.66,4.72v21.72c0,4.73,4.25,9,9.45,9s9.44-3.78,9.44-9V157.32c0-2.83,2.36-4.72,5.67-4.72s5.67,2.36,5.67,4.72v17.47c0,4.72,4.25,9,9.44,9s9.45-3.77,9.45-9V112c0-4.73-4.25-9-9.45-9" fill="#fff" fill-rule="evenodd"/>
+    <path d="M195.33,127.57c-5.19,0-9.44,3.78-9.44,9v35.89c0,4.72,4.25,9,9.44,9s9.45-3.77,9.45-9V136.54c0-5.19-4.25-9-9.45-9" fill="#fff" fill-rule="evenodd"/>
+    <path d="M225.56,145.51c-5.2,0-9.45,3.78-9.45,9v9c0,4.72,4.25,9,9.45,9s9.44-3.78,9.44-9v-9c0-5.2-4.25-9-9.44-9" fill="#fff" fill-rule="evenodd"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/castro.svg b/app/Views/_assets/images/platforms/castro.svg
new file mode 100644
index 0000000000..a1f7c73c1e
--- /dev/null
+++ b/app/Views/_assets/images/platforms/castro.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 300 300">
+  <defs>
+    <linearGradient id="castro" x1="150" x2="150" y2="300" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#00ccbf"/>
+      <stop offset="1" stop-color="#00b265"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient" x1="-678.11" y1="574.88" x2="-678.11" y2="580.63" gradientTransform="translate(20211.11 -16925.38) scale(29.58 29.55)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#151515"/>
+      <stop offset="1" stop-color="#353535"/>
+    </linearGradient>
+  </defs>
+  <g id="castro-2" data-name="castro">
+    <rect width="300" height="300" rx="67" fill="url(#castro)"/>
+    <path id="path-2" d="M150,65a85,85,0,1,1-85,85A84.95,84.95,0,0,1,150,65Z" fill-rule="evenodd" fill="url(#linear-gradient)"/>
+    <path id="path-3" d="M185.42,198.64l-14.17-19.83a36.13,36.13,0,1,0-42,0l-14.16,19.83a60.49,60.49,0,1,1,95.86-49.11A62.82,62.82,0,0,1,185.42,198.64ZM150,134.89a14.64,14.64,0,1,1-14.64,14.64A14.71,14.71,0,0,1,150,134.89Z" fill="#fff" fill-rule="evenodd"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/deezer.svg b/app/Views/_assets/images/platforms/deezer.svg
new file mode 100644
index 0000000000..4dbfbabfaa
--- /dev/null
+++ b/app/Views/_assets/images/platforms/deezer.svg
@@ -0,0 +1,54 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 300 300">
+  <defs>
+    <linearGradient id="linear-gradient" x1="121.67" y1="310.68" x2="178.33" y2="-10.68" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#202122"/>
+      <stop offset="1" stop-color="#26303c"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-2" x1="89.73" y1="16.18" x2="89.54" y2="4.17" gradientTransform="translate(52.42 116.38) scale(1.83)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#358c7b"/>
+      <stop offset="0.53" stop-color="#33a65e"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-3" x1="79.3" y1="31.76" x2="99.98" y2="21.22" gradientTransform="translate(52.42 116.38) scale(1.83)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#222b90"/>
+      <stop offset="1" stop-color="#367b99"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-4" x1="6.87" y1="42.77" x2="26.93" y2="42.77" gradientTransform="translate(52.42 116.38) scale(1.83)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#f90"/>
+      <stop offset="1" stop-color="#ff8000"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-5" x1="31.12" y1="42.77" x2="51.19" y2="42.77" gradientTransform="translate(52.42 116.38) scale(1.83)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#ff8000"/>
+      <stop offset="1" stop-color="#cc1953"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-6" x1="55.37" y1="42.77" x2="75.44" y2="42.77" gradientTransform="translate(52.42 116.38) scale(1.83)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#cc1953"/>
+      <stop offset="1" stop-color="#241284"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-7" x1="79.6" y1="42.77" x2="99.67" y2="42.77" gradientTransform="translate(52.42 116.38) scale(1.83)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#222b90"/>
+      <stop offset="1" stop-color="#3559a6"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-8" x1="54.58" y1="29.66" x2="76.23" y2="23.33" xlink:href="#linear-gradient-6"/>
+    <linearGradient id="linear-gradient-9" x1="30.38" y1="22.58" x2="51.88" y2="30.4" gradientTransform="translate(52.42 116.38) scale(1.83)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#fc0"/>
+      <stop offset="1" stop-color="#ce1938"/>
+    </linearGradient>
+    <linearGradient id="linear-gradient-10" x1="32.79" y1="2.51" x2="49.48" y2="17.91" gradientTransform="translate(52.42 116.38) scale(1.83)" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#ffd100"/>
+      <stop offset="1" stop-color="#fd5a22"/>
+    </linearGradient>
+  </defs>
+  <g id="deezer">
+    <rect width="300" height="300" rx="67" fill="url(#linear-gradient)"/>
+    <rect id="rect8185" x="198.24" y="94.52" width="36.76" height="21.51" fill="#40ab5d"/>
+    <rect id="rect8192" x="198.24" y="124.34" width="36.76" height="21.51" fill="url(#linear-gradient-2)"/>
+    <rect id="rect8199" x="198.24" y="154.16" width="36.76" height="21.51" fill="url(#linear-gradient-3)"/>
+    <rect id="rect8206" x="65" y="183.97" width="36.76" height="21.51" fill="url(#linear-gradient-4)"/>
+    <rect id="rect8213" x="109.39" y="183.97" width="36.76" height="21.51" fill="url(#linear-gradient-5)"/>
+    <rect id="rect8220" x="153.86" y="183.97" width="36.76" height="21.51" fill="url(#linear-gradient-6)"/>
+    <rect id="rect8227" x="198.24" y="183.97" width="36.76" height="21.51" fill="url(#linear-gradient-7)"/>
+    <rect id="rect8234" x="153.86" y="154.16" width="36.76" height="21.51" fill="url(#linear-gradient-8)"/>
+    <rect id="rect8241" x="109.39" y="154.16" width="36.76" height="21.51" fill="url(#linear-gradient-9)"/>
+    <rect id="rect8248" x="109.39" y="124.34" width="36.76" height="21.51" fill="url(#linear-gradient-10)"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/google-podcasts.svg b/app/Views/_assets/images/platforms/google-podcasts.svg
new file mode 100644
index 0000000000..5903413a7b
--- /dev/null
+++ b/app/Views/_assets/images/platforms/google-podcasts.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="google-podcasts">
+    <rect width="300" height="300" rx="67" fill="#fff"/>
+    <path id="Shape" d="M75.39,133.66A10.5,10.5,0,0,0,65,144.08v11.84a10.39,10.39,0,1,0,20.78,0V144.08C86.25,138.4,81.53,133.66,75.39,133.66Z" fill="#0066d9"/>
+    <path id="Shape-2" data-name="Shape" d="M224.61,133.66a10.5,10.5,0,0,0-10.39,10.42v11.84a10.39,10.39,0,1,0,20.78,0V144.08A10.5,10.5,0,0,0,224.61,133.66Z" fill="#4285f4"/>
+    <path id="Shape-3" data-name="Shape" d="M112.22,168.7a10.5,10.5,0,0,0-10.39,10.42V191a10.39,10.39,0,1,0,20.78,0V179.12C123.08,173.44,118.36,168.7,112.22,168.7Z" fill="#ea4335"/>
+    <path id="Shape-4" data-name="Shape" d="M112.22,99.09a10.5,10.5,0,0,0-10.39,10.42V147.4h0a10.39,10.39,0,1,0,20.78,0h0V109.51C123.08,103.83,118.36,99.09,112.22,99.09Z" fill="#ea4335"/>
+    <path id="Shape-5" data-name="Shape" d="M187.78,99.09a10.5,10.5,0,0,0-10.39,10.42v11.84a10.39,10.39,0,1,0,20.78,0V109.51A10.5,10.5,0,0,0,187.78,99.09Z" fill="#34a853"/>
+    <path id="Shape-6" data-name="Shape" d="M150,65a10.5,10.5,0,0,0-10.39,10.42V87.26a10.39,10.39,0,1,0,20.78,0V75.42A10.5,10.5,0,0,0,150,65Z" fill="#fab908"/>
+    <path id="Shape-7" data-name="Shape" d="M150,202.33a10.49,10.49,0,0,0-10.39,10.41v11.84a10.39,10.39,0,1,0,20.78,0V212.74A10.81,10.81,0,0,0,150,202.33Z" fill="#fab908"/>
+    <path id="Shape-8" data-name="Shape" d="M187.78,142.66a10.5,10.5,0,0,0-10.39,10.42V191a10.39,10.39,0,1,0,20.78,0V153.08A10.5,10.5,0,0,0,187.78,142.66Z" fill="#34a853"/>
+    <path id="Shape-9" data-name="Shape" d="M160.39,119.46a10.39,10.39,0,1,0-20.78,0h0V182h0a10.39,10.39,0,1,0,20.78,0h0v-62.5Z" fill="#fab908"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/ivoox.svg b/app/Views/_assets/images/platforms/ivoox.svg
new file mode 100644
index 0000000000..36299c8780
--- /dev/null
+++ b/app/Views/_assets/images/platforms/ivoox.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="ivoox">
+    <rect width="300" height="300" rx="67" fill="#ff8600"/>
+    <path d="M136.58,126.88a23.51,23.51,0,0,0,.31-47c-13.32,0-23.66,10.19-23.66,23.35A23,23,0,0,0,136.58,126.88Z" fill="#fff"/>
+    <path d="M116.52,181.56c0,36.82.16,81.65.16,118.31H159.3V201.62c0-16.45.15-32.91.15-49.52,0-5.32-1.41-10.18-6.11-12.84a20.43,20.43,0,0,0-9.09-2.82c-20.84-.32-41.68-.16-62.36-.16-2.19,0-2.82.78-2.82,3q.24,17.85,0,35.72c0,2.2.63,2.82,2.82,2.82,10.34-.15,20.68,0,31,0C116.52,177.8,116.52,177.8,116.52,181.56Z" fill="#fff"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/listennotes.svg b/app/Views/_assets/images/platforms/listennotes.svg
new file mode 100644
index 0000000000..9d01f6bbaa
--- /dev/null
+++ b/app/Views/_assets/images/platforms/listennotes.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="listennotes">
+    <rect width="300" height="300" rx="67" fill="#fff"/>
+    <path d="M178.46,193.16c-15.55,11.36-32.83,16.1-51.88,13.64C108.33,204.44,93.22,196,81.41,182A70.71,70.71,0,1,1,196.05,99.76a69.54,69.54,0,0,1,9.6,26.08,70.58,70.58,0,0,1-13,52.94c.13.08.22.15.32.2a79.82,79.82,0,0,1,12.38,8.13,113.6,113.6,0,0,1,19.26,19.2,69.62,69.62,0,0,1,7.15,10.9,31.83,31.83,0,0,1,2.83,7.19,13.09,13.09,0,0,1,.39,4.18,5.47,5.47,0,0,1-2.22,4.25,6.07,6.07,0,0,1-3.14,1.08,15.69,15.69,0,0,1-5.85-.74,42,42,0,0,1-8.45-3.67,83.54,83.54,0,0,1-12-8.18,115.42,115.42,0,0,1-16.93-16.73,77.74,77.74,0,0,1-7.69-11.09C178.62,193.39,178.54,193.28,178.46,193.16ZM135.77,80.84a55.91,55.91,0,1,0,55.93,55.95A55.9,55.9,0,0,0,135.77,80.84Z"/>
+    <path d="M108.43,97.91a63.9,63.9,0,0,1,25.67,5.27A66.07,66.07,0,0,1,174,164.05H163.68c0-.21,0-.43,0-.65a55.86,55.86,0,0,0-24.29-45.52,54.44,54.44,0,0,0-17.67-8,52,52,0,0,0-7.62-1.34c-1.59-.16-3.19-.19-4.78-.27l-.86,0Z" fill="#b82f00"/>
+    <path d="M155.73,165.78h-10.3a37.4,37.4,0,0,0-11.48-27c-7.53-7.37-16.69-10.94-27.23-11.13V117.3a49.2,49.2,0,0,1,42.52,24.31A46.75,46.75,0,0,1,155.73,165.78Z" fill="#b82f00"/>
+    <path d="M106.71,143.76v-9.5a31.07,31.07,0,0,1,27.45,16.52,28.64,28.64,0,0,1,3.42,14.06c-.45.12-8.82.13-9.41,0a20.92,20.92,0,0,0-6.4-14.93A21.45,21.45,0,0,0,106.71,143.76Z" fill="#b82f00"/>
+    <path d="M121.11,161a9,9,0,1,1-9.08-9A9,9,0,0,1,121.11,161Z" fill="#b82f00"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/overcast.svg b/app/Views/_assets/images/platforms/overcast.svg
new file mode 100644
index 0000000000..db0bdb49d7
--- /dev/null
+++ b/app/Views/_assets/images/platforms/overcast.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="overcast">
+    <rect width="300" height="300" rx="67" fill="#fc7e0f"/>
+    <path id="overcastCanvas-bezier" d="M150,235a85,85,0,1,1,85-85A84.65,84.65,0,0,1,150,235Zm0-33.53,6.14-6.14L150,170.78l-6.14,24.55Zm-8-.47-3.3,12.28,7.55-7.56ZM158,201l-4.25,4.25,7.55,7.56Zm6.14,21.72L150,208.56l-14.17,14.16A71.77,71.77,0,0,0,150,224.14C154.72,224.14,159.44,224.14,164.17,222.72ZM150,75.39A74.77,74.77,0,0,0,75.39,150a73.52,73.52,0,0,0,50.05,69.89l16.06-59c-3.78-2.36-5.67-6.61-5.67-11.33A14.21,14.21,0,0,1,150,135.36a13.92,13.92,0,0,1,14.17,14.17c0,4.72-2.36,8.5-5.67,11.33l16.06,59A74,74,0,0,0,224.61,150,74.77,74.77,0,0,0,150,75.39Zm48.64,111.44c-1.89,2.36-4.72,2.84-7.56.95-2.36-1.89-2.83-4.72-.94-7.09a54.61,54.61,0,0,0,0-61.38c-1.89-2.37-1.42-5.2.94-7.09s5.67-.94,7.56,1c.94.47,11.8,15.11,11.8,36.83S199.11,186.36,198.64,186.83Zm-26-13.69c-1.89-2.36-1.89-6.14.47-8A23.68,23.68,0,0,0,178.33,150c0-9-5.19-14.64-5.19-15.11-2.36-1.89-2.36-5.67-.47-8a5.29,5.29,0,0,1,7.55-.47,35.56,35.56,0,0,1,0,47.22C177.86,175.5,175,175,172.67,173.14Zm-45.34,0a5.29,5.29,0,0,1-7.55.47,34.37,34.37,0,0,1-9-23.61c0-14.64,8.5-22.67,9-23.61,2.36-1.89,5.66-1.89,7.55.47s1.89,6.14-.47,8c0,.47-5.19,5.67-5.19,15.11a25.31,25.31,0,0,0,5.19,15.11C129.22,167,129.22,170.78,127.33,173.14Zm-18.41,14.64c-2.36,1.89-5.67.94-7.56-.95-.94-.47-11.8-15.11-11.8-36.83s10.86-35.89,11.8-36.83c1.89-2.36,4.72-2.84,7.56-1,2.36,1.89,2.83,4.72.94,7.09a54.61,54.61,0,0,0,0,61.38A4.74,4.74,0,0,1,108.92,187.78Z" fill="#fff" fill-rule="evenodd"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/playerfm.svg b/app/Views/_assets/images/platforms/playerfm.svg
new file mode 100644
index 0000000000..502b7343b4
--- /dev/null
+++ b/app/Views/_assets/images/platforms/playerfm.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="playerfm">
+    <rect width="300" height="300" rx="67" fill="#c81329"/>
+    <path d="M144.92,235A79.78,79.78,0,0,1,126,77.7c9-2.36,18.43,3.3,20.79,12.75,2.36,9-3.31,18.42-12.76,20.79-20.31,4.72-34.48,23.14-34.48,44.4A45.36,45.36,0,0,0,144.92,201c21.26,0,39.21-14.17,44.4-35,2.37-9,11.34-15.11,20.79-12.75,9,2.36,15.11,11.34,12.75,20.78A80.3,80.3,0,0,1,144.92,235Z" fill="#fff"/>
+    <path d="M227.59,139.58a7.1,7.1,0,0,1-7.09-5.2c-12.28-42.51-53.85-55.27-54.32-55.27a7.17,7.17,0,1,1,4.25-13.69c1.89.47,49.6,15.11,64.24,64.71,1,3.78-.94,8-4.72,9A5.17,5.17,0,0,1,227.59,139.58Z" fill="#fff"/>
+    <path d="M204,144.3a7.1,7.1,0,0,1-7.09-5.19c-8.5-27.4-35.43-36.38-35.43-36.85-3.78-1.42-6.14-5.19-4.72-9s5.2-6.14,9-4.73c1.42.47,34.49,11.34,44.88,46.3.95,3.77-.94,8-4.72,9C205.38,143.83,204.44,144.3,204,144.3Z" fill="#fff"/>
+    <path d="M179.88,147.14a7.14,7.14,0,0,1-6.62-4.25c-5.19-11.81-16.53-17.48-16.53-17.48a7.39,7.39,0,1,1,6.61-13.23c.47.47,16.06,8,23.62,24.57a6.93,6.93,0,0,1-3.31,9.44A6.29,6.29,0,0,1,179.88,147.14Z" fill="#fff"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/pocketcasts.svg b/app/Views/_assets/images/platforms/pocketcasts.svg
new file mode 100644
index 0000000000..7cefa63c85
--- /dev/null
+++ b/app/Views/_assets/images/platforms/pocketcasts.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="pocketcasts">
+    <rect width="300" height="300" rx="67" fill="#f43e37"/>
+    <path d="M65,150a85,85,0,0,1,170,0H213.75A63.75,63.75,0,1,0,150,213.75V235A85.25,85.25,0,0,1,65,150Zm85,51a51,51,0,1,1,51-51H182.58A32.58,32.58,0,1,0,150,182.58Z" fill="#fff" fill-rule="evenodd"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/podbean.svg b/app/Views/_assets/images/platforms/podbean.svg
new file mode 100644
index 0000000000..ab3710939b
--- /dev/null
+++ b/app/Views/_assets/images/platforms/podbean.svg
@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="podbean">
+    <rect width="300" height="300" rx="67" fill="#252027"/>
+    <path d="M148.57,167.13c6.39-.62,12.72,2.28,17.07,6.87a30.92,30.92,0,0,1,3.62,5.75c1.55,3.56,1.63,7.54,1.39,11.35A20.88,20.88,0,0,1,162,204.85a18.47,18.47,0,0,1-9.54,3.53,28,28,0,0,1-7.26-.28,25.22,25.22,0,0,1-8.1-3.91,22.63,22.63,0,0,1-6.51-9,20.48,20.48,0,0,1,1.5-17.62,20.12,20.12,0,0,1,3.52-4.57,26,26,0,0,1,6.39-4.31A30.54,30.54,0,0,1,148.57,167.13Z" fill="#fff"/>
+    <path d="M148.25,80.27a82.52,82.52,0,0,1,14.31.74,89.08,89.08,0,0,1,11.79,2.23,97.77,97.77,0,0,1,16.75,5.84,93.84,93.84,0,0,1,18.05,10.77,76.07,76.07,0,0,1,9.17,7.73c1.71,1.74,3.48,3.44,5.17,5.22A91,91,0,0,1,234,126.69a12.09,12.09,0,0,1,1,4.11,10.52,10.52,0,0,1-3.68,8.11,9.54,9.54,0,0,1-5.66,2.22,9.67,9.67,0,0,1-7.44-2.31c-1.95-1.76-3.12-4.14-4.69-6.2a78.41,78.41,0,0,0-20-19,82.9,82.9,0,0,0-11.91-6.53c-1.53-.66-3.11-1.21-4.66-1.83a64.2,64.2,0,0,0-11.13-3.16,82,82,0,0,0-15.15-1.5,71.44,71.44,0,0,0-15.17,1.21,74.6,74.6,0,0,0-11.84,3.24,79,79,0,0,0-35.06,24.66c-1.58,2.07-3.16,4.14-4.59,6.32A13.36,13.36,0,0,1,81,139.45,10.15,10.15,0,0,1,77.26,141a14,14,0,0,1-4.74-.07,9.72,9.72,0,0,1-5.93-4.49,10.1,10.1,0,0,1-1-8.75,16.89,16.89,0,0,1,1.48-2.7,99.33,99.33,0,0,1,35-32.49,109.06,109.06,0,0,1,18-7.7A102.4,102.4,0,0,1,148.25,80.27Z" fill="#8dc742"/>
+    <path d="M149.23,116.85a61.61,61.61,0,0,1,44.29,17.86,54,54,0,0,1,8.92,11.06,10.64,10.64,0,0,1,.74,8.52,10.28,10.28,0,0,1-5.48,5.79,10.11,10.11,0,0,1-10.29-1.36,13.3,13.3,0,0,1-2.68-3,47.3,47.3,0,0,0-6-7.2A42,42,0,0,0,158.5,138a46.12,46.12,0,0,0-9.6-.81c-8.2,0-16.2,2.92-22.93,7.48a46,46,0,0,0-8,7.36c-1.65,1.92-2.92,4.13-4.6,6a10.14,10.14,0,0,1-16.8-4.67,9.48,9.48,0,0,1-.06-5.12,13,13,0,0,1,2-4,70.45,70.45,0,0,1,7.33-9,66.92,66.92,0,0,1,10.51-8.46,60.35,60.35,0,0,1,11.73-5.93A62.53,62.53,0,0,1,149.23,116.85Z" fill="#8dc742"/>
+    <path d="M148.52,156.34a31.67,31.67,0,0,1,27.41,13.37,31.25,31.25,0,0,1,5.64,15.71,42.81,42.81,0,0,1-.23,8.16,33,33,0,0,1-10.05,18,31.78,31.78,0,0,1-15,7.55,33.35,33.35,0,0,1-10.64.37,32.19,32.19,0,0,1-11.24-3.84,33.53,33.53,0,0,1-9.36-8,36.17,36.17,0,0,1-4.84-8.67,31.75,31.75,0,0,1-.57-20.13A32,32,0,0,1,132,161.93a31,31,0,0,1,16.5-5.59m.28,15.08a23.89,23.89,0,0,0-5.29,1.21,20.94,20.94,0,0,0-5.13,3.46,16.85,16.85,0,0,0-4,17.79,18.07,18.07,0,0,0,5.21,7.24,20.08,20.08,0,0,0,6.5,3.14,22.67,22.67,0,0,0,5.82.23,14.86,14.86,0,0,0,7.65-2.83,16.76,16.76,0,0,0,7-11,19.55,19.55,0,0,0-1.12-9.1,24.82,24.82,0,0,0-2.9-4.61A17,17,0,0,0,148.8,171.42Z" fill="#8dc742"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/podcast-addict.svg b/app/Views/_assets/images/platforms/podcast-addict.svg
new file mode 100644
index 0000000000..9544f550d6
--- /dev/null
+++ b/app/Views/_assets/images/platforms/podcast-addict.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 300 300">
+  <defs>
+    <linearGradient id="linear-gradient" x1="142.29" y1="253.24" x2="273.52" y2="121.61" gradientUnits="userSpaceOnUse">
+      <stop offset="0.13" stop-color="#e85d27"/>
+      <stop offset="0.69" stop-color="#f0772b"/>
+    </linearGradient>
+  </defs>
+  <g id="podcast-addict">
+    <rect width="300" height="300" rx="67" fill="#f4842d"/>
+    <path d="M300,233V183.06L217.93,101,147.86,82,85.5,158.53l14,60.86L180.14,300H233A67,67,0,0,0,300,233Z" fill="url(#linear-gradient)"/>
+    <path d="M234.94,148.07a85,85,0,1,0-124.89,77V149.57a40.42,40.42,0,0,1,32.77-39.63v14.13a26.56,26.56,0,0,0-19,25.5v.23h0v.11a26.6,26.6,0,0,0,52.09,7.27H190a40.33,40.33,0,0,1-66.23,22.67v51A85.77,85.77,0,0,0,150,235a84.59,84.59,0,0,0,47.65-14.59V235H235l-.06-86.93Zm-84.62,17a15.52,15.52,0,1,1,15.51-15.51A15.48,15.48,0,0,1,150.32,165.08Zm40.38-15.4H177v-.11a26.58,26.58,0,0,0-26.6-26.6h-.05V109.24h.05a40.33,40.33,0,0,1,40.33,40.33Zm23.65,0H200.73v-.11a50.36,50.36,0,0,0-50.36-50.36h-.05V85.59h.05a64,64,0,0,1,64,64Z" fill="#fff"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/podchaser.svg b/app/Views/_assets/images/platforms/podchaser.svg
new file mode 100644
index 0000000000..b875da9dfe
--- /dev/null
+++ b/app/Views/_assets/images/platforms/podchaser.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="podchaser">
+    <rect width="300" height="300" rx="67" fill="#293b65"/>
+    <circle id="Outer-Pink" cx="150" cy="150" r="85" fill="#f70664"/>
+    <circle id="Outer-White" cx="150" cy="150.14" r="73.19" fill="#fff"/>
+    <path d="M150,97.25a54.32,54.32,0,0,0-54.31,54.31v47.69a79.45,79.45,0,0,0,30.7,20.31V200.47A54.31,54.31,0,1,0,150,97.25Z" fill="#293b65"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/podtail.svg b/app/Views/_assets/images/platforms/podtail.svg
new file mode 100644
index 0000000000..acbeb47fc9
--- /dev/null
+++ b/app/Views/_assets/images/platforms/podtail.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 300 300">
+  <defs>
+    <linearGradient id="linear-gradient" x1="98.65" y1="77.34" x2="182.61" y2="222.76" gradientUnits="userSpaceOnUse">
+      <stop offset="0" stop-color="#fbdb67"/>
+      <stop offset="0.5" stop-color="#906a01"/>
+      <stop offset="1" stop-color="#ab8009"/>
+    </linearGradient>
+  </defs>
+  <g id="podtail">
+    <rect y="0.14" width="300" height="300" rx="67"/>
+    <path d="M150.08,235.14c-1.27-.24-2.55-.45-3.82-.72-8.82-1.82-14.78-6.95-17.8-15.46-1.86-5.25-2.27-10.71-2.36-16.22,0-2.47-.17-2.6-2.63-2.61-3.6,0-7.2,0-10.8,0-2.75,0-4.33-1.68-4.32-4.46s1.61-4.43,4.36-4.45c3.72,0,7.43-.06,11.15,0,1.61,0,2.21-.59,2.18-2.19-.08-3.37-.06-6.73,0-10.09,0-1.41-.58-2-1.93-2.18-15.44-2.55-25-13.85-25-29.49,0-4.53.42-4.1-4.06-4.12-.83,0-1.66,0-2.48,0a5,5,0,0,1-4.37-4.59,4.86,4.86,0,0,1,4.38-4.37c1.3-.1,2.6-.05,3.9-.06,2.53,0,2.62-.08,2.62-2.56q0-18.59,0-37.17c0-14.65,9.25-26,23.65-28.93a8.16,8.16,0,0,0,.84-.26h15.23c5.11,1.19,9.93,3,14,6.5,6.36,5.49,10.11,12.38,10.17,20.81.13,19.18.16,38.37-.1,57.55-.19,13.39-11.09,25-24.42,26.72-3.48.45-3.45,0-3.44,4,0,2.84,0,5.67,0,8.5,0,1.43.58,2,2,2,4.13,0,8.27,0,12.4,0,2.81,0,4.32,1.58,4.34,4.41s-1.51,4.51-4.41,4.52c-4.08,0-8.15,0-12.22,0-1.62,0-2.19.61-2.17,2.2.07,4.56.32,9.08,1.83,13.44a14.53,14.53,0,0,0,12,10.22c8.27,1.47,15.17-.78,20-7.75,3-4.29,5.43-9,8.17-13.45,2.18-3.57,4.13-7.34,6.72-10.58a18.15,18.15,0,0,1,24.66-3.29,12.06,12.06,0,0,1,2.79,2.91,3.66,3.66,0,0,1-.49,5,4.28,4.28,0,0,1-5,.87,8.34,8.34,0,0,1-1.46-1c-4.8-3.75-10.54-3.2-14.16,1.67-2.06,2.78-3.64,5.92-5.47,8.88-3.25,5.25-6.24,10.71-9.88,15.69a24.61,24.61,0,0,1-16.57,10,6.27,6.27,0,0,0-.84.26ZM131,134.07v.06h20.36c2.48,0,2.58-.12,2.6-2.61,0-.3,0-.59,0-.89,0-3.18,0-3.18-3.27-3.18-3.72,0-7.44.07-11.16,0a4.43,4.43,0,0,1-4.45-4.38,4.52,4.52,0,0,1,4.19-4.65,18.77,18.77,0,0,1,2.12-.07c3.49,0,7,0,10.45,0,2,0,2-.23,2.11-2.93,0-1.56-.37-2.25-2.08-2.2q-5.92.13-11.86,0c-3-.06-5-2-4.94-4.63s2-4.46,5.09-4.48c3.77,0,7.55,0,11.33,0,2.34,0,2.42-.1,2.42-2.57s-.07-2.64-2.35-2.65c-4.31,0-8.62,0-12.93,0a4.54,4.54,0,0,1-.48-9,14.59,14.59,0,0,1,2.29-.13c3.66,0,7.32,0,11,0,1.93,0,2.13-.25,1.49-2.06-2.58-7.31-7.67-11.87-15.32-13.07a53.3,53.3,0,0,0-10.94-.3,19.24,19.24,0,0,0-17.4,13.18c-.62,1.91-.44,2.2,1.53,2.22,3.72,0,7.44,0,11.16.07,3,.08,5,2,4.89,4.71s-2.1,4.44-5.12,4.47c-3.6,0-7.2,0-10.8,0-2.53,0-2.63.11-2.64,2.55s.1,2.62,2.56,2.63h12a4.57,4.57,0,0,1,4.36,2.5c1.59,3-.51,6.46-4.15,6.61-4.19.17-8.38.06-12.57.1-2.06,0-2.26.3-2.25,2.63,0,2.16.21,2.39,2.28,2.41,3.89,0,7.79,0,11.68,0,3.34,0,5.47,1.79,5.53,4.48s-2.12,4.66-5.48,4.69-6.62,0-9.92,0c-4.29,0-4.12-.53-4.11,4.12,0,2.31.16,2.46,2.58,2.46Q120.9,134.09,131,134.07Zm.15,9v.06c-6.9,0-13.81,0-20.71,0-1.57,0-2.32.49-2.24,2.13.08,1.47,0,3,.06,4.43a19.21,19.21,0,0,0,18.61,18.27c2.71.08,5.43.11,8.14,0,7.7-.31,13.29-4,16.9-10.76,2-3.67,2.19-7.66,2.07-11.71-.06-2.24-.18-2.4-2.46-2.4Z" fill="url(#linear-gradient)"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/radiopublic.svg b/app/Views/_assets/images/platforms/radiopublic.svg
new file mode 100644
index 0000000000..99f072d1ad
--- /dev/null
+++ b/app/Views/_assets/images/platforms/radiopublic.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="podcastrepublic">
+    <rect width="300" height="300" rx="67" fill="#ce262f"/>
+    <path d="M229.23,230.93c-30.4-14.09-60.8,14.09-91.2,0V90c30.4,14.09,60.8-14.09,91.2,0Zm-49.32-75.56V129.44l3.15-.73c3.92-.9,4.3,1.12,4.3,4.45v15.31c0,2.84-.38,5.27-4.3,6.18Zm0,14.94c2.65-.6,5.3-1.23,8-1.83,11.75-2.68,16.42-9.94,16.42-21.35v-18.4c0-11.22-3-18.35-17.3-15.12-8,1.81-15.91,3.84-23.87,4.56v91.88a128,128,0,0,0,16.8-2.94Z" fill="#fff" fill-rule="evenodd"/>
+    <path d="M163.2,210.13S161.79,218,138,230.93V154.81Z" fill="#c1c1c1" fill-rule="evenodd"/>
+    <path d="M163.12,210c-30.79-14.09-61.57,14.09-92.35,0V69.07c30.78,14.09,61.56-14.09,92.35,0ZM113,131.73V108.52l3.79-.86c3.41-.78,4.17.29,4.16,4.24v12.47c0,4.32-1.26,5.83-4.66,6.62Zm0,54.46V146.92l3.54-.8c3.41-.78,4.41.11,4.41,4.43v26.8c0,4.07.64,5.9,1.52,6.7a115.06,115.06,0,0,1,16.8-2.67v-.62c-.64-1.07-1.39-2.73-1.39-6.68V142.83c0-6.82-4.55-9.42-7.2-9.92a14.67,14.67,0,0,0,7.45-13.31V104.9c0-9.85-5.17-15.05-15.4-12.78-8.84,1.93-17.68,4.29-26.52,5.09v91.87A131,131,0,0,0,113,186.19Z" fill="#fff" fill-rule="evenodd"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/spotify.svg b/app/Views/_assets/images/platforms/spotify.svg
new file mode 100644
index 0000000000..ed4f435a1f
--- /dev/null
+++ b/app/Views/_assets/images/platforms/spotify.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="spotify">
+    <rect width="300" height="300" rx="67"/>
+    <path d="M199.52,140.5c-27-16-71.5-17.5-97.26-9.68a7.83,7.83,0,0,1-4.54-15c29.57-9,78.73-7.24,109.79,11.2a7.83,7.83,0,0,1-8,13.47Zm-.88,23.74a6.54,6.54,0,0,1-9,2.15c-22.5-13.83-56.8-17.84-83.42-9.76a6.53,6.53,0,1,1-3.8-12.49c30.41-9.23,68.21-4.76,94.05,11.12a6.54,6.54,0,0,1,2.15,9ZM188.39,187a5.21,5.21,0,0,1-7.17,1.74c-19.66-12-44.4-14.73-73.55-8.07a5.22,5.22,0,0,1-2.32-10.17c31.89-7.29,59.25-4.16,81.31,9.33a5.21,5.21,0,0,1,1.73,7.17ZM150,66.28A83.72,83.72,0,1,0,233.72,150,83.72,83.72,0,0,0,150,66.28Z" fill="#1ed760"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/spreaker.svg b/app/Views/_assets/images/platforms/spreaker.svg
new file mode 100644
index 0000000000..7cce4d1063
--- /dev/null
+++ b/app/Views/_assets/images/platforms/spreaker.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="spreaker">
+    <rect width="300" height="300" rx="67" fill="#212322"/>
+    <path d="M168.52,65l-40,46.32L68.75,98.42l61.48,52.22L69.76,203.5l59.58-14.05L170.14,235l5-60.92,56.08-24.67-56.4-23.59L168.57,65Z" fill="#ffc107"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/stitcher.svg b/app/Views/_assets/images/platforms/stitcher.svg
new file mode 100644
index 0000000000..327075acce
--- /dev/null
+++ b/app/Views/_assets/images/platforms/stitcher.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="stitcher">
+    <rect width="300" height="300" rx="67" fill="#fff"/>
+    <path d="M168.94,114.31h31V178.8h-31Z" fill="#eb8923"/>
+    <path d="M134.5,123.39h31v62.3h-31v-62.3Z" fill="#fac712"/>
+    <path d="M99.75,118.38h31V178.8h-31Z" fill="#612268"/>
+    <path d="M65,127.77H96.31v55.1H65Z" fill="#3199cd"/>
+    <path d="M203.69,125.58H235v48.84H203.69V125.58Z" fill="#c33626"/>
+  </g>
+</svg>
diff --git a/app/Views/_assets/images/platforms/tunein.svg b/app/Views/_assets/images/platforms/tunein.svg
new file mode 100644
index 0000000000..7e0f073c0c
--- /dev/null
+++ b/app/Views/_assets/images/platforms/tunein.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
+  <g id="tunein">
+    <rect width="300" height="300" rx="67" fill="#1c203b"/>
+    <path d="M230.77,92.47h-80.3a4.21,4.21,0,0,0-4.23,4.23V177a4.21,4.21,0,0,0,4.23,4.23h80.3A4.21,4.21,0,0,0,235,177V96.23A4.14,4.14,0,0,0,230.77,92.47ZM200.25,154a1,1,0,0,1-.94.94H181.93A1,1,0,0,1,181,154v-5.16a1,1,0,0,1,.94-.94h4.23c.47,0,.47,0,.47-.47V126.28c0-.47,0-.47-.47-.47H182.4a1,1,0,0,1-.94-.93v-5.17a1,1,0,0,1,.94-.94h16a1,1,0,0,1,.94.94v5.17a1,1,0,0,1-.94.93h-3.76c-.47,0-.47,0-.47.47v21.14c0,.47,0,.47.47.47h4.23a1,1,0,0,1,.94.94l.47,5.16Z" fill="#14d8cc" fill-rule="evenodd"/>
+    <path d="M145.77,126.75v72.79H73V126.75h72.79m3.76-8H69.23A4.21,4.21,0,0,0,65,123v80.3a4.21,4.21,0,0,0,4.23,4.23h80.3a4.21,4.21,0,0,0,4.23-4.23V122.53a4.52,4.52,0,0,0-4.23-3.76Z" fill="#14d8cc"/>
+    <path d="M120.88,151.17V146a1,1,0,0,0-.94-.94H98.81a1,1,0,0,0-.94.94v5.16a1,1,0,0,0,.94.94h6.58c.47,0,.47,0,.47.47v27.71a1,1,0,0,0,.94.94H112a1,1,0,0,0,.94-.94V152.58c0-.47,0-.47.47-.47h6.57a1,1,0,0,0,.94-.94Z" fill="#14d8cc" fill-rule="evenodd"/>
+  </g>
+</svg>
diff --git a/app/Views/admin/_header.php b/app/Views/admin/_header.php
index 6f3605a114..274f52dc63 100644
--- a/app/Views/admin/_header.php
+++ b/app/Views/admin/_header.php
@@ -3,7 +3,7 @@
         <a href="<?= route_to(
             'admin'
         ) ?>" class="inline-flex items-center text-xl">
-            <?= svg('logo-castopod', 'text-3xl mr-2') ?>
+            <?= svg('logo-castopod', 'h-10 mr-2') ?>
             Admin
         </a>
     </div>
diff --git a/app/Views/admin/_sidenav.php b/app/Views/admin/_sidenav.php
index 6fea0a1cab..5e0b205e38 100644
--- a/app/Views/admin/_sidenav.php
+++ b/app/Views/admin/_sidenav.php
@@ -3,7 +3,7 @@ $navigation = [
     'dashboard' => ['icon' => 'dashboard', 'items' => ['admin']],
     'podcasts' => [
         'icon' => 'mic',
-        'items' => ['podcast-list', 'podcast-create'],
+        'items' => ['podcast-list', 'podcast-create', 'podcast-import'],
     ],
     'users' => ['icon' => 'group', 'items' => ['user-list', 'user-create']],
     'pages' => ['icon' => 'pages', 'items' => ['page-list', 'page-create']],
diff --git a/app/Views/admin/podcast/settings/dashboard.php b/app/Views/admin/podcast/settings/dashboard.php
new file mode 100644
index 0000000000..f7a742fd88
--- /dev/null
+++ b/app/Views/admin/podcast/settings/dashboard.php
@@ -0,0 +1,9 @@
+<?= $this->extend('admin/_layout') ?>
+
+<?= $this->section('title') ?>
+<?= lang('Podcast.platforms.title') ?>
+<?= $this->endSection() ?>
+
+<?= $this->section('content') ?>
+
+<?= $this->endSection() ?>
diff --git a/app/Views/admin/podcast/settings/platforms.php b/app/Views/admin/podcast/settings/platforms.php
new file mode 100644
index 0000000000..5137eed9a2
--- /dev/null
+++ b/app/Views/admin/podcast/settings/platforms.php
@@ -0,0 +1,99 @@
+<?= $this->extend('admin/_layout') ?>
+
+<?= $this->section('title') ?>
+<?= lang('Platforms.title') ?>
+<?= $this->endSection() ?>
+
+<?= $this->section('content') ?>
+
+<?= form_open(route_to('platforms', $podcast->id), [
+    'class' => 'flex flex-col max-w-md',
+]) ?>
+<?= csrf_field() ?>
+
+<?php foreach ($platforms as $platform): ?>
+
+<div class="relative flex items-start mb-4">
+    <div class="flex flex-col w-12 mr-4">
+        <?= platform_icon($platform->icon_filename, 'w-full mb-1') ?>
+        <div class="inline-flex bg-gray-200">
+            <?= anchor($platform->home_url, icon('external-link', 'mx-auto'), [
+                'class' => 'flex-1 text-gray-600 hover:text-gray-900',
+                'target' => '_blank',
+                'rel' => 'noopener noreferrer',
+                'data-toggle' => 'tooltip',
+                'data-placement' => 'bottom',
+                'title' => lang('Platforms.home_url', [
+                    'platformName' => $platform->label,
+                ]),
+            ]) ?>
+            <?= $platform->submit_url
+                ? anchor($platform->submit_url, icon('upload', 'mx-auto'), [
+                    'class' => 'flex-1 text-gray-600 hover:text-gray-900',
+                    'target' => '_blank',
+                    'rel' => 'noopener noreferrer',
+                    'data-toggle' => 'tooltip',
+                    'data-placement' => 'bottom',
+                    'title' => lang('Platforms.submit_url', [
+                        'platformName' => $platform->label,
+                    ]),
+                ])
+                : '' ?>
+        </div>
+    </div>
+    <div class="flex flex-col flex-1">
+        <?= $platform->link_url
+            ? anchor(
+                route_to('platforms-remove', $podcast->id, $platform->id),
+                icon('delete-bin', 'mx-auto'),
+                [
+                    'class' =>
+                        'absolute right-0 p-1 bg-red-200 rounded-full text-red-700 hover:text-red-900',
+                    'data-toggle' => 'tooltip',
+                    'data-placement' => 'bottom',
+                    'title' => lang('Platforms.remove', [
+                        'platformName' => $platform->label,
+                    ]),
+                ]
+            )
+            : '' ?>
+        <?= form_label($platform->label, $platform->name, [
+            'class' => 'font-semibold mb-2',
+        ]) ?>
+        <?= form_input([
+            'id' => $platform->name,
+            'name' => 'platforms[' . $platform->name . '][url]',
+            'class' => 'form-input mb-1 w-full',
+            'value' => old($platform->name, $platform->link_url),
+            'type' => 'url',
+            'placeholder' => 'https://...',
+        ]) ?>
+        <label class="inline-flex items-center mb-4 text-sm">
+            <?= form_checkbox(
+                [
+                    'id' => $platform->name . '_visible',
+                    'name' => 'platforms[' . $platform->name . '][visible]',
+                    'class' => 'form-checkbox',
+                ],
+                'yes',
+                old(
+                    $platform->name . '_visible',
+                    $platform->visible ? $platform->visible : false
+                )
+            ) ?>
+            <span class="ml-2"><?= lang('Platforms.visible') ?></span>
+        </label>
+    </div>
+</div>
+
+<?php endforeach; ?>
+
+<?= form_button([
+    'content' => lang('Platforms.submit'),
+    'type' => 'submit',
+    'class' => 'self-end px-4 py-2 bg-gray-200',
+]) ?>
+
+<?= form_close() ?>
+
+<?= $this->endSection() ?>
diff --git a/app/Views/admin/podcast/view.php b/app/Views/admin/podcast/view.php
index 013e22aa82..52eea1a532 100644
--- a/app/Views/admin/podcast/view.php
+++ b/app/Views/admin/podcast/view.php
@@ -23,6 +23,10 @@
         'contributor-list',
         $podcast->id
     ) ?>"><?= lang('Podcast.see_contributors') ?></a>
+    <a class="inline-flex px-2 py-1 mb-2 text-white bg-indigo-700 hover:bg-indigo-800" href="<?= route_to(
+        'platforms',
+        $podcast->id
+    ) ?>"><?= lang('Platforms.title') ?></a>
     <a class="inline-flex px-2 py-1 text-white bg-gray-700 hover:bg-gray-800" href="<?= route_to(
         'podcast',
         $podcast->name
diff --git a/app/Views/auth/_layout.php b/app/Views/auth/_layout.php
index 5f9c8de38f..6e0a94e2e9 100644
--- a/app/Views/auth/_layout.php
+++ b/app/Views/auth/_layout.php
@@ -14,10 +14,7 @@
 <body class="flex flex-col items-center justify-center min-h-screen mx-auto bg-gray-100">
 	<header class="mb-4">
 		<a href="<?= route_to('home') ?>" class="inline-flex items-center">
-			<?= svg(
-       'logo-castopod',
-       'text-3xl mr-2'
-   ) ?><span class="text-xl">Castopod</span>
+			<?= svg('logo-castopod', 'h-10 mr-2') ?><span class="text-xl">Castopod</span>
 		</a>
 	</header>
 	<main class="w-full max-w-md px-6 py-4 mx-auto bg-white rounded-lg shadow">
diff --git a/package.json b/package.json
index e4d85539f5..f15cb530a5 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
     "watch:css": "postcss app/Views/_assets/styles/index.css -o public/assets/index.css -w",
     "build:css": "postcss app/Views/_assets/styles/index.css -o public/assets/index.css",
     "build:icons": "svgo -f app/Views/_assets/icons -o public/assets/icons --config=./.svgo.icons.yml",
-    "build:svg": "svgo -f app/Views/_assets/images -o public/assets/images --config=./.svgo.yml",
+    "build:svg": "svgo -f app/Views/_assets/images -o public/assets/images -r --config=./.svgo.yml",
     "lint": "eslint --ext js,ts app/Views/_assets",
     "lint:fix": "eslint --ext js,ts app/Views/_assets --fix",
     "lint:css": "stylelint \"app/Views/_assets/**/*.css\"",
-- 
GitLab