Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions qrcodeprofile/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
5 changes: 5 additions & 0 deletions qrcodeprofile/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
obj/
*.sln.iml
.idea/**/*
**/.idea/*
105 changes: 105 additions & 0 deletions qrcodeprofile/Apps/ProfileApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using IvyQrCodeProfileSharing.Services;

namespace IvyQrCodeProfileSharing.Apps;

[App(icon: Icons.User, title: "Profile Creator")]
public class ProfileApp : ViewBase
{
// Profile model with basic fields for sharing
public record ProfileModel(
string FirstName,
string LastName,
string Email,
string? Phone,
string? LinkedIn,
string? GitHub
);

public override object? Build()
{
var profile = UseState(() => new ProfileModel("", "", "", null, null, null));
var qrCodeService = new QrCodeService();
var qrCodeBase64 = UseState<string>("");
var profileSubmitted = UseState<bool>(false);

var formBuilder = profile.ToForm()
.Required(m => m.FirstName, m => m.LastName, m => m.Email)
.Place(m => m.FirstName)
.Place(1, m => m.Email)
.Place(m => m.LastName)
.Place(1, m => m.LinkedIn)
.Place(m => m.Phone)
.Place(1, m => m.GitHub)
.Label(m => m.FirstName, "First Name")
.Label(m => m.LastName, "Last Name")
.Label(m => m.Email, "Email Address")
.Label(m => m.Phone, "Phone Number")
.Label(m => m.LinkedIn, "LinkedIn Profile")
.Label(m => m.GitHub, "GitHub Profile")
.Validate<string>(m => m.Email, email =>
(email.Contains("@") && email.Contains("."), "Please enter a valid email address"))
.Validate<string>(m => m.LinkedIn, linkedin =>
(string.IsNullOrEmpty(linkedin) || linkedin.Contains("linkedin.com"), "Please enter a valid LinkedIn URL"))
.Validate<string>(m => m.GitHub, github =>
(string.IsNullOrEmpty(github) || github.Contains("github.com"), "Please enter a valid GitHub URL"));

var (onSubmit, formView, validationView, loading) = formBuilder.UseForm(this.Context);

async void HandleSubmit()
{
if (await onSubmit())
{
// Generate vCard QR code for contact sharing
qrCodeBase64.Value = qrCodeService.GenerateVCardQrCodeAsBase64(
profile.Value.FirstName,
profile.Value.LastName,
profile.Value.Email,
profile.Value.Phone,
profile.Value.LinkedIn,
profile.Value.GitHub,
8
);
profileSubmitted.Value = true;
}
}

// Sidebar content - Profile form
var formContent = new Card(
Layout.Vertical().Gap(6).Padding(2)
| Text.H2("Create Your Profile")
| Text.Block("Fill in your information to create a shareable profile")
| new Separator()
| formView
| Layout.Horizontal()
| new Button("Create Profile").HandleClick(new Action(HandleSubmit))
.Loading(loading).Disabled(loading)
| validationView
).Height(Size.Full());

// Main content - QR Code display
var qrCodeContent = profileSubmitted.Value && !string.IsNullOrEmpty(qrCodeBase64.Value) ?
new Card(
Layout.Vertical().Gap(6).Padding(2)
| Text.H2("Your QR Code")
| Text.Block("Scan this QR code with your phone to automatically add this contact to your contacts:")
| (Layout.Horizontal().Align(Align.Center)
| new DemoBox(
Text.Html($"<img src=\"data:image/png;base64,{qrCodeBase64.Value}\" />")
).BorderStyle(BorderStyle.None).Width(Size.Units(70)).Height(Size.Units(70)))
).Height(Size.Full())
: new Card(
Layout.Vertical().Gap(6).Padding(2)
| (Layout.Center()
| Text.H2("Welcome to Profile Creator"))
| Text.Block("Fill out the form in the sidebar to create your shareable profile QR code.")
| Text.Block("Once you submit the form, your QR code will appear here in the main content area.")
).Height(Size.Full());

return Layout.Vertical().Height(Size.Full())
| new ResizeablePanelGroup(
new ResizeablePanel(70, formContent),
new ResizeablePanel(30, qrCodeContent)
).Horizontal();

}
}
34 changes: 34 additions & 0 deletions qrcodeprofile/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Base runtime image
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 80

# Build stage
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

# Copy and restore
COPY ["QrCodeProfile.csproj", "./"]
RUN dotnet restore "QrCodeProfile.csproj"

# Copy everything and build
COPY . .
RUN dotnet build "QrCodeProfile.csproj" -c $BUILD_CONFIGURATION -o /app/build

# Publish stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "QrCodeProfile.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=true

# Final runtime image
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

# Set environment variables
ENV PORT=80
ENV ASPNETCORE_URLS="http://+:80"

# Run the executable
ENTRYPOINT ["dotnet","./QrCodeProfile.dll"]
28 changes: 28 additions & 0 deletions qrcodeprofile/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
global using Ivy;
global using Ivy.Apps;
global using Ivy.Auth;
global using Ivy.Chrome;
global using Ivy.Client;
global using Ivy.Core;
global using Ivy.Core.Hooks;
global using Ivy.Helpers;
global using Ivy.Hooks;
global using Ivy.Shared;
global using Ivy.Views;
global using Ivy.Views.Alerts;
global using Ivy.Views.Blades;
global using Ivy.Views.Builders;
global using Ivy.Views.Charts;
global using Ivy.Views.Dashboards;
global using Ivy.Views.Forms;
global using Ivy.Views.Tables;
global using Ivy.Widgets.Inputs;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Logging;
global using System.Collections.Immutable;
global using System.ComponentModel.DataAnnotations;
global using System.Globalization;
global using System.Reactive.Linq;

namespace QrCodeProfile;
12 changes: 12 additions & 0 deletions qrcodeprofile/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using IvyQrCodeProfileSharing.Apps;

CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");
var server = new Server();
#if DEBUG
server.UseHotReload();
#endif
server.AddAppsFromAssembly();
server.AddConnectionsFromAssembly();
var chromeSettings = new ChromeSettings().DefaultApp<ProfileApp>().UseTabs(preventDuplicates: true);
server.UseChrome(chromeSettings);
await server.RunAsync();
24 changes: 24 additions & 0 deletions qrcodeprofile/QrCodeProfile.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>CS8618;CS8603;CS8602;CS8604;CS9113</NoWarn>
<RootNamespace>QrCodeProfile</RootNamespace>
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="Assets/**/*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ivy" Version="1.*" />
<PackageReference Include="QRCoder" Version="1.6.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Apps" />
<Folder Include="Connections" />
</ItemGroup>

</Project>
24 changes: 24 additions & 0 deletions qrcodeprofile/QrCodeProfile.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello", "QrCodeProfile.csproj", "{039638F3-A6D7-FB67-E733-27BD896EFDC4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{039638F3-A6D7-FB67-E733-27BD896EFDC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{039638F3-A6D7-FB67-E733-27BD896EFDC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{039638F3-A6D7-FB67-E733-27BD896EFDC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{039638F3-A6D7-FB67-E733-27BD896EFDC4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F74B1AD-6C2E-4FDA-9DAF-9F7D8053687B}
EndGlobalSection
EndGlobal
17 changes: 17 additions & 0 deletions qrcodeprofile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Hello

Web application created using [Ivy](https://github.com/Ivy-Interactive/Ivy).

Ivy is a web framework for building interactive web applications using C# and .NET.

## Run

```
dotnet watch
```

## Deploy

```
ivy deploy
```
7 changes: 7 additions & 0 deletions qrcodeprofile/Services/IQrCodeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace IvyQrCodeProfileSharing.Services;

public interface IQrCodeService
{
string GenerateQrCodeAsBase64(string text, int pixelsPerModule = 8);
string GenerateVCardQrCodeAsBase64(string firstName, string lastName, string email, string? phone = null, string? linkedin = null, string? github = null, int pixelsPerModule = 8);
}
63 changes: 63 additions & 0 deletions qrcodeprofile/Services/QrCodeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using QRCoder;
using System.Text;

namespace IvyQrCodeProfileSharing.Services;

public class QrCodeService : IQrCodeService
{
public string GenerateQrCodeAsBase64(string text, int pixelsPerModule = 8)
{
using var qrGenerator = new QRCodeGenerator();
using var qrCodeData = qrGenerator.CreateQrCode(text, QRCodeGenerator.ECCLevel.Q);
using var qrCode = new PngByteQRCode(qrCodeData);
var qrCodeBytes = qrCode.GetGraphic(pixelsPerModule);
return Convert.ToBase64String(qrCodeBytes);
}

public string GenerateVCardQrCodeAsBase64(string firstName, string lastName, string email, string? phone = null, string? linkedin = null, string? github = null, int pixelsPerModule = 8)
{
var vCard = GenerateVCard(firstName, lastName, email, phone, linkedin, github);
return GenerateQrCodeAsBase64(vCard, pixelsPerModule);
}

private static string GenerateVCard(string firstName, string lastName, string email, string? phone, string? linkedin, string? github)
{
var vCard = new StringBuilder();

// vCard header
vCard.AppendLine("BEGIN:VCARD");
vCard.AppendLine("VERSION:3.0");

// Full name
vCard.AppendLine($"FN:{firstName} {lastName}");

// Structured name (Last;First;;;)
vCard.AppendLine($"N:{lastName};{firstName};;;");

// Email
vCard.AppendLine($"EMAIL;TYPE=INTERNET:{email}");

// Phone (if provided)
if (!string.IsNullOrWhiteSpace(phone))
{
vCard.AppendLine($"TEL;TYPE=CELL:{phone}");
}

// LinkedIn URL as a URL field
if (!string.IsNullOrWhiteSpace(linkedin))
{
vCard.AppendLine($"URL;TYPE=LinkedIn:{linkedin}");
}

// GitHub URL as a URL field
if (!string.IsNullOrWhiteSpace(github))
{
vCard.AppendLine($"URL;TYPE=GitHub:{github}");
}

// vCard footer
vCard.AppendLine("END:VCARD");

return vCard.ToString();
}
}