panda-core 0.2.3 → 0.2.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 348ffbfa5cfbd2fc7fcb605670dd7ecb3914eddd6c076984d94d689d050e44fe
4
- data.tar.gz: 50f977fc4f31605d4b985f9f37d3167b239a4b1627e2aaedb9638e10d7bd0762
3
+ metadata.gz: ea69195a180331f2758bbae8cfc22ccae44cf8b8ac0133b0642c8e4fb7e11e41
4
+ data.tar.gz: 495cd12223dc77a2c3893568732ae8ae73374bc0bdaf940cf4498227eae082dc
5
5
  SHA512:
6
- metadata.gz: 077e81f9825f43dd1e65425fca186277ba082bab5b4e361a746dba2bbdf3224bdca7db055db47504ca6f4d345962129f58ba3b090557992472689a6e57f90f5a
7
- data.tar.gz: 3be894b015597b1f06a12452398eef7c03824de92e553b1d8e960f161407ee9bf3d5cd6df43e57ea5eb7075cb4b16ac38bdeae6010f30fe11e962467e45b92d6
6
+ metadata.gz: eb217e7a4b3f7ddfee0e9e13e252c5fe31a66535b1b239badf9b0b1bfcd71906effd222b0eb2457b6bd4cd309ba69ac8f7edca6db675f93661b7706969728267
7
+ data.tar.gz: 7b39637eb2c83328f84f349ff0006b550741d9c91afacdbdc5c9f11102dc25baf58ae24dd514b87891ec1a2c1d0bfa3fee7b623d919a0113ef4644ea2d3d2a62
data/README.md CHANGED
@@ -59,6 +59,191 @@ The gems listed in the `panda-core.gemspec` will be added to your Gemfile (or, i
59
59
 
60
60
  Make sure to follow the setup instructions for each of these gems.
61
61
 
62
+ ## Configuration
63
+
64
+ Panda Core is configured in `config/initializers/panda.rb`. The install generator creates this file with a complete default configuration:
65
+
66
+ ```ruby
67
+ # config/initializers/panda.rb
68
+ Panda::Core.configure do |config|
69
+ config.admin_path = "/admin"
70
+
71
+ config.login_page_title = "Panda Admin"
72
+ config.admin_title = "Panda Admin"
73
+
74
+ config.authentication_providers = {
75
+ google_oauth2: {
76
+ enabled: true,
77
+ name: "Google",
78
+ client_id: Rails.application.credentials.dig(:google, :client_id),
79
+ client_secret: Rails.application.credentials.dig(:google, :client_secret),
80
+ options: {
81
+ scope: "email,profile",
82
+ prompt: "select_account",
83
+ hd: "yourdomain.com" # Restrict to specific domain
84
+ }
85
+ }
86
+ }
87
+
88
+ # Core settings
89
+ config.session_token_cookie = :panda_session
90
+ config.user_class = "Panda::Core::User"
91
+ config.user_identity_class = "Panda::Core::UserIdentity"
92
+ config.storage_provider = :active_storage
93
+ config.cache_store = :memory_store
94
+
95
+ # Optional editor configuration
96
+ # config.editor_js_tools = []
97
+ # config.editor_js_tool_config = {}
98
+ end
99
+ ```
100
+
101
+ ### Authentication Providers
102
+
103
+ Panda Core supports multiple OAuth providers. Configure your credentials in Rails credentials:
104
+
105
+ ```yaml
106
+ # config/credentials.yml.enc
107
+ google:
108
+ client_id: "your-google-client-id"
109
+ client_secret: "your-google-client-secret"
110
+
111
+ microsoft:
112
+ client_id: "your-microsoft-client-id"
113
+ client_secret: "your-microsoft-client-secret"
114
+ ```
115
+
116
+ Then reference them in your initializer. You can enable multiple providers simultaneously.
117
+
118
+ ### Admin Path
119
+
120
+ The `admin_path` setting controls where the admin interface is mounted (default: `/admin`). You can customize this to avoid route conflicts or match your preferences:
121
+
122
+ ```ruby
123
+ config.admin_path = "/manage" # Custom admin path
124
+ ```
125
+
126
+ This is useful when:
127
+ - You want to avoid conflicts with existing routes
128
+ - You prefer a different URL structure
129
+ - You're running multiple admin interfaces
130
+ - You need different admin paths per environment
131
+
132
+ ### Asset Pipeline
133
+
134
+ Panda Core provides the **single source of truth** for all Panda admin interface styling. This includes:
135
+
136
+ - Base Tailwind CSS utilities and theme system
137
+ - EditorJS content styles (used by Panda CMS)
138
+ - Admin component styles
139
+ - Form styles and typography
140
+
141
+ **Asset Compilation**
142
+
143
+ Panda Core uses Tailwind CSS v4 to compile all admin interface styling.
144
+
145
+ **Quick Start** - Compile full CSS (Core + CMS):
146
+
147
+ ```bash
148
+ cd /path/to/panda-core
149
+
150
+ bundle exec tailwindcss -i app/assets/tailwind/application.css \
151
+ -o public/panda-core-assets/panda-core.css \
152
+ --content '../cms/app/views/**/*.erb' \
153
+ --content '../cms/app/components/**/*.rb' \
154
+ --content '../cms/app/javascript/**/*.js' \
155
+ --content 'app/views/**/*.erb' \
156
+ --content 'app/components/**/*.rb' \
157
+ --minify
158
+ ```
159
+
160
+ **Result**: `panda-core.css` (~37KB) with all utility classes
161
+
162
+ **Important**: Always compile with full content before committing!
163
+
164
+ For complete documentation on:
165
+ - Development workflows (Core-only vs full stack)
166
+ - Release processes
167
+ - Troubleshooting
168
+ - Best practices
169
+
170
+ See **[docs/ASSET_COMPILATION.md](docs/ASSET_COMPILATION.md)**
171
+
172
+ **How Asset Serving Works:**
173
+
174
+ Core's Rack middleware serves `/panda-core-assets/` from the gem's `public/` directory:
175
+
176
+ ```ruby
177
+ # In Core's engine.rb
178
+ config.app_middleware.use(Rack::Static,
179
+ urls: ["/panda-core-assets"],
180
+ root: Panda::Core::Engine.root.join("public")
181
+ )
182
+ ```
183
+
184
+ This means:
185
+ - ✅ CMS and other gems automatically load CSS from Core
186
+ - ✅ No copying needed - served directly from gem
187
+ - ✅ Version changes are instant (just restart server)
188
+ - ✅ Single source of truth for all admin styling
189
+
190
+ **What Gets Compiled:**
191
+
192
+ The compilation process:
193
+ - Scans all Core and CMS views/components for Tailwind classes (via `tailwind.config.js`)
194
+ - Includes EditorJS content styles for rich text editing
195
+ - Applies theme variables for default and sky themes
196
+ - Outputs a single minified CSS file (~37KB)
197
+
198
+ **Asset Serving**
199
+
200
+ Panda Core automatically serves compiled assets from `/panda-core-assets/` using Rack::Static middleware configured in the engine.
201
+
202
+ **How Panda CMS Uses Core Styling**
203
+
204
+ Panda CMS depends on Panda Core and loads its CSS automatically:
205
+
206
+ ```erb
207
+ <!-- In CMS views -->
208
+ <link rel="stylesheet" href="/panda-core-assets/panda-core.css">
209
+ ```
210
+
211
+ CMS no longer compiles its own CSS - all styling comes from Core.
212
+
213
+ **Content Scanning**
214
+
215
+ The `tailwind.config.js` file configures content scanning to include:
216
+ - Core views: `../../app/views/**/*.html.erb`
217
+ - Core components: `../../app/components/**/*.rb`
218
+ - CMS views: `../../../cms/app/views/**/*.html.erb`
219
+ - CMS components: `../../../cms/app/components/**/*.rb`
220
+ - CMS JavaScript: `../../../cms/app/javascript/**/*.js`
221
+
222
+ This ensures all Tailwind classes used across the Panda ecosystem are included in the compiled CSS.
223
+
224
+ **Customizing Styles**
225
+
226
+ To customize the admin interface styles:
227
+
228
+ 1. Edit `app/assets/tailwind/application.css` in panda-core
229
+ 2. Add custom CSS in the appropriate `@layer` (base, components, or utilities)
230
+ 3. Recompile: `bundle exec tailwindcss -i app/assets/tailwind/application.css -o public/panda-core-assets/panda-core.css --minify`
231
+ 4. Copy the updated CSS to test locations if needed
232
+ 5. Restart your Rails server to see changes
233
+
234
+ **Theme System**
235
+
236
+ Panda Core provides two built-in themes accessible via `data-theme` attribute:
237
+
238
+ - `default`: Purple/pink color scheme
239
+ - `sky`: Blue color scheme
240
+
241
+ Themes use CSS custom properties that can be referenced in your styles:
242
+ - `--color-white`, `--color-black`
243
+ - `--color-light`, `--color-mid`, `--color-dark`
244
+ - `--color-highlight`, `--color-active`, `--color-inactive`
245
+ - `--color-warning`, `--color-error`
246
+
62
247
  ## Development
63
248
 
64
249
  After checking out the repo:
@@ -0,0 +1,182 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --color-white: var(--color-white);
5
+ --color-black: var(--color-black);
6
+ --color-light: var(--color-light);
7
+ --color-mid: var(--color-mid);
8
+ --color-dark: var(--color-dark);
9
+ --color-highlight: var(--color-highlight);
10
+ --color-active: var(--color-active);
11
+ --color-inactive: var(--color-inactive);
12
+ --color-warning: var(--color-warning);
13
+ --color-error: var(--color-error);
14
+ }
15
+
16
+ @layer base {
17
+ html[data-theme="default"] {
18
+ --color-white: rgb(249, 249, 249); /* #F9F9F9 */
19
+ --color-black: rgb(26, 22, 29); /* #1A161D */
20
+
21
+ --color-light: rgb(238, 206, 230); /* #EECEE6 */
22
+ --color-mid: rgb(141, 94, 183); /* #8D5EB7 */
23
+ --color-dark: rgb(33, 29, 73); /* #211D49 */
24
+
25
+ --color-highlight: rgb(208, 64, 20); /* #D04014 */
26
+
27
+ --color-active: rgb(0, 135, 85); /* #008755 */
28
+ --color-warning: rgb(250, 207, 142); /* #FACF8E */
29
+ --color-inactive: rgb(216, 247, 245); /* #d6e4f7 */
30
+ --color-error: rgb(245, 129, 129); /* #F58181 */
31
+ }
32
+
33
+ html[data-theme="sky"] {
34
+ --color-white: rgb(249, 249, 249); /* #F9F9F9 */
35
+ --color-black: rgb(26, 22, 29); /* #1A161D */
36
+ --color-light: rgb(204, 238, 242); /* #CCEEF2 */
37
+ --color-mid: rgb(42, 102, 159); /* #2A669F */
38
+ --color-dark: rgb(20, 32, 74); /* #14204A */
39
+ --color-highlight: rgb(208, 64, 20); /* #D04014 */
40
+
41
+ --color-active: rgb(69, 154, 89); /* #459A59 - darker green with better contrast */
42
+ --color-warning: rgb(244, 190, 102); /* #F4BE66 */
43
+ --color-inactive: rgb(216, 247, 245); /* #d6e4f7 */
44
+ --color-error: rgb(208, 64, 20); /* #D04014 */
45
+ }
46
+
47
+ a.block-link:after {
48
+ position: absolute;
49
+ content: "";
50
+ inset: 0;
51
+ }
52
+
53
+ /* Admin gradient backgrounds */
54
+ html[data-theme="default"] .bg-gradient-admin {
55
+ background: linear-gradient(to bottom right, rgb(33, 29, 73), rgb(141, 94, 183));
56
+ }
57
+
58
+ html[data-theme="sky"] .bg-gradient-admin {
59
+ background: linear-gradient(to bottom right, rgb(20, 32, 74), rgb(42, 102, 159));
60
+ }
61
+ }
62
+
63
+ /* EditorJS content styles */
64
+ @layer components {
65
+ .codex-editor__redactor .ce-block .ce-block__content {
66
+ @apply text-base font-normal font-sans text-dark leading-[1.6] space-y-[1.6rem];
67
+
68
+ h1.ce-header {
69
+ @apply text-3xl md:text-4xl font-semibold font-sans leading-[1.2];
70
+ }
71
+
72
+ h2.ce-header {
73
+ @apply text-2xl font-medium font-sans leading-[1.3] mb-4 mt-8;
74
+ }
75
+
76
+ h3.ce-header {
77
+ @apply text-xl font-normal font-sans leading-[1.3] mb-4 mt-6;
78
+ }
79
+
80
+ p,
81
+ li {
82
+ @apply leading-[1.6] tracking-wide max-w-[85ch];
83
+
84
+ a {
85
+ @apply text-[#1A9597] underline underline-offset-2 hover:text-[#158486] focus:outline-2 focus:outline-offset-2 focus:outline-[#1A9597];
86
+ }
87
+
88
+ strong,
89
+ b {
90
+ @apply font-semibold;
91
+ }
92
+ }
93
+
94
+ p {
95
+ @apply mb-4;
96
+ }
97
+
98
+ .cdx-quote {
99
+ @apply bg-[#eef0f3] border-l-inactive border-l-8 p-6 mb-4;
100
+
101
+ .cdx-quote__caption {
102
+ @apply block ml-6 mt-2 text-sm text-dark;
103
+ }
104
+
105
+ .cdx-quote__text {
106
+ quotes: "\201C" "\201D" "\2018" "\2019";
107
+ @apply pl-6;
108
+
109
+ &:before {
110
+ @apply -ml-8 mr-2 text-dark text-6xl leading-4 align-text-bottom font-serif;
111
+ content: open-quote;
112
+ }
113
+
114
+ p {
115
+ @apply inline italic text-lg;
116
+ }
117
+ }
118
+ }
119
+
120
+ .cdx-list {
121
+ @apply mb-4 pl-6;
122
+
123
+ &--ordered {
124
+ @apply list-decimal;
125
+ }
126
+
127
+ &--unordered {
128
+ @apply list-disc;
129
+ }
130
+
131
+ .cdx-list {
132
+ @apply mt-2 mb-0;
133
+ }
134
+
135
+ .cdx-list__item {
136
+ @apply mb-2 pl-2;
137
+ }
138
+ }
139
+
140
+ .cdx-nested-list {
141
+ @apply mb-4 pl-6;
142
+
143
+ &--ordered {
144
+ @apply list-decimal;
145
+ }
146
+
147
+ &--unordered {
148
+ @apply list-disc;
149
+ }
150
+
151
+ .cdx-nested-list {
152
+ @apply mt-2 mb-0;
153
+ }
154
+
155
+ .cdx-nested-list__item {
156
+ @apply mb-2 pl-2;
157
+ }
158
+ }
159
+
160
+ .cdx-table {
161
+ @apply w-full border-collapse border-2 border-dark my-6;
162
+
163
+ &__head {
164
+ @apply font-semibold border-dark border-r-2 p-3 bg-light;
165
+ }
166
+
167
+ &__row {
168
+ @apply border-dark border-b-2;
169
+ }
170
+
171
+ &__cell {
172
+ @apply border-dark border-r-2 p-3;
173
+ }
174
+ }
175
+
176
+ .cdx-embed {
177
+ iframe {
178
+ @apply w-full border-none;
179
+ }
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,21 @@
1
+ module.exports = {
2
+ content: {
3
+ relative: true,
4
+ files: [
5
+ // Panda Core views and components
6
+ "../../app/views/**/*.html.erb",
7
+ "../../app/components/**/*.html.erb",
8
+ "../../app/components/**/*.rb",
9
+ "../../app/helpers/**/*.rb",
10
+
11
+ // Panda CMS views and components (for compilation)
12
+ "../../../cms/app/views/**/*.html.erb",
13
+ "../../../cms/app/builders/**/*.rb",
14
+ "../../../cms/app/components/**/*.html.erb",
15
+ "../../../cms/app/components/**/*.rb",
16
+ "../../../cms/app/helpers/**/*.rb",
17
+ "../../../cms/app/javascript/**/*.js",
18
+ "../../../cms/vendor/javascript/**/*.js",
19
+ ],
20
+ },
21
+ };
@@ -11,9 +11,8 @@ module Panda
11
11
  if Panda::Core.configuration.dashboard_redirect_path
12
12
  redirect_to Panda::Core.configuration.dashboard_redirect_path
13
13
  else
14
- # This can be overridden by applications using panda-core
15
- # For now, just render a basic view
16
- render plain: "Welcome to Panda Admin"
14
+ # Render the dashboard view
15
+ render :show
17
16
  end
18
17
  end
19
18
  end
@@ -13,9 +13,12 @@ module Panda
13
13
 
14
14
  def create
15
15
  auth = request.env["omniauth.auth"]
16
- provider = params[:provider]&.to_sym
16
+ provider_path = params[:provider]&.to_sym
17
17
 
18
- unless Core.configuration.authentication_providers.key?(provider)
18
+ # Find the actual provider key (might be using path_name override)
19
+ provider = find_provider_by_path(provider_path)
20
+
21
+ unless provider && Core.configuration.authentication_providers.key?(provider)
19
22
  redirect_to admin_login_path, flash: {error: "Authentication provider not enabled"}
20
23
  return
21
24
  end
@@ -63,6 +66,21 @@ module Panda
63
66
 
64
67
  redirect_to admin_login_path, flash: {success: "Successfully logged out"}
65
68
  end
69
+
70
+ private
71
+
72
+ # Find the provider key by path name (handles path_name override)
73
+ def find_provider_by_path(provider_path)
74
+ # First check if it's a direct match
75
+ return provider_path if Core.configuration.authentication_providers.key?(provider_path)
76
+
77
+ # Then check if any provider has a matching path_name
78
+ Core.configuration.authentication_providers.each do |key, config|
79
+ return key if config[:path_name]&.to_sym == provider_path
80
+ end
81
+
82
+ nil
83
+ end
66
84
  end
67
85
  end
68
86
  end
@@ -3,6 +3,8 @@
3
3
  module Panda
4
4
  module Core
5
5
  class AdminController < ApplicationController
6
+ layout "panda/core/admin"
7
+
6
8
  # Automatically require admin authentication for all admin controllers
7
9
  before_action :authenticate_admin_user!
8
10
  before_action :set_initial_breadcrumb
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panda
4
+ module Core
5
+ module SessionsHelper
6
+ # Map OAuth provider names to their FontAwesome icon names
7
+ PROVIDER_ICON_MAP = {
8
+ google_oauth2: "google",
9
+ microsoft_graph: "microsoft",
10
+ github: "github"
11
+ }.freeze
12
+
13
+ # Returns the FontAwesome icon name for a given provider
14
+ # Checks provider config first, then falls back to the mapping, then uses the provider name as-is
15
+ def oauth_provider_icon(provider)
16
+ provider_config = Panda::Core.configuration.authentication_providers[provider]
17
+ provider_config&.dig(:icon) || PROVIDER_ICON_MAP[provider.to_sym] || provider.to_s
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,4 +1,5 @@
1
1
  import { Application } from "@hotwired/stimulus"
2
+ import "@fortawesome/fontawesome-free"
2
3
 
3
4
  const application = Application.start()
4
5
 
@@ -1,59 +1,5 @@
1
- <!DOCTYPE html>
2
- <html lang="en" class="h-full bg-white">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <%= csrf_meta_tags %>
7
- <%= csp_meta_tag %>
8
-
9
- <title><%= content_for(:title) || "Panda Admin" %></title>
10
-
11
- <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
12
-
13
- <% if Rails.env.test? || ENV["CI"].present? %>
14
- <!-- Load compiled Panda Core assets for test environment -->
15
- <link rel="stylesheet" href="/panda-core-assets/panda-core-<%= Panda::Core::VERSION %>.css">
16
- <script src="/panda-core-assets/panda-core-<%= Panda::Core::VERSION %>.js" defer></script>
17
-
18
- <% if defined?(Panda::CMS) %>
19
- <!-- Also load Panda CMS assets if CMS is present -->
20
- <link rel="stylesheet" href="/panda-cms-assets/panda-cms-<%= Panda::CMS::VERSION %>.css">
21
- <script src="/panda-cms-assets/panda-cms-<%= Panda::CMS::VERSION %>.js" defer></script>
22
- <% end %>
23
- <% else %>
24
- <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
25
- <% end %>
26
-
27
- <%= yield :admin_head_extra %>
28
- </head>
29
-
30
- <body class="h-full">
31
- <div class="flex h-full" id="panda-container">
32
- <div class="absolute top-0 w-full lg:flex lg:fixed lg:inset-y-0 lg:z-50 lg:flex-col lg:w-72">
33
- <div class="flex overflow-y-auto flex-col gap-y-5 px-4 pb-4 max-h-16 bg-gradient-to-br lg:max-h-full grow from-gray-900 to-gray-700">
34
- <%= render "panda/core/admin/shared/sidebar" %>
35
- <%= yield :admin_sidebar_extra %>
36
- </div>
37
- </div>
38
-
39
- <div class="flex flex-col flex-1 mt-16 ml-0 lg:mt-0 lg:ml-72" id="panda-inner-container">
40
- <section id="panda-main" class="flex flex-row h-full">
41
- <div class="flex-1 h-full" id="panda-core-primary-content">
42
- <%= render "panda/core/admin/shared/breadcrumbs" if respond_to?(:breadcrumbs) %>
43
- <%= render "panda/core/admin/shared/flash" %>
44
-
45
- <%= yield %>
46
-
47
- <% if content_for?(:sidebar) %>
48
- <%= render "panda/core/admin/shared/slideover" do %>
49
- <%= yield :sidebar %>
50
- <% end %>
51
- <% end %>
52
- </div>
53
- </section>
54
-
55
- <%= yield :admin_footer_extra %>
56
- </div>
57
- </div>
58
- </body>
59
- </html>
1
+ <%= render "panda/core/shared/header", html_class: "h-full", body_class: "bg-gradient-admin" %>
2
+ <div class="flex flex-col items-center justify-center min-h-screen px-4">
3
+ <%= yield %>
4
+ </div>
5
+ <%= render "panda/core/shared/footer" %>
@@ -0,0 +1,73 @@
1
+ <div class="mt-5">
2
+ <div class="p-6 bg-white shadow rounded-lg">
3
+ <h2 class="text-lg font-medium text-gray-900">Welcome to Panda Admin</h2>
4
+ <p class="mt-1 text-sm text-gray-500">Manage your application from this central dashboard.</p>
5
+ </div>
6
+
7
+ <div class="mt-6 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
8
+ <% if defined?(Panda::CMS) %>
9
+ <div class="bg-white overflow-hidden shadow rounded-lg">
10
+ <div class="px-4 py-5 sm:p-6">
11
+ <div class="flex items-center">
12
+ <div class="flex-shrink-0">
13
+ <i class="fa-regular fa-file-lines text-3xl text-gray-400"></i>
14
+ </div>
15
+ <div class="ml-5 w-0 flex-1">
16
+ <dt class="text-sm font-medium text-gray-500 truncate">Content Management</dt>
17
+ <dd class="mt-1 text-lg font-semibold text-gray-900">
18
+ <%= link_to "Manage CMS", panda_cms.admin_cms_dashboard_path, class: "text-indigo-600 hover:text-indigo-900" %>
19
+ </dd>
20
+ </div>
21
+ </div>
22
+ <div class="mt-3">
23
+ <p class="text-sm text-gray-500">Pages, posts, menus, and content blocks</p>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+
29
+ <div class="bg-white overflow-hidden shadow rounded-lg">
30
+ <div class="px-4 py-5 sm:p-6">
31
+ <div class="flex items-center">
32
+ <div class="flex-shrink-0">
33
+ <i class="fa-regular fa-user text-3xl text-gray-400"></i>
34
+ </div>
35
+ <div class="ml-5 w-0 flex-1">
36
+ <dt class="text-sm font-medium text-gray-500 truncate">My Profile</dt>
37
+ <dd class="mt-1 text-lg font-semibold text-gray-900">
38
+ <%= link_to "Edit Profile", edit_admin_my_profile_path, class: "text-indigo-600 hover:text-indigo-900" %>
39
+ </dd>
40
+ </div>
41
+ </div>
42
+ <div class="mt-3">
43
+ <p class="text-sm text-gray-500">Update your personal information</p>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <% # Hook for additional dashboard cards %>
49
+ <% if Panda::Core.configuration.respond_to?(:admin_dashboard_cards) %>
50
+ <% cards = Panda::Core.configuration.admin_dashboard_cards&.call(current_user) %>
51
+ <% cards&.each do |card| %>
52
+ <div class="bg-white overflow-hidden shadow rounded-lg">
53
+ <div class="px-4 py-5 sm:p-6">
54
+ <div class="flex items-center">
55
+ <div class="flex-shrink-0">
56
+ <i class="<%= card[:icon] %> text-3xl text-gray-400"></i>
57
+ </div>
58
+ <div class="ml-5 w-0 flex-1">
59
+ <dt class="text-sm font-medium text-gray-500 truncate"><%= card[:title] %></dt>
60
+ <dd class="mt-1 text-lg font-semibold text-gray-900">
61
+ <%= link_to card[:link_text], card[:path], class: "text-indigo-600 hover:text-indigo-900" %>
62
+ </dd>
63
+ </div>
64
+ </div>
65
+ <div class="mt-3">
66
+ <p class="text-sm text-gray-500"><%= card[:description] %></p>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ <% end %>
71
+ <% end %>
72
+ </div>
73
+ </div>
@@ -12,16 +12,10 @@
12
12
  <% end %>
13
13
  </div>
14
14
  <% else %>
15
- <div class="mt-5 p-6 bg-white shadow rounded-lg">
16
- <h2 class="text-lg font-medium text-gray-900">Welcome to Panda Core Admin</h2>
17
- <p class="mt-1 text-sm text-gray-500">Configure dashboard widgets to display custom content here.</p>
18
- </div>
15
+ <%= render "panda/core/admin/dashboard/default_content" %>
19
16
  <% end %>
20
17
  <% else %>
21
- <div class="mt-5 p-6 bg-white shadow rounded-lg">
22
- <h2 class="text-lg font-medium text-gray-900">Welcome to Panda Core Admin</h2>
23
- <p class="mt-1 text-sm text-gray-500">No dashboard widgets configured.</p>
24
- </div>
18
+ <%= render "panda/core/admin/dashboard/default_content" %>
25
19
  <% end %>
26
20
  <% end %>
27
21
  </div>