1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-08-05 05:25:26 +02:00

feat(backend): migrate site-settings to groups (#673)

* feat(frontend):  add user registration page (WIP)

* feat(backend):  add user registration (WIP)

* test(backend):  add validator testing for registration schema

* feat(backend):  continued work on user sign-up

* feat(backend):  add signup flow and user/group settings

* test(backend):  user-creation tests and small refactor of existing tests

* fix(backend):  fix failing group tests

* style: 🎨 fix lint issues

Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
Hayden 2021-09-05 22:05:29 -08:00 committed by GitHub
parent e179dcdb10
commit 3c504e7048
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 1665 additions and 841 deletions

View file

@ -8,6 +8,8 @@ const routes = {
groupsSelf: `${prefix}/groups/self`,
categories: `${prefix}/groups/categories`,
preferences: `${prefix}/groups/preferences`,
groupsId: (id: string | number) => `${prefix}/groups/${id}`,
};
@ -21,13 +23,34 @@ export interface CreateGroup {
name: string;
}
export interface UpdatePreferences {
privateGroup: boolean;
firstDayOfWeek: number;
recipePublic: boolean;
recipeShowNutrition: boolean;
recipeShowAssets: boolean;
recipeLandscapeView: boolean;
recipeDisableComments: boolean;
recipeDisableAmount: boolean;
}
export interface Preferences extends UpdatePreferences {
id: number;
group_id: number;
}
export interface Group extends CreateGroup {
id: number;
preferences: Preferences;
}
export class GroupAPI extends BaseCRUDAPI<GroupInDB, CreateGroup> {
baseRoute = routes.groups;
itemRoute = routes.groupsId;
/** Returns the Group Data for the Current User
*/
async getCurrentUserGroup() {
return await this.requests.get(routes.groupsSelf);
return await this.requests.get<Group>(routes.groupsSelf);
}
async getCategories() {
@ -37,4 +60,12 @@ export class GroupAPI extends BaseCRUDAPI<GroupInDB, CreateGroup> {
async setCategories(payload: Category[]) {
return await this.requests.put<Category[]>(routes.categories, payload);
}
async getPreferences() {
return await this.requests.get<Preferences>(routes.preferences);
}
async setPreferences(payload: UpdatePreferences) {
return await this.requests.put<Preferences>(routes.preferences, payload);
}
}

View file

@ -0,0 +1,25 @@
import { BaseAPI } from "./_base";
export interface RegisterPayload {
group: string;
groupToken: string;
email: string;
password: string;
passwordConfirm: string;
advanced: boolean;
private: boolean;
}
const prefix = "/api";
const routes = {
register: `${prefix}/users/register`,
};
export class RegisterAPI extends BaseAPI {
/** Returns a list of avaiable .zip files for import into Mealie.
*/
async register(payload: RegisterPayload) {
return await this.requests.post<any>(routes.register, payload);
}
}

View file

@ -13,6 +13,7 @@ import { UnitAPI } from "./class-interfaces/recipe-units";
import { CookbookAPI } from "./class-interfaces/cookbooks";
import { WebhooksAPI } from "./class-interfaces/group-webhooks";
import { AdminAboutAPI } from "./class-interfaces/admin-about";
import { RegisterAPI } from "./class-interfaces/user-registration";
import { ApiRequestInstance } from "~/types/api";
class AdminAPI {
@ -46,6 +47,7 @@ class Api {
public units: UnitAPI;
public cookbooks: CookbookAPI;
public groupWebhooks: WebhooksAPI;
public register: RegisterAPI;
// Utils
public upload: UploadFile;
@ -67,6 +69,7 @@ class Api {
this.groups = new GroupAPI(requests);
this.cookbooks = new CookbookAPI(requests);
this.groupWebhooks = new WebhooksAPI(requests);
this.register = new RegisterAPI(requests);
// Admin
this.events = new EventsAPI(requests);

View file

@ -61,13 +61,7 @@
</v-fade-transition>
</v-card-title>
<v-card-text v-if="edit">
<v-textarea
:key="generateKey('instructions', index)"
v-model="value[index]['text']"
auto-grow
dense
rows="4"
>
<v-textarea :key="'instructions' + index" v-model="value[index]['text']" auto-grow dense rows="4">
</v-textarea>
</v-card-text>
<v-expand-transition>

View file

@ -134,9 +134,9 @@
</template>
<script>
import { ref } from "@nuxtjs/composition-api";
import { validators } from "@/composables/use-validators";
import { fieldTypes } from "@/composables/forms";
import { ref } from "@nuxtjs/composition-api";
const BLUR_EVENT = "blur";

View file

@ -138,9 +138,9 @@
</template>
<script>
import { ref } from "@nuxtjs/composition-api";
import { validators } from "@/composables/use-validators";
import { fieldTypes } from "@/composables/forms";
import { ref } from "@nuxtjs/composition-api";
const BLUR_EVENT = "blur";

View file

@ -3,7 +3,38 @@ import { useAsyncKey } from "./use-utils";
import { useApiSingleton } from "~/composables/use-api";
import { CreateGroup } from "~/api/class-interfaces/groups";
export const useGroup = function () {
export const useGroupSelf = function () {
const api = useApiSingleton();
const actions = {
get() {
const group = useAsync(async () => {
const { data } = await api.groups.getCurrentUserGroup();
return data;
}, useAsyncKey());
return group;
},
async updatePreferences() {
if (!group.value) {
return;
}
const { data } = await api.groups.setPreferences(group.value.preferences);
if (data) {
group.value.preferences = data;
}
},
};
const group = actions.get();
return { actions, group };
};
export const useGroupCategories = function () {
const api = useApiSingleton();
const actions = {
@ -61,7 +92,6 @@ export const useGroups = function () {
}
async function createGroup(payload: CreateGroup) {
console.log(payload);
loading.value = true;
const { data } = await api.groups.createOne(payload);

View file

@ -1,13 +1,10 @@
<template>
<v-app dark>
<!-- <TheSnackbar /> -->
<AppSidebar
v-model="sidebar"
absolute
:top-link="topLinks"
:secondary-links="$auth.user.admin ? adminLinks : null"
:bottom-links="$auth.user.admin ? bottomLinks : null"
:bottom-links="bottomLinks"
:user="{ data: true }"
:secondary-header="$t('user.admin')"
@input="sidebar = !sidebar"
@ -30,7 +27,7 @@
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { defineComponent, ref, useContext } from "@nuxtjs/composition-api";
import AppHeader from "@/components/Layout/AppHeader.vue";
import AppSidebar from "@/components/Layout/AppSidebar.vue";
import TheSnackbar from "~/components/Layout/TheSnackbar.vue";
@ -40,103 +37,110 @@ export default defineComponent({
middleware: "auth",
auth: true,
setup() {
return {};
},
data() {
// @ts-ignore - $globals not found in type definition
const { $globals, i18n } = useContext();
const sidebar = ref(null);
const topLinks = [
{
icon: $globals.icons.viewDashboard,
to: "/admin/dashboard",
title: i18n.t("sidebar.dashboard"),
},
{
icon: $globals.icons.cog,
to: "/admin/site-settings",
title: i18n.t("sidebar.site-settings"),
},
{
icon: $globals.icons.tools,
to: "/admin/toolbox",
title: i18n.t("sidebar.toolbox"),
children: [
{
icon: $globals.icons.bellAlert,
to: "/admin/toolbox/notifications",
title: i18n.t("events.notification"),
},
{
icon: $globals.icons.foods,
to: "/admin/toolbox/foods",
title: "Manage Foods",
},
{
icon: $globals.icons.units,
to: "/admin/toolbox/units",
title: "Manage Units",
},
{
icon: $globals.icons.tags,
to: "/admin/toolbox/categories",
title: i18n.t("sidebar.tags"),
},
{
icon: $globals.icons.tags,
to: "/admin/toolbox/tags",
title: i18n.t("sidebar.categories"),
},
{
icon: $globals.icons.broom,
to: "/admin/toolbox/organize",
title: i18n.t("settings.organize"),
},
],
},
{
icon: $globals.icons.group,
to: "/admin/manage-users",
title: i18n.t("sidebar.manage-users"),
children: [
{
icon: $globals.icons.user,
to: "/admin/manage-users/all-users",
title: i18n.t("user.users"),
},
{
icon: $globals.icons.group,
to: "/admin/manage-users/all-groups",
title: i18n.t("group.groups"),
},
],
},
{
icon: $globals.icons.import,
to: "/admin/migrations",
title: i18n.t("sidebar.migrations"),
},
{
icon: $globals.icons.database,
to: "/admin/backups",
title: i18n.t("sidebar.backups"),
},
];
const bottomLinks = [
{
icon: $globals.icons.heart,
title: i18n.t("about.support"),
href: "https://github.com/sponsors/hay-kot",
},
{
icon: $globals.icons.information,
title: i18n.t("about.about"),
to: "/admin/about",
},
];
return {
sidebar: null,
topLinks: [
{
icon: this.$globals.icons.viewDashboard,
to: "/admin/dashboard",
title: this.$t("sidebar.dashboard"),
},
{
icon: this.$globals.icons.cog,
to: "/admin/site-settings",
title: this.$t("sidebar.site-settings"),
},
{
icon: this.$globals.icons.tools,
to: "/admin/toolbox",
title: this.$t("sidebar.toolbox"),
children: [
{
icon: this.$globals.icons.bellAlert,
to: "/admin/toolbox/notifications",
title: this.$t("events.notification"),
},
{
icon: this.$globals.icons.foods,
to: "/admin/toolbox/foods",
title: "Manage Foods",
},
{
icon: this.$globals.icons.units,
to: "/admin/toolbox/units",
title: "Manage Units",
},
{
icon: this.$globals.icons.tags,
to: "/admin/toolbox/categories",
title: this.$t("sidebar.tags"),
},
{
icon: this.$globals.icons.tags,
to: "/admin/toolbox/tags",
title: this.$t("sidebar.categories"),
},
{
icon: this.$globals.icons.broom,
to: "/admin/toolbox/organize",
title: this.$t("settings.organize"),
},
],
},
{
icon: this.$globals.icons.group,
to: "/admin/manage-users",
title: this.$t("sidebar.manage-users"),
children: [
{
icon: this.$globals.icons.user,
to: "/admin/manage-users/all-users",
title: this.$t("user.users"),
},
{
icon: this.$globals.icons.group,
to: "/admin/manage-users/all-groups",
title: this.$t("group.groups"),
},
],
},
{
icon: this.$globals.icons.import,
to: "/admin/migrations",
title: this.$t("sidebar.migrations"),
},
{
icon: this.$globals.icons.database,
to: "/admin/backups",
title: this.$t("sidebar.backups"),
},
],
bottomLinks: [
{
icon: this.$globals.icons.heart,
title: this.$t("about.support"),
href: "https://github.com/sponsors/hay-kot",
},
{
icon: this.$globals.icons.information,
title: this.$t("about.about"),
to: "/admin/about",
},
],
sidebar,
topLinks,
bottomLinks,
};
},
});
</script>
<style scoped>
</style>+

View file

@ -1,6 +1,6 @@
<template>
<v-app dark>
<!-- <TheSnackbar /> -->
<TheSnackbar />
<AppHeader :menu="false"> </AppHeader>
<v-main>
@ -17,9 +17,10 @@
import { defineComponent } from "@nuxtjs/composition-api";
import AppFooter from "@/components/Layout/AppFooter.vue";
import AppHeader from "@/components/Layout/AppHeader.vue";
import TheSnackbar from "~/components/Layout/TheSnackbar.vue";
export default defineComponent({
components: { AppHeader, AppFooter },
components: { AppHeader, AppFooter, TheSnackbar },
setup() {
return {};
},

View file

@ -8,7 +8,7 @@
:top-link="topLinks"
secondary-header="Cookbooks"
:secondary-links="cookbookLinks || []"
:bottom-links="$auth.user.admin ? bottomLink : []"
:bottom-links="isAdmin ? bottomLink : []"
@input="sidebar = !sidebar"
/>
@ -37,11 +37,13 @@ import { useCookbooks } from "~/composables/use-group-cookbooks";
export default defineComponent({
components: { AppHeader, AppSidebar, AppFloatingButton },
// @ts-ignore
// middleware: process.env.GLOBAL_MIDDLEWARE,
middleware: "auth",
setup() {
const { cookbooks } = useCookbooks();
// @ts-ignore
const { $globals } = useContext();
const { $globals, $auth } = useContext();
const isAdmin = computed(() => $auth.user?.admin);
const cookbookLinks = computed(() => {
if (!cookbooks.value) return [];
@ -53,7 +55,7 @@ export default defineComponent({
};
});
});
return { cookbookLinks };
return { cookbookLinks, isAdmin };
},
data() {
return {

View file

@ -56,7 +56,7 @@ export default {
// https://go.nuxtjs.dev/pwa
"@nuxtjs/pwa",
// https://i18n.nuxtjs.org/setup
"nuxt-i18n",
"@nuxtjs/i18n",
// https://auth.nuxtjs.org/guide/setup
"@nuxtjs/auth-next",
// https://github.com/nuxt-community/proxy-module
@ -81,8 +81,8 @@ export default {
auth: {
redirect: {
login: "/user/login",
logout: "/",
login: "/login",
logout: "/login",
callback: "/login",
home: "/",
},

View file

@ -18,6 +18,7 @@
"@mdi/js": "^5.9.55",
"@nuxtjs/auth-next": "5.0.0-1624817847.21691f1",
"@nuxtjs/axios": "^5.13.6",
"@nuxtjs/i18n": "^7.0.3",
"@nuxtjs/proxy": "^2.1.0",
"@nuxtjs/pwa": "^3.3.5",
"@vue/composition-api": "^1.0.5",
@ -25,7 +26,6 @@
"core-js": "^3.15.1",
"fuse.js": "^6.4.6",
"nuxt": "^2.15.7",
"nuxt-i18n": "^6.28.0",
"vuedraggable": "^2.24.3",
"vuetify": "^2.5.5"
},
@ -33,7 +33,7 @@
"@babel/eslint-parser": "^7.14.7",
"@nuxt/types": "^2.15.7",
"@nuxt/typescript-build": "^2.1.0",
"@nuxtjs/composition-api": "^0.26.0",
"@nuxtjs/composition-api": "^0.28.0",
"@nuxtjs/eslint-config-typescript": "^6.0.1",
"@nuxtjs/eslint-module": "^3.0.2",
"@nuxtjs/vuetify": "^1.12.1",
@ -50,4 +50,4 @@
"resolutions": {
"vite": "2.3.8"
}
}
}

View file

@ -95,8 +95,8 @@
</template>
<script lang="ts">
import AdminBackupImportOptions from "@/components/Domain/Admin/AdminBackupImportOptions.vue";
import { defineComponent, reactive, toRefs, useContext, ref } from "@nuxtjs/composition-api";
import AdminBackupImportOptions from "@/components/Domain/Admin/AdminBackupImportOptions.vue";
import { useBackups } from "~/composables/use-backups";
export default defineComponent({

View file

@ -23,8 +23,8 @@
</template>
<script lang="ts">
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
import { defineComponent, useRoute, ref } from "@nuxtjs/composition-api";
import RecipeCardSection from "@/components/Domain/Recipe/RecipeCardSection.vue";
import { useCookbook } from "~/composables/use-group-cookbooks";
export default defineComponent({
components: { RecipeCardSection },

View file

@ -178,10 +178,9 @@
</v-btn>
</v-form>
</v-card-text>
<v-btn v-if="$config.ALLOW_SIGNUP" class="mx-auto" text to="/user/sign-up"> Sign Up </v-btn>
<v-btn v-if="$config.ALLOW_SIGNUP" class="mx-auto" text to="/register"> Register </v-btn>
<v-btn v-else class="mx-auto" text disabled> Invite Only </v-btn>
</v-card>
<!-- <v-col class="fill-height"> </v-col> -->
</v-container>
</template>

170
frontend/pages/register.vue Normal file
View file

@ -0,0 +1,170 @@
<template>
<v-container fill-height fluid class="d-flex justify-center align-start narrow-container">
<v-card color="background d-flex flex-column align-center" flat width="700px">
<v-card-title class="headline"> User Registration </v-card-title>
<v-card-text>
<v-form ref="domRegisterForm" @submit.prevent="register()">
<ToggleState>
<template #activator="{ toggle }">
<div class="d-flex justify-center my-2">
<v-btn-toggle tile mandatory group color="primary">
<v-btn small @click="toggle(false)"> Create a Group </v-btn>
<v-btn small @click="toggle(true)"> Join a Group </v-btn>
</v-btn-toggle>
</div>
</template>
<template #default="{ state }">
<v-text-field
v-if="!state"
v-model="form.group"
filled
rounded
autofocus
validate-on-blur
class="rounded-lg"
:prepend-icon="$globals.icons.group"
:rules="[tokenOrGroup]"
label="New Group Name"
/>
<v-text-field
v-else
v-model="form.groupToken"
filled
rounded
validate-on-blur
:rules="[tokenOrGroup]"
class="rounded-lg"
:prepend-icon="$globals.icons.group"
label="Group Token"
/>
</template>
</ToggleState>
<v-text-field
v-model="form.email"
filled
rounded
class="rounded-lg"
validate-on-blur
:prepend-icon="$globals.icons.email"
label="Email"
:rules="[validators.required, validators.email]"
/>
<v-text-field
v-model="form.username"
filled
rounded
class="rounded-lg"
:prepend-icon="$globals.icons.user"
label="Username"
:rules="[validators.required]"
/>
<v-text-field
v-model="form.password"
filled
rounded
class="rounded-lg"
:prepend-icon="$globals.icons.lock"
name="password"
label="Password"
type="password"
:rules="[validators.required]"
/>
<v-text-field
v-model="form.passwordConfirm"
filled
rounded
validate-on-blur
class="rounded-lg"
:prepend-icon="$globals.icons.lock"
name="password"
label="Confirm Password"
type="password"
:rules="[validators.required, passwordMatch]"
/>
<div class="mt-n4 px-8">
<v-checkbox v-model="form.private" label="Keep My Recipes Private"></v-checkbox>
<p class="text-caption mt-n4">
Sets your group and all recipes defaults to private. You can always change this later.
</p>
<v-checkbox v-model="form.advanced" label="Enable Advanced Content"></v-checkbox>
<p class="text-caption mt-n4">
Enables advanced features like Recipe Scaling, API keys, Webhooks, and Data Management. Don't worry, you
can always change this later
</p>
</div>
<div class="d-flex flex-column justify-center">
<v-btn :loading="loggingIn" color="primary" type="submit" large rounded class="rounded-xl" block>
Register
</v-btn>
<v-btn class="mx-auto my-2" text to="/login"> Login </v-btn>
</div>
</v-form>
</v-card-text>
</v-card>
</v-container>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, toRefs, ref, useRouter } from "@nuxtjs/composition-api";
import { validators } from "@/composables/use-validators";
import { useApiSingleton } from "~/composables/use-api";
import { alert } from "~/composables/use-toast";
export default defineComponent({
layout: "basic",
setup() {
const api = useApiSingleton();
const state = reactive({
loggingIn: false,
success: false,
});
const allowSignup = computed(() => process.env.AllOW_SIGNUP);
const router = useRouter();
// @ts-ignore
const domRegisterForm = ref<VForm>(null);
const form = reactive({
group: "",
groupToken: "",
email: "",
username: "",
password: "",
passwordConfirm: "",
advanced: false,
private: false,
});
const passwordMatch = () => form.password === form.passwordConfirm || "Passwords do not match";
const tokenOrGroup = () => form.group !== "" || form.groupToken !== "" || "Group name or token must be given";
async function register() {
if (!domRegisterForm.value?.validate()) {
return;
}
const { data, response } = await api.register.register(form);
if (response?.status === 201) {
state.success = true;
alert.success("Registration Success");
router.push("/user/login");
}
console.log(data, response);
}
return {
domRegisterForm,
validators,
allowSignup,
form,
...toRefs(state),
passwordMatch,
tokenOrGroup,
register,
};
},
});
</script>

View file

@ -50,8 +50,8 @@
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { useCookbooks } from "@/composables/use-group-cookbooks";
import draggable from "vuedraggable";
import { useCookbooks } from "@/composables/use-group-cookbooks";
export default defineComponent({
components: { draggable },

View file

@ -1,39 +1,139 @@
<template>
<v-container>
<BasePageTitle divider>
<v-container class="narrow-container">
<BasePageTitle class="mb-5">
<template #header>
<v-img max-height="100" max-width="100" :src="require('~/static/svgs/manage-group-settings.svg')"></v-img>
</template>
<template #title> Group Settings </template>
These items are shared within your group. Editing one of them will change it for the whole group!
</BasePageTitle>
<v-card tag="section" outlined>
<v-card-text>
<BaseCardSectionTitle title="Mealplan Categories">
Set the categories below for the ones that you want to be included in your mealplan random generation.
<div class="mt-2">
<BaseButton save @click="actions.updateAll()" />
</div>
</BaseCardSectionTitle>
<DomainRecipeCategoryTagSelector v-if="categories" v-model="categories" />
</v-card-text>
</v-card>
<section>
<BaseCardSectionTitle title="Mealplan Categories">
Set the categories below for the ones that you want to be included in your mealplan random generation.
</BaseCardSectionTitle>
<DomainRecipeCategoryTagSelector v-if="categories" v-model="categories" />
<v-card-actions>
<v-spacer></v-spacer>
<BaseButton save @click="actions.updateAll()" />
</v-card-actions>
</section>
<section v-if="group">
<BaseCardSectionTitle class="mt-10" title="Group Preferences"></BaseCardSectionTitle>
<v-checkbox
v-model="group.preferences.privateGroup"
class="mt-n4"
label="Private Group"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-select
v-model="group.preferences.firstDayOfWeek"
:prepend-icon="$globals.icons.calendarWeekBegin"
:items="allDays"
item-text="name"
item-value="value"
:label="$t('settings.first-day-of-week')"
@change="groupActions.updatePreferences()"
/>
</section>
<section v-if="group">
<BaseCardSectionTitle class="mt-10" title="Default Recipe Preferences">
These are the default settings when a new recipe is created in your group. These can be changed for indivdual
recipes in the recipe settings menu.
</BaseCardSectionTitle>
<v-checkbox
v-model="group.preferences.recipePublic"
class="mt-n4"
label="Allow users outside of your group to see your recipes"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeShowNutrition"
class="mt-n4"
label="Show nutrition information"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeShowAssets"
class="mt-n4"
label="Show recipe assets"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeLandscapeView"
class="mt-n4"
label="Default to landscape view"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeDisableComments"
class="mt-n4"
label="Allow recipe comments from users in your group"
@change="groupActions.updatePreferences()"
></v-checkbox>
<v-checkbox
v-model="group.preferences.recipeDisableAmount"
class="mt-n4"
label="Enable organizing recipe ingredients by units and food"
@change="groupActions.updatePreferences()"
></v-checkbox>
</section>
</v-container>
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { useGroup } from "~/composables/use-groups";
import { defineComponent, useContext } from "@nuxtjs/composition-api";
import { useGroupCategories, useGroupSelf } from "~/composables/use-groups";
export default defineComponent({
setup() {
const { categories, actions } = useGroup();
const { categories, actions } = useGroupCategories();
const { group, actions: groupActions } = useGroupSelf();
const { i18n } = useContext();
const allDays = [
{
name: i18n.t("general.sunday"),
value: 0,
},
{
name: i18n.t("general.monday"),
value: 1,
},
{
name: i18n.t("general.tuesday"),
value: 2,
},
{
name: i18n.t("general.wednesday"),
value: 3,
},
{
name: i18n.t("general.thursday"),
value: 4,
},
{
name: i18n.t("general.friday"),
value: 5,
},
{
name: i18n.t("general.saturday"),
value: 6,
},
];
return {
categories,
actions,
group,
groupActions,
allDays,
};
},
});
</script>

View file

@ -82,6 +82,18 @@
</template>
</ToggleState>
</section>
<section>
<BaseCardSectionTitle class="mt-10" title="Preferences"> </BaseCardSectionTitle>
<v-checkbox
v-model="userCopy.advanced"
class="mt-n4"
label="Show advanced features (API Keys, Webhooks, and Data Management)"
@change="updateUser"
></v-checkbox>
<div class="d-flex justify-center mt-5">
<v-btn outlined class="rounded-xl" to="/user/group"> Looking for Privacy Settings? </v-btn>
</div>
</section>
</v-container>
</template>

View file

@ -27,6 +27,7 @@
</v-col>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
v-if="user.advanced"
:link="{ text: 'Manage Your API Tokens', to: '/user/profile/api-tokens' }"
:image="require('~/static/svgs/manage-api-tokens.svg')"
>
@ -63,6 +64,7 @@
</v-col>
<v-col cols="12" sm="12" md="6">
<UserProfileLinkCard
v-if="user.advanced"
:link="{ text: 'Manage Webhooks', to: '/user/group/webhooks' }"
:image="require('~/static/svgs/manage-webhooks.svg')"
>

View file

@ -16,7 +16,7 @@
"~/*": ["./*"],
"@/*": ["./*"]
},
"types": ["@nuxt/types", "@nuxtjs/axios", "@types/node", "nuxt-i18n", "@nuxtjs/auth-next"]
"types": ["@nuxt/types", "@nuxtjs/axios", "@types/node", "@nuxtjs/i18n", "@nuxtjs/auth-next"]
},
"exclude": ["node_modules", ".nuxt", "dist"]
}

View file

@ -1,14 +1,17 @@
import Vue from "vue";
import "@nuxt/types";
declare module "vue/types/vue" {
interface Vue {
$globals: any;
}
interface Vue {
$globals: any;
}
}
declare module "vue/types/options" {
interface ComponentOptions<V extends Vue> {
$globals?: any;
}
interface ComponentOptions<V extends Vue> {
$globals?: any;
}
interface ComponentOptions<V extends UseContextReturn> {
$globals?: any;
}
}

File diff suppressed because it is too large Load diff