fedimovies-web/src/views/LandingPage.vue
2021-11-29 21:18:53 +00:00

411 lines
9 KiB
Vue

<template>
<div class="landing-page">
<div v-if="instance" class="instance-info">
<h1 class="instance-title">{{ instance.title }}</h1>
<div class="instance-description">
{{ instance.short_description }}
<br>
<router-link :to="{name: 'about-public'}">Learn more <span class="arrow">&gt;&gt;</span></router-link>
</div>
<div class="login">
<button @click="login()">Sign In</button>
<div v-if="loginErrorMessage" class="error-message">{{ loginErrorMessage }}</div>
</div>
</div>
<form v-if="instance" class="registration-form">
<div v-if="isLoading" class="registration-form-loader">
<loader></loader>
</div>
<div class="form-title">Want to join?</div>
<div class="form-control">
<div class="input-group">
<input id="username" v-model="username" required placeholder="Username">
<div class="addon">@{{ instance.uri }}</div>
</div>
<div class="form-message">Only letters, numbers and underscores are allowed.</div>
</div>
<div class="form-control" v-if="!instance.registrations">
<input
id="invite-token"
v-model="inviteCode"
required
placeholder="Enter the invite code"
>
</div>
<div class="wallet-required">
<img :src="require('@/assets/forkawesome/ethereum.svg')">
<a
href="https://ethereum.org/en/wallets/find-wallet/?filters=has_explore_dapps"
target="_blank"
rel="noreferrer"
>Ethereum Wallet</a> is required
</div>
<button
type="submit"
:disabled="!username"
@click.prevent="register()"
>Sign Up</button>
<div v-if="registrationErrorMessage" class="error-message">{{ registrationErrorMessage }}</div>
</form>
</div>
</template>
<script lang="ts">
import { Options, Vue, setup } from "vue-class-component"
import { Web3Provider } from "@ethersproject/providers"
import { createUser, getAccessToken, getCurrentUser } from "@/api/users"
import { InstanceInfo } from "@/api/instance"
import Loader from "@/components/Loader.vue"
import { useInstanceInfo } from "@/store/instance"
import { useCurrentUser } from "@/store/user"
import { getProvider } from "@/utils/ethereum"
@Options({
components: { Loader },
})
export default class LandingPage extends Vue {
username = ""
inviteCode: string | null = null
isLoading = false
loginErrorMessage: string | null = null
registrationErrorMessage: string | null = null
private store = setup(() => {
const { setCurrentUser, setAuthToken } = useCurrentUser()
const { instance } = useInstanceInfo()
return { setCurrentUser, setAuthToken, instance }
})
get instance(): InstanceInfo | null {
return this.store.instance
}
private async getWalletAddress(provider: Web3Provider): Promise<string | null> {
let walletAddress
try {
[walletAddress] = await provider.send("eth_requestAccounts", [])
} catch (error) {
// Access denied
console.warn(error)
return null
}
return walletAddress
}
private async getSignature(provider: Web3Provider, walletAddress: string, message: string): Promise<string | null> {
let signature
try {
signature = await provider.send(
"personal_sign",
[message, walletAddress],
)
} catch (error) {
// Signature request rejected
console.warn(error)
return null
}
return signature
}
async register() {
this.registrationErrorMessage = null
const provider = getProvider()
if (!provider || !this.store.instance) {
return
}
const loginMessage = this.store.instance.login_message
const walletAddress = await this.getWalletAddress(provider)
if (!walletAddress) {
return
}
const signature = await this.getSignature(provider, walletAddress, loginMessage)
if (!signature) {
return
}
this.isLoading = true
let user
let authToken
try {
user = await createUser({
username: this.username,
password: signature,
wallet_address: walletAddress,
invite_code: this.inviteCode,
})
authToken = await getAccessToken({ wallet_address: walletAddress, signature })
} catch (error) {
this.isLoading = false
this.registrationErrorMessage = error.message
return
}
this.store.setCurrentUser(user)
this.store.setAuthToken(authToken)
this.isLoading = false
this.$router.push({ name: "home" })
}
async login() {
this.loginErrorMessage = null
const provider = getProvider()
if (!provider || !this.store.instance) {
return
}
const loginMessage = this.store.instance.login_message
const walletAddress = await this.getWalletAddress(provider)
if (!walletAddress) {
return
}
const signature = await this.getSignature(provider, walletAddress, loginMessage)
if (!signature) {
return
}
const loginData = { wallet_address: walletAddress, signature }
let user
let authToken
try {
authToken = await getAccessToken(loginData)
user = await getCurrentUser(authToken)
} catch (error) {
this.loginErrorMessage = error.message
return
}
this.store.setCurrentUser(user)
this.store.setAuthToken(authToken)
this.$router.push({ name: "home" })
}
}
</script>
<style scoped lang="scss">
@import "../styles/layout";
@import "../styles/theme";
$text-color: #fff;
button {
background-color: #000;
border: none;
border-radius: 10px;
color: $text-color;
cursor: pointer;
display: block;
font-size: 20px;
font-weight: bold;
padding: 10px 60px;
}
.landing-page {
align-items: flex-start;
background-color: #000;
background-image: url("../assets/startpage.png");
background-repeat: no-repeat;
background-size: cover;
box-sizing: border-box;
color: $text-color;
display: flex;
flex-direction: row;
gap: $content-gap;
justify-content: center;
min-height: 100vh;
padding-top: 20vh;
}
.instance-info {
max-width: $wide-content-width;
min-width: 0;
}
.instance-title {
font-size: 90px;
font-weight: bold;
margin-bottom: 20px;
text-transform: uppercase;
word-wrap: break-word;
}
.instance-description {
font-size: 28px;
line-height: 1.75;
a {
color: $text-color;
}
.arrow {
color: #7DFF54;
&:hover {
color: $text-color;
}
}
}
.login {
display: inline-block;
margin-top: 30px;
text-align: center;
button {
border: 1px solid #979797;
box-shadow: 0 2px 16px -5px #6E6E6E;
&:hover {
background-color: #fff;
border-color: #fff;
color: #000;
}
}
.error-message {
color: $error-color;
margin-top: 5px;
}
}
.registration-form {
border: 1px solid #979797;
border-radius: 10px;
box-sizing: border-box;
display: flex;
flex-direction: column;
min-width: $wide-sidebar-width - 50px;
padding: 25px 40px;
position: relative;
width: $wide-sidebar-width;
.form-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 25px;
text-align: center;
}
.form-control {
margin-bottom: 15px;
}
input,
.addon {
background-color: #201f1f;
border: none;
line-height: 18px;
padding: 15px;
}
input {
border-radius: 10px;
color: $text-color;
min-width: 100px;
&::placeholder {
color: #B3B3B3;
}
}
.input-group {
display: flex;
flex-direction: row;
input {
border-radius: 10px 0 0 10px;
min-width: 0;
}
.addon {
border-radius: 0 10px 10px 0;
color: #B3B3B3;
padding-left: 0;
text-align: right;
}
}
.form-message {
font-size: 12px;
margin-top: 3px;
padding: 0 15px;
}
button {
background: linear-gradient(to right, #FF5959, #FF5EAD, #D835FE, #D963FF);
box-shadow: 0 2px 16px -5px #BB5CC7;
height: 48px;
margin-top: 5px;
&:hover {
background: linear-gradient(to right, #FF7373, #FF78BA, #DD4FFE, #DF7DFF);
}
}
.error-message {
color: $error-color;
margin-top: 10px;
text-align: center;
}
.wallet-required {
align-items: center;
display: flex;
flex-direction: row;
gap: 0.4em;
justify-content: center;
margin-bottom: 15px;
img {
filter: $btn-text-hover-colorizer;
height: 1em;
}
a {
color: $text-color;
text-decoration: underline;
}
}
}
.registration-form-loader {
bottom: 0;
display: flex;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: 1;
.loader {
margin-bottom: auto;
margin-top: auto;
}
}
.loader {
margin: 0 auto;
}
@media screen and (max-width: $screen-breakpoint-medium) {
.registration-form {
padding: 25px;
}
}
@media screen and (max-width: $screen-breakpoint-small) {
.landing-page {
flex-wrap: wrap;
justify-content: flex-start;
padding-top: $content-gap;
}
.registration-form {
margin-right: auto;
min-width: auto;
.form-title {
text-align: left;
}
}
}
</style>