one-for-all-framework 2.0.0 → 4.0.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/README.md +136 -31
- data/app/controllers/cart_controller.rb +52 -0
- data/app/controllers/dashboard_controller.rb +2 -1
- data/app/controllers/products_controller.rb +58 -0
- data/app/models/product.rb +8 -0
- data/app/views/cart_index.erb +112 -0
- data/app/views/cms/pages_form.erb +1 -1
- data/app/views/cms/pages_index.erb +61 -45
- data/app/views/cms/posts_form.erb +1 -1
- data/app/views/cms/posts_index.erb +54 -41
- data/app/views/cms/products_form.erb +89 -0
- data/app/views/cms/products_index.erb +129 -0
- data/app/views/cms/projects_form.erb +8 -8
- data/app/views/cms/projects_index.erb +47 -37
- data/app/views/dashboard.erb +18 -0
- data/app/views/docs.erb +197 -49
- data/app/views/index.erb +1 -1
- data/app/views/layout.erb +26 -7
- data/app/views/product_show.erb +42 -0
- data/app/views/products_index.erb +40 -0
- data/bin/ofa +145 -6
- data/config/boot.rb +1 -0
- data/config/database.json +1 -2
- data/config/database.rb +16 -0
- data/config/routes.rb +32 -0
- data/db/data.sqlite3 +0 -0
- data/db/development.sqlite3 +0 -0
- data/db/migrations/20260502000000_create_products.rb +17 -0
- data/db/target.sqlite3 +0 -0
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6010d541e08ee4d29882dbfe22d000853a511985f01ee3b18239c57086bd7906
|
|
4
|
+
data.tar.gz: d05cdc0c5df07e8f2d9c0f3715169a3817b04b34da654dc63ce8afbaba0b932a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b84becdb59e23e5177cd6d61b33ae6875cbcdc00a1ab81fe98c4e644c97e1549950cdeef4da11758564cf316d009f69bd69b431d33852e9384383294126acbb
|
|
7
|
+
data.tar.gz: 45544d4a74c6636667674f46b3ede29f4bde1e7a5e14e3f0d9385450b03676e76c4f538169386255d8f5ed1a1c70f971f65c3a88bdb948b6ace802ca8bc05170
|
data/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
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
|
|
5
|
+
# ⚡ One-For-All (OFA) Framework v4.0.0
|
|
6
6
|
|
|
7
7
|
[](https://www.ruby-lang.org/)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[]()
|
|
10
10
|
|
|
11
|
-
**One-For-All (OFA)** is a premium, ultra-fast Ruby MVC framework designed for developers who value both high performance and modern aesthetics. Built on the powerful **Eks-Cent** engine and optimized with **Eksa Server**, OFA
|
|
11
|
+
**One-For-All (OFA)** is a premium, ultra-fast Ruby MVC framework designed for developers who value both high performance and modern aesthetics. Built on the powerful **Eks-Cent** engine and optimized with **Eksa Server**, OFA v3.0 now supports **Full E-Commerce integration** alongside its stunning "Glassmorphism" UI.
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
- **💎 Premium Aesthetics**: Beautiful Glassmorphism design system included by default with smooth dark/light mode transitions.
|
|
18
18
|
- **🚀 Blazing Fast**: Built on a modular Nio4r-powered engine for minimal overhead and instant boot times.
|
|
19
|
-
- **📂 Multi-Database**: Seamlessly switch between SQLite, MySQL, MariaDB, and MongoDB Atlas.
|
|
19
|
+
- **📂 Multi-Database**: Seamlessly switch and migrate data between SQLite, MySQL, MariaDB, and MongoDB Atlas.
|
|
20
20
|
- **🛠️ Developer First**: A robust CLI (`ofa`) that handles everything from scaffolding to deployment.
|
|
21
21
|
- **🔐 Enterprise Ready**: Built-in CSRF protection, secure session management, and input validation.
|
|
22
22
|
- **🌐 Global Support**: Multi-language (I18n) support and SEO optimization ready.
|
|
@@ -52,40 +52,117 @@ Your app is now live at `http://localhost:3000` ⚡
|
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
55
|
-
## 🛠️ CLI
|
|
55
|
+
## 🛠️ CLI Deep Dive & Expected Outputs
|
|
56
|
+
|
|
57
|
+
The `ofa` CLI is designed to be interactive and informative. Below is a detailed breakdown of all available commands and the visual feedback they provide.
|
|
58
|
+
|
|
59
|
+
### 📦 Project & Environment
|
|
60
|
+
#### `ofa init [TYPE]`
|
|
61
|
+
Initialize your workspace. Triggers an interactive wizard for Database and Storage setup.
|
|
62
|
+
* **Output Example:**
|
|
63
|
+
```text
|
|
64
|
+
🛠️ Project Configuration
|
|
65
|
+
💾 Choose Database [1. SQLite, 2. MongoDB Atlas]: 2
|
|
66
|
+
🔗 Enter MongoDB Connection String: mongodb+srv://...
|
|
67
|
+
🖼️ Choose Image Storage [1. Local, 2. Cloudinary]: 1
|
|
68
|
+
✅ Connection string saved to .env
|
|
69
|
+
✅ Project structure initialized.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### `ofa run`
|
|
73
|
+
Boots the high-performance Eksa Server engine.
|
|
74
|
+
* **Output Example:**
|
|
75
|
+
```text
|
|
76
|
+
Starting One-For-All server...
|
|
77
|
+
[INFO] Mendengarkan di TCP: 0.0.0.0:3000
|
|
78
|
+
[EksCent] 2026-05-03 14:40:52 | GET / | Status: 200
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 🏗️ Generators (Scaffolding)
|
|
82
|
+
#### `ofa g controller NAME`
|
|
83
|
+
* **Output:** `✅ Created app/controllers/blog_controller.rb`
|
|
84
|
+
#### `ofa g model NAME`
|
|
85
|
+
* **Output:** `✅ Created app/models/product.rb`
|
|
86
|
+
#### `ofa g post TITLE [args]`
|
|
87
|
+
Creates a SEO-optimized blog post with metadata.
|
|
88
|
+
* **Example:** `./ofa g post "Hello World" --author Antigravity`
|
|
89
|
+
* **Output:** `✅ Created app/views/posts/hello_world.erb`
|
|
90
|
+
|
|
91
|
+
### 📂 Database Management
|
|
92
|
+
#### `ofa db switch TYPE [URL]`
|
|
93
|
+
Switches your database adapter on the fly.
|
|
94
|
+
* **Output:** `Switched to mongodb mode.`
|
|
95
|
+
|
|
96
|
+
#### `ofa db migrate-data TYPE [URL]`
|
|
97
|
+
**The most powerful tool.** Moves all data from your current DB to a new one (SQL or MongoDB Atlas).
|
|
98
|
+
* **Output Example:**
|
|
99
|
+
```text
|
|
100
|
+
📦 Starting data migration: sqlite -> mongodb...
|
|
101
|
+
Migrating users... 12 rows.
|
|
102
|
+
Migrating posts... 45 rows.
|
|
103
|
+
✅ Migration successful!
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### `ofa db migrate` (or `ofa migrate`)
|
|
107
|
+
Runs pending database migrations in `db/migrations/`.
|
|
108
|
+
* **Output:** `✅ Migrations completed.`
|
|
109
|
+
|
|
110
|
+
### 🎨 Customization & Security
|
|
111
|
+
#### `ofa theme NAME`
|
|
112
|
+
Changes the entire UI skin (Modern Glass, Retro, Cyber).
|
|
113
|
+
* **Output:** `✅ Theme set to: retro_terminal`
|
|
114
|
+
|
|
115
|
+
#### `ofa storage NAME`
|
|
116
|
+
Switches between local disk and Cloudinary cloud storage.
|
|
117
|
+
* **Output:** `✅ Storage set to: cloudinary`
|
|
118
|
+
|
|
119
|
+
#### `ofa reset-password USR PWD`
|
|
120
|
+
Securely manages admin credentials.
|
|
121
|
+
* **Output:** `✅ Password for 'admin' updated successfully.`
|
|
56
122
|
|
|
57
|
-
|
|
123
|
+
---
|
|
58
124
|
|
|
59
|
-
### Project
|
|
125
|
+
### 📁 Project Lifecycle
|
|
60
126
|
| Command | Description |
|
|
61
127
|
| :--- | :--- |
|
|
62
|
-
| `ofa new NAME [TYPE]` | Create a new project
|
|
63
|
-
| `ofa init [TYPE]` | Initialize
|
|
64
|
-
| `ofa run` | Start the high-performance
|
|
65
|
-
| `ofa deploy` |
|
|
128
|
+
| `ofa new NAME [TYPE]` | **Create a new project.** Generates a new directory, initializes the framework structure, and automatically runs `bundle install`. <br> *Example:* `./ofa new my_portfolio portfolio` |
|
|
129
|
+
| `ofa init [TYPE]` | **Initialize in current folder.** Ideal if you've already created a folder or cloned a repository. It triggers an **Interactive Wizard** to configure your Database (SQLite/MongoDB) and Image Storage (Local/Cloudinary). |
|
|
130
|
+
| `ofa run` | **Start Development Server.** Boots the high-performance Eksa Server. Your app will be accessible at `http://localhost:3000`. |
|
|
131
|
+
| `ofa deploy` | **Production Deployment.** Automatically detects deployment targets. <br> 1. Checks if it's a Git repository. <br> 2. Detects **Railway CLI** and triggers `railway up`. <br> 3. Supports Docker via the included `Dockerfile`. |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
### 🏗️ Scaffolding & Generators (`ofa g`)
|
|
136
|
+
Automate the creation of boilerplate code with the generator command.
|
|
66
137
|
|
|
67
|
-
### Generators (Scaffolding)
|
|
68
138
|
| Command | Description |
|
|
69
139
|
| :--- | :--- |
|
|
70
|
-
| `ofa g controller NAME` |
|
|
71
|
-
| `ofa g model NAME` |
|
|
72
|
-
| `ofa g migration NAME` |
|
|
73
|
-
| `ofa g post TITLE` |
|
|
140
|
+
| `ofa g controller NAME` | Creates a new controller in `app/controllers/{name}_controller.rb` with a default `index` action. |
|
|
141
|
+
| `ofa g model NAME` | Generates a database model in `app/models/{name}.rb` integrated with the Sequel ORM. |
|
|
142
|
+
| `ofa g migration NAME` | Creates a timestamped migration file in `db/migrations/`. Use this to define your schema changes. |
|
|
143
|
+
| `ofa g post TITLE` | Creates a new Markdown/ERB post in `app/views/posts/`. <br> *Args:* `--category`, `--author`, `--image`. <br> *Example:* `./ofa g post "My First Journey" --category Tech --author "John Doe"` |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### 🎨 Configuration & Customization
|
|
148
|
+
Fine-tune your application's behavior and appearance without touching the code.
|
|
74
149
|
|
|
75
|
-
### Configuration & Features
|
|
76
150
|
| Command | Description |
|
|
77
151
|
| :--- | :--- |
|
|
78
|
-
| `ofa type
|
|
79
|
-
| `ofa theme
|
|
80
|
-
| `ofa feature
|
|
81
|
-
| `ofa storage
|
|
82
|
-
|
|
152
|
+
| `ofa type NAME` | **Set Application Type.** Switches the layout logic between `portfolio`, `blog`, `landing_page`, and `e_commerce`. |
|
|
153
|
+
| `ofa theme NAME` | **Change UI Aesthetic.** Instantly swap between premium themes: <br> • `light_glass` / `dark_glass` (Modern Glassmorphism) <br> • `cyber_sidebar` (High-tech) <br> • `retro_terminal` (Old-school hacker vibe) <br> • `light_sidebar` (Professional/Clean) |
|
|
154
|
+
| `ofa feature ACTION FEATURE`| **Toggle Core Features.** Enable or disable system modules. <br> *Usage:* `./ofa feature enable auth` or `./ofa feature disable cms`. |
|
|
155
|
+
| `ofa storage NAME` | **Set Media Storage.** Choose between `local` (uploads folder) or `cloudinary` (Cloud storage). |
|
|
156
|
+
|
|
157
|
+
---
|
|
83
158
|
|
|
84
|
-
### Database
|
|
159
|
+
### 🔐 Security & Database
|
|
85
160
|
| Command | Description |
|
|
86
161
|
| :--- | :--- |
|
|
87
|
-
| `ofa
|
|
88
|
-
| `ofa db migrate
|
|
162
|
+
| `ofa reset-password USR PWD`| **Enterprise Recovery.** Resets admin credentials with strict enforcement: 8+ chars, uppercase, and numbers. Powered by BCrypt hashing. |
|
|
163
|
+
| `ofa db migrate-data TYPE [NAME]`| **Zero-Loss Migration.** The ultimate tool to move data (Users, Posts, Products) from SQLite to MongoDB Atlas or other SQL DBs without losing a single record. |
|
|
164
|
+
| `ofa db switch ADAPTER` | **Hot-swap Engine.** Instantly change your database adapter. Supports `sqlite`, `mysql`, `mariadb`, `postgres`, and `mongodb`. |
|
|
165
|
+
| `ofa db migrate` | **Schema Evolution.** Runs all pending migrations. OFA automatically handles table creation for core models during boot for maximum reliability. |
|
|
89
166
|
|
|
90
167
|
---
|
|
91
168
|
|
|
@@ -100,13 +177,41 @@ OFA follows a strict **MVC (Model-View-Controller)** pattern:
|
|
|
100
177
|
|
|
101
178
|
---
|
|
102
179
|
|
|
103
|
-
## 🚢 Deployment
|
|
104
|
-
|
|
105
|
-
One-For-All is
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
180
|
+
## 🚢 Deployment Guide
|
|
181
|
+
|
|
182
|
+
One-For-All is designed to be cloud-native and "deploy-ready" from day one.
|
|
183
|
+
|
|
184
|
+
### 🚂 Railway (Recommended)
|
|
185
|
+
Railway is the easiest way to get your OFA app live. The framework includes a `Procfile` that Railway detects automatically.
|
|
186
|
+
1. Install [Railway CLI](https://docs.railway.app/guides/cli).
|
|
187
|
+
2. Run `railway login`.
|
|
188
|
+
3. In your project folder, run:
|
|
189
|
+
```bash
|
|
190
|
+
./ofa deploy
|
|
191
|
+
```
|
|
192
|
+
*The CLI will automatically trigger `railway up` and handle the build process.*
|
|
193
|
+
|
|
194
|
+
### 🐳 Docker
|
|
195
|
+
For customized hosting or VPS providers, use the optimized `Dockerfile`.
|
|
196
|
+
1. **Build the image**:
|
|
197
|
+
```bash
|
|
198
|
+
docker build -t my-ofa-app .
|
|
199
|
+
```
|
|
200
|
+
2. **Run the container**:
|
|
201
|
+
```bash
|
|
202
|
+
docker run -p 3000:3000 --env-file .env my-ofa-app
|
|
203
|
+
```
|
|
204
|
+
*Note: Ensure your `.env` contains production-ready database credentials.*
|
|
205
|
+
|
|
206
|
+
### 🖥️ VPS (DigitalOcean, Linode, AWS)
|
|
207
|
+
To run OFA on a raw Linux server:
|
|
208
|
+
1. **Setup**: Clone your repo and run `bundle install --deployment`.
|
|
209
|
+
2. **Database**: Run `./ofa db migrate` to sync your production schema.
|
|
210
|
+
3. **Process Management**: Use [PM2](https://pm2.keymetrics.io/) to keep the server alive:
|
|
211
|
+
```bash
|
|
212
|
+
pm2 start "./ofa run" --name ofa-app
|
|
213
|
+
```
|
|
214
|
+
4. **Reverse Proxy**: We recommend using **Nginx** as a reverse proxy to handle SSL and port 80/443 forwarding to port 3000.
|
|
110
215
|
|
|
111
216
|
---
|
|
112
217
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require_relative 'application_controller'
|
|
2
|
+
|
|
3
|
+
class CartController < ApplicationController
|
|
4
|
+
def index
|
|
5
|
+
@cart_items = session['cart'] || {}
|
|
6
|
+
# Convert string keys to integers for the query
|
|
7
|
+
@products = Product.where(id: @cart_items.keys.map(&:to_i)).all
|
|
8
|
+
@total = @products.sum { |p| p.price * @cart_items[p.id.to_s].to_i }
|
|
9
|
+
render 'cart_index', title: "Your Cart - One-For-All"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def add
|
|
13
|
+
product_id = params['product_id']
|
|
14
|
+
qty = params['qty'].to_i
|
|
15
|
+
qty = 1 if qty <= 0
|
|
16
|
+
|
|
17
|
+
cart = session['cart'] || {}
|
|
18
|
+
cart[product_id] = (cart[product_id].to_i + qty)
|
|
19
|
+
session['cart'] = cart
|
|
20
|
+
|
|
21
|
+
redirect_to '/cart'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def remove
|
|
25
|
+
product_id = params['product_id']
|
|
26
|
+
cart = session['cart'] || {}
|
|
27
|
+
cart.delete(product_id)
|
|
28
|
+
session['cart'] = cart
|
|
29
|
+
|
|
30
|
+
redirect_to '/cart'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update
|
|
34
|
+
product_id = params['product_id']
|
|
35
|
+
qty = params['qty'].to_i
|
|
36
|
+
|
|
37
|
+
cart = session['cart'] || {}
|
|
38
|
+
if qty <= 0
|
|
39
|
+
cart.delete(product_id)
|
|
40
|
+
else
|
|
41
|
+
cart[product_id] = qty
|
|
42
|
+
end
|
|
43
|
+
session['cart'] = cart
|
|
44
|
+
|
|
45
|
+
redirect_to '/cart'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def clear
|
|
49
|
+
session['cart'] = {}
|
|
50
|
+
redirect_to '/cart'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require_relative 'application_controller'
|
|
2
|
+
|
|
3
|
+
class ProductsController < ApplicationController
|
|
4
|
+
def index
|
|
5
|
+
if req.path.start_with?('/dashboard')
|
|
6
|
+
@products = Product.order(Sequel.desc(:created_at)).all
|
|
7
|
+
render 'cms/products_index'
|
|
8
|
+
else
|
|
9
|
+
@products = Product.where(is_active: true).order(Sequel.desc(:created_at)).all
|
|
10
|
+
render 'products_index', title: "Shop - One-For-All"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def new
|
|
15
|
+
@product = Product.new
|
|
16
|
+
render 'cms/products_form'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create
|
|
20
|
+
data = params['product']
|
|
21
|
+
@product = Product.new(data)
|
|
22
|
+
if @product.save
|
|
23
|
+
redirect_to '/dashboard/products'
|
|
24
|
+
else
|
|
25
|
+
render 'cms/products_form'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def edit
|
|
30
|
+
@product = Product[params['id']]
|
|
31
|
+
render 'cms/products_form'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def update
|
|
35
|
+
@product = Product[params['id']]
|
|
36
|
+
if @product.update(params['product'])
|
|
37
|
+
redirect_to '/dashboard/products'
|
|
38
|
+
else
|
|
39
|
+
render 'cms/products_form'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def destroy
|
|
44
|
+
@product = Product[params['id']]
|
|
45
|
+
@product.destroy
|
|
46
|
+
redirect_to '/dashboard/products'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def show
|
|
50
|
+
@product = Product.find(slug: params['slug'], is_active: true)
|
|
51
|
+
if @product
|
|
52
|
+
render 'product_show', title: "#{@product.name} - One-For-All"
|
|
53
|
+
else
|
|
54
|
+
@res.status = 404
|
|
55
|
+
render 'error', message: "Product not found."
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<div class="text-center mb-16">
|
|
2
|
+
<h1 class="text-5xl font-black mb-4 tracking-tighter">Your Shopping Cart</h1>
|
|
3
|
+
<p class="text-slate-500 text-lg">Review your items before checkout.</p>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<div class="glass-panel overflow-hidden mb-10">
|
|
7
|
+
<!-- Desktop Header (Hidden on Mobile) -->
|
|
8
|
+
<div class="hidden md:grid grid-cols-12 bg-white/5 border-b border-white/5 text-slate-400 font-bold uppercase text-xs tracking-widest px-8 py-4">
|
|
9
|
+
<div class="col-span-5">Product</div>
|
|
10
|
+
<div class="col-span-2">Price</div>
|
|
11
|
+
<div class="col-span-2 text-center">Quantity</div>
|
|
12
|
+
<div class="col-span-2 text-right">Subtotal</div>
|
|
13
|
+
<div class="col-span-1"></div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="divide-y divide-white/5">
|
|
17
|
+
<% @products.each do |product| %>
|
|
18
|
+
<% qty = @cart_items[product.id.to_s].to_i %>
|
|
19
|
+
<div class="grid grid-cols-1 md:grid-cols-12 items-center gap-4 md:gap-0 p-6 md:px-8 md:py-6 hover:bg-white/[0.02] transition-colors relative group">
|
|
20
|
+
<!-- Product Info -->
|
|
21
|
+
<div class="col-span-1 md:col-span-5">
|
|
22
|
+
<div class="flex items-center gap-4">
|
|
23
|
+
<div class="w-16 h-16 md:w-20 md:h-20 rounded-2xl overflow-hidden bg-slate-900/50 flex-shrink-0">
|
|
24
|
+
<img src="<%= product.image_url || '/images/logo.png' %>" class="w-full h-full object-cover">
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
<div class="font-black text-lg md:text-base"><%= product.name %></div>
|
|
28
|
+
<div class="text-xs text-slate-500 uppercase tracking-tighter"><%= product.category %></div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Price (Label on Mobile) -->
|
|
34
|
+
<div class="col-span-1 md:col-span-2 flex justify-between md:block items-center">
|
|
35
|
+
<span class="md:hidden text-slate-500 text-sm font-bold uppercase tracking-widest">Price</span>
|
|
36
|
+
<span class="font-mono text-slate-400">$<%= product.price %></span>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<!-- Quantity -->
|
|
40
|
+
<div class="col-span-1 md:col-span-2 flex justify-between md:block items-center">
|
|
41
|
+
<span class="md:hidden text-slate-500 text-sm font-bold uppercase tracking-widest">Quantity</span>
|
|
42
|
+
<div class="flex items-center justify-center gap-2">
|
|
43
|
+
<form action="/cart/update" method="POST" class="inline">
|
|
44
|
+
<%= csrf_tag %>
|
|
45
|
+
<input type="hidden" name="product_id" value="<%= product.id %>">
|
|
46
|
+
<input type="hidden" name="qty" value="<%= qty - 1 %>">
|
|
47
|
+
<button type="submit" class="w-10 h-10 md:w-8 md:h-8 rounded-xl bg-white/5 hover:bg-red-500/10 hover:text-red-500 transition-all flex items-center justify-center">
|
|
48
|
+
<i class="fas fa-minus text-xs"></i>
|
|
49
|
+
</button>
|
|
50
|
+
</form>
|
|
51
|
+
<span class="w-8 text-center font-black text-lg md:text-base"><%= qty %></span>
|
|
52
|
+
<form action="/cart/update" method="POST" class="inline">
|
|
53
|
+
<%= csrf_tag %>
|
|
54
|
+
<input type="hidden" name="product_id" value="<%= product.id %>">
|
|
55
|
+
<input type="hidden" name="qty" value="<%= qty + 1 %>">
|
|
56
|
+
<button type="submit" class="w-10 h-10 md:w-8 md:h-8 rounded-xl bg-white/5 hover:bg-primary/10 hover:text-primary transition-all flex items-center justify-center">
|
|
57
|
+
<i class="fas fa-plus text-xs"></i>
|
|
58
|
+
</button>
|
|
59
|
+
</form>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- Subtotal -->
|
|
64
|
+
<div class="col-span-1 md:col-span-2 flex justify-between md:block items-center border-t md:border-none border-white/5 pt-4 md:pt-0">
|
|
65
|
+
<span class="md:hidden text-slate-500 text-sm font-bold uppercase tracking-widest">Subtotal</span>
|
|
66
|
+
<span class="text-xl md:text-lg md:text-right block font-black text-primary">$<%= product.price * qty %></span>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<!-- Remove Btn -->
|
|
70
|
+
<div class="absolute top-6 right-6 md:static md:col-span-1 md:text-right">
|
|
71
|
+
<form action="/cart/remove" method="POST">
|
|
72
|
+
<%= csrf_tag %>
|
|
73
|
+
<input type="hidden" name="product_id" value="<%= product.id %>">
|
|
74
|
+
<button type="submit" class="w-10 h-10 md:w-auto flex items-center justify-center md:inline text-slate-400 hover:text-red-500 transition-colors">
|
|
75
|
+
<i class="fas fa-trash-alt"></i>
|
|
76
|
+
</button>
|
|
77
|
+
</form>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
|
|
82
|
+
<% if @products.empty? %>
|
|
83
|
+
<div class="px-8 py-20 text-center">
|
|
84
|
+
<div class="w-20 h-20 bg-slate-900/20 rounded-full flex items-center justify-center mx-auto mb-6">
|
|
85
|
+
<i class="fas fa-shopping-basket text-4xl text-slate-700"></i>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="text-slate-500 italic text-lg">Your cart is empty.</div>
|
|
88
|
+
<a href="/" class="btn-premium inline-flex mt-6 px-8">Start Shopping</a>
|
|
89
|
+
</div>
|
|
90
|
+
<% end %>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<% unless @products.empty? %>
|
|
95
|
+
<div class="flex flex-col md:flex-row justify-between items-end gap-8">
|
|
96
|
+
<form action="/cart/clear" method="POST">
|
|
97
|
+
<%= csrf_tag %>
|
|
98
|
+
<button type="submit" class="btn-secondary">Clear Cart</button>
|
|
99
|
+
</form>
|
|
100
|
+
|
|
101
|
+
<div class="glass-card w-full md:w-96 !p-8">
|
|
102
|
+
<div class="flex justify-between items-center mb-6">
|
|
103
|
+
<span class="text-slate-500 font-bold">Total Amount</span>
|
|
104
|
+
<span class="text-3xl font-black text-primary">$<%= @total %></span>
|
|
105
|
+
</div>
|
|
106
|
+
<button class="btn-premium w-full !py-5 !text-xl">
|
|
107
|
+
Proceed to Checkout <i class="fas fa-arrow-right ml-2"></i>
|
|
108
|
+
</button>
|
|
109
|
+
<p class="text-[10px] text-center text-slate-600 mt-4 uppercase tracking-widest font-black">Secure Checkout Powered by OFA.SYS</p>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
<% end %>
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
</aside>
|
|
45
45
|
|
|
46
46
|
<!-- Main Content -->
|
|
47
|
-
<div class="flex-1 space-y-6
|
|
47
|
+
<div class="flex-1 space-y-6 pb-20 min-w-0">
|
|
48
48
|
<div class="flex justify-between items-center bg-white/5 p-6 rounded-3xl border border-white/5">
|
|
49
49
|
<div>
|
|
50
50
|
<h2 class="text-2xl font-black tracking-tight"><%= @page.id ? 'Edit' : 'Add' %> Page</h2>
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
<i class="fas fa-palette w-5"></i>
|
|
30
30
|
<span class="font-semibold">Projects</span>
|
|
31
31
|
</a>
|
|
32
|
+
<% elsif FEATURES_CONFIG['type'] == 'e_commerce' %>
|
|
33
|
+
<a href="/dashboard/products" class="flex items-center gap-3 px-4 py-3 rounded-2xl transition-all duration-300 <%= @req.path.start_with?('/dashboard/products') ? 'bg-primary text-white shadow-lg shadow-primary/30' : 'text-slate-500 hover:bg-white/5' %>">
|
|
34
|
+
<i class="fas fa-tag w-5"></i>
|
|
35
|
+
<span class="font-semibold">Products</span>
|
|
36
|
+
</a>
|
|
32
37
|
<% end %>
|
|
33
38
|
|
|
34
39
|
<div class="h-px bg-white/5 my-4"></div>
|
|
@@ -45,60 +50,71 @@
|
|
|
45
50
|
|
|
46
51
|
<!-- Main Content -->
|
|
47
52
|
<div class="flex-1 space-y-6 w-full">
|
|
48
|
-
<div class="flex justify-between items-center bg-white/5 p-6 rounded-3xl border border-white/5">
|
|
53
|
+
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-white/5 p-6 rounded-3xl border border-white/5">
|
|
49
54
|
<div>
|
|
50
55
|
<h2 class="text-2xl font-black tracking-tight">Page Management</h2>
|
|
51
56
|
<p class="text-slate-500 text-sm">Manage all static pages of your website.</p>
|
|
52
57
|
</div>
|
|
53
|
-
<a href="/dashboard/pages/new" class="btn-premium">
|
|
58
|
+
<a href="/dashboard/pages/new" class="btn-premium w-full sm:w-auto text-center">
|
|
54
59
|
<i class="fas fa-plus mr-2 text-sm"></i> New Page
|
|
55
60
|
</a>
|
|
56
61
|
</div>
|
|
57
62
|
|
|
58
63
|
<div class="glass-panel overflow-hidden border-none shadow-xl">
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
</
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
64
|
+
<!-- Header (Hidden on Mobile) -->
|
|
65
|
+
<div class="hidden md:grid grid-cols-12 bg-white/5 border-b border-white/5 text-slate-400 font-bold uppercase text-[10px] tracking-[0.2em] px-8 py-4">
|
|
66
|
+
<div class="col-span-5">Page Title</div>
|
|
67
|
+
<div class="col-span-3">Slug</div>
|
|
68
|
+
<div class="col-span-2">Created</div>
|
|
69
|
+
<div class="col-span-2 text-right">Actions</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="divide-y divide-white/5">
|
|
73
|
+
<% if @pages.empty? %>
|
|
74
|
+
<div class="px-8 py-20 text-center">
|
|
75
|
+
<div class="w-16 h-16 bg-white/5 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
76
|
+
<i class="fas fa-file-circle-plus text-2xl text-slate-600"></i>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="text-slate-500 italic">No pages created yet.</div>
|
|
79
|
+
</div>
|
|
80
|
+
<% end %>
|
|
81
|
+
<% @pages.each do |page| %>
|
|
82
|
+
<div class="grid grid-cols-1 md:grid-cols-12 items-center gap-4 md:gap-0 p-6 md:px-8 md:py-5 hover:bg-white/[0.02] transition-colors relative group">
|
|
83
|
+
<!-- Title -->
|
|
84
|
+
<div class="col-span-1 md:col-span-5">
|
|
85
|
+
<div class="flex flex-col">
|
|
86
|
+
<span class="font-black text-slate-700 dark:text-slate-200 group-hover:text-primary transition-colors text-lg md:text-base"><%= page.title %></span>
|
|
87
|
+
<span class="md:hidden text-[10px] text-slate-500 uppercase tracking-widest font-black mt-1">Page Title</span>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<!-- Slug -->
|
|
92
|
+
<div class="col-span-1 md:col-span-3 flex flex-col">
|
|
93
|
+
<code class="text-xs bg-primary/10 px-3 py-1.5 rounded-xl text-primary w-fit font-bold font-mono">/<%= page.slug %></code>
|
|
94
|
+
<span class="md:hidden text-[10px] text-slate-500 uppercase tracking-widest font-black mt-2">Route Slug</span>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<!-- Date -->
|
|
98
|
+
<div class="col-span-1 md:col-span-2 flex flex-col">
|
|
99
|
+
<span class="text-sm text-slate-500 font-bold"><%= page.created_at.strftime('%d %b %Y') %></span>
|
|
100
|
+
<span class="md:hidden text-[10px] text-slate-500 uppercase tracking-widest font-black mt-1">Creation Date</span>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!-- Actions -->
|
|
104
|
+
<div class="col-span-1 md:col-span-2 flex justify-end gap-2 pt-4 md:pt-0 border-t md:border-none border-white/5">
|
|
105
|
+
<a href="/dashboard/pages/<%= page.id %>/edit" class="w-10 h-10 flex items-center justify-center rounded-2xl bg-primary/10 text-primary hover:bg-primary hover:text-white transition-all shadow-lg shadow-primary/10">
|
|
106
|
+
<i class="fas fa-edit text-sm"></i>
|
|
107
|
+
</a>
|
|
108
|
+
<form action="/dashboard/pages/<%= page.id %>/delete" method="POST" onsubmit="return confirm('Delete this page?')" class="inline">
|
|
109
|
+
<%= csrf_tag %>
|
|
110
|
+
<button type="submit" class="w-10 h-10 flex items-center justify-center rounded-2xl bg-red-500/10 text-red-500 hover:bg-red-500 hover:text-white transition-all shadow-lg shadow-red-500/10">
|
|
111
|
+
<i class="fas fa-trash-can text-sm"></i>
|
|
112
|
+
</button>
|
|
113
|
+
</form>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
<% end %>
|
|
117
|
+
</div>
|
|
102
118
|
</div>
|
|
103
119
|
</div>
|
|
104
120
|
</div>
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
</aside>
|
|
45
45
|
|
|
46
46
|
<!-- Main Content -->
|
|
47
|
-
<div class="flex-1 space-y-6
|
|
47
|
+
<div class="flex-1 space-y-6 pb-20 min-w-0">
|
|
48
48
|
<div class="flex justify-between items-center bg-white/5 p-6 rounded-3xl border border-white/5">
|
|
49
49
|
<div>
|
|
50
50
|
<h2 class="text-2xl font-black tracking-tight"><%= @post.id ? 'Edit' : 'Add' %> Article</h2>
|