Add unfinished website section
This commit is contained in:
parent
d081be838a
commit
3d6fa86518
@ -25,6 +25,7 @@ services:
|
||||
environment:
|
||||
- "DATABASE_URI=postgres://postgres:postgres@db:5432/postgres"
|
||||
- "CLIENT_ID"
|
||||
- "INVITE_CLIENT_ID_OVERRIDE"
|
||||
- "CLIENT_SECRET"
|
||||
- "REDIRECT_URI"
|
||||
db:
|
||||
|
@ -43,6 +43,17 @@ async def auth_middleware(request, handler):
|
||||
request["system"] = system
|
||||
return await handler(request)
|
||||
|
||||
@web.middleware
|
||||
async def cors_middleware(request, handler):
|
||||
try:
|
||||
resp = await handler(request)
|
||||
except web.HTTPException as r:
|
||||
resp = r
|
||||
resp.headers["Access-Control-Allow-Origin"] = "*"
|
||||
resp.headers["Access-Control-Allow-Methods"] = "GET, POST, PATCH"
|
||||
resp.headers["Access-Control-Allow-Headers"] = "X-Token"
|
||||
return resp
|
||||
|
||||
class Handlers:
|
||||
@require_system
|
||||
async def get_system(request):
|
||||
@ -52,14 +63,14 @@ class Handlers:
|
||||
system_id = request.match_info.get("system")
|
||||
system = await System.get_by_hid(request["conn"], system_id)
|
||||
if not system:
|
||||
raise web.HTTPNotFound()
|
||||
raise web.HTTPNotFound(body="null")
|
||||
return web.json_response(system.to_json())
|
||||
|
||||
async def get_system_members(request):
|
||||
system_id = request.match_info.get("system")
|
||||
system = await System.get_by_hid(request["conn"], system_id)
|
||||
if not system:
|
||||
raise web.HTTPNotFound()
|
||||
raise web.HTTPNotFound(body="null")
|
||||
|
||||
members = await system.get_members(request["conn"])
|
||||
return web.json_response([m.to_json() for m in members])
|
||||
@ -68,7 +79,7 @@ class Handlers:
|
||||
system_id = request.match_info.get("system")
|
||||
system = await System.get_by_hid(request["conn"], system_id)
|
||||
if not system:
|
||||
raise web.HTTPNotFound()
|
||||
raise web.HTTPNotFound(body="null")
|
||||
|
||||
switches = await system.get_switches(request["conn"], 9999)
|
||||
|
||||
@ -85,17 +96,17 @@ class Handlers:
|
||||
system = await System.get_by_hid(request["conn"], system_id)
|
||||
|
||||
if not system:
|
||||
raise web.HTTPNotFound()
|
||||
raise web.HTTPNotFound(body="null")
|
||||
|
||||
members, stamp = await utils.get_fronters(request["conn"], system.id)
|
||||
if not stamp:
|
||||
# No switch has been registered at all
|
||||
raise web.HTTPNotFound()
|
||||
raise web.HTTPNotFound(body="null")
|
||||
|
||||
data = {
|
||||
"timestamp": stamp.isoformat(),
|
||||
"members": [member.to_json() for member in members]
|
||||
}
|
||||
}
|
||||
return web.json_response(data)
|
||||
|
||||
@require_system
|
||||
@ -117,8 +128,11 @@ class Handlers:
|
||||
member_id = request.match_info.get("member")
|
||||
member = await Member.get_member_by_hid(request["conn"], None, member_id)
|
||||
if not member:
|
||||
raise web.HTTPNotFound()
|
||||
return web.json_response(member.to_json())
|
||||
raise web.HTTPNotFound(body="{}")
|
||||
system = await System.get_by_id(request["conn"], member.system)
|
||||
member_json = member.to_json()
|
||||
member_json["system"] = system.to_json()
|
||||
return web.json_response(member_json)
|
||||
|
||||
@require_system
|
||||
async def post_member(request):
|
||||
@ -213,8 +227,9 @@ class Handlers:
|
||||
return web.Response(text=await system.get_token(request["conn"]))
|
||||
|
||||
async def run():
|
||||
app = web.Application(middlewares=[db_middleware, auth_middleware, error_middleware])
|
||||
|
||||
app = web.Application(middlewares=[cors_middleware, db_middleware, auth_middleware, error_middleware])
|
||||
def cors_fallback(req):
|
||||
return web.Response(headers={"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "x-token", "Access-Control-Allow-Methods": "GET, POST, PATCH"}, status=404 if req.method != "OPTIONS" else 200)
|
||||
app.add_routes([
|
||||
web.get("/s", Handlers.get_system),
|
||||
web.post("/s/switches", Handlers.post_switch),
|
||||
@ -227,7 +242,8 @@ async def run():
|
||||
web.post("/m", Handlers.post_member),
|
||||
web.patch("/m/{member}", Handlers.patch_member),
|
||||
web.delete("/m/{member}", Handlers.delete_member),
|
||||
web.post("/discord_oauth", Handlers.discord_oauth)
|
||||
web.post("/discord_oauth", Handlers.discord_oauth),
|
||||
web.route("*", "/{tail:.*}", cors_fallback)
|
||||
])
|
||||
app["pool"] = await db.connect(
|
||||
os.environ["DATABASE_URI"]
|
||||
|
14
web/.babelrc
Normal file
14
web/.babelrc
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": [
|
||||
"last 2 Chrome versions"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
3
web/.gitignore
vendored
Normal file
3
web/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.cache/
|
||||
dist/
|
||||
node_modules/
|
63
web/app/API.js
Normal file
63
web/app/API.js
Normal file
@ -0,0 +1,63 @@
|
||||
import { EventEmitter } from "eventemitter3"
|
||||
|
||||
const SITE_ROOT = process.env.NODE_ENV === "production" ? "https://pluralkit.me" : "http://localhost:1234";
|
||||
const API_ROOT = process.env.NODE_ENV === "production" ? "https://api.pluralkit.me" : "http://localhost:2939";
|
||||
const CLIENT_ID = process.env.NODE_ENV === "production" ? "466378653216014359" : "467772037541134367";
|
||||
export const AUTH_URI = `https://discordapp.com/api/oauth2/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(SITE_ROOT + "/auth/discord")}&response_type=code&scope=identify`
|
||||
|
||||
|
||||
class API extends EventEmitter {
|
||||
async init() {
|
||||
this.token = localStorage.getItem("pk-token");
|
||||
if (this.token) {
|
||||
this.me = await fetch(API_ROOT + "/s", {headers: {"X-Token": this.token}}).then(r => r.json());
|
||||
this.emit("update", this.me);
|
||||
}
|
||||
}
|
||||
|
||||
async fetchSystem(id) {
|
||||
return await fetch(API_ROOT + "/s/" + id).then(r => r.json()) || null;
|
||||
}
|
||||
|
||||
async fetchSystemMembers(id) {
|
||||
return await fetch(API_ROOT + "/s/" + id + "/members").then(r => r.json()) || [];
|
||||
}
|
||||
|
||||
async fetchSystemSwitches(id) {
|
||||
return await fetch(API_ROOT + "/s/" + id + "/switches").then(r => r.json()) || [];
|
||||
}
|
||||
|
||||
async fetchMember(id) {
|
||||
return await fetch(API_ROOT + "/m/" + id).then(r => r.json()) || null;
|
||||
}
|
||||
|
||||
async saveSystem(system) {
|
||||
return await fetch(API_ROOT + "/s", {
|
||||
method: "PATCH",
|
||||
headers: {"X-Token": this.token},
|
||||
body: JSON.stringify(system)
|
||||
});
|
||||
}
|
||||
|
||||
async login(code) {
|
||||
this.token = await fetch(API_ROOT + "/discord_oauth", {method: "POST", body: code}).then(r => r.text());
|
||||
this.me = await fetch(API_ROOT + "/s", {headers: {"X-Token": this.token}}).then(r => r.json());
|
||||
|
||||
if (this.me) {
|
||||
localStorage.setItem("pk-token", this.token);
|
||||
this.emit("update", this.me);
|
||||
} else {
|
||||
this.logout();
|
||||
}
|
||||
return this.me;
|
||||
}
|
||||
|
||||
logout() {
|
||||
localStorage.removeItem("pk-token");
|
||||
this.emit("update", null);
|
||||
this.token = null;
|
||||
this.me = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default new API();
|
61
web/app/App.vue
Normal file
61
web/app/App.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="app">
|
||||
<b-navbar>
|
||||
<b-navbar-brand :to="{name: 'home'}">PluralKit</b-navbar-brand>
|
||||
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
||||
<b-collapse id="nav-collapse" is-nav>
|
||||
<b-navbar-nav class="ml-auto">
|
||||
<b-nav-item v-if="me" :to="{name: 'system', params: {id: me.id}}">My system</b-nav-item>
|
||||
<b-nav-item variant="primary" :href="authUri" v-if="!me">Log in</b-nav-item>
|
||||
<b-nav-item v-on:click="logout" v-if="me">Log out</b-nav-item>
|
||||
</b-navbar-nav>
|
||||
</b-collapse>
|
||||
</b-navbar>
|
||||
|
||||
<router-view :me="me"></router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import API from "./API";
|
||||
import { AUTH_URI } from "./API";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
me: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
API.on("update", this.apply);
|
||||
API.init();
|
||||
},
|
||||
methods: {
|
||||
apply(system) {
|
||||
this.me = system;
|
||||
},
|
||||
logout() {
|
||||
API.logout();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
authUri() {
|
||||
return AUTH_URI;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$font-family-sans-serif: "PT Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default;
|
||||
$container-max-widths: (
|
||||
sm: 540px,
|
||||
md: 720px,
|
||||
lg: 959px,
|
||||
xl: 960px,
|
||||
) !default;
|
||||
|
||||
@import '~bootstrap/scss/bootstrap.scss';
|
||||
@import '~bootstrap-vue/src/index.scss';
|
||||
</style>
|
||||
|
9
web/app/HomePage.vue
Normal file
9
web/app/HomePage.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<h1>Hello</h1>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
64
web/app/MemberCard.vue
Normal file
64
web/app/MemberCard.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="member-card">
|
||||
<div
|
||||
class="member-avatar"
|
||||
:style="{backgroundImage: `url(${member.avatar_url})`, borderColor: member.color}"
|
||||
></div>
|
||||
<div class="member-body">
|
||||
<span class="member-name">{{ member.name }}</span>
|
||||
<div class="member-description">{{ member.description }}</div>
|
||||
<ul class="taglist">
|
||||
<li>
|
||||
<hash-icon></hash-icon>
|
||||
{{ member.id }}
|
||||
</li>
|
||||
<li v-if="member.birthday">
|
||||
<calendar-icon></calendar-icon>
|
||||
{{ member.birthday }}
|
||||
</li>
|
||||
<li v-if="member.pronouns">
|
||||
<message-circle-icon></message-circle-icon>
|
||||
{{ member.pronouns }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { CalendarIcon, HashIcon, MessageCircleIcon } from "vue-feather-icons";
|
||||
|
||||
export default {
|
||||
props: ["member"],
|
||||
components: { HashIcon, CalendarIcon, MessageCircleIcon }
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.member-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.member-avatar {
|
||||
margin: 1.5rem 1rem 0 0;
|
||||
border-radius: 50%;
|
||||
background-size: cover;
|
||||
background-position: top center;
|
||||
flex-basis: 4rem;
|
||||
height: 4rem;
|
||||
border: 4px solid white;
|
||||
}
|
||||
|
||||
.member-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem 1rem 1rem 0;
|
||||
|
||||
.member-name {
|
||||
font-size: 13pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
93
web/app/MemberEditPage.vue
Normal file
93
web/app/MemberEditPage.vue
Normal file
@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<b-container v-if="loading" class="d-flex justify-content-center">
|
||||
<b-spinner class="m-5"></b-spinner>
|
||||
</b-container>
|
||||
<b-container v-else-if="error">Error</b-container>
|
||||
<b-container v-else>
|
||||
<h1>Editing "{{member.name}}"</h1>
|
||||
|
||||
<b-form>
|
||||
<b-form-group label="Name">
|
||||
<b-form-input v-model="member.name" required></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group label="Description">
|
||||
<b-form-textarea v-model="member.description" rows="3" max-rows="6"></b-form-textarea>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group label="Proxy tags">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-input-group prepend="Prefix">
|
||||
<b-form-input class="text-right" v-model="member.prefix" placeholder="ex: ["></b-form-input>
|
||||
</b-input-group>
|
||||
</b-col>
|
||||
<b-col>
|
||||
<b-input-group append="Suffix">
|
||||
<b-form-input v-model="member.suffix" placeholder="ex: ]"></b-form-input>
|
||||
</b-input-group>
|
||||
</b-col>
|
||||
<b-col></b-col>
|
||||
</b-row>
|
||||
<template
|
||||
v-slot:description
|
||||
v-if="member.prefix || member.suffix"
|
||||
>Example proxy message: {{member.prefix}}text{{member.suffix}}</template>
|
||||
<template v-slot:description v-else>(no prefix or suffix defined, proxying will be disabled)</template>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group label="Pronouns" description="Free text field - put anything you'd like :)">
|
||||
<b-form-input v-model="member.pronouns" placeholder="eg. he/him"></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-row>
|
||||
<b-col md>
|
||||
<b-form-group label="Birthday">
|
||||
<b-input-group>
|
||||
<b-input-group-prepend is-text>
|
||||
<input type="checkbox" v-model="hideBirthday" label="uwu"> Hide year
|
||||
</b-input-group-prepend>
|
||||
<b-form-input v-model="member.birthday" type="date"></b-form-input>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
<b-col md>
|
||||
<b-form-group label="Color" description="Will be displayed on system profile cards.">
|
||||
<b-form-input type="color" v-model="member.color"></b-form-input>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import API from "./API";
|
||||
|
||||
export default {
|
||||
props: ["id"],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
error: false,
|
||||
hideBirthday: false,
|
||||
member: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
async fetch() {
|
||||
this.loading = true;
|
||||
this.error = false;
|
||||
this.member = await API.fetchMember(this.id);
|
||||
if (!this.member) this.error = true;
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
20
web/app/OAuthRedirectPage.vue
Normal file
20
web/app/OAuthRedirectPage.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<b-container class="d-flex justify-content-center"><span class="sr-only">Loading...</span><b-spinner class="m-5"></b-spinner></b-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import API from "./API";
|
||||
|
||||
export default {
|
||||
async created() {
|
||||
const code = this.$route.query.code;
|
||||
if (!code) this.$router.push({ name: "home" });
|
||||
const me = await API.login(code);
|
||||
if (me) this.$router.push({ name: "home" });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
73
web/app/SystemEditPage.vue
Normal file
73
web/app/SystemEditPage.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<b-container>
|
||||
<b-container v-if="loading" class="d-flex justify-content-center"><b-spinner class="m-5"></b-spinner></b-container>
|
||||
<b-form v-else>
|
||||
<h1>Editing "{{ system.name || system.id }}"</h1>
|
||||
<b-form-group label="System name">
|
||||
<b-form-input v-model="system.name" placeholder="Enter something..."></b-form-input>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group label="Description">
|
||||
<b-form-textarea v-model="system.description" placeholder="Enter something..." rows="3" max-rows="3" maxlength="1000"></b-form-textarea>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group label="System tag">
|
||||
<b-form-input maxlength="30" v-model="system.tag" placeholder="Enter something..."></b-form-input>
|
||||
<template v-slot:description>
|
||||
This is added to the names of proxied accounts. For example: <code>John {{ system.tag }}</code>
|
||||
</template>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group class="d-flex justify-content-end">
|
||||
<b-button type="reset" variant="outline-secondary">Back</b-button>
|
||||
<b-button v-if="!saving" type="submit" variant="primary" v-on:click="save">Save</b-button>
|
||||
<b-button v-else variant="primary" disabled>
|
||||
<b-spinner small></b-spinner>
|
||||
<span class="sr-only">Saving...</span>
|
||||
</b-button>
|
||||
<b-form-group>
|
||||
</b-form>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import API from "./API";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saving: false,
|
||||
system: null
|
||||
}
|
||||
},
|
||||
props: ["me", "id"],
|
||||
created() {
|
||||
this.fetch()
|
||||
},
|
||||
watch: {
|
||||
"id": "fetch"
|
||||
},
|
||||
methods: {
|
||||
async fetch() {
|
||||
this.loading = true;
|
||||
this.system = await API.fetchSystem(this.id);
|
||||
if (!this.me || !this.system || this.system.id != this.me.id) {
|
||||
this.$router.push({name: "system", params: {id: this.id}});
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
async save() {
|
||||
this.saving = true;
|
||||
if (await API.saveSystem(this.system)) {
|
||||
this.$router.push({ name: "system", params: {id: this.system.id} });
|
||||
}
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
105
web/app/SystemPage.vue
Normal file
105
web/app/SystemPage.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<b-container v-if="loading" class="d-flex justify-content-center"><b-spinner class="m-5"></b-spinner></b-container>
|
||||
<b-container v-else-if="error">An error occurred.</b-container>
|
||||
<b-container v-else>
|
||||
<ul v-if="system" class="taglist">
|
||||
<li>
|
||||
<hash-icon></hash-icon>
|
||||
{{ system.id }}
|
||||
</li>
|
||||
<li v-if="system.tag">
|
||||
<tag-icon></tag-icon>
|
||||
{{ system.tag }}
|
||||
</li>
|
||||
<li v-if="system.tz">
|
||||
<clock-icon></clock-icon>
|
||||
{{ system.tz }}
|
||||
</li>
|
||||
<li v-if="isMine" class="ml-auto">
|
||||
<b-link :to="{name: 'edit-system', params: {id: system.id}}">
|
||||
<edit-2-icon></edit-2-icon>
|
||||
Edit
|
||||
</b-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h1 v-if="system && system.name">{{ system.name }}</h1>
|
||||
<div v-if="system && system.description">{{ system.description }}</div>
|
||||
|
||||
<h2>Members</h2>
|
||||
<div v-if="members">
|
||||
<MemberCard v-for="member in members" :member="member" :key="member.id"/>
|
||||
</div>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import API from "./API";
|
||||
|
||||
import MemberCard from "./MemberCard.vue";
|
||||
|
||||
import { Edit2Icon, ClockIcon, HashIcon, TagIcon } from "vue-feather-icons";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
error: false,
|
||||
system: null,
|
||||
members: null
|
||||
};
|
||||
},
|
||||
props: ["me", "id"],
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
methods: {
|
||||
async fetch() {
|
||||
this.loading = true;
|
||||
this.system = await API.fetchSystem(this.id);
|
||||
if (!this.system) {
|
||||
this.error = true;
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
this.members = await API.fetchSystemMembers(this.id);
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
id: "fetch"
|
||||
},
|
||||
computed: {
|
||||
isMine() {
|
||||
return this.system && this.me && this.me.id == this.system.id;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Edit2Icon,
|
||||
ClockIcon,
|
||||
HashIcon,
|
||||
TagIcon,
|
||||
MemberCard
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.taglist {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #aaa;
|
||||
display: flex;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
list-style-type: none;
|
||||
.feather {
|
||||
display: inline-block;
|
||||
margin-top: -2px;
|
||||
width: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
24
web/app/index.js
Normal file
24
web/app/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import BootstrapVue from "bootstrap-vue";
|
||||
Vue.use(VueRouter);
|
||||
Vue.use(BootstrapVue);
|
||||
|
||||
import App from "./App.vue";
|
||||
import HomePage from "./HomePage.vue";
|
||||
import SystemPage from "./SystemPage.vue";
|
||||
import SystemEditPage from "./SystemEditPage.vue";
|
||||
import MemberEditPage from "./MemberEditPage.vue";
|
||||
import OAuthRedirectPage from "./OAuthRedirectPage.vue";
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: "history",
|
||||
routes: [
|
||||
{ name: "home", path: "/", component: HomePage },
|
||||
{ name: "system", path: "/s/:id", component: SystemPage, props: true },
|
||||
{ name: "edit-system", path: "/s/:id/edit", component: SystemEditPage, props: true },
|
||||
{ name: "edit-member", path: "/m/:id/edit", component: MemberEditPage, props: true },
|
||||
{ name: "auth-discord", path: "/auth/discord", component: OAuthRedirectPage }
|
||||
]
|
||||
})
|
||||
new Vue({ el: "#app", render: r => r(App), router });
|
9
web/index.html
Normal file
9
web/index.html
Normal file
@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=PT+Sans:400,700" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="app/index.js"></script>
|
||||
</body>
|
||||
</html>
|
20
web/package.json
Normal file
20
web/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.3.1",
|
||||
"bootstrap-vue": "^2.0.0-rc.16",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-feather-icons": "^4.10.0",
|
||||
"vue-hot-reload-api": "^2.3.3",
|
||||
"vue-router": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/component-compiler-utils": "^2.6.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"parcel-plugin-bundle-visualiser": "^1.2.0",
|
||||
"sass": "^1.17.3",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
}
|
||||
}
|
3296
web/yarn.lock
Normal file
3296
web/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user