one-for-all-framework 5.2.0 → 5.3.0
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 +4 -4
- data/Dockerfile +1 -1
- data/Procfile +1 -1
- data/README.md +1 -1
- data/app/controllers/auth_controller.rb +2 -0
- data/app/controllers/products_controller.rb +6 -1
- data/app/controllers/projects_controller.rb +4 -0
- data/app/views/docs.erb +1 -1
- data/app/views/index.erb +1 -1
- data/app/views/layout.erb +90 -23
- data/app/views/page.erb +11 -5
- data/app/views/post.erb +13 -10
- data/app/views/project.erb +18 -12
- data/bin/ofa +24 -5
- data/config/boot.rb +10 -5
- data/config/features.json +2 -2
- data/db/data.sqlite3 +0 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 997b63198052b18b0a1f1f74d74829835a408e0efa5b2adc38e795e9753a9576
|
|
4
|
+
data.tar.gz: 7dfe59789c4cef94ab2a49add1d459f3ba2cf1c362ab812238e74643019eb907
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4d233bc2974284d2ba9de83f20eb4f900bc70fbae6bdf1fc58dc2d9ca8348dcfb6d9baf675347d002dca1620d9d47d9313e44dd0fcda36ae11a66d7ee7ec6a1
|
|
7
|
+
data.tar.gz: 8d634a30437413830ce93d54daf1a3dbed0f27cb1ff46e88fe1bc903435048f0873cdba4899e5f7e1e2933bcb318cc677c1d682a6be551067bcb13a3d67074d7
|
data/Dockerfile
CHANGED
data/Procfile
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
web:
|
|
1
|
+
web: bundle exec ofa run
|
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<img src="public/images/logo.png" width="500" height="500" alt="OFA Framework Logo">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
# ⚡ One-For-All (OFA) Framework v5.
|
|
5
|
+
# ⚡ One-For-All (OFA) Framework v5.3.0
|
|
6
6
|
|
|
7
7
|
[](https://www.ruby-lang.org/)
|
|
8
8
|
[](LICENSE)
|
|
@@ -22,6 +22,7 @@ class AuthController < ApplicationController
|
|
|
22
22
|
session['username'] = user.username
|
|
23
23
|
session['last_active_at'] = Time.now.to_i
|
|
24
24
|
|
|
25
|
+
log_activity("User logged in: #{user.username}")
|
|
25
26
|
redirect_to '/dashboard'
|
|
26
27
|
else
|
|
27
28
|
render 'login', title: 'Login - One-For-All', error: 'Invalid credentials'
|
|
@@ -29,6 +30,7 @@ class AuthController < ApplicationController
|
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def logout
|
|
33
|
+
log_activity("User logged out: #{session['username']}")
|
|
32
34
|
['user_id', :user_id, 'username', :username, 'last_active_at', :last_active_at].each { |k| session.delete(k) }
|
|
33
35
|
redirect_to '/login'
|
|
34
36
|
end
|
|
@@ -20,6 +20,7 @@ class ProductsController < ApplicationController
|
|
|
20
20
|
data = params['product']
|
|
21
21
|
@product = Product.new(data)
|
|
22
22
|
if @product.save
|
|
23
|
+
log_activity("Created product: #{@product.name}", @product, "Price: #{@product.price}")
|
|
23
24
|
redirect_to '/dashboard/products'
|
|
24
25
|
else
|
|
25
26
|
render 'cms/products_form'
|
|
@@ -33,7 +34,9 @@ class ProductsController < ApplicationController
|
|
|
33
34
|
|
|
34
35
|
def update
|
|
35
36
|
@product = Product[params['id']]
|
|
36
|
-
|
|
37
|
+
data = params['product']
|
|
38
|
+
if @product.update(data)
|
|
39
|
+
log_activity("Updated product: #{@product.name}", @product, "New Data: #{data.to_json}")
|
|
37
40
|
redirect_to '/dashboard/products'
|
|
38
41
|
else
|
|
39
42
|
render 'cms/products_form'
|
|
@@ -42,7 +45,9 @@ class ProductsController < ApplicationController
|
|
|
42
45
|
|
|
43
46
|
def destroy
|
|
44
47
|
@product = Product[params['id']]
|
|
48
|
+
name = @product.name
|
|
45
49
|
@product.destroy
|
|
50
|
+
log_activity("Deleted product: #{name}")
|
|
46
51
|
redirect_to '/dashboard/products'
|
|
47
52
|
end
|
|
48
53
|
|
|
@@ -16,6 +16,7 @@ class ProjectsController < ApplicationController
|
|
|
16
16
|
data['is_active'] = boolean_param(data['is_active'])
|
|
17
17
|
@project = Project.new(data)
|
|
18
18
|
if @project.save
|
|
19
|
+
log_activity("Created project: #{@project.title}", @project, "Link: #{@project.link}")
|
|
19
20
|
redirect_to '/dashboard/projects'
|
|
20
21
|
else
|
|
21
22
|
render 'cms/projects_form'
|
|
@@ -32,6 +33,7 @@ class ProjectsController < ApplicationController
|
|
|
32
33
|
data = params['project']
|
|
33
34
|
data['is_active'] = boolean_param(data['is_active'])
|
|
34
35
|
if @project.update(data)
|
|
36
|
+
log_activity("Updated project: #{@project.title}", @project, "New Data: #{data.to_json}")
|
|
35
37
|
redirect_to '/dashboard/projects'
|
|
36
38
|
else
|
|
37
39
|
render 'cms/projects_form'
|
|
@@ -42,7 +44,9 @@ class ProjectsController < ApplicationController
|
|
|
42
44
|
@project = Project[params['id']]
|
|
43
45
|
delete_from_storage(@project.image_url) if @project.respond_to?(:image_url)
|
|
44
46
|
delete_all_images_from_content(@project.description) if @project.respond_to?(:description)
|
|
47
|
+
title = @project.title
|
|
45
48
|
@project.destroy
|
|
49
|
+
log_activity("Deleted project: #{title}")
|
|
46
50
|
redirect_to '/dashboard/projects'
|
|
47
51
|
end
|
|
48
52
|
end
|
data/app/views/docs.erb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="space-y-12 pb-20">
|
|
2
2
|
<div class="text-left">
|
|
3
|
-
<div class="badge-premium">Documentation v5.
|
|
3
|
+
<div class="badge-premium">Documentation v5.3.0</div>
|
|
4
4
|
<h1 class="text-5xl font-black tracking-tighter mb-4 text-slate-700 dark:text-white">Framework <span class="text-primary">Guide</span></h1>
|
|
5
5
|
<p class="text-xl text-slate-500 max-w-2xl leading-relaxed">Everything you need to know about building premium web applications with the One-For-All framework.</p>
|
|
6
6
|
</div>
|
data/app/views/index.erb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div class="text-center space-y-6 max-w-2xl mx-auto py-12">
|
|
2
2
|
<div class="inline-flex items-center px-4 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider animate-pulse">
|
|
3
|
-
Framework Version 5.
|
|
3
|
+
Framework Version 5.3.0
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
6
|
<h1 class="text-5xl md:text-7xl font-black tracking-tight leading-tight">
|
data/app/views/layout.erb
CHANGED
|
@@ -283,33 +283,100 @@
|
|
|
283
283
|
|
|
284
284
|
.nav-item {
|
|
285
285
|
color: #64748b;
|
|
286
|
-
font-weight:
|
|
287
|
-
transition:
|
|
286
|
+
font-weight: 700;
|
|
287
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
288
|
+
position: relative;
|
|
289
|
+
padding: 0.5rem 0;
|
|
290
|
+
display: inline-flex;
|
|
291
|
+
align-items: center;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.nav-item::after {
|
|
295
|
+
content: '';
|
|
296
|
+
position: absolute;
|
|
297
|
+
bottom: 0;
|
|
298
|
+
left: 0;
|
|
299
|
+
width: 0;
|
|
300
|
+
height: 2px;
|
|
301
|
+
background: linear-gradient(90deg, var(--primary), var(--secondary));
|
|
302
|
+
transition: width 0.3s ease;
|
|
303
|
+
border-radius: 99px;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.nav-item:hover::after, .nav-item.active::after {
|
|
307
|
+
width: 100%;
|
|
288
308
|
}
|
|
289
309
|
|
|
290
310
|
.nav-item:hover, .nav-item.active {
|
|
291
311
|
color: var(--primary);
|
|
312
|
+
transform: translateY(-1px);
|
|
292
313
|
}
|
|
293
314
|
|
|
294
315
|
/* Markdown Overrides */
|
|
295
|
-
.markdown-content { text-align: left; line-height: 1.
|
|
296
|
-
.markdown-content h1 { font-size: 2.
|
|
297
|
-
.markdown-content
|
|
316
|
+
.markdown-content { text-align: left; line-height: 1.8; color: #475569; overflow-x: auto; }
|
|
317
|
+
.markdown-content h1 { font-size: 2.5rem; font-weight: 900; margin: 2.5rem 0 1.5rem; color: var(--primary); letter-spacing: -0.02em; line-height: 1.2; }
|
|
318
|
+
.markdown-content h2 { font-size: 2rem; font-weight: 800; margin: 2.5rem 0 1.25rem; color: var(--primary); letter-spacing: -0.01em; border-bottom: 1px solid rgba(0,0,0,0.05); padding-bottom: 0.5rem; }
|
|
319
|
+
.markdown-content h3 { font-size: 1.5rem; font-weight: 800; margin: 2rem 0 1rem; color: var(--secondary); }
|
|
320
|
+
.markdown-content h4 { font-size: 1.25rem; font-weight: 700; margin: 1.5rem 0 0.75rem; color: var(--primary); }
|
|
321
|
+
.markdown-content p { font-size: 1.1rem; margin-bottom: 1.5rem; }
|
|
298
322
|
.markdown-content ul { list-style-type: disc !important; margin-left: 1.5rem !important; margin-bottom: 1.5rem !important; display: block !important; }
|
|
299
323
|
.markdown-content ol { list-style-type: decimal !important; margin-left: 1.5rem !important; margin-bottom: 1.5rem !important; display: block !important; }
|
|
300
324
|
.markdown-content li { margin-bottom: 0.5rem !important; display: list-item !important; }
|
|
301
|
-
.markdown-content blockquote { border-left: 4px solid var(--primary); padding: 1rem
|
|
302
|
-
.markdown-content pre { background:
|
|
303
|
-
.markdown-content code { background: rgba(0,0,0,0.05); padding: 0.2rem 0.4rem; border-radius: 0.4rem; font-family: 'Fira Code', monospace; font-size: 0.9em; }
|
|
304
|
-
.markdown-content
|
|
305
|
-
.markdown-content
|
|
306
|
-
.markdown-content
|
|
325
|
+
.markdown-content blockquote { border-left: 4px solid var(--primary); padding: 1rem 1.5rem; font-style: italic; margin: 2rem 0; color: #64748b; background: rgba(var(--primary-rgb), 0.05); border-radius: 0 1rem 1rem 0; }
|
|
326
|
+
.markdown-content pre { background: #0f172a; padding: 1.5rem; border-radius: 1.25rem; overflow-x: auto; font-family: 'Fira Code', monospace; margin: 2rem 0; line-height: 1.5; border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 10px 30px -10px rgba(0,0,0,0.5); }
|
|
327
|
+
.markdown-content code { background: rgba(0,0,0,0.05); padding: 0.2rem 0.4rem; border-radius: 0.4rem; font-family: 'Fira Code', monospace; font-size: 0.9em; color: var(--primary); font-weight: 600; }
|
|
328
|
+
.markdown-content img { border-radius: 1.5rem; margin: 2rem auto; display: block; border: 1px solid rgba(0,0,0,0.05); transition: transform 0.3s ease; }
|
|
329
|
+
.markdown-content img:hover { transform: scale(1.01); }
|
|
330
|
+
.markdown-content table {
|
|
331
|
+
width: 100%;
|
|
332
|
+
border-collapse: separate;
|
|
333
|
+
border-spacing: 0;
|
|
334
|
+
margin: 2.5rem 0;
|
|
335
|
+
border: 1px solid rgba(0,0,0,0.1);
|
|
336
|
+
border-radius: 1.5rem;
|
|
337
|
+
overflow: hidden;
|
|
338
|
+
background: rgba(255, 255, 255, 0.4);
|
|
339
|
+
backdrop-filter: blur(10px);
|
|
340
|
+
box-shadow: 0 10px 30px -10px rgba(0,0,0,0.1);
|
|
341
|
+
}
|
|
342
|
+
.markdown-content th {
|
|
343
|
+
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
|
344
|
+
color: white !important;
|
|
345
|
+
padding: 1.25rem 1.5rem;
|
|
346
|
+
font-weight: 800;
|
|
347
|
+
text-transform: uppercase;
|
|
348
|
+
font-size: 0.7rem;
|
|
349
|
+
text-align: left !important;
|
|
350
|
+
letter-spacing: 0.1em;
|
|
351
|
+
border: none;
|
|
352
|
+
}
|
|
353
|
+
.markdown-content td {
|
|
354
|
+
padding: 1.25rem 1.5rem;
|
|
355
|
+
border-bottom: 1px solid rgba(0,0,0,0.05);
|
|
356
|
+
font-size: 0.9rem;
|
|
357
|
+
color: #475569;
|
|
358
|
+
vertical-align: top;
|
|
359
|
+
line-height: 1.6;
|
|
360
|
+
}
|
|
361
|
+
.markdown-content tr:last-child td { border-bottom: none; }
|
|
362
|
+
.markdown-content tr:nth-child(even) { background: rgba(0,0,0,0.02); }
|
|
363
|
+
.markdown-content tr:hover { background: rgba(0,0,0,0.04); transition: background 0.3s; }
|
|
364
|
+
.markdown-content code { white-space: pre-wrap; word-break: break-word; }
|
|
365
|
+
.markdown-content pre code { white-space: pre; word-break: normal; }
|
|
366
|
+
.markdown-content a { color: var(--primary); font-weight: 700; text-decoration: none; border-bottom: 1px dashed var(--primary); transition: all 0.3s; }
|
|
367
|
+
.markdown-content a:hover { color: var(--secondary); border-bottom-style: solid; }
|
|
307
368
|
|
|
308
|
-
.dark .markdown-content
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
369
|
+
.dark .markdown-content table {
|
|
370
|
+
background: rgba(15, 23, 42, 0.6);
|
|
371
|
+
border-color: rgba(255,255,255,0.1);
|
|
372
|
+
box-shadow: 0 20px 40px -12px rgba(0,0,0,0.5);
|
|
373
|
+
}
|
|
374
|
+
.dark .markdown-content td {
|
|
375
|
+
border-color: rgba(255,255,255,0.05);
|
|
376
|
+
color: #94a3b8;
|
|
377
|
+
}
|
|
378
|
+
.dark .markdown-content tr:nth-child(even) { background: rgba(255,255,255,0.03); }
|
|
379
|
+
.dark .markdown-content tr:hover { background: rgba(255,255,255,0.05); }
|
|
313
380
|
|
|
314
381
|
/* Code Copy Button */
|
|
315
382
|
.code-wrapper { position: relative; margin: 1.5rem 0; }
|
|
@@ -498,7 +565,7 @@
|
|
|
498
565
|
</header>
|
|
499
566
|
|
|
500
567
|
<!-- Main Content Area -->
|
|
501
|
-
<main class="flex-1 md:ml-64 p-
|
|
568
|
+
<main class="flex-1 md:ml-64 p-4 md:p-12 pt-24 md:pt-12 min-w-0 w-full">
|
|
502
569
|
<div class="max-w-5xl mx-auto">
|
|
503
570
|
<%= @content %>
|
|
504
571
|
</div>
|
|
@@ -518,14 +585,14 @@
|
|
|
518
585
|
</div>
|
|
519
586
|
|
|
520
587
|
<ul class="hidden md:flex items-center gap-8">
|
|
521
|
-
<li><a href="/" class="nav-item <%= req.path == '/' ? 'active' : '' %>">Home</a></li>
|
|
588
|
+
<li><a href="/" class="nav-item <%= req.path == '/' ? 'active' : '' %>"><i class="fas fa-house mr-2 text-sm opacity-70"></i> Home</a></li>
|
|
522
589
|
<% if FEATURES_CONFIG['type'] == 'e_commerce' %>
|
|
523
|
-
<li><a href="/cart" class="nav-item <%= req.path == '/cart' ? 'active' : '' %>"><i class="fas fa-shopping-cart mr-
|
|
590
|
+
<li><a href="/cart" class="nav-item <%= req.path == '/cart' ? 'active' : '' %>"><i class="fas fa-shopping-cart mr-2 text-sm opacity-70"></i> Cart</a></li>
|
|
524
591
|
<% end %>
|
|
525
592
|
<% Page.where(is_nav: true, is_active: true).all.each do |p| %>
|
|
526
|
-
<li><a href="/<%= p.slug %>" class="nav-item <%= req.path == "/#{p.slug}" ? 'active' : '' %>"
|
|
593
|
+
<li><a href="/<%= p.slug %>" class="nav-item <%= req.path == "/#{p.slug}" ? 'active' : '' %>"><i class="fas fa-file-lines mr-2 text-sm opacity-70"></i> <%= p.title %></a></li>
|
|
527
594
|
<% end %>
|
|
528
|
-
<li><a href="/dashboard" class="nav-item <%= req.path.start_with?('/dashboard') ? 'active' : '' %>">Dashboard</a></li>
|
|
595
|
+
<li><a href="/dashboard" class="nav-item <%= req.path.start_with?('/dashboard') ? 'active' : '' %>"><i class="fas fa-gauge-high mr-2 text-sm opacity-70"></i> Dashboard</a></li>
|
|
529
596
|
</ul>
|
|
530
597
|
|
|
531
598
|
<div class="flex items-center gap-4">
|
|
@@ -561,7 +628,7 @@
|
|
|
561
628
|
|
|
562
629
|
<nav class="flex flex-col gap-6">
|
|
563
630
|
<a href="/" class="text-lg font-bold <%= req.path == '/' ? 'text-primary' : 'text-slate-500' %>">
|
|
564
|
-
<i class="fas fa-
|
|
631
|
+
<i class="fas fa-house w-8"></i> Home
|
|
565
632
|
</a>
|
|
566
633
|
<% if FEATURES_CONFIG['type'] == 'e_commerce' %>
|
|
567
634
|
<a href="/cart" class="text-lg font-bold <%= req.path == '/cart' ? 'text-primary' : 'text-slate-500' %>">
|
|
@@ -570,7 +637,7 @@
|
|
|
570
637
|
<% end %>
|
|
571
638
|
<% Page.where(is_nav: true, is_active: true).all.each do |p| %>
|
|
572
639
|
<a href="/<%= p.slug %>" class="text-lg font-bold <%= req.path == "/#{p.slug}" ? 'text-primary' : 'text-slate-500' %>">
|
|
573
|
-
<i class="fas fa-file-
|
|
640
|
+
<i class="fas fa-file-lines w-8"></i> <%= p.title %>
|
|
574
641
|
</a>
|
|
575
642
|
<% end %>
|
|
576
643
|
<div class="h-px <%= ['light_sidebar', 'light_glass'].include?(FEATURES_CONFIG['theme']) ? 'bg-black/10' : 'bg-white/5' %> my-2"></div>
|
data/app/views/page.erb
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
<div class="glass-card anime-element">
|
|
2
|
-
<
|
|
3
|
-
|
|
1
|
+
<div class="glass-card anime-element !p-5 md:!p-12 text-left max-w-4xl mx-auto">
|
|
2
|
+
<div class="mb-8 md:mb-12">
|
|
3
|
+
<div class="badge-premium">Halaman Statis</div>
|
|
4
|
+
<h1 class="text-2xl md:text-5xl font-black mb-4 leading-tight"><%= @title %></h1>
|
|
5
|
+
<div class="h-1 w-20 bg-primary rounded-full"></div>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="page-content markdown-content">
|
|
4
9
|
<%= markdown(@content) %>
|
|
5
10
|
</div>
|
|
11
|
+
|
|
6
12
|
<div class="mt-12 pt-8 border-t border-white/5">
|
|
7
|
-
<a href="/" class="btn-secondary">
|
|
8
|
-
<i class="fas fa-arrow-left mr-2"></i>
|
|
13
|
+
<a href="/" class="btn-secondary w-full sm:w-auto">
|
|
14
|
+
<i class="fas fa-arrow-left mr-2"></i> Kembali ke Beranda
|
|
9
15
|
</a>
|
|
10
16
|
</div>
|
|
11
17
|
</div>
|
data/app/views/post.erb
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
<div class="glass-card anime-element
|
|
2
|
-
<div
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
<div class="glass-card anime-element !p-6 md:!p-12 text-left max-w-4xl mx-auto">
|
|
2
|
+
<div class="mb-8 md:mb-12">
|
|
3
|
+
<div class="badge-premium"><%= @post.category %></div>
|
|
4
|
+
<h1 class="text-3xl md:text-5xl font-black mb-4 leading-tight"><%= @post.title %></h1>
|
|
5
|
+
<p class="text-slate-500 dark:text-slate-400 text-sm md:text-base flex items-center gap-2">
|
|
6
|
+
<i class="far fa-calendar-alt"></i> Diterbitkan pada <%= @post.created_at.strftime('%d %B %Y') %>
|
|
7
|
+
</p>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<% if @post.image_url && !@post.image_url.empty? %>
|
|
11
|
-
<
|
|
11
|
+
<div class="relative rounded-2xl md:rounded-3xl overflow-hidden mb-8 md:mb-12 border border-white/10">
|
|
12
|
+
<img src="<%= @post.image_url %>" alt="<%= @post.title %>" class="w-full h-auto max-h-[500px] object-cover">
|
|
13
|
+
<div class="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent"></div>
|
|
14
|
+
</div>
|
|
12
15
|
<% end %>
|
|
13
16
|
|
|
14
17
|
<div class="post-content markdown-content">
|
|
@@ -16,8 +19,8 @@
|
|
|
16
19
|
</div>
|
|
17
20
|
|
|
18
21
|
<div class="mt-12 pt-8 border-t border-white/5">
|
|
19
|
-
<a href="/" class="btn-secondary">
|
|
20
|
-
<i class="fas fa-arrow-left mr-2"></i>
|
|
22
|
+
<a href="/" class="btn-secondary w-full sm:w-auto">
|
|
23
|
+
<i class="fas fa-arrow-left mr-2"></i> Kembali ke Blog
|
|
21
24
|
</a>
|
|
22
25
|
</div>
|
|
23
26
|
</div>
|
data/app/views/project.erb
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
<div class="glass-card anime-element
|
|
2
|
-
<div
|
|
3
|
-
<
|
|
4
|
-
<
|
|
1
|
+
<div class="glass-card anime-element !p-5 md:!p-12 text-left max-w-4xl mx-auto">
|
|
2
|
+
<div class="mb-8 md:mb-12">
|
|
3
|
+
<div class="badge-premium">Detail Proyek</div>
|
|
4
|
+
<h1 class="text-2xl md:text-5xl font-black mb-4 leading-tight"><%= @project.title %></h1>
|
|
5
|
+
<p class="text-slate-500 dark:text-slate-400 text-sm md:text-base flex items-center gap-2">
|
|
6
|
+
<i class="far fa-calendar-alt"></i> Dibuat pada <%= @project.created_at.strftime('%d %B %Y') %>
|
|
7
|
+
</p>
|
|
5
8
|
</div>
|
|
6
9
|
|
|
7
10
|
<% if @project.image_url && !@project.image_url.empty? %>
|
|
8
|
-
<
|
|
11
|
+
<div class="relative rounded-2xl md:rounded-3xl overflow-hidden mb-8 md:mb-12 border border-white/10">
|
|
12
|
+
<img src="<%= @project.image_url %>" alt="<%= @project.title %>" class="w-full h-auto max-h-[500px] object-cover">
|
|
13
|
+
<div class="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent"></div>
|
|
14
|
+
</div>
|
|
9
15
|
<% else %>
|
|
10
|
-
<div
|
|
11
|
-
|
|
16
|
+
<div class="w-full aspect-video bg-gradient-to-br from-primary/20 to-secondary/20 rounded-2xl md:rounded-3xl mb-8 md:mb-12 flex items-center justify-center border border-white/10">
|
|
17
|
+
<i class="fas fa-project-diagram text-4xl md:text-6xl text-primary/40"></i>
|
|
12
18
|
</div>
|
|
13
19
|
<% end %>
|
|
14
20
|
|
|
@@ -16,14 +22,14 @@
|
|
|
16
22
|
<%= markdown(@project.description) %>
|
|
17
23
|
</div>
|
|
18
24
|
|
|
19
|
-
<div class="flex flex-
|
|
25
|
+
<div class="flex flex-col sm:flex-row gap-4 items-center mt-12 pt-8 border-t border-white/5">
|
|
20
26
|
<% if @project.link && !@project.link.empty? %>
|
|
21
|
-
<a href="<%= @project.link %>" target="_blank" class="btn-premium">
|
|
22
|
-
<i class="fas fa-
|
|
27
|
+
<a href="<%= @project.link %>" target="_blank" class="btn-premium w-full sm:w-auto">
|
|
28
|
+
<i class="fas fa-external-link-alt mr-2"></i> Kunjungi Demo
|
|
23
29
|
</a>
|
|
24
30
|
<% end %>
|
|
25
|
-
<a href="/" class="btn-secondary">
|
|
26
|
-
<i class="fas fa-arrow-left mr-2"></i>
|
|
31
|
+
<a href="/" class="btn-secondary w-full sm:w-auto">
|
|
32
|
+
<i class="fas fa-arrow-left mr-2"></i> Kembali ke Beranda
|
|
27
33
|
</a>
|
|
28
34
|
</div>
|
|
29
35
|
</div>
|
data/bin/ofa
CHANGED
|
@@ -33,7 +33,7 @@ def help
|
|
|
33
33
|
puts " / __ \\/ ____/ / | "
|
|
34
34
|
puts " / / / / /_ / /| | Framework "
|
|
35
35
|
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
36
|
-
puts "\\____/_/ /_/ |_| v5.
|
|
36
|
+
puts "\\____/_/ /_/ |_| v5.3.0 "
|
|
37
37
|
puts " "
|
|
38
38
|
puts "✨ One-For-All Framework CLI ✨"
|
|
39
39
|
puts "-----------------------------"
|
|
@@ -249,7 +249,7 @@ when 'init'
|
|
|
249
249
|
puts " / __ \\/ ____/ / | "
|
|
250
250
|
puts " / / / / /_ / /| | Framework "
|
|
251
251
|
puts "/ /_/ / __/ / ___ | Premium MVC "
|
|
252
|
-
puts "\\____/_/ /_/ |_| v5.
|
|
252
|
+
puts "\\____/_/ /_/ |_| v5.3.0 "
|
|
253
253
|
puts " "
|
|
254
254
|
puts "Initializing One-For-All project as '#{app_type}' in #{PROJECT_ROOT}..."
|
|
255
255
|
|
|
@@ -307,6 +307,16 @@ when 'init'
|
|
|
307
307
|
end
|
|
308
308
|
end
|
|
309
309
|
|
|
310
|
+
# Standard project files
|
|
311
|
+
['.env.example', '.gitignore', 'Dockerfile', 'LICENSE', 'README.md', 'Procfile'].each do |file|
|
|
312
|
+
src = File.join(FRAMEWORK_ROOT, file)
|
|
313
|
+
dest = File.join(PROJECT_ROOT, file)
|
|
314
|
+
if File.exist?(src)
|
|
315
|
+
FileUtils.cp(src, dest)
|
|
316
|
+
puts " Creating #{file}..."
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
310
320
|
# Create .env
|
|
311
321
|
env_content = []
|
|
312
322
|
env_content << "EKS_ENV=development"
|
|
@@ -321,8 +331,16 @@ when 'init'
|
|
|
321
331
|
# Generated by One-For-All CLI
|
|
322
332
|
# Target framework: #{FRAMEWORK_ROOT}
|
|
323
333
|
|
|
324
|
-
# Add framework to load path
|
|
325
|
-
|
|
334
|
+
# Add framework to load path dynamically
|
|
335
|
+
require 'bundler/setup'
|
|
336
|
+
framework_spec = Gem.loaded_specs['one-for-all-framework']
|
|
337
|
+
if framework_spec
|
|
338
|
+
$LOAD_PATH.unshift framework_spec.full_gem_path
|
|
339
|
+
else
|
|
340
|
+
# Fallback for local development if not in bundle (though it should be)
|
|
341
|
+
$LOAD_PATH.unshift '#{FRAMEWORK_ROOT}'
|
|
342
|
+
end
|
|
343
|
+
|
|
326
344
|
APP_ROOT = File.expand_path(__dir__)
|
|
327
345
|
require 'config/boot'
|
|
328
346
|
|
|
@@ -347,6 +365,7 @@ when 'init'
|
|
|
347
365
|
gemfile_content = <<~RUBY
|
|
348
366
|
source 'https://rubygems.org'
|
|
349
367
|
|
|
368
|
+
gem 'one-for-all-framework', '5.3.0'
|
|
350
369
|
gem 'eks-cent', '4.0.0'
|
|
351
370
|
gem 'eksa-server'
|
|
352
371
|
gem 'sequel'
|
|
@@ -862,7 +881,7 @@ when 'console'
|
|
|
862
881
|
puts " / __ \\/ ____/ / | "
|
|
863
882
|
puts " / / / / /_ / /| | Framework "
|
|
864
883
|
puts "/ /_/ / __/ / ___ | Console (REPL) "
|
|
865
|
-
puts "\\____/_/ /_/ |_| v5.
|
|
884
|
+
puts "\\____/_/ /_/ |_| v5.3.0 "
|
|
866
885
|
puts " "
|
|
867
886
|
puts "✨ Loading environment... (Type 'exit' to quit)"
|
|
868
887
|
|
data/config/boot.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
Encoding.default_external = Encoding::UTF_8
|
|
2
|
+
Encoding.default_internal = Encoding::UTF_8
|
|
1
3
|
require 'bundler/setup'
|
|
2
4
|
require 'dotenv/load'
|
|
3
5
|
Bundler.require(:default)
|
|
@@ -40,7 +42,7 @@ module EksCent
|
|
|
40
42
|
raise "Template not found: #{template_path}"
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
template_content = File.read(template_path)
|
|
45
|
+
template_content = File.read(template_path, encoding: 'UTF-8')
|
|
44
46
|
context = Object.new
|
|
45
47
|
context.extend(ERB::Util)
|
|
46
48
|
req = @request
|
|
@@ -64,17 +66,20 @@ module EksCent
|
|
|
64
66
|
|
|
65
67
|
context.define_singleton_method(:session) { req ? (req.env['eks_cent.session'] || req.env['rack.session'] || {}) : {} }
|
|
66
68
|
context.define_singleton_method(:h) { |s| CGI.escapeHTML(s.to_s) }
|
|
67
|
-
locals.each
|
|
69
|
+
locals.each do |k, v|
|
|
70
|
+
v = v.force_encoding('UTF-8') if v.is_a?(String) && v.encoding == Encoding::BINARY
|
|
71
|
+
context.instance_variable_set("@#{k}", v)
|
|
72
|
+
end
|
|
68
73
|
|
|
69
|
-
result = ERB.new(template_content).result(context.instance_eval { binding })
|
|
74
|
+
result = ERB.new(template_content).result(context.instance_eval { binding }).force_encoding('UTF-8')
|
|
70
75
|
|
|
71
76
|
if layout
|
|
72
77
|
layout_name = layout == true ? 'layout' : layout.to_s
|
|
73
78
|
layout_path = File.join(APP_ROOT, 'app', 'views', "#{layout_name}.erb")
|
|
74
79
|
if File.file?(layout_path)
|
|
75
80
|
context.instance_variable_set("@content", result)
|
|
76
|
-
layout_content = File.read(layout_path)
|
|
77
|
-
result = ERB.new(layout_content).result(context.instance_eval { binding })
|
|
81
|
+
layout_content = File.read(layout_path, encoding: 'UTF-8')
|
|
82
|
+
result = ERB.new(layout_content).result(context.instance_eval { binding }).force_encoding('UTF-8')
|
|
78
83
|
end
|
|
79
84
|
end
|
|
80
85
|
|
data/config/features.json
CHANGED
data/db/data.sqlite3
CHANGED
|
Binary file
|