first push

This commit is contained in:
2026-05-16 21:29:22 +02:00
commit 25edd4fac7
68 changed files with 3475 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*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
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
+1
View File
@@ -0,0 +1 @@
SA_PASSWORD=PWD_SQL
+63
View File
@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
+374
View File
@@ -0,0 +1,374 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/PhotoShareDb/Migrations/20260211083337_InitialCreate.cs
/PhotoShareDb/Migrations/20260211083337_InitialCreate.Designer.cs
/PhotoShareDb/Migrations/20260211123353_UpdateConfigDB.cs
/PhotoShareDb/Migrations/20260211123353_UpdateConfigDB.Designer.cs
/PhotoShareDb/Migrations/20260219190425_InitPostgres.cs
/PhotoShareDb/Migrations/20260219190425_InitPostgres.Designer.cs
/PhotoShareDb/Migrations/20260223141441_AddShootingTable.cs
/PhotoShareDb/Migrations/20260223141441_AddShootingTable.Designer.cs
/PhotoShareDb/Migrations/PhotoShareDbContextModelSnapshot.cs
/photoshare.tar
/testphotoshare.tar
+13
View File
@@ -0,0 +1,13 @@
# Build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app/publish
# Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "PhotoShareHelri.dll"]
+26
View File
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModelsLib.BDD_Models
{
[Table("T_DesireData")]
public class DesireData
{
public DesireData(int key, string desire)
{
Key = key;
Desire = desire;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Key { get; set; }
public string Desire { get; set; }
}
}
+31
View File
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ModelsLib
{
/// <summary>
/// classe modèle de la table de paramètrage
/// </summary>
[Table("T_Params")]
public class ParametersDb
{
public ParametersDb(string key, string value)
{
Key = key;
Value = value;
}
[Key]
public string Key { get; set; }
public string Value { get; set; }
}
}
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModelsLib
{
[Table("T_ShootingData")]
public class ShootingData
{
public ShootingData(string key, bool isPhotosSelected, int maxChoice, DateTime shootingDate, string instagramName)
{
Key = key;
IsPhotosSelected = isPhotosSelected;
MaxChoice = maxChoice;
ShootingDate = shootingDate;
InstagramName = instagramName;
}
[Key]
public string Key { get; set; }
public bool IsPhotosSelected { get; set; }
public int MaxChoice { get; set; }
public DateTime ShootingDate { get; set; }
public string InstagramName { get; set; }
}
}
+25
View File
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModelsLib
{
public class ContactModel
{
[Required(ErrorMessage = "Le nom est obligatoire")]
public string Name { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
[Required(ErrorMessage = "L'email est obligatoire")]
[EmailAddress(ErrorMessage = "Email invalide")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "Le message est obligatoire")]
[MinLength(10, ErrorMessage = "Le message doit contenir au moins 10 caractères")]
public string Message { get; set; } = string.Empty;
}
}
+25
View File
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ModelsLib
{
public class EntityHome
{
/// <summary>
/// Nom sur haut de page
/// </summary>
public string TitlePseudo { get; set; }
public string SubTitle1 { get; set; }
public string SubTitle2 { get; set; }
public string InstaLink { get; set; }
public string InstaQrCode { get; set; }
public string Credentials { get; set; }
}
}
+13
View File
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.0" />
</ItemGroup>
</Project>
+27
View File
@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
namespace ModelsLib
{
public class PhotoUnit
{
public PhotoUnit()
{
}
public PhotoUnit(string photoName, string photoPath,bool isSelected)
{
PhotoName = photoName;
PhotoPath = photoPath;
IsSelected = isSelected;
}
public string PhotoName { get; set; }
public bool IsSelected { get; set; }
public string PhotoPath { get; set; }
}
}
@@ -0,0 +1,83 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace PhotoShareDb.Migrations
{
[DbContext(typeof(PhotoShareDbContext))]
[Migration("20260315131731_AddDesireTable")]
partial class AddDesireTable
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("ModelsLib.BDD_Models.DesireData", b =>
{
b.Property<int>("Key")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Key"));
b.Property<string>("Desire")
.IsRequired()
.HasColumnType("text");
b.HasKey("Key");
b.ToTable("T_DesireData", (string)null);
});
modelBuilder.Entity("ModelsLib.ParametersDb", b =>
{
b.Property<string>("Key")
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Key");
b.ToTable("T_Params", (string)null);
});
modelBuilder.Entity("ModelsLib.ShootingData", b =>
{
b.Property<string>("Key")
.HasColumnType("text");
b.Property<string>("InstagramName")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsPhotosSelected")
.HasColumnType("boolean");
b.Property<int>("MaxChoice")
.HasColumnType("integer");
b.Property<DateTime>("ShootingDate")
.HasColumnType("timestamp with time zone");
b.HasKey("Key");
b.ToTable("T_ShootingData", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace PhotoShareDb.Migrations
{
/// <inheritdoc />
public partial class AddDesireTable : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "T_DesireData",
columns: table => new
{
Key = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
Desire = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_T_DesireData", x => x.Key);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "T_DesireData");
}
}
}
@@ -0,0 +1,83 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace PhotoShareDb.Migrations
{
[DbContext(typeof(PhotoShareDbContext))]
[Migration("20260315140529_AddDesireTable2")]
partial class AddDesireTable2
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.3")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("ModelsLib.BDD_Models.DesireData", b =>
{
b.Property<int>("Key")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Key"));
b.Property<string>("Desire")
.IsRequired()
.HasColumnType("text");
b.HasKey("Key");
b.ToTable("T_DesireData", (string)null);
});
modelBuilder.Entity("ModelsLib.ParametersDb", b =>
{
b.Property<string>("Key")
.HasColumnType("text");
b.Property<string>("Value")
.IsRequired()
.HasColumnType("text");
b.HasKey("Key");
b.ToTable("T_Params", (string)null);
});
modelBuilder.Entity("ModelsLib.ShootingData", b =>
{
b.Property<string>("Key")
.HasColumnType("text");
b.Property<string>("InstagramName")
.IsRequired()
.HasColumnType("text");
b.Property<bool>("IsPhotosSelected")
.HasColumnType("boolean");
b.Property<int>("MaxChoice")
.HasColumnType("integer");
b.Property<DateTime>("ShootingDate")
.HasColumnType("timestamp with time zone");
b.HasKey("Key");
b.ToTable("T_ShootingData", (string)null);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PhotoShareDb.Migrations
{
/// <inheritdoc />
public partial class AddDesireTable2 : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
+21
View File
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModelsLib\ModelsLib.csproj" />
</ItemGroup>
</Project>
+21
View File
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
using ModelsLib;
using ModelsLib.BDD_Models;
using System.Collections.Generic;
public class PhotoShareDbContext : DbContext
{
public PhotoShareDbContext(DbContextOptions<PhotoShareDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ParametersDb>().ToTable("T_Params");
modelBuilder.Entity<ShootingData>().ToTable("T_ShootingData");
modelBuilder.Entity<DesireData>().ToTable("T_DesireData");
}
public DbSet<ParametersDb> ConfigDb { get; set; }
public DbSet<ShootingData> ShootingDb { get; set; }
public DbSet<DesireData> DesireDb { get; set; }
}
@@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using System.IO;
public class PhotoShareDbContextFactory : IDesignTimeDbContextFactory<PhotoShareDbContext>
{
public PhotoShareDbContext CreateDbContext(string[] args)
{
// Récupère la configuration depuis appsettings.json
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
var optionsBuilder = new DbContextOptionsBuilder<PhotoShareDbContext>();
var connectionString = configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseNpgsql(connectionString);
return new PhotoShareDbContext(optionsBuilder.Options);
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ToolsPhotoShare
{
public class FileTools
{
public void TestExistingFile(string path)
{
if (!System.IO.File.Exists(Path.Combine(path, "select.csv")))
{
System.IO.File.Create(Path.Combine(path, "select.csv"));
}
}
public List<string> GetFilesSelected(string path)
{
TestExistingFile(path);
return new List<string>();
}
public static bool TestExistingShooting(string path)
{
return System.IO.Directory.Exists(path);
}
public void AddSelectedToSaveCsv(List<string> files)
{
}
}
}
@@ -0,0 +1,12 @@
{
"profiles": {
"ToolsPhotoShare": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:60332;http://localhost:60333"
}
}
}
@@ -0,0 +1,53 @@
using Microsoft.EntityFrameworkCore;
using ModelsLib;
public class ConfigAppServices
{
private readonly IDbContextFactory<PhotoShareDbContext> _factory;
public ConfigAppServices(IDbContextFactory<PhotoShareDbContext> factory)
{
_factory = factory;
}
public async Task FillConfigTableWithDataPerDefault()
{
using var db = _factory.CreateDbContext();
db.ConfigDb.AddRange(
new ParametersDb("InstaQrCode", "/Images/qr.png"),
new ParametersDb("InstaLink", "https://www.instagram.com/helrira/"),
new ParametersDb("TitlePseudo", "Kévin"),
new ParametersDb("SubTitle1", "Passionné par la photo depuis 2025, je me spécialise dans le portrait avec modèles pour capturer l'essence et l'émotion de chaque rencontre."),
new ParametersDb("SubTitle2", "J'aime que chaque séance soit cadrée tout en gardant une part de surprise, de non prévu et surtout un bon moment pour chacun."),
new ParametersDb("Credentials", "Kévin Harlay(Helrira)")
);
await db.SaveChangesAsync();
}
public async Task<bool> HasAnyParametersAsync()
{
using var db = _factory.CreateDbContext();
return await db.ConfigDb.AnyAsync();
}
public async Task<EntityHome> readParametersApplication()
{
using var db = _factory.CreateDbContext();
var confDb = await db.ConfigDb
.AsNoTracking()
.ToDictionaryAsync(p => p.Key, p => p.Value);
return new EntityHome
{
InstaQrCode = confDb["InstaQrCode"],
InstaLink = confDb["InstaLink"],
TitlePseudo = confDb["TitlePseudo"],
SubTitle1 = confDb["SubTitle1"],
SubTitle2 = confDb["SubTitle2"],
Credentials = confDb["Credentials"]
};
}
}
+25
View File
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using ModelsLib;
using System;
using System.Collections.Generic;
using System.Text;
public class DesireService
{
private readonly IDbContextFactory<PhotoShareDbContext> _factory;
private readonly IWebHostEnvironment _env;
public DesireService(IDbContextFactory<PhotoShareDbContext> factory, IWebHostEnvironment env)
{
_factory = factory;
_env = env;
}
public List<string> GetDEsireShooting()
{
using var db = _factory.CreateDbContext();
return db.DesireDb.Select(ds => ds.Desire).ToList();
}
}
+70
View File
@@ -0,0 +1,70 @@
using System.Net;
using System.Net.Mail;
using System.Text;
using Microsoft.Extensions.Configuration;
public class EmailService
{
private readonly IConfiguration _configuration;
public EmailService(IConfiguration configuration)
{
_configuration = configuration;
}
public async Task EnvoyerMailAsync(string sujet, StringBuilder contenuHtml)
{
var smtpConfig = _configuration.GetSection("Smtp");
var client = new SmtpClient
{
Host = smtpConfig["Host"],
Port = int.Parse(smtpConfig["Port"]),
EnableSsl = true,
Credentials = new NetworkCredential(
smtpConfig["User"],
smtpConfig["Password"]
)
};
var mail = new MailMessage
{
From = new MailAddress(smtpConfig["From"]),
Subject = sujet,
Body = contenuHtml.ToString(),
IsBodyHtml = true
};
mail.To.Add(smtpConfig["From"]);
await client.SendMailAsync(mail);
}
public async Task EnvoyerMailContactAsync(string Contact, string sujet, StringBuilder contenuHtml)
{
var smtpConfig = _configuration.GetSection("Smtp");
var client = new SmtpClient
{
Host = smtpConfig["Host"],
Port = int.Parse(smtpConfig["Port"]),
EnableSsl = true,
Credentials = new NetworkCredential(
smtpConfig["User"],
smtpConfig["Password"]
)
};
var mail = new MailMessage
{
From = new MailAddress(smtpConfig["From"]),
Subject = sujet,
Body = contenuHtml.ToString(),
IsBodyHtml = true
};
mail.To.Add(Contact);
await client.SendMailAsync(mail);
}
}
+83
View File
@@ -0,0 +1,83 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using ModelsLib;
using System.IO;
public class PhotoService
{
private readonly IDbContextFactory<PhotoShareDbContext> _factory;
private readonly IWebHostEnvironment _env;
public PhotoService(IDbContextFactory<PhotoShareDbContext> factory, IWebHostEnvironment env)
{
_factory = factory;
_env = env;
}
/// <summary>
///
/// </summary>
/// <param name="IdShooting"></param>
/// <param name="isSelected">Si true alors post traitement donc chargement du dossier export du dossier du shooting</param>
/// <returns></returns>
public List<string> LireContenu(string IdShooting, bool isSelected = false)
{
string path = "Photos/" + IdShooting;
if (isSelected)
{
path = path + "/export";
}
var photosPath = Path.Combine(_env.WebRootPath, path);
List<string> resultPathsAllPhotos = new List<string>();
if (Directory.Exists(photosPath))
{
var files = Directory.GetFiles(photosPath, "*.jpg");
foreach (var file in files)
{
var fullPath = file; // ton chemin complet
var relativePath = Path.GetRelativePath(_env.WebRootPath, fullPath);
resultPathsAllPhotos.Add(relativePath);
}
}
return resultPathsAllPhotos;
}
public ShootingData GetInfosShooting(string idShooting)
{
using var db = _factory.CreateDbContext();
return db.ShootingDb.Where(s => s.Key == idShooting).FirstOrDefault();
}
public async Task AddShootingInfoPerDefault(string idShooting)
{
await using var db = _factory.CreateDbContext();
db.ShootingDb.Add(
new ShootingData(idShooting, false, 20, DateTime.UtcNow, "")
);
await db.SaveChangesAsync();
}
public async Task UpdateShootingInfo(ShootingData shootingData)
{
using var db = _factory.CreateDbContext();
var entity = await db.ShootingDb.FirstOrDefaultAsync(x => x.Key == shootingData.Key);
if (entity != null)
{
entity.IsPhotosSelected = true;
await db.SaveChangesAsync();
}
}
}
+24
View File
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.3.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModelsLib\ModelsLib.csproj" />
<ProjectReference Include="..\PhotoShareDb\PhotoShareDb.csproj" />
</ItemGroup>
</Project>
+43
View File
@@ -0,0 +1,43 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.2.11408.102
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToolsPhotoShare", "PhotoShareDll\ToolsPhotoShare.csproj", "{1E146612-8436-47F6-9D54-7488DD3822FB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModelsLib", "ModelsLib\ModelsLib.csproj", "{4EC10D38-2A5D-4B3E-935E-43C552C5A16E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotoShareHelri", "PhotoShareHelri\PhotoShareHelri.csproj", "{0592C280-4C4A-455A-8C45-7DC86CF26AF0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotoShareDb", "PhotoShareDb\PhotoShareDb.csproj", "{2D1B67E3-5CB7-4A0E-B0E7-A2E31D23E003}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1E146612-8436-47F6-9D54-7488DD3822FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E146612-8436-47F6-9D54-7488DD3822FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E146612-8436-47F6-9D54-7488DD3822FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E146612-8436-47F6-9D54-7488DD3822FB}.Release|Any CPU.Build.0 = Release|Any CPU
{4EC10D38-2A5D-4B3E-935E-43C552C5A16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EC10D38-2A5D-4B3E-935E-43C552C5A16E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EC10D38-2A5D-4B3E-935E-43C552C5A16E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EC10D38-2A5D-4B3E-935E-43C552C5A16E}.Release|Any CPU.Build.0 = Release|Any CPU
{0592C280-4C4A-455A-8C45-7DC86CF26AF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0592C280-4C4A-455A-8C45-7DC86CF26AF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0592C280-4C4A-455A-8C45-7DC86CF26AF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0592C280-4C4A-455A-8C45-7DC86CF26AF0}.Release|Any CPU.Build.0 = Release|Any CPU
{2D1B67E3-5CB7-4A0E-B0E7-A2E31D23E003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D1B67E3-5CB7-4A0E-B0E7-A2E31D23E003}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D1B67E3-5CB7-4A0E-B0E7-A2E31D23E003}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D1B67E3-5CB7-4A0E-B0E7-A2E31D23E003}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EABE6739-9C04-4B68-9164-AF0564AC4D0A}
EndGlobalSection
EndGlobal
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="PhotoShareHelri.styles.css" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet" />
<link href="@Assets["_content/MudBlazor/MudBlazor.min.css"]" rel="stylesheet" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
<!-- Add chart.js reference if chart components are used in your application. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.0.1/chart.umd.js" integrity="sha512-gQhCDsnnnUfaRzD8k1L5llCCV6O9HN09zClIzzeJ8OJ9MpGmIlCxm+pdCkqTwqJ4JcjbojFr79rl2F1mzcoLMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Add chartjs-plugin-datalabels.min.js reference if chart components with data label feature is used in your application. -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js" integrity="sha512-JPcRR8yFa8mmCsfrw4TNte1ZvF1e3+1SdGMslZvmrzDYxS69J7J49vkFL8u6u8PlPJK+H3voElBtUCzaXj+6ig==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- Add sortable.js reference if SortableList component is used in your application. -->
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script>
<script src="_framework/blazor.web.js"></script>
<script src="js/download.js"></script>
</body>
</html>
@@ -0,0 +1,88 @@
@using MudBlazor
@if (IsOpen && !string.IsNullOrWhiteSpace(CurrentImage))
{
<MudOverlay Visible="true" DarkBackground="true" ZIndex="1300" @onclick="CloseFromOverlay">
<MudPaper Class="pa-4 mx-auto"
@onclick:stopPropagation="true"
Style="
width:95%;
max-width:1200px;
margin-top:5vh;
background-color:#1e1e1e;
color:white;
position:relative;
border-radius:16px;">
<div class="text-center">
<MudImage Src="@CurrentImage"
Fluid="true"
Class="mb-3"
Style="max-height:75vh; object-fit:contain;" />
<MudStack Row="true"
Justify="Justify.SpaceBetween"
AlignItems="AlignItems.Center"
Class="mt-2">
<MudButton Variant="Variant.Outlined"
Color="Color.Inherit"
OnClick="OnPrevious"
Disabled="@(!CanPrevious)">
⬅ Précédent
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Inherit"
Disabled="@(SelectedImages.Count() >= MaxSelect && !SelectedImages.Contains(CurrentImage))"
OnClick="Toggle">
@(IsSelector ? (IsSelected ? "Retirer" : "Choisir") : "Télécharger")
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Inherit"
OnClick="OnNext"
Disabled="@(!CanNext)">
Suivant ➡
</MudButton>
</MudStack>
<MudText Class="mt-3" Align="Align.Center" Typo="Typo.body1">
@(CurrentIndex + 1) / @Total
</MudText>
</div>
<MudIconButton Icon="@Icons.Material.Filled.Close"
Color="Color.Inherit"
OnClick="OnClose"
Style="position:absolute; top:10px; right:10px;" />
</MudPaper>
</MudOverlay>
}
@code {
[Parameter] public bool IsOpen { get; set; }
[Parameter] public string? CurrentImage { get; set; }
[Parameter] public bool IsSelector { get; set; }
[Parameter] public int CurrentIndex { get; set; }
[Parameter] public int Total { get; set; }
[Parameter] public HashSet<string> SelectedImages { get; set; }
[Parameter] public int MaxSelect { get; set; }
[Parameter] public bool CanNext { get; set; }
[Parameter] public bool CanPrevious { get; set; }
[Parameter] public bool IsSelected { get; set; }
[Parameter] public EventCallback OnClose { get; set; }
[Parameter] public EventCallback OnNext { get; set; }
[Parameter] public EventCallback OnPrevious { get; set; }
[Parameter] public EventCallback Toggle { get; set; }
private async Task CloseFromOverlay()
{
await OnClose.InvokeAsync();
}
}
@@ -0,0 +1,79 @@
.gallery {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.gallery-thumb {
cursor: pointer;
transition: transform 0.2s;
}
.gallery-thumb:hover {
transform: scale(1.05);
}
.header-selection {
display: flex;
align-items: center;
width: 60%;
}
.gallery-wrapper {
display: flex;
flex-direction: column;
}
.gallery-actions {
display: flex;
justify-content: flex-end;
margin: 1rem 0;
}
.titles {
flex: 1;
text-align: center;
}
.image-container {
position: relative;
display: inline-block;
width: 300px;
height: 300px;
overflow: hidden; /* cache le dépassement */
border-radius: 8px;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: contain;
background-color: #000;
transition: opacity 0.2s, border 0.2s, transform 0.2s;
}
/* Image sélectionnée */
.image-container.selected img {
opacity: 0.6;
border: 3px solid limegreen;
}
/* Bouton overlay */
.select-btn {
position: absolute;
bottom: 8px;
right: 8px;
background-color: rgba(0, 0, 0, 0.6);
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
font-size: 18px;
cursor: pointer;
transition: background-color 0.2s;
}
.select-btn:hover {
background-color: rgba(0, 128, 0, 0.8);
}
@@ -0,0 +1,29 @@
@using MudBlazor
@inherits LayoutComponentBase
@* Required *@
<MudThemeProvider />
<MudPopoverProvider />
@* Needed for dialogs *@
<MudDialogProvider />
@* Needed for snackbars *@
<MudSnackbarProvider />
<MudLayout>
<!-- Ton menu -->
<NavMenu />
<MudMainContent>
<MudBreakpointProvider>
@Body
</MudBreakpointProvider>
</MudMainContent>
</MudLayout>
<div id="blazor-error-ui">
Une erreur dans le traitement est survenue.
<a href="" class="reload">Recharger</a>
</div>
@@ -0,0 +1,112 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.main-wrapper {
flex: 1;
display: flex;
justify-content: center;
}
.content {
width: 100%;
max-width: 1200px;
}
.navbar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 1px) {
.page {
flex-direction: column;
}
.navbar {
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
.content-inner {
display: flex;
flex-direction: column;
align-items: center;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@@ -0,0 +1,175 @@
@using Microsoft.AspNetCore.Components.Routing
@using ModelsLib
@using MudBlazor
@inject NavigationManager Navigation
@rendermode InteractiveServer
@inject IWebHostEnvironment Env
@inject PhotoService ShootingDb
@inject IDialogService DialogService
<MudHidden Breakpoint="Breakpoint.MdAndDown">
<MudAppBar Color="Color.Dark" Elevation="4">
<!-- Logo -->
<MudText Typo="Typo.h6" Class="ml-2">
<MudNavLink Href="/" Class="nav-link">
Photo Sharing Helrira
</MudNavLink>
</MudText>
<MudSpacer />
<!-- Menu desktop -->
<MudNavMenu Class="d-flex">
<MudNavLink Href="/shootingdesire" Class="nav-link">
Projets
</MudNavLink>
<MudNavLink Href="/Contact" Class="nav-link">
Contact
</MudNavLink>
</MudNavMenu>
<!-- Recherche desktop -->
<MudStack Row="true" Spacing="2" Class="ml-4">
<MudTextField @bind-Value="Id"
Label="Recherche"
Variant="Variant.Outlined"
Style="color: white;"
InputStyle="color: white;" />
<MudButton OnClick="SendSelection"
Variant="Variant.Outlined"
Color="Color.Inherit">
Rechercher
</MudButton>
</MudStack>
</MudAppBar>
</MudHidden>
<MudHidden Breakpoint="Breakpoint.LgAndUp">
<!-- Drawer mobile -->
<!-- AppBar mobile -->
<MudAppBar Color="Color.Dark" Elevation="4">
<!-- Bouton burger -->
<MudIconButton Icon="@Icons.Material.Filled.Menu"
Color="Color.Default"
OnClick="@(() => _drawerOpen = true)" />
<MudText Typo="Typo.h6" Class="ml-2">
<MudNavLink Href="/" Class="nav-link">
Photo Sharing Helrira
</MudNavLink>
</MudText>
<MudSpacer />
<!-- Bouton recherche -->
<MudIconButton Icon="@Icons.Material.Filled.Search"
Color="Color.Default"
OnClick="OpenSearchDialogAsync" />
</MudAppBar>
<!-- Drawer mobile -->
<MudDrawer @bind-Open="_drawerOpen"
Anchor="Anchor.Left"
Variant="DrawerVariant.Temporary"
Color="Color.Dark"
Class="custom-drawer drawer-fixed-width">
<MudStack Spacing="2" Class="pa-3" Style="display:flex; flex-direction:column; height:100%;">
<MudNavMenu Class="flex-grow-1">
<MudNavLink Href="/shootingdesire">Projets</MudNavLink>
<MudNavLink Href="/Contact">Contact</MudNavLink>
</MudNavMenu>
</MudStack>
</MudDrawer>
</MudHidden>
@code {
private string Id { get; set; } = "";
private bool _drawerOpen = false;
private async Task OpenSearchDialogAsync()
{
try
{
var parameters = new DialogParameters();
parameters.Add("OnSearch", EventCallback.Factory.Create<string>(this, SendSelectionMobil));
var options = new DialogOptions
{
MaxWidth = MaxWidth.Small,
FullWidth = true
};
var dialogRef = await DialogService.ShowAsync<SearchDialog>("", parameters, options);
var result = await dialogRef.Result;
if (!result.Canceled)
{
Console.WriteLine("Résultat : " + result.Data);
}
}
catch (Exception ex)
{
Console.WriteLine("ERREUR DIALOG : " + ex.Message);
Console.WriteLine(ex.StackTrace);
}
}
void ToggleDrawer() => _drawerOpen = !_drawerOpen;
/// <summary>
/// a la validation de la saisie
/// </summary>
private async void SendSelectionMobil(string value)
{
Id = value;
//si une saisie est faite dans le champs
SendSelection();
}
/// <summary>
/// a la validation de la saisie
/// </summary>
private async void SendSelection()
{
//si une saisie est faite dans le champs
if (!string.IsNullOrWhiteSpace(Id))
{
string idSaisie = Id.ToLower().Trim();//passage en full minuscule + suppression espace
var path = Path.Combine(Env.WebRootPath, "Photos", idSaisie);
bool dossierExiste = Directory.Exists(path);//verification de présence du dossier
if (dossierExiste)
{
ShootingData datas = ShootingDb.GetInfosShooting(idSaisie);
if (datas == null)
{
await ShootingDb.AddShootingInfoPerDefault(idSaisie);
}
else
{
if (datas.IsPhotosSelected)
{
Navigation.NavigateTo($"/Share?Id={idSaisie}");//redirection vers le shooting post retouche
}
else
{
Navigation.NavigateTo($"/PhotoSelect?Id={idSaisie}");//redirection vers le shooting à selectionné
}
}
}
else
{
Navigation.NavigateTo($"/PhotoSelectError");//redirection vers page d'erreur
}
}
}
}
@@ -0,0 +1,105 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.drawer-fixed-width {
width: 250px; /* largeur fixe */
min-width: 250px; /* ne peut pas rétrécir */
max-width: 250px; /* ne peut pas grandir */
}
.drawer-fixed-width .mud-drawer-content {
overflow-x: hidden; /* empêche le scroll horizontal */
}
.custom-drawer.mud-shrink {
height: 100vh !important; /* prend toute la hauteur de l’écran */
min-height: 100vh !important;
max-height: 100vh !important;
overflow-y: auto; /* scroll uniquement si contenu > hauteur */
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.nav-item {
font-size: 1.1rem;
padding-bottom: 0.5rem;
font-weight: bold;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-top: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: auto;
background-color: rgba(255,255,255,0.1);
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.5);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}
@@ -0,0 +1,33 @@
@using MudBlazor
<MudDialog>
<DialogContent>
<MudTextField @bind-Value="SearchTerm"
Label="Recherche"
Variant="Variant.Outlined"
FullWidth="true" />
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Class="mt-2"
OnClick="Submit">
Rechercher
</MudButton>
</DialogContent>
</MudDialog>
@code {
private string SearchTerm { get; set; } = "";
[Parameter] public EventCallback<string> OnSearch { get; set; }
[CascadingParameter] IMudDialogInstance Dialog { get; set; }
private async Task Submit()
{
if (OnSearch.HasDelegate)
await OnSearch.InvokeAsync(SearchTerm);
Dialog?.Close(DialogResult.Ok(SearchTerm)); // IMPORTANT
}
}
@@ -0,0 +1,12 @@
.drawer-fixed-width {
width: 250px;
min-width: 250px;
max-width: 250px;
height: 100vh;
display: flex;
flex-direction: column;
}
.custom-drawer .mud-drawer-content {
overflow-y: auto;
}
@@ -0,0 +1,103 @@
@page "/Contact"
@using System.ComponentModel.DataAnnotations
@using System.Text
@using ModelsLib
@inject EmailService mailService
@inject IJSRuntime JS
@rendermode InteractiveServer
@inject NavigationManager Navigation
<!-- Contact -->
<section class="contact-section">
<MudContainer MaxWidth="MaxWidth.Small" Class="text-center">
<!-- Titre -->
<MudText Typo="Typo.h5" Class="mb-2">
Contact
</MudText>
<!-- Sous-titre -->
<MudText Typo="Typo.body1" Class="mb-6">
Une question ? Un projet ? Envie d'une collaboration ? Écrivez-moi directement.
</MudText>
<!-- Formulaire -->
<EditForm EditContext="@editContext" OnValidSubmit="EnvoyerMessage">
<DataAnnotationsValidator />
<ValidationSummary />
<MudStack Spacing="2">
<MudTextField @bind-Value="contact.Name"
Label="Votre nom"
Variant="Variant.Outlined"
Required="true" />
<MudTextField @bind-Value="contact.Email"
Label="Votre adresse mail"
Variant="Variant.Outlined"
Required="true" />
<MudTextField @bind-Value="contact.Title"
Label="Sujet du message"
Variant="Variant.Outlined" />
<MudTextField @bind-Value="contact.Message"
Label="Message"
Variant="Variant.Outlined"
Lines="6"
TextArea="true" />
<MudButton ButtonType="ButtonType.Submit"
Variant="Variant.Filled"
Color="Color.Primary"
Class="mt-4">
Laisser un message
</MudButton>
</MudStack>
</EditForm>
<!-- Message succès -->
@if (messageEnvoye)
{
<MudAlert Severity="Severity.Success" Class="mt-6">
✅ Message envoyé, merci !
</MudAlert>
}
</MudContainer>
</section>
@code {
#region properties
private ContactModel contact = new();
private EditContext? editContext;
private bool messageEnvoye = false;
#endregion
/// <summary>
/// Chargement de la page
/// </summary>
protected override async Task OnInitializedAsync()
{
editContext = new EditContext(contact);
}
/// <summary>
/// Méthode d'envoi de mail
/// </summary>
private void EnvoyerMessage()
{
var body = new StringBuilder();
body.Append("<p>").Append(contact.Message).Append("</p>");
mailService.EnvoyerMailContactAsync(contact.Email,contact.Name+" : " +contact.Title, body).GetAwaiter();
messageEnvoye = true;
contact = new ContactModel();
editContext = new EditContext(contact);
}
}
@@ -0,0 +1,61 @@
body {
}
.contact-section {
padding: 80px 20px;
background: #f4f4f4;
}
.contact-title {
font-size: 40px;
font-weight: 700;
margin-bottom: 10px;
}
.contact-subtitle {
color: #777;
margin-bottom: 50px;
}
.contact-form {
max-width: 800px;
margin: auto;
display: flex;
flex-direction: column;
gap: 20px;
}
.contact-input {
padding: 15px;
border: 1px solid #ddd;
font-size: 16px;
width: 100%;
}
.contact-textarea {
padding: 15px;
border: 1px solid #ddd;
font-size: 16px;
width: 100%;
min-height: 180px;
}
.contact-button {
width: 250px;
padding: 15px;
border: 2px solid #222;
background: white;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.contact-button:hover {
background: #222;
color: white;
}
.success-message {
margin-top: 20px;
color: green;
font-weight: 500;
}
@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}
@@ -0,0 +1,83 @@
@page "/"
@using System.ComponentModel.DataAnnotations
@using System.Text
@using ModelsLib
@using ToolsPhotoShare
@inject EmailService mailService
@inject IJSRuntime JS
@inject ConfigAppServices _configDb
@rendermode InteractiveServer
@using Microsoft.EntityFrameworkCore;
@inject NavigationManager Navigation
<PageTitle>Accueil</PageTitle>
<main class="home-container">
<!-- Hero -->
<section class="hero-section text-center">
<div class="badge">Photographe Amateur</div>
<h1 class="hero-title">Hello,<br /><span class="italic">Moi c'est @DataPage.TitlePseudo</span></h1>
<p class="hero-subtitle">
@DataPage.SubTitle1
</p>
<p class="hero-subtitle">
@DataPage.SubTitle2
</p>
</section>
<section class="social-section text-center">
<div class="social-buttons">
<NavLink href="/ShootingDesire">
<h2 class="social-title">Mes envies de shooting</h2>
</NavLink>
</div>
</section>
<!-- Social -->
<section class="social-section text-center">
<h2 class="social-title">Où me retrouver ?</h2>
<p class="social-subtitle">Suivez mon travail et mes derniers shootings</p>
<div class="social-buttons">
<a href="@DataPage.InstaLink">
<div class="qr-wrapper">
<img src="@DataPage.InstaQrCode" alt="QR Code Instagram" class="qr-image" />
</div>
</a>
</div>
</section>
</main>
<footer class="footer">
<div class="footer-line"></div>
<p>© 2026 @DataPage.Credentials — Droits réservés</p>
</footer>
@code {
#region properties
private EntityHome DataPage = new();
#endregion
/// <summary>
/// Chargement de la page
/// </summary>
protected override async Task OnInitializedAsync()
{
if (!await _configDb.HasAnyParametersAsync())
{
await _configDb.FillConfigTableWithDataPerDefault();
}
DataPage = await _configDb.readParametersApplication();
}
}
@@ -0,0 +1,209 @@
/* --- Containers --- */
.home-container {
width: 90%; /* occupe 90% de l’écran sur petits écrans */
max-width: 800px; /* limite la largeur sur grands écrans */
margin: 0 auto;
padding: 3rem 1.5rem;
display: flex;
flex-direction: column;
gap: 3rem;
overflow-y: scroll; /* Permet le scroll vertical */
scrollbar-width: none; /* Firefox */
}
.home-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
/* --- Hero --- */
.badge {
display: inline-block;
padding: 0.25rem 0.75rem;
font-size: 0.75rem;
font-weight: 500;
text-transform: uppercase;
border-radius: 9999px;
border: 1px solid #d1d5db;
color: #71717a;
}
.hero-title {
font-family: 'Playfair Display', serif;
font-size: 2.5rem;
font-weight: 500;
}
.hero-subtitle {
color: #71717a;
font-weight: 300;
line-height: 1.6;
}
.hero-image-wrapper {
position: relative;
margin-top: 3rem;
border-radius: 1rem;
overflow: hidden;
}
.hero-image {
width: 100%;
height: auto;
display: block;
}
.hero-overlay {
position: absolute;
inset: 0;
background: rgba(255,255,255,0.4);
pointer-events: none;
border-radius: 1rem;
}
.dark .hero-overlay {
background: rgba(18,18,18,0.7);
}
/* --- Social --- */
.social-title {
font-family: 'Playfair Display', serif;
font-size: 1.75rem;
font-style: italic;
}
.social-subtitle {
color: #71717a;
font-weight: 300;
}
.social-buttons {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.instagram-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 2rem;
border: 1px solid #d1d5db;
border-radius: 9999px;
background: white;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.instagram-button:hover {
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.qr-wrapper {
padding: 1.5rem;
background: white;
border: 1px solid #d1d5db;
border-radius: 1rem;
display: flex;
flex-direction: column;
align-items: center;
}
.qr-image {
width: 128px;
height: 128px;
}
.qr-text {
margin-top: 0.5rem;
font-size: 0.625rem;
color: #71717a;
letter-spacing: 0.05em;
text-transform: uppercase;
font-weight: 600;
}
/* --- Contact --- */
.contact-title {
font-family: 'Playfair Display', serif;
font-size: 2rem;
}
.contact-subtitle {
color: #71717a;
font-weight: 300;
margin-bottom: 1.5rem;
}
.contact-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.contact-input, .contact-textarea {
border: none;
border-bottom: 1px solid #d1d5db;
padding: 0.75rem 0;
font-size: 1rem;
outline: none;
background: transparent;
color: #1a1a1a;
}
.contact-input::placeholder, .contact-textarea::placeholder {
color: #d1d5db;
}
.contact-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.5rem 1.5rem;
border-radius: 9999px;
background: #1a1a1a;
color: white;
font-weight: 500;
border: none;
cursor: pointer;
margin: 0 auto;
transition: all 0.2s;
}
.contact-button:hover {
opacity: 0.9;
}
.success-message {
color: green;
font-weight: 500;
}
/* --- Footer --- */
.footer {
text-align: center;
color: #71717a;
font-size: 0.625rem;
text-transform: uppercase;
letter-spacing: 0.1em;
padding-bottom: 3rem;
}
.footer-line {
height: 1px;
width: 3rem;
background: #d1d5db;
margin: 0 auto 1rem auto;
}
/* --- Dark mode toggle --- */
.dark-toggle {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
padding: 0.75rem;
border-radius: 9999px;
background: white;
border: 1px solid #d1d5db;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
@@ -0,0 +1,9 @@
@page "/PhotoSelectError"
<h3>Oups !!!</h3>
Il semblerait que ce shooting soit inexistant ou bien que cet recherche est infructueuse.
@code {
}
@@ -0,0 +1,154 @@
@page "/Share"
@rendermode InteractiveServer
@using BlazorBootstrap;
@using Microsoft.AspNetCore.WebUtilities
@using ModelsLib
@using PhotoShareHelri.Components.Blocs
@using System.Text
@inject NavigationManager NavManager
@inject EmailService mailService
@inject PhotoService ShootingDb
@inject IJSRuntime JS
@using MudBlazor
<MudContainer MaxWidth="MaxWidth.Large" Class="d-flex justify-center mt-6 mb-6">
<MudPaper Elevation="3" Class="pa-6 selection-wrapper">
<!-- Header -->
<MudStack Spacing="1" AlignItems="AlignItems.Center" Class="mb-4 text-center">
<MudText Typo="Typo.h4">Résultat du shooting</MudText>
<MudText Typo="Typo.h6" Class="title-shoot">@Title</MudText>
</MudStack>
<!-- Galerie -->
<MudGrid Justify="Justify.Center" Spacing="3">
@for (int index = 0; index < Images.Count; index++)
{
var img = Images[index];
<MudItem xs="12" sm="6" md="4" lg="3" xl="2">
<MudPaper Class="image-card pa-2 position-relative" Elevation="2">
<MudImage Src="@img"
Alt="Photo sélection"
Class="gallery-thumb"
Fluid="true"
@onclick="() => OpenModalZoom(img)" />
</MudPaper>
</MudItem>
}
</MudGrid>
</MudPaper>
</MudContainer>
@* Modal de zoom sur une image *@
<ModalDiapo IsOpen="@IsModalZoomOpen"
CurrentImage="@CurrentImage"
CurrentIndex="@CurrentIndex"
IsSelector=false
Total="@Images.Count"
CanNext="@CanNext"
CanPrevious="@CanPrevious"
MaxSelect="@MaxSelect"
SelectedImages="@SelectedImages"
IsSelected="@(CurrentImage != null && @SelectedImages.Contains(@CurrentImage))"
OnClose="CloseModalZoom"
OnNext="NextModalZoom"
OnPrevious="PreviousModalZoom"
Toggle="() => ToggleImage(CurrentImage!)" />
@code {
#region Variables
HashSet<string> SelectedImages = new();
private int MaxSelect = 1;
// Liste des images disponibles
private List<string> Images = new();
public string Title { get; set; }
bool IsModalZoomOpen = false;
string? CurrentImage;
int CurrentIndex =>
CurrentImage == null ? -1 : Images.IndexOf(CurrentImage);
bool CanNext => CurrentIndex >= 0 && CurrentIndex < Images.Count - 1;
bool CanPrevious => CurrentIndex > 0;
private string ShootingId;
#endregion
#region modal zoom
/// <summary>
/// Ouverture de la modal à partir du clic sur image voulu
/// </summary>
void OpenModalZoom(string img)
{
CurrentImage = img;
IsModalZoomOpen = true;
}
///<summary>
/// fermeture de la modal
/// </summary>
void CloseModalZoom()
{
IsModalZoomOpen = false;
}
/// <summary>
/// photo suivante dans la modal
/// dans la liste des photos affichés
/// </summary>
void NextModalZoom()
{
if (CanNext)
CurrentImage = Images[CurrentIndex + 1];
}
/// <summary>
/// photo précédente
/// </summary>
void PreviousModalZoom()
{
if (CanPrevious)
CurrentImage = Images[CurrentIndex - 1];
}
#endregion
/// <summary>
/// arrivé sur la page
/// </summary>
protected override async Task OnInitializedAsync()
{
//récupération de l'identifiant de shooting, si identifiant null ou si recherche inexistante redirection vers la page de recherche de shooting
var uri = NavManager.ToAbsoluteUri(NavManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("Id", out var id))
{
if (!string.IsNullOrEmpty(id))
{
ShootingId = id;
Images = ShootingDb.LireContenu(ShootingId, true);
}
}
// Code exécuté au chargement du composant
}
/// <summary>
/// Déclenche le téléchargement de l'image originale sur la modal de visualisation
/// </summary>
/// <param name="img">image sélectionné par son hash</param>
private async Task ToggleImage(string img)
{
var filePath = $"{img}"; // chemin dans wwwroot
await JS.InvokeVoidAsync("downloadFile", filePath, $"{img}");
}
}
@@ -0,0 +1,80 @@
.selection-wrapper {
width: 100%;
max-width: 1400px;
border-radius: 20px;
}
.title-shoot {
opacity: 0.85;
}
.image-card {
border-radius: 16px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
background-color: white;
}
.image-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.gallery-thumb {
width: 100%;
height: 250px;
object-fit: cover;
border-radius: 12px;
cursor: pointer;
display: block;
}
.select-fab {
position: absolute !important;
top: 14px;
right: 14px;
z-index: 10;
}
/* Mobile */
@media (max-width: 768px) {
.selection-wrapper {
padding: 20px 12px !important;
border-radius: 14px;
}
.gallery-thumb {
height: 220px;
}
}
.select-icon-btn {
position: absolute !important;
top: 12px;
right: 12px;
z-index: 10;
background-color: rgba(255, 255, 255, 0.92) !important;
backdrop-filter: blur(4px);
border-radius: 50%;
box-shadow: 0 4px 12px rgba(0,0,0,0.18);
transition: all 0.2s ease;
}
.select-icon-btn:hover {
transform: scale(1.08);
}
.select-icon-btn.selected {
border: 2px solid #2e7d32;
color: #2e7d32 !important;
}
.select-icon-btn.not-selected {
border: 2px solid #c62828;
color: #c62828 !important;
}
.select-icon-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
@@ -0,0 +1,224 @@
@page "/PhotoSelect"
@rendermode InteractiveServer
@using BlazorBootstrap;
@using Microsoft.AspNetCore.WebUtilities
@using ModelsLib
@using PhotoShareHelri.Components.Blocs
@using System.Text
@inject NavigationManager NavManager
@inject EmailService mailService
@inject PhotoService ShootingDb
@inject IDialogService DialogService
@using MudBlazor
<MudContainer MaxWidth="MaxWidth.Large" Class="d-flex justify-center mt-6 mb-6">
<MudPaper Elevation="3" Class="pa-6 selection-wrapper">
<!-- Header -->
<MudStack Spacing="1" AlignItems="AlignItems.Center" Class="mb-4 text-center">
<MudText Typo="Typo.h4">Sélection des photos</MudText>
<MudText Typo="Typo.h6" Class="title-shoot">@Title</MudText>
</MudStack>
<!-- Action top -->
<MudStack AlignItems="AlignItems.Center" Class="mb-3">
<MudButton Variant="Variant.Outlined"
Color="Color.Dark"
Disabled="@(SelectedImages.Count == 0)"
OnClick="SendSelection">
Valider la sélection
</MudButton>
</MudStack>
<!-- Compteur -->
<MudText Typo="Typo.body1" Align="Align.Center" Class="mb-4">
@SelectedImages.Count / @MaxSelect photo(s) sélectionnée(s)
</MudText>
<!-- Galerie -->
<MudGrid Justify="Justify.Center" Spacing="3">
@for (int index = 0; index < Images.Count; index++)
{
var img = Images[index];
var isSelected = SelectedImages.Contains(img);
var buttonClass = $"select-icon-btn {(isSelected ? "selected" : "not-selected")}";
<MudItem xs="12" sm="6" md="4" lg="3" xl="2">
<MudPaper Class="image-card pa-2 position-relative" Elevation="2">
<MudImage Src="@img"
Alt="Photo sélection"
Class="gallery-thumb"
Fluid="true"
@onclick="() => OpenModalZoom(img)" />
<div @onclick:stopPropagation="true">
<MudIconButton Icon="@(isSelected? Icons.Material.Filled.Check : Icons.Material.Outlined.RadioButtonUnchecked)"
Class="@buttonClass"
Disabled="@(SelectedImages.Count >= MaxSelect && !isSelected)"
OnClick="() => ToggleImage(img)" />
</div>
</MudPaper>
</MudItem>
}
</MudGrid>
<!-- Action bottom -->
<MudStack AlignItems="AlignItems.Center" Class="mt-5">
<MudButton Variant="Variant.Outlined"
Disabled="@(SelectedImages.Count == 0)"
Color="Color.Dark"
OnClick="SendSelection">
Valider la sélection
</MudButton>
</MudStack>
</MudPaper>
</MudContainer>
<ModalDiapo IsOpen="@IsModalZoomOpen"
CurrentImage="@CurrentImage"
CurrentIndex="@CurrentIndex"
IsSelector="true"
Total="@Images.Count"
CanNext="@CanNext"
CanPrevious="@CanPrevious"
MaxSelect="@MaxSelect"
SelectedImages="@SelectedImages"
IsSelected="@(CurrentImage != null && SelectedImages.Contains(CurrentImage))"
OnClose="CloseModalZoom"
OnNext="NextModalZoom"
OnPrevious="PreviousModalZoom"
Toggle="() => ToggleImage(CurrentImage!)" />
@code {
#region Variables
HashSet<string> SelectedImages = new();
private int MaxSelect = 1;
// Liste des images disponibles
private List<string> Images = new();
public string Title { get; set; }
bool IsModalZoomOpen = false;
string? CurrentImage;
int CurrentIndex =>
CurrentImage == null ? -1 : Images.IndexOf(CurrentImage);
bool CanNext => CurrentIndex >= 0 && CurrentIndex < Images.Count - 1;
bool CanPrevious => CurrentIndex > 0;
private string ShootingId;
private ShootingData Datas { get; set; }
#endregion
#region modal zoom
/// <summary>
/// Ouverture de la modal à partir du clic sur image voulu
/// </summary>
void OpenModalZoom(string img)
{
CurrentImage = img;
IsModalZoomOpen = true;
}
///<summary>
/// fermeture de la modal
/// </summary>
void CloseModalZoom()
{
IsModalZoomOpen = false;
}
/// <summary>
/// photo suivante dans la modal
/// dans la liste des photos affichés
/// </summary>
void NextModalZoom()
{
if (CanNext)
CurrentImage = Images[CurrentIndex + 1];
}
/// <summary>
/// photo précédente
/// </summary>
void PreviousModalZoom()
{
if (CanPrevious)
CurrentImage = Images[CurrentIndex - 1];
}
#endregion
/// <summary>
/// arrivé sur la page
/// </summary>
protected override async Task OnInitializedAsync()
{
//récupération de l'identifiant de shooting, si identifiant null ou si recherche inexistante redirection vers la page de recherche de shooting
var uri = NavManager.ToAbsoluteUri(NavManager.Uri);
if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("Id", out var id))
{
Datas = ShootingDb.GetInfosShooting(id);
if (!string.IsNullOrEmpty(id))
{
ShootingId = id;
Images = ShootingDb.LireContenu(ShootingId);
}
Title = "Résultat du shooting " + Datas.Key;
}
}
/// <summary>
/// Séléction de l'image dans la liste des photos choisies
/// </summary>
/// <param name="img">image sélectionné par son hash</param>
private void ToggleImage(string img)
{
if (SelectedImages.Contains(img))
{
SelectedImages.Remove(img);
}
else
{
SelectedImages.Add(img);
}
}
/// <summary>
/// envoi par mail de la sélection des photos
/// </summary>
private async void SendSelection()
{
if (SelectedImages == null || SelectedImages.Count == 0)
return;
var fichiers = string.Join(";", SelectedImages)
.Split(';', StringSplitOptions.RemoveEmptyEntries)
.Select(Path.GetFileName)
.ToList();
var body = new StringBuilder();
body.Append("<h3>Photos sélectionnées 📸</h3>");
body.Append("<ul>");
foreach (var fichier in fichiers)
{
body.Append($"<li>{fichier}</li>");
}
body.Append("</ul>");
mailService.EnvoyerMailAsync("Confirmation sélection du shooting" + ShootingId, body).GetAwaiter();
Datas.IsPhotosSelected = true;
await ShootingDb.UpdateShootingInfo(Datas);
}
}
@@ -0,0 +1,80 @@
.selection-wrapper {
width: 100%;
max-width: 1400px;
border-radius: 20px;
}
.title-shoot {
opacity: 0.85;
}
.image-card {
border-radius: 16px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
background-color: white;
}
.image-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.gallery-thumb {
width: 100%;
height: 250px;
object-fit: cover;
border-radius: 12px;
cursor: pointer;
display: block;
}
.select-fab {
position: absolute !important;
top: 14px;
right: 14px;
z-index: 10;
}
/* Mobile */
@media (max-width: 768px) {
.selection-wrapper {
padding: 20px 12px !important;
border-radius: 14px;
}
.gallery-thumb {
height: 220px;
}
}
.select-icon-btn {
position: absolute !important;
top: 12px;
right: 12px;
z-index: 10;
background-color: rgba(255, 255, 255, 0.92) !important;
backdrop-filter: blur(4px);
border-radius: 50%;
box-shadow: 0 4px 12px rgba(0,0,0,0.18);
transition: all 0.2s ease;
}
.select-icon-btn:hover {
transform: scale(1.08);
}
.select-icon-btn.selected {
border: 2px solid #2e7d32;
color: #2e7d32 !important;
}
.select-icon-btn.not-selected {
border: 2px solid #c62828;
color: #c62828 !important;
}
.select-icon-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
@@ -0,0 +1,58 @@
@page "/ShootingDesire"
@rendermode InteractiveServer
@inject IWebHostEnvironment Env
@inject DesireService DesireDb
<PageTitle>Mes Envies de shooting futur</PageTitle>
<main class="home-container">
<section class="shooting-section">
<MudContainer MaxWidth="MaxWidth.Medium" Class="text-center">
<!-- Titre -->
<MudText Typo="Typo.h4" Class="mb-4">
Mes envies de shootings
</MudText>
<!-- Intro -->
<MudText Typo="Typo.body1" Class="mb-6">
Envie de partager un moment ensemble ?
Un de ces projets vous parle et vous souhaiteriez qu'on le réalise ensemble ?
Contactez-moi afin que l'on puisse en discuter et mettre
tout en œuvre pour la réalisation !
</MudText>
<!-- Cartes -->
<MudGrid Justify="Justify.Center" Spacing="3">
<MudItem xs="12" sm="8" md="6">
<MudPaper Class="pa-4 text-center" Elevation="2">
@foreach (var envie in EnviesShooting)
{
<MudText Typo="Typo.body1">
📷 @envie
</MudText>
}
<!-- Texte bas -->
<MudText Typo="Typo.body2" Class="mt-6">
Et bien d'autres encore !!!
</MudText>
</MudPaper>
</MudItem>
</MudGrid>
</MudContainer>
</section>
</main>
@code {
private List<string> EnviesShooting;
protected override void OnInitialized()
{
EnviesShooting = DesireDb.GetDEsireShooting();
}
}
@@ -0,0 +1,133 @@
.social-title {
font-family: 'Playfair Display', serif;
font-size: 1.75rem;
font-style: italic;
}
/* --- Containers --- */
.home-container {
width: 90%; /* occupe 90% de l’écran sur petits écrans */
max-width: 800px; /* limite la largeur sur grands écrans */
margin: 0 auto;
padding: 3rem 1.5rem;
display: flex;
flex-direction: column;
gap: 3rem;
overflow-y: scroll; /* Permet le scroll vertical */
scrollbar-width: none; /* Firefox */
}
.home-container::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
.social-subtitle {
color: #71717a;
font-weight: 300;
}
.social-buttons {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
}
.instagram-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 2rem;
border: 1px solid #d1d5db;
.title {
margin-top: 40px;
margin-bottom: 20px;
}
.intro {
max-width: 800px;
margin: auto;
margin-bottom: 40px;
}
.shooting-container {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(220px,1fr));
gap: 20px;
max-width: 900px;
margin: auto;
}
.shooting-card {
background: white;
border-radius: 12px;
padding: 15px;
box-shadow: 0 6px 15px rgba(0,0,0,0.15);
transition: transform 0.2s, box-shadow 0.2s;
font-weight: 500;
}
.shooting-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0,0,0,0.25);
}
border-radius: 9999px;
background: white;
font-weight: 500;
text-decoration: none;
transition: all 0.2s;
}
.instagram-button:hover {
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.qr-wrapper {
padding: 1.5rem;
background: white;
border: 1px solid #d1d5db;
border-radius: 1rem;
display: flex;
flex-direction: column;
align-items: center;
}
.qr-image {
width: 128px;
height: 128px;
}
.qr-text {
margin-top: 0.5rem;
font-size: 0.625rem;
color: #71717a;
letter-spacing: 0.05em;
text-transform: uppercase;
font-weight: 600;
}
.gallery-wrapper {
display: flex;
flex-direction: column;
}
.header-selection {
display: flex;
align-items: center;
width: 60%;
}
.titles {
flex: 1;
text-align: center;
}
.shooting-box {
border: 2px solid #444;
border-radius: 12px;
padding: 20px;
max-width: 500px;
margin: auto;
background-color: #f8f8f8;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
+6
View File
@@ -0,0 +1,6 @@
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
+11
View File
@@ -0,0 +1,11 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using PhotoShareHelri
@using PhotoShareHelri.Components
@using MudBlazor
+13
View File
@@ -0,0 +1,13 @@
# Build
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app/publish
# Runtime
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "PhotoShareHelri.dll"]
+36
View File
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>a841ad02-d180-4ec3-90a2-908747712f4c</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Compile Remove="wwwroot\bootstrap\**" />
<Content Remove="wwwroot\bootstrap\**" />
<EmbeddedResource Remove="wwwroot\bootstrap\**" />
<None Remove="wwwroot\bootstrap\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Blazor.Bootstrap" Version="3.5.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageReference Include="MudBlazor" Version="9.2.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModelsLib\ModelsLib.csproj" />
<ProjectReference Include="..\PhotoShareDb\PhotoShareDb.csproj" />
<ProjectReference Include="..\PhotoShareDll\ToolsPhotoShare.csproj" />
</ItemGroup>
</Project>
+40
View File
@@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore;
using MudBlazor.Services;
using Npgsql;
using PhotoShareHelri.Components;
using System;
using ToolsPhotoShare;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddMudServices();
builder.Services.AddScoped<EmailService>();
builder.Services.AddDbContextFactory<PhotoShareDbContext>(options =>
options.UseNpgsql(
builder.Configuration.GetConnectionString("DefaultConnection")
));
builder.Services.AddScoped<ConfigAppServices>();
builder.Services.AddScoped<PhotoService>();
builder.Services.AddScoped<DesireService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();
@@ -0,0 +1,49 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5262"
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7042;http://localhost:5262"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:17310",
"sslPort": 44351
}
}
}
@@ -0,0 +1,22 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Smtp": {
"Host": "smtp.gmail.com",
"Port": 587,
"User": "harlay.k1@gmail.com",
"Password": "zned sjls vypl bqgo",
"From": "harlay.k1@gmail.com"
},
"ConnectionStrings": {
"DefaultConnection": "Host=192.168.1.50;Port=5433;Database=PhotoShareDB;Username=Helri;Password=HarKev1191."
//"DefaultConnection": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=PhotoShareDB;Integrated Security=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=False;Application Intent=ReadWrite;Multi Subnet Failover=False;Command Timeout=30"
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Smtp": {
"Host": "smtp.gmail.com",
"Port": 587,
"User": "harlay.k1@gmail.com",
"Password": "zned sjls vypl bqgo",
"From": "harlay.k1@gmail.com"
},
"ConnectionStrings": {
"DefaultConnection": "Host=192.168.1.50;Port=5433;Database=PhotoShareDB;Username=Helri;Password=HarKev1191."
}
}
@@ -0,0 +1,21 @@
Dans les dunes
Cosplay
Foret tenue transparente fée nature
Tenue guerrier cote de maille
Sous leau
Piscine / sauna
Geisha
Pin-up
Fermier/ière
Femme enceinte
Tenue de sport annee 80
Halloween sorcière
Cabane en forêt
Avec drap soie transparence
Ballerine (dans un context anormal)
Tenue de soirée haut de gamme soirée mondaine
Style Grèce antique/ déesse
Toit immeuble domination monde
Cafe en terasse
Horreur
Bain de lait
Binary file not shown.

After

Width:  |  Height:  |  Size: 713 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

+51
View File
@@ -0,0 +1,51 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #006bb7;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+8
View File
@@ -0,0 +1,8 @@
window.downloadFile = (url, fileName) => {
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
+1
View File
@@ -0,0 +1 @@
# PhotoShareHelri