Added Ghost Explore UI

no issue

- Added `/explore` route which requires min. Admin and is behind a feature flag
- Fetches Admin API key and ID to create a token and return back to Ghost Explore with the correct query params
- Fullscreen UI
This commit is contained in:
Aileen Nowak 2022-07-27 14:22:45 +01:00 committed by Aileen Booker
parent fce09a6a1b
commit 515bf1cc14
7 changed files with 228 additions and 1 deletions

View File

@ -0,0 +1,40 @@
import Controller from '@ember/controller';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
export default class ExploreController extends Controller {
@service ghostPaths;
get apiUrl() {
const origin = new URL(window.location.origin);
const subdir = this.ghostPaths.subdir;
// We want the API URL without protocol
let url = this.ghostPaths.url.join(origin.host, subdir);
return url.replace(/\/$/, '');
}
get exploreCredentials() {
const explore = this.model.findBy('slug', 'ghost-explore');
const adminKey = explore.adminKey;
return `${adminKey.id}:${adminKey.secret}`;
}
@action
submitExploreSite() {
const token = this.exploreCredentials;
const apiUrl = this.apiUrl;
// Ghost Explore URL to submit a new site
const destination = new URL('https://ghost.org/explore/submit');
const query = new URLSearchParams();
query.append('token', token);
query.append('url', apiUrl);
destination.search = query;
window.location = destination.toString();
}
}

View File

@ -69,6 +69,8 @@ Router.map(function () {
this.route('user', {path: ':user_slug'}); this.route('user', {path: ':user_slug'});
}); });
this.route('explore');
this.route('settings.integrations', {path: '/settings/integrations'}, function () { this.route('settings.integrations', {path: '/settings/integrations'}, function () {
this.route('new'); this.route('new');
}); });

View File

@ -0,0 +1,19 @@
import AdminRoute from 'ghost-admin/routes/admin';
import {inject as service} from '@ember/service';
export default class ExploreRoute extends AdminRoute {
@service session;
@service store;
@service feature;
beforeModel() {
super.beforeModel(...arguments);
if (!this.feature.explore) {
return this.transitionTo('home');
}
}
model() {
return this.store.findAll('integration');
}
}

View File

@ -70,7 +70,8 @@
@import "layouts/post-preview.css"; @import "layouts/post-preview.css";
@import "layouts/dashboard.css"; @import "layouts/dashboard.css";
@import "layouts/tiers.css"; @import "layouts/tiers.css";
@import "layouts/offers.css" @import "layouts/offers.css";
@import "layouts/explore.css";
/* ---------------------------✈️----------------------------- */ /* ---------------------------✈️----------------------------- */

View File

@ -0,0 +1,121 @@
.fullscreen-explore-container {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
height: 100vh;
background: linear-gradient(180deg, var(--white) 0%, #E1E1E1 100%);
overflow: hidden;
}
.explore-close {
position: absolute;
top: 10px;
right: 10px;
width: 18px;
height: 18px;
}
.explore-close svg {
stroke: var(--middarkgrey);
}
.explore-close svg path {
stroke-width: 1px;
}
.explore-close:hover svg {
stroke: var(--darkgrey);
}
.explore-container {
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: column;
padding: 14rem 2vmin 4vmin;
}
.explore-header {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
.explore-header h1 {
margin: 2rem 0 0;
font-size: 3.7rem;
text-align: center;
font-weight: 700;
letter-spacing: -.03em;
line-height: 45px;
}
.explore-api {
margin-bottom: 50px;
color: rgba(0, 0, 0, 0.52);
font-size: 2.3rem;
font-weight: 400;
line-height: 30px;
text-align: center;
letter-spacing: -0.03em;
}
.explore-permissions {
background: var(--white);
padding: 3rem 3.5rem;
max-width: 457px;
width: 100%;
border-radius: 6px;
}
.explore-permissions svg path {
stroke: #86C600;
}
.explore-permissions > div {
display: flex;
justify-content: space-between;
flex-direction: row;
align-items: baseline;
}
.explore-permissions p {
color: rgba(0, 0, 0, 0.66);
margin: 0;
font-size: 1.9rem;
font-weight: 400;
letter-spacing: -0.03em;
line-height: 1.32;
}
.explore-permissions div:not(:last-of-type) {
margin-bottom: 3rem;
}
.explore-permissions > div span {
padding-right: 18px;
}
.explore button {
margin-top: 4vmin;
max-width: 457px;
width: 100%;
height: 50px;
border-radius: 6px;
}
.explore button span {
font-size: 1.7rem;
font-weight: 500;
color: var(--white);
}
.explore button svg {
fill: var(--white);
margin-left: 0.1em;
height: 14px;
}

View File

@ -0,0 +1,32 @@
<div class="explore fullscreen-explore-container">
<div class="relative">
<LinkTo class="explore-close" data-test-button="close-explore" @route="dashboard">
{{svg-jar "close"}}<span class="hidden">Close</span>
</LinkTo>
<div class="explore-container">
<div class="explore-header">
{{svg-jar "ghost-orb" alt="Ghost" class="w25 v-mid"}}
<h1>Connect to Ghost Explore.</h1>
<p class="explore-api">{{this.apiUrl}}</p>
</div>
<div class="explore-permissions">
<div>
<span>{{svg-jar "check-circle" class="w6 v-mid" alt="checkmark"}}</span>
<p>Give read-only access to your site to create the Explore directory posting.</p>
</div>
<div>
<span>{{svg-jar "check-circle" class="w6 v-mid" alt="checkmark"}}</span>
<p>You will be able to choose what data is publicly visible, or hidden.</p>
</div>
<div>
<span>{{svg-jar "check-circle" class="w6 v-mid" alt="checkmark"}}</span>
<p>Your site will be ranked and promoted to across the entire Ghost ecosystem.</p>
</div>
</div>
<button type="button" class="gh-btn gh-btn-black gh-btn-large gh-btn-icon-right" {{on "click" this.submitExploreSite}} data-test-button="submit-explore">
<span>Connect data &amp; edit listing {{svg-jar "arrow-right-tail"}}</span>
</button>
</div>
</div>
</div>

View File

@ -0,0 +1,12 @@
import {describe, it} from 'mocha';
import {expect} from 'chai';
import {setupTest} from 'ember-mocha';
describe('Unit | Route | explore', function () {
setupTest();
it('exists', function () {
let route = this.owner.lookup('route:explore');
expect(route).to.be.ok;
});
});