Implement modifying app info
This commit is contained in:
parent
52e9fee664
commit
9ff7c35fe9
15 changed files with 360 additions and 11 deletions
|
@ -31,3 +31,11 @@ export class InvalidSymbolError extends ApolloError {
|
||||||
super('Invalid symbol', 'INVALID_SYMBOL');
|
super('Invalid symbol', 'INVALID_SYMBOL');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ApplicationNotFoundError extends ApolloError {
|
||||||
|
public name = 'ApplicationNotFoundError';
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
super('Application not found', 'APPLICATION_NOT_FOUND_ERROR');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
/* eslint-disable class-methods-use-this */
|
/* eslint-disable class-methods-use-this */
|
||||||
|
import { URL } from 'url';
|
||||||
import { UserInputError } from 'apollo-server-fastify';
|
import { UserInputError } from 'apollo-server-fastify';
|
||||||
import {
|
import {
|
||||||
Arg, Ctx, Mutation, Query, Resolver, UnauthorizedError,
|
Arg, Ctx, Mutation, Query, Resolver, UnauthorizedError,
|
||||||
} from 'type-graphql';
|
} from 'type-graphql';
|
||||||
import ApplicationEntity from '../../../../database/entities/application';
|
import ApplicationEntity from '../../../../database/entities/application';
|
||||||
|
import { ApplicationNotFoundError } from '../../errors';
|
||||||
import Application from '../../models/application';
|
import Application from '../../models/application';
|
||||||
import type { WebsiteAPIContext } from '../../types';
|
import type { WebsiteAPIContext } from '../../types';
|
||||||
|
|
||||||
|
@ -57,4 +59,39 @@ export default class ApplicationResolver {
|
||||||
if (!application.developerId.equals(sessionData.loginState.developerId)) throw new UnauthorizedError();
|
if (!application.developerId.equals(sessionData.loginState.developerId)) throw new UnauthorizedError();
|
||||||
return Application.fromEntity(application);
|
return Application.fromEntity(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Application)
|
||||||
|
public async modifyApplication(
|
||||||
|
@Arg('id') id: string,
|
||||||
|
@Arg('name') name: string,
|
||||||
|
@Arg('homepage', () => String, {
|
||||||
|
nullable: true,
|
||||||
|
}) homepage: string | null,
|
||||||
|
@Ctx() { sessionData }: WebsiteAPIContext,
|
||||||
|
): Promise<Application> {
|
||||||
|
if (!sessionData.loginState) throw new UnauthorizedError();
|
||||||
|
if (name !== name.trim()) throw new UserInputError('Name should be trimmed');
|
||||||
|
if (name.trim().length < 3) throw new UserInputError('Name is too short');
|
||||||
|
if (name.trim().length > 32) throw new UserInputError('Name is too long');
|
||||||
|
if (homepage) {
|
||||||
|
if (homepage.trim() === '') throw new UserInputError('Homepage should not be an empty string. Use null instead');
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(homepage.trim());
|
||||||
|
} catch {
|
||||||
|
throw new UserInputError('Homepage URL is invalid');
|
||||||
|
}
|
||||||
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') throw new UserInputError('Invalid homepage URL protocol');
|
||||||
|
}
|
||||||
|
|
||||||
|
const application = await ApplicationEntity.findOne(id);
|
||||||
|
if (!application) throw new ApplicationNotFoundError();
|
||||||
|
if (!application.developerId.equals(sessionData.loginState.developerId)) throw new UnauthorizedError();
|
||||||
|
if (application.homepage === homepage && application.name === name) return Application.fromEntity(application);
|
||||||
|
application.name = name;
|
||||||
|
application.homepage = homepage;
|
||||||
|
application.verified = false;
|
||||||
|
await application.save();
|
||||||
|
return Application.fromEntity(application);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ import {
|
||||||
import { InputValidationRules } from 'vuetify';
|
import { InputValidationRules } from 'vuetify';
|
||||||
import IsEmail from 'isemail';
|
import IsEmail from 'isemail';
|
||||||
import { PromptInfo, VForm } from '@/types';
|
import { PromptInfo, VForm } from '@/types';
|
||||||
import { sdk } from '@/pages/authenticate-prompt/sdk';
|
import { sdk } from '@/graphql/sdk';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'EmailWindow',
|
name: 'EmailWindow',
|
||||||
|
|
|
@ -54,7 +54,7 @@ import {
|
||||||
Component, Prop, Ref, Vue,
|
Component, Prop, Ref, Vue,
|
||||||
} from 'vue-property-decorator';
|
} from 'vue-property-decorator';
|
||||||
import { PromptInfo, VForm } from '@/types';
|
import { PromptInfo, VForm } from '@/types';
|
||||||
import { hasErrorCode, sdk } from '@/pages/authenticate-prompt/sdk';
|
import { hasErrorCode, sdk } from '@/graphql/sdk';
|
||||||
import { InputValidationRules } from 'vuetify';
|
import { InputValidationRules } from 'vuetify';
|
||||||
import VueRecaptcha from 'vue-recaptcha';
|
import VueRecaptcha from 'vue-recaptcha';
|
||||||
import { requireEnv } from '@/utils';
|
import { requireEnv } from '@/utils';
|
||||||
|
|
|
@ -74,7 +74,7 @@ import {
|
||||||
Component, Prop, Ref, Vue,
|
Component, Prop, Ref, Vue,
|
||||||
} from 'vue-property-decorator';
|
} from 'vue-property-decorator';
|
||||||
import { PromptInfo, VForm } from '@/types';
|
import { PromptInfo, VForm } from '@/types';
|
||||||
import { hasErrorCode, sdk } from '@/pages/authenticate-prompt/sdk';
|
import { hasErrorCode, sdk } from '@/graphql/sdk';
|
||||||
|
|
||||||
// This is a JS Symbol, not a diary symbol
|
// This is a JS Symbol, not a diary symbol
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue, Watch } from 'vue-property-decorator';
|
import { Component, Vue, Watch } from 'vue-property-decorator';
|
||||||
import { sdk } from '@/pages/authenticate-prompt/sdk';
|
import { sdk } from '@/graphql/sdk';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'NewAppDialog',
|
name: 'NewAppDialog',
|
||||||
|
|
|
@ -68,6 +68,8 @@ export type Application = {
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
iconUrl: Maybe<Scalars['String']>;
|
iconUrl: Maybe<Scalars['String']>;
|
||||||
iconColor: Scalars['String'];
|
iconColor: Scalars['String'];
|
||||||
|
homepage: Maybe<Scalars['String']>;
|
||||||
|
verified: Scalars['Boolean'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LoginState = {
|
export type LoginState = {
|
||||||
|
@ -83,6 +85,7 @@ export type Mutation = {
|
||||||
login: LoginResult;
|
login: LoginResult;
|
||||||
setSymbol: SetSymbolResult;
|
setSymbol: SetSymbolResult;
|
||||||
createApplication: Application;
|
createApplication: Application;
|
||||||
|
modifyApplication: Application;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MutationCreateUserArgs = {
|
export type MutationCreateUserArgs = {
|
||||||
|
@ -107,6 +110,12 @@ export type MutationCreateApplicationArgs = {
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type MutationModifyApplicationArgs = {
|
||||||
|
homepage: Maybe<Scalars['String']>;
|
||||||
|
name: Scalars['String'];
|
||||||
|
id: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateUserResult = {
|
export type CreateUserResult = {
|
||||||
__typename?: 'CreateUserResult';
|
__typename?: 'CreateUserResult';
|
||||||
success: Scalars['Boolean'];
|
success: Scalars['Boolean'];
|
||||||
|
@ -170,6 +179,20 @@ export type LoginMutation = (
|
||||||
); }
|
); }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type ModifyApplicationMutationVariables = Exact<{
|
||||||
|
id: Scalars['String'];
|
||||||
|
name: Scalars['String'];
|
||||||
|
homepage: Maybe<Scalars['String']>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ModifyApplicationMutation = (
|
||||||
|
{ __typename?: 'Mutation' }
|
||||||
|
& { modifyApplication: (
|
||||||
|
{ __typename?: 'Application' }
|
||||||
|
& Pick<Application, 'name' | 'iconUrl' | 'iconColor' | 'homepage' | 'verified'>
|
||||||
|
); }
|
||||||
|
);
|
||||||
|
|
||||||
export type SetSymbolMutationVariables = Exact<{
|
export type SetSymbolMutationVariables = Exact<{
|
||||||
promptId: Scalars['String'];
|
promptId: Scalars['String'];
|
||||||
symbol: Scalars['String'];
|
symbol: Scalars['String'];
|
||||||
|
@ -187,6 +210,18 @@ export type SetSymbolMutation = (
|
||||||
); }
|
); }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type GetApplicationQueryVariables = Exact<{
|
||||||
|
id: Scalars['String'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type GetApplicationQuery = (
|
||||||
|
{ __typename?: 'Query' }
|
||||||
|
& { application: Maybe<(
|
||||||
|
{ __typename?: 'Application' }
|
||||||
|
& Pick<Application, 'name' | 'iconUrl' | 'iconColor' | 'homepage' | 'verified'>
|
||||||
|
)>; }
|
||||||
|
);
|
||||||
|
|
||||||
export type GetApplicationsQueryVariables = Exact<{ [key: string]: never }>;
|
export type GetApplicationsQueryVariables = Exact<{ [key: string]: never }>;
|
||||||
|
|
||||||
export type GetApplicationsQuery = (
|
export type GetApplicationsQuery = (
|
||||||
|
@ -254,6 +289,17 @@ export const LoginDocument = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const ModifyApplicationDocument = gql`
|
||||||
|
mutation ModifyApplication($id: String!, $name: String!, $homepage: String) {
|
||||||
|
modifyApplication(id: $id, name: $name, homepage: $homepage) {
|
||||||
|
name
|
||||||
|
iconUrl
|
||||||
|
iconColor
|
||||||
|
homepage
|
||||||
|
verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const SetSymbolDocument = gql`
|
export const SetSymbolDocument = gql`
|
||||||
mutation SetSymbol($promptId: String!, $symbol: String!) {
|
mutation SetSymbol($promptId: String!, $symbol: String!) {
|
||||||
setSymbol(promptId: $promptId, symbol: $symbol) {
|
setSymbol(promptId: $promptId, symbol: $symbol) {
|
||||||
|
@ -265,6 +311,17 @@ export const SetSymbolDocument = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const GetApplicationDocument = gql`
|
||||||
|
query GetApplication($id: String!) {
|
||||||
|
application(id: $id) {
|
||||||
|
name
|
||||||
|
iconUrl
|
||||||
|
iconColor
|
||||||
|
homepage
|
||||||
|
verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const GetApplicationsDocument = gql`
|
export const GetApplicationsDocument = gql`
|
||||||
query GetApplications {
|
query GetApplications {
|
||||||
applications {
|
applications {
|
||||||
|
@ -320,9 +377,15 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
|
||||||
Login(variables: LoginMutationVariables, requestHeaders?: Headers): Promise<LoginMutation> {
|
Login(variables: LoginMutationVariables, requestHeaders?: Headers): Promise<LoginMutation> {
|
||||||
return withWrapper(() => client.request<LoginMutation>(print(LoginDocument), variables, requestHeaders));
|
return withWrapper(() => client.request<LoginMutation>(print(LoginDocument), variables, requestHeaders));
|
||||||
},
|
},
|
||||||
|
ModifyApplication(variables: ModifyApplicationMutationVariables, requestHeaders?: Headers): Promise<ModifyApplicationMutation> {
|
||||||
|
return withWrapper(() => client.request<ModifyApplicationMutation>(print(ModifyApplicationDocument), variables, requestHeaders));
|
||||||
|
},
|
||||||
SetSymbol(variables: SetSymbolMutationVariables, requestHeaders?: Headers): Promise<SetSymbolMutation> {
|
SetSymbol(variables: SetSymbolMutationVariables, requestHeaders?: Headers): Promise<SetSymbolMutation> {
|
||||||
return withWrapper(() => client.request<SetSymbolMutation>(print(SetSymbolDocument), variables, requestHeaders));
|
return withWrapper(() => client.request<SetSymbolMutation>(print(SetSymbolDocument), variables, requestHeaders));
|
||||||
},
|
},
|
||||||
|
GetApplication(variables: GetApplicationQueryVariables, requestHeaders?: Headers): Promise<GetApplicationQuery> {
|
||||||
|
return withWrapper(() => client.request<GetApplicationQuery>(print(GetApplicationDocument), variables, requestHeaders));
|
||||||
|
},
|
||||||
GetApplications(variables?: GetApplicationsQueryVariables, requestHeaders?: Headers): Promise<GetApplicationsQuery> {
|
GetApplications(variables?: GetApplicationsQueryVariables, requestHeaders?: Headers): Promise<GetApplicationsQuery> {
|
||||||
return withWrapper(() => client.request<GetApplicationsQuery>(print(GetApplicationsDocument), variables, requestHeaders));
|
return withWrapper(() => client.request<GetApplicationsQuery>(print(GetApplicationsDocument), variables, requestHeaders));
|
||||||
},
|
},
|
||||||
|
|
12
website/src/graphql/mutations/modify-application.ts
Normal file
12
website/src/graphql/mutations/modify-application.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export default gql`mutation ModifyApplication($id: String!, $name: String!, $homepage: String) {
|
||||||
|
modifyApplication(id: $id, name: $name, homepage: $homepage) {
|
||||||
|
name
|
||||||
|
iconUrl
|
||||||
|
iconColor
|
||||||
|
homepage
|
||||||
|
verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
11
website/src/graphql/queries/get-application.ts
Normal file
11
website/src/graphql/queries/get-application.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export default gql`query GetApplication($id: String!) {
|
||||||
|
application(id: $id) {
|
||||||
|
name
|
||||||
|
iconUrl
|
||||||
|
iconColor
|
||||||
|
homepage
|
||||||
|
verified
|
||||||
|
}
|
||||||
|
}`;
|
|
@ -110,7 +110,7 @@ import OverviewWindow from '@/compontents/authenticate-prompt-windows/overview-w
|
||||||
import { PromptInfo, Student } from '@/types';
|
import { PromptInfo, Student } from '@/types';
|
||||||
import LoginWindow from '@/compontents/authenticate-prompt-windows/login-window.vue';
|
import LoginWindow from '@/compontents/authenticate-prompt-windows/login-window.vue';
|
||||||
import StudentsWindow from '@/compontents/authenticate-prompt-windows/students-window.vue';
|
import StudentsWindow from '@/compontents/authenticate-prompt-windows/students-window.vue';
|
||||||
import { sdk } from '@/pages/authenticate-prompt/sdk';
|
import { sdk } from '@/graphql/sdk';
|
||||||
import DialogApp from '@/compontents/dialog-app.vue';
|
import DialogApp from '@/compontents/dialog-app.vue';
|
||||||
import EmailWindow from '@/compontents/authenticate-prompt-windows/email-window.vue';
|
import EmailWindow from '@/compontents/authenticate-prompt-windows/email-window.vue';
|
||||||
import SymbolsWindow from '@/compontents/authenticate-prompt-windows/symbols-window.vue';
|
import SymbolsWindow from '@/compontents/authenticate-prompt-windows/symbols-window.vue';
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
</v-menu>
|
</v-menu>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<v-main>
|
<v-main>
|
||||||
<router-view />
|
<router-view :login-state="loginState" />
|
||||||
</v-main>
|
</v-main>
|
||||||
</div>
|
</div>
|
||||||
</v-app>
|
</v-app>
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import DeveloperSignedOut from '@/pages/developer/views/signed-out.vue';
|
import DeveloperSignedOut from '@/pages/developer/views/signed-out.vue';
|
||||||
import { LoginState } from '@/graphql/generated';
|
import { LoginState } from '@/graphql/generated';
|
||||||
import { sdk } from '@/pages/authenticate-prompt/sdk';
|
import { sdk } from '@/graphql/sdk';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'DeveloperApp',
|
name: 'DeveloperApp',
|
||||||
|
|
|
@ -9,6 +9,11 @@ const routes: Array<RouteConfig> = [
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: () => import(/* webpackChunkName: "developer-home" */ './views/home.vue'),
|
component: () => import(/* webpackChunkName: "developer-home" */ './views/home.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/apps/:appId',
|
||||||
|
name: 'Application',
|
||||||
|
component: () => import(/* webpackChunkName: "developer-application" */ './views/application.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
redirect: '/',
|
redirect: '/',
|
||||||
|
|
213
website/src/pages/developer/views/application.vue
Normal file
213
website/src/pages/developer/views/application.vue
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
<template>
|
||||||
|
<v-container class="application-container" v-if="applicationError">
|
||||||
|
<v-alert type="error">
|
||||||
|
Failed to load application info
|
||||||
|
<template #append>
|
||||||
|
<v-btn light @click="loadApplication">Retry</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-alert>
|
||||||
|
</v-container>
|
||||||
|
<v-container class="application-container" v-else-if="application === null">
|
||||||
|
<v-skeleton-loader type="image" class="mb-4" />
|
||||||
|
<v-skeleton-loader type="image" class="mb-4" />
|
||||||
|
</v-container>
|
||||||
|
<v-container class="application-container" v-else>
|
||||||
|
<div class="mb-4 d-flex align-center">
|
||||||
|
<div>
|
||||||
|
<v-badge
|
||||||
|
:color="application.verified ? 'green' : 'grey'"
|
||||||
|
offset-x="48"
|
||||||
|
offset-y="16"
|
||||||
|
bottom
|
||||||
|
:content="application.verified ? 'Verified' : 'Not verified'"
|
||||||
|
>
|
||||||
|
<app-icon
|
||||||
|
large
|
||||||
|
:color="application.iconColor"
|
||||||
|
:url="application.iconUrl"
|
||||||
|
/>
|
||||||
|
</v-badge>
|
||||||
|
</div>
|
||||||
|
<div class="text-h5 ml-4 text-right grow">{{ application.name }}</div>
|
||||||
|
</div>
|
||||||
|
<v-card outlined class="mb-4">
|
||||||
|
<v-form @submit.prevent="modifyApp">
|
||||||
|
<v-card-title>App information</v-card-title>
|
||||||
|
<v-card-text class="pb-0">
|
||||||
|
<v-text-field
|
||||||
|
label="App name"
|
||||||
|
outlined
|
||||||
|
v-model="nameInput"
|
||||||
|
:error-messages="nameError"
|
||||||
|
counter="32"
|
||||||
|
:counter-value="(v) => v.trim().length"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
label="Homepage URL (optional)"
|
||||||
|
outlined
|
||||||
|
v-model="homepageInput"
|
||||||
|
:error-messages="homepageError"
|
||||||
|
/>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="px-4">
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
color="primary"
|
||||||
|
:disabled="!appInfoValid"
|
||||||
|
type="submit"
|
||||||
|
:loading="appModifyLoading"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-form>
|
||||||
|
<v-divider />
|
||||||
|
<v-card-title>Icon</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
Not implemented yet
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions class="px-4">
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn color="primary" outlined disabled>Remove icon</v-btn>
|
||||||
|
<v-btn color="primary" disabled>Upload</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
<v-divider />
|
||||||
|
<v-card-text>
|
||||||
|
<v-alert type="warning" text :value="application.verified">
|
||||||
|
You will lose the verified badge
|
||||||
|
if you change the app information
|
||||||
|
or upload a new icon
|
||||||
|
</v-alert>
|
||||||
|
Users will see your GitHub profile information,
|
||||||
|
including your name <b>({{ loginState.name || 'not set' }})</b>,
|
||||||
|
login <b>({{ loginState.login }})</b>, avatar and profile URL
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
<v-card outlined class="mb-4">
|
||||||
|
<v-card-title>Client IDs</v-card-title>
|
||||||
|
</v-card>
|
||||||
|
<v-card outlined class="mb-4">
|
||||||
|
<v-card-title>Danger zone</v-card-title>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.application-container {
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import AppIcon from '@/pages/app-icon.vue';
|
||||||
|
import { GetApplicationQuery, LoginState } from '@/graphql/generated';
|
||||||
|
import { sdk } from '@/graphql/sdk';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
name: 'DeveloperApplication',
|
||||||
|
components: { AppIcon },
|
||||||
|
})
|
||||||
|
export default class DeveloperApplication extends Vue {
|
||||||
|
@Prop({
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
})
|
||||||
|
loginState!: LoginState
|
||||||
|
|
||||||
|
application: GetApplicationQuery['application'] | null = null;
|
||||||
|
|
||||||
|
applicationError = false;
|
||||||
|
|
||||||
|
nameInput = '';
|
||||||
|
|
||||||
|
homepageInput = '';
|
||||||
|
|
||||||
|
appModifyLoading = false;
|
||||||
|
|
||||||
|
appModifyError = false;
|
||||||
|
|
||||||
|
get id(): string {
|
||||||
|
return this.$route.params.appId;
|
||||||
|
}
|
||||||
|
|
||||||
|
get nameError(): string | null {
|
||||||
|
if (this.nameInput.trim() === '') return 'The name is required';
|
||||||
|
if (this.nameInput.trim().length < 3) return 'Name too short';
|
||||||
|
if (this.nameInput.trim().length > 32) return 'Name too long';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get homepageError(): string | null {
|
||||||
|
if (this.homepageInput.trim() === '') return null;
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(this.homepageInput.trim());
|
||||||
|
} catch {
|
||||||
|
return 'Invalid URL';
|
||||||
|
}
|
||||||
|
if (url.protocol !== 'http:' && url.protocol !== 'https:') return 'Protocol should be http: or https:';
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get appInfoValid(): boolean {
|
||||||
|
if (!this.application) return false;
|
||||||
|
if (
|
||||||
|
this.nameInput.trim() === this.application.name
|
||||||
|
&& this.homepageInput.trim() === (this.application.homepage ?? '')
|
||||||
|
) return false;
|
||||||
|
return this.nameError === null && this.homepageError === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadApplication() {
|
||||||
|
this.application = null;
|
||||||
|
this.applicationError = false;
|
||||||
|
try {
|
||||||
|
const result = await sdk.GetApplication({
|
||||||
|
id: this.id,
|
||||||
|
});
|
||||||
|
if (!result.application) {
|
||||||
|
console.error('Application not found');
|
||||||
|
this.applicationError = true;
|
||||||
|
} else {
|
||||||
|
this.application = result.application;
|
||||||
|
this.updateAppInfoInput();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
this.applicationError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAppInfoInput() {
|
||||||
|
if (!this.application) return;
|
||||||
|
this.nameInput = this.application.name;
|
||||||
|
this.homepageInput = this.application.homepage ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async created() {
|
||||||
|
await this.loadApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
async modifyApp() {
|
||||||
|
if (this.appModifyLoading || !this.appInfoValid) return;
|
||||||
|
this.appModifyLoading = true;
|
||||||
|
this.appModifyError = false;
|
||||||
|
let homepage: string | null = this.homepageInput.trim();
|
||||||
|
if (homepage === '') homepage = null;
|
||||||
|
try {
|
||||||
|
const result = await sdk.ModifyApplication({
|
||||||
|
id: this.id,
|
||||||
|
homepage,
|
||||||
|
name: this.nameInput.trim(),
|
||||||
|
});
|
||||||
|
this.application = result.modifyApplication;
|
||||||
|
this.updateAppInfoInput();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
this.appModifyError = true;
|
||||||
|
}
|
||||||
|
this.appModifyLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.home-container {
|
.home-container {
|
||||||
max-width: 1100px;
|
max-width: 800px;
|
||||||
|
|
||||||
.applications {
|
.applications {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -68,15 +68,15 @@
|
||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import AppIcon from '@/pages/app-icon.vue';
|
import AppIcon from '@/pages/app-icon.vue';
|
||||||
import NewAppDialog from '@/compontents/developer/new-app-dialog.vue';
|
import NewAppDialog from '@/compontents/developer/new-app-dialog.vue';
|
||||||
import { Application } from '@/graphql/generated';
|
import { GetApplicationsQuery } from '@/graphql/generated';
|
||||||
import { sdk } from '@/pages/authenticate-prompt/sdk';
|
import { sdk } from '@/graphql/sdk';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
name: 'DeveloperHome',
|
name: 'DeveloperHome',
|
||||||
components: { NewAppDialog, AppIcon },
|
components: { NewAppDialog, AppIcon },
|
||||||
})
|
})
|
||||||
export default class DeveloperHome extends Vue {
|
export default class DeveloperHome extends Vue {
|
||||||
applications: Application[] | null = null;
|
applications: GetApplicationsQuery['applications'] | null = null;
|
||||||
|
|
||||||
applicationError = false;
|
applicationError = false;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue