Skip to content

Commit

Permalink
Adding workspaces
Browse files Browse the repository at this point in the history
  • Loading branch information
JulianFun123 committed Nov 10, 2024
1 parent 692d305 commit 5988814
Show file tree
Hide file tree
Showing 17 changed files with 489 additions and 85 deletions.
6 changes: 1 addition & 5 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
<template>
<div id="app">
<navigation />
<router-view />
</div>
</template>
<script setup>
import Navigation from "@/components/Navigation.vue";
</script>
</template>
24 changes: 24 additions & 0 deletions src/PunyshortAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,28 @@ export default class PunyshortAPI extends Cajax {
deleteDomain(id, query = {}) {
return this.delete(`/v1/domains/${id}`, query)
}


getWorkspaces(query = {}) {
return this.get("/v1/workspaces", query)
}

createWorkspaces(body = {}) {
return this.post("/v1/workspaces", body)
}

getWorkspace(id) {
return this.get(`/v1/workspaces/${id}`)
}

getWorkspaceDomains(id, params = {}) {
return this.get(`/v1/workspaces/${id}/domains`, params)
}
addWorkspaceDomains(id, domainId) {
return this.post(`/v1/workspaces/${id}/domains`, {domain_id: domainId})
}

removeWorkspaceDomain(id, domainId) {
return this.delete(`/v1/workspaces/${id}/domains/${domainId}`)
}
}
13 changes: 12 additions & 1 deletion src/assets/css/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,17 @@ label {
background: #32a88f;
}

&.btn-outlined {
background: transparent;
color: #111;
border: 1px solid #CCC;

@media (prefers-color-scheme: dark) {
color: #FFF;
border: 1px solid #555;
}
}

&.btn-transparent {
background: transparent;

Expand Down Expand Up @@ -222,4 +233,4 @@ label {
border-color: #666;
}

// @import './grid';
@import './home';
37 changes: 37 additions & 0 deletions src/assets/css/home.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#home {
height: 100%;

#home-top {
background: #F9F9F9;
height: 100vh;
display: grid;
grid-template-rows: auto fit-content(0px) auto;
transition: 0.3s height;
#home-slug {
text-align: center;
margin-bottom: 60px;
font-weight: 400;
b {
font-weight: 600;
}
}
#home-top-contents {
}

&.logged-in {
height: initial;
grid-template-rows: 50px fit-content(60px) 50px;
#home-slug {
display: none;
}
#home-top-contents {
margin-top: 55px;
}
}


@media (prefers-color-scheme: dark) {
background: #111111;
}
}
}
2 changes: 1 addition & 1 deletion src/assets/css/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
--text-color:#495057;
--text-color-secondary:#6c757d;
--border-radius: 6px;
--primary-color:#3B82F6;
--primary-color:#FF5880;
--primary-color-text:#ffffff;
--surface-ground:#eff3f8;
--surface-section:#ffffff;
Expand Down
71 changes: 71 additions & 0 deletions src/components/CreateWorkspaceModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script setup>
import Modal from "@/components/Modal.vue";
import {apiClient} from "@/main";
import {ref, watch, watchEffect} from "vue";
import router from "@/router";
const visible = defineModel('visible')
const resultModalVisible = ref(false)
const slugChanged = ref(false)
const createWorkspace = ref({
name: '',
slug: ''
})
const result = ref(null)
const emit = defineEmits(['created', 'dnsCheckDone'])
watch(visible, () => {
if (visible.value) {
createWorkspace.value = {
name: '',
slug: ''
}
slugChanged.value = false
}
})
watch(() => createWorkspace.value.name, () => {
if (!slugChanged.value) {
createWorkspace.value.slug = createWorkspace.value.name.replaceAll(' ', '-').toLowerCase().replace(/[^0-9a-z\-_]/gi, '')
}
})
const create = async () => {
const workspace = await apiClient.createWorkspaces(createWorkspace.value)
result.value = {workspace}
visible.value = true
resultModalVisible.value = true
await router.push({
name: 'workspace-home',
params: {
workspace: workspace.slug
}
})
}
</script>
<template>
<Modal v-model:visible="visible" title="Add Workspace" @submit="create">
<div class="mb-3">
<label class="mb-1">NAME</label>
<input autofocus v-model="createWorkspace.name" type="text" class="input">
</div>
<div class="mb-4">
<label class="mb-1">SLUG (Only a-z, 1-9, _, - are allowed)</label>
<input @input="slugChanged = true" @change="slugChanged = true" v-model="createWorkspace.slug" type="text" class="input">
</div>


<button type="submit" class="btn right mt-4">Confirm</button>
</Modal>
</template>

<style scoped>
</style>
12 changes: 9 additions & 3 deletions src/components/LinksList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import {apiClient} from "@/main";
import {getFavicon} from "@/helper/helper";
import {copyStringToClipboard} from "@/helper";
import {onMounted, ref} from "vue";
import {onMounted, ref, watch} from "vue";
import QRCodeModal from "@/components/QRCodeModal.vue";
const pagination = ref({})
Expand All @@ -13,6 +13,9 @@ const search = ref("")
const qrCodeModalOpened = ref({})
const props = defineProps(["workspace"])
const load = async (fresh = false, currentPage = 1) => {
if (fresh)
links.value = []
Expand All @@ -23,14 +26,17 @@ const load = async (fresh = false, currentPage = 1) => {
order_by: 'created_at',
order_desc: 'true',
search: search.value,
page: currentPage
page: currentPage,
'filter_workspaceId': props.workspace?.id ?? "null"
})
page.value = currentPage
links.value = [...links.value, ...pagination.value.data]
loading.value = false
}
watch(() => props.workspace, () => load(true))
const observeEl = el => {
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting === true) {
Expand All @@ -55,7 +61,7 @@ onMounted(() => {
<template>
<div class="links-list">
<input v-model="search" type="text" class="search" placeholder="Search" @input="load(true)">
<router-link v-for="link of links" :key="link.id" class="links-list-item" :to="{name: 'link', params: {id: link.id}}">
<router-link v-for="link of links" :key="link.id" class="links-list-item" :to="workspace ? {name: 'workspace-link', params: {id: link.id, workspace: workspace.slug}} : {name: 'link', params: {id: link.id}}">
<div class="links-list-name">
<div class="flex gap-3 align-items-center w-full">
<div style="width: 30px; height: 30px" class="border-round-2xl flex-none border-1 border-200 overflow-hidden p-1">
Expand Down
16 changes: 13 additions & 3 deletions src/components/Navigation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,35 @@ const router = useRouter()
const userMenuShown = ref(false)
defineProps(['homeRoute'])
const logout = () => {
useUserStore().setUser(null)
router.push({ name: 'home' })
userMenuShown.value = false
localStorage.removeItem("session")
}
const goPageBack = (e) => {
if (window.history.length > 0) {
router.back();
e.preventDefault();
}
};
</script>
<template>
<div class="navigation" v-animate-css="{classes: 'fadeInDown', duration: 300}">
<div class="navigation-contents site-width">
<router-link id="logo" class="scale-active" :to="{name: 'home'}">
<i class="ti ti-arrow-narrow-left" :class="{'i-hidden': $route.name === 'home'}" />
<router-link id="logo" class="scale-active" :to="homeRoute ?? {name: 'home'}">
<i @click="goPageBack" class="ti ti-arrow-narrow-left" :class="{'i-hidden': $route.name === 'home'}" />
<span>punyshort</span>
</router-link>
<div />
<div class="navigation-links">
<div class="navigation-links flex align-items-center">
<!-- <router-link :to="{}" @click.prevent="login" class="scale-active" href="/">
<span>admin</span>
</router-link> -->
<slot name="buttons" />

<template v-if="user">
<div class="relative">
Expand Down
26 changes: 22 additions & 4 deletions src/components/ShortenURL.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const domains = ref([])
const emit = defineEmits(['created'])
const props = defineProps(["workspace"])
const shorten = async () => {
let customPath = path.value.trim() || null
Expand All @@ -28,8 +30,10 @@ const shorten = async () => {
const responseResult = await apiClient.shorten({
long_link: longLink.value,
path: customPath,
domain: domain.value
domain: domain.value,
workspace_id: props.workspace?.id || undefined
})
result.value = responseResult
copiedResult.value = false
emit('created', responseResult)
Expand All @@ -46,10 +50,19 @@ const copyPreview = () => {
copiedResult.value = true
}
const loadDomains = async () => {
if (props.workspace) {
domains.value = (await apiClient.getWorkspaceDomains(props.workspace.id, {order_by: 'created_at', order_desc: false})).data
} else {
domains.value = (await apiClient.get('/v1/domains', {order_by: 'created_at', order_desc: false})).data
}
domain.value = domains.value[0]?.id
}
defineExpose({ loadDomains })
onMounted(async () => {
domains.value = (await apiClient.get('/v1/domains', {order_by: 'created_at', order_desc: false})).data
domain.value = domains.value[0]?.id
loadDomains()
})
</script>
Expand All @@ -63,9 +76,14 @@ onMounted(async () => {
<div :style="{opacity: showSettings ? 0 : 1}" class="shorten-url-splitter" />
<div />
<select :style="{opacity: showSettings ? 0 : 1}" v-model="domain" class="shorten-url-domain-select">
<select v-if="domains.length" :style="{opacity: showSettings ? 0 : 1}" v-model="domain" class="shorten-url-domain-select">
<option v-for="domain of domains" :key="domain.id" :value="domain.id">{{ domain.name }}</option>
</select>
<div v-else class="flex w-full justify-content-between align-items-center">
<p class="opacity-40 text-sm">
{{ workspace ? 'select domains on the linked domains page.' : 'No domains available.' }}
</p>
</div>
<button type="button" class="shorten-url-settings-button scale-active" @click="showSettings = !showSettings" :class="{'settings-shown': showSettings}">
<i class="ti ti-adjustments-horizontal" />
Expand Down
12 changes: 10 additions & 2 deletions src/components/Tabs.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
<script setup>
import {ref} from "vue";
import {ref, watch} from "vue";
import {useRoute, useRouter} from "vue-router";
const route = useRoute()
const router = useRouter()
const props = defineProps({
tabs: Object
})
const selected = ref(Object.keys(props.tabs)[0])
const selected = ref(route.hash?.replace('#', '') || Object.keys(props.tabs)[0])
watch(selected, () => {
router.push({ hash: `#${selected.value}` })
})
</script>
<template>
<div class="tabs">
Expand Down
31 changes: 31 additions & 0 deletions src/components/WorkspaceList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup>
import CreateWorkspaceModal from "@/components/CreateWorkspaceModal.vue";
import {onMounted, ref} from "vue";
import {apiClient} from "@/main";
const createWorkspaceModalVisible = ref(false)
const workspaces = ref([])
onMounted(async () => {
workspaces.value = (await apiClient.getWorkspaces()).data
})
</script>
<template>
<div class="mt-3">
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 10px">
<router-link v-for="workspace of workspaces" :to="{name: 'workspace-home', params: {workspace: workspace.slug}}" class="block no-underline">
<div class="border-1 border-300 p-2 hover:surface-100 transition-duration-300 border-round-md text-900 text-center">
{{ workspace.name }}
</div>
</router-link>

<div @click="createWorkspaceModalVisible = true" class="cursor-pointer border-1 border-300 p-2 border-dashed hover:surface-100 transition-duration-300 border-round-md text-900 text-center">
add workspace
</div>
</div>
<CreateWorkspaceModal v-model:visible="createWorkspaceModalVisible" />
</div>
</template>
7 changes: 7 additions & 0 deletions src/components/layouts/AppLayout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<template>
<navigation />
<router-view />
</template>
<script setup lang="ts">
import Navigation from "@/components/Navigation.vue";
</script>
Loading

0 comments on commit 5988814

Please sign in to comment.