1
0
mirror of https://github.com/muety/wakapi.git synced 2023-08-10 21:12:56 +03:00

feat(wip): polish settings ui for subscriptions

This commit is contained in:
Ferdinand Mütsch 2022-12-23 10:54:56 +01:00
parent 0e83ab02fa
commit ebcf87ea93
11 changed files with 97 additions and 67 deletions

View File

@ -145,6 +145,7 @@ You can specify configuration options either via a config file (default: `config
| `app.heartbeat_max_age /`<br>`WAKAPI_HEARTBEAT_MAX_AGE` | `4320h` | Maximum acceptable age of a heartbeat (see [`ParseDuration`](https://pkg.go.dev/time#ParseDuration)) | | `app.heartbeat_max_age /`<br>`WAKAPI_HEARTBEAT_MAX_AGE` | `4320h` | Maximum acceptable age of a heartbeat (see [`ParseDuration`](https://pkg.go.dev/time#ParseDuration)) |
| `app.custom_languages` | - | Map from file endings to language names | | `app.custom_languages` | - | Map from file endings to language names |
| `app.avatar_url_template` /<br>`WAKAPI_AVATAR_URL_TEMPLATE` | (see [`config.default.yml`](config.default.yml)) | URL template for external user avatar images (e.g. from [Dicebear](https://dicebear.com) or [Gravatar](https://gravatar.com)) | | `app.avatar_url_template` /<br>`WAKAPI_AVATAR_URL_TEMPLATE` | (see [`config.default.yml`](config.default.yml)) | URL template for external user avatar images (e.g. from [Dicebear](https://dicebear.com) or [Gravatar](https://gravatar.com)) |
| `app.support_contact` /<br>`WAKAPI_SUPPORT_CONTACT` | `hostmaster@wakapi.dev` | E-Mail address to display as a support contact on the page |
| `server.port` /<br> `WAKAPI_PORT` | `3000` | Port to listen on | | `server.port` /<br> `WAKAPI_PORT` | `3000` | Port to listen on |
| `server.listen_ipv4` /<br> `WAKAPI_LISTEN_IPV4` | `127.0.0.1` | IPv4 network address to listen on (leave blank to disable IPv4) | | `server.listen_ipv4` /<br> `WAKAPI_LISTEN_IPV4` | `127.0.0.1` | IPv4 network address to listen on (leave blank to disable IPv4) |
| `server.listen_ipv6` /<br> `WAKAPI_LISTEN_IPV6` | `::1` | IPv6 network address to listen on (leave blank to disable IPv6) | | `server.listen_ipv6` /<br> `WAKAPI_LISTEN_IPV6` | `::1` | IPv6 network address to listen on (leave blank to disable IPv6) |

View File

@ -79,6 +79,7 @@ type appConfig struct {
CountCacheTTLMin int `yaml:"count_cache_ttl_min" default:"30" env:"WAKAPI_COUNT_CACHE_TTL_MIN"` CountCacheTTLMin int `yaml:"count_cache_ttl_min" default:"30" env:"WAKAPI_COUNT_CACHE_TTL_MIN"`
DataRetentionMonths int `yaml:"data_retention_months" default:"-1" env:"WAKAPI_DATA_RETENTION_MONTHS"` DataRetentionMonths int `yaml:"data_retention_months" default:"-1" env:"WAKAPI_DATA_RETENTION_MONTHS"`
AvatarURLTemplate string `yaml:"avatar_url_template" default:"api/avatar/{username_hash}.svg" env:"WAKAPI_AVATAR_URL_TEMPLATE"` AvatarURLTemplate string `yaml:"avatar_url_template" default:"api/avatar/{username_hash}.svg" env:"WAKAPI_AVATAR_URL_TEMPLATE"`
SupportContact string `yaml:"support_contact" default:"hostmaster@wakapi.dev" env:"WAKAPI_SUPPORT_CONTACT"`
CustomLanguages map[string]string `yaml:"custom_languages"` CustomLanguages map[string]string `yaml:"custom_languages"`
Colors map[string]map[string]string `yaml:"-"` Colors map[string]map[string]string `yaml:"-"`
} }

View File

@ -3,15 +3,17 @@ package view
import "github.com/muety/wakapi/models" import "github.com/muety/wakapi/models"
type SettingsViewModel struct { type SettingsViewModel struct {
User *models.User User *models.User
LanguageMappings []*models.LanguageMapping LanguageMappings []*models.LanguageMapping
Aliases []*SettingsVMCombinedAlias Aliases []*SettingsVMCombinedAlias
Labels []*SettingsVMCombinedLabel Labels []*SettingsVMCombinedLabel
Projects []string Projects []string
SubscriptionPrice string SubscriptionPrice string
ApiKey string DataRetentionMonths int
Success string SupportContact string
Error string ApiKey string
Success string
Error string
} }
type SettingsVMCombinedAlias struct { type SettingsVMCombinedAlias struct {

View File

@ -740,14 +740,16 @@ func (h *SettingsHandler) buildViewModel(r *http.Request) *view.SettingsViewMode
} }
return &view.SettingsViewModel{ return &view.SettingsViewModel{
User: user, User: user,
LanguageMappings: mappings, LanguageMappings: mappings,
Aliases: combinedAliases, Aliases: combinedAliases,
Labels: combinedLabels, Labels: combinedLabels,
Projects: projects, Projects: projects,
ApiKey: user.ApiKey, ApiKey: user.ApiKey,
SubscriptionPrice: subscriptionPrice, SubscriptionPrice: subscriptionPrice,
Success: r.URL.Query().Get("success"), SupportContact: h.config.App.SupportContact,
Error: r.URL.Query().Get("error"), DataRetentionMonths: h.config.App.DataRetentionMonths,
Success: r.URL.Query().Get("success"),
Error: r.URL.Query().Get("error"),
} }
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/stripe/stripe-go/v74/webhook" "github.com/stripe/stripe-go/v74/webhook"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"strings"
"time" "time"
) )
@ -41,7 +42,7 @@ func NewSubscriptionHandler(
if err != nil { if err != nil {
logbuch.Fatal("failed to fetch stripe plan details: %v", err) logbuch.Fatal("failed to fetch stripe plan details: %v", err)
} }
config.Subscriptions.StandardPrice = fmt.Sprintf("%2.f €", price.UnitAmountDecimal/100.0) // TODO: respect actual currency config.Subscriptions.StandardPrice = strings.TrimSpace(fmt.Sprintf("%2.f €", price.UnitAmountDecimal/100.0)) // TODO: respect actual currency
logbuch.Info("enabling subscriptions with stripe payment for %s / month", config.Subscriptions.StandardPrice) logbuch.Info("enabling subscriptions with stripe payment for %s / month", config.Subscriptions.StandardPrice)
} }

View File

@ -77,6 +77,7 @@ let icons = [
'mdi:code-json', 'mdi:code-json',
'mdi:bash', 'mdi:bash',
'twemoji:frowning-face', 'twemoji:frowning-face',
'ci:dot-03-m'
] ]
const output = path.normalize(path.join(__dirname, '../static/assets/js/icons.dist.js')) const output = path.normalize(path.join(__dirname, '../static/assets/js/icons.dist.js'))

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -555,7 +555,7 @@
<div class="flex flex-wrap md:flex-nowrap mb-8 gap-x-4"> <div class="flex flex-wrap md:flex-nowrap mb-8 gap-x-4">
<div class="w-full md:w-1/2 mb-4 md:mb-0 inline-block"> <div class="w-full md:w-1/2 mb-4 md:mb-0 inline-block">
<label class="font-semibold text-gray-300" for="select-timezone">WakaTime</label> <label class="font-semibold text-gray-300 text-lg" for="select-timezone">WakaTime</label>
<span class="block text-sm text-gray-600"> <span class="block text-sm text-gray-600">
You can connect Wakapi with the official WakaTime (or another Wakapi instance, when optionally specifying a custom API URL) in a way that all heartbeats sent to Wakapi are relayed. This way, you can use both services at the same time. To get started, get <a class="link" href="https://wakatime.com/developers#authentication" rel="noopener noreferrer" target="_blank">your API key</a> and paste it here.<br><br> You can connect Wakapi with the official WakaTime (or another Wakapi instance, when optionally specifying a custom API URL) in a way that all heartbeats sent to Wakapi are relayed. This way, you can use both services at the same time. To get started, get <a class="link" href="https://wakatime.com/developers#authentication" rel="noopener noreferrer" target="_blank">your API key</a> and paste it here.<br><br>
To forward data to another Wakapi instance, use <span class="text-xs font-mono">https://&lt;your-server&gt;/api/compat/wakatime/v1</span> as a URL.<br><br> To forward data to another Wakapi instance, use <span class="text-xs font-mono">https://&lt;your-server&gt;/api/compat/wakatime/v1</span> as a URL.<br><br>
@ -594,7 +594,7 @@
<div class="w-full lg:w-3/4"> <div class="w-full lg:w-3/4">
<div class="flex flex-wrap md:flex-nowrap mb-8 gap-x-4"> <div class="flex flex-wrap md:flex-nowrap mb-8 gap-x-4">
<div class="w-full md:w-1/2 mb-4 md:mb-0 inline-block"> <div class="w-full md:w-1/2 mb-4 md:mb-0 inline-block">
<label class="font-semibold text-gray-300" for="select-timezone">Badges</label> <label class="font-semibold text-gray-300 text-lg" for="select-timezone">Badges</label>
<span class="block text-sm text-gray-600"> <span class="block text-sm text-gray-600">
This integration with allows to generate badges for README pages or forums. To enable this feature, you need to grant public, unauthorized access to the respective endpoints. See <a class="link" href="settings#permissions">Permissions</a>. Adapt the URL's <i>label</i> and <i>color</i> parameters for customized badges.<br><br> This integration with allows to generate badges for README pages or forums. To enable this feature, you need to grant public, unauthorized access to the respective endpoints. See <a class="link" href="settings#permissions">Permissions</a>. Adapt the URL's <i>label</i> and <i>color</i> parameters for customized badges.<br><br>
In addition, there is an endpoint compatible with <a class="link" href="https://shields.io" target="_blank" rel="noreferrer noopener">Shields.IO</a> to allow for even more customization (e.g. different <a class="link" href="https://shields.io/#styles" target="_blank" rel="noreferrer noopener">styles</a>). Only available on public instances, not on localhost. In addition, there is an endpoint compatible with <a class="link" href="https://shields.io" target="_blank" rel="noreferrer noopener">Shields.IO</a> to allow for even more customization (e.g. different <a class="link" href="https://shields.io/#styles" target="_blank" rel="noreferrer noopener">styles</a>). Only available on public instances, not on localhost.
@ -651,7 +651,7 @@
<div class="w-full lg:w-3/4"> <div class="w-full lg:w-3/4">
<div class="flex flex-wrap md:flex-nowrap mb-8 gap-x-4"> <div class="flex flex-wrap md:flex-nowrap mb-8 gap-x-4">
<div class="w-full md:w-1/2 mb-4 md:mb-0 inline-block"> <div class="w-full md:w-1/2 mb-4 md:mb-0 inline-block">
<label class="font-semibold text-gray-300" for="select-timezone">GitHub Readme Stats</label> <label class="font-semibold text-gray-300 text-lg" for="select-timezone">GitHub Readme Stats</label>
<span class="block text-sm text-gray-600"> <span class="block text-sm text-gray-600">
Wakapi intregrates with <a class="link" href="https://github.com/anuraghazra/github-readme-stats#wakatime-week-stats" target="_blank" rel="noreferrer noopener">GitHub Readme Stats</a> to generate fancy cards for you. To enable this feature, you need to grant public, unauthorized access to the respective endpoints. See <a class="link" href="settings#permissions">Permissions</a>.<br><br> Wakapi intregrates with <a class="link" href="https://github.com/anuraghazra/github-readme-stats#wakatime-week-stats" target="_blank" rel="noreferrer noopener">GitHub Readme Stats</a> to generate fancy cards for you. To enable this feature, you need to grant public, unauthorized access to the respective endpoints. See <a class="link" href="settings#permissions">Permissions</a>.<br><br>
Only available on public instances, not on localhost. Only available on public instances, not on localhost.
@ -676,30 +676,52 @@
{{ if .SubscriptionsEnabled }} {{ if .SubscriptionsEnabled }}
<div v-cloak id="subscription" class="tab flex flex-col space-y-4" v-if="isActive('subscription')"> <div v-cloak id="subscription" class="tab flex flex-col space-y-4" v-if="isActive('subscription')">
<div class="w-full lg:w-3/4"> <div class="w-full lg:w-1/2">
<span class="font-semibold text-gray-300 text-lg">Subscription</span>
<span class="block text-sm text-gray-600">
By default, this Wakapi instance will only store historical coding activity for {{ .DataRetentionMonths }} months.
However, if you want to support the project, you can opt for a paid subscription for {{ .SubscriptionPrice }} / month to get unlimited history with no restrictions.
You can cancel your subscription at any times!<br>
Read more about the idea of adding paid subscriptions to Wakapi <a class="link" href="https://github.com/muety/wakapi" target="_blank" rel="noopener noreferrer">here</a>.<br>
</span>
<br>
{{ if not .User.HasActiveSubscription }} {{ if not .User.HasActiveSubscription }}
<form action="subscription/checkout" method="post" class="flex mb-8" id="form-subscription-checkout"> <span class="font-semibold text-gray-300">How it works</span>
<div class="w-1/2 mr-4 inline-block"> <span class="block text-sm text-gray-600">
<span class="font-semibold text-gray-300">Subscription</span> Without a subscription, your coding activity older than {{ .DataRetentionMonths }} months will get deleted by a routine that is run every day.
<span class="block text-sm text-gray-600"> If you do have an active subscription at the time of checking, your data is kept.<br>
By default, this Wakapi instance will only store historical coding activity for 12 months. However, if you want to support the project, you can opt for a paid subscription for {{ .SubscriptionPrice }} / month to get unlimited history with no restrictions. In other words, for every point in time <span class="text-xs font-mono">X</span>, where you do not currently have an active subscription, all data older than <span class="text-xs font-mono">X - {{ .DataRetentionMonths }}</span> months gets dropped.
</span> </span>
</div> <br>
<div class="w-1/2 ml-4 flex items-center"> <span class="font-semibold text-gray-300">Please note</span>
<button type="submit" class="btn-primary ml-1">Subscribe ({{ .SubscriptionPrice }} / mo)</button> <span class="block text-sm text-gray-600">
</div> If you just purchased a subscription, it might take a moment until it's active. Try refresh this page in a minute. Otherwise, please contact <a class="link" href="mailto:{{ .SupportContact }}">{{ .SupportContact }}</a>.
</span>
<br>
{{ end }}
<span class="block text-sm text-gray-600">
Your currently oldest data point is from <span class="text-gray-300 font-semibold">2022-01-01</span>.
</span>
<br>
<span class="font-semibold text-gray-300">Subscription status:</span>
<span class="text-gray-600 ml-1 text-sm">
{{ if .User.HasActiveSubscription }}
<span class="font-semibold text-green-500 text-base">Active</span> (until {{ .User.SubscribedUntil.T | date }})
{{ else }}
<span class="font-semibold text-red-500 text-base">Inactive</span>
{{ end }}
</span>
{{ if not .User.HasActiveSubscription }}
<form action="subscription/checkout" method="post" class="mt-8 mb-8" id="form-subscription-checkout">
<button type="submit" class="btn-primary mt-4">Subscribe ({{ .SubscriptionPrice }} / mo)</button>
</form> </form>
{{ else }} {{ else }}
<form action="subscription/portal" method="post" class="flex mb-8" id="form-subscription-portal"> <form action="subscription/portal" method="post" class="mt-8 mb-8" id="form-subscription-portal">
<div class="w-1/2 mr-4 inline-block"> <button type="submit" class="btn-danger">Cancel subscription</button>
<span class="font-semibold text-gray-300">Subscription</span>
<span class="block text-sm text-gray-600">
Congratulations! You have an active subscription until <strong>{{ .User.SubscribedUntil.T | date }}</strong>.
</span>
</div>
<div class="w-1/2 ml-4 flex items-center">
<button type="submit" class="btn-danger ml-1">Cancel subscription</button>
</div>
</form> </form>
{{ end }} {{ end }}
</div> </div>