From ecb53b711c97da12b81d7efbe119fdec36551d01 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Augusto=20de=20Souza=20Santos?=
 <a61468@alunos.ipb.pt>
Date: Wed, 11 Dec 2024 18:42:16 +0000
Subject: [PATCH] Muita coisa, incluindo product e user

---
 ShoppingList.Domain/IUnitOfWork.cs            |  2 +
 ShoppingList.Domain/Models/User.cs            | 22 ++++++
 .../Repository/IUserRepository.cs             | 10 +++
 .../Repository/ProductRepository.cs           |  7 ++
 .../Repository/Repository.cs                  |  2 +-
 .../Repository/UserRepository.cs              | 51 +++++++++++++
 .../ShoppingListDbContext.cs                  | 24 ++++++
 ShoppingList.Infrastructure/UnitOfWork.cs     |  2 +
 ShoppingList.UWP/App.xaml                     | 15 +++-
 ShoppingList.UWP/App.xaml.cs                  | 45 ++++--------
 .../Converters/BytesToImageConverter.cs       | 32 ++++++++
 ShoppingList.UWP/MainPage.xaml                | 23 ++++--
 ShoppingList.UWP/MainPage.xaml.cs             | 33 ++++++++-
 ShoppingList.UWP/ShoppingList.UWP.csproj      | 16 ++++
 .../ViewModels/ProductViewModel.cs            | 73 +++++++++++++++----
 ShoppingList.UWP/ViewModels/UserViewModel.cs  | 59 +++++++++++++++
 .../Categories/ManageCategoriesPage.xaml      |  4 +-
 .../Views/Products/ManageProductsPage.xaml    | 13 +++-
 .../Views/Products/ManageProductsPage.xaml.cs |  4 +-
 .../Views/Products/ProductFormPage.xaml       | 27 +++++--
 .../Views/Products/ProductFormPage.xaml.cs    | 40 +++++++++-
 ShoppingList.UWP/Views/Users/LoginDialog.xaml | 17 +++++
 .../Views/Users/LoginDialog.xaml.cs           | 30 ++++++++
 .../Views/Users/RegisterDialog.xaml           | 33 +++++++++
 .../Views/Users/RegisterDialog.xaml.cs        | 23 ++++++
 25 files changed, 535 insertions(+), 72 deletions(-)
 create mode 100644 ShoppingList.Domain/Models/User.cs
 create mode 100644 ShoppingList.Domain/Repository/IUserRepository.cs
 create mode 100644 ShoppingList.Infrastructure/Repository/UserRepository.cs
 create mode 100644 ShoppingList.UWP/Converters/BytesToImageConverter.cs
 create mode 100644 ShoppingList.UWP/ViewModels/UserViewModel.cs
 create mode 100644 ShoppingList.UWP/Views/Users/LoginDialog.xaml
 create mode 100644 ShoppingList.UWP/Views/Users/LoginDialog.xaml.cs
 create mode 100644 ShoppingList.UWP/Views/Users/RegisterDialog.xaml
 create mode 100644 ShoppingList.UWP/Views/Users/RegisterDialog.xaml.cs

diff --git a/ShoppingList.Domain/IUnitOfWork.cs b/ShoppingList.Domain/IUnitOfWork.cs
index f390bb9..e10e3a3 100644
--- a/ShoppingList.Domain/IUnitOfWork.cs
+++ b/ShoppingList.Domain/IUnitOfWork.cs
@@ -1,4 +1,5 @@
 using ShoppingList.Domain.Interfaces;
+using ShoppingList.Domain.Repository;
 using System;
 using System.Threading.Tasks;
 
@@ -6,6 +7,7 @@ namespace ShoppingList.Domain {
     public interface IUnitOfWork : IDisposable {
         ICategoryRepository CategoryRepository { get; }
         IProductRepository ProductRepository { get; }
+        IUserRepository UserRepository { get; }
         Task SaveAsync();
     }
 }
diff --git a/ShoppingList.Domain/Models/User.cs b/ShoppingList.Domain/Models/User.cs
new file mode 100644
index 0000000..be217d0
--- /dev/null
+++ b/ShoppingList.Domain/Models/User.cs
@@ -0,0 +1,22 @@
+using ShoppingList.Domain.SeedWork;
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ShoppingList.Domain.Models {
+    public class User : Entity {
+        public string Username { get; set; }
+        private string _password;
+        public string Password {
+            get { return _password; }
+            set {
+                var data = Encoding.UTF8.GetBytes(value);
+                var hasData = new SHA1Managed().ComputeHash(data);
+                _password = BitConverter.ToString(hasData).Replace("-", "").ToUpper();
+            }
+        }
+
+        public bool IsAdmin { get; set; }
+
+    }
+}
diff --git a/ShoppingList.Domain/Repository/IUserRepository.cs b/ShoppingList.Domain/Repository/IUserRepository.cs
new file mode 100644
index 0000000..1d089af
--- /dev/null
+++ b/ShoppingList.Domain/Repository/IUserRepository.cs
@@ -0,0 +1,10 @@
+using ShoppingList.Domain.Models;
+using ShoppingList.Domain.SeedWork;
+using System.Threading.Tasks;
+
+namespace ShoppingList.Domain.Repository {
+    public interface IUserRepository : IRepository<User> {
+        Task<User> FindByUsernameAsync(string username);
+        Task<User> FindByUsernameAndPasswordAsync(string username, string password);
+    }
+}
diff --git a/ShoppingList.Infrastructure/Repository/ProductRepository.cs b/ShoppingList.Infrastructure/Repository/ProductRepository.cs
index af44352..08caf5c 100644
--- a/ShoppingList.Infrastructure/Repository/ProductRepository.cs
+++ b/ShoppingList.Infrastructure/Repository/ProductRepository.cs
@@ -1,6 +1,7 @@
 using Microsoft.EntityFrameworkCore;
 using ShoppingList.Domain.Interfaces;
 using ShoppingList.Domain.Models;
+using System.Collections.Generic;
 using System.Threading.Tasks;
 
 namespace ShoppingList.Infrastructure.Repository {
@@ -44,5 +45,11 @@ namespace ShoppingList.Infrastructure.Repository {
 
             return p;
         }
+
+        public override Task<List<Product>> FindAllAsync() {
+            return _dbContext.Products
+                .Include(x => x.Category)
+                .ToListAsync();
+        }
     }
 }
diff --git a/ShoppingList.Infrastructure/Repository/Repository.cs b/ShoppingList.Infrastructure/Repository/Repository.cs
index 5ca1642..fed9317 100644
--- a/ShoppingList.Infrastructure/Repository/Repository.cs
+++ b/ShoppingList.Infrastructure/Repository/Repository.cs
@@ -30,7 +30,7 @@ namespace ShoppingList.Infrastructure.Repository {
             return _dbContext.Set<T>().SingleAsync(x => x.Id == id);
         }
 
-        public Task<List<T>> FindAllAsync() {
+        public virtual Task<List<T>> FindAllAsync() {
             return _dbContext.Set<T>().ToListAsync();
         }
 
diff --git a/ShoppingList.Infrastructure/Repository/UserRepository.cs b/ShoppingList.Infrastructure/Repository/UserRepository.cs
new file mode 100644
index 0000000..f3b5a7c
--- /dev/null
+++ b/ShoppingList.Infrastructure/Repository/UserRepository.cs
@@ -0,0 +1,51 @@
+using Microsoft.EntityFrameworkCore;
+using ShoppingList.Domain.Models;
+using ShoppingList.Domain.Repository;
+using System.Threading.Tasks;
+
+namespace ShoppingList.Infrastructure.Repository {
+    public class UserRepository : Repository<User>, IUserRepository {
+        public UserRepository(ShoppingListDbContext dbContext) : base(dbContext) {
+        }
+        public async Task<User> FindByUsernameAsync(string username) {
+            return await _dbContext.Users
+                .SingleOrDefaultAsync(x => x.Username == username);
+        }
+
+        public async Task<User> FindByUsernameAndPasswordAsync(string username, string password) {
+            return await _dbContext.Users
+                .SingleOrDefaultAsync(x => x.Username == username && x.Password == password);
+        }
+
+        public override async Task<User> FindOrCreateAsync(User entity) {
+            var f = await FindByUsernameAsync(entity.Username);
+            if (f == null) {
+                Create(entity);
+                f = entity;
+            }
+
+            return f;
+        }
+
+        public override async Task<User> UpsertAsync(User entity) {
+            User u = null;
+            User existing = await FindByUsernameAsync(entity.Username);
+
+            if (existing == null) {
+                if (entity.Id == 0)
+                    Create(entity);
+                else
+                    Update(entity);
+                u = entity;
+            } else if (existing.Id == entity.Id) {
+                if (existing.Username != entity.Username)
+                    Update(entity);
+                u = entity;
+            } else {
+                _dbContext.Entry(entity).State = EntityState.Detached;
+            }
+
+            return u;
+        }
+    }
+}
diff --git a/ShoppingList.Infrastructure/ShoppingListDbContext.cs b/ShoppingList.Infrastructure/ShoppingListDbContext.cs
index 1c11021..513d772 100644
--- a/ShoppingList.Infrastructure/ShoppingListDbContext.cs
+++ b/ShoppingList.Infrastructure/ShoppingListDbContext.cs
@@ -6,6 +6,7 @@ namespace ShoppingList.Infrastructure {
     public class ShoppingListDbContext : DbContext {
         public DbSet<Product> Products { get; set; }
         public DbSet<Category> Categories { get; set; }
+        public DbSet<User> Users { get; set; }
         public string DbPath { get; }
 
         public ShoppingListDbContext() {
@@ -35,6 +36,29 @@ namespace ShoppingList.Infrastructure {
                 .Property(x => x.Name)
                 .IsRequired()
                 .HasMaxLength(50);
+
+            // User
+            modelBuilder.Entity<User>()
+                .HasIndex(x => x.Username)
+                .IsUnique();
+            modelBuilder.Entity<User>()
+                .Property(x => x.Username)
+                .IsRequired()
+                .HasMaxLength(50);
+            modelBuilder.Entity<User>()
+                .Property(x => x.Password)
+                .IsRequired()
+                .HasMaxLength(250);
+            modelBuilder.Entity<User>()
+                .Property(x => x.IsAdmin)
+                .IsRequired();
+            modelBuilder.Entity<User>()
+                .HasData(new User {
+                    Id = 1,
+                    Username = "admin",
+                    Password = "admin",
+                    IsAdmin = true
+                });
         }
     }
 }
diff --git a/ShoppingList.Infrastructure/UnitOfWork.cs b/ShoppingList.Infrastructure/UnitOfWork.cs
index d2bc61a..9964365 100644
--- a/ShoppingList.Infrastructure/UnitOfWork.cs
+++ b/ShoppingList.Infrastructure/UnitOfWork.cs
@@ -1,5 +1,6 @@
 using ShoppingList.Domain;
 using ShoppingList.Domain.Interfaces;
+using ShoppingList.Domain.Repository;
 using ShoppingList.Infrastructure.Repository;
 using System.Threading.Tasks;
 
@@ -20,6 +21,7 @@ namespace ShoppingList.Infrastructure {
         public ICategoryRepository CategoryRepository => new CategoryRepository(_dbContext);
 
         public IProductRepository ProductRepository => new ProductRepository(_dbContext);
+        public IUserRepository UserRepository => new UserRepository(_dbContext);
 
         public void Dispose() {
             _dbContext.Dispose();
diff --git a/ShoppingList.UWP/App.xaml b/ShoppingList.UWP/App.xaml
index 6763bed..3d5d397 100644
--- a/ShoppingList.UWP/App.xaml
+++ b/ShoppingList.UWP/App.xaml
@@ -2,6 +2,17 @@
     x:Class="ShoppingList.UWP.App"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="using:ShoppingList.UWP">
+    xmlns:local="using:ShoppingList.UWP" xmlns:converters="using:ShoppingList.UWP.Converters"
+    RequestedTheme="Light">
+    <Application.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary>
+                    <converters:BytesToImageConverter x:Key="bytesToImageConverter"/>
+                </ResourceDictionary>
 
-</Application>
+
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>
\ No newline at end of file
diff --git a/ShoppingList.UWP/App.xaml.cs b/ShoppingList.UWP/App.xaml.cs
index 92131a3..2d4aa3c 100644
--- a/ShoppingList.UWP/App.xaml.cs
+++ b/ShoppingList.UWP/App.xaml.cs
@@ -1,35 +1,25 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices.WindowsRuntime;
+using ShoppingList.UWP.ViewModels;
+using System;
 using Windows.ApplicationModel;
 using Windows.ApplicationModel.Activation;
-using Windows.Foundation;
-using Windows.Foundation.Collections;
 using Windows.UI.Xaml;
 using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Controls.Primitives;
-using Windows.UI.Xaml.Data;
-using Windows.UI.Xaml.Input;
-using Windows.UI.Xaml.Media;
 using Windows.UI.Xaml.Navigation;
 
-namespace ShoppingList.UWP
-{
+namespace ShoppingList.UWP {
     /// <summary>
     ///Fornece o comportamento específico do aplicativo para complementar a classe Application padrão.
     /// </summary>
-    sealed partial class App : Application
-    {
+    sealed partial class App : Application {
+        public static UserViewModel UserViewModel { get; set; }
         /// <summary>
         /// Inicializa o objeto singleton do aplicativo. Essa é a primeira linha do código criado
         /// executado e, por isso, é o equivalente lógico de main() ou WinMain().
         /// </summary>
-        public App()
-        {
+        public App() {
             this.InitializeComponent();
             this.Suspending += OnSuspending;
+            UserViewModel = new UserViewModel();
         }
 
         /// <summary>
@@ -37,21 +27,18 @@ namespace ShoppingList.UWP
         /// serão usados, por exemplo, quando o aplicativo for iniciado para abrir um arquivo específico.
         /// </summary>
         /// <param name="e">Detalhes sobre a solicitação e o processo de inicialização.</param>
-        protected override void OnLaunched(LaunchActivatedEventArgs e)
-        {
+        protected override void OnLaunched(LaunchActivatedEventArgs e) {
             Frame rootFrame = Window.Current.Content as Frame;
 
             // Não repita a inicialização do aplicativo quando a Janela já tiver conteúdo,
             // apenas verifique se a janela está ativa
-            if (rootFrame == null)
-            {
+            if (rootFrame == null) {
                 // Crie um Quadro para atuar como o contexto de navegação e navegue para a primeira página
                 rootFrame = new Frame();
 
                 rootFrame.NavigationFailed += OnNavigationFailed;
 
-                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
-                {
+                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) {
                     // TODO: Carregue o estado do aplicativo suspenso anteriormente
                 }
 
@@ -59,10 +46,8 @@ namespace ShoppingList.UWP
                 Window.Current.Content = rootFrame;
             }
 
-            if (e.PrelaunchActivated == false)
-            {
-                if (rootFrame.Content == null)
-                {
+            if (e.PrelaunchActivated == false) {
+                if (rootFrame.Content == null) {
                     // Quando a pilha de navegação não for restaurada, navegar para a primeira página,
                     // configurando a nova página passando as informações necessárias como um parâmetro
                     // de navegação
@@ -78,8 +63,7 @@ namespace ShoppingList.UWP
         /// </summary>
         /// <param name="sender">O Quadro com navegação com falha</param>
         /// <param name="e">Detalhes sobre a falha na navegação</param>
-        void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
-        {
+        void OnNavigationFailed(object sender, NavigationFailedEventArgs e) {
             throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
         }
 
@@ -90,8 +74,7 @@ namespace ShoppingList.UWP
         /// </summary>
         /// <param name="sender">A origem da solicitação de suspensão.</param>
         /// <param name="e">Detalhes sobre a solicitação de suspensão.</param>
-        private void OnSuspending(object sender, SuspendingEventArgs e)
-        {
+        private void OnSuspending(object sender, SuspendingEventArgs e) {
             var deferral = e.SuspendingOperation.GetDeferral();
             //TODO: Salvar o estado do aplicativo e parar qualquer atividade em segundo plano
             deferral.Complete();
diff --git a/ShoppingList.UWP/Converters/BytesToImageConverter.cs b/ShoppingList.UWP/Converters/BytesToImageConverter.cs
new file mode 100644
index 0000000..f9e02e5
--- /dev/null
+++ b/ShoppingList.UWP/Converters/BytesToImageConverter.cs
@@ -0,0 +1,32 @@
+using System;
+using Windows.Storage.Streams;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Media.Imaging;
+
+namespace ShoppingList.UWP.Converters {
+    public class BytesToImageConverter : IValueConverter {
+
+        public object Convert(object value, Type targetType, object parameter, string language) {
+            if (value == null || !(value is byte[]))
+                return null;
+
+            using (InMemoryRandomAccessStream ms = new InMemoryRandomAccessStream()) {
+                using (DataWriter writer = new DataWriter(ms.GetOutputStreamAt(0))) {
+                    writer.WriteBytes((byte[])value);
+                    writer.StoreAsync().GetResults();
+                }
+
+                var image = new BitmapImage();
+                image.SetSource(ms);
+                return image;
+            }
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, string language) {
+            if (value == null || !(value is BitmapImage))
+                return (null);
+            return (null);
+        }
+    }
+}
+
diff --git a/ShoppingList.UWP/MainPage.xaml b/ShoppingList.UWP/MainPage.xaml
index 7718618..ce104ba 100644
--- a/ShoppingList.UWP/MainPage.xaml
+++ b/ShoppingList.UWP/MainPage.xaml
@@ -10,18 +10,29 @@
     RequestedTheme="Light">
 
     <Grid>
-        <NavigationView IsBackButtonVisible="Collapsed" IsSettingsVisible="False" x:Name="nvMain" ItemInvoked="NvMain_ItemInvoked">
+        <NavigationView IsBackButtonVisible="Collapsed" IsSettingsVisible="False" 
+                        x:Name="nvMain" ItemInvoked="NvMain_ItemInvoked" 
+                        PaneTitle="{x:Bind UserViewModel.LoggedUser.Username, Mode=OneWay}">
             <NavigationView.MenuItems>
-                <NavigationViewItem x:Name="nvShoppingList" Content="Shopping List" Icon="List"/>
+                <NavigationViewItem x:Name="nvShoppingList" Content="Shopping List" Icon="List" Tag="shoppingList"/>
                 <NavigationViewItem x:Name="nvProducts" Content="Products" Icon="Shop" Tag="products"/>
                 <NavigationViewItem x:Name="nvCategories" Content="Categories" Icon="Library" Tag="categories"/>
-                <NavigationViewItem x:Name="nvUsers" Content="Users" Icon="OtherUser"/>
+                <NavigationViewItem x:Name="nvUsers" Content="Users" Icon="OtherUser" Tag="users"/>
             </NavigationView.MenuItems>
             <NavigationView.PaneFooter>
                 <StackPanel>
-                    <NavigationViewItem x:Name="nvLogin" Content="Login" Icon="Permissions"/>
-                    <NavigationViewItem x:Name="nvLogout" Content="Logout" Icon="NewWindow"/>
-                    <NavigationViewItem x:Name="nvRegister" Content="Register" Icon="ContactInfo"/>
+                    <NavigationViewItem x:Name="nvLogin" Content="Login" 
+                                        Icon="Permissions" 
+                                        Tapped="nvLogin_Tapped" 
+                                        Visibility="{x:Bind UserViewModel.IsNotLogged, Mode=OneWay}"/>
+                    <NavigationViewItem x:Name="nvLogout" Content="Logout" 
+                                        Icon="NewWindow" 
+                                        Tapped="nvLogout_Tapped" 
+                                        Visibility="{x:Bind UserViewModel.IsLogged, Mode=OneWay}"/>
+                    <NavigationViewItem x:Name="nvRegister" Content="Register" 
+                                        Icon="ContactInfo" 
+                                        Tapped="nvRegister_Tapped" 
+                                        Visibility="{x:Bind UserViewModel.IsNotLogged, Mode=OneWay}"/>
                 </StackPanel>
             </NavigationView.PaneFooter>
             <Frame x:Name="frmMain" Padding="10,0">
diff --git a/ShoppingList.UWP/MainPage.xaml.cs b/ShoppingList.UWP/MainPage.xaml.cs
index 35c6667..6b18171 100644
--- a/ShoppingList.UWP/MainPage.xaml.cs
+++ b/ShoppingList.UWP/MainPage.xaml.cs
@@ -1,5 +1,8 @@
-using ShoppingList.UWP.Views.Categories;
+using ShoppingList.UWP.ViewModels;
+using ShoppingList.UWP.Views.Categories;
 using ShoppingList.UWP.Views.Products;
+using ShoppingList.UWP.Views.Users;
+using System;
 using Windows.UI.Xaml.Controls;
 
 // O modelo de item de Página em Branco está documentado em https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x416
@@ -9,8 +12,10 @@ namespace ShoppingList.UWP {
     /// Uma página vazia que pode ser usada isoladamente ou navegada dentro de um Quadro.
     /// </summary>
     public sealed partial class MainPage : Page {
+        public UserViewModel UserViewModel { get; set; }
         public MainPage() {
             this.InitializeComponent();
+            UserViewModel = App.UserViewModel;
         }
 
         private void NvMain_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) {
@@ -22,10 +27,36 @@ namespace ShoppingList.UWP {
                     case "products":
                         frmMain.Navigate(typeof(ManageProductsPage));
                         break;
+                    case "shoppingList":
+                        frmMain.Navigate(typeof(ManageCategoriesPage));
+                        break;
+                    case "users":
+                        frmMain.Navigate(typeof(ManageCategoriesPage));
+                        break;
                     default:
                         break;
                 }
             }
         }
+
+        private void nvLogin_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) {
+
+        }
+
+        private void nvLogout_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) {
+            UserViewModel.DoLogout();
+            frmMain.BackStack.Clear();
+            frmMain.Content = null;
+        }
+
+        private async void nvRegister_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) {
+            RegisterDialog dlg = new RegisterDialog();
+            var res = await dlg.ShowAsync();
+            if (res == ContentDialogResult.Primary) {
+                if (App.UserViewModel.IsLogged) {
+                    frmMain.Navigate(typeof(ManageCategoriesPage));
+                }
+            }
+        }
     }
 }
diff --git a/ShoppingList.UWP/ShoppingList.UWP.csproj b/ShoppingList.UWP/ShoppingList.UWP.csproj
index a3ec477..b361ba1 100644
--- a/ShoppingList.UWP/ShoppingList.UWP.csproj
+++ b/ShoppingList.UWP/ShoppingList.UWP.csproj
@@ -119,6 +119,7 @@
     <Compile Include="App.xaml.cs">
       <DependentUpon>App.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Converters\BytesToImageConverter.cs" />
     <Compile Include="MainPage.xaml.cs">
       <DependentUpon>MainPage.xaml</DependentUpon>
     </Compile>
@@ -126,6 +127,7 @@
     <Compile Include="ViewModels\BindableBase.cs" />
     <Compile Include="ViewModels\CategoryViewModel.cs" />
     <Compile Include="ViewModels\ProductViewModel.cs" />
+    <Compile Include="ViewModels\UserViewModel.cs" />
     <Compile Include="Views\Categories\CategoryFormPage.xaml.cs">
       <DependentUpon>CategoryFormPage.xaml</DependentUpon>
     </Compile>
@@ -138,6 +140,12 @@
     <Compile Include="Views\Products\ProductFormPage.xaml.cs">
       <DependentUpon>ProductFormPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Views\Users\LoginDialog.xaml.cs">
+      <DependentUpon>LoginDialog.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Views\Users\RegisterDialog.xaml.cs">
+      <DependentUpon>RegisterDialog.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <ItemGroup>
     <AppxManifest Include="Package.appxmanifest">
@@ -179,6 +187,14 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\Users\LoginDialog.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="Views\Users\RegisterDialog.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
   </ItemGroup>
   <ItemGroup>
     <PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
diff --git a/ShoppingList.UWP/ViewModels/ProductViewModel.cs b/ShoppingList.UWP/ViewModels/ProductViewModel.cs
index b120911..f0d2c63 100644
--- a/ShoppingList.UWP/ViewModels/ProductViewModel.cs
+++ b/ShoppingList.UWP/ViewModels/ProductViewModel.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
 namespace ShoppingList.UWP.ViewModels {
     public class ProductViewModel : BindableBase {
         public ObservableCollection<Product> Products { get; set; }
+
         private string _productName;
         public string ProductName {
             get { return _productName; }
@@ -15,17 +16,47 @@ namespace ShoppingList.UWP.ViewModels {
                 OnPropertyChanged(nameof(Invalid));
             }
         }
+
+        private string _categoryName;
+        public string CategoryName {
+            get { return _categoryName; }
+            set {
+                Set(ref _categoryName, value);
+                OnPropertyChanged(nameof(Valid));
+                OnPropertyChanged(nameof(Invalid));
+            }
+        }
+
         private Product _product;
         public Product Product {
             get { return _product; }
             set {
                 _product = value;
+                CategoryName = _product?.Category?.Name;
                 ProductName = _product?.Name;
+                Thumb = _product?.Thumb;
             }
         }
 
+        private byte[] _thumb;
+        public byte[] Thumb {
+            get { return _thumb; }
+            set {
+                Set(ref _thumb, value);
+            }
+        }
+
+        private string _title;
+        public string Title {
+            get { return _title; }
+            set { _title = value; }
+        }
+
         public bool Valid {
-            get { return !string.IsNullOrWhiteSpace(ProductName); }
+            get {
+                return !string.IsNullOrWhiteSpace(CategoryName)
+                && !string.IsNullOrWhiteSpace(ProductName);
+            }
         }
 
         public bool Invalid {
@@ -33,13 +64,20 @@ namespace ShoppingList.UWP.ViewModels {
         }
 
         public ProductViewModel() {
-            Product = new Product();
             Products = new ObservableCollection<Product>();
+            Product = new Product();
+            Title = "Products";
+        }
+        public async void DeleteAsync(Product p) {
+            using (UnitOfWork uow = new UnitOfWork()) {
+                uow.ProductRepository.Delete(p);
+                Products.Remove(p);
+                await uow.SaveAsync();
+            }
         }
 
         public async void LoadAllAsync() {
             using (var uow = new UnitOfWork()) {
-                System.Console.WriteLine(uow.GetDbPath());
                 var list = await uow.ProductRepository.FindAllAsync();
                 Products.Clear();
                 foreach (var product in list) {
@@ -48,24 +86,29 @@ namespace ShoppingList.UWP.ViewModels {
             }
         }
 
-        internal async Task<Product> UpsertAsync() {
-            Product res = null;
-
+        internal async Task<ObservableCollection<Category>> LoadCategoriesByNameStartedWithAsync(string categoryName) {
+            ObservableCollection<Category> res = null;
             using (var uow = new UnitOfWork()) {
-                Product.Name = ProductName;
-                Product.CategoryId = 5;
-                res = await uow.ProductRepository.UpsertAsync(Product);
-                await uow.SaveAsync();
+                var list = await uow.CategoryRepository.FindAllByNameStartedWithAsync(categoryName);
+                res = new ObservableCollection<Category>(list);
             }
-
             return res;
         }
 
-        internal async void DeleteAsync(Product p) {
-            using (UnitOfWork uow = new UnitOfWork()) {
-                uow.ProductRepository.Delete(p);
-                Products.Remove(p);
+        internal async Task<Product> UpsertAsync() {
+            using (var uow = new UnitOfWork()) {
+                Category c = new Category() { Name = CategoryName };
+                Category cUpdated = await uow.CategoryRepository.FindOrCreateAsync(c);
                 await uow.SaveAsync();
+
+                Product.CategoryId = cUpdated.Id;
+                Product.Category = null;
+                Product.Name = ProductName;
+                Product.Thumb = Thumb;
+                Product pUpdated = await uow.ProductRepository.UpsertAsync(Product);
+                await uow.SaveAsync();
+
+                return pUpdated;
             }
         }
     }
diff --git a/ShoppingList.UWP/ViewModels/UserViewModel.cs b/ShoppingList.UWP/ViewModels/UserViewModel.cs
new file mode 100644
index 0000000..481abdc
--- /dev/null
+++ b/ShoppingList.UWP/ViewModels/UserViewModel.cs
@@ -0,0 +1,59 @@
+using ShoppingList.Domain.Models;
+using ShoppingList.Infrastructure;
+using System.Threading.Tasks;
+
+namespace ShoppingList.UWP.ViewModels {
+    public class UserViewModel : BindableBase {
+        public User User { get; set; }
+        private User _loggedUser;
+        public User LoggedUser {
+            get { return _loggedUser; }
+            set {
+                _loggedUser = value;
+                OnPropertyChanged();
+            }
+        }
+        public bool IsLogged { get => _loggedUser != null; }
+        public bool IsNotLogged { get => !IsLogged; }
+        private bool _showError;
+        public bool ShowError {
+            get { return _showError; }
+            set {
+                _showError = value;
+                OnPropertyChanged();
+            }
+        }
+        public bool IsAdmin => LoggedUser != null && LoggedUser.IsAdmin;
+
+        public UserViewModel() {
+            User = new User();
+        }
+
+        internal async Task<bool> DoLoginAsync() {
+            using (UnitOfWork uow = new UnitOfWork()) {
+                var user = await uow.UserRepository
+                    .FindByUsernameAndPasswordAsync(User.Username, User.Password);
+                LoggedUser = user;
+                ShowError = user == null;
+                return !ShowError;
+            }
+        }
+
+        internal async Task<bool> DoRegisterAsync() {
+            using (UnitOfWork uow = new UnitOfWork()) {
+                var user = await uow.UserRepository.FindByUsernameAsync(User.Username);
+                if (user == null) {
+                    uow.UserRepository.Create(User);
+                    await uow.SaveAsync();
+                    LoggedUser = User;
+                    ShowError = User == null;
+                }
+            }
+            return !ShowError;
+        }
+
+        internal void DoLogout() {
+            LoggedUser = null;
+        }
+    }
+}
diff --git a/ShoppingList.UWP/Views/Categories/ManageCategoriesPage.xaml b/ShoppingList.UWP/Views/Categories/ManageCategoriesPage.xaml
index 63a3bd4..402edae 100644
--- a/ShoppingList.UWP/Views/Categories/ManageCategoriesPage.xaml
+++ b/ShoppingList.UWP/Views/Categories/ManageCategoriesPage.xaml
@@ -47,13 +47,13 @@
                             </Flyout>
                         </FlyoutBase.AttachedFlyout>
                         <TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{x:Bind Name}" />
-                        <Button x:Name="btnEdit" Grid.Column="1" Margin="5, 0" Click="btnEdit_Click">
+                        <Button x:Name="btnEdit" Grid.Column="1" Margin="5, 0" Click="btnEdit_Click" Background="Transparent">
                             <StackPanel>
                                 <SymbolIcon Symbol="Edit" />
                                 <TextBlock Text="Edit" />
                             </StackPanel>
                         </Button>
-                        <Button x:Name="btnDelete" Grid.Column="2" Margin="5, 0" Click="btnDelete_Click">
+                        <Button x:Name="btnDelete" Grid.Column="2" Margin="5, 0" Click="btnDelete_Click" Background="Transparent">
                             <StackPanel>
                                 <SymbolIcon Symbol="Delete" />
                                 <TextBlock Text="Delete" />
diff --git a/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml b/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml
index 160c1e2..a50e55b 100644
--- a/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml
+++ b/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml
@@ -18,7 +18,7 @@
                 <ColumnDefinition Width="Auto"/>
                 <ColumnDefinition />
             </Grid.ColumnDefinitions>
-            <TextBlock Text="Products" Style="{StaticResource HeaderTextBlockStyle}"/>
+            <TextBlock Text="{x:Bind ProductViewModel.Title}" Style="{StaticResource HeaderTextBlockStyle}"/>
             <CommandBar Grid.Column="1" VerticalAlignment="Center" x:Name="mainCommandBar" Background="Transparent" OverflowButtonVisibility="Collapsed" DefaultLabelPosition="Right" >
                 <AppBarButton x:Name="btnNew" Icon="Add" Label="New" ToolTipService.ToolTip="New Product" Click="btnNew_Click" />
             </CommandBar>
@@ -34,6 +34,7 @@
                 <DataTemplate x:DataType="models:Product">
                     <Grid Margin="0, 5" Tapped="Grid_Tapped">
                         <Grid.ColumnDefinitions>
+                            <ColumnDefinition Width="100"/>
                             <ColumnDefinition />
                             <ColumnDefinition Width="Auto"/>
                             <ColumnDefinition Width="Auto"/>
@@ -46,14 +47,18 @@
                                 </StackPanel>
                             </Flyout>
                         </FlyoutBase.AttachedFlyout>
-                        <TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{x:Bind Name}" />
-                        <Button x:Name="btnEdit" Grid.Column="1" Margin="5, 0" Click="btnEdit_Click">
+                        <Image Margin="10" x:Name="thumb" Source="{x:Bind Thumb, Converter={StaticResource bytesToImageConverter}}"/>
+                        <StackPanel Grid.Column="1">
+                            <TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{x:Bind Name}"/>
+                            <TextBlock Style="{StaticResource SubtitleTextBlockStyle}" Text="{x:Bind Category.Name}" Foreground="Gray"/>
+                        </StackPanel>
+                        <Button x:Name="btnEdit" Grid.Column="2" Margin="5, 0" Click="btnEdit_Click" Background="Transparent">
                             <StackPanel>
                                 <SymbolIcon Symbol="Edit" />
                                 <TextBlock Text="Edit" />
                             </StackPanel>
                         </Button>
-                        <Button x:Name="btnDelete" Grid.Column="2" Margin="5, 0" Click="btnDelete_Click">
+                        <Button x:Name="btnDelete" Grid.Column="3" Margin="5, 0" Click="btnDelete_Click" Background="Transparent">
                             <StackPanel>
                                 <SymbolIcon Symbol="Delete" />
                                 <TextBlock Text="Delete" />
diff --git a/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml.cs b/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml.cs
index 4fc8d8c..d105b8c 100644
--- a/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml.cs
+++ b/ShoppingList.UWP/Views/Products/ManageProductsPage.xaml.cs
@@ -17,8 +17,8 @@ namespace ShoppingList.UWP.Views.Products {
         public ProductViewModel ProductViewModel { get; set; }
 
         public ManageProductsPage() {
-            this.InitializeComponent();
             ProductViewModel = new ProductViewModel();
+            this.InitializeComponent();
         }
 
         protected override void OnNavigatedTo(NavigationEventArgs e) {
@@ -27,7 +27,7 @@ namespace ShoppingList.UWP.Views.Products {
         }
 
         private void btnNew_Click(object sender, RoutedEventArgs e) {
-            this.Frame.Navigate(typeof(ProductFormPage));
+            this.Frame.Navigate(typeof(ProductFormPage), ProductViewModel);
         }
 
         private void Grid_Tapped(object sender, TappedRoutedEventArgs e) {
diff --git a/ShoppingList.UWP/Views/Products/ProductFormPage.xaml b/ShoppingList.UWP/Views/Products/ProductFormPage.xaml
index e69d6da..3743995 100644
--- a/ShoppingList.UWP/Views/Products/ProductFormPage.xaml
+++ b/ShoppingList.UWP/Views/Products/ProductFormPage.xaml
@@ -32,12 +32,25 @@
                 <AppBarButton x:Name="btnCancel" Icon="Cancel" Label="Cancel" Click="btnCancel_Click" />
             </CommandBar>
         </Grid>
-        <StackPanel Grid.Row="1" Width="300" Padding="0, 30">
-            <TextBox x:Name="txtName" Header="Name" Text="{x:Bind ProductViewModel.ProductName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
-            <Border Background="red" Padding="5" Visibility="{x:Bind ProductViewModel.Invalid, Mode=OneWay}">
-                <TextBlock Text="Invalid product name" Foreground="White"/>
-            </Border>
-            <AutoSuggestBox x:Name="txtSuggestCategory" Header="Category" PlaceholderText="Type to search..." Margin="0,10"/>
-        </StackPanel>
+        <ScrollViewer Grid.Row="1" Padding="0, 30">
+            <StackPanel>
+                <AutoSuggestBox x:Name="txtSuggestCategory" 
+                                Header="Category name"
+                                QueryIcon="Find" 
+                                DisplayMemberPath="Name"
+                                Text="{x:Bind ProductViewModel.CategoryName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
+                                TextChanged="txtSuggestCategory_TextChanged" 
+                                QuerySubmitted="txtSuggestCategory_QuerySubmitted" 
+                                SuggestionChosen="txtSuggestCategory_SuggestionChosen"/>
+                <TextBox x:Name="txtName" Header="Product name" Width="300" 
+                         Text="{x:Bind ProductViewModel.ProductName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
+                <Image Margin="0, 10" MaxHeight="350" x:Name="thumb"
+                       Source="{x:Bind ProductViewModel.Thumb, Converter={StaticResource bytesToImageConverter}, Mode=OneWay}"/>
+                <Button Margin="0, 10" x:Name="btnUploadImage" Tapped="btnUploadImage_Tapped" Content="Choose a file..."/>
+                <Border Background="red" Padding="5" Visibility="{x:Bind ProductViewModel.Invalid, Mode=OneWay}">
+                    <TextBlock Text="Invalid product name" Foreground="White"/>
+                </Border>
+            </StackPanel>
+        </ScrollViewer>
     </Grid>
 </Page>
diff --git a/ShoppingList.UWP/Views/Products/ProductFormPage.xaml.cs b/ShoppingList.UWP/Views/Products/ProductFormPage.xaml.cs
index 6bbd538..5d9209c 100644
--- a/ShoppingList.UWP/Views/Products/ProductFormPage.xaml.cs
+++ b/ShoppingList.UWP/Views/Products/ProductFormPage.xaml.cs
@@ -1,4 +1,8 @@
 using ShoppingList.UWP.ViewModels;
+using System;
+using System.IO;
+using Windows.Storage;
+using Windows.Storage.Pickers;
 using Windows.UI.Xaml;
 using Windows.UI.Xaml.Controls;
 using Windows.UI.Xaml.Controls.Primitives;
@@ -12,6 +16,7 @@ namespace ShoppingList.UWP.Views.Products {
     /// </summary>
     public sealed partial class ProductFormPage : Page {
         public ProductViewModel ProductViewModel { get; set; }
+        public CategoryViewModel CategoryViewModel { get; set; }
         public string Title { get; set; }
 
         public ProductFormPage() {
@@ -20,6 +25,9 @@ namespace ShoppingList.UWP.Views.Products {
         }
 
         protected override void OnNavigatedTo(NavigationEventArgs e) {
+            CategoryViewModel.LoadAllAsync();
+            if (e.Parameter != null)
+                ProductViewModel = e.Parameter as ProductViewModel;
             Title = "Add Product";
             if (e.Parameter != null) {
                 ProductViewModel = e.Parameter as ProductViewModel;
@@ -40,8 +48,38 @@ namespace ShoppingList.UWP.Views.Products {
             Frame.GoBack();
         }
 
-        private void txtSuggestCategory_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) {
+        private async void txtSuggestCategory_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) {
+            var list = await ProductViewModel.LoadCategoriesByNameStartedWithAsync(sender.Text);
+            sender.ItemsSource = list;
+        }
 
+        private void txtSuggestCategory_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) {
+            if (args.ChosenSuggestion != null)
+                sender.Text = args.ChosenSuggestion.ToString();
+        }
+
+        private void txtSuggestCategory_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) {
+            sender.Text = args.SelectedItem.ToString();
+        }
+
+        private async void btnUploadImage_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e) {
+            FileOpenPicker openPicker = new FileOpenPicker();
+            openPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
+            openPicker.ViewMode = PickerViewMode.Thumbnail;
+
+            openPicker.FileTypeFilter.Clear();
+            openPicker.FileTypeFilter.Add(".jpg");
+            openPicker.FileTypeFilter.Add(".jpeg");
+            openPicker.FileTypeFilter.Add(".png");
+
+            StorageFile file = await openPicker.PickSingleFileAsync();
+            if (file != null) {
+                using (Stream stream = await file.OpenStreamForReadAsync()) {
+                    byte[] bytes = new byte[stream.Length];
+                    await stream.ReadAsync(bytes, 0, bytes.Length);
+                    ProductViewModel viewModel = new ProductViewModel();
+                }
+            }
         }
     }
 }
diff --git a/ShoppingList.UWP/Views/Users/LoginDialog.xaml b/ShoppingList.UWP/Views/Users/LoginDialog.xaml
new file mode 100644
index 0000000..7048de5
--- /dev/null
+++ b/ShoppingList.UWP/Views/Users/LoginDialog.xaml
@@ -0,0 +1,17 @@
+<ContentDialog
+    x:Class="ShoppingList.UWP.Views.Users.LoginDialog"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:ShoppingList.UWP.Views.Users"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    mc:Ignorable="d"
+    Title="TITLE"
+    PrimaryButtonText="Button1"
+    SecondaryButtonText="Button2"
+    PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
+    SecondaryButtonClick="ContentDialog_SecondaryButtonClick">
+
+    <Grid>
+    </Grid>
+</ContentDialog>
diff --git a/ShoppingList.UWP/Views/Users/LoginDialog.xaml.cs b/ShoppingList.UWP/Views/Users/LoginDialog.xaml.cs
new file mode 100644
index 0000000..76a3aff
--- /dev/null
+++ b/ShoppingList.UWP/Views/Users/LoginDialog.xaml.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// O modelo de item de Caixa de Diálogo de Conteúdo está documentado em https://go.microsoft.com/fwlink/?LinkId=234238
+
+namespace ShoppingList.UWP.Views.Users {
+    public sealed partial class LoginDialog : ContentDialog {
+        public LoginDialog() {
+            this.InitializeComponent();
+        }
+
+        private void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) {
+        }
+
+        private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) {
+        }
+    }
+}
diff --git a/ShoppingList.UWP/Views/Users/RegisterDialog.xaml b/ShoppingList.UWP/Views/Users/RegisterDialog.xaml
new file mode 100644
index 0000000..bae23b8
--- /dev/null
+++ b/ShoppingList.UWP/Views/Users/RegisterDialog.xaml
@@ -0,0 +1,33 @@
+<ContentDialog
+    x:Class="ShoppingList.UWP.Views.Users.RegisterDialog"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:ShoppingList.UWP.Views.Users"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    mc:Ignorable="d"
+    Title="Register a user"
+    PrimaryButtonText="Register"
+    SecondaryButtonText="Cancel"
+    PrimaryButtonClick="ContentDialog_PrimaryButtonClick"
+    SecondaryButtonClick="ContentDialog_SecondaryButtonClick">
+
+    <StackPanel>
+        <TextBox x:Name="txtUsername" Header="Username"
+                 Text="{x:Bind UserViewModel.User.Username, Mode=TwoWay}" />
+        <PasswordBox x:Name="txtPassword" Header="Password"
+                     Password="{x:Bind UserViewModel.User.Password, Mode=TwoWay}" />
+        <Border Background="Red" BorderBrush="Black" BorderThickness="2"
+                Margin="0,10" Padding="5"
+                Visibility="{x:Bind UserViewModel.ShowError, Mode=OneWay}">
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="48"/>
+                    <ColumnDefinition />
+                </Grid.ColumnDefinitions>
+                <FontIcon FontFamily="Segoe MDL2 Assets" Glyph="&#xE1E0;" Foreground="White" />
+                <TextBlock Grid.Column="1" Text="Register failed" Foreground="White"/>
+            </Grid>
+        </Border>
+    </StackPanel>
+</ContentDialog>
diff --git a/ShoppingList.UWP/Views/Users/RegisterDialog.xaml.cs b/ShoppingList.UWP/Views/Users/RegisterDialog.xaml.cs
new file mode 100644
index 0000000..e42b608
--- /dev/null
+++ b/ShoppingList.UWP/Views/Users/RegisterDialog.xaml.cs
@@ -0,0 +1,23 @@
+using ShoppingList.UWP.ViewModels;
+using Windows.UI.Xaml.Controls;
+
+// O modelo de item de Caixa de Diálogo de Conteúdo está documentado em https://go.microsoft.com/fwlink/?LinkId=234238
+
+namespace ShoppingList.UWP.Views.Users {
+    public sealed partial class RegisterDialog : ContentDialog {
+        public UserViewModel UserViewModel { get; set; }
+        public RegisterDialog() {
+            this.InitializeComponent();
+            UserViewModel = new UserViewModel();
+        }
+
+        private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) {
+            var deferral = args.GetDeferral();
+            args.Cancel = !await UserViewModel.DoRegisterAsync();
+            deferral.Complete();
+        }
+
+        private void ContentDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) {
+        }
+    }
+}
-- 
GitLab