diff --git a/PKPL_Individu_2306240124_NairaShafiqaAfiany/settings.py b/PKPL_Individu_2306240124_NairaShafiqaAfiany/settings.py
index 18cf220a9e0d7cff697f773ad80fa31984d6ce64..ee4f0f1fd07bb28aad53659ae1c57f5f4be41ef9 100644
--- a/PKPL_Individu_2306240124_NairaShafiqaAfiany/settings.py
+++ b/PKPL_Individu_2306240124_NairaShafiqaAfiany/settings.py
@@ -37,6 +37,7 @@ INSTALLED_APPS = [
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
+    'main'
 ]
 
 MIDDLEWARE = [
@@ -121,3 +122,4 @@ STATIC_URL = 'static/'
 # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
 
 DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+AUTH_USER_MODEL = 'main.User'
diff --git a/PKPL_Individu_2306240124_NairaShafiqaAfiany/urls.py b/PKPL_Individu_2306240124_NairaShafiqaAfiany/urls.py
index ddd50969980ce513ff1e4bbdc61909d920c4f34d..f6c93c5d7f151a02efe865474b402c7e24f8718a 100644
--- a/PKPL_Individu_2306240124_NairaShafiqaAfiany/urls.py
+++ b/PKPL_Individu_2306240124_NairaShafiqaAfiany/urls.py
@@ -15,8 +15,10 @@ Including another URLconf
     2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
 """
 from django.contrib import admin
-from django.urls import path
+from django.urls import path, include
 
 urlpatterns = [
     path('admin/', admin.site.urls),
+    path('', include('main.urls')),
+
 ]
diff --git a/main/__init__.py b/main/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/main/admin.py b/main/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e
--- /dev/null
+++ b/main/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/main/apps.py b/main/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..167f04426e4e30b75dadbbe56ff974baff39b71a
--- /dev/null
+++ b/main/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class MainConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'main'
diff --git a/main/forms.py b/main/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff84b7fc1c5dc88d0e0d50fc5bddddd6f90a0073
--- /dev/null
+++ b/main/forms.py
@@ -0,0 +1,19 @@
+from django import forms
+from .models import User, Transportasi
+
+class UserForm(forms.ModelForm):
+    class Meta:
+        model = User
+        fields = ['username', 'password', 'birthdate', 'phone_number', 'email', 'blog_url', 'bio']
+        widgets = {
+            'password': forms.PasswordInput(),
+            'birthdate': forms.DateInput(attrs={'type': 'date'}),
+        }
+
+class TransportasiForm(forms.ModelForm):
+    class Meta:
+        model = Transportasi
+        fields = ['lokasi_duduk', 'nomor_rangka']
+        widgets = {
+            'lokasi_duduk': forms.Select(),  # ✅ Supaya bisa pilih dropdown
+        }
diff --git a/main/migrations/0001_initial.py b/main/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d8d8f31ca8997f1c56341aa0f51a8cea9b56942
--- /dev/null
+++ b/main/migrations/0001_initial.py
@@ -0,0 +1,48 @@
+# Generated by Django 5.1.7 on 2025-03-16 07:24
+
+import django.contrib.auth.models
+import django.utils.timezone
+import main.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        ('auth', '0012_alter_user_first_name_max_length'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='User',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
+                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
+                ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
+                ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
+                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
+                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
+                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
+                ('username', models.CharField(max_length=255, unique=True, validators=[main.models.validate_username])),
+                ('password', models.CharField(max_length=255, validators=[main.models.validate_password])),
+                ('birthdate', models.DateField(validators=[main.models.validate_birthdate])),
+                ('phone_number', models.CharField(max_length=15, validators=[main.models.validate_phone])),
+                ('email', models.EmailField(max_length=254, unique=True)),
+                ('blog_url', models.URLField()),
+                ('bio', models.TextField(blank=True, max_length=1000)),
+                ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
+                ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
+            ],
+            options={
+                'verbose_name': 'user',
+                'verbose_name_plural': 'users',
+                'abstract': False,
+            },
+            managers=[
+                ('objects', django.contrib.auth.models.UserManager()),
+            ],
+        ),
+    ]
diff --git a/main/migrations/0002_transportasi.py b/main/migrations/0002_transportasi.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1780bce5a14f570455f2e5bf0d363e53702e9f0
--- /dev/null
+++ b/main/migrations/0002_transportasi.py
@@ -0,0 +1,21 @@
+# Generated by Django 5.1.7 on 2025-03-16 07:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('main', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Transportasi',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('lokasi_duduk', models.CharField(choices=[('Jendela', 'Jendela'), ('Tengah', 'Tengah'), ('Gang', 'Gang')], max_length=10, verbose_name='Lokasi Duduk')),
+                ('nomor_rangka', models.CharField(max_length=15, unique=True, verbose_name='Nomor Rangka Kendaraan')),
+            ],
+        ),
+    ]
diff --git a/main/migrations/__init__.py b/main/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/main/models.py b/main/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..0539da6e010b392a8928be21950a8dc6b3dd33ea
--- /dev/null
+++ b/main/models.py
@@ -0,0 +1,63 @@
+from django.contrib.auth.models import AbstractUser
+from django.db import models
+import re
+from django.core.exceptions import ValidationError
+from datetime import date, timedelta
+
+def validate_username(value):
+    if not re.match(r'^[a-zA-Z0-9._-]+$', value):
+        raise ValidationError("Username hanya boleh berisi huruf, angka, '.', '_', atau '-'.")
+    
+def validate_password(value):
+    if len(value) < 8 or not re.search(r'[\W_]', value):
+        raise ValidationError("Password harus minimal 8 karakter dan mengandung karakter spesial.")
+
+def validate_birthdate(value):
+    min_birthdate = date.today() - timedelta(days=12*365)
+    if value > min_birthdate:
+        raise ValidationError("Usia minimal adalah 12 tahun.")
+
+def validate_phone(value):
+    if not re.match(r'^62[0-9]{6,13}$', value):
+        raise ValidationError("Nomor HP harus dalam format 62xxxxxxxx dan panjangnya 8-15 angka.")
+
+def validate_nomor_rangka(value):
+    if not re.match(r'^[A-Za-z]{5}[A-Za-z0-9]{10}$', value):
+        raise ValidationError("Nomor rangka harus diawali 5 huruf, lalu 10 karakter alfanumerik.")
+
+# MODEL USER
+class User(AbstractUser):
+    username = models.CharField(max_length=255, unique=True, validators=[validate_username])
+    password = models.CharField(max_length=255, validators=[validate_password])
+    birthdate = models.DateField(validators=[validate_birthdate])
+    phone_number = models.CharField(max_length=15, validators=[validate_phone])
+    email = models.EmailField(unique=True)
+    blog_url = models.URLField()
+    bio = models.TextField(max_length=1000, blank=True)
+
+    def __str__(self):
+        return self.username
+
+# MODEL TRANSPORTASI
+class Transportasi(models.Model):
+    LOKASI_DUDUK_CHOICES = [
+        ('Jendela', 'Jendela'),
+        ('Tengah', 'Tengah'),
+        ('Gang', 'Gang'),
+    ]
+
+    lokasi_duduk = models.CharField(
+        max_length=10, 
+        choices=LOKASI_DUDUK_CHOICES,
+        verbose_name="Lokasi Duduk"
+    )
+
+    nomor_rangka = models.CharField(
+        max_length=15, 
+        unique=True, 
+        validators=[validate_nomor_rangka],  
+        verbose_name="Nomor Rangka Kendaraan"
+    )
+
+    def __str__(self):
+        return f"{self.nomor_rangka} - {self.lokasi_duduk}"
diff --git a/main/templates/main.html b/main/templates/main.html
new file mode 100644
index 0000000000000000000000000000000000000000..12a45f89cb4f2e695ef6956cc051c9d1dd6a93d1
--- /dev/null
+++ b/main/templates/main.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="id">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Form User & Transportasi</title>
+</head>
+<body>
+    <h2><strong>Form User</strong></h2>
+    <form method="post">
+        {% csrf_token %}
+        
+        {{ user_form.as_p }}
+        {{ transportasi_form.as_p }}
+
+        <button type="submit">Submit</button>
+    </form>
+</body>
+</html>
diff --git a/main/tests.py b/main/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/main/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/main/urls.py b/main/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..c0dd158c56f23897713f54e43c7002a5f78e658f
--- /dev/null
+++ b/main/urls.py
@@ -0,0 +1,6 @@
+from django.urls import path
+from .views import combined_form_view
+
+urlpatterns = [
+    path('', combined_form_view, name='home'),
+]
diff --git a/main/views.py b/main/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..e81de2f55ccaf3f7f9f994bbff39448a5ec9463e
--- /dev/null
+++ b/main/views.py
@@ -0,0 +1,25 @@
+from django.shortcuts import render, redirect
+from .forms import UserForm, TransportasiForm
+from django.contrib import messages
+
+def combined_form_view(request):
+    if request.method == "POST":
+        user_form = UserForm(request.POST)
+        transportasi_form = TransportasiForm(request.POST)
+
+        if user_form.is_valid() and transportasi_form.is_valid():
+            user_form.save()
+            transportasi_form.save()
+            messages.success(request, "Data berhasil disimpan!")
+            return redirect('home') 
+
+        else:
+            messages.error(request, "Periksa kembali data yang dimasukkan.")
+    else:
+        user_form = UserForm()
+        transportasi_form = TransportasiForm()
+
+    return render(request, "main.html", {
+        "user_form": user_form,
+        "transportasi_form": transportasi_form
+    })