Wip on terms & privacy page configurator

This commit is contained in:
Dennis
2021-08-10 13:51:37 +02:00
parent f0f427a7bb
commit cbd2b8e0e9
16 changed files with 545 additions and 11 deletions

View File

@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Models\Package; use App\Models\Package;
use Illuminate\Http\Request;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
@@ -87,4 +88,41 @@ class SettingController extends Controller
return redirect()->route('admin.settings')->with('success', __('Settings have been updated')); return redirect()->route('admin.settings')->with('success', __('Settings have been updated'));
} }
public function terms()
{
return inertia('Admin/Terms', [
'settings' => [
'name' => setting('name'),
'terms_required' => setting('accept_terms_required'),
'terms' => setting('terms'),
'privacy' => setting('privacy')
]
]);
}
public function updateTerms(Request $request)
{
setting(['accept_terms_required' => $request->input('terms_required'),]);
setting(['terms' => $request->input('terms'),]);
setting(['privacy' => $request->input('privacy'),]);
return redirect()->route('admin.settings.terms')->with('success', __('Terms have been updated'));
}
public function termsTemplate(Request $request)
{
$template = file_get_contents(storage_path('templates/terms-of-service.md'));
$template = str_replace([
'{NAME}',
'{WEBSITE}',
'{DATE}'
], [
setting('name'),
config('app.url'),
date('Y-m-d')
], $template);
return ['content' => $template];
}
} }

View File

@@ -2,10 +2,23 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Support\Str;
class PageController extends Controller class PageController extends Controller
{ {
public function installationIncomplete() public function installationIncomplete()
{ {
return inertia('Core/InstallationIncomplete'); return inertia('Core/InstallationIncomplete');
} }
public function show($slug)
{
if ($slug === 'terms-of-service' && setting('terms')) {
return inertia('Pages/Terms', [
'content' => Str::markdown(setting('terms'))
]);
}
abort(404);
}
} }

View File

@@ -87,7 +87,8 @@ class HandleInertiaRequests extends Middleware
'documentation' => setting('documentation', false), 'documentation' => setting('documentation', false),
'logo' => setting('logo'), 'logo' => setting('logo'),
'allow_registration' => setting('allow_registration'), 'allow_registration' => setting('allow_registration'),
'billing' => config('cashier.key') && config('cashier.secret') 'billing' => config('cashier.key') && config('cashier.secret'),
'has_terms' => setting('terms') ? true : false
]; ];
}, },
'flash' => function () { 'flash' => function () {

93
package-lock.json generated
View File

@@ -4,6 +4,9 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": {
"vue-simplemde": "^2.0.0"
},
"devDependencies": { "devDependencies": {
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@inertiajs/inertia": "^0.10.0", "@inertiajs/inertia": "^0.10.0",
@@ -3090,6 +3093,19 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/codemirror": {
"version": "5.62.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.2.tgz",
"integrity": "sha512-tVFMUa4J3Q8JUd1KL9yQzQB0/BJt7ZYZujZmTPgo/54Lpuq3ez4C8x/ATUY/wv7b7X3AUq8o3Xd+2C5ZrCGWHw=="
},
"node_modules/codemirror-spell-checker": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz",
"integrity": "sha1-HGYPkIlIPMtRE7m6nKGcP0mTNx4=",
"dependencies": {
"typo-js": "*"
}
},
"node_modules/collect.js": { "node_modules/collect.js": {
"version": "4.28.6", "version": "4.28.6",
"resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.28.6.tgz", "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.28.6.tgz",
@@ -6252,6 +6268,17 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/marked": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==",
"bin": {
"marked": "bin/marked"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/md5": { "node_modules/md5": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@@ -8934,6 +8961,16 @@
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
"dev": true "dev": true
}, },
"node_modules/simplemde": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/simplemde/-/simplemde-1.11.2.tgz",
"integrity": "sha1-ojo12XjSxA7wfewAjJLwcNjggOM=",
"dependencies": {
"codemirror": "*",
"codemirror-spell-checker": "*",
"marked": "*"
}
},
"node_modules/slash": { "node_modules/slash": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -9658,6 +9695,11 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/typo-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.0.tgz",
"integrity": "sha512-dELuLBVa2jvWdU/CHTKi2L/POYaRupv942k+vRsFXsM17acXesQGAiGCio82RW7fvcr7bkuD/Zj8XpUh6aPC2A=="
},
"node_modules/unicode-canonical-property-names-ecmascript": { "node_modules/unicode-canonical-property-names-ecmascript": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
@@ -9924,6 +9966,15 @@
"deepmerge": "^4.2.2" "deepmerge": "^4.2.2"
} }
}, },
"node_modules/vue-simplemde": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/vue-simplemde/-/vue-simplemde-2.0.0.tgz",
"integrity": "sha512-Wn/U/CLTy8y/bqAFg6rba8/uZHGcolx3J1TtvcaQ3mqmDlj6yXOiCy4b8uJxbG96t2A6UBdMyryzODLYAzDYmA==",
"dependencies": {
"marked": "*",
"simplemde": "*"
}
},
"node_modules/vue-style-loader": { "node_modules/vue-style-loader": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
@@ -12876,6 +12927,19 @@
"shallow-clone": "^3.0.0" "shallow-clone": "^3.0.0"
} }
}, },
"codemirror": {
"version": "5.62.2",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.2.tgz",
"integrity": "sha512-tVFMUa4J3Q8JUd1KL9yQzQB0/BJt7ZYZujZmTPgo/54Lpuq3ez4C8x/ATUY/wv7b7X3AUq8o3Xd+2C5ZrCGWHw=="
},
"codemirror-spell-checker": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz",
"integrity": "sha1-HGYPkIlIPMtRE7m6nKGcP0mTNx4=",
"requires": {
"typo-js": "*"
}
},
"collect.js": { "collect.js": {
"version": "4.28.6", "version": "4.28.6",
"resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.28.6.tgz", "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.28.6.tgz",
@@ -15364,6 +15428,11 @@
"p-defer": "^1.0.0" "p-defer": "^1.0.0"
} }
}, },
"marked": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA=="
},
"md5": { "md5": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@@ -17367,6 +17436,16 @@
} }
} }
}, },
"simplemde": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/simplemde/-/simplemde-1.11.2.tgz",
"integrity": "sha1-ojo12XjSxA7wfewAjJLwcNjggOM=",
"requires": {
"codemirror": "*",
"codemirror-spell-checker": "*",
"marked": "*"
}
},
"slash": { "slash": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -17925,6 +18004,11 @@
"mime-types": "~2.1.24" "mime-types": "~2.1.24"
} }
}, },
"typo-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.0.tgz",
"integrity": "sha512-dELuLBVa2jvWdU/CHTKi2L/POYaRupv942k+vRsFXsM17acXesQGAiGCio82RW7fvcr7bkuD/Zj8XpUh6aPC2A=="
},
"unicode-canonical-property-names-ecmascript": { "unicode-canonical-property-names-ecmascript": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
@@ -18142,6 +18226,15 @@
"deepmerge": "^4.2.2" "deepmerge": "^4.2.2"
} }
}, },
"vue-simplemde": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/vue-simplemde/-/vue-simplemde-2.0.0.tgz",
"integrity": "sha512-Wn/U/CLTy8y/bqAFg6rba8/uZHGcolx3J1TtvcaQ3mqmDlj6yXOiCy4b8uJxbG96t2A6UBdMyryzODLYAzDYmA==",
"requires": {
"marked": "*",
"simplemde": "*"
}
},
"vue-style-loader": { "vue-style-loader": {
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",

View File

@@ -34,5 +34,8 @@
"vue-meta": "^2.4.0", "vue-meta": "^2.4.0",
"vue-template-compiler": "^2.6.11", "vue-template-compiler": "^2.6.11",
"vuex": "^3.6.2" "vuex": "^3.6.2"
},
"dependencies": {
"vue-simplemde": "^2.0.0"
} }
} }

18
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -25,21 +25,26 @@
to: this.route('admin.settings'), to: this.route('admin.settings'),
active: this.route().current('admin.settings') active: this.route().current('admin.settings')
}, },
{
title: this.__('Alert messages'),
to: this.route('admin.alerts.index'),
active: this.route().current('admin.alerts.*')
},
{ {
title: this.__('System'), title: this.__('System'),
to: this.route('admin.system'), to: this.route('admin.system'),
active: this.route().current('admin.system') active: this.route().current('admin.system')
}, },
{
title: this.__('Terms'),
to: this.route('admin.settings.terms'),
active: this.route().current('admin.settings.terms')
},
{
title: this.__('Alert messages'),
to: this.route('admin.alerts.index'),
active: this.route().current('admin.alerts.*')
},
{ {
title: this.__('Application logs'), title: this.__('Application logs'),
to: this.route('admin.application-logs'), to: this.route('admin.application-logs'),
active: this.route().current('admin.application-logs') active: this.route().current('admin.application-logs')
} },
], ],
} }
}, },

View File

@@ -0,0 +1,179 @@
<template>
<Page>
<TopBar/>
<Content>
<Container>
<PageHeader>
<template #start>
<PageHeaderTitle>{{ __('Terms') }}</PageHeaderTitle>
</template>
</PageHeader>
<PageBody>
<SettingsLayout>
<template #nav>
<Tabs/>
</template>
<template #segments>
<SettingsSegment>
<template #title>{{ __('Overview') }}</template>
<template #subtitle>
{{
__('Enter content for your terms of service and privacy policy here. You may use markdown.')
}}
</template>
<template #form>
<form class="space-y-4" @submit.prevent="submit">
<div>
<input id="terms_required" class="form-checkbox" type="checkbox"
v-model="form.terms_required">
<label for="terms_required" class="ml-2 text-sm">{{
__('Require users to accept terms of service on registration')
}}</label>
<p class="text-small mt-1 text-medium-emphasis">
{{
__('This will require newly registered users to accept the terms of service.')
}}
</p>
</div>
<Button type="button" size="sm" @click="getTemplate('terms')">
Load Terms of Service template
</Button>
<FormCustom label="Content Terms Of Service">
<vue-simplemde v-model="form.terms" ref="terms_of_service"/>
</FormCustom>
<FormCustom label="Content Privacy Policy">
<vue-simplemde v-model="form.privacy"/>
</FormCustom>
<FormActions>
<Button>{{ __('Save changes') }}</Button>
</FormActions>
</form>
</template>
</SettingsSegment>
</template>
</SettingsLayout>
</PageBody>
</Container>
</Content>
</Page>
</template>
<script>
import TopBar from './components/TopBar'
import Container from '@/components/Container'
import Content from '@/components/Content'
import Page from '@/components/Page'
import PageHeader from '@/components/PageHeader'
import PageHeaderTitle from '@/components/PageHeaderTitle'
import PageBody from '@/components/PageBody'
import Button from '@/components/Button'
import List from '@/components/List'
import ListItem from '@/components/ListItem'
import StatusBubble from '@/components/StatusBubble'
import NotificationBadge from '@/components/NotificationBadge'
import MainLayout from '@/Layouts/MainLayout'
import SettingsLayout from '@/components/layouts/SettingsLayout'
import SettingsSegment from '@/components/SettingsSegment'
import FormInput from '@/components/forms/FormInput'
import FormFileInput from '@/components/forms/FormFileInput'
import FormSelect from '@/components/forms/FormSelect'
import Form from '@/components/Form'
import FormActions from '@/components/FormActions'
import FormCustom from '@/components/forms/FormCustom'
import {useNotification} from '@/hooks/notification'
import Tabs from './Tabs'
import VueSimplemde from 'vue-simplemde'
export default {
metaInfo() {
return {
title: `${this.__('Terms')}`,
}
},
layout: MainLayout,
components: {
TopBar,
Container,
Content,
Page,
PageHeader,
PageHeaderTitle,
PageBody,
Button,
List,
ListItem,
StatusBubble,
NotificationBadge,
FormInput,
FormFileInput,
FormSelect,
SettingsLayout,
SettingsSegment,
Form,
FormActions,
Tabs,
VueSimplemde,
FormCustom
},
data() {
return {
sending: false,
form: this.$inertia.form({
terms: this.settings.terms,
terms_required: this.settings.terms_required,
privacy: this.settings.privacy,
})
}
},
props: {
settings: Object,
},
methods: {
useNotification,
submit() {
this.form.patch(this.route('admin.settings.terms.update'));
},
getTemplate(type) {
axios.get(this.route('admin.settings.terms.template'))
.then(response => {
this.$refs.terms_of_service.simplemde.value(response.data.content);
this.form.terms = response.data.content;
useNotification({
variant: 'success',
title: this.__(`Terms`),
message: 'Template has been loaded in, do not forget to save.',
})
})
}
}
}
</script>
<style>
@import '~simplemde/dist/simplemde.min.css';
.editor-toolbar.fullscreen {
z-index: 50;
}
.CodeMirror-fullscreen {
z-index: 50;
}
.editor-preview-side {
z-index: 50;
}
</style>

View File

@@ -27,6 +27,12 @@
v-if="$page.props.settings.allow_registration" block>Register v-if="$page.props.settings.allow_registration" block>Register
</Button> </Button>
</div> </div>
<TextDivider v-if="$page.props.settings.has_terms" :without-text="true"></TextDivider>
<div class="flex" v-if="$page.props.settings.has_terms">
<inertia-link :href="route('page.show', 'terms-of-service')" class="text-medium-emphasis hover:text-high-emphasis border-b border-dotted">Terms Of Service</inertia-link>
</div>
</form> </form>
</Container> </Container>
</div> </div>

View File

@@ -0,0 +1,45 @@
<template>
<div class="flex items-center justify-center w-full min-h-screen py-8 px-8">
<Container size="medium" class="py-4 space-y-8">
<div class="flex flex-col items-center space-y-5">
<img class="h-14" v-if="$page.props.settings.logo" :src="$page.props.settings.logo" />
<h1 class="font-semibold text-center text-heading">
Terms of Service
</h1>
</div>
<TextDivider :without-text="true"></TextDivider>
<ul class="flex justify-between">
<li>
<inertia-link :href="route('login')" class="text-medium-emphasis hover:text-high-emphasis border-b border-dotted">Back to login</inertia-link>
</li>
</ul>
<TextDivider :without-text="true"></TextDivider>
<div class="prose" v-html="content"></div>
</Container>
</div>
</template>
<script>
import TextDivider from '@/components/TextDivider'
import FormInput from '@/components/forms/FormInput'
import Button from '@/components/Button'
import Container from '@/components/Container'
export default {
metaInfo: {title: 'Terms of Service'},
components: {
TextDivider,
FormInput,
Button,
Container,
},
props: {
content: {
type: String,
required: false
}
}
}
</script>

View File

@@ -8,6 +8,7 @@
const baseClasses = 'w-full px-4 sm:px-8 mx-auto' const baseClasses = 'w-full px-4 sm:px-8 mx-auto'
const sizeClasses = { const sizeClasses = {
small: 'max-w-sm', small: 'max-w-sm',
medium: 'max-w-xl',
base: 'max-w-5xl', base: 'max-w-5xl',
large: 'max-w-screen-xl', large: 'max-w-screen-xl',
fluid: 'max-w-none', fluid: 'max-w-none',

View File

@@ -1,8 +1,19 @@
<template> <template>
<div class="relative flex items-center justify-center"> <div class="relative flex items-center justify-center">
<div class="absolute inset-x-0 w-full border-t border-low-emphasis"></div> <div class="absolute inset-x-0 w-full border-t border-low-emphasis"></div>
<div class="relative px-2 py-1 bg-surface-1 text-body text-medium-emphasis"> <div v-if="!withoutText" class="relative px-2 py-1 bg-surface-1 text-body text-medium-emphasis">
<slot></slot> <slot></slot>
</div> </div>
</div> </div>
</template> </template>
<script>
export default {
props: {
withoutText: {
type: Boolean,
default: false,
}
}
}
</script>

View File

@@ -0,0 +1,72 @@
<template>
<FormGroup>
<Label :errors="errors" :forId="id">{{ label }}</Label>
<slot />
<ErrorText v-if="errors">{{ errors[0] }}</ErrorText>
<HelperText v-if="helperText && !errors">{{ helperText }}</HelperText>
</FormGroup>
</template>
<script>
import FormGroup from '@/components/FormGroup'
import Label from '@/components/Label'
import ErrorText from '@/components/ErrorText'
import HelperText from '@/components/HelperText'
const defaultClasses =
'w-full border-medium-emphasis text-body max-w-lg px-2 border rounded bg-surface-1 focus:outline-none focus:border-primary'
export default {
props: {
id: {
type: String,
required: false,
},
label: {
type: String,
required: true,
},
required: {
type: Boolean,
default: () => false,
},
errors: {
type: Array,
},
placeholder: {
type: String,
},
value: {
required: false,
default: '',
},
rows: {
default: 3,
required: false,
},
helperText: {
type: String
},
},
components: {
FormGroup,
Label,
ErrorText,
HelperText
},
data() {
return {
defaultClasses,
}
},
methods: {
updateValue(value) {
this.$emit('input', value);
},
},
}
</script>

View File

@@ -4,8 +4,14 @@ use Illuminate\Support\Facades\Route;
Route::get('/', 'DashboardController@index')->name('dashboard'); Route::get('/', 'DashboardController@index')->name('dashboard');
Route::get('settings', 'SettingController@index')->name('settings'); Route::group(['prefix' => 'settings'], function () {
Route::patch('settings', 'SettingController@update')->name('settings.update'); Route::get('/', 'SettingController@index')->name('settings');
Route::patch('/', 'SettingController@update')->name('settings.update');
Route::get('terms', 'SettingController@terms')->name('settings.terms');
Route::get('terms/template', 'SettingController@termsTemplate')->name('settings.terms.template');
Route::patch('terms', 'SettingController@updateTerms')->name('settings.terms.update');
});
Route::get('system', 'SystemController@index')->name('system'); Route::get('system', 'SystemController@index')->name('system');
Route::post('system/update', 'SystemController@update')->name('system.update'); Route::post('system/update', 'SystemController@update')->name('system.update');

View File

@@ -11,6 +11,8 @@ Route::post('password-creation', 'Auth\CreatePasswordController@start')->name('p
Route::get('installation-incomplete', 'PageController@installationIncomplete')->name('installation-incomplete'); Route::get('installation-incomplete', 'PageController@installationIncomplete')->name('installation-incomplete');
Route::get('page/{slug}', 'PageController@show')->name('page.show');
// All auth protected routes // All auth protected routes
Route::group(['middleware' => ['auth', 'auth.blocked']], function () { Route::group(['middleware' => ['auth', 'auth.blocked']], function () {
Route::get('/', 'DashboardController@index')->name('dashboard'); Route::get('/', 'DashboardController@index')->name('dashboard');

View File

@@ -0,0 +1,43 @@
This document is created on {DATE}.
Please read these terms of service ("terms", "terms of service") carefully before using {WEBSITE} website (the "service") operated by {NAME} ("us", 'we", "our").
## Conditions of Use
We will provide their services to you, which are subject to the conditions stated below in this document. Every time you visit this website, use its services or make a purchase, you accept the following conditions. This is why we urge you to read them carefully.
## Privacy Policy
Before you continue using our website we advise you to read our privacy policy regarding our user data collection. It will help you better understand our practices.
## Copyright
Content published on this website (digital downloads, images, texts, graphics, logos) is the property of {NAME} and/or its content creators and protected by international copyright laws. The entire compilation of the content found on this website is the exclusive property of {NAME}, with copyright authorship for this compilation by {NAME}.
## Communications
The entire communication with us is electronic. Every time you send us an email or visit our website, you are going to be communicating with us. You hereby consent to receive communications from us. If you subscribe to the news on our website, you are going to receive regular emails from us. We will continue to communicate with you by posting news and notices on our website and by sending you emails. You also agree that all notices, disclosures, agreements and other communications we provide to you electronically meet the legal requirements that such communications be in writing.
## Applicable Law
By visiting this website, you agree that the laws of the location you are currently at, without regard to principles of conflict laws, will govern these terms of service, or any dispute of any sort that might come between {NAME} and you, or its business partners and associates.
## Disputes
Any dispute related in any way to your visit to this website or to products you purchase from us shall be arbitrated by state or federal court from your location and you consent to exclusive jurisdiction and venue of such courts.
## Comments, Reviews, and Emails
Visitors may post content as long as it is not obscene, illegal, defamatory, threatening, infringing of intellectual property rights, invasive of privacy or injurious in any other way to third parties. Content has to be free of software viruses, political campaign, and commercial solicitation.
We reserve all rights (but not the obligation) to remove and/or edit such content. When you post your content, you grant {NAME} non-exclusive, royalty-free and irrevocable right to use, reproduce, publish, modify such content throughout the world in any media.
## License and Site Access
We grant you a limited license to access and make personal use of this website. You are not allowed to download or modify it. This may be done only with written consent from us.
## User Account
If you are an owner of an account on this website, you are solely responsible for maintaining the confidentiality of your private user details (username and password). You are responsible for all activities that occur under your account or password.
We reserve all rights to terminate accounts, edit or remove content and cancel orders in their sole discretion.