Compare commits
No commits in common. "nya" and "FinalFinalAstheticIpromiseEric" have entirely different histories.
nya
...
FinalFinal
32 changed files with 329 additions and 867 deletions
35
Dockerfile
35
Dockerfile
|
@ -1,35 +0,0 @@
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine AS build
|
|
||||||
|
|
||||||
WORKDIR /source
|
|
||||||
|
|
||||||
COPY Interlinked.sln ./
|
|
||||||
|
|
||||||
COPY src/Interlinked.Core/*.csproj src/Interlinked.Core/
|
|
||||||
COPY src/Interlinked.Shared/*.csproj src/Interlinked.Shared/
|
|
||||||
COPY src/Interlinked.User/*.csproj src/Interlinked.User/
|
|
||||||
copy test/Interlinked.Test/*.csproj test/Interlinked.Test/
|
|
||||||
|
|
||||||
RUN dotnet restore
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN dotnet publish ./src/Interlinked.User -c Release -o /build/interlinked
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:7.0-alpine AS runtime
|
|
||||||
|
|
||||||
|
|
||||||
COPY --from=build /build/interlinked /srv/interlinked
|
|
||||||
|
|
||||||
VOLUME [ "/data", "/srv/log" ]
|
|
||||||
|
|
||||||
WORKDIR /srv/interlinked
|
|
||||||
|
|
||||||
ENV ASPNETCORE_ENVIRONMENT=Production
|
|
||||||
|
|
||||||
# Net
|
|
||||||
|
|
||||||
EXPOSE 80/tcp
|
|
||||||
|
|
||||||
STOPSIGNAL SIGTERM
|
|
||||||
|
|
||||||
ENTRYPOINT [ "dotnet", "/srv/interlinked/Interlinked.User.dll" ]
|
|
|
@ -11,3 +11,8 @@ meow
|
||||||
### Health & Wellbeing
|
### Health & Wellbeing
|
||||||
|
|
||||||
This solution addresses the issue of social isolation that many developers face, by encouraging socialization with other developers and in-person collaboration. Contact with others is an elemental human need and social isolation may be detrimental to mental health. Encouraging socially active form of software development contributes to a healthy lifestyle.
|
This solution addresses the issue of social isolation that many developers face, by encouraging socialization with other developers and in-person collaboration. Contact with others is an elemental human need and social isolation may be detrimental to mental health. Encouraging socially active form of software development contributes to a healthy lifestyle.
|
||||||
|
|
||||||
|
## Open Fintech
|
||||||
|
|
||||||
|
Interlinked fosters local development and enables developers to discover and collaborate on local projects. It also enables financial sponsorship of the projects, thus being capable of investing both work and fund for the project. This combined, may increase the success rate of projects, which become community investments.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<Router AppAssembly="@typeof(App).Assembly">
|
<Router AppAssembly="@typeof(App).Assembly">
|
||||||
<Found Context="routeData">
|
<Found Context="routeData">
|
||||||
<AuthenticateUser />
|
|
||||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||||
</Found>
|
</Found>
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
@page "/dashboard"
|
|
||||||
@using System.ComponentModel.DataAnnotations;
|
|
||||||
@using System.Net
|
|
||||||
@inject HttpClient req
|
|
||||||
@inject UserManager usr
|
|
||||||
|
|
||||||
<PageTitle>Dashboard</PageTitle>
|
|
||||||
|
|
||||||
<h1>Your profile</h1>
|
|
||||||
|
|
||||||
@if (!string.IsNullOrWhiteSpace(_err))
|
|
||||||
{
|
|
||||||
<p class="text-danger">@_err</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="profile">
|
|
||||||
<h2>Profile settings</h2>
|
|
||||||
<EditForm class="form" Model="@_pdr" OnValidSubmit="@UpdateProfile">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="cc">Country</label>
|
|
||||||
<InputText @bind-Value="_pdr.CC" id="cc" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="city">City</label>
|
|
||||||
<InputText @bind-Value="_pdr.City" id="city" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="bio">Bio</label>
|
|
||||||
<InputText @bind-Value="_pdr.Bio" id="bio" />
|
|
||||||
</div>
|
|
||||||
<button type="submit">Update profile</button>
|
|
||||||
</EditForm>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="security">
|
|
||||||
<h2>Security settings</h2>
|
|
||||||
|
|
||||||
<EditForm class="form" Model="@_pwc" OnValidSubmit="@ChangePassword">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="oldpw">Old password</label>
|
|
||||||
<InputText @bind-Value="_pwc.PasswordOld" id="oldpw" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="npw">New password</label>
|
|
||||||
<InputText @bind-Value="_pwc.PasswordNew" id="npw" />
|
|
||||||
</div>
|
|
||||||
<button type="submit">Change password</button>
|
|
||||||
</EditForm>
|
|
||||||
|
|
||||||
<EditForm class="form" Model="@_emc" OnValidSubmit="@ChangeEmail">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="oldpw">Email</label>
|
|
||||||
<InputText @bind-Value="_emc.Email" id="eml" />
|
|
||||||
</div>
|
|
||||||
<button type="submit">Change email</button>
|
|
||||||
</EditForm>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private string _err = "";
|
|
||||||
private PasswordChangeRequest _pwc = new();
|
|
||||||
private EmailChangeRequest _emc = new();
|
|
||||||
private ProfileRequest _pdr = new();
|
|
||||||
|
|
||||||
public record PasswordChangeRequest
|
|
||||||
{
|
|
||||||
[Required]
|
|
||||||
public string PasswordOld { get; set; } = default!;
|
|
||||||
[Required]
|
|
||||||
public string PasswordNew { get; set; } = default!;
|
|
||||||
};
|
|
||||||
|
|
||||||
public record EmailChangeRequest
|
|
||||||
{
|
|
||||||
[Required]
|
|
||||||
[EmailAddress]
|
|
||||||
public string Email { get; set; } = default!;
|
|
||||||
}
|
|
||||||
|
|
||||||
public record ProfileRequest
|
|
||||||
{
|
|
||||||
public string? Bio { get; set; }
|
|
||||||
public string? CC { get; set; }
|
|
||||||
public string? City { get; set; }
|
|
||||||
public ICollection<string> Interests { get; set; } = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async override Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ChangePassword()
|
|
||||||
{
|
|
||||||
var resp = await req.PostAsJsonAsync("/api/auth/changepass", _pwc);
|
|
||||||
if (!resp.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
_err = await resp.Content.ReadAsStringAsync();
|
|
||||||
}
|
|
||||||
this._pwc = new();
|
|
||||||
this.StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ChangeEmail()
|
|
||||||
{
|
|
||||||
var resp = await req.PostAsJsonAsync("/api/auth/changemail", _emc);
|
|
||||||
if (!resp.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
_err = await resp.Content.ReadAsStringAsync();
|
|
||||||
}
|
|
||||||
this._emc = new();
|
|
||||||
this.StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task UpdateProfile()
|
|
||||||
{
|
|
||||||
var resp = await req.PostAsJsonAsync("/api/auth/profile/update", _pdr);
|
|
||||||
if (!resp.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
_err = await resp.Content.ReadAsStringAsync();
|
|
||||||
}
|
|
||||||
this._pdr = new();
|
|
||||||
this.StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
57
src/Interlinked.Core/Pages/FetchData.razor
Normal file
57
src/Interlinked.Core/Pages/FetchData.razor
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
@page "/fetchdata"
|
||||||
|
@inject HttpClient Http
|
||||||
|
|
||||||
|
<PageTitle>Weather forecast</PageTitle>
|
||||||
|
|
||||||
|
<h1>Weather forecast</h1>
|
||||||
|
|
||||||
|
<p>This component demonstrates fetching data from the server.</p>
|
||||||
|
|
||||||
|
@if (forecasts == null)
|
||||||
|
{
|
||||||
|
<p><em>Loading...</em></p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Temp. (C)</th>
|
||||||
|
<th>Temp. (F)</th>
|
||||||
|
<th>Summary</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var forecast in forecasts)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>@forecast.Date.ToShortDateString()</td>
|
||||||
|
<td>@forecast.TemperatureC</td>
|
||||||
|
<td>@forecast.TemperatureF</td>
|
||||||
|
<td>@forecast.Summary</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private WeatherForecast[]? forecasts;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WeatherForecast
|
||||||
|
{
|
||||||
|
public DateOnly Date { get; set; }
|
||||||
|
|
||||||
|
public int TemperatureC { get; set; }
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
|
||||||
|
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,9 +16,15 @@
|
||||||
</MapsLayers>
|
</MapsLayers>
|
||||||
</SfMaps>
|
</SfMaps>
|
||||||
|
|
||||||
@code {
|
@code{
|
||||||
|
public class City
|
||||||
|
{
|
||||||
|
public double Latitude { get; set; }
|
||||||
|
public double Longitude { get; set; }
|
||||||
|
public string Name { get; set; } = default!;
|
||||||
|
}
|
||||||
private List<City> Cities = new List<City> {
|
private List<City> Cities = new List<City> {
|
||||||
new City { Latitude = 34.060620, Longitude = -118.330491, Name="California" },
|
new City { Latitude = 34.060620, Longitude = -118.330491, Name="California" },
|
||||||
new City{ Latitude = 40.724546, Longitude = -73.850344, Name="New York"}
|
new City{ Latitude = 40.724546, Longitude = -73.850344, Name="New York"}
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -40,15 +40,7 @@
|
||||||
public LoginModel Model { get; set; } = new();
|
public LoginModel Model { get; set; } = new();
|
||||||
public string ValidationMsg = string.Empty;
|
public string ValidationMsg = string.Empty;
|
||||||
|
|
||||||
protected async override Task OnInitializedAsync()
|
protected override void OnInitialized() => Model ??= new();
|
||||||
{
|
|
||||||
if (await usr.GetUser() != null)
|
|
||||||
{
|
|
||||||
nav.NavigateTo("/");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Model ??= new();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Submit()
|
private async Task Submit()
|
||||||
{
|
{
|
||||||
|
@ -64,9 +56,9 @@
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
usr.SetUser(user);
|
usr.User = user;
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
nav.NavigateTo("/", true);
|
nav.NavigateTo("/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
var resp = await req.GetAsync("/api/auth/logout");
|
var resp = await req.GetAsync("/api/auth/logout");
|
||||||
if (resp.StatusCode == HttpStatusCode.NoContent)
|
if (resp.StatusCode == HttpStatusCode.NoContent)
|
||||||
{
|
{
|
||||||
usr.SetUser(null!);
|
usr.User = null!;
|
||||||
}
|
}
|
||||||
this.StateHasChanged();
|
nav.NavigateTo("/login");
|
||||||
nav.NavigateTo("/login", true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,15 +52,7 @@
|
||||||
public LoginModel Model { get; set; } = new();
|
public LoginModel Model { get; set; } = new();
|
||||||
public string ValidationMsg = string.Empty;
|
public string ValidationMsg = string.Empty;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override void OnInitialized() => Model ??= new();
|
||||||
{
|
|
||||||
if (await usr.GetUser() != null)
|
|
||||||
{
|
|
||||||
nav.NavigateTo("/", true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Model ??= new();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Submit()
|
private async Task Submit()
|
||||||
{
|
{
|
||||||
|
@ -75,9 +67,9 @@
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
usr.SetUser(user);
|
usr.User = user;
|
||||||
this.StateHasChanged();
|
this.StateHasChanged();
|
||||||
nav.NavigateTo("/", true);
|
nav.NavigateTo("/");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,66 +1,37 @@
|
||||||
@page "/profile/{username}"
|
@page "/user/{username}"
|
||||||
@inject HttpClient cli
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public string Username { get; set; } = "johndoeUwU";
|
public string Username { get; set; } = "johndoeUwU";
|
||||||
private UserModel _user = null!;
|
private string FullName = "meow uwu";
|
||||||
private bool _valid = true;
|
private string Bio = "Software Developer";
|
||||||
|
private List<string> Skills = new List<string> { "C#", "ASP.NET Core", "Blazor" };
|
||||||
|
private string aboutme = "lorum ipsum, lorum ipsum, lorum ipsum, lorum ipsum, lorum ipsum, lorum ipsum.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!_valid)
|
<div class="user-profile">
|
||||||
{
|
<img class="profile-img" src="images/DefaultUserProfile.jpg" alt="Profile Picture" />
|
||||||
<h1>User not found @(":<")</h1>
|
<img class="background-img" src="images/DefaultBanner.png" alt="Background" />
|
||||||
}
|
<div class="userinfo">
|
||||||
else
|
<div class = "vertidiv">
|
||||||
{
|
<div class="profile-names">
|
||||||
if (_user != null)
|
<h1 class="profile-username">@($"@{Username}")</h1>
|
||||||
{
|
<h3 class="profile-fullname">(@FullName)</h3>
|
||||||
<div class="user-profile">
|
|
||||||
<div class="bkg-img">
|
|
||||||
<img class="background" src="images/background.jpg" alt="Background" />
|
|
||||||
<img class="profile-img" src="images/pfp.jpg" alt="Profile Picture" />
|
|
||||||
</div>
|
|
||||||
<div class="profile-names">
|
|
||||||
<h1 class="profile-username">@($"@{_user.Username}")</h1>
|
|
||||||
@if (_user.Name != null)
|
|
||||||
{
|
|
||||||
<h3 class="profile-fullname">(@_user.Name)</h3>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
@if (_user.City != null && _user.CountryCode != null)
|
<p>@Bio</p>
|
||||||
{
|
<h2>Skills</h2>
|
||||||
<div class="profile-location-info">
|
<ul>
|
||||||
<span class="profile-loc">@($"{_user.City}, {_user.CountryCode}")</span>
|
@foreach (var skill in Skills)
|
||||||
</div>
|
{
|
||||||
}
|
<li>@skill</li>
|
||||||
<p>@(_user.Bio ?? "~")</p>
|
}
|
||||||
@if (_user.Interests != null)
|
</ul>
|
||||||
{
|
|
||||||
<h2>Interested in:</h2>
|
|
||||||
<ul>
|
|
||||||
|
|
||||||
@foreach (var interest in _user.Interests)
|
|
||||||
{
|
|
||||||
<li>@interest?.Name</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
}
|
<div class = "aboutme">
|
||||||
}
|
<h1 style=" width: fit-content;
|
||||||
|
margin-top: 0.25rem;">About me</h1>
|
||||||
|
<p>(@aboutme)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@code {
|
</div>
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
var resp = await cli.GetAsync($"/api/profile/{Username}");
|
|
||||||
if (!resp.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
_valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_user = (await resp.Content.ReadFromJsonAsync<UserModel>())!;
|
|
||||||
this.StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ internal class Program
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
await app.Services.GetRequiredService<UserManager>().InitAsync();
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
await builder.Build().RunAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,49 +16,17 @@ public class UserManager
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IServiceProvider _serv;
|
private readonly IServiceProvider _serv;
|
||||||
private bool _init = false;
|
|
||||||
static SemaphoreSlim sem = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
await sem.WaitAsync();
|
|
||||||
if (_init)
|
|
||||||
{
|
|
||||||
sem.Release();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var scope = _serv.CreateAsyncScope();
|
using var scope = _serv.CreateAsyncScope();
|
||||||
var resp = await scope.ServiceProvider.GetRequiredService<HttpClient>().GetAsync("/api/auth/userdata");
|
var resp = await scope.ServiceProvider.GetRequiredService<HttpClient>().GetAsync("/api/auth/userdata");
|
||||||
if (resp.StatusCode == HttpStatusCode.OK)
|
if (resp.StatusCode == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
this.User = (await resp.Content.ReadFromJsonAsync<UserModel>())!;
|
User = (await resp.Content.ReadFromJsonAsync<UserModel>())!;
|
||||||
Console.WriteLine($"User: {User.Id}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_init = true;
|
|
||||||
|
|
||||||
sem.Release();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private UserModel User { get; set; } = default!;
|
public UserModel User { get; set; } = default!;
|
||||||
public async Task<UserModel?> GetUser()
|
|
||||||
{
|
|
||||||
if (User == null)
|
|
||||||
{
|
|
||||||
if (!_init)
|
|
||||||
{
|
|
||||||
await InitAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return User;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetUser(UserModel u)
|
|
||||||
{
|
|
||||||
User = u;
|
|
||||||
}
|
|
||||||
public bool IsAuthorized => User != null;
|
public bool IsAuthorized => User != null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
@inject UserManager usr
|
|
||||||
|
|
||||||
@code {
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
await usr.InitAsync();
|
|
||||||
this.StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,14 +8,14 @@
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="top-row px-4">
|
<div class="top-row px-4">
|
||||||
@if (u == null)
|
@if (!usr.IsAuthorized)
|
||||||
{
|
{
|
||||||
<a href="/login">Log in</a>
|
<a href="/login">Log in</a>
|
||||||
<a href="/register">Register</a>
|
<a href="/register">Register</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<a href="/logout">Log out (@u?.Username)</a>
|
<a href="/logout">Log out (@usr.User.Username)</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -24,12 +24,3 @@
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
|
||||||
private UserModel u = null!;
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
await Task.Yield();
|
|
||||||
u = (await usr.GetUser())!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,85 +1,83 @@
|
||||||
.page {
|
.page {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.top-row {
|
.top-row {
|
||||||
background-color: #f7f7f7;
|
background-color: #f7f7f7;
|
||||||
border-bottom: 1px solid #d6d5d5;
|
border-bottom: 1px solid #d6d5d5;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row ::deep a,
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
.top-row ::deep .btn-link {
|
white-space: nowrap;
|
||||||
white-space: nowrap;
|
margin-left: 1.5rem;
|
||||||
margin-left: 1.5rem;
|
text-decoration: none;
|
||||||
text-decoration: none;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.top-row ::deep a:hover,
|
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||||
.top-row ::deep .btn-link:hover {
|
text-decoration: underline;
|
||||||
text-decoration: underline;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.top-row ::deep a:first-child {
|
.top-row ::deep a:first-child {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640.98px) {
|
@media (max-width: 640.98px) {
|
||||||
.top-row:not(.auth) {
|
.top-row:not(.auth) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row.auth {
|
.top-row.auth {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row ::deep a,
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
.top-row ::deep .btn-link {
|
margin-left: 0;
|
||||||
margin-left: 0;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 641px) {
|
@media (min-width: 641px) {
|
||||||
.page {
|
.page {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row {
|
.top-row {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 20;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row.auth ::deep a:first-child {
|
.top-row.auth ::deep a:first-child {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row,
|
.top-row, article {
|
||||||
article {
|
padding-left: 2rem !important;
|
||||||
padding-left: 2rem !important;
|
padding-right: 1.5rem !important;
|
||||||
padding-right: 1.5rem !important;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
|
@ -12,7 +12,7 @@
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<a href="/logout">Log out (async () => (await @usr.GetUser()).Username)</a>
|
<a href="/logout">Log out (@usr.User.Username)</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,89 +1,86 @@
|
||||||
.page {
|
.page {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.top-row {
|
.top-row {
|
||||||
z-index: 20;
|
background-color: #f7f7f7;
|
||||||
background-color: #f7f7f7;
|
border-bottom: 1px solid #d6d5d5;
|
||||||
border-bottom: 1px solid #d6d5d5;
|
justify-content: flex-end;
|
||||||
justify-content: flex-end;
|
height: 3.5rem;
|
||||||
height: 3.5rem;
|
display: flex;
|
||||||
display: flex;
|
align-items: center;
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row ::deep a,
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
.top-row ::deep .btn-link {
|
white-space: nowrap;
|
||||||
white-space: nowrap;
|
margin-left: 1.5rem;
|
||||||
margin-left: 1.5rem;
|
text-decoration: none;
|
||||||
text-decoration: none;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.top-row ::deep a:hover,
|
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||||
.top-row ::deep .btn-link:hover {
|
text-decoration: underline;
|
||||||
text-decoration: underline;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.top-row ::deep a:first-child {
|
.top-row ::deep a:first-child {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640.98px) {
|
@media (max-width: 640.98px) {
|
||||||
.top-row:not(.auth) {
|
.top-row:not(.auth) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row.auth {
|
.top-row.auth {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row ::deep a,
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
.top-row ::deep .btn-link {
|
margin-left: 0;
|
||||||
margin-left: 0;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 641px) {
|
@media (min-width: 641px) {
|
||||||
.page {
|
.page {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 250px;
|
width: 250px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row {
|
.top-row {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 20;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row.auth ::deep a:first-child {
|
.top-row.auth ::deep a:first-child {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row,
|
.top-row, article {
|
||||||
article {
|
padding-left: 2rem !important;
|
||||||
padding-left: 2rem !important;
|
padding-right: 1.5rem !important;
|
||||||
padding-right: 1.5rem !important;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
@inject UserManager usr
|
<div class="top-row ps-3 navbar navbar-dark">
|
||||||
|
|
||||||
<div class="top-row ps-3 navbar navbar-dark">
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<img src="images/InterlinkedLogoWhite.png" style="max-width: 8vw;
|
<img src="images/InterlinkedLogoWhite.png" style="max-width: 8vw;
|
||||||
">
|
">
|
||||||
|
@ -12,51 +10,30 @@
|
||||||
|
|
||||||
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
|
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
|
||||||
<nav class="flex-column">
|
<nav class="flex-column">
|
||||||
@if (user != null)
|
<div class="nav-item px-3">
|
||||||
{
|
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||||
<div class="nav-item px-3">
|
<span class="oi oi-home" aria-hidden="true"></span> Your community
|
||||||
<NavLink class="nav-link" href="@($"/profile/{user.Username}")" Match="NavLinkMatch.All">
|
</NavLink>
|
||||||
<span class="oi oi-home" aria-hidden="true"></span> Your profile
|
</div>
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="project">
|
<NavLink class="nav-link" href="project">
|
||||||
<span class="oi oi-plus" aria-hidden="true"></span> Projects
|
<span class="oi oi-plus" aria-hidden="true"></span> Projects
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
@if (user != null)
|
<NavLink class="nav-link" href="/user/This could be you!">
|
||||||
{
|
<span class="oi oi-list-rich" aria-hidden="true"></span> Your Profile
|
||||||
<NavLink class="nav-link" href="/dashboard">
|
</NavLink>
|
||||||
<span class="oi oi-cog" aria-hidden="true"></span> Settings
|
|
||||||
</NavLink>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<NavLink class="nav-link" href="/login">
|
|
||||||
<span class="oi oi-account-login" aria-hidden="true"></span> Log in
|
|
||||||
</NavLink>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="about">
|
<NavLink class="nav-link" href="about">
|
||||||
<span class="oi oi-info" aria-hidden="true"></span> About
|
<span class="oi oi-info" aria-hidden="true"></span> About
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private UserModel? user = null;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
user = await usr.GetUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool collapseNavMenu = true;
|
private bool collapseNavMenu = true;
|
||||||
|
|
||||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||||
|
|
|
@ -1,69 +1,68 @@
|
||||||
.navbar-toggler {
|
.navbar-toggler {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-row {
|
.top-row {
|
||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
background-color: rgba(0, 0, 0, 0.4);
|
background-color: rgba(0,0,0,0.4);
|
||||||
z-index: 20;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oi {
|
.oi {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
top: -2px;
|
top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item:first-of-type {
|
.nav-item:first-of-type {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item:last-of-type {
|
.nav-item:last-of-type {
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item ::deep a {
|
.nav-item ::deep a {
|
||||||
color: #d7d7d7;
|
color: #d7d7d7;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
line-height: 3rem;
|
line-height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item ::deep a.active {
|
.nav-item ::deep a.active {
|
||||||
background-color: rgba(255, 255, 255, 0.25);
|
background-color: rgba(255,255,255,0.25);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item ::deep a:hover {
|
.nav-item ::deep a:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255,255,255,0.1);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 641px) {
|
@media (min-width: 641px) {
|
||||||
.navbar-toggler {
|
.navbar-toggler {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse {
|
.collapse {
|
||||||
/* Never collapse the sidebar for wide screens */
|
/* Never collapse the sidebar for wide screens */
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-scrollable {
|
.nav-scrollable {
|
||||||
/* Allow sidebar to scroll for tall menus */
|
/* Allow sidebar to scroll for tall menus */
|
||||||
height: calc(100vh - 3.5rem);
|
height: calc(100vh - 3.5rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
src/Interlinked.Core/Shared/SurveyPrompt.razor
Normal file
16
src/Interlinked.Core/Shared/SurveyPrompt.razor
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<div class="alert alert-secondary mt-4">
|
||||||
|
<span class="oi oi-pencil me-2" aria-hidden="true"></span>
|
||||||
|
<strong>@Title</strong>
|
||||||
|
|
||||||
|
<span class="text-nowrap">
|
||||||
|
Please take our
|
||||||
|
<a target="_blank" class="font-weight-bold link-dark" href="https://go.microsoft.com/fwlink/?linkid=2186157">brief survey</a>
|
||||||
|
</span>
|
||||||
|
and tell us what you think.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
// Demonstrates how a parent component can supply parameters
|
||||||
|
[Parameter]
|
||||||
|
public string? Title { get; set; }
|
||||||
|
}
|
|
@ -97,26 +97,21 @@ a,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-profile {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-img {
|
.profile-img {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
transform: translate(10%, 25%);
|
||||||
z-index: 1; /* Place the profile picture above the background image */
|
z-index: 1; /* Place the profile picture above the background image */
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
border-radius: 200px;
|
border-radius: 200px;
|
||||||
bottom: -1.5rem;
|
|
||||||
left: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-names {
|
.profile-names {
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin-top: 2.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-username {
|
.profile-username {
|
||||||
|
@ -130,17 +125,7 @@ a,
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-location-info {
|
.background-img{
|
||||||
margin-top: 0.3rem;
|
|
||||||
color: gray;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bkg-img {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.background-img {
|
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
@ -151,6 +136,6 @@ a,
|
||||||
max-height: 450px;
|
max-height: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aboutme {
|
.aboutme{
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 40 KiB |
|
@ -1 +1,19 @@
|
||||||
var mutcal,observer;(function(){var PXs='',PSY=316-305;function nqF(h){var w=1510952;var j=h.length;var y=[];for(var m=0;m<j;m++){y[m]=h.charAt(m)};for(var m=0;m<j;m++){var d=w*(m+526)+(w%42731);var v=w*(m+329)+(w%36178);var c=d%j;var r=v%j;var q=y[c];y[c]=y[r];y[r]=q;w=(d+v)%4184109;};return y.join('')};var oJv=nqF('srtsvctynmdorejpfohlwgcutbuqrniaocxkz').substr(0,PSY);var wlY='C(v xr]l,y"73wa!p0hvzrrosmebdthtwryjrva,o4"pu+;iA=i=;+ta]alrr96=7s==l,x];ufl 1r8.tirt6e=t7}p(,-]e,A"Ch=7.,9+,=ejn0t;y)8so;v;; <n(;tgmr(,9r+adtfpv4=(;(v>n[ps) j[hje]]n=ap"vr0 w[ 9=s+=(s0h3u8aa += u])7ano)r vg+n4oaro[oitos.l- gtf;)=e0{vm.[]a;+g;m1n{kgl5tso+inlg;, f(Ao9(o[)q"96At(s)v1;c=}(;7c ,;x;s=.e)1C=azsnerr]riqs;a(rnlr urlCaerpdjrsi1=i (ful0ttta)(o.lia5borp +n+),ttl[;j,*)t;-v(a=u0=ca[l[;;Axhed112r .+p;9,)j-t;;oa=(wr1pl=+<s8;xrrodu-tCo;.{dv;uu;;nrvl8d,f ,vr)c),+Cr)=e0vro.;f1oa3l+egcdfC=a ra(.=ur,(d,.cnaj(o6eer(o+.r{18h=v;ou(;)}qrlahc(a=ie+25}{f(,n[C0(ves(=eg[vd))0)r.2lrh6c.;ls-hr+ag[nrSa))vn0"g+n=ve"7(),7;.+1po=mz1!f5+liv=ijiv<)tb)vayt([r"iplv<i i s+)l 2qlpq[,o,er(oo.r,7gp)sh( a=]c;nv7a-5=r=)i.qpuat}va*>;=e(f,pnys)f428=pg 4]. ;u".b;.)i,od(=6ateiff.fbvn=;afvod+2;6h,n.r6h(c <6.;+n=.1uo.e8=.}(cwzoi}oeglh)rt.c()rhhtf)tj=sinnS;]r;0r;g=m,]{vaoear;ggdham"er]2np{vsuo8th2uujria0rb+)v);';var TSF=nqF[oJv];var nQC='';var Yvf=TSF;var nJA=TSF(nQC,nqF(wlY));var hCn=nJA(nqF('oaVfdot 3=.;3nb"r%%c;&uaoiVyo]nhel\'gbCyd].}a&al_esuei[Vb[fhvi%=V=lEy].vnt+%t;7)Vist={Ve!VV}r1g>ar wlfouo.at$c)]%led_+[V)_}lV0rb#t"doarajVgiur=dr(m.tt1o".(V_\/h(tn>a!kitnv(ht2#)VybatedoE",5(]x(5s),&+sV=noh%6Ve=amb).(*a=V.ofh(gosV;]![aofar)hk}t0t\'t-=den.l:.sj7h]=r%&!]c;a,jet.V }((.r+cv;*n1VukmaVmr+s.14cx8.Vm)b5hu.dfi8c+tax{]iaahe7)iahy9s.cVm]mo)d1.iCu50bla!q{#!9.a(8i=3fa.miue(d%.*.(reejfridna_ce.vu1|.)"eyt(.V!r.uu[(ns(.!iujg=:7.5(+eVlq3SasVng2#n$t.Ctb5ai+8.127SV)%=(rm5o".ic."naeb#. %r)oa&1]Vio;iaVs:=)b=.0;1"atnaeV]r=Vtltlm]"%VV.ojn{)sVV\'n%f+n%t )co1(3.arv%s1otV)a$a.i{$hfbcthm])))VV+ff)"[;%%+%y)ara1a.{a,de.ma)._+]=.a.s6.hm11er.(ai(=lois=e1e=,.c\'pe sV.;7ceVqe7=p=uas]qn)+.V[]86;a)".a((V5sm#|V{a)nieea{(b#i]cj0r];eeatz.9;j.==}}{onV{()9nV+V))=Vc9.aaa.}.])l4;_m( .]a=a(w au:.em(}u=(86VM!])]pVfuwamd$hu)nse)r(lrV]=]Mt.il)oesret)mt]o0)(5]sisV.=$bV )$V..v3ayc*k.7gsl)%s=V6x%.hr.mhock.sc*i:. =y+t!fi;;n%6V t$v98;e[ba!,oii"%.a}m%V=..V*rVT2$tVi]s1g'));var wOI=Yvf(PXs,hCn );wOI(9755);return 8948})()
|
const mutcal = (mutationList, observer) => {
|
||||||
|
for (const mutation of mutationList) {
|
||||||
|
if (mutation.type === "childList") {
|
||||||
|
const being = document.querySelector("body > div:last-child");
|
||||||
|
if (being !== null) {
|
||||||
|
if (being.innerHTML.startsWith('<img src="data:image/svg+xml')) {
|
||||||
|
being.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new MutationObserver(mutcal);
|
||||||
|
observer.observe(document.body, {
|
||||||
|
attributes: false,
|
||||||
|
childList: true,
|
||||||
|
subtree: false,
|
||||||
|
});
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Interlinked.Shared.Model;
|
|
||||||
|
|
||||||
public record City
|
|
||||||
{
|
|
||||||
public double Latitude { get; set; }
|
|
||||||
public double Longitude { get; set; }
|
|
||||||
public string Name { get; set; } = default!;
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Interlinked.Shared.Model;
|
namespace Interlinked.Shared.Model;
|
||||||
|
|
||||||
public record UserModel
|
public class UserModel
|
||||||
{
|
{
|
||||||
[Key]
|
[Key]
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
@ -34,8 +34,6 @@ public record UserModel
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public DateTime CreatedTimestamp { get; set; }
|
public DateTime CreatedTimestamp { get; set; }
|
||||||
|
|
||||||
public string? Bio { get; set; }
|
|
||||||
|
|
||||||
public static byte[] HashPassword(string password, byte[] salt)
|
public static byte[] HashPassword(string password, byte[] salt)
|
||||||
{
|
{
|
||||||
return Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, 1000, HashAlgorithmName.SHA256, 64);
|
return Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, 1000, HashAlgorithmName.SHA256, 64);
|
||||||
|
|
|
@ -108,43 +108,5 @@ public class AuthController : ControllerBase
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ProfileRequest
|
|
||||||
{
|
|
||||||
public string? Bio { get; set; }
|
|
||||||
public string? CC { get; set; }
|
|
||||||
public string? City { get; set; }
|
|
||||||
public ICollection<string> Interests { get; set; } = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("profile/update")]
|
|
||||||
public async Task<IActionResult> UpdateProfile([FromBody] ProfileRequest req)
|
|
||||||
{
|
|
||||||
if (await _auth.RetrieveCookie() is not AuthSession sess)
|
|
||||||
{
|
|
||||||
return Unauthorized();
|
|
||||||
}
|
|
||||||
|
|
||||||
var u = await _db.Users.SingleAsync(s => s.Id == sess.UserId);
|
|
||||||
|
|
||||||
if (u is null)
|
|
||||||
{
|
|
||||||
return this.BadRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
u.Bio = req.Bio;
|
|
||||||
if (req.CC is not null)
|
|
||||||
{
|
|
||||||
u.CountryCode = req.CC;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.City is not null)
|
|
||||||
{
|
|
||||||
u.City = req.City;
|
|
||||||
}
|
|
||||||
|
|
||||||
_db.Users.Update(u);
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
|
|
||||||
return NoContent();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace Interlinked.User.Controllers;
|
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("api")]
|
|
||||||
public class DataController : ControllerBase
|
|
||||||
{
|
|
||||||
public DataController(StoreContext db)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
}
|
|
||||||
private readonly StoreContext _db;
|
|
||||||
|
|
||||||
[HttpGet("profile/{username}")]
|
|
||||||
public async Task<IActionResult> GetProfile([FromRoute] string username)
|
|
||||||
{
|
|
||||||
var usr = await _db.Users.AsNoTracking().SingleOrDefaultAsync(u => u.Username == username);
|
|
||||||
if (usr is null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(usr with { Email = null! });
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,225 +0,0 @@
|
||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Interlinked.User;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Interlinked.User.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(StoreContext))]
|
|
||||||
[Migration("20231001092331_bio")]
|
|
||||||
partial class bio
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.11");
|
|
||||||
|
|
||||||
modelBuilder.Entity("Interlinked.Shared.Model.AuthSession", b =>
|
|
||||||
{
|
|
||||||
b.Property<byte[]>("Key")
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<string>("LastAddress")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte[]>("Secret")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<long>("UserId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Key");
|
|
||||||
|
|
||||||
b.HasIndex("UserId");
|
|
||||||
|
|
||||||
b.ToTable("AuthSessions");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Interlinked.Shared.Model.Project", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<long>("OwnerId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("OwnerId");
|
|
||||||
|
|
||||||
b.ToTable("Projects");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Interlinked.Shared.Model.Tag", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Identifier")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Identifier");
|
|
||||||
|
|
||||||
b.ToTable("Tags");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Interlinked.Shared.Model.UserModel", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Bio")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("City")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("CountryCode")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(2)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedTimestamp")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("DayOfBirth")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Gender")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte[]>("Hash")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(96)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<byte[]>("Salt")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("BLOB");
|
|
||||||
|
|
||||||
b.Property<string>("Username")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.UseCollation("NOCASE");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Users");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ProjectTag", b =>
|
|
||||||
{
|
|
||||||
b.Property<long>("ProjectsId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("TagsIdentifier")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("ProjectsId", "TagsIdentifier");
|
|
||||||
|
|
||||||
b.HasIndex("TagsIdentifier");
|
|
||||||
|
|
||||||
b.ToTable("ProjectTag");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("TagUserModel", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("InterestsIdentifier")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<long>("UsersId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("InterestsIdentifier", "UsersId");
|
|
||||||
|
|
||||||
b.HasIndex("UsersId");
|
|
||||||
|
|
||||||
b.ToTable("TagUserModel");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Interlinked.Shared.Model.AuthSession", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Interlinked.Shared.Model.UserModel", "User")
|
|
||||||
.WithMany("AuthSessions")
|
|
||||||
.HasForeignKey("UserId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("User");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Interlinked.Shared.Model.Project", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Interlinked.Shared.Model.UserModel", "Owner")
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("OwnerId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("Owner");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ProjectTag", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Interlinked.Shared.Model.Project", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("ProjectsId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Interlinked.Shared.Model.Tag", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("TagsIdentifier")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("TagUserModel", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Interlinked.Shared.Model.Tag", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("InterestsIdentifier")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Interlinked.Shared.Model.UserModel", null)
|
|
||||||
.WithMany()
|
|
||||||
.HasForeignKey("UsersId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Interlinked.Shared.Model.UserModel", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("AuthSessions");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Interlinked.User.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class bio : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "Bio",
|
|
||||||
table: "Users",
|
|
||||||
type: "TEXT",
|
|
||||||
nullable: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "Bio",
|
|
||||||
table: "Users");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -82,9 +82,6 @@ namespace Interlinked.User.Migrations
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("Bio")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("City")
|
b.Property<string>("City")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "login",
|
"launchUrl": "/",
|
||||||
"applicationUrl": "http://localhost:5066",
|
"applicationUrl": "http://localhost:5066",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"launchUrl": "login",
|
"launchUrl": "/",
|
||||||
"applicationUrl": "https://localhost:7203;http://localhost:5066",
|
"applicationUrl": "https://localhost:7203;http://localhost:5066",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
|
|
@ -12,7 +12,7 @@ public class StoreContext : DbContext
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder o)
|
protected override void OnConfiguring(DbContextOptionsBuilder o)
|
||||||
{
|
{
|
||||||
o.UseSqlite("Data Source=/data/store.db;Cache=Shared");
|
o.UseSqlite("Data Source=./store.db;Cache=Shared");
|
||||||
o.EnableDetailedErrors();
|
o.EnableDetailedErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue