feat: Sets up portal public views with rails ERB and tailwind (#5309)
* feat: Sets up portal public views with rails ERB and tailwind * linter fixes * Remove duplicate style file * Shows articles and categories * Specify layout for articles page * Updates public portal styles * Fixes blog content styles * Portal style updates for article page * Review fixes * Adds breadcrumbs * fix: rspec * fix: public portal spec * Code climate fixes * Adds test cases for missing files * Show only published articles * Updates help center routes * Review fixes * Render markdown content for aticle body * Update app/views/public/api/v1/portals/articles/index.html.erb Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Sojan <sojan@pepalo.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: tejaswini chile <tejaswini@chatwoot.com>
This commit is contained in:
parent
a680b08251
commit
1ea289e8b7
27 changed files with 477 additions and 21 deletions
|
@ -3,6 +3,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||||
before_action :set_portal
|
before_action :set_portal
|
||||||
before_action :set_category
|
before_action :set_category
|
||||||
before_action :set_article, only: [:show]
|
before_action :set_article, only: [:show]
|
||||||
|
layout 'portal'
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@articles = @portal.articles
|
@articles = @portal.articles
|
||||||
|
@ -15,6 +16,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||||
|
|
||||||
def set_article
|
def set_article
|
||||||
@article = @category.articles.find(params[:id])
|
@article = @category.articles.find(params[:id])
|
||||||
|
@parsed_content = render_article_content(@article.content)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_category
|
def set_category
|
||||||
|
@ -28,4 +30,10 @@ class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||||
def list_params
|
def list_params
|
||||||
params.permit(:query)
|
params.permit(:query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_article_content(content)
|
||||||
|
# rubocop:disable Rails/OutputSafety
|
||||||
|
CommonMarker.render_html(content).html_safe
|
||||||
|
# rubocop:enable Rails/OutputSafety
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,7 @@ class Public::Api::V1::Portals::CategoriesController < PublicController
|
||||||
before_action :ensure_custom_domain_request, only: [:show, :index]
|
before_action :ensure_custom_domain_request, only: [:show, :index]
|
||||||
before_action :set_portal
|
before_action :set_portal
|
||||||
before_action :set_category, only: [:show]
|
before_action :set_category, only: [:show]
|
||||||
|
layout 'portal'
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@categories = @portal.categories
|
@categories = @portal.categories
|
||||||
|
@ -12,7 +13,7 @@ class Public::Api::V1::Portals::CategoriesController < PublicController
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_category
|
def set_category
|
||||||
@category = @portal.categories.find_by!(locale: params[:locale])
|
@category = @portal.categories.find_by!(locale: params[:locale], slug: params[:category_slug])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_portal
|
def set_portal
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
class Public::Api::V1::PortalsController < PublicController
|
class Public::Api::V1::PortalsController < PublicController
|
||||||
before_action :ensure_custom_domain_request, only: [:show]
|
before_action :ensure_custom_domain_request, only: [:show]
|
||||||
before_action :set_portal
|
before_action :set_portal
|
||||||
|
layout 'portal'
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<woot-button
|
<woot-button
|
||||||
icon="chevron-left"
|
icon="chevron-left"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
|
size="small"
|
||||||
color-scheme="primary"
|
color-scheme="primary"
|
||||||
@click="onClickGoBack"
|
@click="onClickGoBack"
|
||||||
>
|
>
|
||||||
|
|
|
@ -69,7 +69,7 @@ export default {
|
||||||
},
|
},
|
||||||
portalLink() {
|
portalLink() {
|
||||||
const slug = this.$route.params.portalSlug;
|
const slug = this.$route.params.portalSlug;
|
||||||
return `/public/api/v1/portals/${slug}`;
|
return `/hc/${slug}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default {
|
||||||
},
|
},
|
||||||
portalLink() {
|
portalLink() {
|
||||||
const slug = this.$route.params.portalSlug;
|
const slug = this.$route.params.portalSlug;
|
||||||
return `/public/api/v1/portals/${slug}`;
|
return `/hc/${slug}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
16
app/javascript/packs/portal.js
Normal file
16
app/javascript/packs/portal.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// This file is automatically compiled by Webpack, along with any other files
|
||||||
|
// present in this directory. You're encouraged to place your actual application logic in
|
||||||
|
// a relevant structure within app/javascript and only use these pack files to reference
|
||||||
|
// that code so that it will be compiled.
|
||||||
|
|
||||||
|
import Rails from '@rails/ujs';
|
||||||
|
import Turbolinks from 'turbolinks';
|
||||||
|
|
||||||
|
import { navigateToLocalePage } from '../portal/portalHelpers';
|
||||||
|
|
||||||
|
import '../portal/application.scss';
|
||||||
|
|
||||||
|
Rails.start();
|
||||||
|
Turbolinks.start();
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', navigateToLocalePage);
|
78
app/javascript/portal/application.scss
Normal file
78
app/javascript/portal/application.scss
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
@import 'tailwindcss/base';
|
||||||
|
@import 'tailwindcss/components';
|
||||||
|
@import 'tailwindcss/utilities';
|
||||||
|
|
||||||
|
@import 'widget/assets/scss/reset';
|
||||||
|
@import 'widget/assets/scss/variables';
|
||||||
|
@import 'widget/assets/scss/buttons';
|
||||||
|
@import 'widget/assets/scss/mixins';
|
||||||
|
@import 'widget/assets/scss/forms';
|
||||||
|
@import 'shared/assets/fonts/widget_fonts';
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
font-family: $font-family;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woot-survey-wrap {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-content {
|
||||||
|
@apply text-lg;
|
||||||
|
@apply font-sans;
|
||||||
|
@apply text-slate-800;
|
||||||
|
@apply leading-normal;
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
@apply font-sans leading-relaxed font-extrabold text-slate-900;
|
||||||
|
@apply mb-4;
|
||||||
|
@apply mt-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@apply text-5xl leading-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@apply text-4xl leading-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
@apply text-3xl leading-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
@apply text-2xl leading-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
@apply text-lg;
|
||||||
|
@apply font-sans;
|
||||||
|
@apply text-slate-800;
|
||||||
|
@apply leading-relaxed;
|
||||||
|
@apply mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
@apply list-disc;
|
||||||
|
@apply pl-8;
|
||||||
|
@apply ml-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
@apply text-lg;
|
||||||
|
@apply font-sans;
|
||||||
|
@apply text-slate-800;
|
||||||
|
@apply leading-relaxed;
|
||||||
|
@apply mb-2;
|
||||||
|
}
|
||||||
|
}
|
8
app/javascript/portal/portalHelpers.js
Normal file
8
app/javascript/portal/portalHelpers.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export const navigateToLocalePage = () => {
|
||||||
|
const allLocaleSwitcher = document.querySelector('.locale-switcher');
|
||||||
|
|
||||||
|
const { portalSlug } = allLocaleSwitcher.dataset;
|
||||||
|
allLocaleSwitcher.addEventListener('change', event => {
|
||||||
|
window.location = `/hc/${portalSlug}/${event.target.value}/`;
|
||||||
|
});
|
||||||
|
};
|
23
app/javascript/portal/specs/portal.spec.js
Normal file
23
app/javascript/portal/specs/portal.spec.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { navigateToLocalePage } from '../portalHelpers';
|
||||||
|
|
||||||
|
describe('#navigateToLocalePage', () => {
|
||||||
|
it('returns correct cookie name', () => {
|
||||||
|
const elemDiv = document.createElement('div');
|
||||||
|
elemDiv.classList.add('locale-switcher');
|
||||||
|
document.body.appendChild(elemDiv);
|
||||||
|
|
||||||
|
const allLocaleSwitcher = document.querySelector('.locale-switcher');
|
||||||
|
|
||||||
|
allLocaleSwitcher.addEventListener = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce((event, callback) => {
|
||||||
|
callback({ target: { value: 1 } });
|
||||||
|
});
|
||||||
|
|
||||||
|
navigateToLocalePage();
|
||||||
|
expect(allLocaleSwitcher.addEventListener).toBeCalledWith(
|
||||||
|
'change',
|
||||||
|
expect.any(Function)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
42
app/views/layouts/portal.html.erb
Normal file
42
app/views/layouts/portal.html.erb
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<%#
|
||||||
|
# Application Layout
|
||||||
|
|
||||||
|
This view template is used as the layout
|
||||||
|
for every page that Administrate generates.
|
||||||
|
|
||||||
|
By default, it renders:
|
||||||
|
- Navigation
|
||||||
|
- Content for a search bar
|
||||||
|
(if provided by a `content_for` block in a nested page)
|
||||||
|
- Flashes
|
||||||
|
- Links to stylesheets and JavaScripts
|
||||||
|
%>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<%= I18n.locale %>">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="ROBOTS" content="NOODP">
|
||||||
|
<meta name="viewport" content="initial-scale=1">
|
||||||
|
|
||||||
|
<%= javascript_pack_tag 'portal' %>
|
||||||
|
<%= stylesheet_pack_tag 'portal' %>
|
||||||
|
|
||||||
|
|
||||||
|
<%= csrf_meta_tags %>
|
||||||
|
<title><%= @portal.page_title%></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="app-container">
|
||||||
|
<main class="main-content min-h-screen flex flex-col" role="main">
|
||||||
|
<%= render "public/api/v1/portals/header", portal: @portal %>
|
||||||
|
<%= yield %>
|
||||||
|
<%= render "public/api/v1/portals/footer" %>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
44
app/views/public/api/v1/portals/_category-block.html.erb
Normal file
44
app/views/public/api/v1/portals/_category-block.html.erb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||||
|
<section class="bg-white lg:container w-full py-6 px-4 flex flex-col h-full">
|
||||||
|
<div class="flex justify-between items-center w-full">
|
||||||
|
<h3 class="text-xl text-slate-900 font-semibold subpixel-antialiased leading-relaxed hover:underline"">
|
||||||
|
<a href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>"><%= category.name %> </a>
|
||||||
|
</h3> <span class="text-slate-500"><%= category.articles.published.size %> articles</span>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 w-full mt-2 flex-grow">
|
||||||
|
<% if category.articles.published.size == 0 %>
|
||||||
|
<div class="h-full flex items-center justify-center bg-slate-50 rounded-xl mb-4">
|
||||||
|
<p class="text-sm text-slate-500">No articles here</p>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<% category.articles.published.take(5).each do |article| %>
|
||||||
|
<div class="flex justify-between content-center h-8 my-1">
|
||||||
|
<a class="text-slate-800 hover:underline leading-8"
|
||||||
|
href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>/<%= article.id %>" class=""><%= article.title %></a>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 fill-current text-slate-700" width="24" height="24" fill="none" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M8.47 4.22a.75.75 0 0 0 0 1.06L15.19 12l-6.72 6.72a.75.75 0 1 0 1.06 1.06l7.25-7.25a.75.75 0 0 0 0-1.06L9.53 4.22a.75.75 0 0 0-1.06 0Z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>"
|
||||||
|
class="flex flex-row items-center text-base font-sans font-medium text-woot-600 hover:text-slate-900 hover:underline mt-4">
|
||||||
|
|
||||||
|
View all articles
|
||||||
|
<span class="ml-2">
|
||||||
|
<svg class="w-4 h-4 fill-current text-woot-500" width="24" height="24" fill="none" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M13.267 4.209a.75.75 0 0 0-1.034 1.086l6.251 5.955H3.75a.75.75 0 0 0 0 1.5h14.734l-6.251 5.954a.75.75 0 0 0 1.034 1.087l7.42-7.067a.996.996 0 0 0 .3-.58.758.758 0 0 0-.001-.29.995.995 0 0 0-.3-.578l-7.419-7.067Z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
6
app/views/public/api/v1/portals/_footer.html.erb
Normal file
6
app/views/public/api/v1/portals/_footer.html.erb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||||
|
<footer class="bg-slate-50 py-16 flex flex-col items-center justify-center">
|
||||||
|
<div class="mx-auto max-w-2xl">
|
||||||
|
<p class="text-slate-700 py-2 text-center">Made with <a href="/" target="_blank">Chatwoot 💙</a>.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
37
app/views/public/api/v1/portals/_header.html.erb
Normal file
37
app/views/public/api/v1/portals/_header.html.erb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||||
|
<header class="bg-white mx-auto px-4 max-w-4xl w-full border border-slate-600">
|
||||||
|
<nav class="px-0 flex" aria-label="Top">
|
||||||
|
<div class="w-full py-4 flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a href="#">
|
||||||
|
<span class="sr-only"><%= portal.name %>%></span>
|
||||||
|
<img class="h-8 w-auto"
|
||||||
|
src="https://d33wubrfki0l68.cloudfront.net/973467c532160fd8b940300a43fa85fa2d060307/dc9a0/static/brand-73f58cdefae282ae74cebfa74c1d7003.svg"
|
||||||
|
alt="">
|
||||||
|
</a>
|
||||||
|
<div class="ml-8 border-l-1 border-slate-50">
|
||||||
|
<div class="flex-grow flex-shrink-0">
|
||||||
|
<a href="#" class="flex flex-row items-center text-sm font-sans font-medium text-slate-700 hover:text-slate-800 hover:underline"> Goto main site
|
||||||
|
<span class="ml-2">
|
||||||
|
<svg class="w-4 h-4 fill-current text-slate-600" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.267 4.209a.75.75 0 0 0-1.034 1.086l6.251 5.955H3.75a.75.75 0 0 0 0 1.5h14.734l-6.251 5.954a.75.75 0 0 0 1.034 1.087l7.42-7.067a.996.996 0 0 0 .3-.58.758.758 0 0 0-.001-.29.995.995 0 0 0-.3-.578l-7.419-7.067Z" /></svg>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-grow justify-end flex-shrink-0 items-center">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<select
|
||||||
|
value="<%= @portal.config["default_locale"] %>"
|
||||||
|
data-portal-slug="<%= @portal.slug %>"
|
||||||
|
class="h-8 block w-full flex-shrink bg-slate-50 border border-slate-200 text-slate-700 py-1 px-4 pr-8 rounded leading-tight text-base font-medium focus:outline-none focus:bg-white focus:border-slate-500 locale-switcher">
|
||||||
|
<% @portal.config["allowed_locales"].each do |locale| %>
|
||||||
|
<option value="<%= locale %>"><%= locale %></option>
|
||||||
|
<% end %>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
7
app/views/public/api/v1/portals/_hero.html.erb
Normal file
7
app/views/public/api/v1/portals/_hero.html.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||||
|
<section class="bg-slate-50 py-24 flex flex-col items-center justify-center">
|
||||||
|
<div class="mx-auto max-w-2xl">
|
||||||
|
<h1 class="text-4xl text-slate-900 font-semibold subpixel-antialiased leading-relaxed text-center"><%= portal.header_text %></h1>
|
||||||
|
<p class="text-slate-700 py-2 text-center">Search for the articles here or browse the categories below.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
35
app/views/public/api/v1/portals/articles/index.html.erb
Normal file
35
app/views/public/api/v1/portals/articles/index.html.erb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
<div class="bg-slate-50">
|
||||||
|
<div class="max-w-4xl px-6 py-16 mx-auto space-y-12 w-full">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<a class="text-slate-800 hover:underline leading-8"
|
||||||
|
href="/hc/<%= @portal.slug %>/<%= @category.slug %>" class=""><%= @portal.name %> Home</a>
|
||||||
|
<span>/</span>
|
||||||
|
<span>/</span>
|
||||||
|
</div>
|
||||||
|
<% @articles.each do |article| %>
|
||||||
|
<h1 class="text-4xl font-bold md:tracking-normal leading-snug md:text-5xl text-slate-900">
|
||||||
|
<%= article.title %></h1>
|
||||||
|
<div class="flex flex-col items-start justify-between w-full md:flex-row md:items-center pt-2">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<img src="<%= article.author.avatar_url %>" alt="" class="w-12 h-812 border rounded-full">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-base font-medium text-slate-900 mb-2"><%= article.author.name %></h5>
|
||||||
|
<p class="text-sm font-normal text-slate-700">
|
||||||
|
<%= article.author.updated_at.strftime("%B %d %Y") %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="max-w-4xl flex-grow w-full px-6 py-16 mx-auto space-y-12">
|
||||||
|
<article class="space-y-8 ">
|
||||||
|
<div class="text-slate-800 font-sans leading-8 text-lg subpixel-antialiased max-w-3xl blog-content">
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
35
app/views/public/api/v1/portals/articles/show.html.erb
Normal file
35
app/views/public/api/v1/portals/articles/show.html.erb
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<div class="bg-slate-50">
|
||||||
|
<div class="max-w-4xl px-6 py-16 mx-auto space-y-4 w-full">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a class="text-slate-700 hover:underline leading-8 text-sm font-medium"
|
||||||
|
href="/hc/<%= @portal.slug %>/<%= @article.category.locale %>" class=""><%= @portal.name %> Home</a>
|
||||||
|
<span class="text-xs text-slate-600 px-1">/</span>
|
||||||
|
<a class="text-slate-700 hover:underline leading-8 text-sm font-medium"
|
||||||
|
href="/hc/<%= @portal.slug %>/<%= @article.category.locale %>/<%= @article.category.slug %>"
|
||||||
|
class=""><%= @article.category.name %></a>
|
||||||
|
<span class="text-xs text-slate-600 px-1">/</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-4xl font-bold md:tracking-normal leading-snug md:text-5xl text-slate-900">
|
||||||
|
<%= @article.title %></h1>
|
||||||
|
<div class="flex flex-col items-start justify-between w-full md:flex-row md:items-center pt-2">
|
||||||
|
<div class="flex items-center md:space-x-2">
|
||||||
|
<img src="<%= @article.author.avatar_url %>" alt="" class="w-12 h-812 border rounded-full">
|
||||||
|
<div>
|
||||||
|
<h5 class="text-base font-medium text-slate-900 mb-2"><%= @article.author.name %></h5>
|
||||||
|
<p class="text-sm font-normal text-slate-700">
|
||||||
|
<%= @article.author.updated_at.strftime("%B %d %Y") %></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="max-w-4xl flex-grow w-full px-6 py-16 mx-auto space-y-12">
|
||||||
|
<article class="space-y-8 ">
|
||||||
|
<div class="text-slate-800 font-sans leading-8 text-lg subpixel-antialiased max-w-3xl blog-content">
|
||||||
|
<p><%= @parsed_content %></p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
|
@ -0,0 +1,44 @@
|
||||||
|
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||||
|
<section class="bg-white lg:container w-full py-6 px-4 flex flex-col h-full">
|
||||||
|
<div class="flex justify-between items-center w-full">
|
||||||
|
<h3 class="text-xl text-slate-900 font-semibold subpixel-antialiased leading-relaxed hover:underline"">
|
||||||
|
<a href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>"><%= category.name %> </a>
|
||||||
|
</h3> <span class="text-slate-500"><%= category.articles.published.size %> articles</span>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 w-full mt-2 flex-grow">
|
||||||
|
<% if category.articles.published.size == 0 %>
|
||||||
|
<div class="h-full flex items-center justify-center bg-slate-50 rounded-xl mb-4">
|
||||||
|
<p class="text-sm text-slate-500">No articles here</p>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<% category.articles.published.take(5).each do |article| %>
|
||||||
|
<div class="flex justify-between content-center h-8 my-1">
|
||||||
|
<a class="text-slate-800 hover:underline leading-8"
|
||||||
|
href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>/<%= article.id %>" class=""><%= article.title %></a>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<svg class="w-4 h-4 fill-current text-slate-700" width="24" height="24" fill="none" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M8.47 4.22a.75.75 0 0 0 0 1.06L15.19 12l-6.72 6.72a.75.75 0 1 0 1.06 1.06l7.25-7.25a.75.75 0 0 0 0-1.06L9.53 4.22a.75.75 0 0 0-1.06 0Z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a href="/hc/<%= portal.slug %>/<%= category.locale %>/<%= category.slug %>"
|
||||||
|
class="flex flex-row items-center text-base font-sans font-medium text-woot-600 hover:text-slate-900 hover:underline mt-4">
|
||||||
|
|
||||||
|
View all articles
|
||||||
|
<span class="ml-2">
|
||||||
|
<svg class="w-4 h-4 fill-current text-woot-500" width="24" height="24" fill="none" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M13.267 4.209a.75.75 0 0 0-1.034 1.086l6.251 5.955H3.75a.75.75 0 0 0 0 1.5h14.734l-6.251 5.954a.75.75 0 0 0 1.034 1.087l7.42-7.067a.996.996 0 0 0 .3-.58.758.758 0 0 0-.001-.29.995.995 0 0 0-.3-.578l-7.419-7.067Z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||||
|
<section class="bg-slate-50 py-24 flex flex-col items-center justify-center">
|
||||||
|
<div class="mx-auto max-w-2xl">
|
||||||
|
<h1 class="text-4xl text-slate-900 font-semibold subpixel-antialiased leading-relaxed text-center"><%= portal.header_text %></h1>
|
||||||
|
<p class="text-slate-700 py-2 text-center">Search for the articles here or browse the categories below.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
11
app/views/public/api/v1/portals/categories/index.html.erb
Normal file
11
app/views/public/api/v1/portals/categories/index.html.erb
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<%= render "hero", portal: @portal %>
|
||||||
|
|
||||||
|
<div class="max-w-4xl w-full flex-grow mx-auto py-16">
|
||||||
|
<div class="grid grid-cols-2 gap-x-32 gap-y-12">
|
||||||
|
<% @categories.each do |category| %>
|
||||||
|
<%= render "category-block", category: category, portal: @portal %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
41
app/views/public/api/v1/portals/categories/show.html.erb
Normal file
41
app/views/public/api/v1/portals/categories/show.html.erb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
<div class="bg-slate-50">
|
||||||
|
<div class="max-w-4xl px-6 py-16 mx-auto space-y-8">
|
||||||
|
<a class="text-slate-700 text-sm hover:underline leading-8"
|
||||||
|
href="/hc/<%= @portal.slug %>/<%= @category.locale %>" class=""><%= @portal.name %> Home</a>
|
||||||
|
<span class="text-xs text-slate-600 px-1">/</span>
|
||||||
|
|
||||||
|
<div class="flex justify-start items-center w-full">
|
||||||
|
<h1 class="text-3xl font-bold md:tracking-normal leading-snug text-slate-900">
|
||||||
|
<%= @category.name %></h1>
|
||||||
|
<span class="text-slate-500 px-8"><%= @category.articles.published.size %> articles</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<section class="bg-white max-w-4xl w-full mx-auto py-6 px-4 flex flex-col items-center justify-center flex-grow">
|
||||||
|
|
||||||
|
<div class="py-4 w-full mt-2 flex-grow">
|
||||||
|
<% if @category.articles.published.size == 0 %>
|
||||||
|
<div class="h-full flex items-center justify-center bg-slate-50 rounded-xl">
|
||||||
|
<p class="text-sm text-slate-500">No articles here</p>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<% @category.articles.published.each do |article| %>
|
||||||
|
<div class="flex justify-between content-center h-8 my-1">
|
||||||
|
<a class="text-slate-800 hover:underline"
|
||||||
|
href="/hc/<%= @portal.slug %>/<%= @category.locale %>/<%= @category.slug %>/<%= article.id %>"
|
||||||
|
class=""><%= article.title %></a>
|
||||||
|
<span>
|
||||||
|
<svg class="w-4 h-4 fill-current text-slate-700" width="24" height="24" fill="none" viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M8.47 4.22a.75.75 0 0 0 0 1.06L15.19 12l-6.72 6.72a.75.75 0 1 0 1.06 1.06l7.25-7.25a.75.75 0 0 0 0-1.06L9.53 4.22a.75.75 0 0 0-1.06 0Z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
12
app/views/public/api/v1/portals/show.html.erb
Normal file
12
app/views/public/api/v1/portals/show.html.erb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
<%= render "hero", portal: @portal %>
|
||||||
|
|
||||||
|
<div class="max-w-4xl w-full flex-grow mx-auto py-16">
|
||||||
|
<div class="grid grid-cols-2 gap-x-32 gap-y-12">
|
||||||
|
<% @portal.categories.each do |category| %>
|
||||||
|
<%= render "category-block", category: category, portal: @portal %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -287,11 +287,11 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
get 'hc/:slug/:locale', to: 'public/api/v1/portals#show', format: 'json'
|
get 'hc/:slug/:locale', to: 'public/api/v1/portals#show'
|
||||||
get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index', format: 'json'
|
get 'hc/:slug/:locale/categories', to: 'public/api/v1/portals/categories#index'
|
||||||
get 'hc/:slug/:locale/:category_slug', to: 'public/api/v1/portals/categories#show', format: 'json'
|
get 'hc/:slug/:locale/:category_slug', to: 'public/api/v1/portals/categories#show'
|
||||||
get 'hc/:slug/:locale/:category_slug/articles', to: 'public/api/v1/portals/articles#index', format: 'json'
|
get 'hc/:slug/:locale/:category_slug/articles', to: 'public/api/v1/portals/articles#index'
|
||||||
get 'hc/:slug/:locale/:category_slug/:id', to: 'public/api/v1/portals/articles#show', format: 'json'
|
get 'hc/:slug/:locale/:category_slug/:id', to: 'public/api/v1/portals/articles#show'
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
# Used in mailer templates
|
# Used in mailer templates
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"@chatwoot/utils": "^0.0.10",
|
"@chatwoot/utils": "^0.0.10",
|
||||||
"@hcaptcha/vue-hcaptcha": "^0.3.2",
|
"@hcaptcha/vue-hcaptcha": "^0.3.2",
|
||||||
"@rails/actioncable": "6.1.3",
|
"@rails/actioncable": "6.1.3",
|
||||||
|
"@rails/ujs": "^7.0.3-1",
|
||||||
"@rails/webpacker": "5.3.0",
|
"@rails/webpacker": "5.3.0",
|
||||||
"@sentry/tracing": "^6.19.7",
|
"@sentry/tracing": "^6.19.7",
|
||||||
"@sentry/vue": "^6.19.7",
|
"@sentry/vue": "^6.19.7",
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
"semver": "7.3.5",
|
"semver": "7.3.5",
|
||||||
"spinkit": "~1.2.5",
|
"spinkit": "~1.2.5",
|
||||||
"tailwindcss": "^1.9.6",
|
"tailwindcss": "^1.9.6",
|
||||||
|
"turbolinks": "^5.2.0",
|
||||||
"url-loader": "^2.0.0",
|
"url-loader": "^2.0.0",
|
||||||
"v-tooltip": "~2.1.3",
|
"v-tooltip": "~2.1.3",
|
||||||
"videojs-record": "^4.5.0",
|
"videojs-record": "^4.5.0",
|
||||||
|
|
|
@ -20,10 +20,6 @@ RSpec.describe 'Public Articles API', type: :request do
|
||||||
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/articles"
|
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/articles"
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
|
||||||
|
|
||||||
expect(json_response['payload'].length).to eql portal.articles.count
|
|
||||||
expect(json_response['meta']['articles_count']).to be json_response['payload'].size
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'get all articles with searched text query' do
|
it 'get all articles with searched text query' do
|
||||||
|
@ -39,9 +35,6 @@ RSpec.describe 'Public Articles API', type: :request do
|
||||||
headers: agent.create_new_auth_token,
|
headers: agent.create_new_auth_token,
|
||||||
params: { query: 'funny' }
|
params: { query: 'funny' }
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
|
||||||
expect(json_response['payload'].count).to be 1
|
|
||||||
expect(json_response['meta']['articles_count']).to be json_response['payload'].size
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,9 +43,6 @@ RSpec.describe 'Public Articles API', type: :request do
|
||||||
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/#{article.id}"
|
get "/hc/#{portal.slug}/#{category.locale}/#{category.slug}/#{article.id}"
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
|
||||||
|
|
||||||
expect(json_response['title']).to eql article.title
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,9 +14,6 @@ RSpec.describe 'Public Portals API', type: :request do
|
||||||
get "/hc/#{portal.slug}/en"
|
get "/hc/#{portal.slug}/en"
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
json_response = JSON.parse(response.body)
|
|
||||||
expect(json_response['slug']).to eql 'test-portal'
|
|
||||||
expect(json_response['meta']['articles_count']).to be 0
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'Throws unauthorised error for unknown domain' do
|
it 'Throws unauthorised error for unknown domain' do
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -1878,6 +1878,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.3.tgz#c8a67ec4d22ecd6931f7ebd98143fddbc815419a"
|
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.1.3.tgz#c8a67ec4d22ecd6931f7ebd98143fddbc815419a"
|
||||||
integrity sha512-m02524MR9cTnUNfGz39Lkx9jVvuL0tle4O7YgvouJ7H83FILxzG1nQ5jw8pAjLAr9XQGu+P1sY4SKE3zyhCNjw==
|
integrity sha512-m02524MR9cTnUNfGz39Lkx9jVvuL0tle4O7YgvouJ7H83FILxzG1nQ5jw8pAjLAr9XQGu+P1sY4SKE3zyhCNjw==
|
||||||
|
|
||||||
|
"@rails/ujs@^7.0.3-1":
|
||||||
|
version "7.0.3-1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-7.0.3-1.tgz#0a0f4f2b22b887bcbf6e0b0a72b8c86665cd31d9"
|
||||||
|
integrity sha512-g3LgpBAsWmW97xFxh5OTDgyEJLt63fEENJUYb/iNFRXY6aKLI/by6MjFw7x492DSP/+vKQa3oMEdNnjI9+yZgQ==
|
||||||
|
|
||||||
"@rails/webpacker@5.3.0":
|
"@rails/webpacker@5.3.0":
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.3.0.tgz#9d7a615735f850572b9c5e2ad4c57f4af70d70fd"
|
resolved "https://registry.yarnpkg.com/@rails/webpacker/-/webpacker-5.3.0.tgz#9d7a615735f850572b9c5e2ad4c57f4af70d70fd"
|
||||||
|
@ -15087,6 +15092,11 @@ tunnel-agent@^0.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
turbolinks@^5.2.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/turbolinks/-/turbolinks-5.2.0.tgz#e6877a55ea5c1cb3bb225f0a4ae303d6d32ff77c"
|
||||||
|
integrity sha512-pMiez3tyBo6uRHFNNZoYMmrES/IaGgMhQQM+VFF36keryjb5ms0XkVpmKHkfW/4Vy96qiGW3K9bz0tF5sK9bBw==
|
||||||
|
|
||||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||||
version "0.14.5"
|
version "0.14.5"
|
||||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||||
|
|
Loading…
Reference in a new issue