feat: introducing self hosted admin dashboard package (#12)

Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
Co-authored-by: Anwarul Islam <anwaarulislaam@gmail.com>
This commit is contained in:
Joel Jacob Stephen
2023-02-28 13:13:27 +05:30
committed by GitHub
parent 2ba05a46ee
commit 3f59597864
219 changed files with 6737 additions and 1967 deletions

30
packages/hoppscotch-sh-admin/.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
pnpm-lock.yaml
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# GQL generated files
src/helpers/backend/graphql.ts
src/helpers/backend/graphql.schema.json
# Vite Generated Files
vite.config.ts.timestamp-*.js

View File

@@ -0,0 +1,570 @@
* {
@apply backface-hidden;
@apply before:backface-hidden;
@apply after:backface-hidden;
@apply selection:bg-accentDark;
@apply selection:text-accentContrast;
}
:root {
@apply antialiased;
accent-color: var(--accent-color);
font-variant-ligatures: common-ligatures;
}
::-webkit-scrollbar-track {
@apply bg-transparent;
@apply border-solid border-l border-dividerLight border-t-0 border-b-0 border-r-0;
}
::-webkit-scrollbar-thumb {
@apply bg-divider bg-clip-content;
@apply rounded-full;
@apply border-solid border-transparent border-4;
@apply hover:bg-dividerDark;
@apply hover:bg-clip-content;
}
::-webkit-scrollbar {
@apply w-4;
@apply h-0;
}
input::placeholder,
textarea::placeholder,
.cm-placeholder {
@apply text-secondary;
@apply opacity-35;
}
input,
textarea {
@apply text-secondaryDark;
@apply font-medium;
}
html {
scroll-behavior: smooth;
}
body {
@apply bg-primary;
@apply text-secondary text-body;
@apply font-medium;
@apply select-none;
@apply overflow-x-hidden;
@apply leading-body;
animation: fade 300ms forwards;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
@keyframes fade {
0% {
@apply opacity-0;
}
100% {
@apply opacity-100;
}
}
.fade-enter-active,
.fade-leave-active {
@apply transition-opacity;
}
.fade-enter-from,
.fade-leave-to {
@apply opacity-0;
}
.slide-enter-active,
.slide-leave-active {
@apply transition;
@apply duration-300;
}
.slide-enter-from,
.slide-leave-to {
@apply transform;
@apply translate-x-full;
}
.bounce-enter-active,
.bounce-leave-active {
@apply transition;
}
.bounce-enter-from,
.bounce-leave-to {
@apply transform;
@apply scale-95;
}
.svg-icons {
@apply flex-shrink-0;
@apply overflow-hidden;
height: var(--line-height-body);
width: var(--line-height-body);
}
a {
@apply inline-flex;
@apply text-current;
@apply no-underline;
@apply transition;
@apply leading-body;
@apply focus:outline-none;
&.link {
@apply items-center;
@apply py-0.5 px-1;
@apply -my-0.5 -mx-1;
@apply text-accent;
@apply rounded;
@apply hover:text-accentDark;
@apply focus-visible:ring;
@apply focus-visible:ring-accent;
@apply focus-visible:text-accentDark;
}
}
.cm-tooltip {
.tippy-box {
@apply shadow-none;
@apply fixed;
@apply inline-flex;
@apply -mt-8;
}
}
.tippy-box[data-theme~="tooltip"] {
@apply bg-tooltip;
@apply border-solid border-tooltip;
@apply rounded;
@apply shadow;
.tippy-content {
@apply flex;
@apply text-tiny text-primary;
@apply font-semibold;
@apply py-1 px-2;
@apply truncate;
@apply leading-normal;
@apply items-center;
kbd {
@apply hidden;
@apply font-sans;
@apply bg-gray-500/45;
@apply text-primaryLight;
@apply rounded-sm;
@apply px-1;
@apply my-0 ml-1;
@apply truncate;
@apply sm:inline-flex;
}
.env-icon {
@apply transition;
@apply inline-flex;
@apply items-center;
}
}
.tippy-svg-arrow {
svg:first-child {
@apply fill-tooltip;
}
svg:last-child {
@apply fill-tooltip;
}
}
}
.tippy-box[data-theme~="popover"] {
@apply bg-popover;
@apply border-solid border-dividerDark;
@apply rounded;
@apply shadow-lg;
.tippy-content {
@apply flex flex-col;
@apply max-h-56;
@apply items-stretch;
@apply overflow-y-auto;
@apply text-secondary text-body;
@apply p-2;
@apply leading-normal;
@apply focus:outline-none;
scroll-behavior: smooth;
}
.tippy-svg-arrow {
svg:first-child {
@apply fill-dividerDark;
}
svg:last-child {
@apply fill-popover;
}
}
}
[data-v-tippy] {
@apply flex flex-1;
}
[interactive] > div {
@apply flex flex-1;
@apply h-full;
}
hr {
@apply border-b border-dividerLight;
@apply my-2;
}
.heading {
@apply font-bold;
@apply text-secondaryDark text-lg;
@apply tracking-tight;
}
.input,
.select,
.textarea {
@apply flex;
@apply w-full;
@apply py-2 px-4;
@apply bg-transparent;
@apply rounded;
@apply text-secondaryDark;
@apply border border-divider;
@apply focus-visible:border-dividerDark;
}
input,
select,
textarea,
button {
@apply truncate;
@apply transition;
@apply text-body;
@apply leading-body;
@apply focus:outline-none;
@apply disabled:cursor-not-allowed;
}
.input[type="file"],
.input[type="radio"],
#installPWA {
@apply hidden;
}
.floating-input ~ label {
@apply absolute;
@apply px-2 py-0.5;
@apply m-2;
@apply rounded;
@apply transition;
@apply origin-top-left;
}
.floating-input:focus-within ~ label,
.floating-input:not(:placeholder-shown) ~ label {
@apply bg-primary;
@apply transform;
@apply origin-top-left;
@apply scale-75;
@apply translate-x-1 -translate-y-4;
}
.floating-input:focus-within ~ label {
@apply text-secondaryDark;
}
.floating-input ~ .end-actions {
@apply absolute;
@apply right-0.2;
@apply inset-y-0;
@apply flex;
@apply items-center;
}
.floating-input:has(~ .end-actions) {
@apply pr-12;
}
pre.ace_editor {
@apply font-mono;
@apply resize-none;
@apply z-0;
}
.select {
@apply appearance-none;
@apply cursor-pointer;
&::-ms-expand {
@apply hidden;
}
}
.select-wrapper {
@apply flex flex-1;
@apply relative;
@apply after:absolute;
@apply after:flex;
@apply after:inset-y-0;
@apply after:items-center;
@apply after:justify-center;
@apply after:pointer-events-none;
@apply after:font-icon;
@apply after:text-secondaryLight;
@apply after:right-3;
@apply after:content-["\e313"];
}
.info-response {
@apply text-pink-500;
}
.success-response {
@apply text-green-500;
}
.redir-response {
@apply text-yellow-500;
}
.cl-error-response {
@apply text-red-500;
}
.sv-error-response {
@apply text-red-600;
}
.missing-data-response {
@apply text-secondaryLight;
}
.toasted-container {
@apply max-w-md;
.toasted {
&.toasted-primary {
@apply px-4 py-2;
@apply bg-tooltip;
@apply border-secondaryDark;
@apply text-primary text-body;
@apply justify-between;
@apply shadow-lg;
@apply font-semibold;
@apply transition;
@apply leading-body;
@apply sm:rounded;
@apply sm:border;
.action {
@apply relative;
@apply flex flex-shrink-0;
@apply text-body;
@apply px-4;
@apply my-1;
@apply ml-auto;
@apply normal-case;
@apply font-semibold;
@apply leading-body;
@apply tracking-normal;
@apply rounded;
@apply last:ml-4;
@apply sm:ml-8;
@apply before:absolute;
@apply before:bg-current;
@apply before:opacity-10;
@apply before:inset-0;
@apply before:transition;
@apply before:content-DEFAULT;
@apply hover:no-underline;
@apply hover:before:opacity-20;
}
}
&.info {
@apply bg-accent;
@apply text-accentContrast;
@apply border-accentDark;
}
&.error {
@apply bg-red-200;
@apply text-red-800;
@apply border-red-400;
}
&.success {
@apply bg-green-200;
@apply text-green-800;
@apply border-green-400;
}
}
}
.smart-splitter .splitpanes__splitter {
@apply relative;
@apply bg-primaryLight;
@apply before:absolute;
@apply before:inset-0;
@apply before:bg-accentLight;
@apply before:opacity-0;
@apply before:z-20;
@apply before:transition;
@apply before:content-DEFAULT;
@apply after:absolute;
@apply after:inset-0;
@apply after:z-20;
@apply after:transition;
@apply after:flex;
@apply after:items-center;
@apply after:justify-center;
@apply after:text-dividerDark;
@apply after:font-icon;
@apply hover:before:opacity-100;
@apply hover:after:text-accentDark;
}
.no-splitter .splitpanes__splitter {
@apply relative;
@apply bg-primaryLight;
}
.smart-splitter.splitpanes--vertical > .splitpanes__splitter {
@apply w-1;
@apply before:-left-0.5;
@apply before:-right-0.5;
@apply before:h-full;
@apply after:content-["\e5d4"];
}
.smart-splitter.splitpanes--horizontal > .splitpanes__splitter {
@apply h-1;
@apply before:-top-0.5;
@apply before:-bottom-0.5;
@apply before:w-full;
@apply after:content-["\e5d3"];
}
.no-splitter.splitpanes--vertical > .splitpanes__splitter {
@apply w-0.5;
@apply pointer-events-none;
}
.no-splitter.splitpanes--horizontal > .splitpanes__splitter {
@apply h-0.5;
@apply pointer-events-none;
}
.cm-focused {
@apply select-auto;
@apply outline-none #{!important};
.cm-activeLine {
@apply bg-primaryLight;
}
.cm-activeLineGutter {
@apply bg-primaryDark;
}
}
.cm-editor {
.cm-line::selection {
@apply bg-accentDark #{!important};
@apply text-accentContrast #{!important};
}
.cm-line ::selection {
@apply bg-accentDark #{!important};
@apply text-accentContrast #{!important};
}
}
.shortcut-key {
@apply inline-flex;
@apply font-sans;
@apply text-tiny;
@apply bg-divider;
@apply rounded;
@apply ml-2;
@apply px-1;
@apply min-w-5;
@apply min-h-5;
@apply items-center;
@apply justify-center;
@apply border border-dividerDark;
@apply shadow-sm;
@apply <sm:hidden;
}
.capitalize-first {
@apply first-letter:capitalize;
}
details {
@apply select-none;
}
details summary::-webkit-details-marker {
@apply hidden;
}
details summary .indicator {
@apply transition;
}
details[open] summary .indicator {
@apply transform;
@apply rotate-90;
}
@media (max-width: 767px) {
main {
margin-bottom: env(safe-area-inset-bottom);
}
}
.env-highlight {
@apply text-accentContrast;
&.env-found {
@apply bg-accentDark;
@apply hover:bg-accent;
}
&.env-not-found {
@apply bg-red-500;
@apply hover:bg-red-600;
}
}
#nprogress .bar {
@apply bg-accent #{!important};
}
.color-picker[type="color"] {
@apply appearance-none;
}
.color-picker[type="color"]::-webkit-color-swatch-wrapper {
@apply rounded;
@apply p-0;
}
.color-picker[type="color"]::-webkit-color-swatch {
@apply rounded;
@apply border-0;
}

View File

@@ -0,0 +1,307 @@
@mixin base-theme {
--font-sans: "Inter", sans-serif;
--font-mono: "Roboto Mono", monospace;
--font-icon: "Material Icons";
--font-size-tiny: calc(var(--font-size-body) - 0.062rem);
}
@mixin dark-theme {
--primary-color: theme("colors.neutral.900");
--primary-light-color: theme("colors.dark.600");
--primary-dark-color: theme("colors.neutral.800");
--primary-contrast-color: #161616;
--secondary-color: theme("colors.neutral.400");
--secondary-light-color: theme("colors.neutral.500");
--secondary-dark-color: theme("colors.neutral.100");
--divider-color: theme("colors.neutral.800");
--divider-light-color: theme("colors.dark.500");
--divider-dark-color: theme("colors.dark.300");
--error-color: theme("colors.stone.800");
--tooltip-color: theme("colors.neutral.100");
--popover-color: theme("colors.dark.700");
--editor-theme: "merbivore_soft";
}
@mixin light-theme {
--primary-color: theme("colors.white");
--primary-light-color: theme("colors.neutral.50");
--primary-dark-color: theme("colors.neutral.100");
--primary-contrast-color: #fefefe;
--secondary-color: theme("colors.neutral.500");
--secondary-light-color: theme("colors.neutral.400");
--secondary-dark-color: theme("colors.neutral.900");
--divider-color: theme("colors.gray.100");
--divider-light-color: theme("colors.neutral.100");
--divider-dark-color: theme("colors.neutral.300");
--error-color: theme("colors.yellow.100");
--tooltip-color: theme("colors.neutral.800");
--popover-color: theme("colors.white");
--editor-theme: "textmate";
}
@mixin black-theme {
--primary-color: theme("colors.dark.900");
--primary-light-color: theme("colors.neutral.900");
--primary-dark-color: theme("colors.dark.800");
--primary-contrast-color: #0e0e0e;
--secondary-color: theme("colors.neutral.400");
--secondary-light-color: theme("colors.neutral.500");
--secondary-dark-color: theme("colors.neutral.100");
--divider-color: theme("colors.neutral.800");
--divider-light-color: theme("colors.dark.800");
--divider-dark-color: theme("colors.dark.300");
--error-color: theme("colors.stone.900");
--tooltip-color: theme("colors.neutral.100");
--popover-color: theme("colors.dark.600");
--editor-theme: "twilight";
}
@mixin dark-editor-theme {
--editor-type-color: theme("colors.purple.400");
--editor-name-color: theme("colors.blue.400");
--editor-operator-color: theme("colors.indigo.400");
--editor-invalid-color: theme("colors.red.400");
--editor-separator-color: theme("colors.gray.400");
--editor-meta-color: theme("colors.gray.400");
--editor-variable-color: theme("colors.green.400");
--editor-link-color: theme("colors.cyan.400");
--editor-process-color: theme("colors.fuchsia.400");
--editor-constant-color: theme("colors.violet.400");
--editor-keyword-color: theme("colors.pink.400");
}
@mixin light-editor-theme {
--editor-type-color: theme("colors.purple.600");
--editor-name-color: theme("colors.red.600");
--editor-operator-color: theme("colors.indigo.600");
--editor-invalid-color: theme("colors.red.600");
--editor-separator-color: theme("colors.gray.600");
--editor-meta-color: theme("colors.gray.600");
--editor-variable-color: theme("colors.green.600");
--editor-link-color: theme("colors.cyan.600");
--editor-process-color: theme("colors.blue.600");
--editor-constant-color: theme("colors.fuchsia.600");
--editor-keyword-color: theme("colors.pink.600");
}
@mixin black-editor-theme {
--editor-type-color: theme("colors.purple.400");
--editor-name-color: theme("colors.fuchsia.400");
--editor-operator-color: theme("colors.indigo.400");
--editor-invalid-color: theme("colors.red.400");
--editor-separator-color: theme("colors.gray.400");
--editor-meta-color: theme("colors.gray.400");
--editor-variable-color: theme("colors.green.400");
--editor-link-color: theme("colors.cyan.400");
--editor-process-color: theme("colors.violet.400");
--editor-constant-color: theme("colors.blue.400");
--editor-keyword-color: theme("colors.pink.400");
}
@mixin green-theme {
--accent-color: theme("colors.green.500");
--accent-light-color: theme("colors.green.400");
--accent-dark-color: theme("colors.green.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.green.200");
--gradient-via-color: theme("colors.green.400");
--gradient-to-color: theme("colors.green.600");
}
@mixin teal-theme {
--accent-color: theme("colors.teal.500");
--accent-light-color: theme("colors.teal.400");
--accent-dark-color: theme("colors.teal.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.teal.200");
--gradient-via-color: theme("colors.teal.400");
--gradient-to-color: theme("colors.teal.600");
}
@mixin blue-theme {
--accent-color: theme("colors.blue.500");
--accent-light-color: theme("colors.blue.400");
--accent-dark-color: theme("colors.blue.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.blue.200");
--gradient-via-color: theme("colors.blue.400");
--gradient-to-color: theme("colors.blue.600");
}
@mixin indigo-theme {
--accent-color: theme("colors.indigo.500");
--accent-light-color: theme("colors.indigo.400");
--accent-dark-color: theme("colors.indigo.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.indigo.200");
--gradient-via-color: theme("colors.indigo.400");
--gradient-to-color: theme("colors.indigo.600");
}
@mixin purple-theme {
--accent-color: theme("colors.purple.500");
--accent-light-color: theme("colors.purple.400");
--accent-dark-color: theme("colors.purple.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.purple.200");
--gradient-via-color: theme("colors.purple.400");
--gradient-to-color: theme("colors.purple.600");
}
@mixin yellow-theme {
--accent-color: theme("colors.yellow.500");
--accent-light-color: theme("colors.yellow.400");
--accent-dark-color: theme("colors.yellow.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.yellow.200");
--gradient-via-color: theme("colors.yellow.400");
--gradient-to-color: theme("colors.yellow.600");
}
@mixin orange-theme {
--accent-color: theme("colors.orange.500");
--accent-light-color: theme("colors.orange.400");
--accent-dark-color: theme("colors.orange.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.orange.200");
--gradient-via-color: theme("colors.orange.400");
--gradient-to-color: theme("colors.orange.600");
}
@mixin red-theme {
--accent-color: theme("colors.red.500");
--accent-light-color: theme("colors.red.400");
--accent-dark-color: theme("colors.red.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.red.200");
--gradient-via-color: theme("colors.red.400");
--gradient-to-color: theme("colors.red.600");
}
@mixin pink-theme {
--accent-color: theme("colors.pink.500");
--accent-light-color: theme("colors.pink.400");
--accent-dark-color: theme("colors.pink.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.pink.200");
--gradient-via-color: theme("colors.pink.400");
--gradient-to-color: theme("colors.pink.600");
}
:root {
@include base-theme;
@include dark-theme;
@include green-theme;
@include dark-editor-theme;
}
:root.light {
@include light-theme;
@include light-editor-theme;
}
:root.dark {
@include dark-theme;
@include dark-editor-theme;
}
:root.black {
@include black-theme;
@include black-editor-theme;
}
:root[data-accent="blue"] {
@include blue-theme;
}
:root[data-accent="green"] {
@include green-theme;
}
:root[data-accent="teal"] {
@include teal-theme;
}
:root[data-accent="indigo"] {
@include indigo-theme;
}
:root[data-accent="purple"] {
@include purple-theme;
}
:root[data-accent="orange"] {
@include orange-theme;
}
:root[data-accent="pink"] {
@include pink-theme;
}
:root[data-accent="red"] {
@include red-theme;
}
:root[data-accent="yellow"] {
@include yellow-theme;
}
@mixin font-small {
--font-size-body: 0.75rem;
--line-height-body: 1rem;
--upper-primary-sticky-fold: 4.125rem;
--upper-secondary-sticky-fold: 6.188rem;
--upper-tertiary-sticky-fold: 8.25rem;
--upper-mobile-primary-sticky-fold: 6.625rem;
--upper-mobile-secondary-sticky-fold: 8.688rem;
--upper-mobile-sticky-fold: 10.75rem;
--upper-mobile-tertiary-sticky-fold: 8.25rem;
--lower-primary-sticky-fold: 3rem;
--lower-secondary-sticky-fold: 5.063rem;
--lower-tertiary-sticky-fold: 7.125rem;
--sidebar-primary-sticky-fold: 2rem;
}
@mixin font-medium {
--font-size-body: 0.875rem;
--line-height-body: 1.25rem;
--upper-primary-sticky-fold: 4.375rem;
--upper-secondary-sticky-fold: 6.688rem;
--upper-tertiary-sticky-fold: 9rem;
--upper-mobile-primary-sticky-fold: 7.125rem;
--upper-mobile-secondary-sticky-fold: 9.438rem;
--upper-mobile-sticky-fold: 11.75rem;
--upper-mobile-tertiary-sticky-fold: 9rem;
--lower-primary-sticky-fold: 3.25rem;
--lower-secondary-sticky-fold: 5.563rem;
--lower-tertiary-sticky-fold: 7.875rem;
--sidebar-primary-sticky-fold: 2.25rem;
}
@mixin font-large {
--font-size-body: 1rem;
--line-height-body: 1.5rem;
--upper-primary-sticky-fold: 4.625rem;
--upper-secondary-sticky-fold: 7.188rem;
--upper-tertiary-sticky-fold: 9.75rem;
--upper-mobile-primary-sticky-fold: 7.625rem;
--upper-mobile-secondary-sticky-fold: 10.188rem;
--upper-mobile-sticky-fold: 12.75rem;
--upper-mobile-tertiary-sticky-fold: 9.75rem;
--lower-primary-sticky-fold: 3.5rem;
--lower-secondary-sticky-fold: 6.063rem;
--lower-tertiary-sticky-fold: 8.625rem;
--sidebar-primary-sticky-fold: 2.5rem;
}
:root[data-font-size="small"] {
@include font-small;
}
:root[data-font-size="medium"] {
@include font-medium;
}
:root[data-font-size="large"] {
@include font-large;
}

View File

@@ -0,0 +1,13 @@
overwrite: true
schema: ${VITE_BACKEND_GQL_URL}
generates:
src/helpers/backend/graphql.ts:
documents: 'src/**/*.graphql'
plugins:
- 'typescript'
- 'typescript-operations'
- 'typed-document-node'
- 'urql-introspection'
src/helpers/backend/graphql.schema.json:
plugins:
- 'introspection'

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hoppscotch Admin Page</title>
<meta property="og:image" content="/cover.jpg" />
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,57 @@
{
"name": "hoppscotch-sh-admin",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*",
"dev:vite": "vite",
"dev:gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml --watch dotenv_config_path=\"../../.env\"",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@graphql-typed-document-node/core": "^3.1.1",
"@hoppscotch/ui": "workspace:^",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.15",
"@urql/vue": "^1.0.4",
"@vueuse/core": "^9.10.0",
"cors": "^2.8.5",
"express": "^4.18.2",
"express-graphql": "^0.12.0",
"graphql": "^16.6.0",
"ts-node-dev": "^2.0.0",
"unplugin-icons": "^0.14.9",
"unplugin-vue-components": "^0.21.0",
"vue": "^3.2.6",
"vue-router": "4"
},
"devDependencies": {
"@graphql-codegen/cli": "3.0.0",
"@graphql-codegen/client-preset": "^2.0.0",
"@graphql-codegen/introspection": "3.0.0",
"@graphql-codegen/typed-document-node": "^2.3.1",
"@graphql-codegen/typescript": "3.0.0",
"@graphql-codegen/typescript-document-nodes": "3.0.0",
"@graphql-codegen/typescript-operations": "3.0.0",
"@graphql-codegen/urql-introspection": "2.2.1",
"@vitejs/plugin-vue": "^1.6.0",
"@vue/compiler-sfc": "^3.2.6",
"graphql-tag": "^2.12.6",
"npm-run-all": "^4.1.5",
"sass": "^1.57.1",
"ts-node": "^10.9.1",
"typescript": "^4.9.3",
"vite": "^2.5.1",
"vite-plugin-pages": "^0.26.0",
"vite-plugin-vue-layouts": "^0.7.0",
"vite-plugin-windicss": "^1.8.8",
"vue-tsc": "^0.3.0",
"windicss": "^3.5.6"
},
"prettier": {
"singleQuote": true,
"semi": true
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,28 @@
<template>
<component :is="layout">
<router-view />
</component>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRouter } from 'vue-router';
import { useDark, useToggle } from '@vueuse/core';
const defaultLayout = 'default';
const { currentRoute } = useRouter();
const layout = computed(
() => `${currentRoute.value.meta.layout || defaultLayout}-layout`
);
const isDark = useDark();
const toggleDark = useToggle(isDark);
</script>
<style lang="scss">
html.dark {
color-scheme: dark;
}
</style>

View File

@@ -0,0 +1,52 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AppHeader: typeof import('./components/app/Header.vue')['default']
AppModal: typeof import('./components/app/Modal.vue')['default']
AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
ButtonPrimary: typeof import('./../../hoppscotch-ui/src/components/button/Primary.vue')['default']
ButtonSecondary: typeof import('./../../hoppscotch-ui/src/components/button/Secondary.vue')['default']
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
IconLucideBell: typeof import('~icons/lucide/bell')['default']
IconLucideLayoutDashboard: typeof import('~icons/lucide/layout-dashboard')['default']
IconLucideLineChart: typeof import('~icons/lucide/line-chart')['default']
IconLucideLock: typeof import('~icons/lucide/lock')['default']
IconLucideMenu: typeof import('~icons/lucide/menu')['default']
IconLucideSettings: typeof import('~icons/lucide/settings')['default']
IconLucideSidebarClose: typeof import('~icons/lucide/sidebar-close')['default']
IconLucideSidebarOpen: typeof import('~icons/lucide/sidebar-open')['default']
IconLucideUser: typeof import('~icons/lucide/user')['default']
IconLucideUserCog: typeof import('~icons/lucide/user-cog')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SmartAnchor: typeof import('./../../hoppscotch-ui/src/components/smart/Anchor.vue')['default']
SmartAutoComplete: typeof import('./../../hoppscotch-ui/src/components/smart/AutoComplete.vue')['default']
SmartCheckbox: typeof import('./../../hoppscotch-ui/src/components/smart/Checkbox.vue')['default']
SmartConfirmModal: typeof import('./../../hoppscotch-ui/src/components/smart/ConfirmModal.vue')['default']
SmartExpand: typeof import('./../../hoppscotch-ui/src/components/smart/Expand.vue')['default']
SmartFileChip: typeof import('./../../hoppscotch-ui/src/components/smart/FileChip.vue')['default']
SmartIntersection: typeof import('./../../hoppscotch-ui/src/components/smart/Intersection.vue')['default']
SmartItem: typeof import('./../../hoppscotch-ui/src/components/smart/Item.vue')['default']
SmartLink: typeof import('./../../hoppscotch-ui/src/components/smart/Link.vue')['default']
SmartModal: typeof import('./../../hoppscotch-ui/src/components/smart/Modal.vue')['default']
SmartProgressRing: typeof import('./../../hoppscotch-ui/src/components/smart/ProgressRing.vue')['default']
SmartRadio: typeof import('./../../hoppscotch-ui/src/components/smart/Radio.vue')['default']
SmartRadioGroup: typeof import('./../../hoppscotch-ui/src/components/smart/RadioGroup.vue')['default']
SmartSlideOver: typeof import('./../../hoppscotch-ui/src/components/smart/SlideOver.vue')['default']
SmartSpinner: typeof import('./../../hoppscotch-ui/src/components/smart/Spinner.vue')['default']
SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default']
SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default']
SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default']
SmartWindow: typeof import('./../../hoppscotch-ui/src/components/smart/Window.vue')['default']
SmartWindows: typeof import('./../../hoppscotch-ui/src/components/smart/Windows.vue')['default']
TeamsAddMembers: typeof import('./components/teams/AddMembers.vue')['default']
}
}

View File

@@ -0,0 +1,92 @@
<template>
<header
class="flex items-center justify-between border-b border-gray-400 dark:border-gray-600 px-6 py-4 bg-neutral-50 dark:bg-neutral-900 shadow-lg"
>
<div class="flex items-center">
<button
@click="isOpen = true"
class="text-gray-200 focus:outline-none lg:hidden mr-2"
>
<icon-lucide-menu class="text-gray-300 w-6" />
</button>
<div class="mr-3 mt-2">
<button @click="expandSidebar">
<icon-lucide-sidebar-open
v-if="isExpanded"
class="text-gray-300 w-6"
/>
<icon-lucide-sidebar-close v-else class="text-gray-300 w-6" />
</button>
</div>
</div>
<div class="flex items-center">
<button class="flex mx-4 text-gray-400 focus:outline-none toggle-dark">
<icon-lucide-bell class="text-gray-300 w-6" />
</button>
<div class="relative">
<button
@click="dropdownOpen = !dropdownOpen"
class="relative z-10 block w-8 h-8 overflow-hidden rounded-full shadow focus:outline-none"
>
<img
class="object-cover w-full h-full"
src="https://media.licdn.com/dms/image/C5603AQHMCx72bNN1MA/profile-displayphoto-shrink_800_800/0/1630736416611?e=2147483647&v=beta&t=McWCdK_7t_NLeU4ze3JPB8xvwg5w50Okuj2JDBekqjw"
alt="Your avatar"
/>
</button>
<div
v-show="dropdownOpen"
@click="dropdownOpen = false"
class="fixed inset-0 z-10 w-full h-full"
></div>
<transition
enter-active-class="transition duration-150 ease-out transform"
enter-from-class="scale-95 opacity-0"
enter-to-class="scale-100 opacity-100"
leave-active-class="transition duration-150 ease-in transform"
leave-from-class="scale-100 opacity-100"
leave-to-class="scale-95 opacity-0"
>
<div
v-show="dropdownOpen"
class="absolute right-0 z-20 w-48 py-2 mt-2 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-xl"
>
<a
href="#"
class="block px-4 py-2 text-sm text-gray-800 dark:text-gray-200 hover:bg-emerald-700 hover:text-white"
>Profile</a
>
<a
href="#"
class="block px-4 py-2 text-sm text-gray-800 dark:text-gray-200 hover:bg-emerald-700 hover:text-white"
>Settings</a
>
<router-link
to="/"
class="block px-4 py-2 text-sm text-gray-800 dark:text-gray-200 hover:bg-emerald-700 hover:text-white"
>Log out</router-link
>
</div>
</transition>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useSidebar } from '../../composables/useSidebar';
const { isOpen, isExpanded } = useSidebar();
const expandSidebar = () => {
isExpanded.value = !isExpanded.value;
};
const dropdownOpen = ref(false);
</script>

View File

@@ -0,0 +1,89 @@
<template>
<div>
<button
@click="open = true"
class="inline-flex items-center bg-emerald-700 h-8 ml-3 pl-2.5 pr-2 rounded-md shadow text-gray-200 dark:border-gray-800 border border-gray-200 leading-none py-0 hover:bg-emerald-700 focus:outline-none focus:bg-emerald-800"
>
Invite User
</button>
<div
:class="`modal ${
!open && 'opacity-0 pointer-events-none'
} z-50 fixed w-full h-full top-0 left-0 flex items-center justify-center`"
>
<div
@click="open = false"
class="absolute w-full h-full bg-neutral-800 opacity-50 modal-overlay"
></div>
<div
class="z-50 w-11/12 mx-auto overflow-y-auto bg-neutral-900 rounded shadow-lg modal-container md:max-w-md"
>
<div
class="absolute top-0 right-0 z-50 flex flex-col items-center mt-4 mr-4 text-sm text-white cursor-pointer modal-close"
>
<svg
class="text-white fill-current"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 18 18"
>
<path
d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"
/>
</svg>
<span class="text-sm">(Esc)</span>
</div>
<!-- Add margin if you want to see some of the overlay behind the modal-->
<div class="px-6 py-4 text-left modal-content">
<!--Title-->
<div class="flex items-center justify-between pb-3">
<p class="text-2xl ml-3 font-bold">{{ title }}</p>
<div class="z-50 cursor-pointer modal-close" @click="open = false">
<svg
class="text-white fill-current"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 18 18"
>
<path
d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"
/>
</svg>
</div>
</div>
<!--Body-->
<slot name="content"> </slot>
<!--Footer-->
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const open = ref(false);
defineProps({
title: {
type: String,
},
button: {
type: String,
},
});
</script>
<style>
.modal {
transition: opacity 0.25s ease;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<div class="flex">
<!-- Backdrop -->
<div
:class="isOpen ? 'block' : 'hidden'"
@click="isOpen = false"
class="fixed inset-0 z-20 transition-opacity bg-black opacity-50 lg:hidden"
></div>
<!-- End Backdrop -->
<div
:class="isOpen ? 'translate-x-0 ease-out' : '-translate-x-full ease-in'"
class="flex fixed inset-y-0 left-0 z-30 overflow-y-auto transition duration-300 transform bg-neutral-200 dark:bg-neutral-900 lg:translate-x-0 lg:static lg:inset-0 border-r border-gray-300 dark:border-gray-600"
>
<div :class="isExpanded ? 'w-64' : 'w-full'">
<div class="flex items-center justify-start mt-6 ml-6">
<div class="flex items-center">
<router-link class="flex" to="/dashboard">
<img src="/public/cover.jpg" alt="" class="h-7" />
<span
v-if="isExpanded"
class="mx-2 text-xl font-semibold text-gray-600 dark:text-gray-200"
>Hoppscotch</span
>
</router-link>
</div>
</div>
<nav class="mt-10">
<router-link
class="flex items-center px-6 py-4 duration-200 border-l-4"
:class="[route.name === '/dashboard' ? activeClass : inactiveClass]"
to="/dashboard"
>
<icon-lucide-layout-dashboard
class="text-xl"
:class="route.name === '/dashboard' ? 'white' : 'gray'"
/>
<span v-if="isExpanded" class="mx-4">Dashboard</span>
</router-link>
<router-link
class="flex items-center px-6 py-4 duration-200 border-l-4 rounded-sm"
:class="[route.name === 'User' ? activeClass : inactiveClass]"
to="/users"
>
<icon-lucide-user
class="text-xl"
:class="route.path === '/users' ? 'white' : 'gray'"
/>
<span v-if="isExpanded" class="mx-4">Users</span>
</router-link>
<router-link
class="flex items-center px-6 py-4 duration-200 border-l-4 rounded-sm"
:class="[route.name === '/teams' ? activeClass : inactiveClass]"
to="/teams"
>
<icon-lucide-users
class="text-xl"
:class="route.name === '/teams' ? 'white' : 'gray'"
/>
<span v-if="isExpanded" class="mx-4">Teams</span>
</router-link>
<router-link
class="flex items-center px-6 py-4 duration-200 border-l-4 rounded-sm"
:class="[route.name === '/settings' ? activeClass : inactiveClass]"
to="/settings"
>
<icon-lucide-settings
class="text-xl"
:class="route.name === '/settings' ? 'white' : 'gray'"
/>
<span v-if="isExpanded" class="mx-4">Settings</span>
</router-link>
</nav>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useSidebar } from '../../composables/useSidebar';
import { useRoute } from 'vue-router';
const { isOpen, isExpanded } = useSidebar();
const route = useRoute();
const activeClass =
'bg-gray-600 bg-opacity-25 text-gray-100 border-gray-100 border-emerald-600';
const inactiveClass =
'border-gray-900 text-gray-500 hover:bg-gray-600 hover:bg-opacity-25 hover:text-gray-100';
</script>

View File

@@ -0,0 +1,83 @@
<template>
<div class="flex justify-between">
<h3
class="mt-10 text-2xl justify-start font-medium text-zinc-800 dark:text-gray-200"
>
Members
</h3>
<div class="mt-8 ml-2">
<button
@click="addNewMember"
class="px-4 py-2 justify-right text-gray-200 bg-emerald-900 rounded-md hover:bg-emerald-700 focus:outline-none focus:bg-emerald-800"
>
Add New
</button>
</div>
</div>
<div>
<div
v-for="(member, i) in members"
:key="i"
class="mt-3 flex justify-center"
>
<input
type="text"
placeholder="Enter email"
v-model="member.name"
class="bg-primaryDark p-2 border-primaryLight w-90 focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
/>
<select
v-model="member.role"
class="bg-primaryDark ml-0.5 p-2 pr-3 focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
>
<option value="owner">Owner</option>
<option value="editor">Editor</option>
<option value="viewer">Viewer</option>
</select>
<div
class="bg-primaryDark flex items-center ml-0.5 border-primaryLight px-2 text-red-600 focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
>
<span class="flex" @click="deleteMemberRow(i)">
<icon-lucide-trash />
</span>
</div>
</div>
</div>
<div class="flex justify-center sm:justify-end mt-8">
<router-link to="/users">
<button
@click="submitTeam"
class="px-4 py-2 text-gray-200 bg-emerald-900 rounded-md hover:bg-emerald-700 focus:outline-none focus:bg-emerald-800"
>
Save
</button>
</router-link>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const members = reactive([
{
name: '',
role: 'owner',
},
]);
const addNewMember = () => {
members.push({
name: '',
role: 'admin',
});
};
const deleteMemberRow = (index: number) => {
members.splice(index, 1);
};
const submitTeam = () => {
console.log('members', members);
};
</script>

View File

@@ -0,0 +1,13 @@
import { ref } from 'vue';
/** isOpen is used to indicate whether the sidebar is now visible on the screen */
const isOpen = ref(false);
/** isExpanded is used to indicate whether the sidebar is now expanded to also include page names or the sidebar is compressed to show just the icons */
const isExpanded = ref(true);
export function useSidebar() {
return {
isOpen,
isExpanded,
};
}

View File

@@ -0,0 +1,8 @@
query GetCollectionChildren($collectionID: ID!, $cursor: String) {
collection(collectionID: $collectionID) {
children(cursor: $cursor) {
id
title
}
}
}

View File

@@ -0,0 +1,9 @@
query Me {
me {
uid
displayName
photoURL
isAdmin
createdOn
}
}

View File

@@ -0,0 +1,15 @@
<template>
<div class="flex h-screen bg-gray-200 font-roboto">
<AppSidebar />
<div class="flex-1 flex flex-col overflow-hidden">
<AppHeader />
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-primary">
<div class="container mx-auto px-6 py-8">
<router-view />
</div>
</main>
</div>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div>
<router-view />
</div>
</template>

View File

@@ -0,0 +1,45 @@
import { createApp } from 'vue';
import urql, { createClient } from '@urql/vue';
import App from './App.vue';
import '../assets/scss/themes.scss';
import '../assets/scss/styles.scss';
import '@hoppscotch/ui/style.css';
import 'virtual:windi.css';
import {
createRouter,
createWebHashHistory,
createWebHistory,
} from 'vue-router';
import { setupLayouts } from 'virtual:generated-layouts';
import generatedRoutes from 'virtual:generated-pages';
import { plugin as HoppUIPlugin, HoppUIPluginOptions } from '@hoppscotch/ui';
const options: HoppUIPluginOptions = {
/* Define options here */
};
const routes = setupLayouts(generatedRoutes);
const app = createApp(App).use(
urql,
createClient({
url: import.meta.env.VITE_BACKEND_GQL_URL,
fetchOptions: () => {
return {
credentials: 'include',
};
},
})
);
app.use(HoppUIPlugin, options);
app.use(
createRouter({
history: createWebHistory(),
routes,
})
);
app.mount('#app');

View File

@@ -0,0 +1,83 @@
<template>
<div class="sm:px-6 p-4">
<h3 class="text-3xl font-medium text-gray-800 dark:text-gray-200">
Dashboard
</h3>
<div class="mt-4">
<div class="flex flex-wrap -mx-6">
<div class="w-full px-6 sm:w-1/2 xl:w-1/3">
<div
class="flex items-center px-5 py-6 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-sm"
>
<icon-lucide-user-cog class="text-2xl text-emerald-500" />
<div class="mx-5">
<h4
class="text-2xl font-semibold text-gray-700 dark:text-gray-200"
>
1000
</h4>
<div class="text-gray-600 dark:text-gray-400">Total Users</div>
</div>
</div>
</div>
<div class="w-full px-6 mt-6 sm:w-1/2 xl:w-1/3 sm:mt-0">
<div
class="flex items-center px-5 py-6 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-sm"
>
<icon-lucide-users class="text-2xl text-pink-400" />
<div class="mx-5">
<h4
class="text-2xl font-semibold text-gray-700 dark:text-gray-200"
>
200
</h4>
<div class="text-gray-600 dark:text-gray-400">Total Teams</div>
</div>
</div>
</div>
<div class="w-full px-6 mt-6 sm:w-1/2 xl:w-1/3 xl:mt-0">
<div
class="flex items-center px-5 py-6 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-sm"
>
<icon-lucide-lock class="text-2xl text-cyan-400" />
<div class="mx-5">
<h4
class="text-2xl font-semibold text-gray-700 dark:text-gray-200"
>
20
</h4>
<div class="text-gray-600 dark:text-gray-400">Total Roles</div>
</div>
</div>
</div>
<div class="w-full px-6 mt-6 sm:w-1/2 xl:w-1/3 xl:mt-10">
<div
class="flex items-center px-5 py-6 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-sm"
>
<icon-lucide-line-chart class="text-2xl text-orange-400" />
<div class="mx-5">
<h4
class="text-2xl font-semibold text-gray-700 dark:text-gray-200"
>
215
</h4>
<div class="text-gray-600 dark:text-gray-400">
Total Collections
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-8"></div>
</div>
</template>

View File

@@ -0,0 +1,114 @@
<template>
<div
class="flex items-center justify-center h-screen px-6 bg-gray-200 dark:bg-gradient-to-r dark:from-zinc-700 dark:to-gray-900"
>
<div
class="w-full max-w-lg p-8 bg-white dark:bg-gradient-to-r dark:from-zinc-800 dark:to-gray-900 rounded-lg shadow-md"
>
<div class="flex items-center justify-center mt-6">
<div class="flex items-center">
<img src="/public/cover.jpg" alt="" class="h-12" />
<span
class="mx-2 text-2xl font-semibold text-gray-600 dark:text-gray-300"
>Hoppscotch</span
>
</div>
</div>
<form class="mt-4" @submit.prevent="login">
<label class="block">
<span class="text-sm text-gray-400">Email</span>
<input
type="email"
class="block w-full p-2 mt-1 bg-gray-700 border-gray-700 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
v-model="email"
/>
</label>
<label class="block mt-3">
<span class="text-sm text-gray-400">Password</span>
<input
type="password"
class="block w-full p-2 mt-1 bg-gray-700 border-gray-700 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
v-model="password"
/>
</label>
<div class="flex items-center justify-between mt-4">
<div>
<label class="inline-flex items-center">
<input
type="checkbox"
class="text-indigo-600 border-gray-200 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
/>
<span class="mx-2 text-sm text-gray-400">Remember me</span>
</label>
</div>
<div>
<a
class="block text-sm text-emerald-700 fontme hover:underline"
href="#"
>Forgot your password?</a
>
</div>
</div>
<div class="mt-6">
<button
type="submit"
class="w-full px-4 py-2 text-sm text-center text-white bg-emerald-600 rounded-md focus:outline-none hover:bg-emerald-500"
>
Sign in
</button>
</div>
<div class="mt-6">
<button
type="submit"
class="w-full px-4 py-2 text-sm text-center text-white border border-gray-500 rounded-md focus:outline-none hover:bg-gray-700"
>
Sign in with Google
</button>
</div>
<div class="mt-6">
<button
type="submit"
class="w-full px-4 py-2 text-sm text-center text-white border border-gray-500 rounded-md focus:outline-none hover:bg-gray-700"
>
Sign in with Microsoft
</button>
</div>
<div class="mt-6">
<button
type="submit"
class="w-full px-4 py-2 text-sm text-center text-white border border-gray-500 rounded-md focus:outline-none hover:bg-gray-700"
>
Sign in with Github
</button>
</div>
</form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const email = ref('joel@gmail.com');
const password = ref('@#!@#asdf1231!_!@#');
function login() {
router.push('/dashboard');
}
</script>
<route lang="yaml">
meta:
layout: empty
</route>

View File

@@ -0,0 +1,5 @@
<template>
<h3 class="sm:px-6 p-4 text-3xl font-medium text-zinc-800 dark:text-gray-200">
Settings
</h3>
</template>

View File

@@ -0,0 +1,37 @@
<template>
<h3 class="sm:px-6 p-4 text-3xl font-medium text-zinc-800 dark:text-gray-200">
Create Team
</h3>
<div>
<div>
<div class="px-6 rounded-md">
<form>
<div class="flex mt-4 ml-10">
<div>
<label
class="text-gray-800 dark:text-gray-200 mr-5 text-lg"
for="username"
>Name:
</label>
<input
class="w-96 p-2 mt-2 dark:bg-zinc-800 border-gray-200 dark:border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
type="text"
placeholder="Enter Name"
/>
</div>
</div>
</form>
<div class="mt-6"></div>
<div class="mx-10">
<TeamsAddMembers />
</div>
<div class="mt-8"></div>
</div>
</div>
</div>
</template>
<script setup lang="ts"></script>

View File

@@ -0,0 +1,144 @@
<template>
<div class="sm:px-6 p-4">
<h3
class="sm:px-6 p-4 text-3xl font-medium text-zinc-800 dark:text-gray-200"
>
Team Details
</h3>
<div class="mt-5">
<div class="flex flex-wrap justify-center mx-6">
<div class="w-full px-4 sm:w-1/2 xl:w-1/3">
<div
class="h-80 px-6 py-6 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-sm"
>
<div class="flex ml-3 mt-2">
<icon-lucide-user class="text-emerald-400 text-3xl" />
<h4
class="text-3xl ml-2 font-semibold text-gray-700 dark:text-gray-200"
>
Team Info
</h4>
</div>
<div class="flex mt-5 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Team ID:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ team.id }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Team Name:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ team.name }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Creation Date:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ team.date }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Number of members:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ team.members }}
</div>
</div>
</div>
</div>
<div class="w-full px-4 sm:w-1/2 xl:w-1/3">
<div
class="h-80 px-5 py-6 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-sm"
>
<div class="flex ml-3 mt-2">
<icon-lucide-line-chart class="text-yellow-300 text-3xl" />
<h4
class="text-3xl ml-2 font-semibold text-gray-700 dark:text-gray-200"
>
Stats
</h4>
</div>
<div class="flex mt-5 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Number of Collections:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ stats.collections }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Number of Requests:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ stats.requests }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Number of Environments:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ stats.environments }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-6"></div>
<div class="mx-10">
<TeamsAddMembers />
</div>
<div class="mt-8"></div>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
interface Team {
id: string;
name: string;
date: string;
members: number;
}
const team = reactive<Team>({
id: 'absd123',
name: 'SelfHosters',
date: '30-01-2023',
members: 20,
});
interface Stats {
collections: number;
requests: number;
environments: number;
}
const stats = reactive<Stats>({
collections: 10,
requests: 20,
environments: 30,
});
</script>

View File

@@ -0,0 +1,173 @@
<template>
<div>
<h3
class="sm:px-6 p-4 text-3xl font-medium text-zinc-800 dark:text-gray-200"
>
Teams
</h3>
<div class="flex flex-col">
<div class="py-2 -my-2 overflow-x-auto sm:-mx-6 sm:px-4 lg:-mx-8 lg:px-8">
<div class="inline-block min-w-full overflow-hidden align-middle">
<div class="sm:px-7 p-4">
<div v-if="showOptions" class="flex w-full items-center mb-7">
<button
class="inline-flex mr-3 items-center h-8 pl-2.5 pr-2 rounded-md shadow text-gray-700 dark:text-gray-400 dark:border-gray-800 border-2 border-gray-200 leading-none py-0"
>
Last 30 days
<icon-lucide-chevron-down
class="w-4 ml-1.5 text-gray-400 dark:text-gray-600"
/>
</button>
<button
class="inline-flex items-center h-8 pl-2.5 pr-2 rounded-md shadow text-gray-700 dark:text-gray-400 dark:border-gray-800 border-2 border-gray-200 leading-none py-0"
>
Filter by
<icon-lucide-chevron-down
class="w-4 ml-1.5 text-gray-400 dark:text-gray-600"
/>
</button>
<router-link to="/teams/addteam">
<button
class="inline-flex items-center bg-emerald-700 h-8 ml-3 pl-2.5 pr-2 rounded-md shadow text-gray-200 dark:border-gray-800 border border-gray-200 leading-none py-0 hover:bg-emerald-700 focus:outline-none focus:bg-emerald-800"
>
Create Team
</button>
</router-link>
<div
class="ml-auto text-gray-400 text-xs sm:inline-flex hidden items-center"
>
<span class="mr-3">Page 2 of 4</span>
<button
class="inline-flex mr-2 items-center h-8 w-8 justify-center text-gray-400 rounded-md shadow border border-gray-200 dark:border-gray-800 leading-none py-0"
>
<icon-lucide-chevron-left class="text-xl" />
</button>
<button
class="inline-flex items-center h-8 w-8 justify-center text-gray-400 rounded-md shadow border border-gray-200 dark:border-gray-800 leading-none py-0"
>
<icon-lucide-chevron-right class="text-xl" />
</button>
</div>
</div>
<div>
<table class="w-full text-left">
<thead>
<tr
class="text-zinc-900 dark:text-gray-200 border-b border-gray-300 dark:border-gray-600 text-sm"
>
<th class="font-normal px-3 pt-0 pb-3"></th>
<th class="font-normal px-3 pt-0 pb-3">Team ID</th>
<th class="font-normal px-3 pt-0 pb-3">Team Name</th>
<th class="font-normal px-3 pt-0 pb-3 hidden md:table-cell">
Members
</th>
<!-- <th class="font-normal px-3 pt-0 pb-3">Status</th> -->
<th class="font-normal px-3 pt-0 pb-3">Date</th>
</tr>
</thead>
<tbody class="text-gray-600 dark:text-gray-300">
<!-- <router-link :custom="true" class="" :to="'/team/detail'"> -->
<tr
v-for="team in teams"
id="team.id"
class="border-b border-gray-300 dark:border-gray-600 hover:bg-zinc-800 rounded-xl"
@click="goToTeam"
>
<td>
<label>
<input
type="checkbox"
class="appearance-none bg-gray-600 checked:bg-emerald-600 rounded-md ml-3 w-5 h-5"
name="radio"
/>
</label>
</td>
<td class="sm:p-3 py-2 px-1">
<div class="flex">
<span class="ml-3">
{{ team.id }}
</span>
</div>
</td>
<td
class="sm:p-3 py-2 px-1 md:table-cell hidden text-sky-500 dark:text-sky-300"
>
{{ team.name }}
</td>
<td class="sm:p-3 py-2 px-1 justify-center">
{{ team.members }}
</td>
<td class="sm:p-3 py-2 px-1">
<div class="flex items-center">
<div class="sm:flex hidden flex-col">
{{ team.date }}
<div class="text-gray-400 text-xs">11:16 AM</div>
</div>
</div>
</td>
<td>
<button
class="w-8 h-8 inline-flex items-center justify-right text-gray-400"
>
<icon-lucide-more-horizontal />
</button>
</td>
</tr>
<!-- </router-link> -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import { defineProps } from 'vue';
defineProps({
showOptions: {
type: Boolean,
default: true,
},
});
interface Team {
id: string;
name: string;
members: number;
date: string;
}
const teams: Array<Team> = [
{
id: '123e4',
name: 'HoppMain',
members: 100,
date: '15-01-2023',
},
{
id: '12vbe',
name: 'Hopp',
members: 10,
date: '19-05-2023',
},
{
id: 'bg1d2',
name: 'Kratos',
members: 59,
date: '15-03-2023',
},
];
const router = useRouter();
const goToTeam = () => {
router.push('/teams/details');
};
</script>

View File

@@ -0,0 +1,125 @@
<template>
<h3 class="sm:px-6 p-4 text-3xl font-medium text-zinc-800 dark:text-gray-200">
Add User
</h3>
<div>
<div>
<div class="px-6 rounded-md">
<form @submit.prevent="register">
<div class="grid gap-6 mt-4">
<div>
<label class="text-gray-800 dark:text-gray-200" for="username"
>Name</label
>
<input
class="w-full mt-2 dark:bg-zinc-800 border-gray-200 dark:border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
type="text"
placeholder="Enter Name"
v-model="user.name"
/>
</div>
<div>
<label class="text-gray-800 dark:text-gray-200" for="username"
>Username</label
>
<input
class="w-full mt-2 dark:bg-zinc-800 border-gray-200 dark:border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
type="text"
placeholder="Enter Username"
v-model="user.username"
/>
</div>
<div>
<label class="text-gray-800 dark:text-gray-200" for="emailAddress"
>Email Address</label
>
<input
class="w-full mt-2 dark:bg-zinc-800 border-gray-200 dark:border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
type="email"
placeholder="Enter Email Address"
v-model="user.email"
/>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2">
<div class="sm:mt-0 sm:mr-3">
<label class="text-gray-800 dark:text-gray-200" for="password"
>Password</label
>
<input
class="w-full mt-2 dark:bg-zinc-800 border-gray-200 dark:border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
type="password"
placeholder="Enter Password"
v-model="user.password"
/>
</div>
<div class="mt-6 ml-0 sm:mt-0 sm:ml-3">
<label
class="text-gray-800 dark:text-gray-200"
for="passwordConfirmation"
>Password Confirmation</label
>
<input
class="w-full mt-2 dark:bg-zinc-800 border-gray-200 dark:border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
type="password"
placeholder="Password Confirmation"
v-model="user.confirm"
/>
</div>
</div>
</div>
<div class="flex justify-center sm:justify-start mt-8">
<router-link to="/user">
<button
class="px-4 py-2 text-gray-200 bg-emerald-900 rounded-md hover:bg-emerald-700 focus:outline-none focus:bg-emerald-800"
>
Save
</button>
</router-link>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps, ref } from 'vue';
const props = defineProps({
open: {
type: Boolean,
},
});
interface User {
name: string;
username: string;
email: string;
password: string;
confirm: string;
}
const user = ref<User>({
name: '',
username: '',
email: '',
password: '',
confirm: '',
});
const register = () => {
const data = JSON.parse(JSON.stringify(user.value));
console.log('Registered: ', data);
};
</script>
<style>
.modal {
transition: opacity 0.25s ease;
}
</style>

View File

@@ -0,0 +1,126 @@
<template>
<div class="sm:px-6 p-4">
<h3
class="sm:px-6 p-4 text-3xl font-medium text-gray-800 dark:text-gray-200"
>
User Detail
</h3>
<div class="mt-5">
<div class="flex flex-wrap justify-center mx-6">
<div class="w-full px-4 sm:w-1/2 xl:w-1/3">
<div
class="h-80 px-6 py-6 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-sm"
>
<div class="flex ml-3 mt-2">
<icon-lucide-user class="text-emerald-400 text-3xl" />
<h4
class="text-3xl ml-2 font-semibold text-gray-700 dark:text-gray-200"
>
User Info
</h4>
</div>
<div class="flex mt-5 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Name:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ user.name }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Username:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ user.username }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Email:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ user.email }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Password:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ user.password }}
</div>
</div>
</div>
</div>
<div class="w-full px-6 sm:w-1/2 xl:w-1/3">
<div
class="h-80 px-5 py-5 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-sm"
>
<div class="flex ml-5 mt-2">
<icon-lucide-line-chart class="text-yellow-300 text-3xl" />
<h4
class="text-3xl ml-2 font-semibold text-gray-700 dark:text-gray-200"
>
Stats
</h4>
</div>
<div class="flex mt-5 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Role:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">
{{ user.role }}
</div>
</div>
<div class="flex mt-2 ml-5 text-xl">
<h4 class="font-semibold text-gray-700 dark:text-gray-400">
Number of Teams:
</h4>
<div class="text-gray-600 dark:text-gray-200 ml-2">10</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-8"></div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
interface User {
name: string;
username: string;
email: string;
password: string;
confirm: string;
role: string;
}
const user = ref<User>({
name: 'Joel',
username: 'joeljs',
email: 'joel@gmail.com',
password: 'joel@2436',
confirm: 'yes',
role: 'Owner',
});
</script>
<style>
.modal {
transition: opacity 0.25s ease;
}
</style>

View File

@@ -0,0 +1,265 @@
<template>
<div>
<h3
class="sm:px-6 p-4 text-3xl font-medium text-zinc-800 dark:text-gray-200"
>
Users
</h3>
<div class="flex flex-col">
<div class="py-2 -my-2 overflow-x-auto sm:-mx-6 sm:px-4 lg:-mx-8 lg:px-8">
<div class="inline-block min-w-full overflow-hidden align-middle">
<div class="sm:px-7 p-4">
<div class="flex w-full items-center mb-7">
<button
class="inline-flex mr-3 items-center h-8 pl-2.5 pr-2 rounded-md shadow text-gray-700 dark:text-gray-400 dark:border-gray-800 border-2 border-gray-200 leading-none py-0"
>
Last 30 days
<icon-lucide-chevron-down
class="w-4 ml-1.5 text-gray-400 dark:text-gray-600"
/>
</button>
<div class="relative">
<button
@click="dropdownOpen = !dropdownOpen"
class="inline-flex items-center h-8 pl-2.5 pr-2 rounded-md shadow text-gray-700 dark:text-gray-400 dark:border-gray-800 border-2 border-gray-200 leading-none py-0"
>
Filter by
<icon-lucide-chevron-down
class="w-4 ml-1.5 text-gray-400 dark:text-gray-600"
/>
</button>
<div
v-show="dropdownOpen"
@click="dropdownOpen = false"
class="fixed inset-0 z-10 w-full h-full"
></div>
<transition
enter-active-class="transition duration-150 ease-out transform"
enter-from-class="scale-95 opacity-0"
enter-to-class="scale-100 opacity-100"
leave-active-class="transition duration-150 ease-in transform"
leave-from-class="scale-100 opacity-100"
leave-to-class="scale-95 opacity-0"
>
<div
v-show="dropdownOpen"
class="absolute left-0 z-20 w-48 mt-2 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-xl"
>
<button
href="#"
class="block w-full text-left rounded-md px-4 py-2 text-gray-800 dark:text-gray-200 hover:bg-emerald-700 hover:text-white"
>
Status
</button>
</div>
</transition>
</div>
<Modal v-if="open" :button="button" :title="title">
<template #title>
<p class="text-2xl font-bold">Invite User</p>
</template>
<template #content>
<div>
<div>
<div class="px-6 rounded-md">
<form>
<div class="my-4">
<div>
<label
class="text-gray-800 dark:text-gray-200"
for="emailAddress"
>Email Address</label
>
<input
class="w-full p-3 mt-3 dark:bg-zinc-800 border-gray-200 dark:border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
type="email"
v-model="email"
placeholder="Enter Email Address"
/>
</div>
</div>
<div class="flex justify-end my-2 pt-3">
<button
@click="sendInvite"
class="px-4 py-2 font-medium tracking-wide text-white bg-emerald-700 rounded-md hover:bg-emerald-600 focus:outline-none"
>
Send Invite
</button>
</div>
</form>
</div>
</div>
</div>
</template>
</Modal>
<button
class="inline-flex items-center bg-zinc-700 h-8 ml-3 pl-2.5 pr-2 rounded-md shadow text-gray-200 dark:border-gray-800 border border-gray-200 leading-none py-0 hover:bg-emerald-700 focus:outline-none focus:bg-emerald-800"
>
<router-link :to="'/users/invited'">
Invited Users
</router-link>
</button>
<div
class="ml-auto text-gray-400 text-xs sm:inline-flex hidden items-center"
>
<span class="mr-3">Page 2 of 4</span>
<button
class="inline-flex mr-2 items-center h-8 w-8 justify-center text-gray-400 rounded-md shadow border border-gray-200 dark:border-gray-800 leading-none py-0"
>
<icon-lucide-chevron-left class="text-xl" />
</button>
<button
class="inline-flex items-center h-8 w-8 justify-center text-gray-400 rounded-md shadow border border-gray-200 dark:border-gray-800 leading-none py-0"
>
<icon-lucide-chevron-right class="text-xl" />
</button>
</div>
</div>
<div>
<table class="w-full text-left">
<thead>
<tr
class="text-zinc-900 dark:text-gray-200 border-b border-gray-300 dark:border-gray-600 text-sm"
>
<th class="font-normal px-3 pt-0 pb-3"></th>
<th class="font-normal px-3 pt-0 pb-3">User ID</th>
<th class="font-normal px-3 pt-0 pb-3">Sign-In Method</th>
<th class="font-normal px-3 pt-0 pb-3 hidden md:table-cell">
Email
</th>
<th class="font-normal px-3 pt-0 pb-3">Status</th>
<th class="font-normal px-3 pt-0 pb-3">Date</th>
</tr>
</thead>
<tbody class="text-gray-600 dark:text-gray-300">
<tr
v-for="user in users"
@click="goToUser"
class="border-b border-gray-300 dark:border-gray-600 hover:bg-zinc-800 rounded-xl"
>
<td>
<label>
<input
type="checkbox"
class="appearance-none bg-gray-600 checked:bg-emerald-600 rounded-md ml-3 w-5 h-5"
name="radio"
/>
</label>
</td>
<td class="sm:p-3 py-2 px-1">
<div class="flex">
<span class="ml-3 truncate">
{{ user?.id }}
</span>
</div>
</td>
<td class="sm:p-3 py-2 px-1 0">
<div class="flex items-center">
{{ user?.signin }}
</div>
</td>
<td
class="sm:p-3 py-2 px-1 md:table-cell hidden text-sky-500 dark:text-sky-300"
>
{{ user?.email }}
</td>
<td class="sm:p-3 py-2 px-1 text-green-500">
{{ user?.status }}
</td>
<td class="sm:p-3 py-2 px-1">
<div class="flex items-center">
<div class="sm:flex hidden flex-col">
{{ user?.date }}
<div class="text-gray-400 text-xs">11:16 AM</div>
</div>
<button
class="w-8 h-8 inline-flex items-center justify-center text-gray-400 ml-auto"
>
<icon-lucide-more-horizontal />
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import Modal from '../../components/app/Modal.vue';
const open = ref(true);
const sendInvite = () => {
open.value = false;
};
const router = useRouter();
const { fetching, data } = useQuery({
query: `
query {
me {
displayName
}
}
`,
});
const goToUser = () => {
router.push('/users/details');
};
const email = ref('');
type User = {
id: string;
signin: string;
email: string;
status: string;
date: string;
};
const title = 'Invite User';
const button = 'Send Invite';
const dropdownOpen = ref(false);
const users: Array<User> = [
{
id: 'abc12',
signin: 'Microsoft',
email: 'joel@gmail.com',
status: 'Active',
date: '15-01-2023',
},
{
id: 'zxc21',
signin: 'Microsoft',
email: 'andrew@gmail.com',
status: 'Active',
date: '15-01-2023',
},
{
id: 'cadf4',
signin: 'Microsoft',
email: 'liyas@gmail.com',
status: 'Active',
date: '15-01-2023',
},
];
</script>

View File

@@ -0,0 +1,226 @@
<template>
<div>
<h3
class="sm:px-6 p-4 text-3xl font-medium text-zinc-800 dark:text-gray-200"
>
Invited Users
</h3>
<div class="flex flex-col">
<div class="py-2 -my-2 overflow-x-auto sm:-mx-6 sm:px-4 lg:-mx-8 lg:px-8">
<div class="inline-block min-w-full overflow-hidden align-middle">
<div class="sm:px-7 p-4">
<div class="flex w-full items-center mb-7">
<button
class="inline-flex mr-3 items-center h-8 pl-2.5 pr-2 rounded-md shadow text-gray-700 dark:text-gray-400 dark:border-gray-800 border-2 border-gray-200 leading-none py-0"
>
Last 30 days
<icon-lucide-chevron-down
class="w-4 ml-1.5 text-gray-400 dark:text-gray-600"
/>
</button>
<div class="relative">
<button
@click="dropdownOpen = !dropdownOpen"
class="inline-flex items-center h-8 pl-2.5 pr-2 rounded-md shadow text-gray-700 dark:text-gray-400 dark:border-gray-800 border-2 border-gray-200 leading-none py-0"
>
Filter by
<icon-lucide-chevron-down
class="w-4 ml-1.5 text-gray-400 dark:text-gray-600"
/>
</button>
<div
v-show="dropdownOpen"
@click="dropdownOpen = false"
class="fixed inset-0 z-10 w-full h-full"
></div>
<transition
enter-active-class="transition duration-150 ease-out transform"
enter-from-class="scale-95 opacity-0"
enter-to-class="scale-100 opacity-100"
leave-active-class="transition duration-150 ease-in transform"
leave-from-class="scale-100 opacity-100"
leave-to-class="scale-95 opacity-0"
>
<div
v-show="dropdownOpen"
class="absolute left-0 z-20 w-48 mt-2 bg-zinc-200 dark:bg-zinc-800 rounded-md shadow-xl"
>
<button
href="#"
class="block w-full text-left rounded-md px-4 py-2 text-gray-800 dark:text-gray-200 hover:bg-emerald-700 hover:text-white"
>
Status
</button>
</div>
</transition>
</div>
<Modal v-if="open" :button="button" :title="title">
<template #title>
<p class="text-2xl font-bold">Invite User</p>
</template>
<template #content>
<div>
<div>
<div class="px-6 rounded-md">
<form>
<div class="my-4">
<div>
<label
class="text-gray-800 dark:text-gray-200"
for="emailAddress"
>Email Address</label
>
<input
class="w-full mt-2 dark:bg-zinc-800 border-gray-200 dark:border-gray-600 rounded-md focus:border-emerald-600 focus:ring focus:ring-opacity-40 focus:ring-emerald-500"
type="email"
placeholder="Enter Email Address"
/>
</div>
</div>
</form>
</div>
</div>
</div>
</template>
</Modal>
<div
class="ml-auto text-gray-400 text-xs sm:inline-flex hidden items-center"
>
<span class="mr-3">Page 2 of 4</span>
<button
class="inline-flex mr-2 items-center h-8 w-8 justify-center text-gray-400 rounded-md shadow border border-gray-200 dark:border-gray-800 leading-none py-0"
>
<icon-lucide-chevron-left class="text-xl" />
</button>
<button
class="inline-flex items-center h-8 w-8 justify-center text-gray-400 rounded-md shadow border border-gray-200 dark:border-gray-800 leading-none py-0"
>
<icon-lucide-chevron-right class="text-xl" />
</button>
</div>
</div>
<div>
<table class="w-full text-left">
<thead>
<tr
class="text-zinc-900 dark:text-gray-200 border-b border-gray-300 dark:border-gray-600 text-sm"
>
<th class="font-normal px-3 pt-0 pb-3"></th>
<th class="font-normal px-3 pt-0 pb-3">Admin ID</th>
<th class="font-normal px-3 pt-0 pb-3">Admin Email</th>
<th class="font-normal px-3 pt-0 pb-3 hidden md:table-cell">
Invitee Email
</th>
<th class="font-normal px-3 pt-0 pb-3">Invited On</th>
</tr>
</thead>
<tbody
v-for="user in invitedUsers"
id="user.id"
class="text-gray-600 dark:text-gray-300"
>
<tr
@click="goToUser"
class="border-b border-gray-300 dark:border-gray-600 hover:bg-zinc-800 rounded-xl"
>
<td>
<label>
<input
type="checkbox"
class="appearance-none bg-gray-600 checked:bg-emerald-600 rounded-md ml-3 w-5 h-5"
name="radio"
/>
</label>
</td>
<td class="sm:p-3 py-2 px-1">
<div class="flex">
<span class="ml-3">
{{ user.adminId }}
</span>
</div>
</td>
<td class="sm:p-3 py-2 px-1 text-sky-500 dark:text-sky-300">
<div class="flex items-center">
{{ user.adminEmail }}
</div>
</td>
<td
class="sm:p-3 py-2 px-1 md:table-cell hidden text-sky-500 dark:text-sky-300"
>
{{ user.inviteeEmail }}
</td>
<td class="sm:p-3 py-2 px-1">
<div class="flex items-center">
<div class="sm:flex hidden flex-col">
{{ user.invitedOn }}
<div class="text-gray-400 text-xs">11:16 AM</div>
</div>
<button
class="w-8 h-8 inline-flex items-center justify-center text-gray-400 ml-auto"
>
<icon-lucide-more-horizontal />
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import Modal from '../../components/app/Modal.vue';
const router = useRouter();
const goToUser = () => {
router.push('/users/detail');
};
const open = ref(false);
const title = 'Invite User';
const button = 'Send Invite';
const dropdownOpen = ref(false);
type InvitedUser = {
adminId: string;
adminEmail: string;
inviteeEmail: string;
invitedOn: string;
};
const invitedUsers: Array<InvitedUser> = [
{
adminId: 'abc12',
adminEmail: 'joel@gmail.com',
inviteeEmail: 'jack@gmail.com',
invitedOn: '15-01-2023',
},
{
adminId: 'zxc21',
adminEmail: 'andrew@gmail.com',
inviteeEmail: 'max@gmail.com',
invitedOn: '15-01-2023',
},
{
adminId: 'cadf4',
adminEmail: 'liyas@gmail.com',
inviteeEmail: 'john@gmail.com',
invitedOn: '15-01-2023',
},
];
</script>

View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import { defineComponent } from 'vue';
const Component: ReturnType<typeof defineComponent>;
export default Component;
}

View File

@@ -0,0 +1,3 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-pages/client" />
/// <reference types="vite-plugin-vue-layouts/client" />

View File

@@ -0,0 +1,91 @@
// {
// "compilerOptions": {
// "target": "ESNext",
// "useDefineForClassFields": true,
// "module": "ESNext",
// "moduleResolution": "Node",
// "strict": true,
// "jsx": "preserve",
// "resolveJsonModule": true,
// "isolatedModules": true,
// "esModuleInterop": true,
// "lib": ["ESNext", "DOM"],
// "skipLibCheck": true,
// "noEmit": true
// },
// "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
// "references": [{ "path": "./tsconfig.node.json" }]
// }
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,51 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
import Icons from 'unplugin-icons/vite';
import IconResolver from 'unplugin-icons/resolver';
import Components from 'unplugin-vue-components/vite';
import WindiCSS from 'vite-plugin-windicss';
import Pages from 'vite-plugin-pages';
import Layouts from 'vite-plugin-vue-layouts';
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
envDir: path.resolve(__dirname, "../../"),
server: {
port: 3100,
},
plugins: [
vue(),
Pages({
dirs: './src/pages',
routeStyle: 'nuxt',
}),
Layouts({
defaultLayout: 'default',
layoutsDirs: 'src/layouts',
}),
WindiCSS({
root: path.resolve(__dirname),
}),
Components({
dts: './src/components.d.ts',
dirs: ['./src/components', '../hoppscotch-ui/src/components'],
directoryAsNamespace: true,
resolvers: [
IconResolver({
prefix: 'icon',
customCollections: ['hopp', 'auth', 'brands'],
}),
(compName: string) => {
if (compName.startsWith("Hopp"))
return { name: compName, from: "@hoppscotch/ui" }
else return undefined
},
],
}),
Icons({
compiler: 'vue3',
}),
],
});

View File

@@ -0,0 +1,65 @@
import { defineConfig } from 'windicss/helpers';
export default defineConfig({
theme: {
container: {
center: true,
},
extend: {
inset: {
upperPrimaryStickyFold: 'var(--upper-primary-sticky-fold)',
upperSecondaryStickyFold: 'var(--upper-secondary-sticky-fold)',
upperTertiaryStickyFold: 'var(--upper-tertiary-sticky-fold)',
upperMobilePrimaryStickyFold: 'var(--upper-mobile-primary-sticky-fold)',
upperMobileSecondaryStickyFold:
'var(--upper-mobile-secondary-sticky-fold)',
upperMobileStickyFold: 'var(--upper-mobile-sticky-fold)',
upperMobileTertiaryStickyFold:
'var(--upper-mobile-tertiary-sticky-fold)',
lowerPrimaryStickyFold: 'var(--lower-primary-sticky-fold)',
lowerSecondaryStickyFold: 'var(--lower-secondary-sticky-fold)',
lowerTertiaryStickyFold: 'var(--lower-tertiary-sticky-fold)',
sidebarPrimaryStickyFold: 'var(--sidebar-primary-sticky-fold)',
},
colors: {
primary: 'var(--primary-color)',
primaryLight: 'var(--primary-light-color)',
primaryDark: 'var(--primary-dark-color)',
primaryContrast: 'var(--primary-contrast-color)',
secondary: 'var(--secondary-color)',
secondaryLight: 'var(--secondary-light-color)',
secondaryDark: 'var(--secondary-dark-color)',
accent: 'var(--accent-color)',
accentLight: 'var(--accent-light-color)',
accentDark: 'var(--accent-dark-color)',
accentContrast: 'var(--accent-contrast-color)',
divider: 'var(--divider-color)',
dividerLight: 'var(--divider-light-color)',
dividerDark: 'var(--divider-dark-color)',
error: 'var(--error-color)',
tooltip: 'var(--tooltip-color)',
popover: 'var(--popover-color)',
gradientFrom: 'var(--gradient-from-color)',
gradientVia: 'var(--gradient-via-color)',
gradientTo: 'var(--gradient-to-color)',
},
fontFamily: {
sans: 'var(--font-sans)',
mono: 'var(--font-mono)',
icon: 'var(--font-icon)',
},
fontSize: {
tiny: 'var(--font-size-tiny)',
body: 'var(--font-size-body)',
},
lineHeight: {
body: 'var(--line-height-body)',
},
cursor: {
nsResize: 'ns-resize',
grab: 'grab',
grabbing: 'grabbing',
},
},
},
});