Modern mode
Portfolio
Home | Notes
- Loading projects...
- Loading project content...
Modern mode
Home | Notes

Your Digital Identity, Beautifully Crafted, Create and share your beautiful digital cards easily.
Tech stack: Next.js, Supabase
Ucansee adalah aplikasi SaaS untuk membuat digital card atau halaman profil publik. User bisa membuat kartu digital, mengatur profil, menambahkan link, memilih template, upload gambar, publish kartu, lalu membagikan URL publik seperti:
/cards/[slug]
Selain fitur utama untuk user, aplikasi juga memiliki dashboard analytics, sistem subscription/plan, billing manual via QRIS dan WhatsApp, serta panel admin untuk mengatur user, template, plan subscription, dan site settings.
app/api/api/cloudinary/signEntry point aplikasi ada di app/page.tsx.
Saat landing page dibuka, server mengambil data dari tabel site_settings. Data ini dipakai untuk menampilkan nama site, title, description, pricing, dan mode khusus seperti is_free_mode.
Landing page menampilkan:
Jika is_free_mode aktif, aplikasi menampilkan banner bahwa semua fitur premium dibuka.
Autentikasi menggunakan Supabase Auth. User bisa register, login, forgot password, dan update password.
Middleware di middleware.ts bertugas untuk:
/dashboard/login/dashboard/adminadmin atau super_admin/login atau /register ke /dashboardPublic card route sengaja dikecualikan dari middleware agar kartu yang sudah dipublish tetap bisa diakses publik.
Setelah login, user masuk ke /dashboard.
Di halaman dashboard, aplikasi:
user_idget_user_planDashboardAnalyticsClientDashboard juga menyediakan tombol:
Flow create card dimulai dari /dashboard/create.
Sebelum editor dibuka, aplikasi melakukan pengecekan:
/api/subscription/api/cards?count=trueCardEditorFull ditampilkanJika user sebelumnya mencoba simulator di landing page, data sementara bisa diambil dari localStorage dengan key ucansee_pending_card.
Komponen utama editor ada di components/CardEditorFull.tsx.
Editor menggunakan stepper tiga tahap:
Profile
Links
Design & Settings
Editor menggunakan:
react-hook-form untuk state formzod untuk validasi input/api/cards/check-slugTemplateRendererSaat save:
dashboard, api, admin, dan lain-laincards/dashboard/cardsUpload gambar dilakukan lewat komponen ImageUpload.tsx.
Default provider adalah Cloudinary. Alurnya:
/api/cloudinary/signCLOUDINARY_API_SECRETavatar_url atau cover_urlKeuntungan pendekatan ini:
Rendering kartu dilakukan oleh:
components/templates/TemplateRenderer.tsx
components/templates/DynamicTemplate.tsx
Semua template bersifat dinamis. Template disimpan di tabel templates dengan field seperti:
codenamedescriptionconfigstatusis_activeis_proplan_requiredexclusive_user_idEditor mengambil template aktif dari Supabase. Template kemudian difilter:
Admin memiliki Template Builder di:
/dashboard/admin/templates/builder
Di builder, admin bisa:
Halaman publik kartu ada di:
app/cards/[slug]/page.tsx
Alurnya:
slugnotFoundis_public false, return notFoundcard_idTemplateRendererAnalyticsTracker mencatat event viewPublic page dibuat no-store agar perubahan publish, link, atau template bisa langsung terlihat.
Analytics memiliki dua bagian utama:
Endpoint tracking:
POST /api/analytics
Data yang dikirim minimal:
card_idevent_typemetadataUntuk menjaga privacy, aplikasi tidak menyimpan raw visitor identity sebagai identifier utama. Visitor dihitung dengan hash dari:
Hasil hash menjadi visitor_id harian. Dengan cara ini aplikasi bisa menghitung unique visitor harian tanpa menyimpan data mentah sebagai identifier permanen.
Endpoint stats:
GET /api/analytics/stats
Endpoint ini mengolah data dari card_analytics menjadi:
Data ini ditampilkan di dashboard analytics user.
Subscription logic berada di lib/subscription.ts dan beberapa endpoint API.
Fitur yang diatur oleh plan:
Helper penting:
getUserPlangetPlanLimitsgetSubscriptionDetailscanCreateCardcanAddLinkcanAccessTemplategetAnalyticsDaysLimitEndpoint /api/subscription mengembalikan detail plan user, termasuk effective plan. Jika is_free_mode aktif dari site_settings, user bisa mendapatkan effective plan business meskipun plan aslinya free.
Billing page ada di /dashboard/billing. Sistem pembayaran bersifat manual:
Admin area berada di /dashboard/admin.
Akses admin dilindungi di dua level:
Fitur admin meliputi:
Untuk endpoint tertentu seperti mengambil semua auth users, aplikasi memakai Supabase Service Role. Ini hanya dilakukan di server route, bukan di browser.
Menyimpan data user aplikasi yang terhubung ke Supabase Auth.
Field penting:
iduser_idfull_nameavatar_urlrolesubscription_tierMenyimpan kartu digital milik profile.
Field penting:
idprofile_idtitlenameslugdescriptionavatar_urlcover_urlis_publictemplate_codetheme_configcreated_atupdated_atMenyimpan daftar link di dalam card.
Field penting:
idcard_idtitleurliconpositionis_activeparent_idtypeMenyimpan template dinamis yang bisa dipilih user.
Field penting:
codenameconfigstatusis_activeis_proplan_requiredexclusive_user_iddeleted_atMenyimpan konfigurasi plan.
Field penting:
slugnameprice_monthlymax_cardsmax_linksmax_analytics_daystrial_daysgrace_period_daysfeaturesMenyimpan event analytics.
Field penting:
card_idevent_typevisitor_idmetadatacreated_atMenyimpan konfigurasi global aplikasi.
Contoh konfigurasi:
Beberapa mekanisme security yang digunakan:
is_public = trueContoh alur ketika user membuat dan membagikan card:
/dashboardTemplateRenderercardslinks/cards/[slug]Saya membangun aplikasi ini dengan Next.js App Router karena cocok untuk aplikasi SaaS yang butuh gabungan server-rendered page, protected dashboard, dan API route dalam satu codebase. Untuk data dan auth saya memakai Supabase agar development lebih cepat, tetapi tetap punya Postgres, RLS, dan server-side access pattern yang jelas.
Bagian paling penting adalah CardEditorFull. Editor dibuat multi-step supaya user tidak kewalahan. Di dalamnya ada form validation, upload image, template selection, slug availability check, subscription limit, dan live preview. Jadi user bisa melihat hasil kartu secara real-time sebelum publish.
Template tidak di-hardcode sebagai komponen statis saja, tetapi dibuat dynamic melalui config dari database. Ini membuat admin bisa membuat atau mengubah template tanpa perlu deploy ulang aplikasi. Ada juga akses template berdasarkan plan atau user tertentu.
Limit plan tidak hanya dicek di UI. Aplikasi juga punya helper dan RPC untuk menentukan effective plan, trial, grace period, serta limit card/link/template. Ini memisahkan business logic subscription dari komponen UI.
Saya menerapkan proteksi berlapis: middleware untuk route protection, server-side check di API admin, Supabase Auth untuk session, dan RLS/policy di database. Untuk resource publik seperti card page, aplikasi tetap mengecek is_public sebelum menampilkan data.
Analytics dibuat privacy-conscious. Untuk unique visitor, aplikasi membuat hash dari IP, user-agent, tanggal, dan salt. Dengan begitu aplikasi bisa menghitung unique daily visitor tanpa menyimpan identifier mentah secara permanen.
Upload gambar menggunakan signed upload ke Cloudinary. Browser meminta signature ke server, lalu upload langsung ke Cloudinary. Ini menjaga secret tetap aman dan mengurangi beban server aplikasi.
Beberapa tantangan yang menarik di project ini:
Beberapa peningkatan yang bisa dikembangkan berikutnya:
Ucansee adalah SaaS digital card builder. User bisa login, membuat kartu digital, mengatur profil dan link, memilih template, upload gambar, publish kartu ke URL publik, lalu melihat analytics performa kartu. Di sisi admin, aplikasi mendukung pengelolaan user, template, subscription plan, payment setting, dan global site settings.
Project ini menggunakan Next.js 13 App Router, React, TypeScript, Tailwind CSS, Radix/shadcn-style components, Supabase untuk auth dan Postgres database, Cloudinary untuk image upload, Zod dan React Hook Form untuk form validation, Recharts untuk analytics chart, serta dnd-kit untuk drag and drop link ordering.
Bagian tersulit adalah menyatukan editor real-time, dynamic template, subscription limit, dan public rendering. Editor harus nyaman dipakai, tetapi tetap harus memvalidasi slug, membatasi fitur berdasarkan plan, upload gambar dengan aman, dan langsung menampilkan preview sesuai template yang dipilih. Di sisi lain, public card harus cepat, aman, dan hanya tampil jika memang dipublish.