Skip to content

Commit

Permalink
feat: add ui (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
anbraten authored Oct 25, 2023
1 parent 50bd83c commit e1e0adf
Show file tree
Hide file tree
Showing 46 changed files with 9,138 additions and 671 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ jobs:
working-directory: packages/server/
run: pnpm build

# install app again to respect shamefully hoisted dependencies
- name: Build app
working-directory: packages/app/
run: |
pnpm install
pnpm build
- name: Build the Docker image
run: docker build --tag $IMAGE_NAME:latest --cache-from ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:latest .

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ jobs:
working-directory: packages/client/
run: pnpm build

# install app again to respect shamefully hoisted dependencies
- name: Install app
working-directory: packages/app/
run: pnpm install

- name: Typecheck
run: pnpm run -r typecheck

Expand Down
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ packages/client/src/api.ts
packages/client/docs/
packages/server/swagger.json
packages/server/templates/invoice.hbs
.nuxt/
.output/
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ COPY ./packages/server/templates/ ./templates
# TODO: used to suppress warning remove after fixed
RUN mkdir -p /static

# app
ENV NUXT_PUBLIC_API_CLIENT_BASE_URL=/api
COPY ./packages/app/.output .output

RUN chown -R node:node /app
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
nuxt: PORT=3000 node .output/server/index.mjs
api: PORT=7171 node --enable-source-maps index.js
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
[![npm version](https://img.shields.io/npm/v/@geprog/gringotts-client)](https://www.npmjs.com/package/@geprog/gringotts-client)
[![docker image Version (latest by date)](https://img.shields.io/docker/v/geprog/gringotts?label=docker)](https://github.com/geprog/gringotts-payments/pkgs/container/gringotts)

Gringotts is an api service (maybe a frontend will follow at some point) which can be used as gateway between your payment provider and for example your SAAS application. It allows you to easily handle subscriptions, subscription changes and invoice generation for your users.
Gringotts is a service which can be used as gateway between your payment provider and for example your SAAS application. It allows you to easily handle customers, subscriptions and invoice generation for your users.

![Gringotts](./docs/screenshot.png)

## Some (current) opinions

- Subscriptions are always monthly
- Subscription-periods start at the first day the customer started the subscription (exp. 2021-01-15 to 2021-02-14, 2021-02-15 to 2021-03-14, ...)
- Invoices are generated at the end of the subscription-period (exp. 2021-01-15 to 2021-02-14 => invoice is generated on 2021-02-14)

## Usage

Expand All @@ -15,8 +23,8 @@ The container image can be found at `ghcr.io/geprog/gringotts`.

| Name | Description | Default |
| ------------------- | ---------------------------------------------------------------- | ----------------------------------------------------- |
| PORT | Port on which the server should listen | 3000 |
| PUBLIC_URL | Url on which the server is reachable | http://localhost:3000 |
| PORT | Port on which the server should listen | 7171 |
| PUBLIC_URL | Url on which the server is reachable | http://localhost:7171 |
| POSTGRES_URL | Url to the postgres database | postgres://postgres:postgres@localhost:5432/gringotts |
| ADMIN_TOKEN | Token which is used to authenticate admin endpoints like project | |
| CREATE_PROJECT_DATA | Json string which is used to create the first project | |
Expand All @@ -32,7 +40,7 @@ To create a project on start you can set the `CREATE_PROJECT_DATA` environment v
### OpenApi Documention

The OpenApi documentation can be found at `https://<PUBLIC_URL>/docs` like <http://localhost:3000/docs>
The OpenApi documentation can be found at `https://<PUBLIC_URL>/docs` like <http://localhost:7171/docs>

## Development

Expand Down
Binary file added docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@
"engines": {
"node": ">=16",
"pnpm": "8"
},
"pnpm": {
"overrides": {
"ufo": "^1.3.1",
"nuxt": "3.7.4"
}
}
}
23 changes: 23 additions & 0 deletions packages/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Nuxt dev/build outputs
.output
.nuxt
.nitro
.cache
dist

# Node dependencies
node_modules

# Logs
logs
*.log

# Misc
.DS_Store
.fleet
.idea

# Local env files
.env
.env.*
!.env.example
2 changes: 2 additions & 0 deletions packages/app/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
shamefully-hoist=true
strict-peer-dependencies=false
73 changes: 73 additions & 0 deletions packages/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
export default defineAppConfig({
ui: {
primary: 'zinc',
input: {
default: {
size: 'lg',
},
},
select: {
default: {
size: 'lg',
},
},
selectMenu: {
default: {
size: 'lg',
},
},
button: {
default: {
size: 'lg',
},
},
card: {
background: 'bg-white dark:bg-zinc-900',
ring: 'ring-1 ring-zinc-200 dark:ring-zinc-800',
divide: 'divide-y divide-zinc-200 dark:divide-zinc-800',
},
popover: {
background: 'bg-white dark:bg-zinc-900',
ring: 'ring-1 ring-zinc-200 dark:ring-zinc-800',
},
table: {
divide: 'divide-y divide-zinc-300 dark:divide-zinc-700',
tbody: 'divide-y divide-zinc-200 dark:divide-zinc-800',
tr: {
selected: 'bg-zinc-50 dark:bg-zinc-800/50',
active: 'hover:bg-zinc-50 dark:hover:bg-zinc-800/50 cursor-pointer',
},
th: {
color: 'text-zinc-900 dark:text-white',
},
td: {
color: 'text-zinc-500 dark:text-zinc-400',
},
loadingState: {
label: 'text-sm text-center text-zinc-900 dark:text-white',
icon: 'w-6 h-6 mx-auto text-zinc-400 dark:text-zinc-500 mb-4 animate-spin',
},
emptyState: {
label: 'text-sm text-center text-zinc-900 dark:text-white',
icon: 'w-6 h-6 mx-auto text-zinc-400 dark:text-zinc-500 mb-4',
},
},
dropdown: {
background: 'bg-white dark:bg-zinc-800',
ring: 'ring-1 ring-zinc-200 dark:ring-zinc-700',
divide: 'divide-y divide-zinc-200 dark:divide-zinc-700',
item: {
active: 'bg-zinc-100 dark:bg-zinc-900 text-zinc-900 dark:text-white',
inactive: 'text-zinc-700 dark:text-zinc-200',
icon: {
active: 'text-zinc-500 dark:text-zinc-400',
inactive: 'text-zinc-400 dark:text-zinc-500',
},
},
},
notification: {
background: 'bg-white dark:bg-zinc-900',
ring: 'ring-1 ring-zinc-200 dark:ring-zinc-800',
},
},
});
83 changes: 83 additions & 0 deletions packages/app/components/DatePicker.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<template>
<UPopover :popper="{ placement: 'bottom-start' }" class="w-full">
<UButton
icon="i-heroicons-calendar-days-20-solid"
:label="label"
variant="outline"
class="w-full"
:disabled="disabled"
/>
<template #panel="{ close }">
<VCalendarDatePicker
v-model="innerModel"
transparent
:mode="mode"
borderless
:attributes="{
key: 'today',
highlight: {
color: 'blue',
fillMode: 'outline',
class: '!bg-zinc-100 dark:!bg-zinc-800',
},
dates: new Date(),
}"
hide-time-header
is24hr
:is-dark="isDark"
trim-weeks
:first-day-of-week="2"
:disabled="disabled"
@close="close"
/>
</template>
</UPopover>
</template>

<script setup lang="ts">
import { DatePicker as VCalendarDatePicker } from 'v-calendar';
import 'v-calendar/dist/style.css';
const colorMode = useColorMode();
const isDark = computed(() => colorMode.value === 'dark');
const props = withDefaults(
defineProps<{
modelValue: string | Date | undefined;
mode?: 'date' | 'dateTime';
disabled: boolean;
}>(),
{
mode: 'dateTime',
},
);
const emit = defineEmits<{
(event: 'update:modelValue', value: Date | undefined): void;
}>();
const innerModel = computed<Date | undefined>({
get() {
if (!props.modelValue) {
return undefined;
}
return new Date(props.modelValue);
},
set(date) {
emit('update:modelValue', date);
},
});
const label = computed(() => {
if (!innerModel.value) {
return '';
}
if (props.mode === 'date') {
return formatDate(innerModel.value);
}
return formatDateTime(innerModel.value);
});
</script>
106 changes: 106 additions & 0 deletions packages/app/components/Menu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<template>
<aside class="fixed h-screen pb-12 lg:inset-y-0 lg:w-72 lg:flex-col hidden lg:block">
<NuxtLink
class="flex items-center px-8 py-6 text-2xl font-semibold tracking-tight duration-200 cursor-pointer stroke-stone-800 dark:text-stone-200 dark:stroke-stone-500 dark:hover:stroke-white hover:stroke-stone-700 hover:text-stone-700 dark:hover:text-white"
to="/"
>
<img src="/logo_light.svg" alt="CodeCaptain logo" class="w-8 dark:hidden" />
<img src="/logo_dark.svg" alt="CodeCaptain dark logo" class="w-8 hidden dark:block" />
<span
class="ml-2 text-transparent bg-gradient-to-tr from-gray-800 to-gray-400 dark:from-gray-100 dark:to-gray-400 bg-clip-text"
>Grin</span
>
<span>gotts</span>
</NuxtLink>
<div class="space-y-4">
<div class="px-6 py-2">
<h2 class="px-2 mb-2 text-lg font-semibold tracking-tight">Workspace</h2>
<div class="space-y-1">
<MenuItem to="/customers" title="Customers" icon="i-ion-people" />
<MenuItem to="/subscriptions" title="Subscriptions" icon="i-ion-md-refresh" />
<MenuItem to="/invoices" title="Invoices" icon="i-ion-document-text" />
<MenuItem to="/project/settings" title="Settings" icon="i-ion-settings-sharp" />
<!-- <MenuItem to="/docs" title="Api docs" icon="i-ion-document-text-sharp" /> -->
<MenuItem to="https://geprog.com" title="Geprog" icon="i-ion-android-favorite-outline" />
</div>
</div>

<!-- <div class="py-2">
<h2 class="relative px-8 text-lg font-semibold tracking-tight">Projects</h2>
<div dir="ltr" class="relative overflow-hidden px-4">
<div data-radix-scroll-area-viewport="" class="h-full w-full rounded-[inherit]">
<div class="p-2 space-y-1">
<div v-for="project in projects || []" :key="project._id">
<MenuItem to="/" :title="project.name" icon="i-ion-ios-repeat" />
</div>
<MenuItem to="/project/add" title="Add repo" icon="i-heroicons-plus" />
</div>
</div>
</div>
</div> -->
</div>

<div v-if="user" class="absolute inset-x-0 mx-6 bottom-8">
<UPopover>
<button
type="button"
class="flex items-center justify-between gap-4 px-2 py-1 rounded lg:w-full hover:bg-stone-100 dark:hover:bg-stone-800"
>
<div class="flex flex-row-reverse items-center justify-start w-full gap-4 lg:flex-row">
<UAvatar v-if="user.avatarUrl" :src="user.avatarUrl" size="md" alt="Avatar" />

<div class="flex flex-row-reverse items-center gap-4 lg:gap-1 lg:items-start lg:flex-col">
<span class="text-ellipsis overflow-hidden whitespace-nowrap max-w-[8rem]">{{ user.name }}</span>
<span
class="inline-flex items-center font-medium py-0.5 text-xs uppercase rounded-md text-stone-800 dark:text-stone-300"
>FREE</span
>
</div>
</div>

<UIcon name="i-ion-chevron-expand-outline" />
</button>

<template #panel>
<UVerticalNavigation :links="links" class="w-48" />
</template>
</UPopover>
</div>
</aside>
</template>

<script setup lang="ts">
const { user, logout } = await useAuth();
const colorMode = useColorMode();
const isDark = computed({
get() {
return colorMode.value === 'dark';
},
set() {
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark';
},
});
const links = computed(() => [
{
label: 'Theme',
icon: isDark.value ? 'i-heroicons-moon-20-solid' : 'i-heroicons-sun-20-solid',
click: () => {
isDark.value = !isDark.value;
},
},
{
label: 'Logout',
icon: 'i-ion-log-out-outline',
click: logout,
},
]);
// const { data: projects } = await useAsyncData(async () => {
// const { data } = await client.project.projectList();
// return data;
// });
</script>
Loading

0 comments on commit e1e0adf

Please sign in to comment.