Feature/extend pricing page (#130)
* Extend pricing page * Feature/align pricing page with subscription model (#135) * Align pricing page with subscription model * Update changelog
This commit is contained in:
parent
7c22969de1
commit
e7fbcd4fa0
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Moved the tools to a sub path (`/tools`)
|
- Moved the tools to a sub path (`/tools`)
|
||||||
|
- Extended the pricing page and aligned with the subscription model
|
||||||
|
|
||||||
## 1.9.0 - 01.06.2021
|
## 1.9.0 - 01.06.2021
|
||||||
|
|
||||||
|
@ -4,9 +4,10 @@ import { locale } from '@ghostfolio/common/config';
|
|||||||
import { resetHours } from '@ghostfolio/common/helper';
|
import { resetHours } from '@ghostfolio/common/helper';
|
||||||
import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces';
|
import { User as IUser, UserWithSettings } from '@ghostfolio/common/interfaces';
|
||||||
import { getPermissions, permissions } from '@ghostfolio/common/permissions';
|
import { getPermissions, permissions } from '@ghostfolio/common/permissions';
|
||||||
|
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Currency, Prisma, Provider, User, ViewMode } from '@prisma/client';
|
import { Currency, Prisma, Provider, User, ViewMode } from '@prisma/client';
|
||||||
import { add } from 'date-fns';
|
import { add, isBefore } from 'date-fns';
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
@ -24,7 +25,8 @@ export class UserService {
|
|||||||
alias,
|
alias,
|
||||||
id,
|
id,
|
||||||
role,
|
role,
|
||||||
Settings
|
Settings,
|
||||||
|
subscription
|
||||||
}: UserWithSettings): Promise<IUser> {
|
}: UserWithSettings): Promise<IUser> {
|
||||||
const access = await this.prisma.access.findMany({
|
const access = await this.prisma.access.findMany({
|
||||||
include: {
|
include: {
|
||||||
@ -43,6 +45,7 @@ export class UserService {
|
|||||||
return {
|
return {
|
||||||
alias,
|
alias,
|
||||||
id,
|
id,
|
||||||
|
subscription,
|
||||||
access: access.map((accessItem) => {
|
access: access.map((accessItem) => {
|
||||||
return {
|
return {
|
||||||
alias: accessItem.User.alias,
|
alias: accessItem.User.alias,
|
||||||
@ -54,11 +57,7 @@ export class UserService {
|
|||||||
settings: {
|
settings: {
|
||||||
locale,
|
locale,
|
||||||
baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY,
|
baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY,
|
||||||
viewMode: Settings.viewMode ?? ViewMode.DEFAULT
|
viewMode: Settings?.viewMode ?? ViewMode.DEFAULT
|
||||||
},
|
|
||||||
subscription: {
|
|
||||||
expiresAt: resetHours(add(new Date(), { days: 7 })),
|
|
||||||
type: 'Trial'
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -66,26 +65,49 @@ export class UserService {
|
|||||||
public async user(
|
public async user(
|
||||||
userWhereUniqueInput: Prisma.UserWhereUniqueInput
|
userWhereUniqueInput: Prisma.UserWhereUniqueInput
|
||||||
): Promise<UserWithSettings | null> {
|
): Promise<UserWithSettings | null> {
|
||||||
const user = await this.prisma.user.findUnique({
|
const userFromDatabase = await this.prisma.user.findUnique({
|
||||||
include: { Account: true, Settings: true },
|
include: { Account: true, Settings: true, Subscription: true },
|
||||||
where: userWhereUniqueInput
|
where: userWhereUniqueInput
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user?.Settings) {
|
const user: UserWithSettings = userFromDatabase;
|
||||||
if (!user.Settings.currency) {
|
|
||||||
|
if (userFromDatabase?.Settings) {
|
||||||
|
if (!userFromDatabase.Settings.currency) {
|
||||||
// Set default currency if needed
|
// Set default currency if needed
|
||||||
user.Settings.currency = UserService.DEFAULT_CURRENCY;
|
userFromDatabase.Settings.currency = UserService.DEFAULT_CURRENCY;
|
||||||
}
|
}
|
||||||
} else if (user) {
|
} else if (userFromDatabase) {
|
||||||
// Set default settings if needed
|
// Set default settings if needed
|
||||||
user.Settings = {
|
userFromDatabase.Settings = {
|
||||||
currency: UserService.DEFAULT_CURRENCY,
|
currency: UserService.DEFAULT_CURRENCY,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
userId: user?.id,
|
userId: userFromDatabase?.id,
|
||||||
viewMode: ViewMode.DEFAULT
|
viewMode: ViewMode.DEFAULT
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
||||||
|
if (userFromDatabase?.Subscription?.length > 0) {
|
||||||
|
const latestSubscription = userFromDatabase.Subscription.reduce(
|
||||||
|
(a, b) => {
|
||||||
|
return new Date(a.expiresAt) > new Date(b.expiresAt) ? a : b;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
user.subscription = {
|
||||||
|
expiresAt: latestSubscription.expiresAt,
|
||||||
|
type: isBefore(new Date(), latestSubscription.expiresAt)
|
||||||
|
? SubscriptionType.Premium
|
||||||
|
: SubscriptionType.Basic
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
user.subscription = {
|
||||||
|
type: SubscriptionType.Basic
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
public baseCurrency: Currency;
|
public baseCurrency: Currency;
|
||||||
public currencies: Currency[] = [];
|
public currencies: Currency[] = [];
|
||||||
public defaultDateFormat = DEFAULT_DATE_FORMAT;
|
public defaultDateFormat = DEFAULT_DATE_FORMAT;
|
||||||
public hasPermissionForSubscription: boolean;
|
|
||||||
public hasPermissionToUpdateUserSettings: boolean;
|
public hasPermissionToUpdateUserSettings: boolean;
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
@ -35,13 +34,8 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
this.dataService
|
this.dataService
|
||||||
.fetchInfo()
|
.fetchInfo()
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe(({ currencies, globalPermissions }) => {
|
.subscribe(({ currencies }) => {
|
||||||
this.currencies = currencies;
|
this.currencies = currencies;
|
||||||
|
|
||||||
this.hasPermissionForSubscription = hasPermission(
|
|
||||||
globalPermissions,
|
|
||||||
permissions.enableSubscription
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
|
@ -15,13 +15,13 @@
|
|||||||
<div class="w-50" i18n>Alias</div>
|
<div class="w-50" i18n>Alias</div>
|
||||||
<div class="w-50">{{ user.alias }}</div>
|
<div class="w-50">{{ user.alias }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="hasPermissionForSubscription" class="d-flex py-1">
|
<div *ngIf="user?.subscription" class="d-flex py-1">
|
||||||
<div class="w-50" i18n>Membership</div>
|
<div class="w-50" i18n>Membership</div>
|
||||||
<div class="w-50">
|
<div class="w-50">
|
||||||
<div class="align-items-center d-flex mb-1">
|
<div class="align-items-center d-flex mb-1">
|
||||||
{{ user?.subscription?.type }}
|
{{ user.subscription.type }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div *ngIf="user.subscription.expiresAt">
|
||||||
Valid until {{ user.subscription.expiresAt | date:
|
Valid until {{ user.subscription.expiresAt | date:
|
||||||
defaultDateFormat }}
|
defaultDateFormat }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,95 +2,178 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3 class="d-flex justify-content-center mb-3" i18n>Pricing Plans</h3>
|
<h3 class="d-flex justify-content-center mb-3" i18n>Pricing Plans</h3>
|
||||||
|
<p>
|
||||||
|
Our official
|
||||||
|
<strong>Ghostfolio</strong> cloud offering is the easiest way to get
|
||||||
|
started. Due to the time it saves, this will be the best option for most
|
||||||
|
people. The revenue is used for covering the hosting costs.
|
||||||
|
</p>
|
||||||
|
<p class="mb-5">
|
||||||
|
If you prefer to run <strong>Ghostfolio</strong> on your own
|
||||||
|
infrastructure, please find the source code and further instructions on
|
||||||
|
<a href="https://github.com/ghostfolio/ghostfolio">GitHub</a>.
|
||||||
|
</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-4 mb-3">
|
||||||
<mat-card class="mb-3">
|
<mat-card class="d-flex flex-column h-100">
|
||||||
<h4 i18n>Open Source</h4>
|
<div class="flex-grow-1">
|
||||||
<p>Host your <strong>Ghostfolio</strong> instance by yourself.</p>
|
<h4 i18n>Open Source</h4>
|
||||||
<ul class="list-unstyled mb-3">
|
<p>
|
||||||
<li class="align-items-center d-flex mb-1">
|
For tech-savvy investors who prefer to run
|
||||||
<ion-icon
|
<strong>Ghostfolio</strong> on their own infrastructure.
|
||||||
class="mr-1 text-muted"
|
</p>
|
||||||
name="checkmark-circle-outline"
|
<ul class="list-unstyled mb-3">
|
||||||
></ion-icon>
|
<li class="align-items-center d-flex mb-1">
|
||||||
<span>Portfolio Performance</span>
|
<ion-icon
|
||||||
</li>
|
class="mr-1 text-muted"
|
||||||
<li class="align-items-center d-flex mb-1">
|
name="checkmark-circle-outline"
|
||||||
<ion-icon
|
></ion-icon>
|
||||||
class="mr-1 text-muted"
|
<span>Unlimited Transactions</span>
|
||||||
name="checkmark-circle-outline"
|
</li>
|
||||||
></ion-icon>
|
<li class="align-items-center d-flex mb-1">
|
||||||
<span>Portfolio Summary</span>
|
<ion-icon
|
||||||
</li>
|
class="mr-1 text-muted"
|
||||||
<li class="align-items-center d-flex mb-1">
|
name="checkmark-circle-outline"
|
||||||
<ion-icon
|
></ion-icon>
|
||||||
class="mr-1 text-muted"
|
<span>Portfolio Performance</span>
|
||||||
name="checkmark-circle-outline"
|
</li>
|
||||||
></ion-icon>
|
<li class="align-items-center d-flex mb-1">
|
||||||
<span>Unlimited Transactions</span>
|
<ion-icon
|
||||||
</li>
|
class="mr-1 text-muted"
|
||||||
<li class="align-items-center d-flex mb-1">
|
name="checkmark-circle-outline"
|
||||||
<ion-icon
|
></ion-icon>
|
||||||
class="mr-1 text-muted"
|
<span>Zen Mode</span>
|
||||||
name="checkmark-circle-outline"
|
</li>
|
||||||
></ion-icon>
|
<li class="align-items-center d-flex mb-1">
|
||||||
<span>Advanced Insights</span>
|
<ion-icon
|
||||||
</li>
|
class="mr-1 text-muted"
|
||||||
</ul>
|
name="checkmark-circle-outline"
|
||||||
<p class="h5 text-right">
|
></ion-icon>
|
||||||
<span>Free</span>
|
<span>Portfolio Summary</span>
|
||||||
</p>
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1 text-muted"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span>Advanced Insights</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>Self-hosted.</p>
|
||||||
|
<p class="h5 text-right">Free</p>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-4 mb-3">
|
||||||
<mat-card
|
<mat-card
|
||||||
class="mb-3"
|
class="d-flex flex-column h-100"
|
||||||
[ngClass]="{ 'active': user?.subscription?.type === 'Trial' }"
|
[ngClass]="{ 'active': user?.subscription?.type === 'Basic' }"
|
||||||
>
|
>
|
||||||
<h4 class="align-items-center d-flex" i18n>
|
<div class="flex-grow-1">
|
||||||
Diamond
|
<h4 class="align-items-center d-flex" i18n>Basic</h4>
|
||||||
<ion-icon
|
<p>
|
||||||
class="ml-1 text-muted"
|
For new investors who are just getting started with trading.
|
||||||
name="diamond-outline"
|
</p>
|
||||||
></ion-icon>
|
<ul class="list-unstyled mb-3">
|
||||||
</h4>
|
<li class="align-items-center d-flex mb-1">
|
||||||
<p>
|
<ion-icon
|
||||||
Get a fully managed <strong>Ghostfolio</strong> cloud offering.
|
class="mr-1 text-muted"
|
||||||
</p>
|
name="checkmark-circle-outline"
|
||||||
<ul class="list-unstyled mb-3">
|
></ion-icon>
|
||||||
<li class="align-items-center d-flex mb-1">
|
<span>Unlimited Transactions</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1 text-muted"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span>Portfolio Performance</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1 text-muted"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span>Zen Mode</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ion-icon
|
||||||
|
class="invisible"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<ion-icon
|
||||||
|
class="invisible"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>Fully managed <strong>Ghostfolio</strong> cloud offering.</p>
|
||||||
|
<p class="h5 text-right">Free</p>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4 mb-3">
|
||||||
|
<mat-card
|
||||||
|
class="d-flex flex-column h-100"
|
||||||
|
[ngClass]="{ 'active': user?.subscription?.type === 'Premium' }"
|
||||||
|
>
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<h4 class="align-items-center d-flex" i18n>
|
||||||
|
Premium
|
||||||
<ion-icon
|
<ion-icon
|
||||||
class="mr-1 text-muted"
|
class="ml-1 text-muted"
|
||||||
name="checkmark-circle-outline"
|
name="diamond-outline"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
<span>Portfolio Performance</span>
|
</h4>
|
||||||
</li>
|
<p>
|
||||||
<li class="align-items-center d-flex mb-1">
|
For ambitious investors who need the full picture of their
|
||||||
<ion-icon
|
financial assets.
|
||||||
class="mr-1 text-muted"
|
</p>
|
||||||
name="checkmark-circle-outline"
|
<ul class="list-unstyled mb-3">
|
||||||
></ion-icon>
|
<li class="align-items-center d-flex mb-1">
|
||||||
<span>Portfolio Summary</span>
|
<ion-icon
|
||||||
</li>
|
class="mr-1 text-muted"
|
||||||
<li class="align-items-center d-flex mb-1">
|
name="checkmark-circle-outline"
|
||||||
<ion-icon
|
></ion-icon>
|
||||||
class="mr-1 text-muted"
|
<span>Unlimited Transactions</span>
|
||||||
name="checkmark-circle-outline"
|
</li>
|
||||||
></ion-icon>
|
<li class="align-items-center d-flex mb-1">
|
||||||
<span>Unlimited Transactions</span>
|
<ion-icon
|
||||||
</li>
|
class="mr-1 text-muted"
|
||||||
<li class="align-items-center d-flex mb-1">
|
name="checkmark-circle-outline"
|
||||||
<ion-icon
|
></ion-icon>
|
||||||
class="mr-1 text-muted"
|
<span>Portfolio Performance</span>
|
||||||
name="checkmark-circle-outline"
|
</li>
|
||||||
></ion-icon>
|
<li class="align-items-center d-flex mb-1">
|
||||||
<span>Advanced Insights</span>
|
<ion-icon
|
||||||
</li>
|
class="mr-1 text-muted"
|
||||||
</ul>
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span>Zen Mode</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1 text-muted"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span>Portfolio Summary</span>
|
||||||
|
</li>
|
||||||
|
<li class="align-items-center d-flex mb-1">
|
||||||
|
<ion-icon
|
||||||
|
class="mr-1 text-muted"
|
||||||
|
name="checkmark-circle-outline"
|
||||||
|
></ion-icon>
|
||||||
|
<span>Advanced Insights</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>Fully managed <strong>Ghostfolio</strong> cloud offering.</p>
|
||||||
<p class="h5 text-right">
|
<p class="h5 text-right">
|
||||||
<span class="font-weight-normal"
|
<span class="font-weight-normal"
|
||||||
>{{ user?.settings.baseCurrency || baseCurrency }}
|
>{{ user?.settings.baseCurrency || baseCurrency }}
|
||||||
<strong>2.99</strong>
|
<strong>0.00</strong>
|
||||||
<del class="ml-1 text-muted">3.99</del> / Month</span
|
<del class="ml-1 text-muted">3.99</del> / Month</span
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
@ -99,4 +182,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="!user" class="row">
|
||||||
|
<div class="col mt-3 text-center">
|
||||||
|
<a color="primary" i18n mat-flat-button [routerLink]="['/start']">
|
||||||
|
Create Account
|
||||||
|
</a>
|
||||||
|
<p class="text-muted"><small>It's free</small></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { PricingPageRoutingModule } from './pricing-page-routing.module';
|
import { PricingPageRoutingModule } from './pricing-page-routing.module';
|
||||||
import { PricingPageComponent } from './pricing-page.component';
|
import { PricingPageComponent } from './pricing-page.component';
|
||||||
@ -8,7 +10,13 @@ import { PricingPageComponent } from './pricing-page.component';
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [PricingPageComponent],
|
declarations: [PricingPageComponent],
|
||||||
exports: [],
|
exports: [],
|
||||||
imports: [CommonModule, MatCardModule, PricingPageRoutingModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatCardModule,
|
||||||
|
PricingPageRoutingModule,
|
||||||
|
RouterModule
|
||||||
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,15 @@
|
|||||||
color: rgb(var(--dark-primary-text));
|
color: rgb(var(--dark-primary-text));
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(var(--palette-primary-500), 1);
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgba(var(--palette-primary-300), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mat-card {
|
.mat-card {
|
||||||
&.active {
|
&.active {
|
||||||
border-color: rgba(var(--palette-primary-500), 1);
|
border-color: rgba(var(--palette-primary-500), 1);
|
||||||
@ -11,4 +20,8 @@
|
|||||||
|
|
||||||
:host-context(.is-dark-theme) {
|
:host-context(.is-dark-theme) {
|
||||||
color: rgb(var(--light-primary-text));
|
color: rgb(var(--light-primary-text));
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(var(--light-primary-text));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
||||||
import { Account, Settings, User } from '@prisma/client';
|
import { Account, Settings, User } from '@prisma/client';
|
||||||
|
|
||||||
export type UserWithSettings = User & {
|
export type UserWithSettings = User & {
|
||||||
Account: Account[];
|
Account: Account[];
|
||||||
Settings: Settings;
|
Settings: Settings;
|
||||||
|
subscription?: {
|
||||||
|
expiresAt?: Date;
|
||||||
|
type: SubscriptionType;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,6 @@ export interface User {
|
|||||||
settings: UserSettings;
|
settings: UserSettings;
|
||||||
subscription: {
|
subscription: {
|
||||||
expiresAt: Date;
|
expiresAt: Date;
|
||||||
type: 'Trial';
|
type: 'Basic' | 'Premium';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
4
libs/common/src/lib/types/subscription.type.ts
Normal file
4
libs/common/src/lib/types/subscription.type.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum SubscriptionType {
|
||||||
|
Basic = 'Basic',
|
||||||
|
Premium = 'Premium'
|
||||||
|
}
|
@ -99,21 +99,33 @@ model Settings {
|
|||||||
userId String @id
|
userId String @id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Subscription {
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
expiresAt DateTime
|
||||||
|
id String @default(uuid())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
User User @relation(fields: [userId], references: [id])
|
||||||
|
userId String
|
||||||
|
|
||||||
|
@@id([id, userId])
|
||||||
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
Access Access[] @relation("accessGet")
|
Access Access[] @relation("accessGet")
|
||||||
AccessGive Access[] @relation(name: "accessGive")
|
AccessGive Access[] @relation(name: "accessGive")
|
||||||
accessToken String?
|
accessToken String?
|
||||||
Account Account[]
|
Account Account[]
|
||||||
alias String?
|
alias String?
|
||||||
Analytics Analytics?
|
Analytics Analytics?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
Order Order[]
|
Order Order[]
|
||||||
provider Provider?
|
provider Provider?
|
||||||
role Role @default(USER)
|
role Role @default(USER)
|
||||||
Settings Settings?
|
Settings Settings?
|
||||||
|
Subscription Subscription[]
|
||||||
thirdPartyId String?
|
thirdPartyId String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AccountType {
|
enum AccountType {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user