Sfoglia il codice sorgente

feat(app): frontend confirmation views

Peter Thomassen 5 anni fa
parent
commit
d7975c5e0d

+ 22 - 0
webapp/src/components/ActivateAccountActionHandler.vue

@@ -0,0 +1,22 @@
+<script>
+  import GenericActionHandler from "./GenericActionHandler"
+
+  export default {
+    name: 'ActivateAccountActionHandler',
+    extends: GenericActionHandler,
+    data: () => ({
+      auto_submit: true,
+    }),
+    watch: {
+      success(value) {
+        if(value) {
+          let domain = this.response.data.domain
+          if(domain !== undefined) {
+            let token = this.response.data.token
+            this.$router.push({ name: 'dynsetup', params: { domain: domain.name }, hash: `#${token}` })
+          }
+        }
+      }
+    }
+  };
+</script>

+ 36 - 0
webapp/src/components/GenericActionHandler.vue

@@ -0,0 +1,36 @@
+<template>
+  <div>
+    <div class="text-center" v-if="working">
+      <v-progress-circular align="center" indeterminate></v-progress-circular>
+    </div>
+    <v-alert type="success" v-if="success">
+      <p>{{ this.response.data.detail }}</p>
+    </v-alert>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'GenericActionHandler',
+    data: () => ({
+      auto_submit: false,
+    }),
+    async created() {
+      this.auto_submit = this.auto_submit || this.$options.name == 'GenericActionHandler'
+      if(this.auto_submit) {
+        this.$emit('autosubmit')
+      }
+    },
+    props: {
+      payload: Object,
+      response: Object,
+      valid: Boolean,
+      working: Boolean,
+    },
+    computed: {
+      success: function () {
+        return this.response.status >= 200 && this.response.status < 300
+      }
+    },
+  };
+</script>

+ 45 - 0
webapp/src/components/ResetPasswordActionHandler.vue

@@ -0,0 +1,45 @@
+<template>
+  <div>
+    <div class="text-center" v-if="!success">
+        <v-text-field
+                v-model="payload.new_password"
+                :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
+                label="New password"
+                required
+                :disabled="working"
+                :rules="[rules.required, rules.min]"
+                :type="show ? 'text' : 'password'"
+                hint="At least 8 characters"
+                autocomplete="new-password"
+                @click:append="show = !show"
+        ></v-text-field>
+        <v-btn
+                depressed
+                color="primary"
+                type="submit"
+                :disabled="working || !valid"
+                :loading="working"
+        >Submit</v-btn>
+    </div>
+    <v-alert type="success" v-else>
+      <p>{{ this.response.data.detail }}</p>
+    </v-alert>
+  </div>
+</template>
+
+<script>
+  import GenericActionHandler from "./GenericActionHandler"
+
+  export default {
+    name: 'ResetPasswordActionHandler',
+    extends: GenericActionHandler,
+    data: () => ({
+      rules: {
+        required: value => !!value || 'Required.',
+        min: v => (v !== undefined && v.length >= 8) || 'Min 8 characters',
+      },
+      show: false,
+      working: false,
+    }),
+  };
+</script>

+ 6 - 1
webapp/src/router/index.js

@@ -19,7 +19,7 @@ const routes = [
     component: () => import(/* webpackChunkName: "signup" */ '../views/SignUp.vue')
   },
   {
-    path: '/dynsetup/:domain?',
+    path: '/dynsetup/:domain',
     name: 'dynsetup',
     component: () => import(/* webpackChunkName: "signup" */ '../views/DynSetup.vue')
   },
@@ -33,6 +33,11 @@ const routes = [
     name: 'docs',
     beforeEnter(to) { location.href = to.path },
   },
+  {
+    path: '/confirm/:action/:code',
+    name: 'confirmation',
+    component: () => import(/* webpackChunkName: "signup" */ '../views/Confirmation.vue')
+  },
   {
     path: '/reset-password/:email?',
     name: 'reset-password',

+ 126 - 0
webapp/src/views/Confirmation.vue

@@ -0,0 +1,126 @@
+<template>
+  <v-container
+          class="fill-height"
+          fluid
+  >
+    <v-row
+            align="center"
+            justify="center"
+    >
+      <v-col
+              cols="12"
+              sm="8"
+              md="6"
+      >
+        <v-card class="elevation-12">
+          <v-toolbar
+                  color="primary"
+                  dark
+                  flat
+          >
+            <v-toolbar-title class="capitalize">{{ this.$route.params.action | replace(/-/g, ' ') }} Confirmation</v-toolbar-title>
+          </v-toolbar>
+          <v-card-text>
+            <v-alert :value="!!(errors && errors.length)" type="error">
+              <div v-if="errors.length > 1">
+                <li v-for="error of errors" :key="error.message" >
+                  <b>Error: {{ error.message }}</b>
+                  {{ error }}
+                </li>
+              </div>
+              <div v-else>
+                Error: {{ errors[0] }}
+              </div>
+            </v-alert>
+            <v-form @submit.prevent="confirm" class="mb-4" v-model="valid" ref="form">
+              <div
+                      :is="this.actionHandler"
+                      :payload="this.post_payload"
+                      :response="this.post_response"
+                      :valid="this.valid"
+                      :working="this.working"
+                      ref="actionHandler"
+                      @autosubmit="confirm"
+              ></div>
+            </v-form>
+            <h2 class="title">Keep deSEC Going</h2>
+            <p>
+              To offer free DNS hosting for everyone, deSEC relies on donations only.
+              If you like our service, please consider donating.
+            </p>
+            <p>
+              <v-btn block outlined :to="{name: 'donate'}">Donate</v-btn>
+            </p>
+          </v-card-text>
+          <v-card-actions>
+            <v-spacer />
+          </v-card-actions>
+        </v-card>
+      </v-col>
+    </v-row>
+  </v-container>
+</template>
+
+<script>
+  import axios from 'axios';
+  import GenericActionHandler from '@/components/GenericActionHandler.vue';
+  import ActivateAccountActionHandler from '@/components/ActivateAccountActionHandler.vue';
+  import ResetPasswordActionHandler from '@/components/ResetPasswordActionHandler.vue';
+
+  const HTTP = axios.create({
+    baseURL: '/api/v1/',
+    headers: {
+    },
+  });
+
+  export default {
+    name: 'Confirmation',
+    components: {
+      GenericActionHandler,
+      ActivateAccountActionHandler,
+      ResetPasswordActionHandler,
+    },
+    data: () => ({
+      actionHandler: null,
+      errors: [],
+      handler_map: {'reset-password': 'ResetPasswordActionHandler', 'activate-account': 'ActivateAccountActionHandler'},
+      post_payload: {},
+      post_response: {},
+      success: false,
+      valid: true,
+      working: false,
+    }),
+    async mounted() {
+      this.actionHandler = this.handler_map[this.$route.params.action] || 'GenericActionHandler'
+    },
+    methods: {
+      async confirm() {
+        this.errors = []
+        this.working = true
+        let action = this.$route.params.action
+        try {
+          this.post_response = await HTTP.post(`v/${action}/${this.$route.params.code}/`, this.post_payload);
+          this.success = true
+        } catch (error) {
+          this.post_response = error.response
+          this.errors.push(error.response.data)
+        }
+        this.working = false
+      },
+    },
+    filters: {
+      replace: function (value, a, b) {
+        return value.replace(a, b)
+      }
+    },
+  };
+</script>
+
+<style lang="scss">
+  .fixed-width {
+    font-family: monospace;
+  }
+  .capitalize {
+    text-transform: capitalize;
+  }
+</style>