This commit is contained in:
Krister Kruille Karhu 2026-01-26 21:41:37 +01:00
commit c6d0bb8dca
36 changed files with 7120 additions and 56 deletions

3
.gitignore vendored
View file

@ -6,6 +6,9 @@
# dotenv files
.env
*www.api.vbytes.se.key
*www.api.vbytes.se.csr
# User-specific files
*.rsuser
*.suo

29
MODELS.md Normal file
View file

@ -0,0 +1,29 @@
# BaseModel
required string Ssn
# Participant
required bool Member
required string FirstName
required string SurName
required string Grade
optional string PhoneNumber
optional string Email
required string GuardianName
required string GuardianPhoneNumber
required string GuardianEmail
required bool IsVisitor
required bool HasApprovedGdpr
optional string Friends
optional string SpecialDiet
# Volunteer
required string FirstName
required string SurName
required string PhoneNumber
required string Email
required bool HasApprovedGdpr
required string AreasOfInterest (Json)
## AreaOfInterest
required List<string> AreaNames

18
docker-compose.yml Normal file
View file

@ -0,0 +1,18 @@
services:
# PostgreSQL Database
# User: postgres
# Password: postgres
# Port: 5432
postgres:
image: postgres:17
restart: always
container_name: local-postgres
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:

View file

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
using Registration.Domain.Models;
namespace Registration.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ParticipantController : ControllerBase
{
[HttpPost("register")]
public IActionResult RegisterForLan([FromBody] Participant participant)
{
return Ok();
}
}
}

View file

@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Mvc;
using Registration.Infra.Repositories;
namespace Registration.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class RegistrationController(IMemberRepository memberRepository) : ControllerBase
{
private readonly IMemberRepository _memberRepository = memberRepository;
[HttpPost("register/{ssn}")]
public async Task<IActionResult> RegisterMember(string ssn)
{
var added = await _memberRepository.AddRegistration(ssn);
if (added)
{
return Ok(new { Message = "Member registered successfully." });
}
else
{
return Conflict(new { Message = "Member is already registered." });
}
}
[HttpGet("registered/{ssn}")]
public async Task<IActionResult> IsMemberRegistered(string ssn)
{
var isRegistered = await _memberRepository.GetIsRegistered(ssn);
return Ok(new { IsRegistered = isRegistered });
}
[HttpDelete("clear")]
public async Task<IActionResult> ClearRegistrations()
{
await _memberRepository.ClearRegistrations();
return Ok(new { Message = "All registrations cleared." });
}
}
}

View file

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
using Registration.Domain.Models;
namespace Registration.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class VolunteerController : ControllerBase
{
[HttpPost("register")]
public IActionResult RegisterVolunteer([FromBody] Volunteer volunteer)
{
return Ok();
}
}
}

View file

@ -1,32 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace RegistrationAPI.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}

View file

@ -1,23 +1,29 @@
using Registration.Infra.Repositories;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<IMemberRepository, MemberRepository>();
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var repo = scope.ServiceProvider.GetRequiredService<IMemberRepository>();
await repo.EnsureCreated();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View file

@ -7,7 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
<ProjectReference Include="../Registration.Infra/Registration.Infra.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.0" />
</ItemGroup>
</Project>

View file

@ -4,5 +4,11 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Username=postgres;Password=postgres;Database=postgres;Port=5432"
},
"Security": {
"SsnPepper": "VBYTES_LAN_2026_SECRET_PEPPER"
}
}

View file

@ -1,4 +1,10 @@
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Username=postgres;Password=postgres;Database=postgres;Port=5432"
},
"Security": {
"SsnPepper": "VBYTES_LAN_2026_SECRET_PEPPER"
},
"Logging": {
"LogLevel": {
"Default": "Information",

View file

@ -1,6 +0,0 @@
namespace Registration.Domain;
public class Class1
{
}

View file

@ -0,0 +1,7 @@
namespace Registration.Domain.Models;
public class AreasOfInterest
{
public required string Name { get; set; }
}

View file

@ -0,0 +1,19 @@
namespace Registration.Domain.Models;
public class Participant
{
public required string FirstName { get; set; }
public required string SurName { get; set; }
public required string Grade { get; set; }
public string? PhoneNumber { get; set; }
public string? Email { get; set; }
public required string GuardianName { get; set; }
public required string GuardianPhoneNumber { get; set; }
public required string GuardianEmail { get; set; }
public required bool IsVisitor { get; set; }
public required bool HasApprovedGdpr { get; set; }
public string? Friends { get; set; }
public string? SpecialDiet { get; set; }
}

View file

@ -0,0 +1,11 @@
namespace Registration.Domain.Models;
public class Volunteer
{
public required string FirstName { get; set; }
public required string SurName { get; set; }
public required string PhoneNumber { get; set; }
public required string Email { get; set; }
public required bool HasApprovedGdpr { get; set; }
public required AreasOfInterest AreasOfInterest { get; set; }
}

View file

@ -1,6 +0,0 @@
namespace Registration.Infra;
public class Class1
{
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
@ -6,4 +6,15 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.1" />
<PackageReference Include="Npgsql" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Registration.Domain\Registration.Domain.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,79 @@
using Microsoft.Extensions.Configuration;
using System.Security.Cryptography;
using System.Text;
using Dapper;
using Npgsql;
namespace Registration.Infra.Repositories;
public interface IMemberRepository
{
Task EnsureCreated();
Task<bool> GetIsRegistered(string ssn);
Task<bool> AddRegistration(string ssn);
Task ClearRegistrations();
}
public class MemberRepository(IConfiguration configuration) : IMemberRepository
{
private readonly string _connectionString = configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
private readonly string _pepper = configuration["Security:SsnPepper"]
?? throw new InvalidOperationException("Security pepper 'SsnPepper' not found.");
private NpgsqlConnection CreateConnection() => new(_connectionString);
private string ComputeBlindIndex(string ssn)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_pepper));
var hashedBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(ssn));
return Convert.ToBase64String(hashedBytes);
}
public async Task EnsureCreated()
{
using var connection = CreateConnection();
var sql = @"
CREATE TABLE IF NOT EXISTS members (
id SERIAL PRIMARY KEY,
ssn_index VARCHAR(255) NOT NULL UNIQUE
);
CREATE INDEX IF NOT EXISTS idx_members_ssn_index ON members(ssn_index);
";
await connection.ExecuteAsync(sql);
}
public async Task<bool> AddRegistration(string ssn)
{
using var connection = CreateConnection();
var sql = "INSERT INTO members (ssn_index) VALUES (@SsnIndex) ON CONFLICT DO NOTHING";
var ssnIndex = ComputeBlindIndex(ssn);
var rows = await connection.ExecuteAsync(sql, new { SsnIndex = ssnIndex });
return rows > 0;
}
public async Task<bool> GetIsRegistered(string ssn)
{
using var connection = CreateConnection();
var ssnIndex = ComputeBlindIndex(ssn);
var sql = "SELECT EXISTS(SELECT 1 FROM members WHERE ssn_index = @SsnIndex)";
return await connection.ExecuteScalarAsync<bool>(sql, new { SsnIndex = ssnIndex });
}
public async Task ClearRegistrations()
{
using var connection = CreateConnection();
var sql = "DELETE FROM members";
await connection.ExecuteAsync(sql);
}
}

41
src/Web/lan-frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View file

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,26 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

View file

@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}

View file

@ -0,0 +1,65 @@
import Image from "next/image";
export default function Home() {
return (
<div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the page.tsx file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={16}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
</div>
);
}

View file

@ -0,0 +1,18 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;

View file

@ -0,0 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;

6538
src/Web/lan-frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
{
"name": "lan-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"next": "16.1.5",
"react": "19.2.3",
"react-dom": "19.2.3"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.5",
"tailwindcss": "^4",
"typescript": "^5"
}
}

View file

@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

View file

@ -0,0 +1 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 391 B

View file

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 128 B

View file

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

After

Width:  |  Height:  |  Size: 385 B

View file

@ -0,0 +1,34 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": ["node_modules"]
}