Adds dark mode to entire dashboard (#467)

* Adds New Dark Mode Assets

* Moves triangle for dropdown to a reasonable position

* Majority .eex dark implementation

* Fixes Logo Positioning

* Adds theme flag to user schema, uses it

* Uses correct variables for theme applicator script

* Minor missed theme changes/fallbacks

* Individual Component Support + Theme Context

* Sources Tab Support

This was a pain to test D:

* Partial Stats Sections Support

* More of stats modules supported

* Modal +table support

* Improves some Flatpickr in light theme, supports dark theme

* Fixes missed settings tab colors

* Finishes Devices module support

* Fixes bar graph colors

* Better colorizes maps module

* Undoes colorized bars

(they looked bad, on second thought)

* Fixes loading indicator

* Finishes conversions module

* Adds changelog entry

The PR number could be wrong, will double check

* Fixes missed header color

* Fixes naming of migration and removes static alter

* Does migration correctly

As I said, my Elixir is pretty weak heh

* Adds support for spike notifications setting

* Improves contrast and visibility for email settings

* Resolves @ukutaht's comments on #467

* Fixes missing dark style

* Found one more missed dark element (shared links)

* Formatting fixes
This commit is contained in:
Vignesh Joglekar 2020-12-16 03:57:28 -06:00 committed by GitHub
parent a50fd555cf
commit 425975efec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 884 additions and 562 deletions

View File

@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- Display weekday on the visitor graph plausible/analytics#175 - Display weekday on the visitor graph plausible/analytics#175
- Collect and display browser & OS versions plausible/analytics#397 - Collect and display browser & OS versions plausible/analytics#397
- Simple notifications around traffic spikes plausible/analytics#453 - Simple notifications around traffic spikes plausible/analytics#453
- Dark theme option/system setting follow plausible/analytics#467
### Changed ### Changed
- Use alpine as base image to decrease Docker image size plausible/analytics#353 - Use alpine as base image to decrease Docker image size plausible/analytics#353

View File

@ -5,6 +5,7 @@
@import "modal.css"; @import "modal.css";
@import "loader.css"; @import "loader.css";
@import "tooltip.css"; @import "tooltip.css";
@import "flatpickr.dark.css";
.button { .button {
@apply bg-indigo-600 border border-transparent rounded-md py-2 px-4 inline-flex justify-center text-sm leading-5 font-medium text-white transition hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500; @apply bg-indigo-600 border border-transparent rounded-md py-2 px-4 inline-flex justify-center text-sm leading-5 font-medium text-white transition hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500;
@ -156,7 +157,7 @@ blockquote {
.dropdown-content::before { .dropdown-content::before {
top: -16px; top: -16px;
right: 64px; right: 8px;
left: auto; left: auto;
} }
.dropdown-content::before { .dropdown-content::before {
@ -174,7 +175,7 @@ blockquote {
} }
.dropdown-content::after { .dropdown-content::after {
top: -14px; top: -14px;
right: 65px; right: 9px;
left: auto; left: auto;
} }
@ -196,6 +197,14 @@ blockquote {
background-color: #f1f5f8; background-color: #f1f5f8;
} }
.dark .table-striped tbody tr:nth-child(odd) {
background-color: rgb(37, 47, 63);
}
.dark .table-striped tbody tr:nth-child(even) {
background-color: rgb(26, 32, 44);
}
.twitter-icon { .twitter-icon {
width: 1.25em; width: 1.25em;
height: 1.25em; height: 1.25em;
@ -252,3 +261,8 @@ blockquote {
.datamaps-subunit { .datamaps-subunit {
cursor: pointer; cursor: pointer;
} }
/* Only because the map handler doesn't expose an easier way to change the shadow color */
.dark .hoverinfo {
box-shadow: 1px 1px 5px rgb(26, 32, 44);
}

View File

@ -0,0 +1,110 @@
/* Because Flatpickr offers zero support for dynamic theming on its own (outside of third-party plugins) */
.dark .flatpickr-calendar {
background-color: #1f2937;
}
.dark .flatpickr-weekday {
color: #f3f4f6;
}
.dark .flatpickr-prev-month {
fill: #f3f4f6 !important;
}
.dark .flatpickr-next-month {
fill: #f3f4f6 !important;
}
.dark .flatpickr-monthDropdown-months {
color: #f3f4f6 !important;
}
.dark .numInputWrapper {
color: #f3f4f6;
}
.dark .flatpickr-day.prevMonthDay {
color: #94a3af;
}
.dark .flatpickr-day {
color: #E5E7EB;
}
.dark .flatpickr-day.prevMonthDay {
color: #9CA3AF;
}
.dark .flatpickr-day.nextMonthDay {
color: #9CA3AF;
}
.dark .flatpickr-day:hover {
background-color: #374151;
}
.dark :not(.startRange):not(.endRange).flatpickr-day.nextMonthDay:hover {
background-color: #374151;
}
.dark :not(.startRange):not(.endRange).flatpickr-day.prevMonthDay:hover {
background-color: #374151;
}
.dark .flatpickr-next-month {
fill: #f3f4f6;
}
.dark .flatpickr-day.flatpickr-disabled {
color: #4B5563;
}
.dark .flatpickr-day.flatpickr-disabled:hover {
color: #4B5563;
}
.dark .flatpickr-day.today {
background-color: rgba(167, 243, 208, 0.5);
}
.dark .flatpickr-day.today {
border-color: #34D399;
}
.dark .flatpickr-day.inRange {
background-color: #374151;
box-shadow: -5px 0 0 #374151,5px 0 0 #374151;
border-color: #374151;
}
.dark .flatpickr-day.prevMonthDay.inRange {
background-color: #374151;
box-shadow: -5px 0 0 #374151,5px 0 0 #374151;
border-color: #374151;
}
.dark .flatpickr-day.nextMonthDay.inRange {
background-color: #374151;
box-shadow: -5px 0 0 #374151,5px 0 0 #374151;
border-color: #374151;
}
.flatpickr-day.startRange {
background: #6574cd !important;
border-color: #6574cd !important;
}
.flatpickr-day.endRange {
background: #6574cd !important;
border-color: #6574cd !important;
}
.dark .flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)), .flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)), .flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
-webkit-box-shadow: -10px 0 0 #4556c3 !important;
box-shadow: -10px 0 0 #4556c3 !important;
}
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)), .flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)), .flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
-webkit-box-shadow: -10px 0 0 #4556c3 !important;
box-shadow: -10px 0 0 #4556c3 !important;
}

View File

@ -20,6 +20,11 @@
-webkit-animation: spin 1s ease-in-out infinite; -webkit-animation: spin 1s ease-in-out infinite;
} }
.dark .loading div {
border: 3px solid #606f7b;
border-top-color: #dae1e7;
}
.loading.sm div { .loading.sm div {
width: 25px; width: 25px;
height: 25px; height: 25px;

View File

@ -101,11 +101,11 @@ class DatePicker extends React.Component {
renderArrow(period, prevDate, nextDate) { renderArrow(period, prevDate, nextDate) {
return ( return (
<div className="flex rounded shadow bg-white mr-4 cursor-pointer"> <div className="flex rounded shadow bg-white dark:bg-gray-800 mr-4 cursor-pointer">
<QueryLink to={{date: prevDate}} query={this.props.query} className="flex items-center px-2 border-r border-gray-300"> <QueryLink to={{date: prevDate}} query={this.props.query} className="flex items-center px-2 border-r border-gray-300 dark:border-gray-500 dark:text-gray-100">
<svg className="feather h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg> <svg className="feather h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
</QueryLink> </QueryLink>
<QueryLink to={{date: nextDate}} query={this.props.query} className="flex items-center px-2"> <QueryLink to={{date: nextDate}} query={this.props.query} className="flex items-center px-2 dark:text-gray-100">
<svg className="feather h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg> <svg className="feather h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
</QueryLink> </QueryLink>
</div> </div>
@ -135,7 +135,7 @@ class DatePicker extends React.Component {
renderDropDown() { renderDropDown() {
return ( return (
<div className="relative" style={{height: '35.5px', width: '190px'}} ref={node => this.dropDownNode = node}> <div className="relative" style={{height: '35.5px', width: '190px'}} ref={node => this.dropDownNode = node}>
<div onClick={this.open.bind(this)} className="flex items-center justify-between rounded bg-white shadow px-4 pr-3 py-2 leading-tight cursor-pointer text-sm font-medium text-gray-800 h-full"> <div onClick={this.open.bind(this)} className="flex items-center justify-between rounded bg-white dark:bg-gray-800 shadow px-4 pr-3 py-2 leading-tight cursor-pointer text-sm font-medium text-gray-800 dark:text-gray-200 h-full">
<span className="mr-2">{this.timeFrameText()}</span> <span className="mr-2">{this.timeFrameText()}</span>
<svg className="text-pink-500 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg className="text-pink-500 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9"></polyline> <polyline points="6 9 12 15 18 9"></polyline>
@ -176,7 +176,7 @@ class DatePicker extends React.Component {
if (opts.date) { opts.date = formatISO(opts.date) } if (opts.date) { opts.date = formatISO(opts.date) }
return ( return (
<QueryLink to={{period, ...opts}} onClick={this.close.bind(this)} query={this.props.query} className={boldClass + ' block px-4 py-2 text-sm leading-tight hover:bg-gray-100 hover:text-gray-900'}> <QueryLink to={{period, ...opts}} onClick={this.close.bind(this)} query={this.props.query} className={boldClass + ' block px-4 py-2 text-sm leading-tight hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100'}>
{text} {text}
</QueryLink> </QueryLink>
) )
@ -186,29 +186,29 @@ class DatePicker extends React.Component {
if (this.state.mode === 'menu') { if (this.state.mode === 'menu') {
return ( return (
<div className="absolute mt-2 rounded shadow-md z-10" style={{width: '235px', right: '-14px'}}> <div className="absolute mt-2 rounded shadow-md z-10" style={{width: '235px', right: '-14px'}}>
<div className="rounded bg-white ring-1 ring-black ring-opacity-5 font-medium text-gray-800"> <div className="rounded bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 font-medium text-gray-800 dark:text-gray-200">
<div className="py-1"> <div className="py-1">
{ this.renderLink('day', 'Today') } { this.renderLink('day', 'Today') }
{ this.renderLink('realtime', 'Realtime') } { this.renderLink('realtime', 'Realtime') }
</div> </div>
<div className="border-t border-gray-200"></div> <div className="border-t border-gray-200 dark:border-gray-500"></div>
<div className="py-1"> <div className="py-1">
{ this.renderLink('7d', 'Last 7 days') } { this.renderLink('7d', 'Last 7 days') }
{ this.renderLink('30d', 'Last 30 days') } { this.renderLink('30d', 'Last 30 days') }
</div> </div>
<div className="border-t border-gray-200"></div> <div className="border-t border-gray-200 dark:border-gray-500"></div>
<div className="py-1"> <div className="py-1">
{ this.renderLink('month', 'This month') } { this.renderLink('month', 'This month') }
{ this.renderLink('month', 'Last month', {date: lastMonth(this.props.site)}) } { this.renderLink('month', 'Last month', {date: lastMonth(this.props.site)}) }
</div> </div>
<div className="border-t border-gray-200"></div> <div className="border-t border-gray-200 dark:border-gray-500"></div>
<div className="py-1"> <div className="py-1">
{ this.renderLink('6mo', 'Last 6 months') } { this.renderLink('6mo', 'Last 6 months') }
{ this.renderLink('12mo', 'Last 12 months') } { this.renderLink('12mo', 'Last 12 months') }
</div> </div>
<div className="border-t border-gray-200"></div> <div className="border-t border-gray-200 dark:border-gray-500"></div>
<div className="py-1"> <div className="py-1">
<span onClick={e => this.setState({mode: 'calendar'}, this.openCalendar.bind(this))} className="block px-4 py-2 text-sm leading-tight hover:bg-gray-100 hover:text-gray-900 cursor-pointer">Custom range</span> <span onClick={e => this.setState({mode: 'calendar'}, this.openCalendar.bind(this))} className="block px-4 py-2 text-sm leading-tight hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 cursor-pointer">Custom range</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -14,7 +14,7 @@ export default class ErrorBoundary extends React.Component {
render() { render() {
if (this.state.error) { if (this.state.error) {
return ( return (
<div className="text-center text-gray-900 mt-36"> <div className="text-center text-gray-900 dark:text-gray-100 mt-36">
<RocketIcon /> <RocketIcon />
<div className="text-lg font-bold">Oops! Something went wrong</div> <div className="text-lg font-bold">Oops! Something went wrong</div>
<div className="text-lg">{this.state.error.name + ': ' + this.state.error.message}</div> <div className="text-lg">{this.state.error.name + ': ' + this.state.error.message}</div>

View File

@ -68,7 +68,7 @@ function renderFilter(history, [key, value], query) {
} }
return ( return (
<span key={key} title={value} className="inline-flex bg-white text-gray-700 shadow text-sm rounded py-2 px-3 mr-4"> <span key={key} title={value} className="inline-flex bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 shadow text-sm rounded py-2 px-3 mr-4">
{filterText(key, value, query)} <b className="ml-1 cursor-pointer" onClick={removeFilter}></b> {filterText(key, value, query)} <b className="ml-1 cursor-pointer" onClick={removeFilter}></b>
</span> </span>
) )

View File

@ -5,7 +5,7 @@ import Historical from './historical'
import Realtime from './realtime' import Realtime from './realtime'
import {parseQuery} from './query' import {parseQuery} from './query'
import * as api from './api' import * as api from './api'
import { withThemeProvider } from './theme-provider-hoc';
const THIRTY_SECONDS = 30000 const THIRTY_SECONDS = 30000
@ -51,4 +51,4 @@ class Dashboard extends React.Component {
} }
} }
export default withRouter(Dashboard) export default withRouter(withThemeProvider(Dashboard))

View File

@ -45,9 +45,9 @@ export default class SiteSwitcher extends React.Component {
} }
renderSiteLink(domain) { renderSiteLink(domain) {
const extraClass = domain === this.props.site.domain ? 'font-medium text-gray-900' : 'hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900' const extraClass = domain === this.props.site.domain ? 'font-medium text-gray-900 dark:text-gray-100' : 'hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-900 dark:focus:text-gray-100'
return ( return (
<a href={`/${encodeURIComponent(domain)}`} key={domain} className={`block truncate px-4 py-2 text-sm leading-5 text-gray-700 ${extraClass}`}> <a href={`/${encodeURIComponent(domain)}`} key={domain} className={`block truncate px-4 py-2 text-sm leading-5 text-gray-700 dark:text-gray-300 ${extraClass}`}>
<img src={`https://icons.duckduckgo.com/ip3/${domain}.ico`} referrerPolicy="no-referrer" className="inline w-4 mr-2 align-middle" /> <img src={`https://icons.duckduckgo.com/ip3/${domain}.ico`} referrerPolicy="no-referrer" className="inline w-4 mr-2 align-middle" />
<span>{domain}</span> <span>{domain}</span>
</a> </a>
@ -58,17 +58,17 @@ export default class SiteSwitcher extends React.Component {
if (this.state.loading) { if (this.state.loading) {
return <div className="px-4 py-6"><div className="loading sm mx-auto"><div></div></div></div> return <div className="px-4 py-6"><div className="loading sm mx-auto"><div></div></div></div>
} else if (this.state.error) { } else if (this.state.error) {
return <div className="mx-auto px-4 py-6">Something went wrong, try again</div> return <div className="mx-auto px-4 py-6 dark:text-gray-100">Something went wrong, try again</div>
} else { } else {
return ( return (
<React.Fragment> <React.Fragment>
<div className="py-1"> <div className="py-1">
<a href={`/${encodeURIComponent(this.props.site.domain)}/settings`} className="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem"> <a href={`/${encodeURIComponent(this.props.site.domain)}/settings`} className="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-900 dark:focus:text-gray-100" role="menuitem">
<svg viewBox="0 0 20 20" fill="currentColor" className="mr-2 h-4 w-4 text-gray-500 group-hover:text-gray-600 group-focus:text-gray-500"><path d="M5 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4zM11 4a1 1 0 10-2 0v1.268a2 2 0 000 3.464V16a1 1 0 102 0V8.732a2 2 0 000-3.464V4zM16 3a1 1 0 011 1v7.268a2 2 0 010 3.464V16a1 1 0 11-2 0v-1.268a2 2 0 010-3.464V4a1 1 0 011-1z" /></svg> <svg viewBox="0 0 20 20" fill="currentColor" className="mr-2 h-4 w-4 text-gray-500 dark:text-gray-200 group-hover:text-gray-600 dark:group-hover:text-gray-400 group-focus:text-gray-500 dark:group-focus:text-gray-200"><path d="M5 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4zM11 4a1 1 0 10-2 0v1.268a2 2 0 000 3.464V16a1 1 0 102 0V8.732a2 2 0 000-3.464V4zM16 3a1 1 0 011 1v7.268a2 2 0 010 3.464V16a1 1 0 11-2 0v-1.268a2 2 0 010-3.464V4a1 1 0 011-1z" /></svg>
Site settings Site settings
</a> </a>
</div> </div>
<div className="border-t border-gray-100"></div> <div className="border-t border-gray-100 dark:border-gray-900"></div>
<div className="py-1"> <div className="py-1">
{ this.state.sites.map(this.renderSiteLink.bind(this)) } { this.state.sites.map(this.renderSiteLink.bind(this)) }
</div> </div>
@ -88,11 +88,11 @@ export default class SiteSwitcher extends React.Component {
} }
render() { render() {
const hoverClass = this.props.loggedIn ? 'hover:text-gray-500 focus:border-blue-300 focus:ring ' : 'cursor-default' const hoverClass = this.props.loggedIn ? 'hover:text-gray-500 dark:hover:text-gray-200 focus:border-blue-300 focus:ring ' : 'cursor-default'
return ( return (
<div className="relative inline-block text-left z-10 mr-8"> <div className="relative inline-block text-left z-10 mr-8">
<button onClick={this.toggle.bind(this)} className={`inline-flex items-center text-lg w-full rounded-md py-2 leading-5 font-bold text-gray-700 focus:outline-none transition ease-in-out duration-150 ${hoverClass}`}> <button onClick={this.toggle.bind(this)} className={`inline-flex items-center text-lg w-full rounded-md py-2 leading-5 font-bold text-gray-700 dark:text-gray-300 focus:outline-none transition ease-in-out duration-150 ${hoverClass}`}>
<img src={`https://icons.duckduckgo.com/ip3/${this.props.site.domain}.ico`} referrerPolicy="no-referrer" className="inline w-4 mr-2 align-middle" /> <img src={`https://icons.duckduckgo.com/ip3/${this.props.site.domain}.ico`} referrerPolicy="no-referrer" className="inline w-4 mr-2 align-middle" />
{this.props.site.domain} {this.props.site.domain}
@ -109,7 +109,7 @@ export default class SiteSwitcher extends React.Component {
leaveTo="opacity-0 scale-95" leaveTo="opacity-0 scale-95"
> >
<div className="origin-top-left absolute left-0 mt-2 w-64 rounded-md shadow-lg" ref={node => this.dropDownNode = node} > <div className="origin-top-left absolute left-0 mt-2 w-64 rounded-md shadow-lg" ref={node => this.dropDownNode = node} >
<div className="rounded-md bg-white ring-1 ring-black ring-opacity-5"> <div className="rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5">
{ this.renderDropdown() } { this.renderDropdown() }
</div> </div>
</div> </div>

View File

@ -50,11 +50,11 @@ export default class Conversions extends React.Component {
return ( return (
<div className="my-2 text-sm" key={goal.name}> <div className="my-2 text-sm" key={goal.name}>
<div className="flex items-center justify-between my-2"> <div className="flex items-center justify-between my-2">
<div className="w-full h-8 relative" style={{maxWidth: 'calc(100% - 16rem)'}}> <div className="w-full h-8 relative dark:text-gray-300" style={{maxWidth: 'calc(100% - 16rem)'}}>
<Bar count={goal.count} all={this.state.goals} bg="bg-red-50" /> <Bar count={goal.count} all={this.state.goals} bg="bg-red-50 dark:bg-gray-500 dark:bg-opacity-15" />
{this.renderGoalText(goal.name)} {this.renderGoalText(goal.name)}
</div> </div>
<div> <div className="dark:text-gray-200">
<span className="font-medium inline-block w-20 text-right">{numberFormatter(goal.count)}</span> <span className="font-medium inline-block w-20 text-right">{numberFormatter(goal.count)}</span>
<span className="font-medium inline-block w-20 text-right">{numberFormatter(goal.total_count)}</span> <span className="font-medium inline-block w-20 text-right">{numberFormatter(goal.total_count)}</span>
<span className="font-medium inline-block w-20 text-right">{goal.conversion_rate}%</span> <span className="font-medium inline-block w-20 text-right">{goal.conversion_rate}%</span>
@ -68,15 +68,15 @@ export default class Conversions extends React.Component {
render() { render() {
if (this.state.loading) { if (this.state.loading) {
return ( return (
<div className="w-full bg-white shadow-xl rounded p-4" style={{height: '94px'}}> <div className="w-full bg-white dark:bg-gray-825 shadow-xl rounded p-4" style={{height: '94px'}}>
<div className="loading my-2 mx-auto"><div></div></div> <div className="loading my-2 mx-auto"><div></div></div>
</div> </div>
) )
} else if (this.state.goals) { } else if (this.state.goals) {
return ( return (
<div className="w-full bg-white shadow-xl rounded p-4"> <div className="w-full bg-white dark:bg-gray-825 shadow-xl rounded p-4">
<h3 className="font-bold">{this.props.title || "Goal Conversions"}</h3> <h3 className="font-bold dark:text-gray-100">{this.props.title || "Goal Conversions"}</h3>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 text-xs font-bold tracking-wide"> <div className="flex items-center mt-3 mb-2 justify-between text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide">
<span>Goal</span> <span>Goal</span>
<div className="text-right"> <div className="text-right">
<span className="inline-block w-20">Uniques</span> <span className="inline-block w-20">Uniques</span>

View File

@ -39,7 +39,7 @@ export default class PropertyBreakdown extends React.Component {
if (value.is_url) { if (value.is_url) {
return ( return (
<a target="_blank" href={value.name} className="hidden group-hover:block"> <a target="_blank" href={value.name} className="hidden group-hover:block">
<svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg> <svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg>
</a> </a>
) )
} }
@ -52,15 +52,15 @@ export default class PropertyBreakdown extends React.Component {
return ( return (
<div className="flex items-center justify-between my-2" key={value.name}> <div className="flex items-center justify-between my-2" key={value.name}>
<div className="w-full h-8 relative" style={{maxWidth: 'calc(100% - 16rem)'}}> <div className="w-full h-8 relative" style={{maxWidth: 'calc(100% - 16rem)'}}>
<Bar count={value.count} all={this.state.breakdown} bg="bg-red-50" /> <Bar count={value.count} all={this.state.breakdown} bg="bg-red-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2 group" style={{marginTop: '-26px'}}> <span className="flex px-2 group dark:text-gray-300" style={{marginTop: '-26px'}}>
<Link to={{pathname: window.location.pathname, search: query.toString()}} className="hover:underline block truncate"> <Link to={{pathname: window.location.pathname, search: query.toString()}} className="hover:underline block truncate">
{ value.name } { value.name }
</Link> </Link>
{ this.renderUrl(value) } { this.renderUrl(value) }
</span> </span>
</div> </div>
<div> <div className="dark:text-gray-200">
<span className="font-medium inline-block w-20 text-right">{numberFormatter(value.count)}</span> <span className="font-medium inline-block w-20 text-right">{numberFormatter(value.count)}</span>
<span className="font-medium inline-block w-20 text-right">{numberFormatter(value.total_count)}</span> <span className="font-medium inline-block w-20 text-right">{numberFormatter(value.total_count)}</span>
<span className="font-medium inline-block w-20 text-right">{numberFormatter(value.conversion_rate)}%</span> <span className="font-medium inline-block w-20 text-right">{numberFormatter(value.conversion_rate)}%</span>
@ -86,9 +86,9 @@ export default class PropertyBreakdown extends React.Component {
const isActive = this.state.propKey === key const isActive = this.state.propKey === key
if (isActive) { if (isActive) {
return <li key={key} className="inline-block h-5 text-indigo-700 font-bold border-b-2 border-indigo-700">{key}</li> return <li key={key} className="inline-block h-5 text-indigo-700 dark:text-indigo-500 font-bold border-b-2 border-indigo-700 dark:border-indigo-500 ">{key}</li>
} else { } else {
return <li key={key} className="hover:text-indigo-700 cursor-pointer" onClick={this.changePropKey.bind(this, key)}>{key}</li> return <li key={key} className="hover:text-indigo-600 cursor-pointer" onClick={this.changePropKey.bind(this, key)}>{key}</li>
} }
} }
@ -96,8 +96,8 @@ export default class PropertyBreakdown extends React.Component {
return ( return (
<div className="w-full pl-6 mt-4"> <div className="w-full pl-6 mt-4">
<div className="flex items-center pb-1"> <div className="flex items-center pb-1">
<span className="text-xs font-bold text-gray-600">Breakdown by:</span> <span className="text-xs font-bold text-gray-600 dark:text-gray-300">Breakdown by:</span>
<ul className="flex font-medium text-xs text-gray-500 space-x-2 leading-5 pl-1"> <ul className="flex font-medium text-xs text-gray-500 dark:text-gray-400 space-x-2 leading-5 pl-1">
{ this.props.goal.prop_names.map(this.renderPill.bind(this)) } { this.props.goal.prop_names.map(this.renderPill.bind(this)) }
</ul> </ul>
</div> </div>

View File

@ -8,11 +8,13 @@ import Bar from './bar'
import MoreLink from './more-link' import MoreLink from './more-link'
import * as api from '../api' import * as api from '../api'
import { navigateToQuery } from '../query' import { navigateToQuery } from '../query'
import { withThemeConsumer } from '../theme-consumer-hoc';
class Countries extends React.Component { class Countries extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.resizeMap = this.resizeMap.bind(this) this.resizeMap = this.resizeMap.bind(this)
this.drawMap = this.drawMap.bind(this)
this.getDataset = this.getDataset.bind(this)
this.state = {loading: true} this.state = {loading: true}
} }
@ -31,6 +33,14 @@ class Countries extends React.Component {
this.setState({loading: true, countries: null}) this.setState({loading: true, countries: null})
this.fetchCountries().then(this.drawMap.bind(this)) this.fetchCountries().then(this.drawMap.bind(this))
} }
if (this.props.darkTheme !== prevProps.darkTheme) {
if (document.getElementById('map-container')) {
document.getElementById('map-container').removeChild(document.querySelector('.datamaps-hoverover'));
document.getElementById('map-container').removeChild(document.querySelector('.datamap'));
}
this.drawMap();
}
} }
getDataset() { getDataset() {
@ -41,7 +51,10 @@ class Countries extends React.Component {
var paletteScale = d3.scale.linear() var paletteScale = d3.scale.linear()
.domain([0,maxValue]) .domain([0,maxValue])
.range(["#f3ebff","#a779e9"]); .range([
this.props.darkTheme ? "#2e3954" : "#f3ebff",
this.props.darkTheme ? "#6366f1" : "#a779e9"
]);
this.state.countries.forEach(function(item){ this.state.countries.forEach(function(item){
dataset[item.name] = {numberOfThings: item.count, fillColor: paletteScale(item.count)}; dataset[item.name] = {numberOfThings: item.count, fillColor: paletteScale(item.count)};
@ -68,26 +81,30 @@ class Countries extends React.Component {
drawMap() { drawMap() {
var dataset = this.getDataset(); var dataset = this.getDataset();
const label = this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors' const label = this.props.query.period === 'realtime' ? 'Current visitors' : 'Visitors'
const defaultFill = this.props.darkTheme ? '#2d3747' : '#f8fafc'
const highlightFill = this.props.darkTheme ? '#374151' : '#F5F5F5'
const borderColor = this.props.darkTheme ? '#1f2937' : '#dae1e7'
const highlightBorderColor = this.props.darkTheme ? '#4f46e5' : '#a779e9'
this.map = new Datamap({ this.map = new Datamap({
element: document.getElementById('map-container'), element: document.getElementById('map-container'),
responsive: true, responsive: true,
projection: 'mercator', projection: 'mercator',
fills: { defaultFill: '#f8fafc' }, fills: { defaultFill },
data: dataset, data: dataset,
geographyConfig: { geographyConfig: {
borderColor: '#dae1e7', borderColor,
highlightBorderWidth: 2, highlightBorderWidth: 2,
highlightFillColor: function(geo) { highlightFillColor: function(geo) {
return geo['fillColor'] || '#F5F5F5'; return geo['fillColor'] || highlightFill;
}, },
highlightBorderColor: '#a779e9', highlightBorderColor,
popupTemplate: function(geo, data) { popupTemplate: function(geo, data) {
if (!data) { return ; } if (!data) { return ; }
const pluralizedLabel = data.numberOfThings === 1 ? label.slice(0, -1) : label const pluralizedLabel = data.numberOfThings === 1 ? label.slice(0, -1) : label
return ['<div class="hoverinfo">', return ['<div class="hoverinfo dark:bg-gray-800 dark:shadow-gray-850 dark:border-gray-850 dark:text-gray-200">',
'<strong>', geo.properties.name, '</strong>', '<strong>', geo.properties.name, '</strong>',
'<br><strong>', numberFormatter(data.numberOfThings), '</strong> ' + pluralizedLabel, '<br><strong class="dark:text-indigo-400">', numberFormatter(data.numberOfThings), '</strong> ' + pluralizedLabel,
'</div>'].join(''); '</div>'].join('');
} }
}, },
@ -109,7 +126,7 @@ class Countries extends React.Component {
if (this.state.countries) { if (this.state.countries) {
return ( return (
<React.Fragment> <React.Fragment>
<h3 className="font-bold">Countries</h3> <h3 className="font-bold dark:text-gray-100">Countries</h3>
<div className="mt-6 mx-auto" style={{width: '100%', maxWidth: '475px', height: '320px'}} id="map-container"></div> <div className="mt-6 mx-auto" style={{width: '100%', maxWidth: '475px', height: '320px'}} id="map-container"></div>
<MoreLink site={this.props.site} list={this.state.countries} endpoint="countries" /> <MoreLink site={this.props.site} list={this.state.countries} endpoint="countries" />
</React.Fragment> </React.Fragment>
@ -119,7 +136,7 @@ class Countries extends React.Component {
render() { render() {
return ( return (
<div className="stats-item relative bg-white shadow-xl rounded p-4" style={{height: '436px'}}> <div className="stats-item relative bg-white dark:bg-gray-825 shadow-xl rounded p-4" style={{height: '436px'}}>
{ this.state.loading && <div className="loading my-32 mx-auto"><div></div></div> } { this.state.loading && <div className="loading my-32 mx-auto"><div></div></div> }
<FadeIn show={!this.state.loading}> <FadeIn show={!this.state.loading}>
{ this.renderBody() } { this.renderBody() }
@ -129,4 +146,4 @@ class Countries extends React.Component {
} }
} }
export default withRouter(Countries) export default withRouter(withThemeConsumer(Countries))

View File

@ -25,7 +25,7 @@ export default class CurrentVisitors extends React.Component {
const { currentVisitors } = this.state; const { currentVisitors } = this.state;
if (currentVisitors !== null) { if (currentVisitors !== null) {
return ( return (
<Link to={`/${encodeURIComponent(this.props.site.domain)}?period=realtime`} className="block text-sm font-bold text-gray-500"> <Link to={`/${encodeURIComponent(this.props.site.domain)}?period=realtime`} className="block text-sm font-bold text-gray-500 dark:text-gray-300">
<svg className="w-2 mr-2 fill-current text-green-500 inline" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <svg className="w-2 mr-2 fill-current text-green-500 inline" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8"/> <circle cx="8" cy="8" r="8"/>
</svg> </svg>

View File

@ -45,14 +45,14 @@ export default class Browsers extends React.Component {
return ( return (
<div className="flex items-center justify-between my-1 text-sm" key={browser.name}> <div className="flex items-center justify-between my-1 text-sm" key={browser.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}> <div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}>
<Bar count={browser.count} all={this.state.browsers} bg="bg-green-50" /> <Bar count={browser.count} all={this.state.browsers} bg="bg-green-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2" style={{marginTop: '-26px'}} > <span className="flex px-2 dark:text-gray-300" style={{marginTop: '-26px'}} >
<Link className="block truncate hover:underline" to={{search: query.toString()}}> <Link className="block truncate hover:underline" to={{search: query.toString()}}>
{browser.name} {browser.name}
</Link> </Link>
</span> </span>
</div> </div>
<span className="font-medium">{numberFormatter(browser.count)} <span className="inline-block text-xs w-8 text-right">({browser.percentage}%)</span></span> <span className="font-medium dark:text-gray-200">{numberFormatter(browser.count)} <span className="inline-block text-xs w-8 text-right">({browser.percentage}%)</span></span>
</div> </div>
) )
} }
@ -68,7 +68,7 @@ export default class Browsers extends React.Component {
if (this.state.browsers && this.state.browsers.length > 0) { if (this.state.browsers && this.state.browsers.length > 0) {
return ( return (
<React.Fragment> <React.Fragment>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 text-xs font-bold tracking-wide"> <div className="flex items-center mt-3 mb-2 justify-between text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide">
<span>{ key }</span> <span>{ key }</span>
<span>{ this.label() }</span> <span>{ this.label() }</span>
</div> </div>
@ -76,7 +76,7 @@ export default class Browsers extends React.Component {
</React.Fragment> </React.Fragment>
) )
} else { } else {
return <div className="text-center mt-44 font-medium text-gray-500">No data yet</div> return <div className="text-center mt-44 font-medium text-gray-500 dark:text-gray-400">No data yet</div>
} }
} }

View File

@ -67,14 +67,14 @@ class ScreenSizes extends React.Component {
return ( return (
<div className="flex items-center justify-between my-1 text-sm" key={size.name}> <div className="flex items-center justify-between my-1 text-sm" key={size.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}> <div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}>
<Bar count={size.count} all={this.state.sizes} bg="bg-green-50" /> <Bar count={size.count} all={this.state.sizes} bg="bg-green-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span tooltip={EXPLANATION[size.name]} className="flex px-2" style={{marginTop: '-26px'}} > <span tooltip={EXPLANATION[size.name]} className="flex px-2 dark:text-gray-300" style={{marginTop: '-26px'}} >
<Link className="block truncate hover:underline" to={{search: query.toString()}}> <Link className="block truncate hover:underline" to={{search: query.toString()}}>
{iconFor(size.name)} {size.name} {iconFor(size.name)} {size.name}
</Link> </Link>
</span> </span>
</div> </div>
<span className="font-medium">{numberFormatter(size.count)} <span className="inline-block text-xs w-8 text-right">({size.percentage}%)</span></span> <span className="font-medium dark:text-gray-200">{numberFormatter(size.count)} <span className="inline-block text-xs w-8 text-right">({size.percentage}%)</span></span>
</div> </div>
) )
} }
@ -95,7 +95,7 @@ class ScreenSizes extends React.Component {
</React.Fragment> </React.Fragment>
) )
} else { } else {
return <div className="text-center mt-44 font-medium text-gray-500">No data yet</div> return <div className="text-center mt-44 font-medium text-gray-500 dark:text-gray-400">No data yet</div>
} }
} }
@ -142,21 +142,21 @@ export default class Devices extends React.Component {
const isActive = this.state.mode === mode const isActive = this.state.mode === mode
if (isActive) { if (isActive) {
return <li className="inline-block h-5 text-indigo-700 font-bold border-b-2 border-indigo-700">{name}</li> return <li className="inline-block h-5 text-indigo-700 dark:text-indigo-500 font-bold border-b-2 border-indigo-700 dark:border-indigo-500">{name}</li>
} else { } else {
return <li className="hover:text-indigo-700 cursor-pointer" onClick={this.setMode(mode)}>{name}</li> return <li className="hover:text-indigo-600 cursor-pointer" onClick={this.setMode(mode)}>{name}</li>
} }
} }
render() { render() {
return ( return (
<div className="stats-item"> <div className="stats-item">
<div className="bg-white shadow-xl rounded p-4 relative" style={{height: '436px'}}> <div className="bg-white dark:bg-gray-825 shadow-xl rounded p-4 relative" style={{height: '436px'}}>
<div className="w-full flex justify-between"> <div className="w-full flex justify-between">
<h3 className="font-bold">Devices</h3> <h3 className="font-bold dark:text-gray-100">Devices</h3>
<ul className="flex font-medium text-xs text-gray-500 space-x-2"> <ul className="flex font-medium text-xs text-gray-500 dark:text-gray-400 space-x-2">
{ this.renderPill('Size', 'size') } { this.renderPill('Size', 'size') }
{ this.renderPill('Browser', 'browser') } { this.renderPill('Browser', 'browser') }
{ this.renderPill('OS', 'os') } { this.renderPill('OS', 'os') }

View File

@ -45,14 +45,14 @@ export default class OperatingSystems extends React.Component {
return ( return (
<div className="flex items-center justify-between my-1 text-sm" key={os.name}> <div className="flex items-center justify-between my-1 text-sm" key={os.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}> <div className="w-full h-8" style={{maxWidth: 'calc(100% - 6rem)'}}>
<Bar count={os.count} all={this.state.operatingSystems} bg="bg-green-50" /> <Bar count={os.count} all={this.state.operatingSystems} bg="bg-green-50 dark:gray-500 dark:bg-opacity-15" />
<span className="flex px-2" style={{marginTop: '-26px'}}> <span className="flex px-2 dark:text-gray-300" style={{marginTop: '-26px'}}>
<Link className="block truncate hover:underline" to={{search: query.toString()}}> <Link className="block truncate hover:underline" to={{search: query.toString()}}>
{os.name} {os.name}
</Link> </Link>
</span> </span>
</div> </div>
<span className="font-medium">{numberFormatter(os.count)} <span className="inline-block text-xs w-8 text-right">({os.percentage}%)</span></span> <span className="font-medium dark:text-gray-200">{numberFormatter(os.count)} <span className="inline-block text-xs w-8 text-right">({os.percentage}%)</span></span>
</div> </div>
) )
} }
@ -67,7 +67,7 @@ export default class OperatingSystems extends React.Component {
if (this.state.operatingSystems && this.state.operatingSystems.length > 0) { if (this.state.operatingSystems && this.state.operatingSystems.length > 0) {
return ( return (
<React.Fragment> <React.Fragment>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 text-xs font-bold tracking-wide"> <div className="flex items-center mt-3 mb-2 justify-between text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide">
<span>{ key }</span> <span>{ key }</span>
<span>{ this.label() }</span> <span>{ this.label() }</span>
</div> </div>
@ -75,7 +75,7 @@ export default class OperatingSystems extends React.Component {
</React.Fragment> </React.Fragment>
) )
} else { } else {
return <div className="text-center mt-44 font-medium text-gray-500">No data yet</div> return <div className="text-center mt-44 font-medium text-gray-500 dark:text-gray-400">No data yet</div>
} }
} }

View File

@ -26,7 +26,7 @@ class CountriesModal extends React.Component {
query.set('country', country.name) query.set('country', country.name)
return ( return (
<tr className="text-sm" key={country.name}> <tr className="text-sm dark:text-gray-200" key={country.name}>
<td className="p-2"> <td className="p-2">
<Link className="hover:underline" to={{search: query.toString(), pathname: '/' + encodeURIComponent(this.props.site.domain)}}> <Link className="hover:underline" to={{search: query.toString(), pathname: '/' + encodeURIComponent(this.props.site.domain)}}>
{country.full_country_name} {country.full_country_name}
@ -51,15 +51,15 @@ class CountriesModal extends React.Component {
} else if (this.state.countries) { } else if (this.state.countries) {
return ( return (
<React.Fragment> <React.Fragment>
<h1 className="text-xl font-bold">Top countries</h1> <h1 className="text-xl font-bold dark:text-gray-100">Top countries</h1>
<div className="my-4 border-b border-gray-300"></div> <div className="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<main className="modal__content"> <main className="modal__content">
<table className="w-full table-striped table-fixed"> <table className="w-full table-striped table-fixed">
<thead> <thead>
<tr> <tr>
<th className="p-2 text-xs tracking-wide font-bold text-gray-500" align="left">Country</th> <th className="p-2 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="left">Country</th>
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">{this.label()}</th> <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">{this.label()}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -43,7 +43,7 @@ class GoogleKeywordsModal extends React.Component {
return ( return (
<React.Fragment key={term.name}> <React.Fragment key={term.name}>
<tr className="text-sm" key={term.name}> <tr className="text-sm dark:text-gray-200" key={term.name}>
<td className="p-2 truncate">{term.name}</td> <td className="p-2 truncate">{term.name}</td>
<td className="p-2 w-32 font-medium" align="right">{numberFormatter(term.count)}</td> <td className="p-2 w-32 font-medium" align="right">{numberFormatter(term.count)}</td>
</tr> </tr>
@ -54,7 +54,7 @@ class GoogleKeywordsModal extends React.Component {
renderKeywords() { renderKeywords() {
if (this.state.query.filters.goal) { if (this.state.query.filters.goal) {
return ( return (
<div className="text-center text-gray-700 mt-6"> <div className="text-center text-gray-700 dark:text-gray-300 mt-6">
<RocketIcon /> <RocketIcon />
<div className="text-lg">Sorry, we cannot show which keywords converted best for goal <b>{this.state.query.filters.goal}</b></div> <div className="text-lg">Sorry, we cannot show which keywords converted best for goal <b>{this.state.query.filters.goal}</b></div>
<div className="text-lg">Google does not share this information</div> <div className="text-lg">Google does not share this information</div>
@ -63,7 +63,7 @@ class GoogleKeywordsModal extends React.Component {
} else if (this.state.notConfigured) { } else if (this.state.notConfigured) {
if (this.state.isOwner) { if (this.state.isOwner) {
return ( return (
<div className="text-center text-gray-700 mt-6"> <div className="text-center text-gray-700 dark:text-gray-300 mt-6">
<RocketIcon /> <RocketIcon />
<div className="text-lg">The site is not connected to Google Search Keywords</div> <div className="text-lg">The site is not connected to Google Search Keywords</div>
<div className="text-lg">Configure the integration to view search terms</div> <div className="text-lg">Configure the integration to view search terms</div>
@ -72,7 +72,7 @@ class GoogleKeywordsModal extends React.Component {
) )
} else { } else {
return ( return (
<div className="text-center text-gray-700 mt-6"> <div className="text-center text-gray-700 dark:text-gray-300 mt-6">
<RocketIcon /> <RocketIcon />
<div className="text-lg">The site is not connected to Google Search Kewyords</div> <div className="text-lg">The site is not connected to Google Search Kewyords</div>
<div className="text-lg">Cannot show search terms</div> <div className="text-lg">Cannot show search terms</div>
@ -84,8 +84,8 @@ class GoogleKeywordsModal extends React.Component {
<table className="w-full table-striped table-fixed"> <table className="w-full table-striped table-fixed">
<thead> <thead>
<tr> <tr>
<th className="p-2 text-xs tracking-wide font-bold text-gray-500" align="left">Search Term</th> <th className="p-2 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="left">Search Term</th>
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">Visitors</th> <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Visitors</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -95,7 +95,7 @@ class GoogleKeywordsModal extends React.Component {
) )
} else { } else {
return ( return (
<div className="text-center text-gray-700 mt-6"> <div className="text-center text-gray-700 dark:text-gray-300 mt-6">
<RocketIcon /> <RocketIcon />
<div className="text-lg">Could not find any search terms for this period</div> <div className="text-lg">Could not find any search terms for this period</div>
</div> </div>
@ -106,7 +106,7 @@ class GoogleKeywordsModal extends React.Component {
renderGoalText() { renderGoalText() {
if (this.state.query.filters.goal) { if (this.state.query.filters.goal) {
return ( return (
<h1 className="text-xl font-semibold text-gray-500 leading-none">completed {this.state.query.filters.goal}</h1> <h1 className="text-xl font-semibold text-gray-500 dark:text-gray-200 leading-none">completed {this.state.query.filters.goal}</h1>
) )
} }
} }
@ -119,11 +119,11 @@ class GoogleKeywordsModal extends React.Component {
} else { } else {
return ( return (
<React.Fragment> <React.Fragment>
<Link to={`/${encodeURIComponent(this.props.site.domain)}/referrers${window.location.search}`} className="font-bold text-gray-700 hover:underline"> All referrers</Link> <Link to={`/${encodeURIComponent(this.props.site.domain)}/referrers${window.location.search}`} className="font-bold text-gray-700 dark:text-gray-200 hover:underline"> All referrers</Link>
<div className="my-4 border-b border-gray-300"></div> <div className="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<main className="modal__content"> <main className="modal__content">
<h1 className="text-xl font-semibold mb-0 leading-none"> <h1 className="text-xl font-semibold mb-0 leading-none dark:text-gray-200">
{this.state.totalVisitors} visitors from Google<br /> {this.state.totalVisitors} visitors from Google<br />
{toHuman(this.state.query)} {toHuman(this.state.query)}
</h1> </h1>

View File

@ -47,7 +47,7 @@ class Modal extends React.Component {
<div className="modal is-open" onClick={this.props.onClick}> <div className="modal is-open" onClick={this.props.onClick}>
<div className="modal__overlay"> <div className="modal__overlay">
<button className="modal__close"></button> <button className="modal__close"></button>
<div ref={this.node} className="modal__container"> <div ref={this.node} className="modal__container dark:bg-gray-800">
{this.props.children} {this.props.children}
</div> </div>

View File

@ -51,7 +51,7 @@ class PagesModal extends React.Component {
query.set('page', page.name) query.set('page', page.name)
return ( return (
<tr className="text-sm" key={page.name}> <tr className="text-sm dark:text-gray-200" key={page.name}>
<td className="p-2"> <td className="p-2">
<Link to={{pathname: `/${encodeURIComponent(this.props.site.domain)}`, search: query.toString()}} className="hover:underline">{page.name}</Link> <Link to={{pathname: `/${encodeURIComponent(this.props.site.domain)}`, search: query.toString()}} className="hover:underline">{page.name}</Link>
</td> </td>
@ -79,17 +79,17 @@ class PagesModal extends React.Component {
} else if (this.state.pages) { } else if (this.state.pages) {
return ( return (
<React.Fragment> <React.Fragment>
<h1 className="text-xl font-bold">{this.title()}</h1> <h1 className="text-xl font-bold dark:text-gray-100">{this.title()}</h1>
<div className="my-4 border-b border-gray-300"></div> <div className="my-4 border-b border-gray-300"></div>
<main className="modal__content"> <main className="modal__content">
<table className="w-full table-striped table-fixed"> <table className="w-full table-striped table-fixed">
<thead> <thead>
<tr> <tr>
<th className="p-2 text-xs tracking-wide font-bold text-gray-500" align="left">Page url</th> <th className="p-2 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="left">Page url</th>
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">{ this.label() }</th> <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">{ this.label() }</th>
{this.showPageviews() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">Pageviews</th>} {this.showPageviews() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Pageviews</th>}
{this.showBounceRate() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">Bounce rate</th>} {this.showBounceRate() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Bounce rate</th>}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -47,7 +47,7 @@ class ReferrerDrilldownModal extends React.Component {
if (name !== 'Direct / None') { if (name !== 'Direct / None') {
return ( return (
<a target="_blank" href={'//' + name} className="hidden group-hover:block"> <a target="_blank" href={'//' + name} className="hidden group-hover:block">
<svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg> <svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg>
</a> </a>
) )
} }
@ -60,7 +60,7 @@ class ReferrerDrilldownModal extends React.Component {
return ( return (
<span className="flex group items-center"> <span className="flex group items-center">
<img src={`https://icons.duckduckgo.com/ip3/${referrer.url}.ico`} referrerPolicy="no-referrer" className="h-4 w-4 mr-2 inline" /> <img src={`https://icons.duckduckgo.com/ip3/${referrer.url}.ico`} referrerPolicy="no-referrer" className="h-4 w-4 mr-2 inline" />
<Link className="block truncate hover:underline" to={{search: query.toString(), pathname: '/' + this.props.site.domain}} title={referrer.name}> <Link className="block truncate hover:underline dark:text-gray-200" to={{search: query.toString(), pathname: '/' + this.props.site.domain}} title={referrer.name}>
{referrer.name} {referrer.name}
</Link> </Link>
{ this.renderExternalLink(name) } { this.renderExternalLink(name) }
@ -71,7 +71,7 @@ class ReferrerDrilldownModal extends React.Component {
renderTweet(tweet, index) { renderTweet(tweet, index) {
const authorUrl = `https://twitter.com/${tweet.author_handle}` const authorUrl = `https://twitter.com/${tweet.author_handle}`
const tweetUrl = `${authorUrl}/status/${tweet.tweet_id}` const tweetUrl = `${authorUrl}/status/${tweet.tweet_id}`
const border = index === 0 ? '' : ' pt-4 border-t border-gray-300' const border = index === 0 ? '' : ' pt-4 border-t border-gray-300 dark:border-gray-500'
return ( return (
<div key={tweet.tweet_id}> <div key={tweet.tweet_id}>
@ -80,14 +80,14 @@ class ReferrerDrilldownModal extends React.Component {
<img className="rounded-full w-8" src={tweet.author_image} /> <img className="rounded-full w-8" src={tweet.author_image} />
<div className="ml-2 leading-tight"> <div className="ml-2 leading-tight">
<div className="font-bold group-hover:text-blue-500">{tweet.author_name}</div> <div className="font-bold group-hover:text-blue-500">{tweet.author_name}</div>
<div className="text-xs text-gray-500">@{tweet.author_handle}</div> <div className="text-xs text-gray-500 dark:text-gray-400">@{tweet.author_handle}</div>
</div> </div>
</a> </a>
<a className="ml-auto twitter-icon" href={tweetUrl} target="_blank"></a> <a className="ml-auto twitter-icon" href={tweetUrl} target="_blank"></a>
</div> </div>
<div className="my-2 cursor-text tweet-text whitespace-pre-wrap" dangerouslySetInnerHTML={{__html: tweet.text}}> <div className="my-2 cursor-text tweet-text whitespace-pre-wrap" dangerouslySetInnerHTML={{__html: tweet.text}}>
</div> </div>
<div className="text-xs text-gray-700 font-medium"> <div className="text-xs text-gray-700 dark:text-gray-300 font-medium">
{formatFullDate(new Date(tweet.created))} {formatFullDate(new Date(tweet.created))}
</div> </div>
</div> </div>
@ -97,13 +97,13 @@ class ReferrerDrilldownModal extends React.Component {
renderReferrer(referrer) { renderReferrer(referrer) {
if (referrer.tweets) { if (referrer.tweets) {
return ( return (
<tr className="text-sm" key={referrer.name}> <tr className="text-sm dark:text-gray-200" key={referrer.name}>
<td className="p-2"> <td className="p-2">
{ this.renderReferrerName(referrer) } { this.renderReferrerName(referrer) }
<span className="text-gray-500 ml-2 text-xs"> <span className="text-gray-500 ml-2 text-xs">
appears in {referrer.tweets.length} tweets appears in {referrer.tweets.length} tweets
</span> </span>
<div className="my-4 pl-4 border-l-2 border-gray-300"> <div className="my-4 pl-4 border-l-2 border-gray-300 dark:border-gray-500">
{ referrer.tweets.map(this.renderTweet) } { referrer.tweets.map(this.renderTweet) }
</div> </div>
</td> </td>
@ -114,7 +114,7 @@ class ReferrerDrilldownModal extends React.Component {
) )
} else { } else {
return ( return (
<tr className="text-sm" key={referrer.name}> <tr className="text-sm dark:text-gray-200" key={referrer.name}>
<td className="p-2"> <td className="p-2">
{ this.renderReferrerName(referrer) } { this.renderReferrerName(referrer) }
</td> </td>
@ -129,7 +129,7 @@ class ReferrerDrilldownModal extends React.Component {
renderGoalText() { renderGoalText() {
if (this.state.query.filters.goal) { if (this.state.query.filters.goal) {
return ( return (
<h1 className="text-xl font-semibold text-gray-500 leading-none">completed {this.state.query.filters.goal}</h1> <h1 className="text-xl font-semibold text-gray-500 dark:text-gray-300 leading-none">completed {this.state.query.filters.goal}</h1>
) )
} }
} }
@ -142,20 +142,20 @@ class ReferrerDrilldownModal extends React.Component {
} else if (this.state.referrers) { } else if (this.state.referrers) {
return ( return (
<React.Fragment> <React.Fragment>
<h1 className="text-xl font-bold">Referrer drilldown</h1> <h1 className="text-xl font-bold dark:text-gray-100">Referrer drilldown</h1>
<div className="my-4 border-b border-gray-300"></div> <div className="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<main className="modal__content mt-0"> <main className="modal__content mt-0">
<h1 className="text-xl font-semibold mb-0 leading-none">{this.state.totalVisitors} visitors from {decodeURIComponent(this.props.match.params.referrer)}<br /> {toHuman(this.state.query)}</h1> <h1 className="text-xl font-semibold mb-0 leading-none dark:text-gray-200">{this.state.totalVisitors} visitors from {decodeURIComponent(this.props.match.params.referrer)}<br /> {toHuman(this.state.query)}</h1>
{this.renderGoalText()} {this.renderGoalText()}
<table className="w-full table-striped table-fixed mt-4"> <table className="w-full table-striped table-fixed mt-4">
<thead> <thead>
<tr> <tr>
<th className="p-2 text-xs tracking-wide font-bold text-gray-500" align="left">Referrer</th> <th className="p-2 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="left">Referrer</th>
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">Visitors</th> <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Visitors</th>
{this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">Bounce rate</th>} {this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Bounce rate</th>}
{this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">Visit duration</th>} {this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Visit duration</th>}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -84,7 +84,7 @@ class SourcesModal extends React.Component {
if (filter === 'utm_campaigns') query.set('utm_campaign', source.name) if (filter === 'utm_campaigns') query.set('utm_campaign', source.name)
return ( return (
<tr className="text-sm" key={source.name}> <tr className="text-sm dark:text-gray-200" key={source.name}>
<td className="p-2"> <td className="p-2">
<img src={`https://icons.duckduckgo.com/ip3/${source.url}.ico`} referrerPolicy="no-referrer" className="h-4 w-4 mr-2 align-middle inline" /> <img src={`https://icons.duckduckgo.com/ip3/${source.url}.ico`} referrerPolicy="no-referrer" className="h-4 w-4 mr-2 align-middle inline" />
<Link className="hover:underline" to={{search: query.toString(), pathname: '/' + encodeURIComponent(this.props.site.domain)}}>{ source.name }</Link> <Link className="hover:underline" to={{search: query.toString(), pathname: '/' + encodeURIComponent(this.props.site.domain)}}>{ source.name }</Link>
@ -121,18 +121,18 @@ class SourcesModal extends React.Component {
render() { render() {
return ( return (
<Modal site={this.props.site}> <Modal site={this.props.site}>
<h1 className="text-xl font-bold">{this.title()}</h1> <h1 className="text-xl font-bold dark:text-gray-100">{this.title()}</h1>
<div className="my-4 border-b border-gray-300"></div> <div className="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<main className="modal__content"> <main className="modal__content">
<table className="w-full table-striped table-fixed"> <table className="w-full table-striped table-fixed">
<thead> <thead>
<tr> <tr>
<th className="p-2 text-xs tracking-wide font-bold text-gray-500" align="left">Source</th> <th className="p-2 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="left">Source</th>
<th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">{this.label()}</th> <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">{this.label()}</th>
{this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">Bounce rate</th>} {this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Bounce rate</th>}
{this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500" align="right">Visit duration</th>} {this.showExtra() && <th className="p-2 w-32 text-xs tracking-wide font-bold text-gray-500 dark:text-gray-400" align="right">Visit duration</th>}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -5,7 +5,7 @@ export default function MoreLink({site, list, endpoint}) {
if (list.length > 0) { if (list.length > 0) {
return ( return (
<div className="text-center w-full absolute bottom-0 left-0 pb-3"> <div className="text-center w-full absolute bottom-0 left-0 pb-3">
<Link to={`/${encodeURIComponent(site.domain)}/${endpoint}${window.location.search}`} className="leading-snug font-bold text-sm text-gray-500 hover:text-red-500 transition tracking-wide"> <Link to={`/${encodeURIComponent(site.domain)}/${endpoint}${window.location.search}`} className="leading-snug font-bold text-sm text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition tracking-wide">
<svg className="feather mr-1" style={{marginTop: '-2px'}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg> <svg className="feather mr-1" style={{marginTop: '-2px'}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/></svg>
MORE MORE
</Link> </Link>

View File

@ -45,15 +45,15 @@ export default class Pages extends React.Component {
return ( return (
<div className="flex items-center justify-between my-1 text-sm" key={page.name}> <div className="flex items-center justify-between my-1 text-sm" key={page.name}>
<div className="w-full h-8 truncate" style={{maxWidth: 'calc(100% - 4rem)'}}> <div className="w-full h-8 truncate" style={{maxWidth: 'calc(100% - 4rem)'}}>
<Bar count={page.count} all={this.state.pages} bg="bg-orange-50" /> <Bar count={page.count} all={this.state.pages} bg="bg-orange-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2 group" style={{marginTop: '-26px'}} > <span className="flex px-2 group dark:text-gray-300" style={{marginTop: '-26px'}} >
<Link to={{pathname: window.location.pathname, search: query.toString()}} className="block hover:underline">{page.name}</Link> <Link to={{pathname: window.location.pathname, search: query.toString()}} className="block hover:underline">{page.name}</Link>
<a target="_blank" href={'http://' + this.props.site.domain + page.name} className="hidden group-hover:block"> <a target="_blank" href={'http://' + this.props.site.domain + page.name} className="hidden group-hover:block">
<svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg> <svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg>
</a> </a>
</span> </span>
</div> </div>
<span className="font-medium">{numberFormatter(page.count)}</span> <span className="font-medium dark:text-gray-200">{numberFormatter(page.count)}</span>
</div> </div>
) )
} }
@ -73,7 +73,7 @@ export default class Pages extends React.Component {
if (this.state.pages.length > 0) { if (this.state.pages.length > 0) {
return ( return (
<React.Fragment> <React.Fragment>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 text-xs font-bold tracking-wide"> <div className="flex items-center mt-3 mb-2 justify-between text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide">
<span>Page url</span> <span>Page url</span>
<span>{ this.label() }</span> <span>{ this.label() }</span>
</div> </div>
@ -84,7 +84,7 @@ export default class Pages extends React.Component {
</React.Fragment> </React.Fragment>
) )
} else { } else {
return <div className="text-center mt-44 font-medium text-gray-500">No data yet</div> return <div className="text-center mt-44 font-medium text-gray-500 dark:text-gray-400">No data yet</div>
} }
} }
@ -97,7 +97,7 @@ export default class Pages extends React.Component {
if (this.state.pages) { if (this.state.pages) {
return ( return (
<React.Fragment> <React.Fragment>
<h3 className="font-bold">{this.title()}</h3> <h3 className="font-bold dark:text-gray-100">{this.title()}</h3>
{ this.renderList() } { this.renderList() }
<MoreLink site={this.props.site} list={this.state.pages} endpoint="pages" /> <MoreLink site={this.props.site} list={this.state.pages} endpoint="pages" />
</React.Fragment> </React.Fragment>
@ -107,7 +107,7 @@ export default class Pages extends React.Component {
render() { render() {
return ( return (
<div className="stats-item relative bg-white shadow-xl rounded p-4" style={{height: '436px'}}> <div className="stats-item relative bg-white dark:bg-gray-825 shadow-xl rounded p-4" style={{height: '436px'}}>
{ this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> } { this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> }
<FadeIn show={!this.state.loading}> <FadeIn show={!this.state.loading}>
{ this.renderContent() } { this.renderContent() }

View File

@ -57,7 +57,7 @@ export default class Referrers extends React.Component {
if (this.props.query.filters.source && this.props.query.filters.source !== 'Google' && referrer.name !== 'Direct / None') { if (this.props.query.filters.source && this.props.query.filters.source !== 'Google' && referrer.name !== 'Direct / None') {
return ( return (
<a target="_blank" href={'//' + referrer.name} className="hidden group-hover:block"> <a target="_blank" href={'//' + referrer.name} className="hidden group-hover:block">
<svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg> <svg className="inline h-4 w-4 ml-1 -mt-1 text-gray-600 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"></path><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"></path></svg>
</a> </a>
) )
} }
@ -71,16 +71,16 @@ export default class Referrers extends React.Component {
return ( return (
<div className="flex items-center justify-between my-1 text-sm" key={referrer.name}> <div className="flex items-center justify-between my-1 text-sm" key={referrer.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 4rem)'}}> <div className="w-full h-8" style={{maxWidth: 'calc(100% - 4rem)'}}>
<Bar count={referrer.count} all={this.state.referrers} bg="bg-blue-50" /> <Bar count={referrer.count} all={this.state.referrers} bg="bg-blue-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2 group" style={{marginTop: '-26px'}} > <span className="flex px-2 group" style={{marginTop: '-26px'}} >
<LinkOption className="block truncate" to={{search: query.toString()}} disabled={referrer.name === 'Direct / None'}> <LinkOption className="block truncate dark:text-gray-300" to={{search: query.toString()}} disabled={referrer.name === 'Direct / None'}>
<img src={`https://icons.duckduckgo.com/ip3/${referrer.url}.ico`} referrerPolicy="no-referrer" className="inline h-4 w-4 mr-2 align-middle -mt-px" /> <img src={`https://icons.duckduckgo.com/ip3/${referrer.url}.ico`} referrerPolicy="no-referrer" className="inline h-4 w-4 mr-2 align-middle -mt-px" />
{ referrer.name } { referrer.name }
</LinkOption> </LinkOption>
{ this.renderExternalLink(referrer) } { this.renderExternalLink(referrer) }
</span> </span>
</div> </div>
<span className="font-medium">{numberFormatter(referrer.count)}</span> <span className="font-medium dark:text-gray-200">{numberFormatter(referrer.count)}</span>
</div> </div>
) )
} }
@ -93,7 +93,7 @@ export default class Referrers extends React.Component {
if (this.state.referrers.length > 0) { if (this.state.referrers.length > 0) {
return ( return (
<React.Fragment> <React.Fragment>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 text-xs font-bold tracking-wide"> <div className="flex items-center mt-3 mb-2 justify-between text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide">
<span>Referrer</span> <span>Referrer</span>
<span>{ this.label() }</span> <span>{ this.label() }</span>
</div> </div>
@ -104,7 +104,7 @@ export default class Referrers extends React.Component {
</React.Fragment> </React.Fragment>
) )
} else { } else {
return <div className="text-center mt-44 font-medium text-gray-500">No data yet</div> return <div className="text-center mt-44 font-medium text-gray-500 dark:text-gray-400">No data yet</div>
} }
} }
@ -112,7 +112,7 @@ export default class Referrers extends React.Component {
if (this.state.referrers) { if (this.state.referrers) {
return ( return (
<React.Fragment> <React.Fragment>
<h3 className="font-bold">Top Referrers</h3> <h3 className="font-bold dark:text-gray-100">Top Referrers</h3>
{ this.renderList() } { this.renderList() }
<MoreLink site={this.props.site} list={this.state.referrers} endpoint={`referrers/${this.props.query.filters.source}`} /> <MoreLink site={this.props.site} list={this.state.referrers} endpoint={`referrers/${this.props.query.filters.source}`} />
</React.Fragment> </React.Fragment>
@ -122,7 +122,7 @@ export default class Referrers extends React.Component {
render() { render() {
return ( return (
<div className="stats-item relative bg-white shadow-xl rounded p-4" style={{height: '436px'}}> <div className="stats-item relative bg-white dark:bg-gray-825 shadow-xl rounded p-4" style={{height: '436px'}}>
{ this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> } { this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> }
<FadeIn show={!this.state.loading}> <FadeIn show={!this.state.loading}>
{ this.renderContent() } { this.renderContent() }

View File

@ -37,14 +37,14 @@ export default class SearchTerms extends React.Component {
return ( return (
<div className="flex items-center justify-between my-1 text-sm" key={term.name}> <div className="flex items-center justify-between my-1 text-sm" key={term.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 4rem)'}}> <div className="w-full h-8" style={{maxWidth: 'calc(100% - 4rem)'}}>
<Bar count={term.count} all={this.state.searchTerms} bg="bg-blue-50" /> <Bar count={term.count} all={this.state.searchTerms} bg="bg-blue-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2" style={{marginTop: '-26px'}} > <span className="flex px-2 dark:text-gray-300" style={{marginTop: '-26px'}} >
<span className="block truncate"> <span className="block truncate">
{ term.name } { term.name }
</span> </span>
</span> </span>
</div> </div>
<span className="font-medium">{numberFormatter(term.count)}</span> <span className="font-medium dark:text-gray-200">{numberFormatter(term.count)}</span>
</div> </div>
) )
} }
@ -52,7 +52,7 @@ export default class SearchTerms extends React.Component {
renderList() { renderList() {
if (this.props.query.filters.goal) { if (this.props.query.filters.goal) {
return ( return (
<div className="text-center text-gray-700 text-sm mt-20"> <div className="text-center text-gray-700 dark:text-gray-300 text-sm mt-20">
<RocketIcon /> <RocketIcon />
<div>Sorry, we cannot show which keywords converted best for goal <b>{this.props.query.filters.goal}</b></div> <div>Sorry, we cannot show which keywords converted best for goal <b>{this.props.query.filters.goal}</b></div>
<div>Google does not share this information</div> <div>Google does not share this information</div>
@ -61,7 +61,7 @@ export default class SearchTerms extends React.Component {
} else if (this.state.notConfigured) { } else if (this.state.notConfigured) {
return ( return (
<div className="text-center text-gray-700 text-sm mt-20"> <div className="text-center text-gray-700 dark:text-gray-300 text-sm mt-20">
<RocketIcon /> <RocketIcon />
<div>The site is not connected to Google Search Keywords</div> <div>The site is not connected to Google Search Keywords</div>
<div>Cannot show search terms</div> <div>Cannot show search terms</div>
@ -73,7 +73,7 @@ export default class SearchTerms extends React.Component {
return ( return (
<React.Fragment> <React.Fragment>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 text-xs font-bold tracking-wide"> <div className="flex items-center mt-3 mb-2 justify-between text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide">
<span>Search term</span> <span>Search term</span>
<span>{valLabel}</span> <span>{valLabel}</span>
</div> </div>
@ -83,7 +83,7 @@ export default class SearchTerms extends React.Component {
) )
} else { } else {
return ( return (
<div className="text-center text-gray-700 text-sm mt-20"> <div className="text-center text-gray-700 dark:text-gray-300 text-sm mt-20">
<RocketIcon /> <RocketIcon />
<div>Could not find any search terms for this period</div> <div>Could not find any search terms for this period</div>
<div>Google Search Console data is sampled and delayed by 24-36h</div> <div>Google Search Console data is sampled and delayed by 24-36h</div>
@ -97,7 +97,7 @@ export default class SearchTerms extends React.Component {
if (this.state.searchTerms) { if (this.state.searchTerms) {
return ( return (
<React.Fragment> <React.Fragment>
<h3 className="font-bold">Search Terms</h3> <h3 className="font-bold dark:text-gray-100">Search Terms</h3>
{ this.renderList() } { this.renderList() }
<MoreLink site={this.props.site} list={this.state.searchTerms} endpoint="referrers/Google" /> <MoreLink site={this.props.site} list={this.state.searchTerms} endpoint="referrers/Google" />
</React.Fragment> </React.Fragment>
@ -107,7 +107,7 @@ export default class SearchTerms extends React.Component {
render() { render() {
return ( return (
<div className="stats-item relative bg-white shadow-xl rounded p-4" style={{height: '436px'}}> <div className="stats-item relative bg-white dark:bg-gray-825 shadow-xl rounded p-4" style={{height: '436px'}}>
{ this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> } { this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> }
<FadeIn show={!this.state.loading}> <FadeIn show={!this.state.loading}>
{ this.renderContent() } { this.renderContent() }

View File

@ -42,15 +42,15 @@ class AllSources extends React.Component {
return ( return (
<div className="flex items-center justify-between my-1 text-sm" key={referrer.name}> <div className="flex items-center justify-between my-1 text-sm" key={referrer.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 4rem)'}}> <div className="w-full h-8" style={{maxWidth: 'calc(100% - 4rem)'}}>
<Bar count={referrer.count} all={this.state.referrers} bg="bg-blue-50" /> <Bar count={referrer.count} all={this.state.referrers} bg="bg-blue-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2" style={{marginTop: '-26px'}} > <span className="flex px-2 dark:text-gray-300" style={{marginTop: '-26px'}} >
<Link className="block truncate hover:underline" to={{search: query.toString()}}> <Link className="block truncate hover:underline" to={{search: query.toString()}}>
<img src={`https://icons.duckduckgo.com/ip3/${referrer.url}.ico`} referrerPolicy="no-referrer" className="inline h-4 w-4 mr-2 align-middle -mt-px" /> <img src={`https://icons.duckduckgo.com/ip3/${referrer.url}.ico`} referrerPolicy="no-referrer" className="inline h-4 w-4 mr-2 align-middle -mt-px" />
{ referrer.name } { referrer.name }
</Link> </Link>
</span> </span>
</div> </div>
<span className="font-medium">{numberFormatter(referrer.count)}</span> <span className="font-medium dark:text-gray-200">{numberFormatter(referrer.count)}</span>
</div> </div>
) )
} }
@ -83,7 +83,7 @@ class AllSources extends React.Component {
return ( return (
<React.Fragment> <React.Fragment>
<div className="w-full flex justify-between"> <div className="w-full flex justify-between">
<h3 className="font-bold">Top sources</h3> <h3 className="font-bold dark:text-gray-100">Top sources</h3>
{ this.props.renderTabs() } { this.props.renderTabs() }
</div> </div>
{ this.renderList() } { this.renderList() }
@ -95,7 +95,7 @@ class AllSources extends React.Component {
render() { render() {
return ( return (
<div className="stats-item relative bg-white shadow-xl rounded p-4" style={{height: '436px'}}> <div className="stats-item relative bg-white dark:bg-gray-825 shadow-xl rounded p-4" style={{height: '436px'}}>
{ this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> } { this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> }
<FadeIn show={!this.state.loading}> <FadeIn show={!this.state.loading}>
{ this.renderContent() } { this.renderContent() }
@ -146,14 +146,14 @@ class UTMSources extends React.Component {
return ( return (
<div className="flex items-center justify-between my-1 text-sm" key={referrer.name}> <div className="flex items-center justify-between my-1 text-sm" key={referrer.name}>
<div className="w-full h-8" style={{maxWidth: 'calc(100% - 4rem)'}}> <div className="w-full h-8" style={{maxWidth: 'calc(100% - 4rem)'}}>
<Bar count={referrer.count} all={this.state.referrers} bg="bg-blue-50" /> <Bar count={referrer.count} all={this.state.referrers} bg="bg-blue-50 dark:bg-gray-500 dark:bg-opacity-15" />
<span className="flex px-2" style={{marginTop: '-26px'}} > <span className="flex px-2 dark:text-gray-300" style={{marginTop: '-26px'}} >
<Link className="block truncate hover:underline" to={{search: query.toString()}}> <Link className="block truncate hover:underline" to={{search: query.toString()}}>
{ referrer.name } { referrer.name }
</Link> </Link>
</span> </span>
</div> </div>
<span className="font-medium">{numberFormatter(referrer.count)}</span> <span className="font-medium dark:text-gray-200">{numberFormatter(referrer.count)}</span>
</div> </div>
) )
} }
@ -166,7 +166,7 @@ class UTMSources extends React.Component {
if (this.state.referrers.length > 0) { if (this.state.referrers.length > 0) {
return ( return (
<React.Fragment> <React.Fragment>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 text-xs font-bold tracking-wide"> <div className="flex items-center mt-3 mb-2 justify-between text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide">
<span>{UTM_TAGS[this.props.tab].label}</span> <span>{UTM_TAGS[this.props.tab].label}</span>
<span>{this.label()}</span> <span>{this.label()}</span>
</div> </div>
@ -177,7 +177,7 @@ class UTMSources extends React.Component {
</React.Fragment> </React.Fragment>
) )
} else { } else {
return <div className="text-center mt-44 font-medium text-gray-500">No data yet</div> return <div className="text-center mt-44 font-medium text-gray-500 dark:text-gray-400">No data yet</div>
} }
} }
@ -186,7 +186,7 @@ class UTMSources extends React.Component {
return ( return (
<React.Fragment> <React.Fragment>
<div className="w-full flex justify-between"> <div className="w-full flex justify-between">
<h3 className="font-bold">Top sources</h3> <h3 className="font-bold dark:text-gray-100">Top sources</h3>
{ this.props.renderTabs() } { this.props.renderTabs() }
</div> </div>
{ this.renderList() } { this.renderList() }
@ -198,7 +198,7 @@ class UTMSources extends React.Component {
render() { render() {
return ( return (
<div className="stats-item relative bg-white shadow-xl rounded p-4" style={{height: '436px'}}> <div className="stats-item relative bg-white dark:bg-gray-825 shadow-xl rounded p-4" style={{height: '436px'}}>
{ this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> } { this.state.loading && <div className="loading mt-44 mx-auto"><div></div></div> }
<FadeIn show={!this.state.loading}> <FadeIn show={!this.state.loading}>
{ this.renderContent() } { this.renderContent() }
@ -226,10 +226,10 @@ export default class SourceList extends React.Component {
} }
renderTabs() { renderTabs() {
const activeClass = 'inline-block h-5 text-indigo-700 font-bold border-b-2 border-indigo-700' const activeClass = 'inline-block h-5 text-indigo-700 dark:text-indigo-500 font-bold border-b-2 border-indigo-700 dark:border-indigo-500'
const defaultClass = 'hover:text-indigo-700 cursor-pointer' const defaultClass = 'hover:text-indigo-600 cursor-pointer'
return ( return (
<ul className="flex font-medium text-xs text-gray-500 space-x-2"> <ul className="flex font-medium text-xs text-gray-500 dark:text-gray-400 space-x-2">
<li className={this.state.tab === 'all' ? activeClass : defaultClass} onClick={this.setTab('all')}>All</li> <li className={this.state.tab === 'all' ? activeClass : defaultClass} onClick={this.setTab('all')}>All</li>
<li className={this.state.tab === 'utm_medium' ? activeClass : defaultClass} onClick={this.setTab('utm_medium')}>Medium</li> <li className={this.state.tab === 'utm_medium' ? activeClass : defaultClass} onClick={this.setTab('utm_medium')}>Medium</li>
<li className={this.state.tab === 'utm_source' ? activeClass : defaultClass} onClick={this.setTab('utm_source')}>Source</li> <li className={this.state.tab === 'utm_source' ? activeClass : defaultClass} onClick={this.setTab('utm_source')}>Source</li>

View File

@ -4,6 +4,7 @@ import Chart from 'chart.js'
import { eventName, navigateToQuery } from '../query' import { eventName, navigateToQuery } from '../query'
import numberFormatter, {durationFormatter} from '../number-formatter' import numberFormatter, {durationFormatter} from '../number-formatter'
import * as api from '../api' import * as api from '../api'
import {ThemeContext} from '../theme-context'
function buildDataSet(plot, present_index, ctx, label) { function buildDataSet(plot, present_index, ctx, label) {
var gradient = ctx.createLinearGradient(0, 0, 0, 300); var gradient = ctx.createLinearGradient(0, 0, 0, 300);
@ -92,13 +93,18 @@ function dateFormatter(interval, longForm) {
} }
class LineGraph extends React.Component { class LineGraph extends React.Component {
componentDidMount() { constructor(props) {
super(props);
this.regenerateChart = this.regenerateChart.bind(this);
}
regenerateChart() {
const {graphData} = this.props const {graphData} = this.props
this.ctx = document.getElementById("main-graph-canvas").getContext('2d'); this.ctx = document.getElementById("main-graph-canvas").getContext('2d');
const label = this.props.query.filters.goal ? 'Converted visitors' : graphData.interval === 'minute' ? 'Pageviews' : 'Visitors' const label = this.props.query.filters.goal ? 'Converted visitors' : graphData.interval === 'minute' ? 'Pageviews' : 'Visitors'
const dataSet = buildDataSet(graphData.plot, graphData.present_index, this.ctx, label) const dataSet = buildDataSet(graphData.plot, graphData.present_index, this.ctx, label)
this.chart = new Chart(this.ctx, { return new Chart(this.ctx, {
type: 'line', type: 'line',
data: { data: {
labels: graphData.labels, labels: graphData.labels,
@ -157,6 +163,7 @@ class LineGraph extends React.Component {
beginAtZero: true, beginAtZero: true,
autoSkip: true, autoSkip: true,
maxTicksLimit: 8, maxTicksLimit: 8,
fontColor: this.props.darkTheme ? 'rgb(243, 244, 246)' : undefined
}, },
gridLines: { gridLines: {
zeroLineColor: 'transparent', zeroLineColor: 'transparent',
@ -171,6 +178,7 @@ class LineGraph extends React.Component {
autoSkip: true, autoSkip: true,
maxTicksLimit: 8, maxTicksLimit: 8,
callback: dateFormatter(graphData.interval), callback: dateFormatter(graphData.interval),
fontColor: this.props.darkTheme ? 'rgb(243, 244, 246)' : undefined
} }
}] }]
} }
@ -178,6 +186,10 @@ class LineGraph extends React.Component {
}); });
} }
componentDidMount() {
this.chart = this.regenerateChart();
}
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (this.props.graphData !== prevProps.graphData) { if (this.props.graphData !== prevProps.graphData) {
const label = this.props.query.filters.goal ? 'Converted visitors' : this.props.graphData.interval === 'minute' ? 'Pageviews' : 'Visitors' const label = this.props.query.filters.goal ? 'Converted visitors' : this.props.graphData.interval === 'minute' ? 'Pageviews' : 'Visitors'
@ -189,6 +201,11 @@ class LineGraph extends React.Component {
this.chart.update() this.chart.update()
} }
if (prevProps.darkTheme !== this.props.darkTheme) {
this.chart = this.regenerateChart();
this.chart.update();
}
} }
onClick(e) { onClick(e) {
@ -220,12 +237,12 @@ class LineGraph extends React.Component {
if (comparison > 0) { if (comparison > 0) {
const color = name === 'Bounce rate' ? 'text-red-400' : 'text-green-500' const color = name === 'Bounce rate' ? 'text-red-400' : 'text-green-500'
return <span className="text-xs"><span className={color + ' font-bold'}>&uarr;</span> {formattedComparison}%</span> return <span className="text-xs dark:text-gray-100"><span className={color + ' font-bold'}>&uarr;</span> {formattedComparison}%</span>
} else if (comparison < 0) { } else if (comparison < 0) {
const color = name === 'Bounce rate' ? 'text-green-500' : 'text-red-400' const color = name === 'Bounce rate' ? 'text-green-500' : 'text-red-400'
return <span className="text-xs"><span className={color + ' font-bold'}>&darr;</span> {formattedComparison}%</span> return <span className="text-xs dark:text-gray-100"><span className={color + ' font-bold'}>&darr;</span> {formattedComparison}%</span>
} else if (comparison === 0) { } else if (comparison === 0) {
return <span className="text-xs text-gray-700">&#12336; N/A</span> return <span className="text-xs text-gray-700 dark:text-gray-300">&#12336; N/A</span>
} }
} }
@ -247,9 +264,9 @@ class LineGraph extends React.Component {
return ( return (
<div className={`px-8 w-1/2 my-4 lg:w-auto ${border}`} key={stat.name}> <div className={`px-8 w-1/2 my-4 lg:w-auto ${border}`} key={stat.name}>
<div className="text-gray-500 text-xs font-bold tracking-wide uppercase">{stat.name}</div> <div className="text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide uppercase">{stat.name}</div>
<div className="my-1 flex justify-between items-center"> <div className="my-1 flex justify-between items-center">
<b className="text-2xl mr-4">{ this.renderTopStatNumber(stat) }</b> <b className="text-2xl mr-4 dark:text-gray-100">{ this.renderTopStatNumber(stat) }</b>
{this.renderComparison(stat.name, stat.change)} {this.renderComparison(stat.name, stat.change)}
</div> </div>
</div> </div>
@ -268,7 +285,7 @@ class LineGraph extends React.Component {
return ( return (
<a href={endpoint} download> <a href={endpoint} download>
<svg className="feather w-4 h-5 absolute text-gray-700" style={{right: '2rem', top: '-2rem'}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg> <svg className="feather w-4 h-5 absolute text-gray-700 dark:text-gray-300" style={{right: '2rem', top: '-2rem'}} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
</a> </a>
) )
} }
@ -321,14 +338,18 @@ export default class VisitorGraph extends React.Component {
renderInner() { renderInner() {
if (this.state.graphData) { if (this.state.graphData) {
return ( return (
<LineGraph graphData={this.state.graphData} site={this.props.site} query={this.props.query} /> <ThemeContext.Consumer>
{theme => (
<LineGraph graphData={this.state.graphData} site={this.props.site} query={this.props.query} darkTheme={theme}/>
)}
</ThemeContext.Consumer>
) )
} }
} }
render() { render() {
return ( return (
<div className="w-full relative bg-white shadow-xl rounded mt-6 main-graph"> <div className="w-full relative bg-white dark:bg-gray-825 shadow-xl rounded mt-6 main-graph">
{ this.state.loading && <div className="loading pt-24 sm:pt-32 md:pt-48 mx-auto"><div></div></div> } { this.state.loading && <div className="loading pt-24 sm:pt-32 md:pt-48 mx-auto"><div></div></div> }
{ this.renderInner() } { this.renderInner() }
</div> </div>

View File

@ -0,0 +1,16 @@
import React from 'react';
import { ThemeContext } from './theme-context'
export const withThemeConsumer = (WrappedComponent) => {
return class extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{theme => (
<WrappedComponent darkTheme={theme} {...this.props} />
)}
</ThemeContext.Consumer>
);
}
}
}

View File

@ -0,0 +1,5 @@
import React from 'react';
export const ThemeContext = React.createContext({
dark: false
});

View File

@ -0,0 +1,37 @@
import React from 'react';
import { ThemeContext } from './theme-context'
export const withThemeProvider = (WrappedComponent) => {
return class extends React.Component {
constructor(props) {
super(props)
this.state = {
dark: document.querySelector('html').classList.contains('dark') || false
};
this.mutationObserver = new MutationObserver((mutationsList, observer) => {
mutationsList.forEach(mutation => {
if (mutation.attributeName === 'class') {
this.setState({ dark: mutation.target.classList.contains('dark') });
}
});
});
}
componentDidMount() {
this.mutationObserver.observe(document.querySelector('html'), { attributes: true });
}
componentWillUnmount() {
this.mutationObserver.disconnect();
}
render() {
return (
<ThemeContext.Provider value={this.state.dark}>
<WrappedComponent {...this.props}/>
</ThemeContext.Provider>
);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,37 @@
var pref = document.currentScript.dataset.pref;
function reapplyTheme() {
var userPref = pref || "system";
var mediaPref = window.matchMedia('(prefers-color-scheme: dark)').matches;
var htmlRef = document.querySelector('html');
var hcaptchaRefs = document.getElementsByClassName('h-captcha');
htmlRef.classList.remove('dark');
for (let i = 0; i < hcaptchaRefs.length; i++) {
hcaptchaRefs[i].dataset.theme = "light";
}
switch (userPref) {
case "dark":
htmlRef.classList.add('dark');
for (let i = 0; i < hcaptchaRefs.length; i++) {
hcaptchaRefs[i].dataset.theme = "dark";
}
break;
case "system":
if (mediaPref) {
htmlRef.classList.add('dark');
for (let i = 0; i < hcaptchaRefs.length; i++) {
hcaptchaRefs[i].dataset.theme = "dark";
}
}
break;
}
}
reapplyTheme();
window.matchMedia('(prefers-color-scheme: dark)').addListener(reapplyTheme);
window.onload = function() {
reapplyTheme();
};

View File

@ -5,7 +5,7 @@ module.exports = {
'./js/**/*.js', './js/**/*.js',
'../lib/plausible_web/templates/**/*.html.eex', '../lib/plausible_web/templates/**/*.html.eex',
], ],
darkMode: false, darkMode: 'class',
theme: { theme: {
container: { container: {
center: true, center: true,
@ -14,18 +14,29 @@ module.exports = {
extend: { extend: {
colors: { colors: {
orange: colors.orange, orange: colors.orange,
'gray-850': 'rgb(26, 32, 44)',
'gray-825': 'rgb(37, 47, 63)'
}, },
spacing: { spacing: {
'44': '11rem' '44': '11rem'
}, },
width: { width: {
'31percent': '31%', '31percent': '31%',
},
opacity: {
'15': '0.15',
} }
}, },
}, },
variants: { variants: {
textColor: ['responsive', 'hover', 'focus', 'group-hover'], textColor: ['responsive', 'hover', 'focus', 'group-hover'],
display: ['responsive', 'hover', 'focus', 'group-hover'] display: ['responsive', 'hover', 'focus', 'group-hover'],
extend: {
textColor: ['dark'],
borderWidth: ['dark'],
backgroundOpacity: ['dark'],
display: ['dark'],
}
}, },
plugins: [ plugins: [
require('@tailwindcss/forms'), require('@tailwindcss/forms'),

View File

@ -17,6 +17,7 @@ defmodule Plausible.Auth.User do
field :name, :string field :name, :string
field :last_seen, :naive_datetime field :last_seen, :naive_datetime
field :trial_expiry_date, :date field :trial_expiry_date, :date
field :theme, :string
field :email_verified, :boolean field :email_verified, :boolean
has_many :site_memberships, Plausible.Site.Membership has_many :site_memberships, Plausible.Site.Membership
@ -40,7 +41,7 @@ defmodule Plausible.Auth.User do
def changeset(user, attrs \\ %{}) do def changeset(user, attrs \\ %{}) do
user user
|> cast(attrs, [:email, :name, :email_verified]) |> cast(attrs, [:email, :name, :email_verified, :theme])
|> validate_required([:email, :name, :email_verified]) |> validate_required([:email, :name, :email_verified])
|> unique_constraint(:email) |> unique_constraint(:email)
end end

11
lib/plausible/themes.ex Normal file
View File

@ -0,0 +1,11 @@
defmodule Plausible.Themes do
@options [
[key: "Follow System Theme", value: "system"],
[key: "Light", value: "light"],
[key: "Dark", value: "dark"]
]
def options() do
@options
end
end

View File

@ -272,7 +272,8 @@ defmodule PlausibleWeb.AuthController do
render(conn, "user_settings.html", render(conn, "user_settings.html",
changeset: changeset, changeset: changeset,
subscription: conn.assigns[:current_user].subscription subscription: conn.assigns[:current_user].subscription,
theme: conn.assigns[:current_user].theme || "system"
) )
end end

View File

@ -6,30 +6,30 @@
<!-- Complete Step --> <!-- Complete Step -->
<li class="flex items-start"> <li class="flex items-start">
<span class="flex-shrink-0 relative h-5 w-5 flex items-center justify-center"> <span class="flex-shrink-0 relative h-5 w-5 flex items-center justify-center">
<svg class="h-full w-full text-indigo-600" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <svg class="h-full w-full text-indigo-600 dark:text-indigo-500" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg> </svg>
</span> </span>
<span class="ml-3 text-sm font-medium text-gray-500"><%= step %></span> <span class="ml-3 text-sm font-medium text-gray-500 dark:text-gray-300"><%= step %></span>
</li> </li>
<% end %> <% end %>
<%= if index == @current_step do %> <%= if index == @current_step do %>
<!-- Current Step --> <!-- Current Step -->
<li class="flex items-start"> <li class="flex items-start">
<span class="flex-shrink-0 h-5 w-5 relative flex items-center justify-center"> <span class="flex-shrink-0 h-5 w-5 relative flex items-center justify-center">
<span class="absolute h-4 w-4 rounded-full bg-indigo-200"></span> <span class="absolute h-4 w-4 rounded-full bg-indigo-200 dark:bg-indigo-100"></span>
<span class="relative block w-2 h-2 bg-indigo-600 rounded-full"></span> <span class="relative block w-2 h-2 bg-indigo-600 dark:bg-indigo-500 rounded-full"></span>
</span> </span>
<span class="ml-3 text-sm font-medium text-indigo-600"><%= step %></span> <span class="ml-3 text-sm font-medium text-indigo-600 dark:text-indigo-500"><%= step %></span>
</li> </li>
<% end %> <% end %>
<%= if index > @current_step do %> <%= if index > @current_step do %>
<!-- Upcoming Step --> <!-- Upcoming Step -->
<li class="flex items-start"> <li class="flex items-start">
<div class="flex-shrink-0 h-5 w-5 relative flex items-center justify-center"> <div class="flex-shrink-0 h-5 w-5 relative flex items-center justify-center">
<div class="h-2 w-2 bg-gray-300 rounded-full"></div> <div class="h-2 w-2 bg-gray-300 dark:bg-gray-700 rounded-full"></div>
</div> </div>
<p class="ml-3 text-sm font-medium text-gray-500"><%= step %></p> <p class="ml-3 text-sm font-medium text-gray-500 dark:text-gray-300"><%= step %></p>
</li> </li>
<% end %> <% end %>
<% end %> <% end %>

View File

@ -1,32 +1,32 @@
<div class="w-full max-w-3xl mt-4 mx-auto flex"> <div class="w-full max-w-3xl mt-4 mx-auto flex">
<%= if @has_pin do %> <%= if @has_pin do %>
<%= form_for @conn, "/activate", [class: "w-full max-w-lg mx-auto bg-white shadow-md rounded px-8 py-6 mb-4 mt-8"], fn f -> %> <%= form_for @conn, "/activate", [class: "w-full max-w-lg mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black">Activate your account</h2> <h2 class="text-xl font-black dark:text-gray-100">Activate your account</h2>
<div class="mt-2 text-sm text-gray-500 leading-tight"> <div class="mt-2 text-sm text-gray-500 dark:text-gray-200 leading-tight">
Please enter the 4-digit code we sent to <b><%= @conn.assigns[:current_user].email %></b> Please enter the 4-digit code we sent to <b><%= @conn.assigns[:current_user].email %></b>
</div> </div>
<div class="mt-12 flex items-stretch flex-grow"> <div class="mt-12 flex items-stretch flex-grow">
<div> <div>
<%= text_input f, :code, class: "tracking-widest font-medium shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-36 px-8 border-gray-300 rounded-l-md", oninput: "this.value=this.value.replace(/[^0-9]/g, ''); if (this.value.length >= 4) document.getElementById('submit').focus()", onclick: "this.select();", maxlength: "4", placeholder: "••••", style: "letter-spacing: 10px;" %> <%= text_input f, :code, class: "tracking-widest font-medium shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-36 px-8 border-gray-300 dark:border-gray-500 rounded-l-md dark:text-gray-200 dark:bg-gray-900", oninput: "this.value=this.value.replace(/[^0-9]/g, ''); if (this.value.length >= 4) document.getElementById('submit').focus()", onclick: "this.select();", maxlength: "4", placeholder: "••••", style: "letter-spacing: 10px;" %>
</div> </div>
<button id="submit" class="button rounded-l-none">Activate &rarr;</button> <button id="submit" class="button rounded-l-none">Activate &rarr;</button>
</div> </div>
<%= error_tag(assigns, :error) %> <%= error_tag(assigns, :error) %>
<div class="mt-16 text-sm"> <div class="mt-16 text-sm dark:text-gray-100">
Didn't receive an email? Didn't receive an email?
</div> </div>
<div class="mt-2 text-sm text-gray-500 leading-tight"> <div class="mt-2 text-sm text-gray-500 leading-tight">
Please check your spam folder and contact <a class="underline text-gray-800" href="mailto:support@plausible.io">support@plausible.io</a> if the problem persists Please check your spam folder and contact <a class="underline text-indigo-500" href="mailto:support@plausible.io">support@plausible.io</a> if the problem persists
</div> </div>
<% end %> <% end %>
<% else %> <% else %>
<div class="w-full max-w-lg mx-auto bg-white shadow-md rounded px-8 py-6 mb-4 mt-8"> <div class="w-full max-w-lg mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mb-4 mt-8">
<h2 class="text-xl font-black">Activate your account</h2> <h2 class="text-xl font-black dark:text-gray-100">Activate your account</h2>
<div class="mt-2 text-sm text-gray-500 leading-tight"> <div class="mt-2 text-sm text-gray-500 dark:text-gray-200 leading-tight">
A 4-digit activation code will be sent to <b><%= @conn.assigns[:current_user].email %></b> A 4-digit activation code will be sent to <b><%= @conn.assigns[:current_user].email %></b>
</div> </div>

View File

@ -1,5 +1,5 @@
<%= form_for @conn, "/login", [class: "w-full max-w-md mx-auto bg-white shadow-md rounded px-8 py-6 mt-8"], fn f -> %> <%= form_for @conn, "/login", [class: "w-full max-w-md mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mt-8"], fn f -> %>
<h1 class="text-xl font-black"><%= get_flash(@conn, :login_title) || "Enter your email and password" %></h1> <h1 class="text-xl font-black dark:text-gray-100"><%= get_flash(@conn, :login_title) || "Enter your email and password" %></h1>
<%= if get_flash(@conn, :login_instructions) do %> <%= if get_flash(@conn, :login_instructions) do %>
<p class="text-gray-500 text-sm mt-1 mb-2"><%= get_flash(@conn, :login_instructions) %></p> <p class="text-gray-500 text-sm mt-1 mb-2"><%= get_flash(@conn, :login_instructions) %></p>
<% end %> <% end %>
@ -7,18 +7,18 @@
<div class="text-red-500 text-xs italic mt-4"><%= @conn.assigns[:error] %></div> <div class="text-red-500 text-xs italic mt-4"><%= @conn.assigns[:error] %></div>
<% end %> <% end %>
<div class="my-4 mt-8"> <div class="my-4 mt-8">
<%= label f, :email, class: "block text-gray-700 text-sm font-bold mb-2" %> <%= label f, :email, class: "block text-gray-700 dark:text-gray-300 text-sm font-bold mb-2" %>
<%= email_input f, :email, class: "bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300", placeholder: "user@example.com" %> <%= email_input f, :email, class: "bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal appearance-none focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500", placeholder: "user@example.com" %>
</div> </div>
<div class="my-4"> <div class="my-4">
<%= label f, :password, class: "block text-gray-700 text-sm font-bold mb-2" %> <%= label f, :password, class: "block text-gray-700 dark:text-gray-300 text-sm font-bold mb-2" %>
<%= password_input f, :password, class: "transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300" %> <%= password_input f, :password, class: "transition bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal appearance-none focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500" %>
<p class="text-gray-500 text-xs my-2">Forgot password? <a href="/password/request-reset" class="underline text-gray-800">Click here</a> to reset it.</p> <p class="text-gray-500 text-xs my-2">Forgot password? <a href="/password/request-reset" class="underline text-gray-800 dark:text-gray-50">Click here</a> to reset it.</p>
</div> </div>
<%= submit "Login →", class: "button mt-4 w-full" %> <%= submit "Login →", class: "button mt-4 w-full" %>
<%= if !Keyword.fetch!(Application.get_env(:plausible, :selfhost),:disable_registration) do %> <%= if !Keyword.fetch!(Application.get_env(:plausible, :selfhost),:disable_registration) do %>
<p class="text-center text-gray-500 text-xs mt-4"> <p class="text-center text-gray-500 text-xs mt-4">
Don't have an account? <%= link("Register", to: "/register", class: "underline text-gray-800") %> instead. Don't have an account? <%= link("Register", to: "/register", class: "text-gray-800 dark:text-gray-50 underline") %> instead.
</p> </p>
<% end %> <% end %>
<% end %> <% end %>

View File

@ -1,14 +1,14 @@
<%= form_for @conn, "/password", [class: "bg-white max-w-md w-full mx-auto shadow-md rounded px-8 py-6 mt-8"], fn f -> %> <%= form_for @conn, "/password", [class: "bg-white dark:bg-gray-800 max-w-md w-full mx-auto shadow-md rounded px-8 py-6 mt-8"], fn f -> %>
<h2 class="text-xl font-black">Set your password</h2> <h2 class="text-xl font-black dark:text-gray-100">Set your password</h2>
<div class="my-4"> <div class="my-4">
<p class="text-gray-600 text-sm mt-1 mb-2">Min 6 characters</p> <p class="text-gray-600 dark:text-gray-400 text-sm mt-1 mb-2">Min 6 characters</p>
<%= password_input f, :password, class: "transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300" %> <%= password_input f, :password, class: "transition bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal appearance-none focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500" %>
<%= if @conn.assigns[:changeset] do %> <%= if @conn.assigns[:changeset] do %>
<%= error_tag @changeset, :password %> <%= error_tag @changeset, :password %>
<% end %> <% end %>
</div> </div>
<%= submit "Set password →", class: "button mt-4 w-full" %> <%= submit "Set password →", class: "button mt-4 w-full" %>
<p class="text-center text-gray-600 text-xs mt-4"> <p class="text-center text-gray-500 text-xs mt-4">
Don't have an account? <%= link("Register", to: "/register") %> instead. Don't have an account? <%= link("Register", to: "/register", class: "underline text-gray-800 dark:text-gray-200") %> instead.
</p> </p>
<% end %> <% end %>

View File

@ -1,15 +1,15 @@
<%= form_for @conn, "/password/reset", [class: "bg-white max-w-md w-full mx-auto shadow-md rounded px-8 py-6 mt-8"], fn f -> %> <%= form_for @conn, "/password/reset", [class: "bg-white dark:bg-gray-800 max-w-md w-full mx-auto shadow-md rounded px-8 py-6 mt-8"], fn f -> %>
<h2 class="text-xl font-black">Reset your password</h2> <h2 class="text-xl font-black dark:text-gray-100">Reset your password</h2>
<div class="my-4"> <div class="my-4">
<p class="text-gray-600 text-sm mt-1 mb-2">Min 6 characters</p> <p class="text-gray-600 dark:text-gray-400 text-sm mt-1 mb-2">Min 6 characters</p>
<%= password_input f, :password, class: "transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300" %> <%= password_input f, :password, class: "transition bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal appearance-none focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500" %>
<%= if @conn.assigns[:changeset] do %> <%= if @conn.assigns[:changeset] do %>
<%= error_tag @changeset, :password %> <%= error_tag @changeset, :password %>
<% end %> <% end %>
</div> </div>
<%= hidden_input f, :token, value: @token %> <%= hidden_input f, :token, value: @token %>
<%= submit "Set password →", class: "button mt-4 w-full" %> <%= submit "Set password →", class: "button mt-4 w-full" %>
<p class="text-center text-gray-600 text-xs mt-4"> <p class="text-center text-gray-500 text-xs mt-4">
Don't have an account? <%= link("Register", to: "/register") %> instead. Don't have an account? <%= link("Register", to: "/register", class: "underline text-gray-800 dark:text-gray-200") %> instead.
</p> </p>
<% end %> <% end %>

View File

@ -1,8 +1,8 @@
<%= form_for @conn, "/password/request-reset", [class: "max-w-md w-full mx-auto bg-white shadow-md rounded px-8 py-6 mt-8"], fn f -> %> <%= form_for @conn, "/password/request-reset", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mt-8"], fn f -> %>
<h1 class="text-xl font-black">Reset your password</h1> <h1 class="text-xl font-black dark:text-gray-100">Reset your password</h1>
<div class="mt-4">Enter your email so we can send a password reset link</div> <div class="mt-4 dark:text-gray-100">Enter your email so we can send a password reset link</div>
<div class="my-4 mt-8"> <div class="my-4 mt-8">
<%= email_input f, :email, class: "transition bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300", placeholder: "user@example.com" %> <%= email_input f, :email, class: "transition bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500", placeholder: "user@example.com" %>
</div> </div>
<%= if @conn.assigns[:error] do %> <%= if @conn.assigns[:error] do %>
<div class="text-red-500 text-xs italic my-2"><%= @conn.assigns[:error] %></div> <div class="text-red-500 text-xs italic my-2"><%= @conn.assigns[:error] %></div>

View File

@ -1,12 +1,12 @@
<div class="bg-white max-w-md w-full mx-auto shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"> <div class="bg-white dark:bg-gray-800 max-w-md w-full mx-auto shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8">
<h2 class="text-xl font-black">Success!</h2> <h2 class="text-xl font-black dark:text-gray-100">Success!</h2>
<div class="my-4 leading-tight"> <div class="my-4 leading-tight dark:text-gray-100">
We have sent an email with password reset instructions to <b><%= @email %></b> if it exists in our database. We have sent an email with password reset instructions to <b><%= @email %></b> if it exists in our database.
</div> </div>
<div class="mt-8 text-sm"> <div class="mt-8 text-sm dark:text-gray-100">
Didn't receive an email? Didn't receive an email?
</div> </div>
<div class="mt-2 text-sm text-gray-600 leading-tight"> <div class="mt-2 text-sm text-gray-600 dark:text-gray-400 leading-tight">
Please check your spam folder and contact <a href="mailto:support@plausible.io">support@plausible.io</a> if the problem persists Please check your spam folder and contact <a href="mailto:support@plausible.io" class="text-indigo-500">support@plausible.io</a> if the problem persists
</div> </div>
</div> </div>

View File

@ -1,51 +1,51 @@
<div class="mx-auto mt-6 text-center"> <div class="mx-auto mt-6 text-center dark:text-gray-300">
<h1 class="text-3xl font-black">Register your 30-day unlimited-use free trial</h1> <h1 class="text-3xl font-black">Register your 30-day unlimited-use free trial</h1>
<div class="text-xl font-medium">Set up privacy-friendly analytics with just a few clicks</div> <div class="text-xl font-medium">Set up privacy-friendly analytics with just a few clicks</div>
</div> </div>
<div class="w-full max-w-3xl mt-4 mx-auto flex"> <div class="w-full max-w-3xl mt-4 mx-auto flex">
<%= form_for @changeset, "/register", [class: "w-full max-w-md mx-auto bg-white shadow-md rounded px-8 py-6 mb-4 mt-8", id: "register-form"], fn f -> %> <%= form_for @changeset, "/register", [class: "w-full max-w-md mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mb-4 mt-8", id: "register-form"], fn f -> %>
<h2 class="text-xl font-black">Enter your details</h2> <h2 class="text-xl font-black dark:text-gray-100">Enter your details</h2>
<div class="my-4"> <div class="my-4">
<%= label f, :name, "Full name", class: "block text-sm font-medium text-gray-700" %> <%= label f, :name, "Full name", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<div class="mt-1"> <div class="mt-1">
<%= text_input f, :name, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md", placeholder: "Jane Doe" %> <%= text_input f, :name, class: "dark:bg-gray-900 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:text-gray-300", placeholder: "Jane Doe" %>
</div> </div>
<%= error_tag f, :name %> <%= error_tag f, :name %>
</div> </div>
<div class="my-4"> <div class="my-4">
<div class="flex justify-between"> <div class="flex justify-between">
<%= label f, :email, class: "block text-sm font-medium text-gray-700" %> <%= label f, :email, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<p class="text-xs text-gray-500 mt-1">No spam, guaranteed.</p> <p class="text-xs text-gray-500 mt-1">No spam, guaranteed.</p>
</div> </div>
<div class="mt-1"> <div class="mt-1">
<%= email_input f, :email, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md", placeholder: "example@email.com" %> <%= email_input f, :email, class: "dark:bg-gray-900 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:text-gray-300", placeholder: "example@email.com" %>
</div> </div>
<%= error_tag f, :email %> <%= error_tag f, :email %>
</div> </div>
<div class="my-4"> <div class="my-4">
<div class="flex justify-between"> <div class="flex justify-between">
<%= label f, :password, class: "block text-sm font-medium text-gray-700" %> <%= label f, :password, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<p class="text-xs text-gray-500 mt-1">Min 6 characters</p> <p class="text-xs text-gray-500 mt-1">Min 6 characters</p>
</div> </div>
<div class="mt-1"> <div class="mt-1">
<%= password_input f, :password, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %> <%= password_input f, :password, class: "dark:bg-gray-900 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:text-gray-300" %>
</div> </div>
<%= error_tag f, :password %> <%= error_tag f, :password %>
</div> </div>
<div class="my-4"> <div class="my-4">
<%= label f, :password_confirmation, class: "block text-sm font-medium text-gray-700" %> <%= label f, :password_confirmation, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<div class="mt-1"> <div class="mt-1">
<%= password_input f, :password_confirmation, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %> <%= password_input f, :password_confirmation, class: "dark:bg-gray-900 shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:text-gray-300" %>
</div> </div>
<%= error_tag f, :password_confirmation %> <%= error_tag f, :password_confirmation %>
</div> </div>
<%= if PlausibleWeb.Captcha.enabled?() do %> <%= if PlausibleWeb.Captcha.enabled?() do %>
<div class="mt-4"> <div class="mt-4">
<div class="h-captcha" data-sitekey="<%= PlausibleWeb.Captcha.sitekey() %>"></div> <div class="h-captcha"></div>
<%= if assigns[:captcha_error] do %> <%= if assigns[:captcha_error] do %>
<div class="text-red-500 text-xs italic mt-3"><%= @captcha_error %></div> <div class="text-red-500 text-xs italic mt-3"><%= @captcha_error %></div>
<% end %> <% end %>
@ -55,8 +55,8 @@
<%= submit "Start my free trial →", class: "button mt-4 w-full" %> <%= submit "Start my free trial →", class: "button mt-4 w-full" %>
<p class="text-center text-gray-600 text-xs mt-4"> <p class="text-center text-gray-600 dark:text-gray-500 text-xs mt-4">
Already have an account? <%= link("Log in", to: "/login", class: "underline text-gray-800") %> instead. Already have an account? <%= link("Log in", to: "/login", class: "underline text-gray-800 dark:text-gray-50") %> instead.
</p> </p>
<% end %> <% end %>
<div class="pt-12 pl-8 hidden md:block"> <div class="pt-12 pl-8 hidden md:block">

View File

@ -1,20 +1,20 @@
<div class="mx-auto mt-6 text-center"> <div class="mx-auto mt-6 text-center dark:text-gray-300">
<h1 class="text-3xl font-black">Register your 30-day unlimited-use free trial</h1> <h1 class="text-3xl font-black">Register your 30-day unlimited-use free trial</h1>
<div class="text-xl font-medium">Set up privacy-friendly analytics with just a few clicks</div> <div class="text-xl font-medium">Set up privacy-friendly analytics with just a few clicks</div>
</div> </div>
<div class="w-full max-w-3xl mt-4 mx-auto flex"> <div class="w-full max-w-3xl mt-4 mx-auto flex">
<%= form_for @conn, "/claim-activation", [class: "w-full max-w-lg mx-auto bg-white shadow-md rounded px-8 py-6 mb-4 mt-8", id: "register-form"], fn f -> %> <%= form_for @conn, "/claim-activation", [class: "w-full max-w-lg mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mb-4 mt-8", id: "register-form"], fn f -> %>
<h2 class="text-xl font-black">Activate your account</h2> <h2 class="text-xl font-black dark:text-gray-100">Activate your account</h2>
<div class="mt-2 text-sm text-gray-500 leading-tight"> <div class="mt-2 text-sm text-gray-500 dark:text-gray-200 leading-tight">
Please enter the 4-digit code we sent to <b><%= @email %></b> Please enter the 4-digit code we sent to <b><%= @email %></b>
</div> </div>
<div class="mt-12 flex items-stretch flex-grow"> <div class="mt-12 flex items-stretch flex-grow">
<div> <div>
<%= text_input f, :code, class: "tracking-widest font-medium shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-36 px-8 border-gray-300 rounded-l-md", oninput: "this.value=this.value.replace(/[^0-9]/g, ''); if (this.value.length >= 4) document.getElementById('submit').focus()", onclick: "this.select();", maxlength: "4", placeholder: "••••", style: "letter-spacing: 10px;" %> <%= text_input f, :code, class: "tracking-widest font-medium shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-36 px-8 border-gray-300 dark:border-gray-500 rounded-l-md dark:text-gray-200 dark:bg-gray-900", oninput: "this.value=this.value.replace(/[^0-9]/g, ''); if (this.value.length >= 4) document.getElementById('submit').focus()", onclick: "this.select();", maxlength: "4", placeholder: "••••", style: "letter-spacing: 10px;" %>
<%= error_tag f, :name %> <%= error_tag f, :name %>
</div> </div>
<button id="submit" class="button rounded-l-none">Activate &rarr;</button> <button id="submit" class="button rounded-l-none">Activate &rarr;</button>
@ -24,7 +24,7 @@
Didn't receive an email? Didn't receive an email?
</div> </div>
<div class="mt-2 text-sm text-gray-500 leading-tight"> <div class="mt-2 text-sm text-gray-500 leading-tight">
Please check your spam folder and contact <a class="underline text-gray-800" href="mailto:support@plausible.io">support@plausible.io</a> if the problem persists Please check your spam folder and contact <a class="underline text-indigo-500" href="mailto:support@plausible.io">support@plausible.io</a> if the problem persists
</div> </div>
<% end %> <% end %>

View File

@ -1,7 +1,7 @@
<%= if !Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_subscription) do %> <%= if !Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_subscription) do %>
<div class="max-w-2xl mx-auto bg-white shadow-md rounded rounded-t-none border-t-2 border-orange-200 px-8 pt-6 pb-8 mt-24"> <div class="max-w-2xl mx-auto bg-white dark:bg-gray-800 shadow-md rounded rounded-t-none border-t-2 border-orange-200 dark:border-orange-200 px-8 pt-6 pb-8 mt-24 ">
<div class="flex justify-between"> <div class="flex justify-between">
<h2 class="text-xl font-black">Subscription Plan</h2> <h2 class="text-xl font-black dark:text-gray-100">Subscription Plan</h2>
<%= if @subscription do %> <%= if @subscription do %>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-bold leading-5 <%= subscription_colors(@subscription.status) %>"> <span class="inline-flex items-center px-2.5 py-0.5 rounded-md text-sm font-bold leading-5 <%= subscription_colors(@subscription.status) %>">
<%= present_subscription_status(@subscription.status) %> <%= present_subscription_status(@subscription.status) %>
@ -31,48 +31,48 @@
<% end %> <% end %>
<div class="flex flex-col items-center sm:flex-row sm:items-start justify-between mt-8"> <div class="flex flex-col items-center sm:flex-row sm:items-start justify-between mt-8">
<div class="text-center bg-gray-100 py-4 px-2 rounded h-32 my-4" style="width: 11.75rem;"> <div class="text-center bg-gray-100 dark:bg-gray-900 py-4 px-2 rounded h-32 my-4" style="width: 11.75rem;">
<h4 class="font-black">Monthly quota</h4> <h4 class="font-black dark:text-gray-100">Monthly quota</h4>
<%= if @subscription do %> <%= if @subscription do %>
<div class="text-xl py-2 font-medium"><%= subscription_quota(@subscription) %> pageviews</div> <div class="text-xl py-2 font-medium dark:text-gray-100"><%= subscription_quota(@subscription) %> pageviews</div>
<%= case @subscription.status do %> <%= case @subscription.status do %>
<% "active" -> %> <% "active" -> %>
<%= link("Change plan", to: "/billing/change-plan", class: "text-sm text-indigo-500 font-medium") %> <%= link("Change plan", to: "/billing/change-plan", class: "text-sm text-indigo-500 font-medium") %>
<% "past_due" -> %> <% "past_due" -> %>
<span class="text-sm text-gray-600 font-medium" tooltip="Please update your billing details before changing plans">Change plan</span> <span class="text-sm text-gray-600 dark:text-gray-400 font-medium" tooltip="Please update your billing details before changing plans">Change plan</span>
<% _ -> %> <% _ -> %>
<% end %> <% end %>
<% else %> <% else %>
<div class="text-xl py-2 font-medium">Free trial</div> <div class="text-xl py-2 font-medium dark:text-gray-100">Free trial</div>
<%= link("Upgrade", to: "/billing/upgrade", class: "text-sm text-indigo-500 font-medium") %> <%= link("Upgrade", to: "/billing/upgrade", class: "text-sm text-indigo-500 font-medium") %>
<% end %> <% end %>
</div> </div>
<div class="text-center bg-gray-100 py-4 px-2 rounded h-32 my-4" style="width: 11.75rem;"> <div class="text-center bg-gray-100 dark:bg-gray-900 py-4 px-2 rounded h-32 my-4" style="width: 11.75rem;">
<h4 class="font-black">Next bill amount</h4> <h4 class="font-black dark:text-gray-100">Next bill amount</h4>
<%= if @subscription && @subscription.status in ["active", "past_due"] do %> <%= if @subscription && @subscription.status in ["active", "past_due"] do %>
<div class="text-xl py-2 font-medium">$<%= @subscription.next_bill_amount %></div> <div class="text-xl py-2 font-medium dark:text-gray-100">$<%= @subscription.next_bill_amount %></div>
<%= if @subscription.update_url do %> <%= if @subscription.update_url do %>
<%= link("Update billing info", to: @subscription.update_url, class: "text-sm text-indigo-500 font-medium") %> <%= link("Update billing info", to: @subscription.update_url, class: "text-sm text-indigo-500 font-medium") %>
<% end %> <% end %>
<% else %> <% else %>
<div class="text-xl py-2 font-medium">---</div> <div class="text-xl py-2 font-medium dark:text-gray-100">---</div>
<% end %> <% end %>
</div> </div>
<div class="text-center bg-gray-100 py-4 px-2 rounded h-32 my-4" style="width: 11.75rem;"> <div class="text-center bg-gray-100 dark:bg-gray-900 py-4 px-2 rounded h-32 my-4" style="width: 11.75rem;">
<h4 class="font-black">Next bill date</h4> <h4 class="font-black dark:text-gray-100">Next bill date</h4>
<%= if @subscription && @subscription.next_bill_date && @subscription.status in ["active", "past_due"] do %> <%= if @subscription && @subscription.next_bill_date && @subscription.status in ["active", "past_due"] do %>
<div class="text-xl py-2 font-medium"><%= Timex.format!(@subscription.next_bill_date, "{Mshort} {D}, {YYYY}") %></div> <div class="text-xl py-2 font-medium dark:text-gray-100"><%= Timex.format!(@subscription.next_bill_date, "{Mshort} {D}, {YYYY}") %></div>
<div class="text-sm text-gray-600 font-medium">(<%= subscription_interval(@subscription) %> billing)</div> <div class="text-sm text-gray-600 dark:text-gray-400 font-medium">(<%= subscription_interval(@subscription) %> billing)</div>
<% else %> <% else %>
<div class="text-xl py-2 font-medium">---</div> <div class="text-xl py-2 font-medium dark:text-gray-100">---</div>
<% end %> <% end %>
</div> </div>
</div> </div>
<h3 class="text-xl font-bold mt-8">Your usage</h3> <h3 class="text-xl font-bold mt-8 dark:text-gray-100">Your usage</h3>
<div class="py-2"> <div class="py-2 dark:text-gray-100">
<b><%= delimit_integer(Plausible.Billing.usage(@conn.assigns[:current_user])) %></b> <b><%= delimit_integer(Plausible.Billing.usage(@conn.assigns[:current_user])) %></b>
pageviews in the last 30 days pageviews in the last 30 days
</div> </div>
@ -80,7 +80,7 @@
<%= cond do %> <%= cond do %>
<% @subscription && @subscription.status in ["active", "past_due", "paused"] && @subscription.cancel_url -> %> <% @subscription && @subscription.status in ["active", "past_due", "paused"] && @subscription.cancel_url -> %>
<div class="mt-8"> <div class="mt-8">
<%= link("Cancel my subscription", to: @subscription.cancel_url, class: "inline-block mt-4 px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-red-700 bg-white hover:text-red-500 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150") %> <%= link("Cancel my subscription", to: @subscription.cancel_url, class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150") %>
</div> </div>
<% true -> %> <% true -> %>
<div class="mt-8"> <div class="mt-8">
@ -90,23 +90,37 @@
</div> </div>
<% end %> <% end %>
<div class="max-w-2xl mx-auto bg-white shadow-md rounded rounded-t-none border-t-2 border-indigo-100 px-8 pt-6 pb-8 mt-16"> <div class="max-w-2xl mx-auto bg-white dark:bg-gray-800 shadow-md rounded rounded-t-none border-t-2 border-green-500 px-8 pt-6 pb-8 mt-16">
<h2 class="text-xl font-black">Account settings</h2> <h2 class="text-xl font-black dark:text-gray-100">Dashboard Appearance</h2>
<div class="my-4 border-b border-gray-300"></div> <div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<%= form_for @changeset, "/settings", [class: "max-w-sm"], fn f -> %>
<div class="col-span-4 sm:col-span-2">
<%= label f, :theme, "Theme Selection", class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300" %>
<%= select f, :theme, Plausible.Themes.options(), class: "dark:bg-gray-900 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100 cursor-pointer" %>
</div>
<%= submit "Save", class: "button mt-4" %>
<% end %>
</div>
<div class="max-w-2xl mx-auto bg-white dark:bg-gray-800 shadow-md rounded rounded-t-none border-t-2 border-indigo-100 dark:border-indigo-500 px-8 pt-6 pb-8 mt-16">
<h2 class="text-xl font-black dark:text-gray-100">Account settings</h2>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<%= form_for @changeset, "/settings", [class: "max-w-sm"], fn f -> %> <%= form_for @changeset, "/settings", [class: "max-w-sm"], fn f -> %>
<div class="my-4"> <div class="my-4">
<%= label f, :name, class: "block text-sm font-medium text-gray-700" %> <%= label f, :name, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<div class="mt-1"> <div class="mt-1">
<%= text_input f, :name, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %> <%= text_input f, :name, class: "shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:bg-gray-800" %>
<%= error_tag f, :name %> <%= error_tag f, :name %>
</div> </div>
</div> </div>
<div class="my-4"> <div class="my-4">
<%= label f, :email, class: "block text-sm font-medium text-gray-700" %> <%= label f, :email, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<div class="mt-1"> <div class="mt-1">
<%= email_input f, :email, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %> <%= email_input f, :email, class: "shadow-sm dark:bg-gray-900 dark:text-gray-300 focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 dark:border-gray-500 rounded-md" %>
<%= error_tag f, :email %> <%= error_tag f, :email %>
</div> </div>
</div> </div>
@ -114,19 +128,19 @@
<% end %> <% end %>
</div> </div>
<div class="max-w-2xl mx-auto bg-white shadow-md rounded rounded-t-none border-t-2 border-red-600 px-8 pt-6 pb-8 mt-16 mb-24"> <div class="max-w-2xl mx-auto bg-white dark:bg-gray-800 shadow-md rounded rounded-t-none border-t-2 border-red-600 px-8 pt-6 pb-8 mt-16 mb-24">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h2 class="text-xl font-black">Delete account</h2> <h2 class="text-xl font-black dark:text-gray-100">Delete account</h2>
</div> </div>
<div class="my-4 border-b border-gray-300"></div> <div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<p>Deleting your account removes all sites and stats you've collected</p> <p class="dark:text-gray-100">Deleting your account removes all sites and stats you've collected</p>
<%= if @subscription && @subscription.status == "active" do %> <%= if @subscription && @subscription.status == "active" do %>
<span class="button bg-gray-300 mt-6 hover:shadow-none">Delete my account</span> <span class="button bg-gray-300 dark:bg-gray-800 mt-6 hover:shadow-none">Delete my account</span>
<p class="text-gray-600 text-sm mt-2">Your account cannot be deleted because you have an active subscription. If you want to delete your account, please cancel your subscription first.</p> <p class="text-gray-600 dark:text-gray-400 text-sm mt-2">Your account cannot be deleted because you have an active subscription. If you want to delete your account, please cancel your subscription first.</p>
<% else %> <% else %>
<%= link("Delete my account", to: "/me", class: "inline-block mt-4 px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-red-700 bg-white hover:text-red-500 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete", data: [confirm: "Deleting your account cannot be reversed. Are you sure?"]) %> <%= link("Delete my account", to: "/me", class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete", data: [confirm: "Deleting your account cannot be reversed. Are you sure?"]) %>
<% end %> <% end %>
</div> </div>

View File

@ -3,16 +3,16 @@
</script> </script>
<div class="mx-auto mt-6 text-center"> <div class="mx-auto mt-6 text-center">
<h1 class="text-3xl font-black">Change subscription plan</h1> <h1 class="text-3xl font-black dark:text-gray-100">Change subscription plan</h1>
</div> </div>
<div class="w-full max-w-lg px-4 mt-4 mx-auto"> <div class="w-full max-w-lg px-4 mt-4 mx-auto">
<div x-data="{volume: '10k', billingCycle: 'monthly'}" class="flex-1 bg-white shadow-md rounded px-8 py-4 mb-4 mt-8"> <div x-data="{volume: '10k', billingCycle: 'monthly'}" class="flex-1 bg-white dark:bg-gray-800 shadow-md rounded px-8 py-4 mb-4 mt-8">
<div class="w-full pt-2 text-xl font-bold"> <div class="w-full pt-2 text-xl font-bold dark:text-gray-100">
Select your new plan Select your new plan
</div> </div>
<div class="w-full text-gray-500 text-sm"> <div class="w-full text-gray-500 dark:text-gray-200 text-sm">
Depending on which plan you choose, your card might be charged immediately and your Depending on which plan you choose, your card might be charged immediately and your
next payment date could change. You can preview these changes before committing. next payment date could change. You can preview these changes before committing.
</div> </div>
@ -20,12 +20,12 @@
<div class="pt-8"></div> <div class="pt-8"></div>
<span class="relative z-0 inline-flex shadow-sm w-full"> <span class="relative z-0 inline-flex shadow-sm w-full">
<button type="button" @click="billingCycle = 'monthly'" :class="{'bg-indigo-600 text-white border-indigo-600': billingCycle === 'monthly', 'bg-white text-gray-700 hover:text-gray-500 border-gray-300': billingCycle === 'yearly'}" class="relative w-full text-center px-4 py-2 rounded-l-md border text-sm leading-5 font-medium focus:outline-none focus:border-blue-300 focus:ring transition ease-in-out duration-150"> <button type="button" @click="billingCycle = 'monthly'" :class="{'bg-indigo-600 text-white border-indigo-600': billingCycle === 'monthly', 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-200 border-gray-300 dark:border-gray-500': billingCycle === 'yearly'}" class="relative w-full text-center px-4 py-2 rounded-l-md border text-sm leading-5 font-medium focus:outline-none focus:border-blue-300 focus:ring transition ease-in-out duration-150">
Monthly billing Monthly billing
</button> </button>
<button type="button" @click="billingCycle = 'yearly'" :class="{'bg-indigo-600 text-white border-indigo-600': billingCycle === 'yearly', 'bg-white text-gray-700 hover:text-gray-500 border-gray-300': billingCycle === 'monthly'}" class="-ml-px relative w-full text-center px-4 py-2 rounded-r-md border text-sm leading-5 font-medium focus:outline-none focus:border-blue-300 focus:ring transition ease-in-out duration-150"> <button type="button" @click="billingCycle = 'yearly'" :class="{'bg-indigo-600 text-white border-indigo-600': billingCycle === 'yearly', 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-200 border-gray-300 dark:border-gray-500': billingCycle === 'monthly'}" class="-ml-px relative w-full text-center px-4 py-2 rounded-r-md border text-sm leading-5 font-medium focus:outline-none focus:border-blue-300 focus:ring transition ease-in-out duration-150">
Yearly billing Yearly billing
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-yellow-100 text-yellow-800"> <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-yellow-200 text-yellow-800 dark:text-yellow-900">
-33% -33%
</span> </span>
</button> </button>
@ -35,64 +35,64 @@
<div class="flex flex-col"> <div class="flex flex-col">
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8"> <div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200"> <div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-t dark:border-l dark:border-r dark:shadow-none">
<table class="min-w-full"> <table class="min-w-full">
<thead> <thead>
<tr> <tr>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
Monthly pageviews Monthly pageviews
</th> </th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
Price per month Price per month
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white"> <tbody class="bg-white dark:bg-gray-800">
<tr @click="volume = '10k'" :class="{'border-2 border-green-300 bg-green-50': volume === '10k', 'border-b border-gray-200 cursor-pointer': volume !== '10k'}"> <tr @click="volume = '10k'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '10k', 'border-b border-gray-200 cursor-pointer': volume !== '10k'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '10k'}">10k</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '10k'}">10k</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '10k'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '10k'}">
<span x-show="billingCycle === 'monthly'">$6</span> <span x-show="billingCycle === 'monthly'">$6</span>
<span x-show="billingCycle === 'yearly'">$4</span> <span x-show="billingCycle === 'yearly'">$4</span>
</td> </td>
</tr> </tr>
<tr @click="volume = '100k'" :class="{'border-2 border-green-300 bg-green-50': volume === '100k', 'border-b border-gray-200 cursor-pointer': volume !== '100k'}"> <tr @click="volume = '100k'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '100k', 'border-b border-gray-200 cursor-pointer': volume !== '100k'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '100k'}">100k</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '100k'}">100k</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '100k'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '100k'}">
<span x-show="billingCycle === 'monthly'">$12</span> <span x-show="billingCycle === 'monthly'">$12</span>
<span x-show="billingCycle === 'yearly'">$8</span> <span x-show="billingCycle === 'yearly'">$8</span>
</td> </td>
</tr> </tr>
<tr @click="volume = '200k'" :class="{'border-2 border-green-300 bg-green-50': volume === '200k', 'border-b border-gray-200 cursor-pointer': volume !== '200k'}"> <tr @click="volume = '200k'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '200k', 'border-b border-gray-200 cursor-pointer': volume !== '200k'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '200k'}">200k</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '200k'}">200k</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '200k'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '200k'}">
<span x-show="billingCycle === 'monthly'">$18</span> <span x-show="billingCycle === 'monthly'">$18</span>
<span x-show="billingCycle === 'yearly'">$12</span> <span x-show="billingCycle === 'yearly'">$12</span>
</td> </td>
</tr> </tr>
<tr @click="volume = '500k'" :class="{'border-2 border-green-300 bg-green-50': volume === '500k', 'border-b border-gray-200 cursor-pointer': volume !== '500k'}"> <tr @click="volume = '500k'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '500k', 'border-b border-gray-200 cursor-pointer': volume !== '500k'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '500k'}">500k</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '500k'}">500k</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '500k'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '500k'}">
<span x-show="billingCycle === 'monthly'">$27</span> <span x-show="billingCycle === 'monthly'">$27</span>
<span x-show="billingCycle === 'yearly'">$18</span> <span x-show="billingCycle === 'yearly'">$18</span>
</td> </td>
</tr> </tr>
<tr @click="volume = '1m'" :class="{'border-2 border-green-300 bg-green-50': volume === '1m', 'border-b border-gray-200 cursor-pointer': volume !== '1m'}"> <tr @click="volume = '1m'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '1m', 'border-b border-gray-200 cursor-pointer': volume !== '1m'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '1m'}">1m</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '1m'}">1m</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '1m'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '1m'}">
<span x-show="billingCycle === 'monthly'">$48</span> <span x-show="billingCycle === 'monthly'">$48</span>
<span x-show="billingCycle === 'yearly'">$32</span> <span x-show="billingCycle === 'yearly'">$32</span>
</td> </td>
</tr> </tr>
<tr @click="volume = '2m'" :class="{'border-2 border-green-300 bg-green-50': volume === '2m', 'border-b border-gray-200 cursor-pointer': volume !== '1m'}"> <tr @click="volume = '2m'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '2m', 'border-b border-gray-200 cursor-pointer': volume !== '1m'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '2m'}">2m</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '2m'}">2m</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '2m'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '2m'}">
<span x-show="billingCycle === 'monthly'">$69</span> <span x-show="billingCycle === 'monthly'">$69</span>
<span x-show="billingCycle === 'yearly'">$46</span> <span x-show="billingCycle === 'yearly'">$46</span>
</td> </td>
</tr> </tr>
<tr @click="volume = '5m'" :class="{'border-2 border-green-300 bg-green-50': volume === '5m', 'border-b border-gray-200 cursor-pointer': volume !== '1m'}"> <tr @click="volume = '5m'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': volume === '5m', 'border-b border-gray-200 cursor-pointer': volume !== '1m'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '5m'}">5m</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '5m'}">5m</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': volume === '5m'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': volume === '5m'}">
<span x-show="billingCycle === 'monthly'">$99</span> <span x-show="billingCycle === 'monthly'">$99</span>
<span x-show="billingCycle === 'yearly'">$66</span> <span x-show="billingCycle === 'yearly'">$66</span>
</td> </td>
@ -110,7 +110,7 @@
Preview changes Preview changes
</a> </a>
<a x-show="window.plans[billingCycle][volume].product_id === '<%= @subscription.paddle_plan_id %>'" style="display: none;" tooltip="Select a different plan to continue" class="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-gray-400"> <a x-show="window.plans[billingCycle][volume].product_id === @subscription.paddle_plan_id %>'" style="display: none;" tooltip="Select a different plan to continue" class="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-gray-400">
<svg fill="currentColor" viewBox="0 0 20 20" class="w-4 h-4 inline mr-2"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path></svg> <svg fill="currentColor" viewBox="0 0 20 20" class="w-4 h-4 inline mr-2"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path></svg>
Preview changes Preview changes
</a> </a>
@ -119,7 +119,7 @@
</div> </div>
</div> </div>
<div class="text-center mt-8"> <div class="text-center mt-8 dark:text-gray-100">
Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %> Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %>
</div> </div>

View File

@ -1,32 +1,32 @@
<div class="mx-auto mt-6 text-center"> <div class="mx-auto mt-6 text-center">
<h1 class="text-3xl font-black">Confirm new subscription plan</h1> <h1 class="text-3xl font-black dark:text-gray-100">Confirm new subscription plan</h1>
</div> </div>
<div class="w-full max-w-lg px-4 mt-4 mx-auto"> <div class="w-full max-w-lg px-4 mt-4 mx-auto">
<div class="flex-1 bg-white shadow-md rounded px-8 py-4 mb-4 mt-8"> <div class="flex-1 bg-white dark:bg-gray-800 shadow-md rounded px-8 py-4 mb-4 mt-8">
<div class="text-lg font-bold">Due now</div> <div class="text-lg font-bold dark:text-gray-100">Due now</div>
<div class="block text-gray-500 text-sm"> <div class="block text-gray-500 dark:text-gray-200 text-sm">
Your card will be charged a pro-rated amount for the current billing period Your card will be charged a pro-rated amount for the current billing period
</div> </div>
<div class="flex flex-col mt-4"> <div class="flex flex-col mt-4">
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8"> <div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200"> <div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-t dark:border-l dark:border-r dark:shadow-none">
<table class="min-w-full"> <table class="min-w-full">
<thead> <thead>
<tr> <tr>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
Amount Amount
</th> </th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
Date Date
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white"> <tbody class="bg-white dark:bg-gray-800">
<tr class="border-b border-gray-200"> <tr class="border-b border-gray-200">
<td class="px-6 py-4 text-sm leading-5 font-bold">$<%= @preview_info["immediate_payment"]["amount"] %></td> <td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100">$<%= @preview_info["immediate_payment"]["amount"] %></td>
<td class="px-6 py-4 text-sm leading-5"><%= present_date(@preview_info["immediate_payment"]["date"]) %></td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100"><%= present_date(@preview_info["immediate_payment"]["date"]) %></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -41,22 +41,22 @@
<div class="flex flex-col"> <div class="flex flex-col">
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8"> <div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200"> <div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-t dark:border-l dark:border-r dark:shadow-none">
<table class="min-w-full"> <table class="min-w-full">
<thead> <thead>
<tr> <tr>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
Amount Amount
</th> </th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
Date Date
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white"> <tbody class="bg-white dark:bg-gray-800">
<tr class="border-b border-gray-200"> <tr class="border-b border-gray-200">
<td class="px-6 py-4 text-sm leading-5 font-bold">$<%= @preview_info["next_payment"]["amount"] %></td> <td class="px-6 py-4 text-sm leading-5 font-bold dark:text-gray-100">$<%= @preview_info["next_payment"]["amount"] %></td>
<td class="px-6 py-4 text-sm leading-5"><%= present_date(@preview_info["next_payment"]["date"]) %></td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100"><%= present_date(@preview_info["next_payment"]["date"]) %></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -66,18 +66,18 @@
<div class="flex items-center justify-between mt-10"> <div class="flex items-center justify-between mt-10">
<span class="flex rounded-md shadow-sm"> <span class="flex rounded-md shadow-sm">
<a href="/billing/change-plan" type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:ring active:text-gray-800 active:bg-gray-50 transition ease-in-out duration-150"> <a href="/billing/change-plan" type="button" class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:text-gray-500 dark:hover:text-gray-200 focus:outline-none focus:border-blue-300 focus:ring active:text-gray-800 dark:active:text-gray-200 active:bg-gray-50 transition ease-in-out duration-150">
Back Back
</a> </a>
</span> </span>
<span class="flex space-betwee rounded-md shadow-sm"> <span class="flex space-betwee rounded-md shadow-sm">
<%= button("Confirm plan change", to: "/billing/change-plan/#{@preview_info["plan_id"]}", method: :post, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150") %> <%= button("Confirm plan change", to: "/billing/change-plan/", method: :post, class: "inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150") %>
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div class="text-center mt-8"> <div class="text-center mt-8 dark:text-gray-100">
Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %> Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %>
</div> </div>

View File

@ -3,25 +3,25 @@
</script> </script>
<div class="mx-auto mt-6 text-center"> <div class="mx-auto mt-6 text-center">
<h1 class="text-3xl font-black">Upgrade your free trial</h1> <h1 class="text-3xl font-black dark:text-gray-100">Upgrade your free trial</h1>
</div> </div>
<div> <div>
<div class="w-full max-w-4xl px-4 mt-4 mx-auto flex flex-col md:flex-row"> <div class="w-full max-w-4xl px-4 mt-4 mx-auto flex flex-col md:flex-row">
<div x-data="{planSize: '10k', billingCycle: 'monthly'}" class="flex-1 bg-white shadow-md rounded px-8 py-4 mb-4 mt-8"> <div x-data="{planSize: '10k', billingCycle: 'monthly'}" class="flex-1 bg-white dark:bg-gray-800 shadow-md rounded px-8 py-4 mb-4 mt-8">
<div class="w-full py-4"> <div class="w-full py-4 dark:text-gray-100">
<span>You've used <b><%= PlausibleWeb.AuthView.delimit_integer(@usage) %></b> pageviews in the last 30 days</span> <span>You've used <b><%= PlausibleWeb.AuthView.delimit_integer(@usage) %></b> pageviews in the last 30 days</span>
</div> </div>
<div class="pt-2"></div> <div class="pt-2"></div>
<span class="relative z-0 inline-flex shadow-sm w-full"> <span class="relative z-0 inline-flex shadow-sm w-full">
<button type="button" @click="billingCycle = 'monthly'" :class="{'bg-indigo-600 text-white border-indigo-600': billingCycle === 'monthly', 'bg-white text-gray-700 hover:text-gray-500 border-gray-300': billingCycle === 'yearly'}" class="relative w-full text-center px-4 py-2 rounded-l-md border text-sm leading-5 font-medium focus:outline-none focus:border-blue-300 focus:ring transition ease-in-out duration-150"> <button type="button" @click="billingCycle = 'monthly'" :class="{'bg-indigo-600 text-white border-indigo-600': billingCycle === 'monthly', 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-200 border-gray-300 dark:border-gray-500': billingCycle === 'yearly'}" class="relative w-full text-center px-4 py-2 rounded-l-md border text-sm leading-5 font-medium focus:outline-none focus:border-blue-300 focus:ring transition ease-in-out duration-150">
Monthly billing Monthly billing
</button> </button>
<button type="button" @click="billingCycle = 'yearly'" :class="{'bg-indigo-600 text-white border-indigo-600': billingCycle === 'yearly', 'bg-white text-gray-700 hover:text-gray-500 border-gray-300': billingCycle === 'monthly'}" class="-ml-px relative w-full text-center px-4 py-2 rounded-r-md border text-sm leading-5 font-medium focus:outline-none focus:border-blue-300 focus:ring transition ease-in-out duration-150"> <button type="button" @click="billingCycle = 'yearly'" :class="{'bg-indigo-600 text-white border-indigo-600': billingCycle === 'yearly', 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:text-gray-500 dark:hover:text-gray-200 border-gray-300 dark:border-gray-500': billingCycle === 'monthly'}" class="-ml-px relative w-full text-center px-4 py-2 rounded-r-md border text-sm leading-5 font-medium focus:outline-none focus:border-blue-300 focus:ring transition ease-in-out duration-150">
Yearly billing Yearly billing
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-yellow-100 text-yellow-800"> <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium leading-4 bg-yellow-200 text-yellow-800 dark:text-yellow-900">
-33% -33%
</span> </span>
</button> </button>
@ -31,64 +31,64 @@
<div class="flex flex-col"> <div class="flex flex-col">
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8"> <div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200"> <div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200 dark:border-t dark:border-l dark:border-r dark:shadow-none">
<table class="min-w-full"> <table class="min-w-full">
<thead> <thead>
<tr> <tr>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
Monthly pageviews Monthly pageviews
</th> </th>
<th class="px-6 py-3 border-b border-gray-200 bg-gray-100 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 border-b border-gray-200 bg-gray-100 dark:bg-gray-900 text-left text-xs leading-4 font-medium text-gray-500 dark:text-gray-200 uppercase tracking-wider">
Price per month Price per month
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody class="bg-white"> <tbody class="bg-white dark:bg-gray-800">
<tr @click="planSize = '10k'" :class="{'border-2 border-green-300': planSize === '10k', 'border-b border-gray-200 cursor-pointer': planSize !== '10k'}"> <tr @click="planSize = '10k'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': planSize === '10k', 'border-b border-gray-200 cursor-pointer': planSize !== '10k'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '10k'}">10k</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '10k'}">10k</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '10k'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '10k'}">
<span x-show="billingCycle === 'monthly'">$6</span> <span x-show="billingCycle === 'monthly'">$6</span>
<span x-show="billingCycle === 'yearly'">$4</span> <span x-show="billingCycle === 'yearly'">$4</span>
</td> </td>
</tr> </tr>
<tr @click="planSize = '100k'" :class="{'border-2 border-green-300': planSize === '100k', 'border-b border-gray-200 cursor-pointer': planSize !== '100k'}"> <tr @click="planSize = '100k'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': planSize === '100k', 'border-b border-gray-200 cursor-pointer': planSize !== '100k'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '100k'}">100k</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '100k'}">100k</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '100k'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '100k'}">
<span x-show="billingCycle === 'monthly'">$12</span> <span x-show="billingCycle === 'monthly'">$12</span>
<span x-show="billingCycle === 'yearly'">$8</span> <span x-show="billingCycle === 'yearly'">$8</span>
</td> </td>
</tr> </tr>
<tr @click="planSize = '200k'" :class="{'border-2 border-green-300': planSize === '200k', 'border-b border-gray-200 cursor-pointer': planSize !== '200k'}"> <tr @click="planSize = '200k'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': planSize === '200k', 'border-b border-gray-200 cursor-pointer': planSize !== '200k'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '200k'}">200k</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '200k'}">200k</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '200k'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '200k'}">
<span x-show="billingCycle === 'monthly'">$18</span> <span x-show="billingCycle === 'monthly'">$18</span>
<span x-show="billingCycle === 'yearly'">$12</span> <span x-show="billingCycle === 'yearly'">$12</span>
</td> </td>
</tr> </tr>
<tr @click="planSize = '500k'" :class="{'border-2 border-green-300': planSize === '500k', 'border-b border-gray-200 cursor-pointer': planSize !== '500k'}"> <tr @click="planSize = '500k'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': planSize === '500k', 'border-b border-gray-200 cursor-pointer': planSize !== '500k'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '500k'}">500k</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '500k'}">500k</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '500k'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '500k'}">
<span x-show="billingCycle === 'monthly'">$27</span> <span x-show="billingCycle === 'monthly'">$27</span>
<span x-show="billingCycle === 'yearly'">$18</span> <span x-show="billingCycle === 'yearly'">$18</span>
</td> </td>
</tr> </tr>
<tr @click="planSize = '1m'" :class="{'border-2 border-green-300': planSize === '1m', 'border-b border-gray-200 cursor-pointer': planSize !== '1m'}"> <tr @click="planSize = '1m'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': planSize === '1m', 'border-b border-gray-200 cursor-pointer': planSize !== '1m'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '1m'}">1m</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '1m'}">1m</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '1m'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '1m'}">
<span x-show="billingCycle === 'monthly'">$48</span> <span x-show="billingCycle === 'monthly'">$48</span>
<span x-show="billingCycle === 'yearly'">$32</span> <span x-show="billingCycle === 'yearly'">$32</span>
</td> </td>
</tr> </tr>
<tr @click="planSize = '2m'" :class="{'border-2 border-green-300': planSize === '2m', 'border-b border-gray-200 cursor-pointer': planSize !== '2m'}"> <tr @click="planSize = '2m'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': planSize === '2m', 'border-b border-gray-200 cursor-pointer': planSize !== '2m'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '2m'}">2m</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '2m'}">2m</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '2m'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '2m'}">
<span x-show="billingCycle === 'monthly'">$69</span> <span x-show="billingCycle === 'monthly'">$69</span>
<span x-show="billingCycle === 'yearly'">$46</span> <span x-show="billingCycle === 'yearly'">$46</span>
</td> </td>
</tr> </tr>
<tr @click="planSize = '5m'" :class="{'border-2 border-green-300': planSize === '5m', 'border-b border-gray-200 cursor-pointer': planSize !== '5m'}"> <tr @click="planSize = '5m'" :class="{'border-2 border-green-300 bg-opacity-20 bg-green-300': planSize === '5m', 'border-b border-gray-200 cursor-pointer': planSize !== '5m'}">
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '5m'}">5m</td> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '5m'}">5m</td>
<td class="px-6 py-4 text-sm leading-5" :class="{'font-bold': planSize === '5m'}"> <td class="px-6 py-4 text-sm leading-5 dark:text-gray-100" :class="{'font-bold': planSize === '5m'}">
<span x-show="billingCycle === 'monthly'">$99</span> <span x-show="billingCycle === 'monthly'">$99</span>
<span x-show="billingCycle === 'yearly'">$66</span> <span x-show="billingCycle === 'yearly'">$66</span>
</td> </td>
@ -100,7 +100,7 @@
</div> </div>
<div class="text-right mt-6"> <div class="text-right mt-6">
<div class="mb-4 text-sm font-medium">Due today: <b x-text="window.plans[billingCycle][planSize].due_now">$6</b></div> <div class="mb-4 text-sm font-medium dark:text-gray-100">Due today: <b x-text="window.plans[billingCycle][planSize].due_now">$6</b></div>
<span class="inline-flex rounded-md shadow-sm"> <span class="inline-flex rounded-md shadow-sm">
<button type="button" data-theme="none" :data-product="window.plans[billingCycle][planSize].product_id" data-email="<%= @conn.assigns[:current_user].email %>" data-disable-logout="true" data-passthrough="<%= @conn.assigns[:current_user].id %>" data-success="/billing/upgrade-success" class="paddle_button inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150"> <button type="button" data-theme="none" :data-product="window.plans[billingCycle][planSize].product_id" data-email="<%= @conn.assigns[:current_user].email %>" data-disable-logout="true" data-passthrough="<%= @conn.assigns[:current_user].id %>" data-success="/billing/upgrade-success" class="paddle_button inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:ring active:bg-indigo-700 transition ease-in-out duration-150">
<svg fill="currentColor" viewBox="0 0 20 20" class="w-4 h-4 inline mr-2"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg> <svg fill="currentColor" viewBox="0 0 20 20" class="w-4 h-4 inline mr-2"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg>
@ -111,10 +111,10 @@
</div> </div>
<div class="flex-1 pt-14 pl-8"> <div class="flex-1 pt-14 pl-8">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">
What happens if I go over my page views limit? What happens if I go over my page views limit?
</h3> </h3>
<div class="mt-2 text-base leading-6 text-gray-500"> <div class="mt-2 text-base leading-6 text-gray-500 dark:text-gray-200">
You will never be charged extra for an occasional traffic spike. There are no surprise fees and your card will never be charged unexpectedly.<br /><br /> You will never be charged extra for an occasional traffic spike. There are no surprise fees and your card will never be charged unexpectedly.<br /><br />
If your page views exceed your plan for two consecutive months, we will contact you to upgrade to a higher plan for the following month. You will have two weeks to make a decision. You can decide to continue with a higher plan or to cancel your account at that point. If your page views exceed your plan for two consecutive months, we will contact you to upgrade to a higher plan for the following month. You will have two weeks to make a decision. You can decide to continue with a higher plan or to cancel your account at that point.
</div> </div>
@ -123,7 +123,7 @@
</div> </div>
</div> </div>
<div class="text-center mt-8"> <div class="text-center mt-8 dark:text-gray-100">
Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %> Questions? Contact <%= link("support@plausible.io", to: "mailto: support@plausible.io", class: "text-indigo-500") %>
</div> </div>

View File

@ -1,10 +1,10 @@
<div class="w-full max-w-lg px-4 mt-4 mx-auto"> <div class="w-full max-w-lg px-4 mt-4 mx-auto">
<div x-data="{volume: '10k', billingCycle: 'monthly'}" class="flex-1 bg-white shadow-md rounded px-8 py-4 mb-4 mt-8"> <div x-data="{volume: '10k', billingCycle: 'monthly'}" class="flex-1 bg-white dark:bg-gray-800 shadow-md rounded px-8 py-4 mb-4 mt-8">
<div class="w-full pt-2 text-xl font-bold"> <div class="w-full pt-2 text-xl font-bold dark:text-gray-100">
Subscription created succesfully Subscription created succesfully
</div> </div>
<div class="w-full text-gray-500 text-sm py-4"> <div class="w-full text-gray-500 dark:text-gray-200 text-sm py-4">
Thank you for upgrading your subscription. You will be redirected to your Thank you for upgrading your subscription. You will be redirected to your
account within 5 seconds. account within 5 seconds.
</div> </div>

View File

@ -8,11 +8,13 @@
<link rel="icon" type="image/png" sizes="32x32" href="<%= PlausibleWeb.Router.Helpers.static_path(@conn, "/images/icon/plausible_favicon.png") %>"> <link rel="icon" type="image/png" sizes="32x32" href="<%= PlausibleWeb.Router.Helpers.static_path(@conn, "/images/icon/plausible_favicon.png") %>">
<title><%= assigns[:title] || "Plausible · Web analytics" %></title> <title><%= assigns[:title] || "Plausible · Web analytics" %></title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/> <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<script type="text/javascript" data-pref="<%= @conn.assigns[:current_user] && @conn.assigns[:current_user].theme %>" src="<%= Routes.static_path(@conn, "/js/applyTheme.js") %>"></script>
</head> </head>
<body class="flex flex-col h-full bg-gray-100"> <body class="flex flex-col h-full bg-gray-100 dark:bg-gray-900">
<div class="container text-center mt-24"> <div class="container text-center mt-24">
<h1 class="text-5xl font-black"><%= @status %></h1> <h1 class="text-5xl font-black dark:text-gray-100"><%= @status %></h1>
<div class="my-4 text-xl"><%= @message %></div> <div class="my-4 text-xl dark:text-gray-100"><%= @message %></div>
<%= link("Go to the homepage", to: PlausibleWeb.LayoutView.home_dest(@conn), class: "button mt-4") %> <%= link("Go to the homepage", to: PlausibleWeb.LayoutView.home_dest(@conn), class: "button mt-4") %>
</div> </div>
</body> </body>

View File

@ -1,6 +1,6 @@
<%= if get_flash(@conn, :success) do %> <%= if get_flash(@conn, :success) do %>
<div class="z-50 fixed inset-0 flex items-end justify-center px-4 py-6 pointer-events-none sm:p-6 sm:items-start sm:justify-end"> <div class="z-50 fixed inset-0 flex items-end justify-center px-4 py-6 pointer-events-none sm:p-6 sm:items-start sm:justify-end">
<div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 4000)" x-transition:enter="transform ease-out duration-300 transition" x-transition:enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" x-transition:enter-end="translate-y-0 opacity-100 sm:translate-x-0" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto"> <div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 4000)" x-transition:enter="transform ease-out duration-300 transition" x-transition:enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" x-transition:enter-end="translate-y-0 opacity-100 sm:translate-x-0" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="max-w-sm w-full bg-white dark:bg-gray-800 shadow-lg rounded-lg pointer-events-auto">
<div class="rounded-lg ring-1 ring-black ring-opacity-5 overflow-hidden"> <div class="rounded-lg ring-1 ring-black ring-opacity-5 overflow-hidden">
<div class="p-4"> <div class="p-4">
<div class="flex items-start"> <div class="flex items-start">
@ -10,15 +10,15 @@
</svg> </svg>
</div> </div>
<div class="ml-3 w-0 flex-1 pt-0.5"> <div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm leading-5 font-medium text-gray-900"> <p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
<%= get_flash(@conn, :success_title) || "Success!" %> <%= get_flash(@conn, :success_title) || "Success!" %>
</p> </p>
<p class="mt-1 text-sm leading-5 text-gray-500"> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
<%= get_flash(@conn, :success) %> <%= get_flash(@conn, :success) %>
</p> </p>
</div> </div>
<div class="ml-4 flex-shrink-0 flex"> <div class="ml-4 flex-shrink-0 flex">
<button class="inline-flex text-gray-400 focus:outline-none focus:text-gray-500 transition ease-in-out duration-150" @click="show = false"> <button class="inline-flex text-gray-400 focus:outline-none focus:text-gray-500 dark:focus:text-gray-200 transition ease-in-out duration-150" @click="show = false">
<!-- Heroicon name: x --> <!-- Heroicon name: x -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
@ -34,7 +34,7 @@
<%= if get_flash(@conn, :error) do %> <%= if get_flash(@conn, :error) do %>
<div class="z-50 fixed inset-0 flex items-end justify-center px-4 py-6 pointer-events-none sm:p-6 sm:items-start sm:justify-end"> <div class="z-50 fixed inset-0 flex items-end justify-center px-4 py-6 pointer-events-none sm:p-6 sm:items-start sm:justify-end">
<div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 4000)" x-transition:enter="transform ease-out duration-300 transition" x-transition:enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" x-transition:enter-end="translate-y-0 opacity-100 sm:translate-x-0" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto"> <div x-data="{ show: true }" x-show="show" x-init="setTimeout(() => show = false, 4000)" x-transition:enter="transform ease-out duration-300 transition" x-transition:enter-start="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" x-transition:enter-end="translate-y-0 opacity-100 sm:translate-x-0" x-transition:leave="transition ease-in duration-100" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="max-w-sm w-full bg-white dark:bg-gray-800 shadow-lg rounded-lg pointer-events-auto">
<div class="rounded-lg ring-1 ring-black ring-opacity-5 overflow-hidden"> <div class="rounded-lg ring-1 ring-black ring-opacity-5 overflow-hidden">
<div class="p-4"> <div class="p-4">
<div class="flex items-start"> <div class="flex items-start">
@ -42,15 +42,15 @@
<svg class="w-6 h-6 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <svg class="w-6 h-6 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
</div> </div>
<div class="ml-3 w-0 flex-1 pt-0.5"> <div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm leading-5 font-medium text-gray-900"> <p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
<%= get_flash(@conn, :error_title) || "Error" %> <%= get_flash(@conn, :error_title) || "Error" %>
</p> </p>
<p class="mt-1 text-sm leading-5 text-gray-500"> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">
<%= get_flash(@conn, :error) %> <%= get_flash(@conn, :error) %>
</p> </p>
</div> </div>
<div class="ml-4 flex-shrink-0 flex"> <div class="ml-4 flex-shrink-0 flex">
<button class="inline-flex text-gray-400 focus:outline-none focus:text-gray-500 transition ease-in-out duration-150" @click="show = false"> <button class="inline-flex text-gray-400 focus:outline-none focus:text-gray-500 dark:focus:text-gray-200 transition ease-in-out duration-150" @click="show = false">
<!-- Heroicon name: x --> <!-- Heroicon name: x -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />

View File

@ -1,4 +1,4 @@
<div class="bg-gray-800 mt-12"> <div class="bg-gray-800 dark:bg-gray-800 mt-12">
<div class="container py-12 px-4 sm:px-6 lg:py-16 lg:px-8"> <div class="container py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
<div class="xl:grid xl:grid-cols-3 xl:gap-8"> <div class="xl:grid xl:grid-cols-3 xl:gap-8">
<div class="my-8 xl:my-0"> <div class="my-8 xl:my-0">

View File

@ -1,5 +1,5 @@
<%= if is_current_tab(@conn, @this_tab) do %> <%= if is_current_tab(@conn, @this_tab) do %>
<a href="/<%= URI.encode_www_form(@site.domain) %>/settings/<%= @this_tab %>" class="flex items-center px-3 py-2 text-sm leading-5 font-medium text-gray-900 rounded-md bg-gray-100 hover:text-gray-900 hover:bg-gray-100 focus:outline-none focus:bg-gray-200 transition ease-in-out duration-150 <%= if @this_tab != "general", do: "mt-1" %>"><%= @text %></a> <a href="/<%= URI.encode_www_form(@site.domain) %>/settings/<%= @this_tab %>" class="flex items-center px-3 py-2 text-sm leading-5 font-medium text-gray-900 dark:text-gray-100 rounded-md bg-gray-100 dark:bg-gray-900 hover:text-gray-900 hover:bg-gray-100 outline-none focus:outline-none focus:bg-gray-200 dark:focus:bg-gray-800 transition ease-in-out duration-150 cursor-default <%= if @this_tab != "general", do: "mt-1" %>"><%= @text %></a>
<% else %> <% else %>
<a href="/<%= URI.encode_www_form(@site.domain) %>/settings/<%= @this_tab %>" class="flex items-center px-3 py-2 text-sm leading-5 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50 focus:outline-none focus:text-gray-900 focus:bg-gray-50 transition ease-in-out duration-150 <%= if @this_tab != "general", do: "mt-1" %>"><%= @text %></a> <a href="/<%= URI.encode_www_form(@site.domain) %>/settings/<%= @this_tab %>" class="flex items-center px-3 py-2 text-sm leading-5 font-medium text-gray-600 dark:text-gray-400 rounded-md hover:text-gray-900 dark:hover:text-gray-100 hover:bg-gray-50 dark:hover:bg-gray-800 outline-none focus:outline-none focus:text-gray-900 focus:bg-gray-50 dark:focus:text-gray-100 dark:focus:bg-gray-800 transition ease-in-out duration-150 <%= if @this_tab != "general", do: "mt-1" %>"><%= @text %></a>
<% end %> <% end %>

View File

@ -10,14 +10,18 @@
<title><%= assigns[:title] || "Plausible · Simple, privacy-friendly alternative to Google Analytics" %></title> <title><%= assigns[:title] || "Plausible · Simple, privacy-friendly alternative to Google Analytics" %></title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/> <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<%= render("_tracking.html", assigns) %> <%= render("_tracking.html", assigns) %>
<script type="text/javascript" data-pref="<%= @conn.assigns[:current_user] && @conn.assigns[:current_user].theme %>" src="<%= Routes.static_path(@conn, "/js/applyTheme.js") %>"></script>
</head> </head>
<body class="flex flex-col h-full bg-gray-50"> <body class="flex flex-col h-full bg-gray-50 dark:bg-gray-850">
<nav class="relative py-8 z-10"> <nav class="relative py-8 z-10">
<div class="container"> <div class="container">
<nav class="relative flex items-center justify-between sm:h-10 md:justify-center"> <nav class="relative flex items-center justify-between sm:h-10 md:justify-center">
<div class="flex items-center flex-1 md:absolute md:inset-y-0 md:left-0"> <div class="flex items-center flex-1 md:absolute md:inset-y-0 md:left-0">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a href="<%= home_dest(@conn) %>"><%= img_tag(PlausibleWeb.Router.Helpers.static_path(@conn, "/images/icon/plausible_logo.png"), class: "h-8 w-auto sm:h-10 -mt-2", alt: "Plausible logo") %></a> <a href="<%= home_dest(@conn) %>">
<%= img_tag(PlausibleWeb.Router.Helpers.static_path(@conn, "/images/icon/plausible_logo_dark.png"), class: "h-8 w-auto sm:h-10 -mt-2 hidden dark:inline", alt: "Plausible logo")%>
<%= img_tag(PlausibleWeb.Router.Helpers.static_path(@conn, "/images/icon/plausible_logo.png"), class: "h-8 w-auto sm:h-10 -mt-2 inline dark:hidden", alt: "Plausible logo") %>
</a>
</div> </div>
</div> </div>
<div class="absolute flex items-center justify-end inset-y-0 right-0"> <div class="absolute flex items-center justify-end inset-y-0 right-0">
@ -26,23 +30,23 @@
<ul class="flex"> <ul class="flex">
<%= if !Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_subscription) && @conn.assigns[:current_user].subscription == nil do %> <%= if !Keyword.fetch!(Application.get_env(:plausible, :selfhost), :disable_subscription) && @conn.assigns[:current_user].subscription == nil do %>
<li class="mr-6 hidden sm:block"> <li class="mr-6 hidden sm:block">
<%= link(trial_notificaton(@conn.assigns[:current_user]), to: "/settings", class: "font-bold text-orange-900 rounded p-2 bg-orange-200", style: "line-height: 40px;") %> <%= link(trial_notificaton(@conn.assigns[:current_user]), to: "/settings", class: "font-bold text-orange-900 dark:text-yellow-900 rounded p-2 bg-orange-200 dark:bg-yellow-100", style: "line-height: 40px;") %>
</li> </li>
<% else %> <% else %>
<li class="mr-6 hidden sm:block"> <li class="mr-6 hidden sm:block">
<%= link("Docs", to: "https://docs.plausible.io", class: "font-bold rounded p-2 hover:bg-gray-200", style: "line-height: 40px;", target: "_blank") %> <%= link("Docs", to: "https://docs.plausible.io", class: "font-bold rounded p-2 hover:bg-gray-200 dark:hover:bg-gray-900 dark:text-gray-100", style: "line-height: 40px;", target: "_blank") %>
</li> </li>
<% end %> <% end %>
<li> <li>
<div class="relative font-bold rounded"> <div class="relative font-bold rounded">
<div data-dropdown-trigger class="flex items-center hover:bg-gray-200 rounded p-2 cursor-pointer"> <div data-dropdown-trigger class="flex items-center hover:bg-gray-200 dark:hover:bg-gray-900 rounded p-2 cursor-pointer dark:text-gray-100">
<span class="mr-2"><%= @conn.assigns[:current_user].name || @conn.assigns[:current_user].email %></span> <span class="mr-2"><%= @conn.assigns[:current_user].name || @conn.assigns[:current_user].email %></span>
<svg style="height: 18px; transform: translateY(2px); fill: #606f7b;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 512 640" enable-background="new 0 0 512 512" xml:space="preserve"><g><circle cx="256" cy="52.8" r="50.8"/><circle cx="256" cy="256" r="50.8"/><circle cx="256" cy="459.2" r="50.8"/></g></svg> <svg style="height: 18px; transform: translateY(2px); fill: #606f7b;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 512 640" enable-background="new 0 0 512 512" xml:space="preserve"><g><circle cx="256" cy="52.8" r="50.8"/><circle cx="256" cy="256" r="50.8"/><circle cx="256" cy="459.2" r="50.8"/></g></svg>
</div> </div>
<div data-dropdown style="top: 42px; right: 0px; width: 185px;" class="dropdown-content hidden absolute right-0 bg-white border border-gray-300 rounded shadow-md z-10"> <div data-dropdown style="top: 42px; right: 0px; width: 185px;" class="dropdown-content hidden absolute right-0 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-500 rounded shadow-md z-10">
<%= link("Settings", to: "/settings", class: "block py-2 px-2 border-b border-gray-300 hover:bg-gray-100") %> <%= link("Settings", to: "/settings", class: "block py-2 px-2 border-b border-gray-300 dark:border-gray-500 hover:bg-gray-100 dark:hover:bg-gray-900 dark:text-gray-100") %>
<%= link("Log out", to: "/logout", method: :post, class: "block py-2 px-2 hover:bg-gray-100") %> <%= link("Log out", to: "/logout", method: :post, class: "block py-2 px-2 hover:bg-gray-100 dark:hover:bg-gray-900 dark:text-gray-100") %>
</div> </div>
</div> </div>
</li> </li>
@ -59,7 +63,7 @@
<ul class="flex" x-show="!document.cookie.includes('logged_in=true')"> <ul class="flex" x-show="!document.cookie.includes('logged_in=true')">
<li> <li>
<div class="inline-flex"> <div class="inline-flex">
<a href="/login" class="font-medium text-gray-500 hover:text-gray-900 focus:outline-none focus:text-gray-900 transition duration-150 ease-in-out">Login</a> <a href="/login" class="font-medium text-gray-500 dark:text-gray-200 hover:text-gray-900 focus:outline-none focus:text-gray-900 transition duration-150 ease-in-out">Login</a>
</div> </div>
</li> </li>
</ul> </ul>
@ -67,7 +71,7 @@
<ul class="flex" x-show="!document.cookie.includes('logged_in=true')"> <ul class="flex" x-show="!document.cookie.includes('logged_in=true')">
<li> <li>
<div class="inline-flex"> <div class="inline-flex">
<a href="/login" class="font-medium text-gray-500 hover:text-gray-900 focus:outline-none focus:text-gray-900 transition duration-150 ease-in-out">Login</a> <a href="/login" class="font-medium text-gray-500 dark:text-gray-200 hover:text-gray-900 focus:outline-none focus:text-gray-900 transition duration-150 ease-in-out">Login</a>
</div> </div>
<div class="inline-flex rounded shadow ml-6"> <div class="inline-flex rounded shadow ml-6">
<a href="/register" class="inline-flex items-center justify-center px-5 py-2 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:ring transition duration-150 ease-in-out">Sign up</a> <a href="/register" class="inline-flex items-center justify-center px-5 py-2 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:ring transition duration-150 ease-in-out">Sign up</a>
@ -102,7 +106,7 @@
</div> </div>
<div class="order-3 mt-2 flex-shrink-0 w-full sm:order-2 sm:mt-0 sm:w-auto"> <div class="order-3 mt-2 flex-shrink-0 w-full sm:order-2 sm:mt-0 sm:w-auto">
<div class="rounded-md shadow-sm"> <div class="rounded-md shadow-sm">
<%= link("Update billing info", to: @conn.assigns[:current_user].subscription.update_url, class: "flex items-center justify-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-gray-600 bg-white hover:text-gray-500 focus:outline-none focus:ring transition ease-in-out duration-150") %> <%= link("Update billing info", to: @conn.assigns[:current_user].subscription.update_url, class: "flex items-center justify-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-gray-600 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-500 dark:hover:text-gray-200 focus:outline-none focus:ring transition ease-in-out duration-150") %>
</div> </div>
</div> </div>
</div> </div>
@ -124,7 +128,7 @@
</div> </div>
<div class="order-3 mt-2 flex-shrink-0 w-full sm:order-2 sm:mt-0 sm:w-auto"> <div class="order-3 mt-2 flex-shrink-0 w-full sm:order-2 sm:mt-0 sm:w-auto">
<div class="rounded-md shadow-sm"> <div class="rounded-md shadow-sm">
<%= link("Update billing info", to: @conn.assigns[:current_user].subscription.update_url, class: "flex items-center justify-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-gray-600 bg-white hover:text-gray-500 focus:outline-none focus:ring transition ease-in-out duration-150") %> <%= link("Update billing info", to: @conn.assigns[:current_user].subscription.update_url, class: "flex items-center justify-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-gray-600 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-500 dark:hover:text-gray-200 focus:outline-none focus:ring transition ease-in-out duration-150") %>
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,11 +9,13 @@
<title><%= assigns[:title] || "Plausible · Web analytics" %></title> <title><%= assigns[:title] || "Plausible · Web analytics" %></title>
<link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/> <link rel="stylesheet" href="<%= Routes.static_path(@conn, "/css/app.css") %>"/>
<%= render("_tracking.html", assigns) %> <%= render("_tracking.html", assigns) %>
<script type="text/javascript" data-pref="<%= @conn.assigns[:current_user] && @conn.assigns[:current_user].theme %>" src="<%= Routes.static_path(@conn, "/js/applyTheme.js") %>"></script>
</head> </head>
<body class="flex flex-col h-full bg-gray-100"> <body class="flex flex-col h-full bg-gray-100 dark:bg-gray-900">
<div class="text-center w-full my-8"> <div class="text-center w-full my-8">
<a href="<%= home_dest(@conn) %>"> <a href="<%= home_dest(@conn) %>">
<%= img_tag(PlausibleWeb.Router.Helpers.static_path(@conn, "/images/icon/plausible_logo.png"), class: "inline", style: "height: 2.5rem;", alt: "Plausible logo") %> <%= img_tag(PlausibleWeb.Router.Helpers.static_path(@conn, "/images/icon/plausible_logo_dark.png"), class: "hidden dark:inline", style: "height: 2.5rem;", alt: "Plausible logo") %>
<%= img_tag(PlausibleWeb.Router.Helpers.static_path(@conn, "/images/icon/plausible_logo.png"), class: "inline dark:hidden", style: "height: 2.5rem;", alt: "Plausible logo")%>
</a> </a>
</div> </div>

View File

@ -1,15 +1,15 @@
<%= render_layout "app.html", assigns do %> <%= render_layout "app.html", assigns do %>
<div class="container pt-6"> <div class="container pt-6">
<%= link("← Back to stats", to: "/#{URI.encode_www_form(@site.domain)}", class: "text-sm text-indigo-600 font-bold") %> <%= link("← Back to stats", to: "/#{URI.encode_www_form(@site.domain)}", class: "text-sm text-indigo-600 font-bold") %>
<div class="pb-5 border-b border-gray-200"> <div class="pb-5 border-b border-gray-200 dark:border-gray-500">
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:leading-9 sm:truncate"> <h2 class="text-2xl font-bold leading-7 text-gray-900 dark:text-gray-100 sm:text-3xl sm:leading-9 sm:truncate">
Settings for <%= @site.domain %> Settings for <%= @site.domain %>
</h2> </h2>
</div> </div>
<div class="lg:grid lg:grid-cols-12 lg:gap-x-5 lg:mt-4"> <div class="lg:grid lg:grid-cols-12 lg:gap-x-5 lg:mt-4">
<div class="py-4 g:py-0 lg:col-span-3"> <div class="py-4 g:py-0 lg:col-span-3">
<%= form_for @conn, "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/recipients", [class: "lg:hidden"], fn f -> %> <%= form_for @conn, "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/recipients", [class: "lg:hidden"], fn f -> %>
<%= select f, :tab, settings_tabs(), class: "mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md", onchange: "location.href = location.href.replace(/[^\/\/]*$/, event.target.value)", selected: List.last(@conn.path_info) %> <%= select f, :tab, settings_tabs(), class: "dark:bg-gray-800 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 outline-none focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100", onchange: "location.href = location.href.replace(/[^\/\/]*$/, event.target.value)", selected: List.last(@conn.path_info) %>
<% end %> <% end %>
<div class="hidden lg:block"> <div class="hidden lg:block">
<%= for [key: key, value: val] <- settings_tabs() do %> <%= for [key: key, value: val] <- settings_tabs() do %>

View File

@ -1,3 +1,3 @@
<div class="relative bg-gray-50 overflow-hidden text-center"> <div class="relative bg-gray-50 dark:bg-gray-800 overflow-hidden text-center dark:text-gray-100">
You will be redirected... If it doesn't work, please click login. You will be redirected... If it doesn't work, please click login.
</div> </div>

View File

@ -1,14 +1,14 @@
<div class="max-w-md w-full mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"> <div class="max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8">
<h2 class="text-xl font-black">DNS for <%= @site.custom_domain.domain %></h2> <h2 class="text-xl font-black dark:text-gray-100">DNS for <%= @site.custom_domain.domain %></h2>
<ol class="list-disc pl-4 my-4"> <ol class="list-disc pl-4 my-4">
<li>Go to your DNS providers website</li> <li class="dark:text-gray-100">Go to your DNS providers website</li>
<li class="mt-4">Create a new CNAME record for <code><%= @site.custom_domain.domain %></code></li> <li class="mt-4 dark:text-gray-100">Create a new CNAME record for <code><%= @site.custom_domain.domain %></code></li>
<li class="mt-4">Point the record to <code>custom.<%= base_domain() %>.</code> (including the dot)</li> <li class="mt-4 dark:text-gray-100">Point the record to <code>custom.<%= base_domain() %>.</code> (including the dot)</li>
<li class="mt-4">If you're using Cloudflare, make sure to disable the orange cloud proxy:</li> <li class="mt-4 dark:text-gray-100">If you're using Cloudflare, make sure to disable the orange cloud proxy:</li>
<%= img_tag(Routes.static_path(@conn, "/images/cloudflare_orange_cloud.png"), class: "w-36 my-4 inline") %> <%= img_tag(Routes.static_path(@conn, "/images/cloudflare_orange_cloud.png"), class: "w-36 my-4 inline") %>
<span class="text-lg font-bold">&rarr;</span> <span class="text-lg font-bold dark:text-gray-100">&rarr;</span>
<%= img_tag(Routes.static_path(@conn, "/images/cloudflare_gray_cloud.png"), class: "w-36 my-4 inline") %> <%= img_tag(Routes.static_path(@conn, "/images/cloudflare_gray_cloud.png"), class: "w-36 my-4 inline") %>
</ol> </ol>
<%= link("Done ->", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/snippet", class: "button w-full mt-6") %> <%= link("Done ", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/snippet", class: "button w-full mt-6") %>
</div> </div>

View File

@ -1,25 +1,25 @@
<%= form_for @conn, "/", [class: "max-w-md w-full mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> <%= form_for @conn, "/", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black">Update javascript snippet</h2> <h2 class="text-xl font-black dark:text-gray-100">Update javascript snippet</h2>
<div class="my-4"> <div class="my-4">
<p> <p class="dark:text-gray-100">
Allow up to 4 hours for DNS changes to propagate and for us to obtain an SSL certificate for <code><%= @site.custom_domain.domain %></code> Allow up to 4 hours for DNS changes to propagate and for us to obtain an SSL certificate for <code><%= @site.custom_domain.domain %></code>
</p> </p>
<p class="mt-4"> <p class="mt-4 dark:text-gray-100">
The setup is working when <a href="//<%= @site.custom_domain.domain %>/js/index.js" target="_blank" class="text-indigo-500"><%= @site.custom_domain.domain %>/js/index.js</a> loads the javascript tracker. The setup is working when <a href="//<%= @site.custom_domain.domain %>/js/index.js" target="_blank" class="text-indigo-500"><%= @site.custom_domain.domain %>/js/index.js</a> loads the javascript tracker.
</p> </p>
<p class="mt-4"> <p class="mt-4 dark:text-gray-100">
To finish your setup, please replace the tracking snippet on your site with the following. To finish your setup, please replace the tracking snippet on your site with the following.
</p> </p>
<div class="relative"> <div class="relative">
<%= textarea f, :domain, id: "snippet_code", class: "transition overflow-hidden bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300 text-xs mt-4 resize-none", value: snippet(@site), rows: 3, readonly: "readonly" %> <%= textarea f, :domain, id: "snippet_code", class: "transition overflow-hidden bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 dark:text-gray-300 leading-normal appearance-none focus:outline-none focus:bg-white dark:focus:bg-gray-900 focus:border-gray-300 dark:focus:border-gray-500 text-xs mt-4 resize-none", value: snippet(@site), rows: 3, readonly: "readonly" %>
<a onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="no-underline text-indigo-500 text-sm hover:underline"> <a onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="no-underline text-indigo-500 text-sm hover:underline">
<svg class="absolute text-indigo-500" style="top: 24px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> <svg class="absolute text-indigo-500" style="top: 24px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</a> </a>
</div> </div>
</div> </div>
<%= link("Back to settings →", class: "button mt-4 w-full", to: "/#{URI.encode_www_form(@site.domain)}/settings/custom-domain") %> <%= link("Back to settings →", class: "button mt-4 w-full", to: "/#{URI.encode_www_form(@site.domain)}/settings/custom-domain") %>
<p class="mt-4 text-gray-600 text-sm"> <p class="mt-4 text-gray-600 text-sm dark:text-gray-400">
Problems? <%= link("Get help via email", to: "mailto:support@plausible.io", class: "text-indigo-800 underline") %> Problems? <%= link("Get help via email", to: "mailto:support@plausible.io", class: "text-indigo-500 underline") %>
</p> </p>
<% end %> <% end %>

View File

@ -1,6 +1,6 @@
<div class="container pt-6"> <div class="container pt-6">
<div class="pb-5 border-b border-gray-200 flex items-center justify-between"> <div class="pb-5 border-b border-gray-200 dark:border-gray-500 flex items-center justify-between">
<h2 class="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:leading-9 sm:truncate"> <h2 class="text-2xl font-bold leading-7 text-gray-900 dark:text-gray-100 sm:text-3xl sm:leading-9 sm:truncate">
My sites My sites
</h2> </h2>
<a href="/sites/new" class="button w-full my-2 sm:my-0 sm:w-auto">+ Add a website</a> <a href="/sites/new" class="button w-full my-2 sm:my-0 sm:w-auto">+ Add a website</a>
@ -8,23 +8,23 @@
<ul class="my-6 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"> <ul class="my-6 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<%= if Enum.empty?(@sites) do %> <%= if Enum.empty?(@sites) do %>
<p>You don't have any sites yet</p> <p class="dark:text-gray-100">You don't have any sites yet</p>
<% end %> <% end %>
<%= for site <- @sites do %> <%= for site <- @sites do %>
<div class="relative group"> <div class="relative group">
<%= link(to: "/" <> URI.encode_www_form(site.domain)) do %> <%= link(to: "/" <> URI.encode_www_form(site.domain)) do %>
<li class="col-span-1 bg-white rounded-lg shadow p-4 group-hover:shadow-lg cursor-pointer"> <li class="col-span-1 bg-white dark:bg-gray-800 rounded-lg shadow p-4 group-hover:shadow-lg cursor-pointer">
<div class="w-full flex items-center justify-between space-x-4"> <div class="w-full flex items-center justify-between space-x-4">
<img src="https://icons.duckduckgo.com/ip3/<%= site.domain %>.ico" referrerpolicy="no-referrer" class="w-4 h-4 flex-shrink-0 mt-px"> <img src="https://icons.duckduckgo.com/ip3/<%= site.domain %>.ico" referrerpolicy="no-referrer" class="w-4 h-4 flex-shrink-0 mt-px">
<div class="flex-1 truncate -mt-px"> <div class="flex-1 truncate -mt-px">
<h3 class="text-gray-900 font-medium text-lg truncate"><%= site.domain %></h3> <h3 class="text-gray-900 font-medium text-lg truncate dark:text-gray-100"><%= site.domain %></h3>
</div> </div>
</div> </div>
<div class="pl-8 mt-2 flex items-center justify-between"> <div class="pl-8 mt-2 flex items-center justify-between">
<span class="text-gray-600 text-sm truncate"> <span class="text-gray-600 dark:text-gray-400 text-sm truncate">
<span class="text-gray-800"> <span class="text-gray-800 dark:text-gray-200">
<b><%= PlausibleWeb.StatsView.large_number_format(Map.get(@visitors, site.domain, 0)) %></b> visitors in last 24h <b><%= PlausibleWeb.StatsView.large_number_format(Map.get(@visitors, site.domain, 0)) %></b> visitor<%= if Map.get(@visitors, site.domain, 0) != 1 do %>s<% end %> in last 24h
</span> </span>
</span> </span>
</div> </div>
@ -32,7 +32,7 @@
<% end %> <% end %>
<%= link(to: "/" <> URI.encode_www_form(site.domain) <> "/settings", class: "absolute top-0 right-0 p-4 mt-1") do %> <%= link(to: "/" <> URI.encode_www_form(site.domain) <> "/settings", class: "absolute top-0 right-0 p-4 mt-1") do %>
<svg class="w-5 h-5 text-gray-600 opacity-0 group-hover:opacity-100 transition" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"></path></svg> <svg class="w-5 h-5 text-gray-600 dark:text-gray-400 opacity-0 group-hover:opacity-100 transition hover:text-gray-900 dark:hover:text-gray-100" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>

View File

@ -1,22 +1,22 @@
<div class="w-full max-w-3xl mt-4 mx-auto flex"> <div class="w-full max-w-3xl mt-4 mx-auto flex">
<%= form_for @changeset, "/sites", [class: "max-w-lg w-full mx-auto bg-white shadow-lg rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> <%= form_for @changeset, "/sites", [class: "max-w-lg w-full mx-auto bg-white dark:bg-gray-800 shadow-lg rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black">Your website details</h2> <h2 class="text-xl font-black dark:text-gray-100">Your website details</h2>
<div class="my-6"> <div class="my-6">
<%= label f, :domain, class: "block text-sm font-medium text-gray-700" %> <%= label f, :domain, class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<div class="mt-2 flex rounded-md shadow-sm"> <div class="mt-2 flex rounded-md shadow-sm">
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm"> <span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-850 text-gray-500 dark:text-gray-400 sm:text-sm">
https:// https://
</span> </span>
<%= text_input f, :domain, class: "focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full px-3 py-2 rounded-none rounded-r-md sm:text-sm border-gray-300", placeholder: "example.com" %> <%= text_input f, :domain, class: "focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 flex-1 block w-full px-3 py-2 rounded-none rounded-r-md sm:text-sm border-gray-300 dark:border-gray-500 dark:bg-gray-900 dark:text-gray-300", placeholder: "example.com" %>
</div> </div>
<%= error_tag f, :domain %> <%= error_tag f, :domain %>
</div> </div>
<div class="my-6"> <div class="my-6">
<%= label f, :timezone, "Reporting Timezone", class: "block text-sm font-medium text-gray-700" %> <%= label f, :timezone, "Reporting Timezone", class: "block text-sm font-medium text-gray-700 dark:text-gray-300" %>
<p class="text-gray-500 text-xs mt-1">To make sure we agree on what 'today' means</p> <p class="text-gray-500 dark:text-gray-400 text-xs mt-1">To make sure we agree on what 'today' means</p>
<div class="inline-block relative w-full"> <div class="inline-block relative w-full">
<%= select f, :timezone, Plausible.Timezones.options(), id: "tz-select", selected: "Etc/Greenwich", class: "mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" %> <%= select f, :timezone, Plausible.Timezones.options(), id: "tz-select", selected: "Etc/Greenwich", class: "mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 dark:bg-gray-900 dark:text-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" %>
</div> </div>
</div> </div>
<script> <script>

View File

@ -1,15 +1,15 @@
<%= form_for @changeset, "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains", [class: "max-w-md w-full mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> <%= form_for @changeset, "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black">Setup custom domain</h2> <h2 class="text-xl font-black dark:text-gray-100">Setup custom domain</h2>
<div class="my-4"> <div class="my-4 dark:text-gray-100">
We recommend using a subdomain of the website you're running Plausible on. We recommend using a subdomain of the website you're running Plausible on.
If your site is on <code>example.com</code> you can use <code>stats.example.com</code>. If your site is on <code>example.com</code> you can use <code>stats.example.com</code>.
<br /><br /> The name of the subdomain can be anything, it doesn't have to be <code>stats</code>. <br /><br /> The name of the subdomain can be anything, it doesn't have to be <code>stats</code>.
</div> </div>
<div class="my-4"> <div class="my-4">
<%= label f, :domain, class: "block text-sm font-bold" %> <%= label f, :domain, class: "block text-sm font-bold dark:text-gray-100" %>
<%= text_input f, :domain, class: "transition mt-3 bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300", placeholder: "stats.[yourdomain].com" %> <%= text_input f, :domain, class: "transition mt-3 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500", placeholder: "stats.[yourdomain].com" %>
<%= error_tag f, :domain %> <%= error_tag f, :domain %>
</div> </div>
<%= submit "DNS setup ->", class: "button mt-4 w-full" %> <%= submit "DNS setup ", class: "button mt-4 w-full dark:text-gray-100" %>
<% end %> <% end %>

View File

@ -1,19 +1,19 @@
<%= form_for @changeset, "/#{URI.encode_www_form(@site.domain)}/goals", [class: "max-w-md w-full mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> <%= form_for @changeset, "/#{URI.encode_www_form(@site.domain)}/goals", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black">Add goal for <%= @site.domain %></h2> <h2 class="text-xl font-black dark:text-gray-100">Add goal for <%= @site.domain %></h2>
<div class="mt-6 text-sm font-bold">Goal trigger</div> <div class="mt-6 text-sm font-bold dark:text-gray-100">Goal trigger</div>
<div class="my-3 w-full flex rounded border border-gray-300"> <div class="my-3 w-full flex rounded border border-gray-300 dark:border-gray-500">
<div class="w-1/2 text-center py-2 border-r border-gray-300 shadow-inner font-bold bg-gray-100 cursor-pointer" id="pageview-tab">Pageview</div> <div class="w-1/2 text-center py-2 border-r border-gray-300 dark:border-gray-500 shadow-inner font-bold cursor-pointer text-white dark:text-gray-100 bg-indigo-600" id="pageview-tab">Pageview</div>
<div class="w-1/2 text-center py-2 bg-gray-100 cursor-pointer" id="event-tab">Custom event</div> <div class="w-1/2 text-center py-2 cursor-pointer dark:text-gray-100" id="event-tab">Custom event</div>
</div> </div>
<div class="my-6"> <div class="my-6">
<div id="pageview-fields"> <div id="pageview-fields">
<%= label f, :page_path, class: "block text-sm font-bold" %> <%= label f, :page_path, class: "block text-sm font-bold dark:text-gray-100" %>
<%= text_input f, :page_path, class: "transition mt-3 bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300", placeholder: "/success" %> <%= text_input f, :page_path, class: "transition mt-3 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500", placeholder: "/success" %>
<%= error_tag f, :page_path %> <%= error_tag f, :page_path %>
</div> </div>
<div id="event-fields" class="hidden"> <div id="event-fields" class="hidden">
<%= label f, :event_name, class: "block text-sm font-bold" %> <%= label f, :event_name, class: "block text-sm font-bold dark:text-gray-100" %>
<%= text_input f, :event_name, class: "transition mt-3 bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300", placeholder: "Signup" %> <%= text_input f, :event_name, class: "transition mt-3 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500", placeholder: "Signup" %>
<%= error_tag f, :event_name %> <%= error_tag f, :event_name %>
</div> </div>
</div> </div>
@ -24,14 +24,16 @@
<script> <script>
document.getElementById('pageview-tab').onclick = function() { document.getElementById('pageview-tab').onclick = function() {
document.getElementById('pageview-fields').classList.remove('hidden') document.getElementById('pageview-fields').classList.remove('hidden')
document.getElementById('pageview-tab').classList.add('shadow-inner', 'font-bold') document.getElementById('pageview-tab').classList.add('shadow-inner', 'font-bold', 'bg-indigo-600', 'text-white')
document.getElementById('event-fields').classList.add('hidden') document.getElementById('event-fields').classList.add('hidden')
document.getElementById('event-tab').classList.remove('shadow-inner', 'font-bold') document.getElementById('event-tab').classList.remove('shadow-inner', 'font-bold', 'bg-indigo-600', 'text-white')
document.getElementById('event-tab').classList.add('dark:text-gray-100')
} }
document.getElementById('event-tab').onclick = function() { document.getElementById('event-tab').onclick = function() {
document.getElementById('event-fields').classList.remove('hidden') document.getElementById('event-fields').classList.remove('hidden')
document.getElementById('event-tab').classList.add('shadow-inner', 'font-bold') document.getElementById('event-tab').classList.add('shadow-inner', 'font-bold', 'bg-indigo-600', 'text-white')
document.getElementById('pageview-fields').classList.add('hidden') document.getElementById('pageview-fields').classList.add('hidden')
document.getElementById('pageview-tab').classList.remove('shadow-inner', 'font-bold') document.getElementById('pageview-tab').classList.remove('shadow-inner', 'font-bold', 'bg-indigo-600', 'text-white')
document.getElementById('pageview-tab').classList.add('dark:text-gray-100')
} }
</script> </script>

View File

@ -1,12 +1,12 @@
<%= form_for @changeset, "/sites/#{URI.encode_www_form(@site.domain)}/shared-links", [class: "max-w-md w-full mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> <%= form_for @changeset, "/sites/#{URI.encode_www_form(@site.domain)}/shared-links", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black">New shared link</h2> <h2 class="text-xl font-black dark:text-gray-100">New shared link</h2>
<div class="my-4"> <div class="my-4 dark:text-gray-100">
Add a password or leave it blank so anyone with the link can see the stats. Add a password or leave it blank so anyone with the link can see the stats.
Once the link is created, we cannot reveal the password. Please make sure you save it in a secure place. Once the link is created, we cannot reveal the password. Please make sure you save it in a secure place.
</div> </div>
<div class="my-6"> <div class="my-6">
<%= label f, :password, "Password (optional)", class: "block text-sm font-bold" %> <%= label f, :password, "Password (optional)", class: "block text-sm font-bold dark:text-gray-100" %>
<%= password_input f, :password, class: "transition mt-3 bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300" %> <%= password_input f, :password, class: "transition mt-3 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500" %>
<%= error_tag f, :password %> <%= error_tag f, :password %>
</div> </div>

View File

@ -1,16 +1,18 @@
<div class="shadow bg-white sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"> <div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">Custom domain</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Custom domain</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">Serve the tracking script from your domain name as a first-party resource instead of loading the script from our domain.</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Serve the tracking script from your domain name as a first-party resource instead of loading the script from our domain.</p>
<%= link(to: "https://docs.plausible.io/custom-domain/", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/custom-domain/", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
</header> </header>
<div class="mt-6"> <div class="mt-6 dark:text-gray-100 flex justify-between items-center">
<%= if @site.custom_domain do %> <%= if @site.custom_domain do %>
Configured domain: <b><%= @site.custom_domain.domain %></b> <span>
<%= link("Remove custom domain", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/#{@site.custom_domain.id}", class: "inline-block mt-4 px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-red-700 bg-white hover:text-red-500 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete") %> Configured domain: <b><%= @site.custom_domain.domain %></b>
</span>
<%= link("Remove custom domain", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/#{@site.custom_domain.id}", class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete") %>
<% else %> <% else %>
<%= link("Add custom domain", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/new", class: "button") %> <%= link("Add custom domain", to: "/sites/#{URI.encode_www_form(@site.domain)}/custom-domains/new", class: "button") %>
<% end %> <% end %>

View File

@ -1,32 +1,32 @@
<div class="sm:rounded-md sm:overflow-hidden shadow"> <div class="sm:rounded-md sm:overflow-hidden shadow">
<div class="bg-white py-6 px-4 space-y-6 sm:p-6"> <div class="bg-white dark:bg-gray-800 py-6 px-4 space-y-6 sm:p-6">
<div> <div>
<h2 class="text-lg leading-6 font-medium text-gray-900">Danger zone</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Danger zone</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">Desctructive actions below can result in irrecoverable data loss. Be careful.</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Desctructive actions below can result in irrecoverable data loss. Be careful.</p>
</div> </div>
<li class="py-4 flex items-center justify-between space-x-4"> <li class="py-4 flex items-center justify-between space-x-4">
<div class="flex flex-col"> <div class="flex flex-col">
<p class="text-sm leading-5 font-medium text-gray-900"> <p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
Reset stats Reset stats
</p> </p>
<p class="text-sm leading-5 text-gray-500"> <p class="text-sm leading-5 text-gray-500 dark:text-gray-200">
Removes all pageviews but keeps the site configuration Removes all pageviews but keeps the site configuration
</p> </p>
</div> </div>
<%= link("Reset #{@site.domain} stats", to: "/#{URI.encode_www_form(@site.domain)}/stats", method: :delete, class: "inline-block px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-red-700 bg-white hover:text-red-500 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", data: [confirm: "Resetting the stats cannot be reversed. Are you sure?"]) %> <%= link("Reset #{@site.domain} stats", to: "/#{URI.encode_www_form(@site.domain)}/stats", method: :delete, class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", data: [confirm: "Resetting the stats cannot be reversed. Are you sure?"]) %>
</li> </li>
<div class="border-b border-gray-200"></div> <div class="border-b border-gray-200 dark:border-gray-500"></div>
<li class="py-4 flex items-center justify-between space-x-4"> <li class="py-4 flex items-center justify-between space-x-4">
<div class="flex flex-col"> <div class="flex flex-col">
<p class="text-sm leading-5 font-medium text-gray-900"> <p class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">
Delete site Delete site
</p> </p>
<p class="text-sm leading-5 text-gray-500"> <p class="text-sm leading-5 text-gray-500 dark:text-gray-200">
Removes all stats along with the site configuration Removes all stats along with the site configuration
</p> </p>
</div> </div>
<%= link "Delete #{@site.domain}", to: "/#{URI.encode_www_form(@site.domain)}", method: :delete, class: "inline-block px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-50 focus:outline-none focus:border-red-300 focus:ring active:bg-red-200 transition ease-in-out duration-150 sm:text-sm sm:leading-5", data: [confirm: "Deleting the site data cannot be reversed. Are you sure?"] %> <%= link "Delete #{@site.domain}", to: "/#{URI.encode_www_form(@site.domain)}", method: :delete, class: "inline-block px-4 py-2 border border-transparent font-medium rounded-md text-red-700 dark:text-red-800 bg-red-100 dark:bg-red-200 hover:bg-red-50 dark:hover:bg-red-300 focus:outline-none focus:border-red-300 focus:ring active:bg-red-200 transition ease-in-out duration-150 sm:text-sm sm:leading-5", data: [confirm: "Deleting the site data cannot be reversed. Are you sure?"] %>
</li> </li>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<div class="shadow bg-white sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"> <div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">Email reports</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Email reports</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">Send weekly/monthly analytics reports to as many addresses as you wish</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Send weekly/monthly analytics reports to as many addresses as you wish</p>
<%= link(to: "https://docs.plausible.io/email-reports/", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/email-reports/", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
@ -10,20 +10,20 @@
<div class="my-8 flex items-center"> <div class="my-8 flex items-center">
<%= if @weekly_report do %> <%= if @weekly_report do %>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/weekly-report/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/weekly-report/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span> <span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
<% end %> <% end %>
<% else %> <% else %>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/weekly-report/enable", method: :post, class: "bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/weekly-report/enable", method: :post, class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span> <span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
<% end %> <% end %>
<% end %> <% end %>
<span class="ml-2">Send a weekly email report every Monday</span> <span class="ml-2 dark:text-gray-100">Send a weekly email report every Monday</span>
</div> </div>
<%= if @weekly_report do %> <%= if @weekly_report do %>
<div class="text-sm text-gray-700 mt-6"> <div class="text-sm text-gray-700 dark:text-gray-300 mt-6">
<h4 class="font-bold my-2">Weekly report recipients</h4> <h4 class="font-bold my-2 dark:text-gray-100">Weekly report recipients</h4>
<%= for recipient <- @weekly_report.recipients do %> <%= for recipient <- @weekly_report.recipients do %>
<div class="p-2 pl-3 flex justify-between bg-gray-100 rounded my-2 max-w-md"> <div class="p-2 pl-3 flex justify-between bg-gray-100 dark:bg-gray-900 rounded my-2 max-w-md">
<span> <span>
<svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> <svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" /> <path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
@ -45,7 +45,7 @@
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" /> <path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg> </svg>
</div> </div>
<%= email_input f, :recipient, class: "focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300", placeholder: "recipient@example.com" %> <%= email_input f, :recipient, class: "focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100", placeholder: "recipient@example.com" %>
</div> </div>
<%= submit class: "-ml-px relative button rounded-l-none" do %> <%= submit class: "-ml-px relative button rounded-l-none" do %>
@ -57,24 +57,24 @@
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
<div class="my-8 border-b border-gray-300"></div> <div class="my-8 border-b border-gray-300 dark:border-gray-500"></div>
<div class="my-8 flex items-center"> <div class="my-8 flex items-center">
<%= if @monthly_report do %> <%= if @monthly_report do %>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span> <span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
<% end %> <% end %>
<% else %> <% else %>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/enable", method: :post, class: "bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/monthly-report/enable", method: :post, class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span> <span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
<% end %> <% end %>
<% end %> <% end %>
<span class="ml-2">Send a monthly email report on 1st of the month</span> <span class="ml-2 dark:text-gray-100">Send a monthly email report on 1st of the month</span>
</div> </div>
<%= if @monthly_report do %> <%= if @monthly_report do %>
<div class="text-sm text-gray-700 mt-6"> <div class="text-sm text-gray-700 dark:text-gray-300 mt-6">
<h4 class="font-bold my-2">Monthly report recipients</h4> <h4 class="font-bold my-2">Monthly report recipients</h4>
<%= for recipient <- @monthly_report.recipients do %> <%= for recipient <- @monthly_report.recipients do %>
<div class="p-2 pl-3 flex justify-between bg-gray-100 rounded my-2 max-w-md"> <div class="p-2 pl-3 flex justify-between bg-gray-100 dark:bg-gray-900 rounded my-2 max-w-md">
<span> <span>
<svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> <svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" /> <path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
@ -96,7 +96,7 @@
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" /> <path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg> </svg>
</div> </div>
<%= email_input f, :recipient, class: "focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300", placeholder: "recipient@example.com" %> <%= email_input f, :recipient, class: "focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100", placeholder: "recipient@example.com" %>
</div> </div>
<%= submit class: "-ml-px relative button rounded-l-none" do %> <%= submit class: "-ml-px relative button rounded-l-none" do %>
@ -110,10 +110,10 @@
<% end %> <% end %>
</div> </div>
<div class="shadow bg-white sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"> <div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">Traffic spike notifications</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Traffic spike notifications</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">Get notified when your site has unusually high number of current visitors</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Get notified when your site has unusually high number of current visitors</p>
<%= link(to: "https://docs.plausible.io/email-reports/", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/email-reports/", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
@ -122,18 +122,18 @@
<div class="my-8 flex items-center"> <div class="my-8 flex items-center">
<%= if @spike_notification do %> <%= if @spike_notification do %>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification/disable", method: :post, class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span> <span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
<% end %> <% end %>
<% else %> <% else %>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification/enable", method: :post, class: "bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification/enable", method: :post, class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span> <span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
<% end %> <% end %>
<% end %> <% end %>
<span class="ml-2">Send notifications of traffic spikes</span> <span class="ml-2 dark:text-gray-100">Send notifications of traffic spikes</span>
</div> </div>
<%= if @spike_notification do %> <%= if @spike_notification do %>
<div class="text-sm text-gray-700 mt-6"> <div class="text-sm text-gray-700 dark:text-gray-300 mt-6">
<%= form_for Plausible.Site.SpikeNotification.changeset(@spike_notification, %{}), "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification", fn f -> %> <%= form_for Plausible.Site.SpikeNotification.changeset(@spike_notification, %{}), "/sites/#{URI.encode_www_form(@site.domain)}/spike-notification", fn f -> %>
<h4 class="font-bold my-2">Current visitor threshold</h4> <h4 class="font-bold my-2">Current visitor threshold</h4>
@ -145,16 +145,16 @@
<path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" /> <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" />
</svg> </svg>
</div> </div>
<%= number_input f, :threshold, class: "focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300" %> <%= number_input f, :threshold, class: "focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:text-gray-100" %>
</div> </div>
<button class="-ml-px relative button rounded-l-none"> <button class="-ml-px relative button rounded-l-none">
<span>Save threshold</span> <span>Save threshold</span>
</button> </button>
</div> </div>
<% end %> <% end %>
<h4 class="font-bold mt-6">Notification recipients</h4> <h4 class="font-bold mt-6 dark:text-gray-100">Notification recipients</h4>
<%= for recipient <- @spike_notification.recipients do %> <%= for recipient <- @spike_notification.recipients do %>
<div class="p-2 pl-3 flex justify-between bg-gray-100 rounded my-2 max-w-md"> <div class="p-2 pl-3 flex justify-between bg-gray-100 dark:bg-gray-900 rounded my-2 max-w-md">
<span> <span>
<svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> <svg class="h-5 w-5 text-gray-400 inline mr-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" /> <path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
@ -176,7 +176,7 @@
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" /> <path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg> </svg>
</div> </div>
<%= email_input f, :recipient, class: "focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300", placeholder: "recipient@example.com" %> <%= email_input f, :recipient, class: "focus:ring-indigo-500 dark:bg-gray-900 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:text-sm border-gray-300 dark:border-gray-500 dark:placeholder-gray-400 dark:text-gray-100", placeholder: "recipient@example.com" %>
</div> </div>
<%= submit class: "-ml-px relative button rounded-l-none" do %> <%= submit class: "-ml-px relative button rounded-l-none" do %>

View File

@ -1,37 +1,37 @@
<%= form_for @changeset, "/#{URI.encode_www_form(@site.domain)}/settings", fn f -> %> <%= form_for @changeset, "/#{URI.encode_www_form(@site.domain)}/settings", fn f -> %>
<div class="shadow sm:rounded-md sm:overflow-hidden"> <div class="shadow sm:rounded-md sm:overflow-hidden">
<div class="bg-white py-6 px-4 space-y-6 sm:p-6"> <div class="bg-white dark:bg-gray-800 py-6 px-4 space-y-6 sm:p-6">
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">General information</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">General information</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">Update your reporting timezone.</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Update your reporting timezone.</p>
<%= link(to: "https://docs.plausible.io/general/", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/general/", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
</header> </header>
<div class="grid grid-cols-4 gap-6"> <div class="grid grid-cols-4 gap-6">
<div class="col-span-4 sm:col-span-2"> <%= label f, :domain, class: "block text-sm font-medium leading-5 text-gray-700" %> <div class="col-span-4 sm:col-span-2"> <%= label f, :domain, class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300" %>
<%= text_input f, :domain, class: "mt-1 block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md", disabled: "disabled" %> <%= text_input f, :domain, class: "dark:bg-gray-900 mt-1 block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 dark:border-gray-500 rounded-md dark:text-gray-100", disabled: "disabled" %>
</div> </div>
<div class="col-span-4 sm:col-span-2"> <div class="col-span-4 sm:col-span-2">
<%= label f, :timezone, "Reporting Timezone", class: "block text-sm font-medium leading-5 text-gray-700" %> <%= label f, :timezone, "Reporting Timezone", class: "block text-sm font-medium leading-5 text-gray-700 dark:text-gray-300" %>
<%= select f, :timezone, Plausible.Timezones.options(), class: "mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md" %> <%= select f, :timezone, Plausible.Timezones.options(), class: "dark:bg-gray-900 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 dark:border-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md dark:text-gray-100 cursor-pointer" %>
</div> </div>
</div> </div>
</div> </div>
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6"> <div class="px-4 py-3 bg-gray-50 dark:bg-gray-850 text-right sm:px-6">
<span class="inline-flex rounded-md shadow-sm"> <span class="inline-flex rounded-md shadow-sm">
<%= submit "Save", class: "bg-indigo-600 border border-transparent rounded-md py-2 px-4 inline-flex justify-center text-sm leading-5 font-medium text-white hover:bg-gray-700 focus:outline-none focus:border-gray-900 focus:ring active:bg-gray-900 transition duration-150 ease-in-out" %> <%= submit "Save", class: "button" %>
</span> </span>
</div> </div>
</div> </div>
<% end %> <% end %>
<%= form_for @conn, "/", [class: "shadow bg-white sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"], fn f -> %> <%= form_for @conn, "/", [class: "shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"], fn f -> %>
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">Javascript snippet</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Javascript snippet</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">Include this snippet in the <code>&lt;head&gt;</code> of your website.</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Include this snippet in the <code>&lt;head&gt;</code> of your website.</p>
<%= link(to: "https://docs.plausible.io/plausible-script", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/plausible-script", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
@ -40,7 +40,7 @@
<div class="my-4"> <div class="my-4">
<div class="relative"> <div class="relative">
<%= textarea f, :domain, id: "snippet_code", class: "transition overflow-hidden bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300 text-xs mt-2 resize-none", value: snippet(@site), rows: 2 %> <%= textarea f, :domain, id: "snippet_code", class: "transition overflow-hidden bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white focus:border-gray-300 dark:focus:border-gray-500 text-xs mt-2 resize-none", value: snippet(@site), rows: 2 %>
<a onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="no-underline text-indigo-500 text-sm hover:underline"> <a onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="no-underline text-indigo-500 text-sm hover:underline">
<svg class="absolute text-indigo-500" style="top: 24px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> <svg class="absolute text-indigo-500" style="top: 24px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</a> </a>

View File

@ -1,7 +1,7 @@
<div class="shadow bg-white sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"> <div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">Goals</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Goals</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">Define actions that you want your users to take like visiting a certain page, submitting a form, etc.</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Define actions that you want your users to take like visiting a certain page, submitting a form, etc.</p>
<%= link(to: "https://docs.plausible.io/goal-conversions/", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/goal-conversions/", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
@ -10,8 +10,8 @@
<%= if Enum.count(@goals) > 0 do %> <%= if Enum.count(@goals) > 0 do %>
<div class="mt-4"> <div class="mt-4">
<%= for goal <- @goals do %> <%= for goal <- @goals do %>
<div class="border-b border-gray-300 py-3 flex justify-between"> <div class="border-b border-gray-300 dark:border-gray-500 py-3 flex justify-between">
<span class="text-sm font-medium text-gray-900"><%= goal_name(goal) %></span> <span class="text-sm font-medium text-gray-900 dark:text-gray-100"><%= goal_name(goal) %></span>
<%= button(to: "/#{URI.encode_www_form(@site.domain)}/goals/#{goal.id}", method: :delete, class: "text-sm text-red-600", data: [confirm: "Are you sure you want to remove goal #{goal_name(goal)}? This will just affect the UI, all of your analytics data will stay intact."]) do %> <%= button(to: "/#{URI.encode_www_form(@site.domain)}/goals/#{goal.id}", method: :delete, class: "text-sm text-red-600", data: [confirm: "Are you sure you want to remove goal #{goal_name(goal)}? This will just affect the UI, all of your analytics data will stay intact."]) do %>
<svg class="feather feather-sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg> <svg class="feather feather-sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
<% end %> <% end %>
@ -19,7 +19,7 @@
<% end %> <% end %>
</div> </div>
<% else %> <% else %>
<div class="mt-4">No goals configured for this site yet</div> <div class="mt-4 dark:text-gray-100">No goals configured for this site yet</div>
<% end %> <% end %>
<%= link("+ Add goal", to: "/#{URI.encode_www_form(@site.domain)}/goals/new", class: "button mt-6") %> <%= link("+ Add goal", to: "/#{URI.encode_www_form(@site.domain)}/goals/new", class: "button mt-6") %>

View File

@ -1,7 +1,7 @@
<div class="shadow bg-white sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"> <div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">Google Search Console integration</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Google Search Console integration</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">You can integrate with Google Search Console to get all of your important search results stats such as keyword phrases people find your site with.</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">You can integrate with Google Search Console to get all of your important search results stats such as keyword phrases people find your site with.</p>
<%= link(to: "https://docs.plausible.io/google-search-console-integration/", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/google-search-console-integration/", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
@ -9,18 +9,18 @@
<%= if @site.google_auth do %> <%= if @site.google_auth do %>
<div class="py-2"></div> <div class="py-2"></div>
<span class="text-gray-700">Linked Google account: <b><%= @site.google_auth.email %></b></span> <span class="text-gray-700 dark:text-gray-300">Linked Google account: <b><%= @site.google_auth.email %></b></span>
<%= link("Unlink Google account", to: "/#{URI.encode_www_form(@site.domain)}/settings/google", class: "inline-block mt-4 px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-red-700 bg-white hover:text-red-500 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete") %> <%= link("Unlink Google account", to: "/#{URI.encode_www_form(@site.domain)}/settings/google", class: "inline-block mt-4 px-4 py-2 border border-gray-300 dark:border-gray-500 text-sm leading-5 font-medium rounded-md text-red-700 bg-white dark:bg-gray-800 hover:text-red-500 dark:hover:text-red-400 focus:outline-none focus:border-blue-300 focus:ring active:text-red-800 active:bg-gray-50 transition ease-in-out duration-150", method: "delete") %>
<%= case @search_console_domains do %> <%= case @search_console_domains do %>
<% {:ok, domains} -> %> <% {:ok, domains} -> %>
<%= if @site.google_auth.property && !(@site.google_auth.property in domains) do %> <%= if @site.google_auth.property && !(@site.google_auth.property in domains) do %>
<p class="text-gray-700 mt-6 font-bold"> <p class="text-gray-700 dark:text-gray-300 mt-6 font-bold">
NB: Your Google account does not have access to your currently configured property, <%= @site.google_auth.property %>. Please select a verified property from the list below. NB: Your Google account does not have access to your currently configured property, <%= @site.google_auth.property %>. Please select a verified property from the list below.
</p> </p>
<% else %> <% else %>
<p class="text-gray-700 mt-6"> <p class="text-gray-700 dark:text-gray-300 mt-6">
Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.plausible.io/google-search-console-integration", class: "text-indigo-500") %> on Search Console first. Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.plausible.io/google-search-console-integration", class: "text-indigo-500") %> on Search Console first.
</p> </p>
<% end %> <% end %>
@ -35,13 +35,13 @@
<%= submit "Save", class: "button" %> <%= submit "Save", class: "button" %>
<% end %> <% end %>
<% {:error, error} -> %> <% {:error, error} -> %>
<p class="text-gray-700 mt-6">The following error happened when fetching your Google Search Console domains.</p> <p class="text-gray-700 dark:text-gray-300 mt-6">The following error happened when fetching your Google Search Console domains.</p>
<p class="text-red-700 font-medium mt-3"><%= error %></p> <p class="text-red-700 font-medium mt-3"><%= error %></p>
<% end %> <% end %>
<% else %> <% else %>
<%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-8") %> <%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-8") %>
<div class="text-gray-700 mt-8"> <div class="text-gray-700 dark:text-gray-300 mt-8">
NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.plausible.io/google-search-console-integration", class: "text-indigo-500") %> NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.plausible.io/google-search-console-integration", class: "text-indigo-500") %>
</div> </div>
<% end %> <% end %>

View File

@ -1,7 +1,7 @@
<div class="shadow bg-white sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"> <div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">Public dashboard</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Public dashboard</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">Share your stats publicly or keep them private</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">Share your stats publicly or keep them private</p>
<%= link(to: "https://docs.plausible.io/visibility", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/visibility", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
@ -10,24 +10,24 @@
<%= if @site.public do %> <%= if @site.public do %>
<div class="flex items-center space-x-3 mt-4"> <div class="flex items-center space-x-3 mt-4">
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/make-private", method: "POST", class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/make-private", method: "POST", class: "bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
<span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span> <span class="translate-x-5 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
<% end %> <% end %>
<span class="text-sm leading-5 font-medium text-gray-900">Make stats publicly available on <a href="<%= plausible_url() <> "/" <> URI.encode_www_form(@site.domain)%>" class="text-indigo-500"><%= plausible_url() <> "/" <> URI.encode_www_form(@site.domain)%></a></span> <span class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">Make stats publicly available on <a href="<%= plausible_url() <> "/" <> URI.encode_www_form(@site.domain)%>" class="text-indigo-500"><%= plausible_url() <> "/" <> URI.encode_www_form(@site.domain)%></a></span>
</div> </div>
<% else %> <% else %>
<div class="flex items-center space-x-3 mt-4"> <div class="flex items-center space-x-3 mt-4">
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/make-public", method: "POST", class: "bg-gray-200 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/make-public", method: "POST", class: "bg-gray-200 dark:bg-gray-700 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring") do %>
<span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span> <span class="translate-x-0 inline-block h-5 w-5 rounded-full bg-white dark:bg-gray-800 shadow transform transition ease-in-out duration-200"></span>
<% end %> <% end %>
<span class="text-sm leading-5 font-medium text-gray-900">Make stats publicly available on <a href="<%= plausible_url() <> "/" <> URI.encode_www_form(@site.domain)%>" class="text-indigo-500"><%= plausible_url() <> "/" <> URI.encode_www_form(@site.domain)%></a></span> <span class="text-sm leading-5 font-medium text-gray-900 dark:text-gray-100">Make stats publicly available on <a href="<%= plausible_url() <> "/" <> URI.encode_www_form(@site.domain)%>" class="text-indigo-500"><%= plausible_url() <> "/" <> URI.encode_www_form(@site.domain)%></a></span>
</div> </div>
<% end %> <% end %>
</div> </div>
<div class="shadow bg-white sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6"> <div class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden py-6 px-4 sm:p-6">
<header class="relative"> <header class="relative">
<h2 class="text-lg leading-6 font-medium text-gray-900">Shared links</h2> <h2 class="text-lg leading-6 font-medium text-gray-900 dark:text-gray-100">Shared links</h2>
<p class="mt-1 text-sm leading-5 text-gray-500">You can share your stats privately by generating a shared link. The links are impossible to guess and you can add password protection for extra security.</p> <p class="mt-1 text-sm leading-5 text-gray-500 dark:text-gray-200">You can share your stats privately by generating a shared link. The links are impossible to guess and you can add password protection for extra security.</p>
<%= link(to: "https://docs.plausible.io/shared-links", target: "_blank") do %> <%= link(to: "https://docs.plausible.io/shared-links", target: "_blank") do %>
<svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg> <svg class="w-6 h-6 absolute top-0 right-0 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
<% end %> <% end %>
@ -36,11 +36,11 @@
<div class="mt-6"> <div class="mt-6">
<%= for link <- @shared_links do %> <%= for link <- @shared_links do %>
<div class="flex relative w-full max-w-xl mt-2 text-sm"> <div class="flex relative w-full max-w-xl mt-2 text-sm">
<input type="text" id="<%= link.slug %>" readonly="readonly" value="<%= shared_link_dest(link) %>" class="transition bg-gray-100 appearance-none border border-transparent rounded rounded-r-none w-full p-2 text-gray-700 appearance-none focus:outline-none focus:border-gray-300" /> <input type="text" id="<%= link.slug %>" readonly="readonly" value="<%= shared_link_dest(link) %>" class="transition bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded rounded-r-none w-full p-2 text-gray-700 dark:text-gray-300 focus:outline-none focus:border-gray-300 dark:focus:border-gray-500" />
<button onclick="var input = document.getElementById('<%= link.slug %>'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="py-2 px-4 bg-gray-100 text-indigo-800 rounded-none border-r border-gray-300"> <button onclick="var input = document.getElementById('<%= link.slug %>'); input.focus(); input.select(); document.execCommand('copy');" href="javascript:void(0)" class="py-2 px-4 bg-gray-200 dark:bg-gray-850 text-indigo-800 dark:text-indigo-500 rounded-none border-r border-gray-300 dark:border-gray-500 hover:bg-gray-300 dark:hover:bg-gray-825">
<svg class="feather-sm" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> <svg class="feather-sm" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</button> </button>
<%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/shared-links/#{link.slug}", method: :delete, class: "py-2 px-4 bg-gray-100 text-red-600 rounded-l-none", data: [confirm: "Are you sure you want to delete this shared link? The stats will not be accessible with this link anymore."]) do %> <%= button(to: "/sites/#{URI.encode_www_form(@site.domain)}/shared-links/#{link.slug}", method: :delete, class: "py-2 px-4 bg-gray-200 dark:bg-gray-850 text-red-600 dark:text-red-500 rounded-l-none hover:bg-gray-300 dark:hover:bg-gray-825", data: [confirm: "Are you sure you want to delete this shared link? The stats will not be accessible with this link anymore."]) do %>
<svg class="feather feather-sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg> <svg class="feather feather-sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
<% end %> <% end %>
</div> </div>

View File

@ -1,11 +1,11 @@
<div class="w-full max-w-3xl mt-4 mx-auto flex"> <div class="w-full max-w-3xl mt-4 mx-auto flex">
<%= form_for @conn, "/", [class: "max-w-lg w-full mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> <%= form_for @conn, "/", [class: "max-w-lg w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-bold">Add javascript snippet</h2> <h2 class="text-xl font-bold dark:text-gray-100">Add javascript snippet</h2>
<div class="my-4"> <div class="my-4">
<p>Paste this snippet in the <code>&lt;head&gt;</code> of your website.</p> <p class="dark:text-gray-100">Paste this snippet in the <code>&lt;head&gt;</code> of your website.</p>
<div class="relative"> <div class="relative">
<%= textarea f, :domain, id: "snippet_code", class: "transition overflow-hidden bg-gray-100 appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-400 text-xs mt-4 resize-none", value: snippet(@site), rows: 3, readonly: "readonly" %> <%= textarea f, :domain, id: "snippet_code", class: "transition overflow-hidden bg-gray-100 dark:bg-gray-900 appearance-none border border-transparent rounded w-full p-2 pr-6 text-gray-700 dark:text-gray-300 leading-normal appearance-none focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-400 dark:focus:border-gray-500 text-xs mt-4 resize-none", value: snippet(@site), rows: 3, readonly: "readonly" %>
<a onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="no-underline text-indigo-500 text-sm hover:underline"> <a onclick="var textarea = document.getElementById('snippet_code'); textarea.focus(); textarea.select(); document.execCommand('copy');" href="javascript:void(0)" class="no-underline text-indigo-500 text-sm hover:underline">
<svg class="absolute text-indigo-500" style="top: 24px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg> <svg class="absolute text-indigo-500" style="top: 24px; right: 12px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
</a> </a>

View File

@ -1,12 +1,12 @@
<%= form_for @conn, "/share/#{@link.slug}/authenticate", [class: "max-w-md w-full mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> <%= form_for @conn, "/share/#{@link.slug}/authenticate", [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>
<h2 class="text-xl font-black">Enter password</h2> <h2 class="text-xl font-black dark:text-gray-100">Enter password</h2>
<div class="my-4"> <div class="my-4 dark:text-gray-100">
This link is password-protected. Please enter the password to continue to the dashboard. This link is password-protected. Please enter the password to continue to the dashboard.
</div> </div>
<div class="my-6"> <div class="my-6">
<%= label f, :password, "Password", class: "block text-sm font-bold" %> <%= label f, :password, "Password", class: "block text-sm font-bold dark:text-gray-100" %>
<%= password_input f, :password, class: "transition mt-3 bg-gray-100 appearance-none border border-transparent rounded w-full p-2 text-gray-700 leading-normal appearance-none focus:outline-none focus:bg-white focus:border-gray-300" %> <%= password_input f, :password, class: "transition mt-3 bg-gray-100 dark:bg-gray-900 outline-none appearance-none border border-transparent rounded w-full p-2 text-gray-700 dark:text-gray-300 leading-normal focus:outline-none focus:bg-white dark:focus:bg-gray-800 focus:border-gray-300 dark:focus:border-gray-500" %>
<%= if @conn.assigns[:error] do %> <%= if @conn.assigns[:error] do %>
<div class="text-red-500 text-xs italic mt-4"><%= @conn.assigns[:error] %></div> <div class="text-red-500 text-xs italic mt-4"><%= @conn.assigns[:error] %></div>

View File

@ -8,9 +8,9 @@
<div id="stats-react-container" data-domain="<%= @site.domain %>" data-offset="<%= Timex.Timezone.total_offset(Timex.Timezone.get(@site.timezone)) %>" data-has-goals="<%= @has_goals %>" data-logged-in="<%= !!@conn.assigns[:current_user] %>" data-inserted-at="<%= @site.inserted_at %>"></div> <div id="stats-react-container" data-domain="<%= @site.domain %>" data-offset="<%= Timex.Timezone.total_offset(Timex.Timezone.get(@site.timezone)) %>" data-has-goals="<%= @has_goals %>" data-logged-in="<%= !!@conn.assigns[:current_user] %>" data-inserted-at="<%= @site.inserted_at %>"></div>
<div id="modal_root"></div> <div id="modal_root"></div>
<%= if !@conn.assigns[:current_user] && @conn.assigns[:demo] do %> <%= if !@conn.assigns[:current_user] && @conn.assigns[:demo] do %>
<div class="bg-gray-50"> <div class="bg-gray-50 dark:bg-gray-850">
<div class="py-12 lg:py-16 lg:flex lg:items-center lg:justify-between"> <div class="py-12 lg:py-16 lg:flex lg:items-center lg:justify-between">
<h2 class="text-3xl leading-9 font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:leading-10"> <h2 class="text-3xl leading-9 font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:leading-10 dark:text-gray-100">
Want these stats for your website? Want these stats for your website?
<br /> <br />
<span class="text-indigo-600">Start your free trial today.</span> <span class="text-indigo-600">Start your free trial today.</span>
@ -22,7 +22,7 @@
</a> </a>
</div> </div>
<div class="ml-3 inline-flex rounded-md shadow"> <div class="ml-3 inline-flex rounded-md shadow">
<a href="/" class="inline-flex items-center justify-center px-5 py-3 border border-transparent text-base leading-6 font-medium rounded-md text-indigo-600 bg-white hover:text-indigo-500 focus:outline-none focus:ring transition duration-150 ease-in-out"> <a href="/" class="inline-flex items-center justify-center px-5 py-3 border border-transparent text-base leading-6 font-medium rounded-md text-indigo-600 dark:text-gray-100 bg-white dark:bg-gray-800 hover:text-indigo-500 dark:hover:text-indigo-500 focus:outline-none focus:ring transition duration-150 ease-in-out">
Learn more Learn more
</a> </a>
</div> </div>

View File

@ -14,14 +14,14 @@
</script> </script>
<div class="w-full max-w-md mx-auto mt-8"> <div class="w-full max-w-md mx-auto mt-8">
<div class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-16 relative text-center"> <div class="bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-16 relative text-center">
<h2 class="text-xl font-bold">Waiting for first pageview</h2> <h2 class="text-xl font-bold dark:text-gray-100">Waiting for first pageview</h2>
<h2 class="text-xl font-bold">on <%= @site.domain %></h2> <h2 class="text-xl font-bold dark:text-gray-100">on <%= @site.domain %></h2>
<div class="my-44"> <div class="my-44">
<div class="block pulsating-circle"></div> <div class="block pulsating-circle"></div>
<p class="text-gray-500 text-xs absolute left-0 bottom-0 mb-6 w-full text-center leading-normal"> <p class="text-gray-600 dark:text-gray-400 text-xs absolute left-0 bottom-0 mb-6 w-full text-center leading-normal">
Need to see the snippet again? <%= link("Click here", to: "/#{URI.encode_www_form(@site.domain)}/snippet")%><br /> Need to see the snippet again? <%= link("Click here", to: "/#{URI.encode_www_form(@site.domain)}/snippet")%><br />
Not working? Contact <a href="mailto:support@plausible.io" class="text-gray-700 underline">support@plausible.io</a> to get set up Not working? Contact <a href="mailto:support@plausible.io" class="text-indigo-500 underline">support@plausible.io</a> to get set up
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
<div class="w-full max-w-md mx-auto bg-white shadow-md rounded px-8 py-6 mt-8"]> <div class="w-full max-w-md mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 py-6 mt-8"]>
<h2>Unsubscribe successful</h2> <h2 class="dark:text-gray-100">Unsubscribe successful</h2>
<p class="mt-4">You will no longer receive a <%= @interval %> analytics report for <%= @site %></p> <p class="mt-4 dark:text-gray-100">You will no longer receive a <%= @interval %> analytics report for <%= @site %></p>
</div> </div>

View File

@ -0,0 +1,9 @@
defmodule Plausible.Repo.Migrations.AddThemePrefToUsers do
use Ecto.Migration
def change do
alter table(:users) do
add_if_not_exists :theme, :string, default: "system"
end
end
end