Full Stack Amazon Clone ve Admin Paneli Yazalım | Node.js, Flutter #1

Gauloran

Moderasyon Tim Lideri
7 Tem 2013
8,267
751
Merhaba. Bu konuda beraber admin paneliyle birlikte amazon clone'u yazacağız. İnternette bununla ilgili tutorial mevcut fakat güncel Türkçe içerik göremedim. Bu yüzden halihazırda bulunan kaynaklardan da faydalanarak projedeki sorunları da çözüp güncel sorunsuz bir amazon clone yazacağız.

Konuyu okuyup tamamlamak için gereklilikler:
-Temel Dart ve Flutter bilgisi
Temel Dart ve Flutter bilgim yok ne yapacağım?

Sırasıyla başlıyorsun:

0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #1
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #2
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #3
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #4
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #5
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #6
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #7
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #8
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #9
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #10
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #11
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #12
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #13
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #14
0'dan İleri Seviyeye Mobil Uygulama Geliştirme Eğitimi Veriyorum #15
Flutter & Dart Fundamentals | Örnek Quiz Uygulaması #16
Flutter uygulamalarında debugging mantığı #17
Flutter & Dart widget tree, element tree, render tree #18
Dart Futures, Async, Await ufak açıklamalar
Dart Flutter ile Whatsapp Responsive UI Yazalım

- Javascript ve Node.js önceden biliyor olmam lazım mı?
GEREKMEZ
Seri süresince projeyi tamamlamamıza yetecek kadar js ve node.js ile ilgili şeyleri anlatacağım.

ÖZELLİKLER

- Email & Şifre ile giriş yapma
- Ürünleri arama
- Ürünleri kategori bazlı filtreleme
- Ürün detayları
- Ürün değerlendirmesi
- Google/Apple pay ile ödeme yapma
- Siparişleri görüntüleme
- Admin paneli
- Bütün ürünleri görme
- Ürün ekleme
- Ürün silme
- Sipariş durumunu değiştirme
- Toplam kazancı görüntüleme
- Kategori bazlı kazancı grafik üzerinde görüntüleme

TECH STACK:

Server:
- Node.js
- Express
- Mongoose
- MongoDB
- Cloudinary

Client:
- Flutter
- Provider

1- PROJE YAPISINI OLUŞTURUP GİRİŞ VE KAYIT OLMA EKRANINI YAZALIM


Yeni bir proje oluşturduktan sonra main.dart içerisindeki yorum satırı bulunan kodları temizleyin. Sadece MyApp classını bırakın. Daha sonra lib klasörü altında constants klasörü oluşturun bu genellikle projedeki ortak sabit şeyleri içerecek. Buraya global değişkenler adında yani global_variables.dart dosyasını oluşturun:

Kod:
import 'package:flutter/material.dart';[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]
String uri = 'http://<seninip>:3000';

class GlobalVariables {
  // Renkle
  static const appBarGradient = LinearGradient(
    colors: [
      Color.fromARGB(255, 29, 201, 192),
      Color.fromARGB(255, 125, 221, 216),
    ],
    stops: [0.5, 1.0],
  );

  static const secondaryColor = Color.fromRGBO(255, 153, 0, 1);
  static const backgroundColor = Colors.white;
  static const Color greyBackgroundCOlor = Color(0xffebecee);
  static var selectedNavBarColor = Colors.cyan[800]!;
  static const unselectedNavBarColor = Colors.black87;

  // statik resimler
  static const List<String> carouselImages = [
    'https://images-eu.ssl-images-amazon.com/images/G/31/img21/Wireless/WLA/TS/D37847648_Accessories_savingdays_Jan22_Cat_PC_1500.jpg',
    'https://images-eu.ssl-images-amazon.com/images/G/31/img2021/Vday/bwl/English.jpg',
    'https://images-eu.ssl-images-amazon.com/images/G/31/img22/Wireless/AdvantagePrime/BAU/14thJan/D37196025_IN_WL_AdvantageJustforPrime_Jan_Mob_ingress-banner_1242x450.jpg',
    'https://images-na.ssl-images-amazon.com/images/G/31/Symbol/2020/00NEW/1242_450Banners/PL31_copy._CB432483346_.jpg',
    'https://images-na.ssl-images-amazon.com/images/G/31/img21/shoes/September/SSW/pc-header._CB641971330_.jpg',
  ];

  static const List<Map<String, String>> categoryImages = [
    {
      'title': 'Mobiles',
      'image': 'assets/images/mobiles.jpeg',
    },
    {
      'title': 'Essentials',
      'image': 'assets/images/essentials.jpeg',
    },
    {
      'title': 'Appliances',
      'image': 'assets/images/appliances.jpeg',
    },
    {
      'title': 'Books',
      'image': 'assets/images/books.jpeg',
    },
    {
      'title': 'Fashion',
      'image': 'assets/images/fashion.jpeg',
    },
  ];
}


Burada projeyi yazarken kullanacağımız bazı statik resimler (kategori resimleri, renkler) vb. şeylerin tutulduğu dosyamız artık hazır. Bunları bir sınıf oluşturup static olarka tanımlarsak direkt olarak o sınıftan nesne oluşturmadan erişebiliriz. Bu arada Error Lens eklentisini vscode üzerinde yazıyorsanız kullanmanızı tavsiye ederim. Hatalar kodların hemen yanında gözükür. Şimdi main.dart'a geri dönelim:

Kod:
import 'package:amazon_clone/constants/global_variables.dart';[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Amazon Clone',
      theme: ThemeData(
        scaffoldBackgroundColor: GlobalVariables.backgroundColor,
        useMaterial3: true,
        colorScheme: const ColorScheme.light(primary: GlobalVariables.secondaryColor),
        appBarTheme: const AppBarTheme(
          elevation: 0,
          iconTheme: IconThemeData(color: Colors.black)
        )
      ),
      onGenerateRoute: ,
      home: Scaffold(
        appBar: AppBar(
        
          title: const Text('Hello'),
        ),
        body: Column(
          children: [
            const Center(
              child: Text('Flutter Demo Home Page'),
            ),
            ElevatedButton(onPressed: () {
              
            }, child: const Text('Click'),)
          ],
        ),
      ),
    );
  }
}


Henüz temel ayarlamaları yapıyoruz bir Text widgetını ortalayıp denemeler yapıyoruz sürekli olarak ayrı ayrı her widgeta renk vs. vermek yerine bunu temadan genel bir şekilde kontrol etmek için. Ayrıca şimdi lib klasörü altında features klasörü oluşturalım her bir özelliği bir klasörde tutacağız ve her bir klasörün örneğin giriş işlemleri özellikleriyle ilgili olan klasöre auth dedikten sonra bu klasör altında widgets (tekrar kullanılabilecek olan widgetlar için) screens (ui tasarımı için) ve services (api callar vs. ile ilgili kodlar için) bu şekilde sistematik ilerleyeceğiz. Şimdi auth için yapmamız gereken şu:

screens klasörü altında auth_screen.dart dosyasını oluşturuyoruz:

Kod:
import 'package:flutter/material.dart';[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]
class AuthScreen extends StatefulWidget {
 
  const AuthScreen({super.key});

  @override
  State<AuthScreen> createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(

    );
  }
}


Pek bir şey yapmadık sadece stateful widget oluşturduk ismi de Auth Screen. Şimdi de router olayını halletmemiz gerekiyor. Şimdi lib içerisinde direkt olarak router.dart dosyasını oluşturuyoruz.

Kod:
import 'package:amazon_clone/features/auth/screens/auth_screen.dart';[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]import 'package:flutter/material.dart';

Route<dynamic> generateRoute(RouteSettings routeSettings) {
  switch (routeSettings.name) {
    case AuthScreen.routeName:
      return MaterialPageRoute(
        settings: routeSettings,
        builder: (_) => const AuthScreen(),
      );
    default:
      return MaterialPageRoute(
        settings: routeSettings,
        builder: (_) => const Scaffold(
          body: Center(
            child: Text('Screen does not exist'),
          ),
        ),
      );
  }
}


Bir route oluşturmuş olduk Route için material'ı import etmemiz gerekti ve bir switch case yapısı kullandık. Burada yapmaya çalıştığımız şey eğer bu fonksiyon kullanıldığında parametre olarak gelen routeSettings.name'yani gelen şey ne ise örneğin String olarak '/auth_screen' tarzında bir şey geliyorsa ki biz bunu AuthScreen'e gidip

Kod:
class AuthScreen extends StatefulWidget {[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]  static const String routeName = '/auth-screen';


şu ufak güncellemeyi yapıyoruz böylece AuthScreen.routeName diyerek '/auth-screen' e erişebiliyoruz. Daha sonra MaterialPageRoute döndürüyoruz eğer buysa AuthScreen'e gidecek bir materialpageroute. Buraya ilerleyen zamanda eklemeler yapabiliriz. Switch yapısında ise default olarak bu ekran gözükmüyor anlamına gelen ortalanmış bir yazı koyduk scaffold altında isterseniz buraya siz böyle bir ekran yok yazan kendi tasarımınızı yapın ve onu buraya aktarın. Devam edelim. Bunu sağlıklı bir şekilde kullanmak için main.dart'ı güncelleyelim:

main.dart taki butonu şu şekilde güncelliyoruz:

Kod:
Builder([/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]              builder: (context) {
                return ElevatedButton(
                  onPressed: () {
                    Navigator.pushNamed(context, AuthScreen.routeName);
                  },
                  child: const Text('Click'),
                );
              }
            )


route Settings ile ilgili olan kısmı da şu şekilde güncelliyoruz:

Kod:
onGenerateRoute: (settings) => generateRoute(settings),

Yaptığımız şey elevatedbutton'ı bir builder ile sarmalamak hata çıkmaması için onGenerateRoute'da ise oluşturmuş olduğumuz route'umuzu veriyoruz. Şimdi main.dart içerisindeki home: kısmını tamamen silin ve home'a AuthScreen'i verin const yapabilirsiniz. Deneme yapmak için yazmıştık oraları zaten. Şimdi auth_screen.dart'a gelin ve giriş ekranı ile ilgili UI kısmını yazmaya başlayalım.

Öncelikle SafeArea ekliyoruz Scaffold'un hemen altına ardından Column oluşturuyoruz ve o Column'a her yönden bir padding veriyoruz. Daha sonra column'ın çocukları olarak Text widgetımızı ekliyoruz bu textin size'ı 22 olacak ve fontWeight'ini ayarladık. Ardından Column'ın bir diğer çocuğu olan ListTile widgetımızı ekliyoruz text'in altına gelecek yani.

Kod:
import 'package:amazon_clone/constants/global_variables.dart';[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]import 'package:flutter/material.dart';

class AuthScreen extends StatefulWidget {
  static const String routeName = '/auth-screen';
  const AuthScreen({super.key});

  @override
  State<AuthScreen> createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      backgroundColor: GlobalVariables.greyBackgroundCOlor,
      body: SafeArea(
        child: Padding(
          padding: EdgeInsets.all(16.0),
          child: Column(
            children: [
              Text(
                'Welcome',
                style: TextStyle(
                  fontSize: 22,
                  fontWeight: FontWeight.w500,
                ),
              ),
              ListTile(
                title: Text(
                  'Create Account',
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                ),
                leading: Radio(
                  activeColor: GlobalVariables.secondaryColor,
                  value:
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}


ListTile widgetının title'ına bir text widgetı ekliyoruz yazı olarak Hesap oluştur veya Create Account yazabilirsiniz size kalmış. Bunu da kalınlaştırdıktan sonra leading'e bir Radiobutton eklemek için radio widgetını kullanıyoruz. aktif rengini daha önce oluşturduğumuz globalvariables.secondaryColor'dan alıyoruz. Value'süne geldik işte bu noktada bir enum oluşturalım. Burada bir mantık kuracağız

Kod:
enum Auth {[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]  signin,
  signup,
}


Kod:
class _AuthScreenState extends State<AuthScreen> {[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]  Auth _auth = Auth.signup;


default olarak _auth Auth.signup'ı içerecek. Radiobuttonun group değerine _auth'u veriyoruz.

Kod:
 leading: Radio([/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]                  activeColor: GlobalVariables.secondaryColor,
                  value: Auth.signup,
                  groupValue: _auth,
                  onChanged: (Auth? val) {
                    setState(() {
                      _auth = val!;
                    });
                  },


bu şekilde güncellediğimizde groupValue'suna _auth'u veriyoruz ona da default olarak signup atalı olduğu için seçili halde gelecek Radiobutton. Şimdi formu yazmamız gerekli bu noktada yapmamız gereken 2 tane form key oluşturmak biri signin diğeri signup için

Kod:
class _AuthScreenState extends State<AuthScreen> {[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]  Auth _auth = Auth.signup;
  final _signUpFormKey = GlobalKey<FormState>();
  final _signInFormKey = GlobalKey<FormState>();


bu şekilde güncelledikten sonra

Kod:
if (_auth == Auth.signup)[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]                Form(
                  key: _signUpFormKey,
                  child: Column(
                    children: [],
                  ),
                ),
              ListTile(
                title: const Text(
                  'Sign In.',


burada eğer _auth == enumlardan olan Auth.signup'a eşitse bu form'u göster diye bir mantık kuruyoruz basitçe. Column içerisinde children'ı boş bıraktık burada kullanacağımız text kutucuğunu projenin her yerinde tekrar tekrar kullanacağımızı biliyoruz o nedenle lib klasörü altına common klasörünü açıyoruz daha sonra widgets klasörü açıyoruz common içerisine. Burada tekrar tekrar kullanılan ortak widgetları yazacağız. Buraya custom_textfield.dart dosyasını oluşturun.

Kod:
import 'package:flutter/material.dart';[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]
class CustomTextField extends StatelessWidget {
  final TextEditingController controller;
  const CustomTextField({super.key, required this.controller});

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: controller,
      decoration: const InputDecoration(
        border:  OutlineInputBorder(
          borderSide: BorderSide(
            color: Colors.black38,
          ),
        ),
        enabledBorder:  OutlineInputBorder(
          borderSide: BorderSide(
            color: Colors.black38,
          ),
        ),
        
      ),
      validator: (val) {
        
      },
    );
  }
}


Burada TextFormField oluşturduk ve contructordan controller kabul ediyoruz required yaptık gerekli olduğu için bu sınıf kullanıldığında verilen controllerı TextFormField'ın controllerına koyuyoruz ardından dekorasyon için InputDecoration veriyoruz köşeler için border: diyerek OutlineInputBorder dedikten sonra borderSide diyerek köşelerin rengini veriyoruz. enabledBorder'a da border ile aynı şeyi veriyoruz şimdilik bir validate işlemi yapmadığımız için validator kısmını boş bırakıyoruz. Tahmin edeceğiniz üzere authscreenstate'e gelip 3 kere bu customTextFieldımızı kullanacağız ve her birine farklı controller vereceğimiz için o controllerları oluşturuyoruz name, password ve email için. Bunları dispose etmeyi de unutmuyoruz.

Kod:
class _AuthScreenState extends State<AuthScreen> {[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]  Auth _auth = Auth.signup;
  final _signUpFormKey = GlobalKey<FormState>();
  final _signInFormKey = GlobalKey<FormState>();
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final TextEditingController _nameController = TextEditingController();

  @override
  void dispose() {
  
    super.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    _nameController.dispose();
  }


Bunları yaptıktan sonra o Column kısmında widgetımızı ekliyoruz ve controllerlarını veriyoruz. Bu arada bu kutucukların her birinde farklı hintText gözükmesini istediğimiz için tekrar gidip

Kod:
class CustomTextField extends StatelessWidget {[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]  final TextEditingController controller;
  final String hintText;


şeklinde güncelliyoruz.

Kod:
decoration: InputDecoration([/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]        hintText: hintText,


Kod:
 Column([/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]                      children: [
                        CustomTextField(
                          controller: _nameController,
                          hintText: 'Name',
                        ),
                        const SizedBox(height: 10,),
                        CustomTextField(
                          controller: _emailController,
                          hintText: 'Email',
                        ),
                        const SizedBox(height: 10,),
                        CustomTextField(
                          controller: _passwordController,
                          hintText: 'Password',
                        ),
                      ],
                    ),


Bu şekilde eklemeleri yaptığımızda yavaş yavaş giriş ekranımız oluşmaya başlıyor. Ardından kendimize özel bir buton oluşturalım bunun için common/widgets altında custom_button.dart açıyoruz

Kod:
[/I][/FONT][/SIZE][/CENTER]
[SIZE=4][FONT=trebuchet ms][I][CENTER]import 'package:amazon_clone/constants/global_variables.dart';
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
  final String text;
  final VoidCallback onTap;
  const CustomButton({super.key, required this.text, required this.onTap});
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onTap,
      style: ElevatedButton.styleFrom(
        minimumSize: const Size(double.infinity, 50),
        backgroundColor: GlobalVariables.secondaryColor,
        foregroundColor: GlobalVariables.backgroundColor,
        shape: BeveledRectangleBorder(
          borderRadius: BorderRadius.circular(3),
        ),
      ),
      child: Text(
        text,
      ),
    );
  }
}


Yaptığımız şey CustomButton'ı kullandığımızda bizden text isteyecek ve bir VoidCallback yani fonksiyon isteyecek ikisini verdiğimizde ilgili texti butonun yazısı olarak gösterecek fonksiyonu da butona tıklandığında olacak şeyleri gerçekleştirmek için onPressed: özelliğine verdik.

fObqeo.png
fObUDL.png



Serinin devamında görüşmek üzere. Serinin devamı için Node.js kurmayı unutmuyoruz.

<3 Gauloran

 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.