@plutocms/supabase 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.1](https://github.com/plutocms/supabase/compare/v0.2.0...v0.2.1) (2026-09-09)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **admin:** register navbar and isolate layer styles ([#37](https://github.com/plutocms/supabase/issues/37)) ([38853b9](https://github.com/plutocms/supabase/commit/38853b93d2664b583cc2b1c993c937cc8e895354))
9
+
10
+ ## [0.2.0](https://github.com/plutocms/supabase/compare/v0.1.2...v0.2.0) (2026-09-09)
11
+
12
+
13
+ ### Features
14
+
15
+ * make admin account views responsive ([#35](https://github.com/plutocms/supabase/issues/35)) ([cfa5bf0](https://github.com/plutocms/supabase/commit/cfa5bf0b902e0180272eb40d0d170589442ea179))
16
+
3
17
  ## [0.1.2](https://github.com/plutocms/supabase/compare/v0.1.1...v0.1.2) (2026-09-08)
4
18
 
5
19
 
package/FEATURES.md ADDED
@@ -0,0 +1,7 @@
1
+ # Features
2
+
3
+ This file lists every implemented feature of this project. Each entry links to a skill file
4
+ with full detail, so this file stays short.
5
+
6
+ See the workspace-level `CLAUDE.md` (one directory up) for the delegation, writing, and git
7
+ policies shared by every project here.
@@ -1,5 +1,6 @@
1
- @import '@nuxt/ui' source(none);
2
- @import 'tailwindcss' source(none);
1
+ @import '@nuxt/ui';
2
+ @import 'tailwindcss';
3
3
 
4
+ @source '../..';
4
5
  @source '../../../node_modules/.c12/*/app/**';
5
6
  @source '@plutocms/pluto/app/**/*';
@@ -1,13 +1,36 @@
1
1
  <script setup lang="ts">
2
- import type { DropdownMenuItem } from '@nuxt/ui'
2
+ import { UDropdownMenu } from '#components'
3
3
  import { domainFromUrl } from '#imports'
4
4
 
5
5
  const host = useRequestURL().host
6
6
 
7
7
  const route = useRoute()
8
+ const isSidebarOpen = useState<boolean>('pluto-admin-sidebar-open', () => false)
9
+
10
+ function openSidebar() {
11
+ isSidebarOpen.value = true
12
+ }
8
13
 
9
14
  const { isLoggedIn, user, logout } = await useAuth()
10
15
 
16
+ const displayName = computed(() => {
17
+ return (
18
+ user.value?.display_name ||
19
+ user.value?.full_name ||
20
+ user.value?.name ||
21
+ user.value?.username ||
22
+ 'User'
23
+ )
24
+ })
25
+
26
+ const shortDisplayName = computed(() => displayName.value.split(' ')[0])
27
+
28
+ const userMenuLabel = computed(() => {
29
+ return user.value?.username
30
+ ? `${displayName.value} (${user.value.username})`
31
+ : displayName.value
32
+ })
33
+
11
34
  const has_settings_modified = useState<boolean>('has_settings_modified')
12
35
 
13
36
  const { data: settingsData, status } = await useFetch('/api/settings', {
@@ -22,10 +45,14 @@ const maybeDevUrl = computed(() => {
22
45
  return settingsData.value?.settings.website_url || ''
23
46
  })
24
47
 
25
- const items = ref<DropdownMenuItem[][]>([
48
+ type DropdownItems = NonNullable<
49
+ Parameters<typeof UDropdownMenu>[0]['items']
50
+ >
51
+
52
+ const items = computed<DropdownItems>(() => [
26
53
  [
27
54
  {
28
- label: `${user.value?.display_name} (${user.value?.username})` || 'User',
55
+ label: userMenuLabel.value,
29
56
  avatar: {
30
57
  icon: 'lucide:user',
31
58
  class: 'bg-green-600',
@@ -81,8 +108,20 @@ defineShortcuts(extractShortcuts(items.value))
81
108
 
82
109
  <template>
83
110
  <PlutoNavbarAdmin>
84
- <div class="flex h-full items-stretch justify-between px-4">
85
- <div class="flex items-center gap-x-3">
111
+ <div
112
+ class="flex h-full min-w-0 items-stretch justify-between gap-1 px-2 lg:px-4"
113
+ >
114
+ <div class="flex min-w-0 items-center gap-x-1 lg:gap-x-3">
115
+ <UButton
116
+ class="lg:hidden"
117
+ icon="lucide:menu"
118
+ color="neutral"
119
+ variant="ghost"
120
+ aria-label="Open navigation"
121
+ square
122
+ @click="openSidebar"
123
+ />
124
+
86
125
  <div class="h-full shrink-0 py-1">
87
126
  <NuxtLink
88
127
  title="Go to dashboard home"
@@ -96,8 +135,8 @@ defineShortcuts(extractShortcuts(items.value))
96
135
  </NuxtLink>
97
136
  </div>
98
137
 
99
- <div class="h-full">
100
- <ul class="flex h-full items-center text-sm">
138
+ <div class="min-w-0 h-full overflow-hidden">
139
+ <ul class="flex h-full min-w-0 items-center text-sm">
101
140
  <NavbarAdminActionButton
102
141
  :show="route.path.startsWith('/admin')"
103
142
  :title="`Visit ${domainFromUrl(maybeDevUrl)}`"
@@ -129,7 +168,7 @@ defineShortcuts(extractShortcuts(items.value))
129
168
  </div>
130
169
  </div>
131
170
 
132
- <div class="flex items-center gap-3">
171
+ <div class="flex shrink-0 items-center gap-1 lg:gap-3">
133
172
  <ColorModeButton />
134
173
 
135
174
  <div class="h-full">
@@ -140,7 +179,11 @@ defineShortcuts(extractShortcuts(items.value))
140
179
  }"
141
180
  :modal="false"
142
181
  >
143
- <button class="group h-full py-1">
182
+ <button
183
+ class="group h-full py-1"
184
+ aria-label="Open user menu"
185
+ type="button"
186
+ >
144
187
  <div
145
188
  class="flex h-full items-center gap-x-2 rounded-sm rounded-l-xl pr-2 pl-0.5 group-hover:bg-white/20"
146
189
  >
@@ -149,16 +192,16 @@ defineShortcuts(extractShortcuts(items.value))
149
192
  root: 'rounded-xl',
150
193
  icon: 'text-white text-lg',
151
194
  }"
152
- :alt="user?.value?.display_name || 'User Avatar'"
195
+ :alt="displayName"
153
196
  size="sm"
154
197
  icon="lucide:user"
155
198
  class="bg-green-600"
156
199
  />
157
200
 
158
201
  <span
159
- class="light:text-zinc-800 text-sm font-bold dark:text-white"
202
+ class="light:text-zinc-800 hidden text-sm font-bold dark:text-white lg:inline"
160
203
  >
161
- {{ user?.display_name.split(' ')[0] || 'User' }}
204
+ {{ shortDisplayName }}
162
205
  </span>
163
206
  </div>
164
207
  </button>
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ const route = useRoute()
3
+ const session = useSupabaseSession()
4
+
5
+ const shouldShowNavbar = computed(() => {
6
+ return route.path === '/admin'
7
+ || route.path.startsWith('/admin/')
8
+ || Boolean(session.value)
9
+ })
10
+ </script>
11
+
12
+ <template>
13
+ <NavbarAdmin v-if="shouldShowNavbar" />
14
+ </template>
@@ -7,15 +7,46 @@ interface PlutoSupabaseAuthOptions {
7
7
  route?: RouteLocationNormalizedGeneric
8
8
  }
9
9
 
10
+ interface PlutoUserMetadata {
11
+ display_name?: string
12
+ full_name?: string
13
+ name?: string
14
+ username?: string
15
+ is_admin?: boolean
16
+ [key: string]: unknown
17
+ }
18
+
10
19
  export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
11
20
  const toast = useToast()
12
21
 
13
22
  const supabase = useSupabaseClient()
14
23
  const supabaseSession = useSupabaseSession()
15
24
 
16
- const supabaseUser = await supabase.auth.getUser()
25
+ const supabaseUser = useSupabaseUser()
26
+ const userMetadata = useState<PlutoUserMetadata | null>(
27
+ 'pluto-auth-user-metadata',
28
+ () => null
29
+ )
30
+
31
+ const { data: authenticatedUser } = await supabase.auth.getUser()
32
+
33
+ if (authenticatedUser.user?.user_metadata) {
34
+ userMetadata.value = authenticatedUser.user.user_metadata
35
+ }
36
+
37
+ watch(
38
+ supabaseUser,
39
+ (currentUser) => {
40
+ if (currentUser?.user_metadata) {
41
+ userMetadata.value = currentUser.user_metadata
42
+ }
43
+ },
44
+ { immediate: true }
45
+ )
17
46
 
18
- const user = computed(() => supabaseUser.data.user?.user_metadata ?? null)
47
+ const user = computed(
48
+ () => supabaseUser.value?.user_metadata ?? userMetadata.value
49
+ )
19
50
  const isLoggedIn = computed<boolean>(() => !!supabaseSession.value)
20
51
  const isSubmitting = ref<boolean>(false)
21
52
 
@@ -94,6 +125,7 @@ export async function useAuth(authOptions?: PlutoSupabaseAuthOptions) {
94
125
 
95
126
  async function logout(options?: LogoutOptions) {
96
127
  await supabase.auth.signOut()
128
+ userMetadata.value = null
97
129
 
98
130
  if (options?.showToast) {
99
131
  console.warn(`User logged out`)
@@ -9,8 +9,8 @@ useHead({
9
9
  </script>
10
10
 
11
11
  <template>
12
- <div class="grid h-full place-items-center">
13
- <UCard class="w-100">
12
+ <div class="grid min-h-dvh place-items-center p-4">
13
+ <UCard class="w-full max-w-100">
14
14
  <div class="space-y-6 py-10">
15
15
  <div class="text-center space-y-4">
16
16
  <hgroup class="flex flex-col gap-y-1">
@@ -22,8 +22,8 @@ async function submitForm() {
22
22
  </script>
23
23
 
24
24
  <template>
25
- <div class="grid h-full place-items-center">
26
- <UCard class="w-100">
25
+ <div class="grid min-h-dvh place-items-center p-4">
26
+ <UCard class="w-full max-w-100">
27
27
  <div v-if="isEmailSent" class="space-y-6 py-10">
28
28
  <div class="text-center space-y-4">
29
29
  <hgroup class="flex flex-col gap-y-1">
@@ -68,9 +68,16 @@ async function submitForm() {
68
68
  />
69
69
  </UFormField>
70
70
 
71
- <div class="flex items-center justify-between gap-x-4">
71
+ <div
72
+ class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
73
+ >
72
74
  <UFormField>
73
- <UButton :loading="isSubmitting" type="submit" icon="lucide:send">
75
+ <UButton
76
+ :loading="isSubmitting"
77
+ type="submit"
78
+ icon="lucide:send"
79
+ class="w-full justify-center sm:w-auto"
80
+ >
74
81
  Send Reset Link
75
82
  </UButton>
76
83
  </UFormField>
@@ -25,8 +25,8 @@ function submitForm() {
25
25
  </script>
26
26
 
27
27
  <template>
28
- <div class="grid h-full place-items-center">
29
- <UCard class="w-100">
28
+ <div class="grid min-h-dvh place-items-center p-4">
29
+ <UCard class="w-full max-w-100">
30
30
  <form @submit.prevent="submitForm">
31
31
  <div class="flex flex-col gap-y-6">
32
32
  <h1 class="text-3xl font-bold">Login</h1>
@@ -49,12 +49,15 @@ function submitForm() {
49
49
  />
50
50
  </UFormField>
51
51
 
52
- <div class="flex items-center justify-between gap-x-4">
52
+ <div
53
+ class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
54
+ >
53
55
  <UFormField>
54
56
  <UButton
55
57
  :loading="isSubmitting"
56
58
  type="submit"
57
59
  icon="lucide:log-in"
60
+ class="w-full justify-center sm:w-auto"
58
61
  >
59
62
  Login
60
63
  </UButton>
@@ -67,7 +70,9 @@ function submitForm() {
67
70
  </UFormField>
68
71
  </div>
69
72
 
70
- <div class="flex items-center justify-between">
73
+ <div
74
+ class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"
75
+ >
71
76
  <UButton
72
77
  variant="link"
73
78
  icon="lucide:arrow-left"
@@ -68,8 +68,8 @@ async function submitForm() {
68
68
  </script>
69
69
 
70
70
  <template>
71
- <div class="grid h-full place-items-center">
72
- <UCard class="w-100">
71
+ <div class="grid min-h-dvh place-items-center p-4">
72
+ <UCard class="w-full max-w-120">
73
73
  <div v-if="isEmailVerificationMessageVisible" class="space-y-6 py-10">
74
74
  <div class="text-center space-y-4">
75
75
  <hgroup class="flex flex-col gap-y-1">
@@ -118,7 +118,7 @@ async function submitForm() {
118
118
  />
119
119
  </UFormField>
120
120
 
121
- <div class="flex gap-x-4">
121
+ <div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
122
122
  <UFormField label="Username" required>
123
123
  <UInput
124
124
  v-model="form.username"
@@ -159,7 +159,9 @@ async function submitForm() {
159
159
  />
160
160
  </UFormField>
161
161
 
162
- <div class="flex items-center justify-between gap-x-4">
162
+ <div
163
+ class="flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
164
+ >
163
165
  <UFormField>
164
166
  <UButton
165
167
  to="/admin/login"
@@ -176,6 +178,7 @@ async function submitForm() {
176
178
  :disabled="isSubmitting || !passwordMatch"
177
179
  :loading="isSubmitting"
178
180
  type="submit"
181
+ class="w-full justify-center sm:w-auto"
179
182
  >
180
183
  Create account
181
184
  </UButton>
@@ -48,8 +48,8 @@ async function submitForm() {
48
48
  </script>
49
49
 
50
50
  <template>
51
- <div class="grid h-full place-items-center">
52
- <UCard class="w-100">
51
+ <div class="grid min-h-dvh place-items-center p-4">
52
+ <UCard class="w-full max-w-100">
53
53
  <div v-if="isPasswordUpdated" class="space-y-6 py-10">
54
54
  <div class="text-center space-y-4">
55
55
  <hgroup class="flex flex-col gap-y-1">
@@ -109,13 +109,16 @@ async function submitForm() {
109
109
  />
110
110
  </UFormField>
111
111
 
112
- <div class="flex items-center justify-between gap-x-4">
112
+ <div
113
+ class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-x-4"
114
+ >
113
115
  <UFormField>
114
116
  <UButton
115
117
  :loading="isSubmitting"
116
118
  :disabled="!passwordMatch"
117
119
  type="submit"
118
120
  icon="lucide:lock-keyhole"
121
+ class="w-full justify-center sm:w-auto"
119
122
  >
120
123
  Update Password
121
124
  </UButton>
@@ -68,12 +68,16 @@ async function submitForm() {
68
68
 
69
69
  <template>
70
70
  <AdminView>
71
- <h1 class="text-4xl font-bold">Settings</h1>
71
+ <h1 class="text-3xl font-bold lg:text-4xl">Settings</h1>
72
72
 
73
73
  <UCard>
74
74
  <UForm :schema="schema" :state="form" @submit="submitForm">
75
75
  <div class="flex flex-col gap-y-6">
76
- <UFormField label="Website Title" name="website_title" class="w-1/2">
76
+ <UFormField
77
+ label="Website Title"
78
+ name="website_title"
79
+ class="w-full lg:w-1/2"
80
+ >
77
81
  <UInput
78
82
  v-model="form.website_title"
79
83
  placeholder="e.g. My Website"
@@ -83,7 +87,7 @@ async function submitForm() {
83
87
  <UFormField
84
88
  label="Website Description"
85
89
  name="website_description"
86
- class="w-1/2"
90
+ class="w-full lg:w-1/2"
87
91
  >
88
92
  <UInput
89
93
  v-model="form.website_description"
@@ -91,7 +95,11 @@ async function submitForm() {
91
95
  />
92
96
  </UFormField>
93
97
 
94
- <UFormField label="Website URL" name="website_url" class="w-1/2">
98
+ <UFormField
99
+ label="Website URL"
100
+ name="website_url"
101
+ class="w-full lg:w-1/2"
102
+ >
95
103
  <UInput
96
104
  v-model="form.website_url"
97
105
  type="url"
@@ -99,12 +107,13 @@ async function submitForm() {
99
107
  />
100
108
  </UFormField>
101
109
 
102
- <div>
110
+ <div class="flex">
103
111
  <UButton
104
112
  :loading="isSubmitting"
105
113
  :disabled="isSubmitting"
106
114
  type="submit"
107
115
  icon="lucide:save"
116
+ class="w-full justify-center sm:w-auto"
108
117
  >
109
118
  Save
110
119
  </UButton>
@@ -123,8 +123,8 @@ function handleStepChange(step: number) {
123
123
 
124
124
  <template>
125
125
  <UApp>
126
- <div class="flex justify-center items-center h-screen">
127
- <UCard class="w-125">
126
+ <div class="flex min-h-dvh items-center justify-center p-4">
127
+ <UCard class="w-full max-w-125">
128
128
  <div class="flex flex-col gap-y-8">
129
129
  <UStepper ref="stepper" v-model="currentStep" :items="items" linear />
130
130
 
@@ -205,7 +205,7 @@ function handleStepChange(step: number) {
205
205
  </p>
206
206
 
207
207
  <div>
208
- <UPopover :content="{ side: 'right' }" arrow>
208
+ <UPopover :content="{ side: 'bottom' }" arrow>
209
209
  <UButton
210
210
  icon="lucide:lightbulb"
211
211
  label="Technical details"
@@ -284,13 +284,14 @@ function handleStepChange(step: number) {
284
284
  </div>
285
285
 
286
286
  <div class="flex justify-end">
287
- <div>
287
+ <div class="w-full sm:w-auto">
288
288
  <UButton
289
289
  v-if="stepper?.hasNext"
290
290
  :form="currentStepId"
291
291
  :loading="currentLoading"
292
292
  leading-icon="lucide:check"
293
293
  type="submit"
294
+ class="w-full justify-center sm:w-auto"
294
295
  >
295
296
  {{ currentStepSubmitLabel }}
296
297
  </UButton>
@@ -0,0 +1,7 @@
1
+ import NavbarAdminProvider from '../components/navbar/NavbarAdminProvider.vue'
2
+
3
+ export default defineNuxtPlugin(() => {
4
+ const { registerNavbar } = useNavbarAdmin()
5
+
6
+ registerNavbar(NavbarAdminProvider)
7
+ })
package/nuxt.config.ts CHANGED
@@ -12,6 +12,8 @@ export default defineNuxtConfig({
12
12
  name: 'supabase',
13
13
  },
14
14
 
15
+ css: ['#layers/supabase/app/assets/css/tailwind.css'],
16
+
15
17
  runtimeConfig: {
16
18
  supabaseUrl: process.env.SUPABASE_URL,
17
19
  supabaseKey: process.env.SUPABASE_KEY,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@plutocms/supabase",
3
3
  "type": "module",
4
- "version": "0.1.2",
4
+ "version": "0.2.1",
5
5
  "trustedDependencies": [
6
6
  "@parcel/watcher",
7
7
  "@plutocms/pluto",
@@ -38,8 +38,8 @@
38
38
  "dependencies": {
39
39
  "@nuxt/ui": "^4.9.0",
40
40
  "@nuxtjs/supabase": "^2.0.9",
41
- "@plutocms/pluto": "^0.1.0",
42
- "@plutocms/utils": "^0.1.0",
41
+ "@plutocms/pluto": "^0.3.1",
42
+ "@plutocms/utils": "^0.2.1",
43
43
  "postgres": "^3.4.9",
44
44
  "supabase": "^2.109.1",
45
45
  "yup": "1.7.1"